Allow copying and pasting symbols between QgsSymbolButtons

This commit is contained in:
Nyall Dawson 2017-07-24 14:52:22 +10:00
parent 15f3bbf9c8
commit 78b05c1a7f
19 changed files with 270 additions and 4 deletions

View File

@ -88,7 +88,7 @@ return new default symbol for specified geometry type
:rtype: QgsSymbolLayer
%End
int symbolLayerCount();
int symbolLayerCount() const;
%Docstring
Returns total number of symbol layers contained in the symbol.
:return: count of symbol layers

View File

@ -231,8 +231,10 @@ class QgsSymbolLayer
virtual QgsSymbol *subSymbol();
%Docstring
Returns the symbol's sub symbol, if present.
:rtype: QgsSymbol
%End
virtual bool setSubSymbol( QgsSymbol *symbol /Transfer/ );
%Docstring
set layer's subsymbol. takes ownership of the passed symbol

View File

@ -548,6 +548,25 @@ Writes a collection of symbols to XML with specified tagName for the top-level e
static void clearSymbolMap( QgsSymbolMap &symbols );
static QMimeData *symbolToMimeData( QgsSymbol *symbol ) /Factory/;
%Docstring
Creates new mime data from a ``symbol``.
This also sets the mime color data to match the symbol's color, so that copied symbols
can be paste in places where a color is expected.
.. seealso:: symbolFromMimeData()
.. versionadded:: 3.0
:rtype: QMimeData
%End
static QgsSymbol *symbolFromMimeData( const QMimeData *data ) /Factory/;
%Docstring
Attempts to parse ``mime`` data as a symbol. A new symbol instance will be returned
if the data was successfully converted to a symbol.
.. seealso:: symbolToMimeData()
.. versionadded:: 3.0
:rtype: QgsSymbol
%End
static QgsColorRamp *loadColorRamp( QDomElement &element ) /Factory/;
%Docstring
Creates a color ramp from the settings encoded in an XML element

View File

@ -32,6 +32,21 @@ class QgsSymbolButton : QToolButton
virtual QSize minimumSizeHint() const;
void setSymbolType( QgsSymbol::SymbolType type );
%Docstring
Sets the symbol ``type`` which the button requires.
If the type differs from the current symbol type, the symbol will be reset
to a default symbol style of the new type.
.. seealso:: symbolType()
%End
QgsSymbol::SymbolType symbolType() const;
%Docstring
Returns the symbol type which the button requires.
.. seealso:: setSymbolType()
:rtype: QgsSymbol.SymbolType
%End
void setDialogTitle( const QString &title );
%Docstring
Sets the ``title`` for the symbol settings dialog window.
@ -105,6 +120,19 @@ class QgsSymbolButton : QToolButton
to the previous symbol color.
%End
void copySymbol();
%Docstring
Copies the current symbol to the clipboard.
.. seealso:: pasteSymbol()
%End
void pasteSymbol();
%Docstring
Pastes a symbol from the clipboard. If clipboard does not contain a valid
symbol then no change is applied.
.. seealso:: copySymbol()
%End
void copyColor();
%Docstring
Copies the current symbol color to the clipboard.

View File

@ -45,6 +45,8 @@ QgsComposerShapeWidget::QgsComposerShapeWidget( QgsComposerShape *composerShape
mShapeComboBox->addItem( tr( "Rectangle" ) );
mShapeComboBox->addItem( tr( "Triangle" ) );
mShapeStyleButton->setSymbolType( QgsSymbol::Fill );
setGuiElementValues();
blockAllSignals( false );

View File

@ -33,6 +33,9 @@ QgsAnnotationWidget::QgsAnnotationWidget( QgsMapCanvasAnnotationItem *item, QWid
setupUi( this );
mLayerComboBox->setAllowEmptyLayer( true );
mMapMarkerButton->setSymbolType( QgsSymbol::Marker );
mFrameStyleButton->setSymbolType( QgsSymbol::Fill );
if ( mItem && mItem->annotation() )
{
QgsAnnotation *annotation = mItem->annotation();

View File

@ -34,6 +34,9 @@ QgsDecorationGridDialog::QgsDecorationGridDialog( QgsDecorationGrid &deco, QWidg
{
setupUi( this );
mMarkerSymbolButton->setSymbolType( QgsSymbol::Marker );
mLineSymbolButton->setSymbolType( QgsSymbol::Line );
mAnnotationFontButton->setMode( QgsFontButton::ModeQFont );
QgsSettings settings;
@ -65,6 +68,7 @@ QgsDecorationGridDialog::QgsDecorationGridDialog( QgsDecorationGrid &deco, QWidg
connect( mAnnotationFontButton, &QgsFontButton::changed, this, &QgsDecorationGridDialog::annotationFontChanged );
mMarkerSymbolButton->setMapCanvas( QgisApp::instance()->mapCanvas() );
mLineSymbolButton->setMapCanvas( QgisApp::instance()->mapCanvas() );
}
void QgsDecorationGridDialog::updateGuiElements()

View File

@ -35,6 +35,8 @@ QgsDecorationLayoutExtentDialog::QgsDecorationLayoutExtentDialog( QgsDecorationL
{
setupUi( this );
mSymbolButton->setSymbolType( QgsSymbol::Fill );
QgsSettings settings;
restoreGeometry( settings.value( "/Windows/DecorationLayoutExtent/geometry" ).toByteArray() );

View File

@ -136,7 +136,7 @@ class CORE_EXPORT QgsSymbol
* \see symbolLayers
* \see symbolLayer
*/
int symbolLayerCount() { return mLayers.count(); }
int symbolLayerCount() const { return mLayers.count(); }
/**
* Insert symbol layer to specified index

View File

@ -252,7 +252,11 @@ class CORE_EXPORT QgsSymbolLayer
virtual void drawPreviewIcon( QgsSymbolRenderContext &context, QSize size ) = 0;
/**
* Returns the symbol's sub symbol, if present.
*/
virtual QgsSymbol *subSymbol() { return nullptr; }
//! set layer's subsymbol. takes ownership of the passed symbol
virtual bool setSubSymbol( QgsSymbol *symbol SIP_TRANSFER ) { delete symbol; return false; }

View File

@ -2809,6 +2809,48 @@ void QgsSymbolLayerUtils::clearSymbolMap( QgsSymbolMap &symbols )
symbols.clear();
}
QMimeData *QgsSymbolLayerUtils::symbolToMimeData( QgsSymbol *symbol )
{
if ( !symbol )
return nullptr;
std::unique_ptr< QMimeData >mimeData( new QMimeData );
QDomDocument symbolDoc;
QDomElement symbolElem = saveSymbol( QStringLiteral( "symbol" ), symbol, symbolDoc, QgsReadWriteContext() );
symbolDoc.appendChild( symbolElem );
mimeData->setText( symbolDoc.toString() );
mimeData->setImageData( symbolPreviewPixmap( symbol, QSize( 100, 100 ), 18 ).toImage() );
mimeData->setColorData( symbol->color() );
return mimeData.release();
}
QgsSymbol *QgsSymbolLayerUtils::symbolFromMimeData( const QMimeData *data )
{
if ( !data )
return nullptr;
QString text = data->text();
if ( !text.isEmpty() )
{
QDomDocument doc;
QDomElement elem;
if ( doc.setContent( text ) )
{
elem = doc.documentElement();
if ( elem.nodeName() != QStringLiteral( "symbol" ) )
elem = elem.firstChildElement( QStringLiteral( "symbol" ) );
return loadSymbol( elem, QgsReadWriteContext() );
}
}
return nullptr;
}
QgsColorRamp *QgsSymbolLayerUtils::loadColorRamp( QDomElement &element )
{

View File

@ -365,6 +365,23 @@ class CORE_EXPORT QgsSymbolLayerUtils
static void clearSymbolMap( QgsSymbolMap &symbols );
/**
* Creates new mime data from a \a symbol.
* This also sets the mime color data to match the symbol's color, so that copied symbols
* can be paste in places where a color is expected.
* \see symbolFromMimeData()
* \since QGIS 3.0
*/
static QMimeData *symbolToMimeData( QgsSymbol *symbol ) SIP_FACTORY;
/**
* Attempts to parse \a mime data as a symbol. A new symbol instance will be returned
* if the data was successfully converted to a symbol.
* \see symbolToMimeData()
* \since QGIS 3.0
*/
static QgsSymbol *symbolFromMimeData( const QMimeData *data ) SIP_FACTORY;
/** Creates a color ramp from the settings encoded in an XML element
* \param element DOM element
* \returns new color ramp. Caller takes responsibility for deleting the returned value.

View File

@ -47,6 +47,7 @@ QgsFieldConditionalFormatWidget::QgsFieldConditionalFormatWidget( QWidget *paren
mModel = new QStandardItemModel( listView );
listView->setModel( mModel );
mPresetsList->setModel( mPresetsModel );
btnChangeIcon->setSymbolType( QgsSymbol::Marker );
btnChangeIcon->setSymbol( QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry ) );
setPresets( defaultPresets() );

View File

@ -52,6 +52,29 @@ QSize QgsSymbolButton::minimumSizeHint() const
return QSize( size.width(), qMax( size.height(), fontHeight ) );
}
void QgsSymbolButton::setSymbolType( QgsSymbol::SymbolType type )
{
if ( type != mType )
{
switch ( type )
{
case QgsSymbol::Marker:
mSymbol.reset( QgsMarkerSymbol::createSimple( QgsStringMap() ) );
break;
case QgsSymbol::Line:
mSymbol.reset( QgsLineSymbol::createSimple( QgsStringMap() ) );
break;
case QgsSymbol::Fill:
mSymbol.reset( QgsFillSymbol::createSimple( QgsStringMap() ) );
break;
}
}
updatePreview();
mType = type;
}
void QgsSymbolButton::showSettingsDialog()
{
QgsExpressionContext context;
@ -155,6 +178,18 @@ void QgsSymbolButton::setColor( const QColor &color )
emit changed();
}
void QgsSymbolButton::copySymbol()
{
QApplication::clipboard()->setMimeData( QgsSymbolLayerUtils::symbolToMimeData( mSymbol.get() ) );
}
void QgsSymbolButton::pasteSymbol()
{
std::unique_ptr< QgsSymbol > symbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
if ( symbol && symbol->type() == mType )
setSymbol( symbol.release() );
}
void QgsSymbolButton::copyColor()
{
QApplication::clipboard()->setMimeData( QgsSymbolLayerUtils::colorToMimeData( mSymbol->color() ) );
@ -265,6 +300,24 @@ void QgsSymbolButton::prepareMenu()
mMenu->addAction( configureAction );
connect( configureAction, &QAction::triggered, this, &QgsSymbolButton::showSettingsDialog );
QAction *copySymbolAction = new QAction( tr( "Copy symbol" ), this );
mMenu->addAction( copySymbolAction );
connect( copySymbolAction, &QAction::triggered, this, &QgsSymbolButton::copySymbol );
QAction *pasteSymbolAction = new QAction( tr( "Paste symbol" ), this );
//enable or disable paste action based on current clipboard contents. We always show the paste
//action, even if it's disabled, to give hint to the user that pasting symbols is possible
std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
if ( tempSymbol && tempSymbol->type() == mType )
{
pasteSymbolAction->setIcon( QgsSymbolLayerUtils::symbolPreviewIcon( tempSymbol.get(), QSize( 16, 16 ), 1 ) );
}
else
{
pasteSymbolAction->setEnabled( false );
}
mMenu->addAction( pasteSymbolAction );
connect( pasteSymbolAction, &QAction::triggered, this, &QgsSymbolButton::pasteSymbol );
mMenu->addSeparator();
QgsColorWheel *colorWheel = new QgsColorWheel( mMenu );

View File

@ -53,6 +53,20 @@ class GUI_EXPORT QgsSymbolButton : public QToolButton
virtual QSize minimumSizeHint() const override;
/**
* Sets the symbol \a type which the button requires.
* If the type differs from the current symbol type, the symbol will be reset
* to a default symbol style of the new type.
* \see symbolType()
*/
void setSymbolType( QgsSymbol::SymbolType type );
/**
* Returns the symbol type which the button requires.
* \see setSymbolType()
*/
QgsSymbol::SymbolType symbolType() const { return mType; }
/**
* Sets the \a title for the symbol settings dialog window.
* \see dialogTitle()
@ -143,6 +157,18 @@ class GUI_EXPORT QgsSymbolButton : public QToolButton
*/
void setColor( const QColor &color );
/** Copies the current symbol to the clipboard.
* \see pasteSymbol()
*/
void copySymbol();
/**
* Pastes a symbol from the clipboard. If clipboard does not contain a valid
* symbol then no change is applied.
* \see copySymbol()
*/
void pasteSymbol();
/**
* Copies the current symbol color to the clipboard.
* \see pasteColor()
@ -200,6 +226,8 @@ class GUI_EXPORT QgsSymbolButton : public QToolButton
QString mDialogTitle;
QgsSymbol::SymbolType mType = QgsSymbol::Fill;
QgsMapCanvas *mMapCanvas = nullptr;
QPoint mDragStartPosition;

View File

@ -53,6 +53,8 @@ QgsPointClusterRendererWidget::QgsPointClusterRendererWidget( QgsVectorLayer *la
mDistanceUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << QgsUnitTypes::RenderMillimeters << QgsUnitTypes::RenderMapUnits << QgsUnitTypes::RenderPixels
<< QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches );
mCenterSymbolToolButton->setSymbolType( QgsSymbol::Marker );
if ( renderer )
{
mRenderer = QgsPointClusterRenderer::convertFromRenderer( renderer );

View File

@ -53,6 +53,7 @@ QgsPointDisplacementRendererWidget::QgsPointDisplacementRendererWidget( QgsVecto
mLabelFontButton->setMode( QgsFontButton::ModeQFont );
mDistanceUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << QgsUnitTypes::RenderMillimeters << QgsUnitTypes::RenderMetersInMapUnits << QgsUnitTypes::RenderMapUnits << QgsUnitTypes::RenderPixels
<< QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches );
mCenterSymbolToolButton->setSymbolType( QgsSymbol::Marker );
if ( renderer )
{

View File

@ -14,7 +14,7 @@ __revision__ = '$Format:%H$'
import qgis # NOQA
from qgis.core import QgsFillSymbol, QgsMarkerSymbol
from qgis.core import QgsFillSymbol, QgsMarkerSymbol, QgsSymbol
from qgis.gui import QgsSymbolButton, QgsMapCanvas
from qgis.testing import start_app, unittest
from qgis.PyQt.QtGui import QColor, QFont
@ -36,6 +36,50 @@ class TestQgsSymbolButton(unittest.TestCase):
button.setMapCanvas(canvas)
self.assertEqual(button.mapCanvas(), canvas)
button.setSymbolType(QgsSymbol.Line)
self.assertEqual(button.symbolType(), QgsSymbol.Line)
def testSettingSymbolType(self)
button = QgsSymbolButton()
button.setSymbolType(QgsSymbol.Marker)
symbol = QgsMarkerSymbol.createSimple({})
symbol.setColor(QColor(255, 0, 0))
button.setSymbol(symbol)
# if same symbol type, existing symbol should be kept
button.setSymbolType(QgsSymbol.Marker)
self.assertEqual(button.symbol(), symbol)
# if setting different symbol type, symbol should be reset to new type
button.setSymbolType(QgsSymbol.Fill)
self.assertTrue(isinstance(button.symbol(), QgsFillSymbol))
def testPasteSymbol(self):
button = QgsSymbolButton()
button.setSymbolType(QgsSymbol.Marker)
symbol = QgsMarkerSymbol.createSimple({})
symbol.setColor(QColor(255, 0, 0))
button.setSymbol(symbol)
button2 = QgsSymbolButton()
button2.setSymbolType(QgsSymbol.Marker)
symbol2 = QgsMarkerSymbol.createSimple({})
symbol2.setColor(QColor(0, 255, 0))
button2.setSymbol(symbol2)
button.copySymbol()
button2.pasteSymbol()
self.assertEqual(button2.symbol().color(), QColor(255, 0, 0))
# try pasting incompatible symbol
button2.setSymbolType(QgsSymbol.Fill)
fill_symbol = QgsFillSymbol.createSimple({})
fill_symbol.setColor(QColor(0,0,255))
button2.setSymbol(fill_symbol)
button.copySymbol() # copied a marker symbol
button2.pasteSymbol() # should have no effect
self.assertEqual(button2.symbol(), fill_symbol)
def testSetGetSymbol(self):
button = QgsSymbolButton()
symbol = QgsMarkerSymbol.createSimple({})

View File

@ -14,7 +14,7 @@ __revision__ = '$Format:%H$'
import qgis # NOQA
from qgis.core import QgsSymbolLayerUtils
from qgis.core import QgsSymbolLayerUtils, QgsMarkerSymbol
from qgis.PyQt.QtCore import QSizeF, QPointF
from qgis.testing import unittest
@ -49,6 +49,20 @@ class PyQgsSymbolLayerUtils(unittest.TestCase):
s2 = QgsSymbolLayerUtils.decodePoint('')
self.assertEqual(s2, QPointF())
def testSymbolToFromMimeData(self):
"""
Test converting symbols to and from mime data
"""
symbol = QgsMarkerSymbol.createSimple({})
symbol.setColor(QColor(255, 0, 255))
self.assertFalse(QgsSymbolLayerUtils.symbolFromMimeData(None))
self.assertFalse(QgsSymbolLayerUtils.symbolToMimeData(None))
mime = QgsSymbolLayerUtils.symbolToMimeData(symbol)
self.assertTrue(mime is not None)
symbol2 = QgsSymbolLayerUtils.symbolFromMimeData(mime)
self.assertTrue(symbol2 is not None)
self.assertEqual(symbol2.color().name(), symbol.color().name())
if __name__ == '__main__':
unittest.main()