QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
qgssymbolselectordialog.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgssymbolselectordialog.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
17
19#include "qgsstyle.h"
20#include "qgssymbol.h"
21#include "qgssymbollayer.h"
23#include "qgssymbollayerutils.h"
24
25#include <QString>
26
27#include "moc_qgssymbolselectordialog.cpp"
28
29using namespace Qt::StringLiterals;
30
31// the widgets
34#include "qgsapplication.h"
35#include "qgsvectorlayer.h"
36#include "qgssvgcache.h"
37#include "qgsimagecache.h"
38#include "qgsproject.h"
39#include "qgsguiutils.h"
40#include "qgsgui.h"
41#include "qgsmarkersymbol.h"
42#include "qgslinesymbol.h"
43#include "qscreen.h"
44
45#include <QColorDialog>
46#include <QPainter>
47#include <QStandardItemModel>
48#include <QInputDialog>
49#include <QMessageBox>
50#include <QKeyEvent>
51#include <QMenu>
52
53#include <QWidget>
54#include <QFile>
55#include <QStandardItem>
56#include <memory>
57
59
60static const int SYMBOL_LAYER_ITEM_TYPE = QStandardItem::UserType + 1;
61
62DataDefinedRestorer::DataDefinedRestorer( QgsSymbol *symbol, const QgsSymbolLayer *symbolLayer )
63
64{
65 if ( symbolLayer->type() == Qgis::SymbolType::Marker && symbol->type() == Qgis::SymbolType::Marker )
66 {
67 Q_ASSERT( symbol->type() == Qgis::SymbolType::Marker );
68 mMarker = static_cast<QgsMarkerSymbol *>( symbol );
69 mMarkerSymbolLayer = static_cast<const QgsMarkerSymbolLayer *>( symbolLayer );
70 mDDSize = mMarker->dataDefinedSize();
71 mDDAngle = mMarker->dataDefinedAngle();
72 // check if restore is actually needed
73 if ( !mDDSize && !mDDAngle )
74 mMarker = nullptr;
75 }
76 else if ( symbolLayer->type() == Qgis::SymbolType::Line && symbol->type() == Qgis::SymbolType::Line )
77 {
78 mLine = static_cast<QgsLineSymbol *>( symbol );
79 mLineSymbolLayer = static_cast<const QgsLineSymbolLayer *>( symbolLayer );
80 mDDWidth = mLine->dataDefinedWidth();
81 // check if restore is actually needed
82 if ( !mDDWidth )
83 mLine = nullptr;
84 }
85 save();
86}
87
88void DataDefinedRestorer::save()
89{
90 if ( mMarker )
91 {
92 mSize = mMarkerSymbolLayer->size();
93 mAngle = mMarkerSymbolLayer->angle();
94 mMarkerOffset = mMarkerSymbolLayer->offset();
95 }
96 else if ( mLine )
97 {
98 mWidth = mLineSymbolLayer->width();
99 mLineOffset = mLineSymbolLayer->offset();
100 }
101}
102
103void DataDefinedRestorer::restore()
104{
105 if ( mMarker )
106 {
107 if ( mDDSize && ( mSize != mMarkerSymbolLayer->size() || mMarkerOffset != mMarkerSymbolLayer->offset() ) )
108 mMarker->setDataDefinedSize( mDDSize );
109 if ( mDDAngle && mAngle != mMarkerSymbolLayer->angle() )
110 mMarker->setDataDefinedAngle( mDDAngle );
111 }
112 else if ( mLine )
113 {
114 if ( mDDWidth && ( mWidth != mLineSymbolLayer->width() || mLineOffset != mLineSymbolLayer->offset() ) )
115 mLine->setDataDefinedWidth( mDDWidth );
116 }
117 save();
118}
119
120// Hybrid item which may represent a symbol or a layer
121// Check using item->isLayer()
122class SymbolLayerItem : public QStandardItem
123{
124 public:
125 explicit SymbolLayerItem( QgsSymbolLayer *layer, Qgis::SymbolType symbolType, QgsVectorLayer *vectorLayer, QScreen *screen )
126 : mVectorLayer( vectorLayer )
127 , mScreen( screen )
128 {
129 setLayer( layer, symbolType );
130 }
131
132 explicit SymbolLayerItem( QgsSymbol *symbol, QgsVectorLayer *vectorLayer, QScreen *screen )
133 : mVectorLayer( vectorLayer )
134 , mScreen( screen )
135 {
136 setSymbol( symbol );
137 }
138
139 void setLayer( QgsSymbolLayer *layer, Qgis::SymbolType symbolType )
140 {
141 mLayer = layer;
142 mIsLayer = true;
143 mSymbol = nullptr;
144 mSymbolType = symbolType;
145 updatePreview();
146 }
147
148 void setSymbol( QgsSymbol *symbol )
149 {
150 mSymbol = symbol;
151 mIsLayer = false;
152 mLayer = nullptr;
153 updatePreview();
154 }
155
156 void updatePreview()
157 {
158 if ( !mSize.isValid() )
159 {
160 const int size = QgsGuiUtils::scaleIconSize( 16 );
161 mSize = QSize( size, size );
162 }
163 QIcon icon;
164 if ( mIsLayer )
165 icon = QgsSymbolLayerUtils::symbolLayerPreviewIcon( mLayer, Qgis::RenderUnit::Millimeters, mSize, QgsMapUnitScale(), mSymbol ? mSymbol->type() : mSymbolType, mVectorLayer, QgsScreenProperties( mScreen.data() ) );
166 else
167 {
168 QgsExpressionContext expContext;
170 icon = QIcon( QgsSymbolLayerUtils::symbolPreviewPixmap( mSymbol, mSize, 0, nullptr, false, &expContext, nullptr, QgsScreenProperties( mScreen.data() ) ) );
171 }
172 setIcon( icon );
173
174 if ( auto *lParent = parent() )
175 static_cast<SymbolLayerItem *>( lParent )->updatePreview();
176 }
177
178 int type() const override { return SYMBOL_LAYER_ITEM_TYPE; }
179 bool isLayer() const { return mIsLayer; }
180
181 // returns the symbol pointer; helpful in determining a layer's parent symbol
182 QgsSymbol *symbol()
183 {
184 return mSymbol;
185 }
186
187 QgsSymbolLayer *layer()
188 {
189 return mLayer;
190 }
191
192 QVariant data( int role ) const override
193 {
194 if ( role == Qt::DisplayRole || role == Qt::EditRole )
195 {
196 if ( mIsLayer )
197 {
198 QgsSymbolLayerAbstractMetadata *m = QgsApplication::symbolLayerRegistry()->symbolLayerMetadata( mLayer->layerType() );
199 if ( m )
200 return m->visibleName();
201 else
202 return QString();
203 }
204 else
205 {
206 switch ( mSymbol->type() )
207 {
209 return QCoreApplication::translate( "SymbolLayerItem", "Marker" );
211 return QCoreApplication::translate( "SymbolLayerItem", "Fill" );
213 return QCoreApplication::translate( "SymbolLayerItem", "Line" );
214 default:
215 return "Symbol";
216 }
217 }
218 }
219 else if ( role == Qt::ForegroundRole && mIsLayer )
220 {
221 if ( !mLayer->enabled() )
222 {
223 QPalette pal = qApp->palette();
224 QBrush brush = QStandardItem::data( role ).value<QBrush>();
225 brush.setColor( pal.color( QPalette::Disabled, QPalette::WindowText ) );
226 return brush;
227 }
228 else
229 {
230 return QVariant();
231 }
232 }
233
234 // if ( role == Qt::SizeHintRole )
235 // return QVariant( QSize( 32, 32 ) );
236 if ( role == Qt::CheckStateRole )
237 return QVariant(); // could be true/false
238 return QStandardItem::data( role );
239 }
240
241 protected:
242 QgsSymbolLayer *mLayer = nullptr;
243 QgsSymbol *mSymbol = nullptr;
244 QPointer<QgsVectorLayer> mVectorLayer;
245 bool mIsLayer = false;
246 QSize mSize;
248 QPointer<QScreen> mScreen;
249};
250
252
254
256 : QgsPanelWidget( parent )
257 , mStyle( style )
258 , mSymbol( symbol )
259 , mVectorLayer( vl )
260{
261#ifdef Q_OS_MAC
262 setWindowModality( Qt::WindowModal );
263#endif
264
265 setupUi( this );
266 this->layout()->setContentsMargins( 0, 0, 0, 0 );
267
268 layersTree->setMaximumHeight( static_cast<int>( Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 7 ) );
269 layersTree->setMinimumHeight( layersTree->maximumHeight() );
270 lblPreview->setMaximumWidth( layersTree->maximumHeight() );
271
272 // setup icons
273 btnAddLayer->setIcon( QIcon( QgsApplication::iconPath( "symbologyAdd.svg" ) ) );
274 btnRemoveLayer->setIcon( QIcon( QgsApplication::iconPath( "symbologyRemove.svg" ) ) );
275 QIcon iconLock;
276 iconLock.addFile( QgsApplication::iconPath( u"locked.svg"_s ), QSize(), QIcon::Normal, QIcon::On );
277 iconLock.addFile( QgsApplication::iconPath( u"locked.svg"_s ), QSize(), QIcon::Active, QIcon::On );
278 iconLock.addFile( QgsApplication::iconPath( u"unlocked.svg"_s ), QSize(), QIcon::Normal, QIcon::Off );
279 iconLock.addFile( QgsApplication::iconPath( u"unlocked.svg"_s ), QSize(), QIcon::Active, QIcon::Off );
280
281 QIcon iconColorLock;
282 iconColorLock.addFile( QgsApplication::iconPath( u"mIconColorLocked.svg"_s ), QSize(), QIcon::Normal, QIcon::On );
283 iconColorLock.addFile( QgsApplication::iconPath( u"mIconColorLocked.svg"_s ), QSize(), QIcon::Active, QIcon::On );
284 iconColorLock.addFile( QgsApplication::iconPath( u"mIconColorUnlocked.svg"_s ), QSize(), QIcon::Normal, QIcon::Off );
285 iconColorLock.addFile( QgsApplication::iconPath( u"mIconColorUnlocked.svg"_s ), QSize(), QIcon::Active, QIcon::Off );
286
287 mLockColorAction = new QAction( tr( "Lock Color" ), this );
288 mLockColorAction->setToolTip( tr( "Avoid changing the color of the layer when the symbol color is changed" ) );
289 mLockColorAction->setCheckable( true );
290 mLockColorAction->setIcon( iconColorLock );
291
292 QIcon iconSelectLock;
293 iconSelectLock.addFile( QgsApplication::iconPath( u"mIconSelectLocked.svg"_s ), QSize(), QIcon::Normal, QIcon::On );
294 iconSelectLock.addFile( QgsApplication::iconPath( u"mIconSelectLocked.svg"_s ), QSize(), QIcon::Active, QIcon::On );
295 iconSelectLock.addFile( QgsApplication::iconPath( u"mIconSelectUnlocked.svg"_s ), QSize(), QIcon::Normal, QIcon::Off );
296 iconSelectLock.addFile( QgsApplication::iconPath( u"mIconSelectUnlocked.svg"_s ), QSize(), QIcon::Active, QIcon::Off );
297
298 mLockSelectionColorAction = new QAction( tr( "Lock Color When Selected" ), this );
299 mLockSelectionColorAction->setToolTip( tr( "Avoid changing the color of the layer when a feature is selected" ) );
300 mLockSelectionColorAction->setCheckable( true );
301 mLockSelectionColorAction->setIcon( iconSelectLock );
302
303 QMenu *lockMenu = new QMenu( this );
304 lockMenu->addAction( mLockColorAction );
305 lockMenu->addAction( mLockSelectionColorAction );
306 btnLock->setMenu( lockMenu );
307 btnLock->setPopupMode( QToolButton::InstantPopup );
308
309 btnDuplicate->setIcon( QIcon( QgsApplication::iconPath( "mActionDuplicateLayer.svg" ) ) );
310 btnUp->setIcon( QIcon( QgsApplication::iconPath( "mActionArrowUp.svg" ) ) );
311 btnDown->setIcon( QIcon( QgsApplication::iconPath( "mActionArrowDown.svg" ) ) );
312
313 mSymbolLayersModel = new QStandardItemModel( layersTree );
314 // Set the symbol
315 layersTree->setModel( mSymbolLayersModel );
316 layersTree->setHeaderHidden( true );
317
318 //get first feature from layer for previews
319 if ( mVectorLayer )
320 {
321#if 0 // this is too expensive to do for many providers. TODO revisit when support for connection timeouts is complete across all providers
322 // short timeout for request - it doesn't really matter if we don't get the feature, and this call is blocking UI
323 QgsFeatureIterator it = mVectorLayer->getFeatures( QgsFeatureRequest().setLimit( 1 ).setConnectionTimeout( 100 ) );
324 it.nextFeature( mPreviewFeature );
325#endif
326 mPreviewExpressionContext.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( mVectorLayer ) );
327#if 0
328 mPreviewExpressionContext.setFeature( mPreviewFeature );
329#endif
330 }
331 else
332 {
333 mPreviewExpressionContext.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) );
334 }
335
336 QItemSelectionModel *selModel = layersTree->selectionModel();
337 connect( selModel, &QItemSelectionModel::currentChanged, this, &QgsSymbolSelectorWidget::layerChanged );
338
339 loadSymbol( mSymbol, static_cast<SymbolLayerItem *>( mSymbolLayersModel->invisibleRootItem() ) );
341
342 connect( btnUp, &QAbstractButton::clicked, this, &QgsSymbolSelectorWidget::moveLayerUp );
343 connect( btnDown, &QAbstractButton::clicked, this, &QgsSymbolSelectorWidget::moveLayerDown );
344 connect( btnAddLayer, &QAbstractButton::clicked, this, &QgsSymbolSelectorWidget::addLayer );
345 connect( btnRemoveLayer, &QAbstractButton::clicked, this, &QgsSymbolSelectorWidget::removeLayer );
346 connect( mLockColorAction, &QAction::toggled, this, &QgsSymbolSelectorWidget::lockLayer );
347 connect( mLockSelectionColorAction, &QAction::toggled, this, [this]( bool checked ) {
348 QgsSymbolLayer *layer = currentLayer();
349 if ( !layer )
350 return;
351
352 Qgis::SymbolLayerUserFlags flags = layer->userFlags();
354 layer->setUserFlags( flags );
355 updateLockButtonIcon();
356 emit symbolModified();
357 } );
358 connect( btnDuplicate, &QAbstractButton::clicked, this, &QgsSymbolSelectorWidget::duplicateLayer );
360
361 updateLockButtonIcon();
362
363 updateUi();
364
365 // set symbol as active item in the tree
366 const QModelIndex newIndex = layersTree->model()->index( 0, 0 );
367 layersTree->setCurrentIndex( newIndex );
368
369 setPanelTitle( tr( "Symbol Selector" ) );
370
371 // when a remote svg has been fetched, update the widget's previews
372 // this is required if the symbol utilizes remote svgs, and the current previews
373 // have been generated using the temporary "downloading" svg. In this case
374 // we require the preview to be regenerated to use the correct fetched
375 // svg
376 connect( QgsApplication::svgCache(), &QgsSvgCache::remoteSvgFetched, this, &QgsSymbolSelectorWidget::projectDataChanged );
377
378 // when a remote image has been fetched, update the widget's previews
379 // this is required if the symbol utilizes remote images, and the current previews
380 // have been generated using the temporary "downloading" image. In this case
381 // we require the preview to be regenerated to use the correct fetched
382 // image
383 connect( QgsApplication::imageCache(), &QgsImageCache::remoteImageFetched, this, &QgsSymbolSelectorWidget::projectDataChanged );
384
385 // if project color scheme changes, we need to redraw symbols - they may use project colors and accordingly
386 // need updating to reflect the new colors
387 connect( QgsProject::instance(), &QgsProject::projectColorsChanged, this, &QgsSymbolSelectorWidget::projectDataChanged );
388
389 connect( QgsProject::instance(), static_cast<void ( QgsProject::* )( const QList<QgsMapLayer *> &layers )>( &QgsProject::layersWillBeRemoved ), this, &QgsSymbolSelectorWidget::layersAboutToBeRemoved );
390}
391
393{
394 QgsSymbolSelectorWidget *widget = new QgsSymbolSelectorWidget( symbol.get(), style, vl, parent );
395 // transfer ownership of symbol to widget, so that we are guaranteed it will last for the duration of the widget
396 widget->mOwnedSymbol = std::move( symbol );
397 return widget;
398}
399
401{
402 if ( !mAdvancedMenu )
403 {
404 mAdvancedMenu = new QMenu( this );
405 // Brute force method to activate the Advanced menu
406 layerChanged();
407 }
408 return mAdvancedMenu;
409}
410
412{
413 mContext = context;
414
415 if ( auto *lExpressionContext = mContext.expressionContext() )
416 {
417 mPreviewExpressionContext = *lExpressionContext;
418 if ( mVectorLayer )
419 mPreviewExpressionContext.appendScope( QgsExpressionContextUtils::layerScope( mVectorLayer ) );
420
421 mPreviewExpressionContext.setFeature( mPreviewFeature );
422 }
423
424 QWidget *widget = stackedWidget->currentWidget();
425 if ( QgsLayerPropertiesWidget *layerProp = qobject_cast<QgsLayerPropertiesWidget *>( widget ) )
426 {
427 layerProp->setContext( context );
428 }
429 else if ( QgsSymbolsListWidget *listWidget = qobject_cast<QgsSymbolsListWidget *>( widget ) )
430 {
431 listWidget->setContext( context );
432 }
433
434 layerChanged();
436}
437
439{
440 return mContext;
441}
442
443void QgsSymbolSelectorWidget::loadSymbol( QgsSymbol *symbol, SymbolLayerItem *parent )
444{
445 if ( !symbol )
446 return;
447
448 if ( !parent )
449 {
450 mSymbol = symbol;
451 mSymbolLayersModel->clear();
452 parent = static_cast<SymbolLayerItem *>( mSymbolLayersModel->invisibleRootItem() );
453 }
454
455 SymbolLayerItem *symbolItem = new SymbolLayerItem( symbol, mVectorLayer, screen() );
456 QFont boldFont = symbolItem->font();
457 boldFont.setBold( true );
458 symbolItem->setFont( boldFont );
459 parent->appendRow( symbolItem );
460
461 const int count = symbol->symbolLayerCount();
462 for ( int i = count - 1; i >= 0; i-- )
463 {
464 SymbolLayerItem *layerItem = new SymbolLayerItem( symbol->symbolLayer( i ), symbol->type(), mVectorLayer, screen() );
465 layerItem->setEditable( false );
466 symbolItem->appendRow( layerItem );
467 if ( symbol->symbolLayer( i )->subSymbol() )
468 {
469 loadSymbol( symbol->symbolLayer( i )->subSymbol(), layerItem );
470 }
471 layersTree->setExpanded( layerItem->index(), true );
472 }
473 layersTree->setExpanded( symbolItem->index(), true );
474
475 if ( mSymbol == symbol && !layersTree->currentIndex().isValid() )
476 {
477 // make sure root item for symbol is selected in tree
478 layersTree->setCurrentIndex( symbolItem->index() );
479 }
480}
481
482void QgsSymbolSelectorWidget::reloadSymbol()
483{
484 mSymbolLayersModel->clear();
485 loadSymbol( mSymbol, static_cast<SymbolLayerItem *>( mSymbolLayersModel->invisibleRootItem() ) );
486}
487
488void QgsSymbolSelectorWidget::updateUi()
489{
490 const QModelIndex currentIdx = layersTree->currentIndex();
491 if ( !currentIdx.isValid() )
492 return;
493
494 SymbolLayerItem *item = static_cast<SymbolLayerItem *>( mSymbolLayersModel->itemFromIndex( currentIdx ) );
495 if ( !item->isLayer() )
496 {
497 btnUp->setEnabled( false );
498 btnDown->setEnabled( false );
499 btnRemoveLayer->setEnabled( false );
500 btnLock->setEnabled( false );
501 btnDuplicate->setEnabled( false );
502 return;
503 }
504
505 const int rowCount = item->parent()->rowCount();
506 const int currentRow = item->row();
507
508 btnUp->setEnabled( currentRow > 0 );
509 btnDown->setEnabled( currentRow < rowCount - 1 );
510 btnRemoveLayer->setEnabled( rowCount > 1 );
511 btnLock->setEnabled( true );
512 btnDuplicate->setEnabled( true );
513}
514
516{
517 if ( !mSymbol )
518 return;
519
520 std::unique_ptr<QgsSymbol> symbolClone( mSymbol->clone() );
521 const QImage preview = symbolClone->bigSymbolPreviewImage( &mPreviewExpressionContext, Qgis::SymbolPreviewFlag::FlagIncludeCrosshairsForMarkerSymbols, QgsScreenProperties( screen() ) );
522 lblPreview->setPixmap( QPixmap::fromImage( preview ) );
523 // Hope this is a appropriate place
524 if ( !mBlockModified )
525 emit symbolModified();
526}
527
529{
530 // get current layer item and update its icon
531 SymbolLayerItem *item = currentLayerItem();
532 if ( item )
533 item->updatePreview();
534 // update also preview of the whole symbol
536}
537
538SymbolLayerItem *QgsSymbolSelectorWidget::currentLayerItem()
539{
540 const QModelIndex idx = layersTree->currentIndex();
541 if ( !idx.isValid() )
542 return nullptr;
543
544 SymbolLayerItem *item = static_cast<SymbolLayerItem *>( mSymbolLayersModel->itemFromIndex( idx ) );
545 if ( !item->isLayer() )
546 return nullptr;
547
548 return item;
549}
550
551QgsSymbolLayer *QgsSymbolSelectorWidget::currentLayer()
552{
553 const QModelIndex idx = layersTree->currentIndex();
554 if ( !idx.isValid() )
555 return nullptr;
556
557 SymbolLayerItem *item = static_cast<SymbolLayerItem *>( mSymbolLayersModel->itemFromIndex( idx ) );
558 if ( item->isLayer() )
559 return item->layer();
560
561 return nullptr;
562}
563
565{
566 updateUi();
567
568 SymbolLayerItem *currentItem = static_cast<SymbolLayerItem *>( mSymbolLayersModel->itemFromIndex( layersTree->currentIndex() ) );
569 if ( !currentItem )
570 return;
571
572 if ( currentItem->isLayer() )
573 {
574 SymbolLayerItem *parent = static_cast<SymbolLayerItem *>( currentItem->parent() );
575 mDataDefineRestorer = std::make_unique<DataDefinedRestorer>( parent->symbol(), currentItem->layer() );
576 QgsLayerPropertiesWidget *layerProp = new QgsLayerPropertiesWidget( currentItem->layer(), parent->symbol(), mVectorLayer );
577 layerProp->setDockMode( this->dockMode() );
578 layerProp->setContext( mContext );
579 setWidget( layerProp );
580 connect( layerProp, &QgsLayerPropertiesWidget::changed, mDataDefineRestorer.get(), &DataDefinedRestorer::restore );
582 // This connection when layer type is changed
584
585 connectChildPanel( layerProp );
586 }
587 else
588 {
589 // then it must be a symbol
590 mDataDefineRestorer.reset();
592 currentItem->symbol()->setLayer( mVectorLayer );
594 // Now populate symbols of that type using the symbols list widget:
595 QgsSymbolsListWidget *symbolsList = new QgsSymbolsListWidget( currentItem->symbol(), mStyle, mAdvancedMenu, this, mVectorLayer );
596 symbolsList->setContext( mContext );
597
598 setWidget( symbolsList );
600 }
601 updateLockButton();
602}
603
605{
606 SymbolLayerItem *currentItem = static_cast<SymbolLayerItem *>( mSymbolLayersModel->itemFromIndex( layersTree->currentIndex() ) );
607 if ( !currentItem || currentItem->isLayer() )
608 return;
609 // disconnect to avoid recreating widget
610 disconnect( layersTree->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsSymbolSelectorWidget::layerChanged );
611 if ( currentItem->parent() )
612 {
613 // it is a sub-symbol
614 QgsSymbol *symbol = currentItem->symbol();
615 SymbolLayerItem *parent = static_cast<SymbolLayerItem *>( currentItem->parent() );
616 parent->removeRow( 0 );
617 loadSymbol( symbol, parent );
618 layersTree->setCurrentIndex( parent->child( 0 )->index() );
619 parent->updatePreview();
620 }
621 else
622 {
623 //it is the symbol itself
624 reloadSymbol();
625 const QModelIndex newIndex = layersTree->model()->index( 0, 0 );
626 layersTree->setCurrentIndex( newIndex );
627 }
629 // connect it back once things are set
630 connect( layersTree->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsSymbolSelectorWidget::layerChanged );
631}
632
633void QgsSymbolSelectorWidget::setWidget( QWidget *widget )
634{
635 const int index = stackedWidget->addWidget( widget );
636 stackedWidget->setCurrentIndex( index );
637 if ( mPresentWidget )
638 mPresentWidget->deleteLater();
639 mPresentWidget = widget;
640}
641
642void QgsSymbolSelectorWidget::updateLockButton()
643{
644 QgsSymbolLayer *layer = currentLayer();
645 if ( !layer )
646 return;
647 mLockColorAction->setChecked( layer->isLocked() );
648 mLockSelectionColorAction->setChecked( layer->userFlags() & Qgis::SymbolLayerUserFlag::DisableSelectionRecoloring );
649
650 updateLockButtonIcon();
651}
652
653void QgsSymbolSelectorWidget::updateLockButtonIcon()
654{
655 if ( mLockColorAction->isChecked() && mLockSelectionColorAction->isChecked() )
656 btnLock->setIcon( QgsApplication::getThemeIcon( u"locked.svg"_s ) );
657 else if ( mLockColorAction->isChecked() )
658 btnLock->setIcon( QgsApplication::getThemeIcon( u"mIconColorLocked.svg"_s ) );
659 else if ( mLockSelectionColorAction->isChecked() )
660 btnLock->setIcon( QgsApplication::getThemeIcon( u"mIconSelectLocked.svg"_s ) );
661 else
662 btnLock->setIcon( QgsApplication::getThemeIcon( u"unlocked.svg"_s ) );
663}
664
666{
667 const QModelIndex idx = layersTree->currentIndex();
668 if ( !idx.isValid() )
669 return;
670
671 int insertIdx = -1;
672 SymbolLayerItem *item = static_cast<SymbolLayerItem *>( mSymbolLayersModel->itemFromIndex( idx ) );
673 if ( item->isLayer() )
674 {
675 insertIdx = item->row();
676 item = static_cast<SymbolLayerItem *>( item->parent() );
677 }
678
679 QgsSymbol *parentSymbol = item->symbol();
680
681 // save data-defined values at marker level
682 const QgsProperty ddSize( parentSymbol->type() == Qgis::SymbolType::Marker ? static_cast<QgsMarkerSymbol *>( parentSymbol )->dataDefinedSize() : QgsProperty() );
683 const QgsProperty ddAngle( parentSymbol->type() == Qgis::SymbolType::Marker ? static_cast<QgsMarkerSymbol *>( parentSymbol )->dataDefinedAngle() : QgsProperty() );
684 const QgsProperty ddWidth( parentSymbol->type() == Qgis::SymbolType::Line ? static_cast<QgsLineSymbol *>( parentSymbol )->dataDefinedWidth() : QgsProperty() );
685
686 QgsSymbolLayer *newLayerPtr = nullptr;
687 {
688 std::unique_ptr< QgsSymbolLayer > newLayer = QgsSymbolLayerRegistry::defaultSymbolLayer( parentSymbol->type() );
689 newLayerPtr = newLayer.get();
690 if ( insertIdx == -1 )
691 parentSymbol->appendSymbolLayer( newLayer.release() );
692 else
693 parentSymbol->insertSymbolLayer( item->rowCount() - insertIdx, newLayer.release() );
694 }
695
696 // restore data-defined values at marker level
697 if ( ddSize )
698 static_cast<QgsMarkerSymbol *>( parentSymbol )->setDataDefinedSize( ddSize );
699 if ( ddAngle )
700 static_cast<QgsMarkerSymbol *>( parentSymbol )->setDataDefinedAngle( ddAngle );
701 if ( ddWidth )
702 static_cast<QgsLineSymbol *>( parentSymbol )->setDataDefinedWidth( ddWidth );
703
704 // TODO -- using newLayerPtr is not safe in some circumstances here. This needs reworking so that SymbolLayerItem does has
705 // its own owned QgsSymbolLayer clone, and isn't reliant on a pointer to the object owned by parentSymbol.
706 SymbolLayerItem *newLayerItem = new SymbolLayerItem( newLayerPtr, parentSymbol->type(), mVectorLayer, screen() ); // cppcheck-suppress invalidLifetime
707 item->insertRow( insertIdx == -1 ? 0 : insertIdx, newLayerItem );
708 item->updatePreview();
709
710 layersTree->setCurrentIndex( mSymbolLayersModel->indexFromItem( newLayerItem ) );
711 updateUi();
713}
714
716{
717 SymbolLayerItem *item = currentLayerItem();
718 const int row = item->row();
719 SymbolLayerItem *parent = static_cast<SymbolLayerItem *>( item->parent() );
720
721 const int layerIdx = parent->rowCount() - row - 1; // IMPORTANT
722 QgsSymbol *parentSymbol = parent->symbol();
723 QgsSymbolLayer *tmpLayer = parentSymbol->takeSymbolLayer( layerIdx );
724
725 parent->removeRow( row );
726 parent->updatePreview();
727
728 const QModelIndex newIdx = parent->child( 0 )->index();
729 layersTree->setCurrentIndex( newIdx );
730
731 updateUi();
733 //finally delete the removed layer pointer
734 delete tmpLayer;
735}
736
738{
739 moveLayerByOffset( +1 );
740}
741
743{
744 moveLayerByOffset( -1 );
745}
746
747void QgsSymbolSelectorWidget::moveLayerByOffset( int offset )
748{
749 SymbolLayerItem *item = currentLayerItem();
750 if ( !item )
751 return;
752 const int row = item->row();
753
754 SymbolLayerItem *parent = static_cast<SymbolLayerItem *>( item->parent() );
755 QgsSymbol *parentSymbol = parent->symbol();
756
757 const int layerIdx = parent->rowCount() - row - 1;
758 // switch layers
759 QgsSymbolLayer *tmpLayer = parentSymbol->takeSymbolLayer( layerIdx );
760 parentSymbol->insertSymbolLayer( layerIdx - offset, tmpLayer );
761
762 QList<QStandardItem *> rowItems = parent->takeRow( row );
763 parent->insertRows( row + offset, rowItems );
764 parent->updatePreview();
765
766 const QModelIndex newIdx = rowItems[0]->index();
767 layersTree->setCurrentIndex( newIdx );
768
770 updateUi();
771}
772
774{
775 QgsSymbolLayer *layer = currentLayer();
776 if ( !layer )
777 return;
778 layer->setLocked( mLockColorAction->isChecked() );
779 updateLockButtonIcon();
780 emit symbolModified();
781}
782
784{
785 const QModelIndex idx = layersTree->currentIndex();
786 if ( !idx.isValid() )
787 return;
788
789 SymbolLayerItem *item = static_cast<SymbolLayerItem *>( mSymbolLayersModel->itemFromIndex( idx ) );
790 if ( !item->isLayer() )
791 return;
792
793 QgsSymbolLayer *source = item->layer();
794
795 const int insertIdx = item->row();
796 item = static_cast<SymbolLayerItem *>( item->parent() );
797
798 QgsSymbol *parentSymbol = item->symbol();
799
800 QgsSymbolLayer *newLayer = source->clone();
802 if ( insertIdx == -1 )
803 parentSymbol->appendSymbolLayer( newLayer );
804 else
805 parentSymbol->insertSymbolLayer( item->rowCount() - insertIdx, newLayer );
806
807 SymbolLayerItem *newLayerItem = new SymbolLayerItem( newLayer, parentSymbol->type(), mVectorLayer, screen() );
808 item->insertRow( insertIdx == -1 ? 0 : insertIdx, newLayerItem );
809 if ( newLayer->subSymbol() )
810 {
811 loadSymbol( newLayer->subSymbol(), newLayerItem );
812 layersTree->setExpanded( newLayerItem->index(), true );
813 }
814 item->updatePreview();
815
816 layersTree->setCurrentIndex( mSymbolLayersModel->indexFromItem( newLayerItem ) );
817 updateUi();
819}
820
822{
823 SymbolLayerItem *item = currentLayerItem();
824
825 if ( item->rowCount() > 0 )
826 {
827 item->removeRow( 0 );
828 }
829 QgsSymbol *symbol = static_cast<SymbolLayerItem *>( item->parent() )->symbol();
830
831 // update symbol layer item
832 item->setLayer( newLayer, symbol->type() );
833 // When it is a marker symbol
834 if ( newLayer->subSymbol() )
835 {
836 loadSymbol( newLayer->subSymbol(), item );
837 layersTree->setExpanded( item->index(), true );
838 }
839
840 // Change the symbol at last to avoid deleting item's layer
841 const int layerIdx = item->parent()->rowCount() - item->row() - 1;
842 symbol->changeSymbolLayer( layerIdx, newLayer );
843
844 item->updatePreview();
846 // Important: This lets the layer have its own layer properties widget
847 layerChanged();
848}
849
851 : QDialog( parent )
852{
853 setLayout( new QVBoxLayout() );
854
855 mSelectorWidget = new QgsSymbolSelectorWidget( symbol, style, vl, this );
856 mButtonBox = new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok );
857
858 connect( mButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
859 connect( mButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
860 connect( mButtonBox, &QDialogButtonBox::helpRequested, this, &QgsSymbolSelectorDialog::showHelp );
861
862 layout()->addWidget( mSelectorWidget );
863 layout()->addWidget( mButtonBox );
864
865 connect( mSelectorWidget, &QgsPanelWidget::panelAccepted, this, &QDialog::reject );
866
867 mSelectorWidget->setMinimumSize( 460, 560 );
868 setObjectName( u"SymbolSelectorDialog"_s );
870
871 // Can be embedded in renderer properties dialog
872 if ( embedded )
873 {
874 mButtonBox->hide();
875 layout()->setContentsMargins( 0, 0, 0, 0 );
876 }
877 else
878 {
879 setWindowTitle( tr( "Symbol Selector" ) );
880 }
881 mSelectorWidget->setDockMode( embedded );
882}
883
885{
886 return mSelectorWidget->advancedMenu();
887}
888
890{
891 mSelectorWidget->setContext( context );
892}
893
895{
896 return mSelectorWidget->context();
897}
898
900{
901 return mSelectorWidget->symbol();
902}
903
905{
906 // Ignore the ESC key to avoid close the dialog without the properties window
907 if ( !isWindow() && e->key() == Qt::Key_Escape )
908 {
909 e->ignore();
910 }
911 else
912 {
913 QDialog::keyPressEvent( e );
914 }
915}
916
917void QgsSymbolSelectorDialog::reloadSymbol()
918{
919 mSelectorWidget->reloadSymbol();
920}
921
922void QgsSymbolSelectorDialog::loadSymbol( QgsSymbol *symbol, SymbolLayerItem *parent )
923{
924 mSelectorWidget->loadSymbol( symbol, parent );
925}
926
927void QgsSymbolSelectorDialog::updateUi()
928{
929 mSelectorWidget->updateUi();
930}
931
932void QgsSymbolSelectorDialog::updateLockButton()
933{
934 mSelectorWidget->updateLockButton();
935}
936
937SymbolLayerItem *QgsSymbolSelectorDialog::currentLayerItem()
938{
939 return mSelectorWidget->currentLayerItem();
940}
941
942QgsSymbolLayer *QgsSymbolSelectorDialog::currentLayer()
943{
944 return mSelectorWidget->currentLayer();
945}
946
947void QgsSymbolSelectorDialog::moveLayerByOffset( int offset )
948{
949 mSelectorWidget->moveLayerByOffset( offset );
950}
951
952void QgsSymbolSelectorDialog::setWidget( QWidget *widget )
953{
954 mSelectorWidget->setWidget( widget );
955}
956
958{
959 mSelectorWidget->moveLayerDown();
960}
961
963{
964 mSelectorWidget->moveLayerUp();
965}
966
968{
969 mSelectorWidget->addLayer();
970}
971
973{
974 mSelectorWidget->removeLayer();
975}
976
978{
979 mSelectorWidget->lockLayer();
980}
981
983{
984 mSelectorWidget->duplicateLayer();
985}
986
988{
989 mSelectorWidget->layerChanged();
990}
991
993{
994 mSelectorWidget->updateLayerPreview();
995}
996
998{
999 mSelectorWidget->updatePreview();
1000}
1001
1003{
1004 mSelectorWidget->symbolChanged();
1005}
1006
1008{
1009 mSelectorWidget->changeLayer( layer );
1010}
1011
1012QDialogButtonBox *QgsSymbolSelectorDialog::buttonBox() const
1013{
1014 return mButtonBox;
1015}
1016
1017void QgsSymbolSelectorDialog::showHelp()
1018{
1019 QgsHelp::openHelp( u"style_library/symbol_selector.html"_s );
1020}
1021
1022void QgsSymbolSelectorWidget::projectDataChanged()
1023{
1024 mBlockModified = true;
1025 symbolChanged();
1026 updatePreview();
1027 mBlockModified = false;
1028}
1029
1030void QgsSymbolSelectorWidget::layersAboutToBeRemoved( const QList<QgsMapLayer *> &layers )
1031{
1032 if ( mVectorLayer && layers.contains( mVectorLayer ) )
1033 {
1034 disconnect( QgsProject::instance(), &QgsProject::projectColorsChanged, this, &QgsSymbolSelectorWidget::projectDataChanged );
1035 }
1036}
QFlags< SymbolLayerUserFlag > SymbolLayerUserFlags
Symbol layer user flags.
Definition qgis.h:929
@ Millimeters
Millimeters.
Definition qgis.h:5256
@ FlagIncludeCrosshairsForMarkerSymbols
Include a crosshairs reference image in the background of marker symbol previews.
Definition qgis.h:882
@ DisableSelectionRecoloring
If present, indicates that the symbol layer should not be recolored when rendering selected features.
Definition qgis.h:920
SymbolType
Symbol types.
Definition qgis.h:629
@ Marker
Marker symbol.
Definition qgis.h:630
@ Line
Line symbol.
Definition qgis.h:631
@ Fill
Fill symbol.
Definition qgis.h:632
@ Hybrid
Hybrid symbol.
Definition qgis.h:633
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:6499
static QgsSymbolLayerRegistry * symbolLayerRegistry()
Returns the application's symbol layer registry, used for managing symbol layers.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsImageCache * imageCache()
Returns the application's image cache, used for caching resampled versions of raster images.
static QgsSvgCache * svgCache()
Returns the application's SVG cache, used for caching SVG images and handling parameter replacement w...
static QString iconPath(const QString &iconFile)
Returns path to the desired icon file.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
void appendScopes(const QList< QgsExpressionContextScope * > &scopes)
Appends a list of scopes to the end of the context.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Wraps a request for features to a vector layer (or directly its vector data provider).
static void enableAutoGeometryRestore(QWidget *widget, const QString &key=QString())
Register the widget to allow its position to be automatically saved and restored when open and closed...
Definition qgsgui.cpp:224
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition qgshelp.cpp:41
void remoteImageFetched(const QString &url)
Emitted when the cache has finished retrieving an image file from a remote url.
A widget which allows configuration of the properties of a single QgsSymbolLayer.
void setDockMode(bool dockMode) override
Set the widget in dock mode which tells the widget to emit panel widgets and not open dialogs.
void changeLayer(QgsSymbolLayer *layer)
Emitted when the symbol layer is changed in the widget.
void setContext(const QgsSymbolWidgetContext &context)
Sets the context in which the symbol widget is shown, e.g., the associated map canvas and expression ...
void changed()
Emitted when the symbol layer configuration is changed in the widget.
Abstract base class for line symbol layers.
A line symbol type, for rendering LineString and MultiLineString geometries.
QgsProperty dataDefinedWidth() const
Returns data defined width for whole symbol (including all symbol layers).
Abstract base class for marker symbol layers.
A marker symbol type, for rendering Point and MultiPoint geometries.
QgsProperty dataDefinedAngle() const
Returns data defined angle for whole symbol (including all symbol layers).
QgsProperty dataDefinedSize() const
Returns data defined size for whole symbol (including all symbol layers).
bool dockMode() const
Returns the dock mode state.
void panelAccepted(QgsPanelWidget *panel)
Emitted when the panel is accepted by the user.
void connectChildPanel(QgsPanelWidget *panel)
Connect the given sub panel widgets showPanel signals to this current panels main showPanel event to ...
QgsPanelWidget(QWidget *parent=nullptr)
Base class for any widget that can be shown as an inline panel.
void widgetChanged()
Emitted when the widget state changes.
void setPanelTitle(const QString &panelTitle)
Set the title of the panel when shown in the interface.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:112
static QgsProject * instance()
Returns the QgsProject singleton instance.
void layersWillBeRemoved(const QStringList &layerIds)
Emitted when one or more layers are about to be removed from the registry.
void projectColorsChanged()
Emitted whenever the project's color scheme has been changed.
A store for object properties.
Stores properties relating to a screen.
A database of saved style entities, including symbols, color ramps, text formats and others.
Definition qgsstyle.h:89
void remoteSvgFetched(const QString &url)
Emitted when the cache has finished retrieving an SVG file from a remote url.
static std::unique_ptr< QgsSymbolLayer > defaultSymbolLayer(Qgis::SymbolType type)
create a new instance of symbol layer for specified symbol type with default settings
QgsSymbolLayerAbstractMetadata * symbolLayerMetadata(const QString &name) const
Returns metadata for specified symbol layer. Returns nullptr if not found.
static QIcon symbolLayerPreviewIcon(const QgsSymbolLayer *layer, Qgis::RenderUnit u, QSize size, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::SymbolType parentSymbolType=Qgis::SymbolType::Hybrid, QgsMapLayer *mapLayer=nullptr, const QgsScreenProperties &screen=QgsScreenProperties())
Draws a symbol layer preview to an icon.
static QPixmap symbolPreviewPixmap(const QgsSymbol *symbol, QSize size, int padding=0, QgsRenderContext *customContext=nullptr, bool selected=false, const QgsExpressionContext *expressionContext=nullptr, const QgsLegendPatchShape *shape=nullptr, const QgsScreenProperties &screen=QgsScreenProperties())
Returns a pixmap preview for a color ramp.
static void resetSymbolLayerIds(QgsSymbol *symbol)
Regenerate recursively unique id from all symbol symbol layers.
Abstract base class for symbol layers.
virtual QgsSymbolLayer * clone() const =0
Shall be reimplemented by subclasses to create a deep copy of the instance.
Qgis::SymbolType type() const
bool isLocked() const
Returns true if the symbol layer colors are locked and the layer will ignore any symbol-level color c...
void setUserFlags(Qgis::SymbolLayerUserFlags flags)
Sets user-controlled flags which control the symbol layer's behavior.
virtual QgsSymbol * subSymbol()
Returns the symbol's sub symbol, if present.
Qgis::SymbolLayerUserFlags userFlags() const
Returns user-controlled flags which control the symbol layer's behavior.
void setLocked(bool locked)
Sets whether the layer's colors are locked.
void setContext(const QgsSymbolWidgetContext &context)
Sets the context in which the symbol widget is shown, e.g., the associated map canvas and expression ...
QgsSymbolSelectorDialog(QgsSymbol *symbol, QgsStyle *style, QgsVectorLayer *vl, QWidget *parent=nullptr, bool embedded=false)
Constructor for QgsSymbolSelectorDialog.
QgsSymbolWidgetContext context() const
Returns the context in which the symbol widget is shown, e.g., the associated map canvas and expressi...
QMenu * advancedMenu()
Returns menu for "advanced" button - create it if doesn't exist and show the advanced button.
void symbolChanged()
Slot to update tree when a new symbol from style.
QDialogButtonBox * buttonBox() const
Returns a reference to the dialog's button box.
QgsSymbol * symbol()
Returns the symbol that is currently active in the widget.
void keyPressEvent(QKeyEvent *e) override
void duplicateLayer()
Duplicates the current symbol layer and places the duplicated layer above the current symbol layer.
void changeLayer(QgsSymbolLayer *layer)
Alters tree and sets proper widget when Layer Type is changed.
void loadSymbol(QgsSymbol *symbol, SymbolLayerItem *parent=nullptr)
Loads the given symbol into the widget.
Symbol selector widget that can be used to select and build a symbol.
void loadSymbol(QgsSymbol *symbol, SymbolLayerItem *parent=nullptr)
Loads the given symbol into the widget.
void symbolChanged()
Slot to update tree when a new symbol from style.
void addLayer()
Add a symbol layer to the bottom of the stack.
QMenu * advancedMenu()
Returns menu for "advanced" button - create it if doesn't exist and show the advanced button.
void layerChanged()
Called when the layer changes in the widget.
void changeLayer(QgsSymbolLayer *layer)
Alters tree and sets proper widget when Layer Type is changed.
void updatePreview()
Update the preview of the whole symbol in the interface.
QgsSymbolSelectorWidget(QgsSymbol *symbol, QgsStyle *style, QgsVectorLayer *vl, QWidget *parent=nullptr)
Symbol selector widget that can be used to select and build a symbol.
void removeLayer()
Remove the current active symbol layer.
QgsSymbolWidgetContext context() const
Returns the context in which the symbol widget is shown, e.g., the associated map canvas and expressi...
void moveLayerDown()
Move the active symbol layer down.
void symbolModified()
Emitted when a symbol is modified in the widget.
void duplicateLayer()
Duplicates the current symbol layer and places the duplicated layer above the current symbol layer.
void lockLayer()
Lock the current active symbol layer.
void updateLayerPreview()
Update the single symbol layer preview in the widget.
void setContext(const QgsSymbolWidgetContext &context)
Sets the context in which the symbol widget is shown, e.g., the associated map canvas and expression ...
QgsSymbol * symbol()
Returns the symbol that is currently active in the widget.
void moveLayerUp()
Move the active symbol layer up.
static QgsSymbolSelectorWidget * createWidgetWithSymbolOwnership(std::unique_ptr< QgsSymbol > symbol, QgsStyle *style, QgsVectorLayer *vl, QWidget *parent=nullptr)
Creates a QgsSymbolSelectorWidget which takes ownership of a symbol and maintains the ownership for t...
Contains settings which reflect the context in which a symbol (or renderer) widget is shown,...
Abstract base class for all rendered symbols.
Definition qgssymbol.h:231
bool appendSymbolLayer(QgsSymbolLayer *layer)
Appends a symbol layer at the end of the current symbol layer list.
bool insertSymbolLayer(int index, QgsSymbolLayer *layer)
Inserts a symbol layer to specified index.
QgsSymbolLayer * takeSymbolLayer(int index)
Removes a symbol layer from the list and returns a pointer to it.
Qgis::SymbolType type() const
Returns the symbol's type.
Definition qgssymbol.h:294
A widget which presents symbol-level properties (such as size), and allows selection of symbols from ...
void changed()
Emitted when the symbol is modified in the widget.
Represents a vector layer which manages a vector based dataset.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:7451
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7450