QGIS API Documentation  3.2.0-Bonn (bc43194)
qgssymbolbutton.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgssymbolbutton.h
3  -----------------
4  Date : July 2017
5  Copyright : (C) 2017 by Nyall Dawson
6  Email : nyall dot dawson at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include "qgssymbolbutton.h"
17 #include "qgspanelwidget.h"
18 #include "qgsexpressioncontext.h"
20 #include "qgsvectorlayer.h"
22 #include "qgsstyle.h"
23 #include "qgscolorwidgets.h"
24 #include "qgscolorschemeregistry.h"
25 #include "qgscolorswatchgrid.h"
26 #include "qgssymbollayerutils.h"
27 #include <QMenu>
28 #include <QClipboard>
29 #include <QDrag>
30 
31 QgsSymbolButton::QgsSymbolButton( QWidget *parent, const QString &dialogTitle )
32  : QToolButton( parent )
33  , mDialogTitle( dialogTitle.isEmpty() ? tr( "Symbol Settings" ) : dialogTitle )
34 {
35  mSymbol.reset( QgsFillSymbol::createSimple( QgsStringMap() ) );
36 
37  setAcceptDrops( true );
38  connect( this, &QAbstractButton::clicked, this, &QgsSymbolButton::showSettingsDialog );
39 
40  //setup dropdown menu
41  mMenu = new QMenu( this );
42  connect( mMenu, &QMenu::aboutToShow, this, &QgsSymbolButton::prepareMenu );
43  setMenu( mMenu );
44  setPopupMode( QToolButton::MenuButtonPopup );
45 
46  //make sure height of button looks good under different platforms
47  QSize size = QToolButton::minimumSizeHint();
48  int fontHeight = Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 1.4;
49  mSizeHint = QSize( size.width(), std::max( size.height(), fontHeight ) );
50 }
51 
53 {
54 
55  return mSizeHint;
56 }
57 
59 {
60  return mSizeHint;
61 }
62 
64 {
65  if ( type != mType )
66  {
67  switch ( type )
68  {
69  case QgsSymbol::Marker:
70  mSymbol.reset( QgsMarkerSymbol::createSimple( QgsStringMap() ) );
71  break;
72 
73  case QgsSymbol::Line:
74  mSymbol.reset( QgsLineSymbol::createSimple( QgsStringMap() ) );
75  break;
76 
77  case QgsSymbol::Fill:
78  mSymbol.reset( QgsFillSymbol::createSimple( QgsStringMap() ) );
79  break;
80 
81  case QgsSymbol::Hybrid:
82  break;
83  }
84  }
85  updatePreview();
86  mType = type;
87 }
88 
89 void QgsSymbolButton::showSettingsDialog()
90 {
91  QgsExpressionContext context;
92  if ( mExpressionContextGenerator )
93  context = mExpressionContextGenerator->createExpressionContext();
94  else
95  {
97  }
98  QgsSymbol *newSymbol = mSymbol->clone();
99 
100  QgsSymbolWidgetContext symbolContext;
101  symbolContext.setExpressionContext( &context );
102  symbolContext.setMapCanvas( mMapCanvas );
103 
105  if ( panel && panel->dockMode() )
106  {
107  QgsSymbolSelectorWidget *d = new QgsSymbolSelectorWidget( newSymbol, QgsStyle::defaultStyle(), mLayer, nullptr );
108  d->setContext( symbolContext );
109  connect( d, &QgsPanelWidget::widgetChanged, this, &QgsSymbolButton::updateSymbolFromWidget );
110  connect( d, &QgsPanelWidget::panelAccepted, this, &QgsSymbolButton::cleanUpSymbolSelector );
111  panel->openPanel( d );
112  }
113  else
114  {
115  QgsSymbolSelectorDialog dialog( newSymbol, QgsStyle::defaultStyle(), mLayer, nullptr );
116  dialog.setWindowTitle( mDialogTitle );
117  dialog.setContext( symbolContext );
118  if ( dialog.exec() )
119  {
120  setSymbol( newSymbol );
121  }
122  else
123  {
124  delete newSymbol;
125  }
126 
127  // reactivate button's window
128  activateWindow();
129  }
130 }
131 
132 void QgsSymbolButton::updateSymbolFromWidget()
133 {
134  if ( QgsSymbolSelectorWidget *w = qobject_cast<QgsSymbolSelectorWidget *>( sender() ) )
135  setSymbol( w->symbol()->clone() );
136 }
137 
138 void QgsSymbolButton::cleanUpSymbolSelector( QgsPanelWidget *container )
139 {
140  QgsSymbolSelectorWidget *w = qobject_cast<QgsSymbolSelectorWidget *>( container );
141  if ( !w )
142  return;
143 
144  delete w->symbol();
145 }
146 
148 {
149  return mMapCanvas;
150 }
151 
153 {
154  mMapCanvas = mapCanvas;
155 }
156 
158 {
159  return mLayer;
160 }
161 
163 {
164  mLayer = layer;
165 }
166 
168 {
169  mExpressionContextGenerator = generator;
170 }
171 
173 {
174  mSymbol.reset( symbol );
175  updatePreview();
176  emit changed();
177 }
178 
179 void QgsSymbolButton::setColor( const QColor &color )
180 {
181  QColor opaque = color;
182  opaque.setAlphaF( 1.0 );
183 
184  if ( opaque == mSymbol->color() )
185  return;
186 
187  mSymbol->setColor( opaque );
188  updatePreview();
189  emit changed();
190 }
191 
193 {
194  QApplication::clipboard()->setMimeData( QgsSymbolLayerUtils::symbolToMimeData( mSymbol.get() ) );
195 }
196 
198 {
199  std::unique_ptr< QgsSymbol > symbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
200  if ( symbol && symbol->type() == mType )
201  setSymbol( symbol.release() );
202 }
203 
205 {
206  QApplication::clipboard()->setMimeData( QgsSymbolLayerUtils::colorToMimeData( mSymbol->color() ) );
207 }
208 
210 {
211  QColor clipColor;
212  bool hasAlpha = false;
213  if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor, hasAlpha ) )
214  {
215  //paste color
216  setColor( clipColor );
218  }
219 }
220 
221 void QgsSymbolButton::mousePressEvent( QMouseEvent *e )
222 {
223  if ( e->button() == Qt::RightButton )
224  {
225  QToolButton::showMenu();
226  return;
227  }
228  else if ( e->button() == Qt::LeftButton )
229  {
230  mDragStartPosition = e->pos();
231  }
232  QToolButton::mousePressEvent( e );
233 }
234 
235 void QgsSymbolButton::mouseMoveEvent( QMouseEvent *e )
236 {
237  //handle dragging colors/symbols from button
238 
239  if ( !( e->buttons() & Qt::LeftButton ) )
240  {
241  //left button not depressed, so not a drag
242  QToolButton::mouseMoveEvent( e );
243  return;
244  }
245 
246  if ( ( e->pos() - mDragStartPosition ).manhattanLength() < QApplication::startDragDistance() )
247  {
248  //mouse not moved, so not a drag
249  QToolButton::mouseMoveEvent( e );
250  return;
251  }
252 
253  //user is dragging
254  QDrag *drag = new QDrag( this );
255  drag->setMimeData( QgsSymbolLayerUtils::colorToMimeData( mSymbol->color() ) );
256  drag->setPixmap( QgsColorWidget::createDragIcon( mSymbol->color() ) );
257  drag->exec( Qt::CopyAction );
258  setDown( false );
259 }
260 
261 void QgsSymbolButton::dragEnterEvent( QDragEnterEvent *e )
262 {
263  //is dragged data valid color data?
264  QColor mimeColor;
265  bool hasAlpha = false;
266 
267  if ( colorFromMimeData( e->mimeData(), mimeColor, hasAlpha ) )
268  {
269  //if so, we accept the drag, and temporarily change the button's color
270  //to match the dragged color. This gives immediate feedback to the user
271  //that colors can be dropped here
272  e->acceptProposedAction();
273  updatePreview( mimeColor );
274  }
275 }
276 
277 void QgsSymbolButton::dragLeaveEvent( QDragLeaveEvent *e )
278 {
279  Q_UNUSED( e );
280  //reset button color
281  updatePreview();
282 }
283 
284 void QgsSymbolButton::dropEvent( QDropEvent *e )
285 {
286  //is dropped data valid format data?
287  QColor mimeColor;
288  bool hasAlpha = false;
289  if ( colorFromMimeData( e->mimeData(), mimeColor, hasAlpha ) )
290  {
291  //accept drop and set new color
292  e->acceptProposedAction();
293  mimeColor.setAlphaF( 1.0 );
294  mSymbol->setColor( mimeColor );
296  updatePreview();
297  emit changed();
298  }
299  updatePreview();
300 }
301 
302 void QgsSymbolButton::prepareMenu()
303 {
304  //we need to tear down and rebuild this menu every time it is shown. Otherwise the space allocated to any
305  //QgsColorSwatchGridAction is not recalculated by Qt and the swatch grid may not be the correct size
306  //for the number of colors shown in the grid. Note that we MUST refresh color swatch grids every time this
307  //menu is opened, otherwise color schemes like the recent color scheme grid are meaningless
308  mMenu->clear();
309 
310  QAction *configureAction = new QAction( tr( "Configure Symbol…" ), this );
311  mMenu->addAction( configureAction );
312  connect( configureAction, &QAction::triggered, this, &QgsSymbolButton::showSettingsDialog );
313 
314  QAction *copySymbolAction = new QAction( tr( "Copy Symbol" ), this );
315  mMenu->addAction( copySymbolAction );
316  connect( copySymbolAction, &QAction::triggered, this, &QgsSymbolButton::copySymbol );
317  QAction *pasteSymbolAction = new QAction( tr( "Paste Symbol" ), this );
318  //enable or disable paste action based on current clipboard contents. We always show the paste
319  //action, even if it's disabled, to give hint to the user that pasting symbols is possible
320  std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
321  if ( tempSymbol && tempSymbol->type() == mType )
322  {
323  pasteSymbolAction->setIcon( QgsSymbolLayerUtils::symbolPreviewIcon( tempSymbol.get(), QSize( 16, 16 ), 1 ) );
324  }
325  else
326  {
327  pasteSymbolAction->setEnabled( false );
328  }
329  mMenu->addAction( pasteSymbolAction );
330  connect( pasteSymbolAction, &QAction::triggered, this, &QgsSymbolButton::pasteSymbol );
331 
332  mMenu->addSeparator();
333 
334  QgsColorWheel *colorWheel = new QgsColorWheel( mMenu );
335  colorWheel->setColor( mSymbol->color() );
336  QgsColorWidgetAction *colorAction = new QgsColorWidgetAction( colorWheel, mMenu, mMenu );
337  colorAction->setDismissOnColorSelection( false );
338  connect( colorAction, &QgsColorWidgetAction::colorChanged, this, &QgsSymbolButton::setColor );
339  mMenu->addAction( colorAction );
340 
342  QColor alphaColor = mSymbol->color();
343  alphaColor.setAlphaF( mSymbol->opacity() );
344  alphaRamp->setColor( alphaColor );
345  QgsColorWidgetAction *alphaAction = new QgsColorWidgetAction( alphaRamp, mMenu, mMenu );
346  alphaAction->setDismissOnColorSelection( false );
347  connect( alphaAction, &QgsColorWidgetAction::colorChanged, this, [ = ]( const QColor & color )
348  {
349  double opacity = color.alphaF();
350  mSymbol->setOpacity( opacity );
351  updatePreview();
352  emit changed();
353  } );
354  connect( colorAction, &QgsColorWidgetAction::colorChanged, alphaRamp, [alphaRamp]( const QColor & color ) { alphaRamp->setColor( color, false ); }
355  );
356  mMenu->addAction( alphaAction );
357 
358  //get schemes with ShowInColorButtonMenu flag set
359  QList< QgsColorScheme * > schemeList = QgsApplication::colorSchemeRegistry()->schemes( QgsColorScheme::ShowInColorButtonMenu );
360  QList< QgsColorScheme * >::iterator it = schemeList.begin();
361  for ( ; it != schemeList.end(); ++it )
362  {
363  QgsColorSwatchGridAction *colorAction = new QgsColorSwatchGridAction( *it, mMenu, QStringLiteral( "symbology" ), this );
364  colorAction->setBaseColor( mSymbol->color() );
365  mMenu->addAction( colorAction );
366  connect( colorAction, &QgsColorSwatchGridAction::colorChanged, this, &QgsSymbolButton::setColor );
367  connect( colorAction, &QgsColorSwatchGridAction::colorChanged, this, &QgsSymbolButton::addRecentColor );
368  }
369 
370  mMenu->addSeparator();
371 
372  QAction *copyColorAction = new QAction( tr( "Copy Color" ), this );
373  mMenu->addAction( copyColorAction );
374  connect( copyColorAction, &QAction::triggered, this, &QgsSymbolButton::copyColor );
375 
376  QAction *pasteColorAction = new QAction( tr( "Paste Color" ), this );
377  //enable or disable paste action based on current clipboard contents. We always show the paste
378  //action, even if it's disabled, to give hint to the user that pasting colors is possible
379  QColor clipColor;
380  bool hasAlpha = false;
381  if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor, hasAlpha ) )
382  {
383  pasteColorAction->setIcon( createColorIcon( clipColor ) );
384  }
385  else
386  {
387  pasteColorAction->setEnabled( false );
388  }
389  mMenu->addAction( pasteColorAction );
390  connect( pasteColorAction, &QAction::triggered, this, &QgsSymbolButton::pasteColor );
391 }
392 
393 void QgsSymbolButton::addRecentColor( const QColor &color )
394 {
396 }
397 
398 
400 {
401  if ( e->type() == QEvent::EnabledChange )
402  {
403  updatePreview();
404  }
405  QToolButton::changeEvent( e );
406 }
407 
408 void QgsSymbolButton::showEvent( QShowEvent *e )
409 {
410  updatePreview();
411  QToolButton::showEvent( e );
412 }
413 
414 void QgsSymbolButton::resizeEvent( QResizeEvent *event )
415 {
416  QToolButton::resizeEvent( event );
417  //recalculate icon size and redraw icon
418  mIconSize = QSize();
419  updatePreview();
420 }
421 
422 void QgsSymbolButton::updatePreview( const QColor &color, QgsSymbol *tempSymbol )
423 {
424  std::unique_ptr< QgsSymbol > previewSymbol;
425 
426  if ( tempSymbol )
427  previewSymbol.reset( tempSymbol->clone() );
428  else
429  previewSymbol.reset( mSymbol->clone() );
430 
431  if ( color.isValid() )
432  previewSymbol->setColor( color );
433 
434  QSize currentIconSize;
435  //icon size is button size with a small margin
436  if ( menu() )
437  {
438  if ( !mIconSize.isValid() )
439  {
440  //calculate size of push button part of widget (ie, without the menu dropdown button part)
441  QStyleOptionToolButton opt;
442  initStyleOption( &opt );
443  QRect buttonSize = QApplication::style()->subControlRect( QStyle::CC_ToolButton, &opt, QStyle::SC_ToolButton,
444  this );
445  //make sure height of icon looks good under different platforms
446 #ifdef Q_OS_WIN
447  mIconSize = QSize( buttonSize.width() - 10, height() - 6 );
448 #else
449  mIconSize = QSize( buttonSize.width() - 10, height() - 12 );
450 #endif
451  }
452  currentIconSize = mIconSize;
453  }
454  else
455  {
456  //no menu
457 #ifdef Q_OS_WIN
458  currentIconSize = QSize( width() - 10, height() - 6 );
459 #else
460  currentIconSize = QSize( width() - 10, height() - 12 );
461 #endif
462  }
463 
464  if ( !currentIconSize.isValid() || currentIconSize.width() <= 0 || currentIconSize.height() <= 0 )
465  {
466  return;
467  }
468 
469  //create an icon pixmap
470  QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( previewSymbol.get(), currentIconSize );
471  setIconSize( currentIconSize );
472  setIcon( icon );
473 }
474 
475 bool QgsSymbolButton::colorFromMimeData( const QMimeData *mimeData, QColor &resultColor, bool &hasAlpha )
476 {
477  hasAlpha = false;
478  QColor mimeColor = QgsSymbolLayerUtils::colorFromMimeData( mimeData, hasAlpha );
479 
480  if ( mimeColor.isValid() )
481  {
482  resultColor = mimeColor;
483  return true;
484  }
485 
486  //could not get color from mime data
487  return false;
488 }
489 
490 QPixmap QgsSymbolButton::createColorIcon( const QColor &color ) const
491 {
492  //create an icon pixmap
493  QPixmap pixmap( 16, 16 );
494  pixmap.fill( Qt::transparent );
495 
496  QPainter p;
497  p.begin( &pixmap );
498 
499  //draw color over pattern
500  p.setBrush( QBrush( color ) );
501 
502  //draw border
503  p.setPen( QColor( 197, 197, 197 ) );
504  p.drawRect( 0, 0, 15, 15 );
505  p.end();
506  return pixmap;
507 }
508 
509 void QgsSymbolButton::setDialogTitle( const QString &title )
510 {
511  mDialogTitle = title;
512 }
513 
514 QString QgsSymbolButton::dialogTitle() const
515 {
516  return mDialogTitle;
517 }
518 
520 {
521  return mSymbol.get();
522 }
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
A color swatch grid which can be embedded into a menu.
static QgsSymbol * symbolFromMimeData(const QMimeData *data)
Attempts to parse mime data as a symbol.
void registerExpressionContextGenerator(QgsExpressionContextGenerator *generator)
Register an expression context generator class that will be used to retrieve an expression context fo...
QSize minimumSizeHint() const override
bool dockMode()
Returns the dock mode state.
void copyColor()
Copies the current symbol color to the clipboard.
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:151
QgsSymbol * symbol()
Returns the symbol that is currently active in the widget.
void mousePressEvent(QMouseEvent *e) override
virtual void setColor(const QColor &color, bool emitSignals=false)
Sets the color for the widget.
static QgsLineSymbol * createSimple(const QgsStringMap &properties)
Create a line symbol with one symbol layer: SimpleLine with specified properties. ...
Definition: qgssymbol.cpp:1098
void setDialogTitle(const QString &title)
Sets the title for the symbol settings dialog window.
static QgsFillSymbol * createSimple(const QgsStringMap &properties)
Create a fill symbol with one symbol layer: SimpleFill with specified properties. ...
Definition: qgssymbol.cpp:1109
void showEvent(QShowEvent *e) override
void changed()
Emitted when the symbol&#39;s settings are changed.
void colorChanged(const QColor &color)
Emitted when a color has been selected from the widget.
Base class for any widget that can be shown as a inline panel.
Line symbol.
Definition: qgssymbol.h:86
Show scheme in color button drop-down menu.
void panelAccepted(QgsPanelWidget *panel)
Emitted when the panel is accepted by the user.
void setMapCanvas(QgsMapCanvas *canvas)
Sets the map canvas associated with the widget.
QMap< QString, QString > QgsStringMap
Definition: qgis.h:501
void pasteSymbol()
Pastes a symbol from the clipboard.
void pasteColor()
Pastes a color from the clipboard to the symbol.
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:74
static QgsStyle * defaultStyle()
Returns default application-wide style.
Definition: qgsstyle.cpp:46
void setContext(const QgsSymbolWidgetContext &context)
Sets the context in which the symbol widget is shown, e.g., the associated map canvas and expression ...
SymbolType
Type of the symbol.
Definition: qgssymbol.h:83
QList< QgsColorScheme * > schemes() const
Returns all color schemes in the registry.
virtual QgsExpressionContext createExpressionContext() const =0
This method needs to be reimplemented in all classes which implement this interface and return an exp...
void resizeEvent(QResizeEvent *event) override
static QIcon symbolPreviewIcon(QgsSymbol *symbol, QSize size, int padding=0)
Returns an icon preview for a color ramp.
A color wheel widget.
QgsSymbolButton(QWidget *parent=nullptr, const QString &dialogTitle=QString())
Construct a new symbol button.
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget...
Contains settings which reflect the context in which a symbol (or renderer) widget is shown...
Alpha component (opacity) of color.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
A color ramp widget.
void mouseMoveEvent(QMouseEvent *e) override
void setColor(const QColor &color, bool emitSignals=false) override
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer&#39;s project and layer.
Symbol selector widget that can be used to select and build a symbol.
void setColor(const QColor &color)
Sets the current color for the symbol.
QString dialogTitle() const
Returns the title for the symbol settings dialog window.
static void addRecentColor(const QColor &color)
Adds a color to the list of recent colors.
void widgetChanged()
Emitted when the widget state changes.
void changeEvent(QEvent *e) override
void setExpressionContext(QgsExpressionContext *context)
Sets the optional expression context used for the widget.
Abstract interface for generating an expression context.
QgsVectorLayer * layer() const
Returns the layer associated with the widget.
Marker symbol.
Definition: qgssymbol.h:85
static QgsColorSchemeRegistry * colorSchemeRegistry()
Returns the application&#39;s color scheme registry, used for managing color schemes. ...
Fill symbol.
Definition: qgssymbol.h:87
void setContext(const QgsSymbolWidgetContext &context)
Sets the context in which the symbol widget is shown, e.g., the associated map canvas and expression ...
void setLayer(QgsVectorLayer *layer)
Sets a layer to associate with the widget.
virtual QgsSymbol * clone() const =0
Gets a deep copy of this symbol.
An action containing a color widget, which can be embedded into a menu.
void dropEvent(QDropEvent *e) override
static QMimeData * colorToMimeData(const QColor &color)
Creates mime data from a color.
void dragLeaveEvent(QDragLeaveEvent *e) override
QSize sizeHint() const override
void setMapCanvas(QgsMapCanvas *canvas)
Sets a map canvas to associate with the widget.
static QMimeData * symbolToMimeData(QgsSymbol *symbol)
Creates new mime data from a symbol.
void setSymbol(QgsSymbol *symbol)
Sets the symbol for the button.
void appendScopes(const QList< QgsExpressionContextScope *> &scopes)
Appends a list of scopes to the end of the context.
static QgsMarkerSymbol * createSimple(const QgsStringMap &properties)
Create a marker symbol with one symbol layer: SimpleMarker with specified properties.
Definition: qgssymbol.cpp:1087
QgsMapCanvas * mapCanvas() const
Returns the map canvas associated with the widget.
void copySymbol()
Copies the current symbol to the clipboard.
void setDismissOnColorSelection(bool dismiss)
Sets whether the parent menu should be dismissed and closed when a color is selected from the action&#39;...
Represents a vector layer which manages a vector based data sets.
void setSymbolType(QgsSymbol::SymbolType type)
Sets the symbol type which the button requires.
static QPixmap createDragIcon(const QColor &color)
Create an icon for dragging colors.
void dragEnterEvent(QDragEnterEvent *e) override
QgsSymbol * symbol()
Returns the current symbol defined by the button.
Hybrid symbol.
Definition: qgssymbol.h:88
static QColor colorFromMimeData(const QMimeData *data, bool &hasAlpha)
Attempts to parse mime data as a color.
void colorChanged(const QColor &color)
Emitted when a color has been selected from the widget.
void setBaseColor(const QColor &baseColor)
Sets the base color for the color grid.