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