QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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 )
166 symbolLayerPreviewIcon( mLayer, Qgis::RenderUnit::Millimeters, mSize, QgsMapUnitScale(), mSymbol ? mSymbol->type() : mSymbolType, mVectorLayer, QgsScreenProperties( mScreen.data() ) );
167 else
168 {
169 QgsExpressionContext expContext;
171 icon = QIcon( QgsSymbolLayerUtils::symbolPreviewPixmap( mSymbol, mSize, 0, nullptr, false, &expContext, nullptr, QgsScreenProperties( mScreen.data() ) ) );
172 }
173 setIcon( icon );
174
175 if ( auto *lParent = parent() )
176 static_cast<SymbolLayerItem *>( lParent )->updatePreview();
177 }
178
179 int type() const override { return SYMBOL_LAYER_ITEM_TYPE; }
180 bool isLayer() const { return mIsLayer; }
181
182 // returns the symbol pointer; helpful in determining a layer's parent symbol
183 QgsSymbol *symbol() { return mSymbol; }
184
185 QgsSymbolLayer *layer() { return mLayer; }
186
187 QVariant data( int role ) const override
188 {
189 if ( role == Qt::DisplayRole || role == Qt::EditRole )
190 {
191 if ( mIsLayer )
192 {
193 QgsSymbolLayerAbstractMetadata *m = QgsApplication::symbolLayerRegistry()->symbolLayerMetadata( mLayer->layerType() );
194 if ( m )
195 return m->visibleName();
196 else
197 return QString();
198 }
199 else
200 {
201 switch ( mSymbol->type() )
202 {
204 return QCoreApplication::translate( "SymbolLayerItem", "Marker" );
206 return QCoreApplication::translate( "SymbolLayerItem", "Fill" );
208 return QCoreApplication::translate( "SymbolLayerItem", "Line" );
209 default:
210 return "Symbol";
211 }
212 }
213 }
214 else if ( role == Qt::ForegroundRole && mIsLayer )
215 {
216 if ( !mLayer->enabled() )
217 {
218 QPalette pal = qApp->palette();
219 QBrush brush = QStandardItem::data( role ).value<QBrush>();
220 brush.setColor( pal.color( QPalette::Disabled, QPalette::WindowText ) );
221 return brush;
222 }
223 else
224 {
225 return QVariant();
226 }
227 }
228
229 // if ( role == Qt::SizeHintRole )
230 // return QVariant( QSize( 32, 32 ) );
231 if ( role == Qt::CheckStateRole )
232 return QVariant(); // could be true/false
233 return QStandardItem::data( role );
234 }
235
236 protected:
237 QgsSymbolLayer *mLayer = nullptr;
238 QgsSymbol *mSymbol = nullptr;
239 QPointer<QgsVectorLayer> mVectorLayer;
240 bool mIsLayer = false;
241 QSize mSize;
243 QPointer<QScreen> mScreen;
244};
245
247
249
251 : QgsPanelWidget( parent )
252 , mStyle( style )
253 , mSymbol( symbol )
254 , mVectorLayer( vl )
255{
256#ifdef Q_OS_MAC
257 setWindowModality( Qt::WindowModal );
258#endif
259
260 setupUi( this );
261 this->layout()->setContentsMargins( 0, 0, 0, 0 );
262
263 layersTree->setMaximumHeight( static_cast<int>( Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 7 ) );
264 layersTree->setMinimumHeight( layersTree->maximumHeight() );
265 lblPreview->setMaximumWidth( layersTree->maximumHeight() );
266
267 // setup icons
268 btnAddLayer->setIcon( QIcon( QgsApplication::iconPath( "symbologyAdd.svg" ) ) );
269 btnRemoveLayer->setIcon( QIcon( QgsApplication::iconPath( "symbologyRemove.svg" ) ) );
270 QIcon iconLock;
271 iconLock.addFile( QgsApplication::iconPath( u"locked.svg"_s ), QSize(), QIcon::Normal, QIcon::On );
272 iconLock.addFile( QgsApplication::iconPath( u"locked.svg"_s ), QSize(), QIcon::Active, QIcon::On );
273 iconLock.addFile( QgsApplication::iconPath( u"unlocked.svg"_s ), QSize(), QIcon::Normal, QIcon::Off );
274 iconLock.addFile( QgsApplication::iconPath( u"unlocked.svg"_s ), QSize(), QIcon::Active, QIcon::Off );
275
276 QIcon iconColorLock;
277 iconColorLock.addFile( QgsApplication::iconPath( u"mIconColorLocked.svg"_s ), QSize(), QIcon::Normal, QIcon::On );
278 iconColorLock.addFile( QgsApplication::iconPath( u"mIconColorLocked.svg"_s ), QSize(), QIcon::Active, QIcon::On );
279 iconColorLock.addFile( QgsApplication::iconPath( u"mIconColorUnlocked.svg"_s ), QSize(), QIcon::Normal, QIcon::Off );
280 iconColorLock.addFile( QgsApplication::iconPath( u"mIconColorUnlocked.svg"_s ), QSize(), QIcon::Active, QIcon::Off );
281
282 mLockColorAction = new QAction( tr( "Lock Color" ), this );
283 mLockColorAction->setToolTip( tr( "Avoid changing the color of the layer when the symbol color is changed" ) );
284 mLockColorAction->setCheckable( true );
285 mLockColorAction->setIcon( iconColorLock );
286
287 QIcon iconSelectLock;
288 iconSelectLock.addFile( QgsApplication::iconPath( u"mIconSelectLocked.svg"_s ), QSize(), QIcon::Normal, QIcon::On );
289 iconSelectLock.addFile( QgsApplication::iconPath( u"mIconSelectLocked.svg"_s ), QSize(), QIcon::Active, QIcon::On );
290 iconSelectLock.addFile( QgsApplication::iconPath( u"mIconSelectUnlocked.svg"_s ), QSize(), QIcon::Normal, QIcon::Off );
291 iconSelectLock.addFile( QgsApplication::iconPath( u"mIconSelectUnlocked.svg"_s ), QSize(), QIcon::Active, QIcon::Off );
292
293 mLockSelectionColorAction = new QAction( tr( "Lock Color When Selected" ), this );
294 mLockSelectionColorAction->setToolTip( tr( "Avoid changing the color of the layer when a feature is selected" ) );
295 mLockSelectionColorAction->setCheckable( true );
296 mLockSelectionColorAction->setIcon( iconSelectLock );
297
298 QMenu *lockMenu = new QMenu( this );
299 lockMenu->addAction( mLockColorAction );
300 lockMenu->addAction( mLockSelectionColorAction );
301 btnLock->setMenu( lockMenu );
302 btnLock->setPopupMode( QToolButton::InstantPopup );
303
304 btnDuplicate->setIcon( QIcon( QgsApplication::iconPath( "mActionDuplicateLayer.svg" ) ) );
305 btnUp->setIcon( QIcon( QgsApplication::iconPath( "mActionArrowUp.svg" ) ) );
306 btnDown->setIcon( QIcon( QgsApplication::iconPath( "mActionArrowDown.svg" ) ) );
307
308 mSymbolLayersModel = new QStandardItemModel( layersTree );
309 // Set the symbol
310 layersTree->setModel( mSymbolLayersModel );
311 layersTree->setHeaderHidden( true );
312
313 //get first feature from layer for previews
314 if ( mVectorLayer )
315 {
316#if 0 // this is too expensive to do for many providers. TODO revisit when support for connection timeouts is complete across all providers
317 // short timeout for request - it doesn't really matter if we don't get the feature, and this call is blocking UI
318 QgsFeatureIterator it = mVectorLayer->getFeatures( QgsFeatureRequest().setLimit( 1 ).setConnectionTimeout( 100 ) );
319 it.nextFeature( mPreviewFeature );
320#endif
321 mPreviewExpressionContext.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( mVectorLayer ) );
322#if 0
323 mPreviewExpressionContext.setFeature( mPreviewFeature );
324#endif
325 }
326 else
327 {
328 mPreviewExpressionContext.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) );
329 }
330
331 QItemSelectionModel *selModel = layersTree->selectionModel();
332 connect( selModel, &QItemSelectionModel::currentChanged, this, &QgsSymbolSelectorWidget::layerChanged );
333
334 loadSymbol( mSymbol, static_cast<SymbolLayerItem *>( mSymbolLayersModel->invisibleRootItem() ) );
336
337 connect( btnUp, &QAbstractButton::clicked, this, &QgsSymbolSelectorWidget::moveLayerUp );
338 connect( btnDown, &QAbstractButton::clicked, this, &QgsSymbolSelectorWidget::moveLayerDown );
339 connect( btnAddLayer, &QAbstractButton::clicked, this, &QgsSymbolSelectorWidget::addLayer );
340 connect( btnRemoveLayer, &QAbstractButton::clicked, this, &QgsSymbolSelectorWidget::removeLayer );
341 connect( mLockColorAction, &QAction::toggled, this, &QgsSymbolSelectorWidget::lockLayer );
342 connect( mLockSelectionColorAction, &QAction::toggled, this, [this]( bool checked ) {
343 QgsSymbolLayer *layer = currentLayer();
344 if ( !layer )
345 return;
346
347 Qgis::SymbolLayerUserFlags flags = layer->userFlags();
349 layer->setUserFlags( flags );
350 updateLockButtonIcon();
351 emit symbolModified();
352 } );
353 connect( btnDuplicate, &QAbstractButton::clicked, this, &QgsSymbolSelectorWidget::duplicateLayer );
355
356 updateLockButtonIcon();
357
358 updateUi();
359
360 // set symbol as active item in the tree
361 const QModelIndex newIndex = layersTree->model()->index( 0, 0 );
362 layersTree->setCurrentIndex( newIndex );
363
364 setPanelTitle( tr( "Symbol Selector" ) );
365
366 // when a remote svg has been fetched, update the widget's previews
367 // this is required if the symbol utilizes remote svgs, and the current previews
368 // have been generated using the temporary "downloading" svg. In this case
369 // we require the preview to be regenerated to use the correct fetched
370 // svg
371 connect( QgsApplication::svgCache(), &QgsSvgCache::remoteSvgFetched, this, &QgsSymbolSelectorWidget::projectDataChanged );
372
373 // when a remote image has been fetched, update the widget's previews
374 // this is required if the symbol utilizes remote images, and the current previews
375 // have been generated using the temporary "downloading" image. In this case
376 // we require the preview to be regenerated to use the correct fetched
377 // image
378 connect( QgsApplication::imageCache(), &QgsImageCache::remoteImageFetched, this, &QgsSymbolSelectorWidget::projectDataChanged );
379
380 // if project color scheme changes, we need to redraw symbols - they may use project colors and accordingly
381 // need updating to reflect the new colors
382 connect( QgsProject::instance(), &QgsProject::projectColorsChanged, this, &QgsSymbolSelectorWidget::projectDataChanged );
383
384 connect( QgsProject::instance(), static_cast<void ( QgsProject::* )( const QList<QgsMapLayer *> &layers )>( &QgsProject::layersWillBeRemoved ), this, &QgsSymbolSelectorWidget::layersAboutToBeRemoved );
385}
386
388{
389 QgsSymbolSelectorWidget *widget = new QgsSymbolSelectorWidget( symbol.get(), style, vl, parent );
390 // transfer ownership of symbol to widget, so that we are guaranteed it will last for the duration of the widget
391 widget->mOwnedSymbol = std::move( symbol );
392 return widget;
393}
394
396{
397 if ( !mAdvancedMenu )
398 {
399 mAdvancedMenu = new QMenu( this );
400 // Brute force method to activate the Advanced menu
401 layerChanged();
402 }
403 return mAdvancedMenu;
404}
405
407{
408 mContext = context;
409
410 if ( auto *lExpressionContext = mContext.expressionContext() )
411 {
412 mPreviewExpressionContext = *lExpressionContext;
413 if ( mVectorLayer )
414 mPreviewExpressionContext.appendScope( QgsExpressionContextUtils::layerScope( mVectorLayer ) );
415
416 mPreviewExpressionContext.setFeature( mPreviewFeature );
417 }
418
419 QWidget *widget = stackedWidget->currentWidget();
420 if ( QgsLayerPropertiesWidget *layerProp = qobject_cast<QgsLayerPropertiesWidget *>( widget ) )
421 {
422 layerProp->setContext( context );
423 }
424 else if ( QgsSymbolsListWidget *listWidget = qobject_cast<QgsSymbolsListWidget *>( widget ) )
425 {
426 listWidget->setContext( context );
427 }
428
429 layerChanged();
431}
432
434{
435 return mContext;
436}
437
438void QgsSymbolSelectorWidget::loadSymbol( QgsSymbol *symbol, SymbolLayerItem *parent )
439{
440 if ( !symbol )
441 return;
442
443 if ( !parent )
444 {
445 mSymbol = symbol;
446 mSymbolLayersModel->clear();
447 parent = static_cast<SymbolLayerItem *>( mSymbolLayersModel->invisibleRootItem() );
448 }
449
450 SymbolLayerItem *symbolItem = new SymbolLayerItem( symbol, mVectorLayer, screen() );
451 QFont boldFont = symbolItem->font();
452 boldFont.setBold( true );
453 symbolItem->setFont( boldFont );
454 parent->appendRow( symbolItem );
455
456 const int count = symbol->symbolLayerCount();
457 for ( int i = count - 1; i >= 0; i-- )
458 {
459 SymbolLayerItem *layerItem = new SymbolLayerItem( symbol->symbolLayer( i ), symbol->type(), mVectorLayer, screen() );
460 layerItem->setEditable( false );
461 symbolItem->appendRow( layerItem );
462 if ( symbol->symbolLayer( i )->subSymbol() )
463 {
464 loadSymbol( symbol->symbolLayer( i )->subSymbol(), layerItem );
465 }
466 layersTree->setExpanded( layerItem->index(), true );
467 }
468 layersTree->setExpanded( symbolItem->index(), true );
469
470 if ( mSymbol == symbol && !layersTree->currentIndex().isValid() )
471 {
472 // make sure root item for symbol is selected in tree
473 layersTree->setCurrentIndex( symbolItem->index() );
474 }
475}
476
477void QgsSymbolSelectorWidget::reloadSymbol()
478{
479 mSymbolLayersModel->clear();
480 loadSymbol( mSymbol, static_cast<SymbolLayerItem *>( mSymbolLayersModel->invisibleRootItem() ) );
481}
482
483void QgsSymbolSelectorWidget::updateUi()
484{
485 const QModelIndex currentIdx = layersTree->currentIndex();
486 if ( !currentIdx.isValid() )
487 return;
488
489 SymbolLayerItem *item = static_cast<SymbolLayerItem *>( mSymbolLayersModel->itemFromIndex( currentIdx ) );
490 if ( !item->isLayer() )
491 {
492 btnUp->setEnabled( false );
493 btnDown->setEnabled( false );
494 btnRemoveLayer->setEnabled( false );
495 btnLock->setEnabled( false );
496 btnDuplicate->setEnabled( false );
497 return;
498 }
499
500 const int rowCount = item->parent()->rowCount();
501 const int currentRow = item->row();
502
503 btnUp->setEnabled( currentRow > 0 );
504 btnDown->setEnabled( currentRow < rowCount - 1 );
505 btnRemoveLayer->setEnabled( rowCount > 1 );
506 btnLock->setEnabled( true );
507 btnDuplicate->setEnabled( true );
508}
509
511{
512 if ( !mSymbol )
513 return;
514
515 std::unique_ptr<QgsSymbol> symbolClone( mSymbol->clone() );
516 const QImage preview = symbolClone->bigSymbolPreviewImage( &mPreviewExpressionContext, Qgis::SymbolPreviewFlag::FlagIncludeCrosshairsForMarkerSymbols, QgsScreenProperties( screen() ) );
517 lblPreview->setPixmap( QPixmap::fromImage( preview ) );
518 // Hope this is a appropriate place
519 if ( !mBlockModified )
520 emit symbolModified();
521}
522
524{
525 // get current layer item and update its icon
526 SymbolLayerItem *item = currentLayerItem();
527 if ( item )
528 item->updatePreview();
529 // update also preview of the whole symbol
531}
532
533SymbolLayerItem *QgsSymbolSelectorWidget::currentLayerItem()
534{
535 const QModelIndex idx = layersTree->currentIndex();
536 if ( !idx.isValid() )
537 return nullptr;
538
539 SymbolLayerItem *item = static_cast<SymbolLayerItem *>( mSymbolLayersModel->itemFromIndex( idx ) );
540 if ( !item->isLayer() )
541 return nullptr;
542
543 return item;
544}
545
546QgsSymbolLayer *QgsSymbolSelectorWidget::currentLayer()
547{
548 const QModelIndex idx = layersTree->currentIndex();
549 if ( !idx.isValid() )
550 return nullptr;
551
552 SymbolLayerItem *item = static_cast<SymbolLayerItem *>( mSymbolLayersModel->itemFromIndex( idx ) );
553 if ( item->isLayer() )
554 return item->layer();
555
556 return nullptr;
557}
558
560{
561 updateUi();
562
563 SymbolLayerItem *currentItem = static_cast<SymbolLayerItem *>( mSymbolLayersModel->itemFromIndex( layersTree->currentIndex() ) );
564 if ( !currentItem )
565 return;
566
567 if ( currentItem->isLayer() )
568 {
569 SymbolLayerItem *parent = static_cast<SymbolLayerItem *>( currentItem->parent() );
570 mDataDefineRestorer = std::make_unique<DataDefinedRestorer>( parent->symbol(), currentItem->layer() );
571 QgsLayerPropertiesWidget *layerProp = new QgsLayerPropertiesWidget( currentItem->layer(), parent->symbol(), mVectorLayer );
572 layerProp->setDockMode( this->dockMode() );
573 layerProp->setContext( mContext );
574 setWidget( layerProp );
575 connect( layerProp, &QgsLayerPropertiesWidget::changed, mDataDefineRestorer.get(), &DataDefinedRestorer::restore );
577 // This connection when layer type is changed
579
580 connectChildPanel( layerProp );
581 }
582 else
583 {
584 // then it must be a symbol
585 mDataDefineRestorer.reset();
587 currentItem->symbol()->setLayer( mVectorLayer );
589 // Now populate symbols of that type using the symbols list widget:
590 QgsSymbolsListWidget *symbolsList = new QgsSymbolsListWidget( currentItem->symbol(), mStyle, mAdvancedMenu, this, mVectorLayer );
591 symbolsList->setContext( mContext );
592
593 setWidget( symbolsList );
595 }
596 updateLockButton();
597}
598
600{
601 SymbolLayerItem *currentItem = static_cast<SymbolLayerItem *>( mSymbolLayersModel->itemFromIndex( layersTree->currentIndex() ) );
602 if ( !currentItem || currentItem->isLayer() )
603 return;
604 // disconnect to avoid recreating widget
605 disconnect( layersTree->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsSymbolSelectorWidget::layerChanged );
606 if ( currentItem->parent() )
607 {
608 // it is a sub-symbol
609 QgsSymbol *symbol = currentItem->symbol();
610 SymbolLayerItem *parent = static_cast<SymbolLayerItem *>( currentItem->parent() );
611 parent->removeRow( 0 );
612 loadSymbol( symbol, parent );
613 layersTree->setCurrentIndex( parent->child( 0 )->index() );
614 parent->updatePreview();
615 }
616 else
617 {
618 //it is the symbol itself
619 reloadSymbol();
620 const QModelIndex newIndex = layersTree->model()->index( 0, 0 );
621 layersTree->setCurrentIndex( newIndex );
622 }
624 // connect it back once things are set
625 connect( layersTree->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsSymbolSelectorWidget::layerChanged );
626}
627
628void QgsSymbolSelectorWidget::setWidget( QWidget *widget )
629{
630 const int index = stackedWidget->addWidget( widget );
631 stackedWidget->setCurrentIndex( index );
632 if ( mPresentWidget )
633 mPresentWidget->deleteLater();
634 mPresentWidget = widget;
635}
636
637void QgsSymbolSelectorWidget::updateLockButton()
638{
639 QgsSymbolLayer *layer = currentLayer();
640 if ( !layer )
641 return;
642 mLockColorAction->setChecked( layer->isLocked() );
643 mLockSelectionColorAction->setChecked( layer->userFlags() & Qgis::SymbolLayerUserFlag::DisableSelectionRecoloring );
644
645 updateLockButtonIcon();
646}
647
648void QgsSymbolSelectorWidget::updateLockButtonIcon()
649{
650 if ( mLockColorAction->isChecked() && mLockSelectionColorAction->isChecked() )
651 btnLock->setIcon( QgsApplication::getThemeIcon( u"locked.svg"_s ) );
652 else if ( mLockColorAction->isChecked() )
653 btnLock->setIcon( QgsApplication::getThemeIcon( u"mIconColorLocked.svg"_s ) );
654 else if ( mLockSelectionColorAction->isChecked() )
655 btnLock->setIcon( QgsApplication::getThemeIcon( u"mIconSelectLocked.svg"_s ) );
656 else
657 btnLock->setIcon( QgsApplication::getThemeIcon( u"unlocked.svg"_s ) );
658}
659
661{
662 const QModelIndex idx = layersTree->currentIndex();
663 if ( !idx.isValid() )
664 return;
665
666 int insertIdx = -1;
667 SymbolLayerItem *item = static_cast<SymbolLayerItem *>( mSymbolLayersModel->itemFromIndex( idx ) );
668 if ( item->isLayer() )
669 {
670 insertIdx = item->row();
671 item = static_cast<SymbolLayerItem *>( item->parent() );
672 }
673
674 QgsSymbol *parentSymbol = item->symbol();
675
676 // save data-defined values at marker level
677 const QgsProperty ddSize( parentSymbol->type() == Qgis::SymbolType::Marker ? static_cast<QgsMarkerSymbol *>( parentSymbol )->dataDefinedSize() : QgsProperty() );
678 const QgsProperty ddAngle( parentSymbol->type() == Qgis::SymbolType::Marker ? static_cast<QgsMarkerSymbol *>( parentSymbol )->dataDefinedAngle() : QgsProperty() );
679 const QgsProperty ddWidth( parentSymbol->type() == Qgis::SymbolType::Line ? static_cast<QgsLineSymbol *>( parentSymbol )->dataDefinedWidth() : QgsProperty() );
680
681 QgsSymbolLayer *newLayerPtr = nullptr;
682 {
683 std::unique_ptr< QgsSymbolLayer > newLayer = QgsSymbolLayerRegistry::defaultSymbolLayer( parentSymbol->type() );
684 newLayerPtr = newLayer.get();
685 if ( insertIdx == -1 )
686 parentSymbol->appendSymbolLayer( newLayer.release() );
687 else
688 parentSymbol->insertSymbolLayer( item->rowCount() - insertIdx, newLayer.release() );
689 }
690
691 // restore data-defined values at marker level
692 if ( ddSize )
693 static_cast<QgsMarkerSymbol *>( parentSymbol )->setDataDefinedSize( ddSize );
694 if ( ddAngle )
695 static_cast<QgsMarkerSymbol *>( parentSymbol )->setDataDefinedAngle( ddAngle );
696 if ( ddWidth )
697 static_cast<QgsLineSymbol *>( parentSymbol )->setDataDefinedWidth( ddWidth );
698
699 // TODO -- using newLayerPtr is not safe in some circumstances here. This needs reworking so that SymbolLayerItem does has
700 // its own owned QgsSymbolLayer clone, and isn't reliant on a pointer to the object owned by parentSymbol.
701 SymbolLayerItem *newLayerItem = new SymbolLayerItem( newLayerPtr, parentSymbol->type(), mVectorLayer, screen() ); // cppcheck-suppress invalidLifetime
702 item->insertRow( insertIdx == -1 ? 0 : insertIdx, newLayerItem );
703 item->updatePreview();
704
705 layersTree->setCurrentIndex( mSymbolLayersModel->indexFromItem( newLayerItem ) );
706 updateUi();
708}
709
711{
712 SymbolLayerItem *item = currentLayerItem();
713 const int row = item->row();
714 SymbolLayerItem *parent = static_cast<SymbolLayerItem *>( item->parent() );
715
716 const int layerIdx = parent->rowCount() - row - 1; // IMPORTANT
717 QgsSymbol *parentSymbol = parent->symbol();
718 QgsSymbolLayer *tmpLayer = parentSymbol->takeSymbolLayer( layerIdx );
719
720 parent->removeRow( row );
721 parent->updatePreview();
722
723 const QModelIndex newIdx = parent->child( 0 )->index();
724 layersTree->setCurrentIndex( newIdx );
725
726 updateUi();
728 //finally delete the removed layer pointer
729 delete tmpLayer;
730}
731
733{
734 moveLayerByOffset( +1 );
735}
736
738{
739 moveLayerByOffset( -1 );
740}
741
742void QgsSymbolSelectorWidget::moveLayerByOffset( int offset )
743{
744 SymbolLayerItem *item = currentLayerItem();
745 if ( !item )
746 return;
747 const int row = item->row();
748
749 SymbolLayerItem *parent = static_cast<SymbolLayerItem *>( item->parent() );
750 QgsSymbol *parentSymbol = parent->symbol();
751
752 const int layerIdx = parent->rowCount() - row - 1;
753 // switch layers
754 QgsSymbolLayer *tmpLayer = parentSymbol->takeSymbolLayer( layerIdx );
755 parentSymbol->insertSymbolLayer( layerIdx - offset, tmpLayer );
756
757 QList<QStandardItem *> rowItems = parent->takeRow( row );
758 parent->insertRows( row + offset, rowItems );
759 parent->updatePreview();
760
761 const QModelIndex newIdx = rowItems[0]->index();
762 layersTree->setCurrentIndex( newIdx );
763
765 updateUi();
766}
767
769{
770 QgsSymbolLayer *layer = currentLayer();
771 if ( !layer )
772 return;
773 layer->setLocked( mLockColorAction->isChecked() );
774 updateLockButtonIcon();
775 emit symbolModified();
776}
777
779{
780 const QModelIndex idx = layersTree->currentIndex();
781 if ( !idx.isValid() )
782 return;
783
784 SymbolLayerItem *item = static_cast<SymbolLayerItem *>( mSymbolLayersModel->itemFromIndex( idx ) );
785 if ( !item->isLayer() )
786 return;
787
788 QgsSymbolLayer *source = item->layer();
789
790 const int insertIdx = item->row();
791 item = static_cast<SymbolLayerItem *>( item->parent() );
792
793 QgsSymbol *parentSymbol = item->symbol();
794
795 QgsSymbolLayer *newLayer = source->clone();
797 if ( insertIdx == -1 )
798 parentSymbol->appendSymbolLayer( newLayer );
799 else
800 parentSymbol->insertSymbolLayer( item->rowCount() - insertIdx, newLayer );
801
802 SymbolLayerItem *newLayerItem = new SymbolLayerItem( newLayer, parentSymbol->type(), mVectorLayer, screen() );
803 item->insertRow( insertIdx == -1 ? 0 : insertIdx, newLayerItem );
804 if ( newLayer->subSymbol() )
805 {
806 loadSymbol( newLayer->subSymbol(), newLayerItem );
807 layersTree->setExpanded( newLayerItem->index(), true );
808 }
809 item->updatePreview();
810
811 layersTree->setCurrentIndex( mSymbolLayersModel->indexFromItem( newLayerItem ) );
812 updateUi();
814}
815
817{
818 SymbolLayerItem *item = currentLayerItem();
819
820 if ( item->rowCount() > 0 )
821 {
822 item->removeRow( 0 );
823 }
824 QgsSymbol *symbol = static_cast<SymbolLayerItem *>( item->parent() )->symbol();
825
826 // update symbol layer item
827 item->setLayer( newLayer, symbol->type() );
828 // When it is a marker symbol
829 if ( newLayer->subSymbol() )
830 {
831 loadSymbol( newLayer->subSymbol(), item );
832 layersTree->setExpanded( item->index(), true );
833 }
834
835 // Change the symbol at last to avoid deleting item's layer
836 const int layerIdx = item->parent()->rowCount() - item->row() - 1;
837 symbol->changeSymbolLayer( layerIdx, newLayer );
838
839 item->updatePreview();
841 // Important: This lets the layer have its own layer properties widget
842 layerChanged();
843}
844
846 : QDialog( parent )
847{
848 setLayout( new QVBoxLayout() );
849
850 mSelectorWidget = new QgsSymbolSelectorWidget( symbol, style, vl, this );
851 mButtonBox = new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok );
852
853 connect( mButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
854 connect( mButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
855 connect( mButtonBox, &QDialogButtonBox::helpRequested, this, &QgsSymbolSelectorDialog::showHelp );
856
857 layout()->addWidget( mSelectorWidget );
858 layout()->addWidget( mButtonBox );
859
860 connect( mSelectorWidget, &QgsPanelWidget::panelAccepted, this, &QDialog::reject );
861
862 mSelectorWidget->setMinimumSize( 460, 560 );
863 setObjectName( u"SymbolSelectorDialog"_s );
865
866 // Can be embedded in renderer properties dialog
867 if ( embedded )
868 {
869 mButtonBox->hide();
870 layout()->setContentsMargins( 0, 0, 0, 0 );
871 }
872 else
873 {
874 setWindowTitle( tr( "Symbol Selector" ) );
875 }
876 mSelectorWidget->setDockMode( embedded );
877}
878
880{
881 return mSelectorWidget->advancedMenu();
882}
883
885{
886 mSelectorWidget->setContext( context );
887}
888
890{
891 return mSelectorWidget->context();
892}
893
895{
896 return mSelectorWidget->symbol();
897}
898
900{
901 // Ignore the ESC key to avoid close the dialog without the properties window
902 if ( !isWindow() && e->key() == Qt::Key_Escape )
903 {
904 e->ignore();
905 }
906 else
907 {
908 QDialog::keyPressEvent( e );
909 }
910}
911
912void QgsSymbolSelectorDialog::reloadSymbol()
913{
914 mSelectorWidget->reloadSymbol();
915}
916
917void QgsSymbolSelectorDialog::loadSymbol( QgsSymbol *symbol, SymbolLayerItem *parent )
918{
919 mSelectorWidget->loadSymbol( symbol, parent );
920}
921
922void QgsSymbolSelectorDialog::updateUi()
923{
924 mSelectorWidget->updateUi();
925}
926
927void QgsSymbolSelectorDialog::updateLockButton()
928{
929 mSelectorWidget->updateLockButton();
930}
931
932SymbolLayerItem *QgsSymbolSelectorDialog::currentLayerItem()
933{
934 return mSelectorWidget->currentLayerItem();
935}
936
937QgsSymbolLayer *QgsSymbolSelectorDialog::currentLayer()
938{
939 return mSelectorWidget->currentLayer();
940}
941
942void QgsSymbolSelectorDialog::moveLayerByOffset( int offset )
943{
944 mSelectorWidget->moveLayerByOffset( offset );
945}
946
947void QgsSymbolSelectorDialog::setWidget( QWidget *widget )
948{
949 mSelectorWidget->setWidget( widget );
950}
951
953{
954 mSelectorWidget->moveLayerDown();
955}
956
958{
959 mSelectorWidget->moveLayerUp();
960}
961
963{
964 mSelectorWidget->addLayer();
965}
966
968{
969 mSelectorWidget->removeLayer();
970}
971
973{
974 mSelectorWidget->lockLayer();
975}
976
978{
979 mSelectorWidget->duplicateLayer();
980}
981
983{
984 mSelectorWidget->layerChanged();
985}
986
988{
989 mSelectorWidget->updateLayerPreview();
990}
991
993{
994 mSelectorWidget->updatePreview();
995}
996
998{
999 mSelectorWidget->symbolChanged();
1000}
1001
1003{
1004 mSelectorWidget->changeLayer( layer );
1005}
1006
1007QDialogButtonBox *QgsSymbolSelectorDialog::buttonBox() const
1008{
1009 return mButtonBox;
1010}
1011
1012void QgsSymbolSelectorDialog::showHelp()
1013{
1014 QgsHelp::openHelp( u"style_library/symbol_selector.html"_s );
1015}
1016
1017void QgsSymbolSelectorWidget::projectDataChanged()
1018{
1019 mBlockModified = true;
1020 symbolChanged();
1021 updatePreview();
1022 mBlockModified = false;
1023}
1024
1025void QgsSymbolSelectorWidget::layersAboutToBeRemoved( const QList<QgsMapLayer *> &layers )
1026{
1027 if ( mVectorLayer && layers.contains( mVectorLayer ) )
1028 {
1029 disconnect( QgsProject::instance(), &QgsProject::projectColorsChanged, this, &QgsSymbolSelectorWidget::projectDataChanged );
1030 }
1031}
QFlags< SymbolLayerUserFlag > SymbolLayerUserFlags
Symbol layer user flags.
Definition qgis.h:936
@ Millimeters
Millimeters.
Definition qgis.h:5341
@ FlagIncludeCrosshairsForMarkerSymbols
Include a crosshairs reference image in the background of marker symbol previews.
Definition qgis.h:889
@ DisableSelectionRecoloring
If present, indicates that the symbol layer should not be recolored when rendering selected features.
Definition qgis.h:927
SymbolType
Symbol types.
Definition qgis.h:636
@ Marker
Marker symbol.
Definition qgis.h:637
@ Line
Line symbol.
Definition qgis.h:638
@ Fill
Fill symbol.
Definition qgis.h:639
@ Hybrid
Hybrid symbol.
Definition qgis.h:640
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:6591
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:113
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:227
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:296
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:7504
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7503