1 /***************************************************************************
2     qgsrendererwidget.cpp
3     ---------------------
4     begin                : November 2009
5     copyright            : (C) 2009 by Martin Dobias
6     email                : wonder dot sk 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 #include "qgsrendererwidget.h"
16 
17 #include "qgsdatadefinedsizelegendwidget.h"
18 #include "qgssymbol.h"
19 #include "qgsvectorlayer.h"
20 #include "qgscolordialog.h"
21 #include "qgssymbollevelsdialog.h"
22 #include "qgssymbollayer.h"
23 #include "qgsexpressionbuilderdialog.h"
24 #include "qgsmapcanvas.h"
25 #include "qgspanelwidget.h"
26 #include "qgsproject.h"
27 #include "qgsexpressioncontextutils.h"
28 #include "qgssymbollayerutils.h"
29 #include "qgstemporalcontroller.h"
30 #include "qgsmarkersymbol.h"
31 #include "qgslinesymbol.h"
32 
33 #include <QMessageBox>
34 #include <QInputDialog>
35 #include <QMenu>
36 #include <QClipboard>
37 
QgsRendererWidget(QgsVectorLayer * layer,QgsStyle * style)38 QgsRendererWidget::QgsRendererWidget( QgsVectorLayer *layer, QgsStyle *style )
39   : mLayer( layer )
40   , mStyle( style )
41 {
42   contextMenu = new QMenu( tr( "Renderer Options" ), this );
43 
44   mCopyAction = new QAction( tr( "Copy" ), this );
45   connect( mCopyAction, &QAction::triggered, this, &QgsRendererWidget::copy );
46   mCopyAction->setShortcut( QKeySequence( QKeySequence::Copy ) );
47   mPasteAction = new QAction( tr( "Paste" ), this );
48   mPasteAction->setShortcut( QKeySequence( QKeySequence::Paste ) );
49   connect( mPasteAction, &QAction::triggered, this, &QgsRendererWidget::paste );
50 
51   mCopySymbolAction = new QAction( tr( "Copy Symbol" ), this );
52   contextMenu->addAction( mCopySymbolAction );
53   connect( mCopySymbolAction, &QAction::triggered, this, &QgsRendererWidget::copySymbol );
54   mPasteSymbolAction = new QAction( tr( "Paste Symbol" ), this );
55   contextMenu->addAction( mPasteSymbolAction );
56   connect( mPasteSymbolAction, &QAction::triggered, this, &QgsRendererWidget::pasteSymbolToSelection );
57 
58   contextMenu->addSeparator();
59   contextMenu->addAction( tr( "Change Color…" ), this, SLOT( changeSymbolColor() ) );
60   contextMenu->addAction( tr( "Change Opacity…" ), this, SLOT( changeSymbolOpacity() ) );
61   contextMenu->addAction( tr( "Change Output Unit…" ), this, SLOT( changeSymbolUnit() ) );
62 
63   if ( mLayer && mLayer->geometryType() == QgsWkbTypes::LineGeometry )
64   {
65     contextMenu->addAction( tr( "Change Width…" ), this, SLOT( changeSymbolWidth() ) );
66   }
67   else if ( mLayer && mLayer->geometryType() == QgsWkbTypes::PointGeometry )
68   {
69     contextMenu->addAction( tr( "Change Size…" ), this, SLOT( changeSymbolSize() ) );
70     contextMenu->addAction( tr( "Change Angle…" ), this, SLOT( changeSymbolAngle() ) );
71   }
72 
73   connect( contextMenu, &QMenu::aboutToShow, this, [ = ]
74   {
75     const std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
76     mPasteSymbolAction->setEnabled( static_cast< bool >( tempSymbol ) );
77   } );
78 }
79 
contextMenuViewCategories(QPoint)80 void QgsRendererWidget::contextMenuViewCategories( QPoint )
81 {
82   contextMenu->exec( QCursor::pos() );
83 }
84 
changeSymbolColor()85 void QgsRendererWidget::changeSymbolColor()
86 {
87   const QList<QgsSymbol *> symbolList = selectedSymbols();
88   if ( symbolList.isEmpty() )
89   {
90     return;
91   }
92 
93   QgsSymbol *firstSymbol = nullptr;
94   for ( QgsSymbol *symbol : symbolList )
95   {
96     if ( symbol )
97     {
98       firstSymbol = symbol;
99       break;
100     }
101   }
102   if ( !firstSymbol )
103     return;
104 
105   const QColor currentColor = firstSymbol->color();
106 
107   QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( qobject_cast< QWidget * >( parent() ) );
108   if ( panel && panel->dockMode() )
109   {
110     QgsCompoundColorWidget *colorWidget = new QgsCompoundColorWidget( panel, currentColor, QgsCompoundColorWidget::LayoutVertical );
111     colorWidget->setPanelTitle( tr( "Change Symbol Color" ) );
112     colorWidget->setAllowOpacity( true );
113     connect( colorWidget, &QgsCompoundColorWidget::currentColorChanged, this, [ = ]( const QColor & color )
114     {
115       for ( QgsSymbol *symbol : symbolList )
116       {
117         if ( symbol )
118           symbol->setColor( color );
119       }
120       refreshSymbolView();
121     } );
122     panel->openPanel( colorWidget );
123   }
124   else
125   {
126     // modal dialog version... yuck
127     const QColor color = QgsColorDialog::getColor( firstSymbol->color(), this, QStringLiteral( "Change Symbol Color" ), true );
128     if ( color.isValid() )
129     {
130       for ( QgsSymbol *symbol : symbolList )
131       {
132         if ( symbol )
133           symbol->setColor( color );
134       }
135       refreshSymbolView();
136     }
137   }
138 }
139 
changeSymbolOpacity()140 void QgsRendererWidget::changeSymbolOpacity()
141 {
142   const QList<QgsSymbol *> symbolList = selectedSymbols();
143   if ( symbolList.isEmpty() )
144   {
145     return;
146   }
147 
148   QgsSymbol *firstSymbol = nullptr;
149   const auto constSymbolList = symbolList;
150   for ( QgsSymbol *symbol : constSymbolList )
151   {
152     if ( symbol )
153     {
154       firstSymbol = symbol;
155       break;
156     }
157   }
158   if ( !firstSymbol )
159     return;
160 
161   bool ok;
162   const double oldOpacity = firstSymbol->opacity() * 100; // convert to %
163   const double opacity = QInputDialog::getDouble( this, tr( "Opacity" ), tr( "Change symbol opacity [%]" ), oldOpacity, 0.0, 100.0, 1, &ok );
164   if ( ok )
165   {
166     const auto constSymbolList = symbolList;
167     for ( QgsSymbol *symbol : constSymbolList )
168     {
169       if ( symbol )
170         symbol->setOpacity( opacity / 100.0 );
171     }
172     refreshSymbolView();
173   }
174 }
175 
changeSymbolUnit()176 void QgsRendererWidget::changeSymbolUnit()
177 {
178   const QList<QgsSymbol *> symbolList = selectedSymbols();
179   if ( symbolList.isEmpty() )
180   {
181     return;
182   }
183 
184   QgsSymbol *firstSymbol = nullptr;
185   const auto constSymbolList = symbolList;
186   for ( QgsSymbol *symbol : constSymbolList )
187   {
188     if ( symbol )
189     {
190       firstSymbol = symbol;
191       break;
192     }
193   }
194   if ( !firstSymbol )
195     return;
196 
197   bool ok;
198   const int currentUnit = ( firstSymbol->outputUnit() == QgsUnitTypes::RenderMillimeters ) ? 0 : 1;
199   const QString item = QInputDialog::getItem( this, tr( "Symbol unit" ), tr( "Select symbol unit" ), QStringList() << tr( "Millimeter" ) << tr( "Map unit" ), currentUnit, false, &ok );
200   if ( ok )
201   {
202     const QgsUnitTypes::RenderUnit unit = ( item.compare( tr( "Millimeter" ) ) == 0 ) ? QgsUnitTypes::RenderMillimeters : QgsUnitTypes::RenderMapUnits;
203 
204     const auto constSymbolList = symbolList;
205     for ( QgsSymbol *symbol : constSymbolList )
206     {
207       if ( symbol )
208         symbol->setOutputUnit( unit );
209     }
210     refreshSymbolView();
211   }
212 }
213 
changeSymbolWidth()214 void QgsRendererWidget::changeSymbolWidth()
215 {
216   const QList<QgsSymbol *> symbolList = selectedSymbols();
217   if ( symbolList.isEmpty() )
218   {
219     return;
220   }
221 
222   QgsDataDefinedWidthDialog dlg( symbolList, mLayer );
223 
224   dlg.setContext( mContext );
225 
226   if ( QDialog::Accepted == dlg.exec() )
227   {
228     if ( !dlg.mDDBtn->isActive() )
229     {
230       const auto constSymbolList = symbolList;
231       for ( QgsSymbol *symbol : constSymbolList )
232       {
233         if ( !symbol )
234           continue;
235 
236         if ( symbol->type() == Qgis::SymbolType::Line )
237           static_cast<QgsLineSymbol *>( symbol )->setWidth( dlg.mSpinBox->value() );
238       }
239     }
240     refreshSymbolView();
241   }
242 }
243 
changeSymbolSize()244 void QgsRendererWidget::changeSymbolSize()
245 {
246   const QList<QgsSymbol *> symbolList = selectedSymbols();
247   if ( symbolList.isEmpty() )
248   {
249     return;
250   }
251 
252   QgsDataDefinedSizeDialog dlg( symbolList, mLayer );
253   dlg.setContext( mContext );
254 
255   if ( QDialog::Accepted == dlg.exec() )
256   {
257     if ( !dlg.mDDBtn->isActive() )
258     {
259       const auto constSymbolList = symbolList;
260       for ( QgsSymbol *symbol : constSymbolList )
261       {
262         if ( !symbol )
263           continue;
264 
265         if ( symbol->type() == Qgis::SymbolType::Marker )
266           static_cast<QgsMarkerSymbol *>( symbol )->setSize( dlg.mSpinBox->value() );
267       }
268     }
269     refreshSymbolView();
270   }
271 }
272 
changeSymbolAngle()273 void QgsRendererWidget::changeSymbolAngle()
274 {
275   const QList<QgsSymbol *> symbolList = selectedSymbols();
276   if ( symbolList.isEmpty() )
277   {
278     return;
279   }
280 
281   QgsDataDefinedRotationDialog dlg( symbolList, mLayer );
282   dlg.setContext( mContext );
283 
284   if ( QDialog::Accepted == dlg.exec() )
285   {
286     if ( !dlg.mDDBtn->isActive() )
287     {
288       const auto constSymbolList = symbolList;
289       for ( QgsSymbol *symbol : constSymbolList )
290       {
291         if ( !symbol )
292           continue;
293 
294         if ( symbol->type() == Qgis::SymbolType::Marker )
295           static_cast<QgsMarkerSymbol *>( symbol )->setAngle( dlg.mSpinBox->value() );
296       }
297     }
298     refreshSymbolView();
299   }
300 }
301 
pasteSymbolToSelection()302 void QgsRendererWidget::pasteSymbolToSelection()
303 {
304 
305 }
306 
copySymbol()307 void QgsRendererWidget::copySymbol()
308 {
309   const QList<QgsSymbol *> symbolList = selectedSymbols();
310   if ( symbolList.isEmpty() )
311   {
312     return;
313   }
314 
315   QApplication::clipboard()->setMimeData( QgsSymbolLayerUtils::symbolToMimeData( symbolList.at( 0 ) ) );
316 }
317 
showSymbolLevelsDialog(QgsFeatureRenderer * r)318 void QgsRendererWidget::showSymbolLevelsDialog( QgsFeatureRenderer *r )
319 {
320   QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this );
321   if ( panel && panel->dockMode() )
322   {
323     QgsSymbolLevelsWidget *widget = new QgsSymbolLevelsWidget( r->legendSymbolItems(), r->usingSymbolLevels(), panel );
324     widget->setPanelTitle( tr( "Symbol Levels" ) );
325     connect( widget, &QgsPanelWidget::widgetChanged, this, [ = ]()
326     {
327       setSymbolLevels( widget->symbolLevels(), widget->usingLevels() );
328     } );
329     panel->openPanel( widget );
330   }
331   else
332   {
333     QgsSymbolLevelsDialog dlg( r, r->usingSymbolLevels(), panel );
334     if ( dlg.exec() )
335     {
336       setSymbolLevels( dlg.symbolLevels(), dlg.usingLevels() );
337     }
338   }
339 }
340 
setContext(const QgsSymbolWidgetContext & context)341 void QgsRendererWidget::setContext( const QgsSymbolWidgetContext &context )
342 {
343   mContext = context;
344 }
345 
context() const346 QgsSymbolWidgetContext QgsRendererWidget::context() const
347 {
348   return mContext;
349 }
350 
applyChanges()351 void QgsRendererWidget::applyChanges()
352 {
353   apply();
354 }
355 
setDockMode(bool dockMode)356 void QgsRendererWidget::setDockMode( bool dockMode )
357 {
358   if ( dockMode )
359   {
360     // when in dock mode, these shortcuts conflict with the main window shortcuts and cannot be used
361     if ( mCopyAction )
362       mCopyAction->setShortcut( QKeySequence() );
363     if ( mPasteAction )
364       mPasteAction->setShortcut( QKeySequence() );
365   }
366   QgsPanelWidget::setDockMode( dockMode );
367 }
368 
disableSymbolLevels()369 void QgsRendererWidget::disableSymbolLevels()
370 {
371 }
372 
createDataDefinedSizeLegendWidget(const QgsMarkerSymbol * symbol,const QgsDataDefinedSizeLegend * ddsLegend)373 QgsDataDefinedSizeLegendWidget *QgsRendererWidget::createDataDefinedSizeLegendWidget( const QgsMarkerSymbol *symbol, const QgsDataDefinedSizeLegend *ddsLegend )
374 {
375   const QgsProperty ddSize = symbol->dataDefinedSize();
376   if ( !ddSize || !ddSize.isActive() )
377   {
378     QMessageBox::warning( this, tr( "Data-defined Size Legend" ), tr( "Data-defined size is not enabled!" ) );
379     return nullptr;
380   }
381 
382   QgsDataDefinedSizeLegendWidget *panel = new QgsDataDefinedSizeLegendWidget( ddsLegend, ddSize, symbol->clone(), mContext.mapCanvas() );
383   connect( panel, &QgsPanelWidget::widgetChanged, this, &QgsPanelWidget::widgetChanged );
384   return panel;
385 }
386 
setSymbolLevels(const QList<QgsLegendSymbolItem> &,bool)387 void QgsRendererWidget::setSymbolLevels( const QList< QgsLegendSymbolItem > &, bool )
388 {
389 
390 }
391 
392 //
393 // QgsDataDefinedValueDialog
394 //
395 
QgsDataDefinedValueDialog(const QList<QgsSymbol * > & symbolList,QgsVectorLayer * layer,const QString & label)396 QgsDataDefinedValueDialog::QgsDataDefinedValueDialog( const QList<QgsSymbol *> &symbolList, QgsVectorLayer *layer, const QString &label )
397   : mSymbolList( symbolList )
398   , mLayer( layer )
399 {
400   setupUi( this );
401   setWindowFlags( Qt::WindowStaysOnTopHint );
402   mLabel->setText( label );
403   connect( mDDBtn, &QgsPropertyOverrideButton::changed, this, &QgsDataDefinedValueDialog::dataDefinedChanged );
404 }
405 
setContext(const QgsSymbolWidgetContext & context)406 void QgsDataDefinedValueDialog::setContext( const QgsSymbolWidgetContext &context )
407 {
408   mContext = context;
409 }
410 
context() const411 QgsSymbolWidgetContext QgsDataDefinedValueDialog::context() const
412 {
413   return mContext;
414 }
415 
createExpressionContext() const416 QgsExpressionContext QgsDataDefinedValueDialog::createExpressionContext() const
417 {
418   QgsExpressionContext expContext;
419   expContext << QgsExpressionContextUtils::globalScope()
420              << QgsExpressionContextUtils::projectScope( QgsProject::instance() )
421              << QgsExpressionContextUtils::atlasScope( nullptr );
422   if ( auto *lMapCanvas = mContext.mapCanvas() )
423   {
424     expContext << QgsExpressionContextUtils::mapSettingsScope( lMapCanvas->mapSettings() )
425                << new QgsExpressionContextScope( lMapCanvas->expressionContextScope() );
426 
427     if ( const QgsExpressionContextScopeGenerator *generator = dynamic_cast< const QgsExpressionContextScopeGenerator * >( lMapCanvas->temporalController() ) )
428     {
429       expContext << generator->createExpressionContextScope();
430     }
431   }
432   else
433   {
434     expContext << QgsExpressionContextUtils::mapSettingsScope( QgsMapSettings() );
435   }
436 
437   if ( auto *lVectorLayer = vectorLayer() )
438     expContext << QgsExpressionContextUtils::layerScope( lVectorLayer );
439 
440   // additional scopes
441   const auto constAdditionalExpressionContextScopes = mContext.additionalExpressionContextScopes();
442   for ( const QgsExpressionContextScope &scope : constAdditionalExpressionContextScopes )
443   {
444     expContext.appendScope( new QgsExpressionContextScope( scope ) );
445   }
446 
447   return expContext;
448 }
449 
init(int propertyKey)450 void QgsDataDefinedValueDialog::init( int propertyKey )
451 {
452   const QgsProperty dd( symbolDataDefined() );
453 
454   mDDBtn->init( propertyKey, dd, QgsSymbolLayer::propertyDefinitions(), mLayer );
455   mDDBtn->registerExpressionContextGenerator( this );
456 
457   QgsSymbol *initialSymbol = nullptr;
458   const auto constMSymbolList = mSymbolList;
459   for ( QgsSymbol *symbol : constMSymbolList )
460   {
461     if ( symbol )
462     {
463       initialSymbol = symbol;
464     }
465   }
466   mSpinBox->setValue( initialSymbol ? value( initialSymbol ) : 0 );
467   mSpinBox->setEnabled( !mDDBtn->isActive() );
468 }
469 
symbolDataDefined() const470 QgsProperty QgsDataDefinedValueDialog::symbolDataDefined() const
471 {
472   if ( mSymbolList.isEmpty() || !mSymbolList.back() )
473     return QgsProperty();
474 
475   // check that all symbols share the same size expression
476   const QgsProperty dd = symbolDataDefined( mSymbolList.back() );
477   const auto constMSymbolList = mSymbolList;
478   for ( QgsSymbol *it : constMSymbolList )
479   {
480     const QgsProperty symbolDD( symbolDataDefined( it ) );
481     if ( !it || !dd || !symbolDD || symbolDD != dd )
482       return QgsProperty();
483   }
484   return dd;
485 }
486 
dataDefinedChanged()487 void QgsDataDefinedValueDialog::dataDefinedChanged()
488 {
489   const QgsProperty dd( mDDBtn->toProperty() );
490   mSpinBox->setEnabled( !dd.isActive() );
491 
492   const QgsProperty symbolDD( symbolDataDefined() );
493 
494   if ( // shall we remove datadefined expressions for layers ?
495     ( symbolDD && symbolDD.isActive() && !dd.isActive() )
496     // shall we set the "en masse" expression for properties ?
497     || dd.isActive() )
498   {
499     const auto constMSymbolList = mSymbolList;
500     for ( QgsSymbol *it : constMSymbolList )
501       setDataDefined( it, dd );
502   }
503 }
504 
QgsDataDefinedSizeDialog(const QList<QgsSymbol * > & symbolList,QgsVectorLayer * layer)505 QgsDataDefinedSizeDialog::QgsDataDefinedSizeDialog( const QList<QgsSymbol *> &symbolList, QgsVectorLayer *layer )
506   : QgsDataDefinedValueDialog( symbolList, layer, tr( "Size" ) )
507 {
508   init( QgsSymbolLayer::PropertySize );
509   if ( !symbolList.isEmpty() && symbolList.at( 0 ) && vectorLayer() )
510   {
511     mAssistantSymbol.reset( static_cast<const QgsMarkerSymbol *>( symbolList.at( 0 ) )->clone() );
512     mDDBtn->setSymbol( mAssistantSymbol );
513   }
514 }
515 
symbolDataDefined(const QgsSymbol * symbol) const516 QgsProperty QgsDataDefinedSizeDialog::symbolDataDefined( const QgsSymbol *symbol ) const
517 {
518   const QgsMarkerSymbol *marker = static_cast<const QgsMarkerSymbol *>( symbol );
519   return marker->dataDefinedSize();
520 }
521 
value(const QgsSymbol * symbol) const522 double QgsDataDefinedSizeDialog::value( const QgsSymbol *symbol ) const
523 {
524   return static_cast<const QgsMarkerSymbol *>( symbol )->size();
525 }
526 
setDataDefined(QgsSymbol * symbol,const QgsProperty & dd)527 void QgsDataDefinedSizeDialog::setDataDefined( QgsSymbol *symbol, const QgsProperty &dd )
528 {
529   static_cast<QgsMarkerSymbol *>( symbol )->setDataDefinedSize( dd );
530   static_cast<QgsMarkerSymbol *>( symbol )->setScaleMethod( Qgis::ScaleMethod::ScaleDiameter );
531 }
532 
533 
QgsDataDefinedRotationDialog(const QList<QgsSymbol * > & symbolList,QgsVectorLayer * layer)534 QgsDataDefinedRotationDialog::QgsDataDefinedRotationDialog( const QList<QgsSymbol *> &symbolList, QgsVectorLayer *layer )
535   : QgsDataDefinedValueDialog( symbolList, layer, tr( "Rotation" ) )
536 {
537   init( QgsSymbolLayer::PropertyAngle );
538 }
539 
symbolDataDefined(const QgsSymbol * symbol) const540 QgsProperty QgsDataDefinedRotationDialog::symbolDataDefined( const QgsSymbol *symbol ) const
541 {
542   const QgsMarkerSymbol *marker = static_cast<const QgsMarkerSymbol *>( symbol );
543   return marker->dataDefinedAngle();
544 }
545 
value(const QgsSymbol * symbol) const546 double QgsDataDefinedRotationDialog::value( const QgsSymbol *symbol ) const
547 {
548   return static_cast<const QgsMarkerSymbol *>( symbol )->angle();
549 }
550 
setDataDefined(QgsSymbol * symbol,const QgsProperty & dd)551 void QgsDataDefinedRotationDialog::setDataDefined( QgsSymbol *symbol, const QgsProperty &dd )
552 {
553   static_cast<QgsMarkerSymbol *>( symbol )->setDataDefinedAngle( dd );
554 }
555 
556 
QgsDataDefinedWidthDialog(const QList<QgsSymbol * > & symbolList,QgsVectorLayer * layer)557 QgsDataDefinedWidthDialog::QgsDataDefinedWidthDialog( const QList<QgsSymbol *> &symbolList, QgsVectorLayer *layer )
558   : QgsDataDefinedValueDialog( symbolList, layer, tr( "Width" ) )
559 {
560   init( QgsSymbolLayer::PropertyStrokeWidth );
561 }
562 
symbolDataDefined(const QgsSymbol * symbol) const563 QgsProperty QgsDataDefinedWidthDialog::symbolDataDefined( const QgsSymbol *symbol ) const
564 {
565   const QgsLineSymbol *line = static_cast<const QgsLineSymbol *>( symbol );
566   return line->dataDefinedWidth();
567 }
568 
value(const QgsSymbol * symbol) const569 double QgsDataDefinedWidthDialog::value( const QgsSymbol *symbol ) const
570 {
571   return static_cast<const QgsLineSymbol *>( symbol )->width();
572 }
573 
setDataDefined(QgsSymbol * symbol,const QgsProperty & dd)574 void QgsDataDefinedWidthDialog::setDataDefined( QgsSymbol *symbol, const QgsProperty &dd )
575 {
576   static_cast<QgsLineSymbol *>( symbol )->setDataDefinedWidth( dd );
577 }
578 
apply()579 void QgsRendererWidget::apply()
580 {
581 
582 }
583