QGIS API Documentation 4.1.0-Master (467af3bbe65)
Loading...
Searching...
No Matches
qgsstylemanagerdialog.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsstylemanagerdialog.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
18#include "qgs3dsymbolregistry.h"
19#include "qgs3dsymbolwidget.h"
20#include "qgsabstract3dsymbol.h"
22#include "qgsapplication.h"
24#include "qgscolorramp.h"
26#include "qgsfileutils.h"
27#include "qgsfillsymbol.h"
29#include "qgsiconutils.h"
30#include "qgslabelinggui.h"
33#include "qgslinesymbol.h"
34#include "qgslogger.h"
35#include "qgsmarkersymbol.h"
36#include "qgsmaterialwidget.h"
37#include "qgsmessagebar.h"
39#include "qgsproject.h"
41#include "qgssettings.h"
45#include "qgsstylemodel.h"
46#include "qgsstylesavedialog.h"
47#include "qgssymbol.h"
48#include "qgssymbollayerutils.h"
50#include "qgstextformatwidget.h"
51
52#include <QAction>
53#include <QClipboard>
54#include <QDesktopServices>
55#include <QFile>
56#include <QFileDialog>
57#include <QInputDialog>
58#include <QMenu>
59#include <QMessageBox>
60#include <QPushButton>
61#include <QShortcut>
62#include <QStandardItemModel>
63#include <QString>
64#include <QUrl>
65
66#include "moc_qgsstylemanagerdialog.cpp"
67
68using namespace Qt::StringLiterals;
69
71 = new QgsSettingsEntryString( u"last-style-database-folder"_s, sTtreeStyleManager, QString(), u"Last used folder for style databases"_s );
72
73//
74// QgsCheckableStyleModel
75//
76
78QgsCheckableStyleModel::QgsCheckableStyleModel( QgsStyleModel *sourceModel, QObject *parent, bool readOnly )
79 : QgsStyleProxyModel( sourceModel, parent )
80 , mStyle( sourceModel->style() )
81 , mReadOnly( readOnly )
82{}
83
84QgsCheckableStyleModel::QgsCheckableStyleModel( QgsStyle *style, QObject *parent, bool readOnly )
85 : QgsStyleProxyModel( style, parent )
86 , mStyle( style )
87 , mReadOnly( readOnly )
88{}
89
90void QgsCheckableStyleModel::setCheckable( bool checkable )
91{
92 if ( checkable == mCheckable )
93 return;
94
95 mCheckable = checkable;
96 emit dataChanged( index( 0, 0 ), index( rowCount() - 1, 0 ), QVector<int>() << Qt::CheckStateRole );
97}
98
99void QgsCheckableStyleModel::setCheckTag( const QString &tag )
100{
101 if ( tag == mCheckTag )
102 return;
103
104 mCheckTag = tag;
105 emit dataChanged( index( 0, 0 ), index( rowCount() - 1, 0 ), QVector<int>() << Qt::CheckStateRole );
106}
107
108Qt::ItemFlags QgsCheckableStyleModel::flags( const QModelIndex &index ) const
109{
110 Qt::ItemFlags f = QgsStyleProxyModel::flags( index );
111 if ( !mReadOnly && mCheckable && index.column() == 0 )
112 f |= Qt::ItemIsUserCheckable;
113
114 if ( mReadOnly )
115 f &= ~Qt::ItemIsEditable;
116
117 return f;
118}
119
120QVariant QgsCheckableStyleModel::data( const QModelIndex &index, int role ) const
121{
122 switch ( role )
123 {
124 case Qt::FontRole:
125 {
126 // drop font size to get reasonable amount of item name shown
127 QFont f = QgsStyleProxyModel::data( index, role ).value<QFont>();
128 f.setPointSize( 9 );
129 return f;
130 }
131
132 case Qt::CheckStateRole:
133 {
134 if ( !mCheckable || index.column() != 0 )
135 return QVariant();
136
137 const QStringList tags = data( index, static_cast<int>( QgsStyleModel::CustomRole::Tag ) ).toStringList();
138 return tags.contains( mCheckTag ) ? Qt::Checked : Qt::Unchecked;
139 }
140
141 default:
142 break;
143 }
144 return QgsStyleProxyModel::data( index, role );
145}
146
147bool QgsCheckableStyleModel::setData( const QModelIndex &i, const QVariant &value, int role )
148{
149 if ( i.row() < 0 || i.row() >= rowCount( QModelIndex() ) || ( role != Qt::EditRole && role != Qt::CheckStateRole ) )
150 return false;
151
152 if ( mReadOnly )
153 return false;
154
155 if ( role == Qt::CheckStateRole )
156 {
157 if ( !mCheckable || mCheckTag.isEmpty() )
158 return false;
159
160 const QString name = data( index( i.row(), QgsStyleModel::Name ), Qt::DisplayRole ).toString();
161 const QgsStyle::StyleEntity entity = static_cast<QgsStyle::StyleEntity>( data( i, static_cast<int>( QgsStyleModel::CustomRole::Type ) ).toInt() );
162
163 if ( value.toInt() == Qt::Checked )
164 return mStyle->tagSymbol( entity, name, QStringList() << mCheckTag );
165 else
166 return mStyle->detagSymbol( entity, name, QStringList() << mCheckTag );
167 }
168 return QgsStyleProxyModel::setData( i, value, role );
169}
171
172//
173// QgsStyleManagerDialog
174//
175
176#include "qgsgui.h"
177
178QString QgsStyleManagerDialog::sPreviousTag;
179
180QgsStyleManagerDialog::QgsStyleManagerDialog( QgsStyle *style, QWidget *parent, Qt::WindowFlags flags, bool readOnly )
181 : QDialog( parent, flags )
182 , mReadOnly( readOnly )
183{
184 init();
185 setCurrentStyle( style );
186 mStyleDatabaseWidget->hide();
187}
188
189QgsStyleManagerDialog::QgsStyleManagerDialog( QWidget *parent, Qt::WindowFlags flags )
190 : QDialog( parent, flags )
191{
192 init();
193
194 mProjectStyleModel = new QgsProjectStyleDatabaseModel( QgsProject::instance()->styleSettings(), this );
195 mProjectStyleModel->setShowDefaultStyle( true );
196 mComboBoxStyleDatabase->setModel( mProjectStyleModel );
197
198 setCurrentStyle( QgsStyle::defaultStyle() );
199
200 connect( mComboBoxStyleDatabase, qOverload<int>( &QComboBox::currentIndexChanged ), this, [this]() {
201 if ( mBlockStyleDatabaseChanges )
202 return;
203
204 const QModelIndex index = mProjectStyleModel->index( mComboBoxStyleDatabase->currentIndex(), 0, QModelIndex() );
205 setCurrentStyle( mProjectStyleModel->styleFromIndex( index ) );
206 } );
207
208 connect( mButtonAddStyleDatabase, &QAbstractButton::clicked, this, [this] { addStyleDatabase( false ); } );
209 connect( mButtonNewStyleDatabase, &QAbstractButton::clicked, this, [this] { addStyleDatabase( true ); } );
210}
211
212void QgsStyleManagerDialog::init()
213{
214 setupUi( this );
216 connect( tabItemType, &QTabWidget::currentChanged, this, &QgsStyleManagerDialog::tabItemType_currentChanged );
217 connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsStyleManagerDialog::showHelp );
218 connect( buttonBox, &QDialogButtonBox::rejected, this, &QgsStyleManagerDialog::onClose );
219
220 QPushButton *downloadButton = buttonBox->addButton( tr( "Browse Online Styles" ), QDialogButtonBox::ResetRole );
221 downloadButton->setToolTip( tr( "Download new styles from the online QGIS style repository" ) );
222 downloadButton->setIcon( QgsApplication::getThemeIcon( u"/mActionFindReplace.svg"_s ) );
223 connect( downloadButton, &QPushButton::clicked, this, [] { QDesktopServices::openUrl( QUrl( u"https://hub.qgis.org/styles/"_s ) ); } );
224
225 mMessageBar = new QgsMessageBar();
226 mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
227 mVerticalLayout->insertWidget( 0, mMessageBar );
228
229#ifdef Q_OS_MAC
230 setWindowModality( Qt::WindowModal );
231#endif
232
233 QgsSettings settings;
234
235 mSplitter->setSizes( QList<int>() << 170 << 540 );
236 mSplitter->restoreState( settings.value( u"Windows/StyleV2Manager/splitter"_s ).toByteArray() );
237
238 tabItemType->setDocumentMode( true );
239 searchBox->setShowSearchIcon( true );
240 searchBox->setPlaceholderText( tr( "Filter symbols…" ) );
241
242 connect( this, &QDialog::finished, this, &QgsStyleManagerDialog::onFinished );
243 connect( listItems, &QAbstractItemView::doubleClicked, this, &QgsStyleManagerDialog::editItem );
244 connect( btnEditItem, &QPushButton::clicked, this, [this]( bool ) { editItem(); } );
245 connect( actnEditItem, &QAction::triggered, this, [this]( bool ) { editItem(); } );
246
247
248 connect( btnAddItem, &QPushButton::clicked, this, [this]( bool ) {
249 // only show add item if the btn doesn't have a menu -- otherwise it should show the menu instead!
250 if ( !btnAddItem->menu() )
251 {
252 addItem();
253 }
254 } );
255
256 connect( btnRemoveItem, &QPushButton::clicked, this, [this]( bool ) { removeItem(); } );
257 connect( actnRemoveItem, &QAction::triggered, this, [this]( bool ) { removeItem(); } );
258
259 mShareMenu = new QMenu( tr( "Share Menu" ), this );
260 mExportAction = new QAction( tr( "Export Item(s)…" ), this );
261 mExportAction->setIcon( QIcon( QgsApplication::iconPath( "mActionFileSave.svg" ) ) );
262 mShareMenu->addAction( mExportAction );
263
264 connect( mCopyToDefaultButton, &QPushButton::clicked, this, &QgsStyleManagerDialog::copyItemsToDefault );
265
266 mActionCopyItem = new QAction( tr( "Copy Item" ), this );
267 connect( mActionCopyItem, &QAction::triggered, this, &QgsStyleManagerDialog::copyItem );
268 mActionPasteItem = new QAction( tr( "Paste Item…" ), this );
269 connect( mActionPasteItem, &QAction::triggered, this, &QgsStyleManagerDialog::pasteItem );
270
271 QShortcut *copyShortcut = new QShortcut( QKeySequence( QKeySequence::StandardKey::Copy ), this );
272 connect( copyShortcut, &QShortcut::activated, this, &QgsStyleManagerDialog::copyItem );
273 QShortcut *pasteShortcut = new QShortcut( QKeySequence( QKeySequence::StandardKey::Paste ), this );
274 connect( pasteShortcut, &QShortcut::activated, this, &QgsStyleManagerDialog::pasteItem );
275 QShortcut *removeShortcut = new QShortcut( QKeySequence( QKeySequence::StandardKey::Delete ), this );
276 connect( removeShortcut, &QShortcut::activated, this, &QgsStyleManagerDialog::removeItem );
277 QShortcut *editShortcut = new QShortcut( QKeySequence( Qt::Key_Return ), this );
278 connect( editShortcut, &QShortcut::activated, this, &QgsStyleManagerDialog::editItem );
279
280 mShareMenu->addSeparator();
281 mShareMenu->addAction( actnExportAsPNG );
282 mShareMenu->addAction( actnExportAsSVG );
283
284 connect( actnExportAsPNG, &QAction::triggered, this, &QgsStyleManagerDialog::exportItemsPNG );
285 connect( actnExportAsSVG, &QAction::triggered, this, &QgsStyleManagerDialog::exportItemsSVG );
286 connect( mExportAction, &QAction::triggered, this, &QgsStyleManagerDialog::exportItems );
287 btnShare->setMenu( mShareMenu );
288
289 listItems->setTextElideMode( Qt::TextElideMode::ElideRight );
290 double treeIconSize = Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 2;
291 mSymbolTreeView->setIconSize( QSize( static_cast<int>( treeIconSize ), static_cast<int>( treeIconSize ) ) );
292
293 listItems->setSelectionBehavior( QAbstractItemView::SelectRows );
294 listItems->setSelectionMode( QAbstractItemView::ExtendedSelection );
295 mSymbolTreeView->setSelectionMode( listItems->selectionMode() );
296
297 QStandardItemModel *groupModel = new QStandardItemModel( groupTree );
298 groupTree->setModel( groupModel );
299 groupTree->setHeaderHidden( true );
300
301 connect( groupTree->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsStyleManagerDialog::groupChanged );
302 connect( groupModel, &QStandardItemModel::itemChanged, this, &QgsStyleManagerDialog::groupRenamed );
303
304 QMenu *groupMenu = new QMenu( tr( "Group Actions" ), this );
305 connect( actnTagSymbols, &QAction::triggered, this, &QgsStyleManagerDialog::tagSymbolsAction );
306 groupMenu->addAction( actnTagSymbols );
307 connect( actnFinishTagging, &QAction::triggered, this, &QgsStyleManagerDialog::tagSymbolsAction );
308 actnFinishTagging->setVisible( false );
309 groupMenu->addAction( actnFinishTagging );
310 groupMenu->addAction( actnEditSmartGroup );
311 btnManageGroups->setMenu( groupMenu );
312
313 connect( searchBox, &QLineEdit::textChanged, this, &QgsStyleManagerDialog::filterSymbols );
314
315 // Context menu for groupTree
316 groupTree->setContextMenuPolicy( Qt::CustomContextMenu );
317 connect( groupTree, &QWidget::customContextMenuRequested, this, &QgsStyleManagerDialog::grouptreeContextMenu );
318
319 // Context menu for listItems
320 listItems->setContextMenuPolicy( Qt::CustomContextMenu );
321 connect( listItems, &QWidget::customContextMenuRequested, this, &QgsStyleManagerDialog::listitemsContextMenu );
322 mSymbolTreeView->setContextMenuPolicy( Qt::CustomContextMenu );
323 connect( mSymbolTreeView, &QWidget::customContextMenuRequested, this, &QgsStyleManagerDialog::listitemsContextMenu );
324
325 mMenuBtnAddItemAll = new QMenu( this );
326 mMenuBtnAddItemColorRamp = new QMenu( this );
327 mMenuBtnAddItemLabelSettings = new QMenu( this );
328 mMenuBtnAddItemLegendPatchShape = new QMenu( this );
329 mMenuBtnAddItemSymbol3D = new QMenu( this );
330
331 QAction *item = new QAction( QgsIconUtils::iconPoint(), tr( "Marker…" ), this );
332 connect( item, &QAction::triggered, this, [this]( bool ) { addSymbol( static_cast<int>( Qgis::SymbolType::Marker ) ); } );
333 mMenuBtnAddItemAll->addAction( item );
334 item = new QAction( QgsIconUtils::iconLine(), tr( "Line…" ), this );
335 connect( item, &QAction::triggered, this, [this]( bool ) { addSymbol( static_cast<int>( Qgis::SymbolType::Line ) ); } );
336 mMenuBtnAddItemAll->addAction( item );
337 item = new QAction( QgsIconUtils::iconPolygon(), tr( "Fill…" ), this );
338 connect( item, &QAction::triggered, this, [this]( bool ) { addSymbol( static_cast<int>( Qgis::SymbolType::Fill ) ); } );
339 mMenuBtnAddItemAll->addAction( item );
340 mMenuBtnAddItemAll->addSeparator();
341
342 const QList<QPair<QString, QString>> rampTypes = QgsColorRamp::rampTypes();
343 for ( const QPair<QString, QString> &rampType : rampTypes )
344 {
345 item = new QAction( QgsApplication::getThemeIcon( u"styleicons/color.svg"_s ), tr( "%1…" ).arg( rampType.second ), this );
346 connect( item, &QAction::triggered, this, [this, rampType]( bool ) { addColorRamp( rampType.first ); } );
347 mMenuBtnAddItemAll->addAction( item );
348 mMenuBtnAddItemColorRamp->addAction( item );
349 }
350 mMenuBtnAddItemAll->addSeparator();
351 item = new QAction( QgsApplication::getThemeIcon( u"mIconFieldText.svg"_s ), tr( "Text Format…" ), this );
352 connect( item, &QAction::triggered, this, [this]( bool ) { addTextFormat(); } );
353 mMenuBtnAddItemAll->addAction( item );
354 mMenuBtnAddItemAll->addSeparator();
355 item = new QAction( QgsApplication::getThemeIcon( u"labelingSingle.svg"_s ), tr( "Point Label Settings…" ), this );
356 connect( item, &QAction::triggered, this, [this]( bool ) { addLabelSettings( Qgis::GeometryType::Point ); } );
357 mMenuBtnAddItemAll->addAction( item );
358 mMenuBtnAddItemLabelSettings->addAction( item );
359 item = new QAction( QgsApplication::getThemeIcon( u"labelingSingle.svg"_s ), tr( "Line Label Settings…" ), this );
360 connect( item, &QAction::triggered, this, [this]( bool ) { addLabelSettings( Qgis::GeometryType::Line ); } );
361 mMenuBtnAddItemAll->addAction( item );
362 mMenuBtnAddItemLabelSettings->addAction( item );
363 item = new QAction( QgsApplication::getThemeIcon( u"labelingSingle.svg"_s ), tr( "Polygon Label Settings…" ), this );
364 connect( item, &QAction::triggered, this, [this]( bool ) { addLabelSettings( Qgis::GeometryType::Polygon ); } );
365 mMenuBtnAddItemAll->addAction( item );
366 mMenuBtnAddItemLabelSettings->addAction( item );
367
368 mMenuBtnAddItemAll->addSeparator();
369 item = new QAction( QgsApplication::getThemeIcon( u"legend.svg"_s ), tr( "Marker Legend Patch Shape…" ), this );
370 connect( item, &QAction::triggered, this, [this]( bool ) { addLegendPatchShape( Qgis::SymbolType::Marker ); } );
371 mMenuBtnAddItemAll->addAction( item );
372 mMenuBtnAddItemLegendPatchShape->addAction( item );
373 item = new QAction( QgsApplication::getThemeIcon( u"legend.svg"_s ), tr( "Line Legend Patch Shape…" ), this );
374 connect( item, &QAction::triggered, this, [this]( bool ) { addLegendPatchShape( Qgis::SymbolType::Line ); } );
375 mMenuBtnAddItemAll->addAction( item );
376 mMenuBtnAddItemLegendPatchShape->addAction( item );
377 item = new QAction( QgsApplication::getThemeIcon( u"legend.svg"_s ), tr( "Fill Legend Patch Shape…" ), this );
378 connect( item, &QAction::triggered, this, [this]( bool ) { addLegendPatchShape( Qgis::SymbolType::Fill ); } );
379 mMenuBtnAddItemAll->addAction( item );
380 mMenuBtnAddItemLegendPatchShape->addAction( item );
381
382 mMenuBtnAddItemAll->addSeparator();
383 item = new QAction( QgsApplication::getThemeIcon( u"3d.svg"_s ), tr( "Material…" ), this );
384 connect( item, &QAction::triggered, this, [this]( bool ) { addMaterialSettings(); } );
385 mMenuBtnAddItemAll->addAction( item );
386
387 mMenuBtnAddItemAll->addSeparator();
388 item = new QAction( QgsApplication::getThemeIcon( u"3d.svg"_s ), tr( "3D Point Symbol…" ), this );
389 connect( item, &QAction::triggered, this, [this]( bool ) { addSymbol3D( u"point"_s ); } );
390 mMenuBtnAddItemAll->addAction( item );
391 mMenuBtnAddItemSymbol3D->addAction( item );
392 item = new QAction( QgsApplication::getThemeIcon( u"3d.svg"_s ), tr( "3D Line Symbol…" ), this );
393 connect( item, &QAction::triggered, this, [this]( bool ) { addSymbol3D( u"line"_s ); } );
394 mMenuBtnAddItemAll->addAction( item );
395 mMenuBtnAddItemSymbol3D->addAction( item );
396 item = new QAction( QgsApplication::getThemeIcon( u"3d.svg"_s ), tr( "3D Polygon Symbol…" ), this );
397 connect( item, &QAction::triggered, this, [this]( bool ) { addSymbol3D( u"polygon"_s ); } );
398 mMenuBtnAddItemAll->addAction( item );
399 mMenuBtnAddItemSymbol3D->addAction( item );
400
401 // Context menu for symbols/colorramps. The menu entries for every group are created when displaying the menu.
402 mGroupMenu = new QMenu( this );
403 mGroupListMenu = new QMenu( mGroupMenu );
404 mGroupListMenu->setTitle( tr( "Add to Tag" ) );
405 mGroupListMenu->setEnabled( false );
406
407 connect( actnAddFavorite, &QAction::triggered, this, &QgsStyleManagerDialog::addFavoriteSelectedSymbols );
408 connect( actnRemoveFavorite, &QAction::triggered, this, &QgsStyleManagerDialog::removeFavoriteSelectedSymbols );
409 connect( actnDetag, &QAction::triggered, this, &QgsStyleManagerDialog::detagSelectedSymbols );
410
411 // Context menu for the group tree
412 mGroupTreeContextMenu = new QMenu( this );
413 connect( actnEditSmartGroup, &QAction::triggered, this, &QgsStyleManagerDialog::editSmartgroupAction );
414 connect( actnAddTag, &QAction::triggered, this, [this]( bool ) { addTag(); } );
415 connect( actnAddSmartgroup, &QAction::triggered, this, [this]( bool ) { addSmartgroup(); } );
416 connect( actnRemoveGroup, &QAction::triggered, this, &QgsStyleManagerDialog::removeGroup );
417
418 tabItemType_currentChanged( 0 );
419
420 connect( mButtonIconView, &QToolButton::toggled, this, [this]( bool active ) {
421 if ( active )
422 {
423 mSymbolViewStackedWidget->setCurrentIndex( 0 );
424 // note -- we have to save state here and not in destructor, as new symbol list widgets are created before the previous ones are destroyed
425 QgsSettings().setValue( u"Windows/StyleV2Manager/lastIconView"_s, 0, QgsSettings::Gui );
426 }
427 } );
428 connect( mButtonListView, &QToolButton::toggled, this, [this]( bool active ) {
429 if ( active )
430 {
431 QgsSettings().setValue( u"Windows/StyleV2Manager/lastIconView"_s, 1, QgsSettings::Gui );
432 mSymbolViewStackedWidget->setCurrentIndex( 1 );
433 }
434 } );
435 // restore previous view
436 const int currentView = settings.value( u"Windows/StyleV2Manager/lastIconView"_s, 0, QgsSettings::Gui ).toInt();
437 if ( currentView == 0 )
438 mButtonIconView->setChecked( true );
439 else
440 mButtonListView->setChecked( true );
441
442 mSymbolTreeView->header()->restoreState( settings.value( u"Windows/StyleV2Manager/treeState"_s, QByteArray(), QgsSettings::Gui ).toByteArray() );
443 connect( mSymbolTreeView->header(), &QHeaderView::sectionResized, this, [this] {
444 // note -- we have to save state here and not in destructor, as new symbol list widgets are created before the previous ones are destroyed
445 QgsSettings().setValue( u"Windows/StyleV2Manager/treeState"_s, mSymbolTreeView->header()->saveState(), QgsSettings::Gui );
446 } );
447
448 const int thumbnailSize = settings.value( u"Windows/StyleV2Manager/thumbnailSize"_s, 0, QgsSettings::Gui ).toInt();
449 mSliderIconSize->setValue( thumbnailSize );
450 connect( mSliderIconSize, &QSlider::valueChanged, this, &QgsStyleManagerDialog::setThumbnailSize );
451 setThumbnailSize( thumbnailSize );
452}
453
454void QgsStyleManagerDialog::setCurrentStyle( QgsStyle *style )
455{
456 if ( mStyle == style )
457 return;
458
459 if ( mStyle )
460 {
461 disconnect( mStyle, &QgsStyle::symbolSaved, this, &QgsStyleManagerDialog::populateList );
463 disconnect( mStyle, &QgsStyle::aboutToBeDestroyed, this, &QgsStyleManagerDialog::currentStyleAboutToBeDestroyed );
464 }
465
466 QgsCheckableStyleModel *oldModel = mModel;
467
468 mStyle = style;
469 const bool readOnly = isReadOnly();
470 if ( mStyle != QgsStyle::defaultStyle() )
471 {
472 if ( !mActionCopyToDefault )
473 {
474 mActionCopyToDefault = new QAction( tr( "Copy Selection to Default Style…" ), this );
475 mShareMenu->insertAction( mActionCopyItem, mActionCopyToDefault );
476 connect( mActionCopyToDefault, &QAction::triggered, this, &QgsStyleManagerDialog::copyItemsToDefault );
477 }
478 mCopyToDefaultButton->show();
479 mModel = new QgsCheckableStyleModel( mStyle, this, readOnly );
480 }
481 else
482 {
483 mCopyToDefaultButton->hide();
484 if ( mActionCopyToDefault )
485 {
486 mActionCopyToDefault->deleteLater();
487 mActionCopyToDefault = nullptr;
488 }
489 mModel = new QgsCheckableStyleModel( QgsApplication::defaultStyleModel(), this, readOnly );
490 }
491 mModel->addDesiredIconSize( mSymbolTreeView->iconSize() );
492 mModel->addDesiredIconSize( listItems->iconSize() );
493 mModel->addTargetScreenProperties( QgsScreenProperties( screen() ) );
494
495 mModel->setFilterString( searchBox->text() );
496
497 listItems->setModel( mModel );
498 mSymbolTreeView->setModel( mModel );
499
500 mSymbolTreeView->setSelectionModel( listItems->selectionModel() );
501
502 connect( listItems->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsStyleManagerDialog::symbolSelected );
503 connect( listItems->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsStyleManagerDialog::selectedSymbolsChanged );
504
505 if ( oldModel )
506 {
507 oldModel->deleteLater();
508 oldModel = nullptr;
509 }
510
513 connect( mStyle, &QgsStyle::aboutToBeDestroyed, this, &QgsStyleManagerDialog::currentStyleAboutToBeDestroyed );
514
515 if ( mProjectStyleModel )
516 {
517 const QModelIndex styleIndex = mProjectStyleModel->indexFromStyle( mStyle );
518 mBlockStyleDatabaseChanges++;
519 mComboBoxStyleDatabase->setCurrentIndex( styleIndex.row() );
520 mBlockStyleDatabaseChanges--;
521 }
522
523 if ( readOnly )
524 {
525 btnAddTag->setEnabled( false );
526 btnAddSmartgroup->setEnabled( false );
527 btnManageGroups->setEnabled( false );
528
529 btnAddItem->setVisible( false );
530 btnRemoveItem->setVisible( false );
531 btnEditItem->setVisible( false );
532 btnAddSmartgroup->setVisible( false );
533 btnAddTag->setVisible( false );
534 btnManageGroups->setVisible( false );
535
536 delete mImportAction;
537 mImportAction = nullptr;
538
539 mGroupTreeContextMenu->clear();
540 mGroupMenu->clear();
541 mGroupMenu->addAction( mActionCopyItem );
542 }
543 else
544 {
545 btnAddTag->setEnabled( true );
546 btnAddSmartgroup->setEnabled( true );
547 btnManageGroups->setEnabled( true );
548
549 btnAddItem->setVisible( true );
550 btnRemoveItem->setVisible( true );
551 btnEditItem->setVisible( true );
552 btnAddSmartgroup->setVisible( true );
553 btnAddTag->setVisible( true );
554 btnManageGroups->setVisible( true );
555
556 if ( !mImportAction )
557 {
558 mImportAction = new QAction( tr( "Import Item(s)…" ), this );
559 mImportAction->setIcon( QIcon( QgsApplication::iconPath( "mActionFileOpen.svg" ) ) );
560 mShareMenu->insertAction( mShareMenu->actions().at( mShareMenu->actions().indexOf( mExportAction ) + 1 ), mImportAction );
561 connect( mImportAction, &QAction::triggered, this, &QgsStyleManagerDialog::importItems );
562 }
563
564 mGroupTreeContextMenu->clear();
565 mGroupTreeContextMenu->addAction( actnEditSmartGroup );
566 mGroupTreeContextMenu->addAction( actnAddTag );
567 mGroupTreeContextMenu->addAction( actnAddSmartgroup );
568 mGroupTreeContextMenu->addAction( actnRemoveGroup );
569
570 mGroupMenu->clear();
571 mGroupMenu->addAction( actnAddFavorite );
572 mGroupMenu->addAction( actnRemoveFavorite );
573 mGroupMenu->addSeparator()->setParent( this );
574 mGroupMenu->addMenu( mGroupListMenu );
575 mGroupMenu->addAction( actnDetag );
576 mGroupMenu->addSeparator()->setParent( this );
577 mGroupMenu->addAction( actnRemoveItem );
578 mGroupMenu->addAction( actnEditItem );
579 mGroupMenu->addAction( mActionCopyItem );
580 mGroupMenu->addAction( mActionPasteItem );
581 mGroupMenu->addSeparator()->setParent( this );
582 }
583
584 if ( mActionCopyToDefault )
585 {
586 mGroupMenu->addAction( mActionCopyToDefault );
587 }
588 mGroupMenu->addAction( actnExportAsPNG );
589 mGroupMenu->addAction( actnExportAsSVG );
590
592 const QModelIndexList prevIndex = groupTree->model()->match( groupTree->model()->index( 0, 0 ), Qt::UserRole + 1, sPreviousTag, 1, Qt::MatchFixedString | Qt::MatchCaseSensitive | Qt::MatchRecursive );
593 groupTree->setCurrentIndex( !prevIndex.empty() ? prevIndex.at( 0 ) : groupTree->model()->index( 0, 0 ) );
594 populateList();
595
596 tabItemType_currentChanged( tabItemType->currentIndex() );
597
598 // set initial disabled state for actions requiring a selection
599 selectedSymbolsChanged( QItemSelection(), QItemSelection() );
600}
601
602void QgsStyleManagerDialog::currentStyleAboutToBeDestroyed()
603{
604 if ( mStyle != QgsStyle::defaultStyle() )
605 {
606 setCurrentStyle( QgsStyle::defaultStyle() );
607 }
608}
609
611{
612 QgsSettings settings;
613 settings.setValue( u"Windows/StyleV2Manager/splitter"_s, mSplitter->saveState() );
614}
615
618
619void QgsStyleManagerDialog::tabItemType_currentChanged( int )
620{
621 // when in Color Ramp tab, add menu to add item button and hide "Export symbols as PNG/SVG"
622 const bool isSymbol = currentItemType() != 3 && currentItemType() != 4 && currentItemType() != 5 && currentItemType() != 6 && currentItemType() != 7 && currentItemType() != 8;
623 const bool isColorRamp = currentItemType() == 3;
624 const bool isTextFormat = currentItemType() == 4;
625 const bool isLabelSettings = currentItemType() == 5;
626 const bool isLegendPatchShape = currentItemType() == 6;
627 const bool isSymbol3D = currentItemType() == 7;
628 const bool isMaterialSettings = currentItemType() == 8;
629 searchBox->setPlaceholderText(
630 isSymbol ? tr( "Filter symbols…" )
631 : isColorRamp ? tr( "Filter color ramps…" )
632 : isTextFormat ? tr( "Filter text symbols…" )
633 : isLabelSettings ? tr( "Filter label settings…" )
634 : isLegendPatchShape ? tr( "Filter legend patch shapes…" )
635 : isSymbol3D ? tr( "Filter 3D symbols…" )
636 : tr( "Filter materials…" )
637 );
638
639 const bool readOnly = isReadOnly();
640 if ( !readOnly && isColorRamp ) // color ramp tab
641 {
642 btnAddItem->setMenu( mMenuBtnAddItemColorRamp );
643 }
644 else if ( !readOnly && isLegendPatchShape ) // legend patch shape tab
645 {
646 btnAddItem->setMenu( mMenuBtnAddItemLegendPatchShape );
647 }
648 else if ( !readOnly && isSymbol3D ) // 3D symbol tab
649 {
650 btnAddItem->setMenu( mMenuBtnAddItemSymbol3D );
651 }
652 else if ( !readOnly && isMaterialSettings ) // material settings tab
653 {
654 btnAddItem->setMenu( nullptr );
655 }
656 else if ( !readOnly && isLabelSettings ) // label settings tab
657 {
658 btnAddItem->setMenu( mMenuBtnAddItemLabelSettings );
659 }
660 else if ( !readOnly && !isSymbol && !isColorRamp ) // text format tab
661 {
662 btnAddItem->setMenu( nullptr );
663 }
664 else if ( !readOnly && tabItemType->currentIndex() == 0 ) // all symbols tab
665 {
666 btnAddItem->setMenu( mMenuBtnAddItemAll );
667 }
668 else
669 {
670 btnAddItem->setMenu( nullptr );
671 }
672
673 actnExportAsPNG->setVisible( isSymbol );
674 actnExportAsSVG->setVisible( isSymbol );
675
676 if ( mModel )
677 {
678 mModel->setEntityFilter(
679 isSymbol ? QgsStyle::SymbolEntity
680 : ( isColorRamp ? QgsStyle::ColorrampEntity
681 : isTextFormat ? QgsStyle::TextFormatEntity
682 : isLabelSettings ? QgsStyle::LabelSettingsEntity
683 : isLegendPatchShape ? QgsStyle::LegendPatchShapeEntity
684 : isSymbol3D ? QgsStyle::Symbol3DEntity
686 );
687 mModel->setEntityFilterEnabled( !allTypesSelected() );
688 mModel->setSymbolTypeFilterEnabled( isSymbol && !allTypesSelected() );
689 if ( isSymbol && !allTypesSelected() )
690 mModel->setSymbolType( static_cast<Qgis::SymbolType>( currentItemType() ) );
691 }
692
693 populateList();
694}
695
696void QgsStyleManagerDialog::copyItemsToDefault()
697{
698 const QList<ItemDetails> items = selectedItems();
699 if ( !items.empty() )
700 {
701 bool ok = false;
702 QStringList options;
703 if ( !mBaseName.isEmpty() )
704 options.append( mBaseName );
705
706 QStringList defaultTags = QgsStyle::defaultStyle()->tags();
707 defaultTags.sort( Qt::CaseInsensitive );
708 options.append( defaultTags );
709 const QString tags = QInputDialog::getItem( this, tr( "Import Items" ), tr( "Additional tags to add (comma separated)" ), options, mBaseName.isEmpty() ? -1 : 0, true, &ok );
710 if ( !ok )
711 return;
712
713 const QStringList parts = tags.split( ',', Qt::SkipEmptyParts );
714 QStringList additionalTags;
715 additionalTags.reserve( parts.count() );
716 for ( const QString &tag : parts )
717 additionalTags << tag.trimmed();
718
719 auto cursorOverride = std::make_unique<QgsTemporaryCursorOverride>( Qt::WaitCursor );
720 const int count = copyItems( items, mStyle, QgsStyle::defaultStyle(), this, cursorOverride, true, additionalTags, false, false );
721 cursorOverride.reset();
722 if ( count > 0 )
723 {
724 mMessageBar->pushSuccess( tr( "Import Items" ), count > 1 ? tr( "Successfully imported %n item(s).", nullptr, count ) : tr( "Successfully imported item." ) );
725 }
726 }
727}
728
729void QgsStyleManagerDialog::copyItem()
730{
731 const QList<ItemDetails> items = selectedItems();
732 if ( items.empty() )
733 return;
734
735 ItemDetails details = items.at( 0 );
736 switch ( details.entityType )
737 {
739 {
740 std::unique_ptr<QgsSymbol> symbol( mStyle->symbol( details.name ) );
741 if ( !symbol )
742 return;
743 QApplication::clipboard()->setMimeData( QgsSymbolLayerUtils::symbolToMimeData( symbol.get() ) );
744 break;
745 }
746
748 {
749 const QgsTextFormat format( mStyle->textFormat( details.name ) );
750 QApplication::clipboard()->setMimeData( format.toMimeData() );
751 break;
752 }
753
755 {
756 const QgsPalLayerSettings labelSettings( mStyle->labelSettings( details.name ) );
757 QApplication::clipboard()->setMimeData( labelSettings.toMimeData() );
758 break;
759 }
760
767 return;
768 }
769}
770
771void QgsStyleManagerDialog::pasteItem()
772{
773 const QString defaultTag = groupTree->currentIndex().isValid() ? groupTree->currentIndex().data( GroupModelRoles::TagName ).toString() : QString();
774 std::unique_ptr<QgsSymbol> tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
775 if ( tempSymbol )
776 {
777 QgsStyleSaveDialog saveDlg( this );
778 saveDlg.setWindowTitle( tr( "Paste Symbol" ) );
779 saveDlg.setDefaultTags( defaultTag );
780 if ( !saveDlg.exec() || saveDlg.name().isEmpty() )
781 return;
782
783 if ( mStyle->symbolNames().contains( saveDlg.name() ) )
784 {
785 int res = QMessageBox::warning( this, tr( "Paste Symbol" ), tr( "A symbol with the name '%1' already exists. Overwrite?" ).arg( saveDlg.name() ), QMessageBox::Yes | QMessageBox::No );
786 if ( res != QMessageBox::Yes )
787 {
788 return;
789 }
790 mStyle->removeSymbol( saveDlg.name() );
791 }
792
793 QStringList symbolTags = saveDlg.tags().split( ',' );
794 QgsSymbol *newSymbol = tempSymbol.get();
795 mStyle->addSymbol( saveDlg.name(), tempSymbol.release() );
796 // make sure the symbol is stored
797 mStyle->saveSymbol( saveDlg.name(), newSymbol, saveDlg.isFavorite(), symbolTags );
798 return;
799 }
800
801 bool ok = false;
802
803 const QgsPalLayerSettings labelSettings = QgsPalLayerSettings::fromMimeData( QApplication::clipboard()->mimeData(), &ok );
804 if ( ok )
805 {
806 QgsStyleSaveDialog saveDlg( this, QgsStyle::LabelSettingsEntity );
807 saveDlg.setDefaultTags( defaultTag );
808 saveDlg.setWindowTitle( tr( "Paste Label Settings" ) );
809 if ( !saveDlg.exec() || saveDlg.name().isEmpty() )
810 return;
811
812 if ( mStyle->labelSettingsNames().contains( saveDlg.name() ) )
813 {
814 int res = QMessageBox::warning( this, tr( "Paste Label Settings" ), tr( "A label setting with the name '%1' already exists. Overwrite?" ).arg( saveDlg.name() ), QMessageBox::Yes | QMessageBox::No );
815 if ( res != QMessageBox::Yes )
816 {
817 return;
818 }
819 mStyle->removeLabelSettings( saveDlg.name() );
820 }
821
822 QStringList symbolTags = saveDlg.tags().split( ',' );
823 mStyle->addLabelSettings( saveDlg.name(), labelSettings );
824 // make sure the settings are stored
825 mStyle->saveLabelSettings( saveDlg.name(), labelSettings, saveDlg.isFavorite(), symbolTags );
826 return;
827 }
828
829 ok = false;
830 const QgsTextFormat format = QgsTextFormat::fromMimeData( QApplication::clipboard()->mimeData(), &ok );
831 if ( ok )
832 {
833 QgsStyleSaveDialog saveDlg( this, QgsStyle::TextFormatEntity );
834 saveDlg.setDefaultTags( defaultTag );
835 saveDlg.setWindowTitle( tr( "Paste Text Format" ) );
836 if ( !saveDlg.exec() || saveDlg.name().isEmpty() )
837 return;
838
839 if ( mStyle->textFormatNames().contains( saveDlg.name() ) )
840 {
841 int res = QMessageBox::warning( this, tr( "Paste Text Format" ), tr( "A format with the name '%1' already exists. Overwrite?" ).arg( saveDlg.name() ), QMessageBox::Yes | QMessageBox::No );
842 if ( res != QMessageBox::Yes )
843 {
844 return;
845 }
846 mStyle->removeTextFormat( saveDlg.name() );
847 }
848
849 QStringList symbolTags = saveDlg.tags().split( ',' );
850 mStyle->addTextFormat( saveDlg.name(), format );
851 // make sure the foprmatis stored
852 mStyle->saveTextFormat( saveDlg.name(), format, saveDlg.isFavorite(), symbolTags );
853 return;
854 }
855}
856
857void QgsStyleManagerDialog::setThumbnailSize( int value )
858{
859 // value ranges from 0-10
860 const double iconSize = Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * ( value * 2.5 + 10 );
861 // set a grid size which allows sufficient vertical spacing to fit reasonably sized entity names
862 const double spacing = Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * ( value * 2.2 + 14 );
863 const double verticalSpacing = Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 7 + iconSize * 0.8;
864 listItems->setIconSize( QSize( static_cast<int>( iconSize ), static_cast<int>( iconSize * 0.9 ) ) );
865 listItems->setGridSize( QSize( static_cast<int>( spacing ), static_cast<int>( verticalSpacing ) ) );
866 if ( mModel )
867 {
868 mModel->addDesiredIconSize( listItems->iconSize() );
869 }
870
871 QgsSettings().setValue( u"Windows/StyleV2Manager/thumbnailSize"_s, value, QgsSettings::Gui );
872}
873
874int QgsStyleManagerDialog::selectedItemType()
875{
876 QModelIndex index = listItems->selectionModel()->currentIndex();
877 if ( !index.isValid() )
878 return 0;
879
880 const QgsStyle::StyleEntity entity = static_cast<QgsStyle::StyleEntity>( mModel->data( index, static_cast<int>( QgsStyleModel::CustomRole::Type ) ).toInt() );
881 if ( entity == QgsStyle::ColorrampEntity )
882 return 3;
883 else if ( entity == QgsStyle::TextFormatEntity )
884 return 4;
885 else if ( entity == QgsStyle::LabelSettingsEntity )
886 return 5;
887 else if ( entity == QgsStyle::LegendPatchShapeEntity )
888 return 6;
889 else if ( entity == QgsStyle::Symbol3DEntity )
890 return 7;
891 else if ( entity == QgsStyle::MaterialSettingsEntity )
892 return 8;
893
894 return mModel->data( index, static_cast<int>( QgsStyleModel::CustomRole::SymbolType ) ).toInt();
895}
896
897bool QgsStyleManagerDialog::allTypesSelected() const
898{
899 return tabItemType->currentIndex() == 0;
900}
901
902bool QgsStyleManagerDialog::isReadOnly() const
903{
904 return mReadOnly || ( mStyle && mStyle->isReadOnly() );
905}
906
907QList<QgsStyleManagerDialog::ItemDetails> QgsStyleManagerDialog::selectedItems()
908{
909 QList<QgsStyleManagerDialog::ItemDetails> res;
910 QModelIndexList indices = listItems->selectionModel()->selectedRows();
911 for ( const QModelIndex &index : indices )
912 {
913 if ( !index.isValid() )
914 continue;
915
916 ItemDetails details;
917 details.entityType = static_cast<QgsStyle::StyleEntity>( mModel->data( index, static_cast<int>( QgsStyleModel::CustomRole::Type ) ).toInt() );
918 if ( details.entityType == QgsStyle::SymbolEntity )
919 details.symbolType = static_cast<Qgis::SymbolType>( mModel->data( index, static_cast<int>( QgsStyleModel::CustomRole::SymbolType ) ).toInt() );
920 details.name = mModel->data( mModel->index( index.row(), QgsStyleModel::Name, index.parent() ), Qt::DisplayRole ).toString();
921
922 res << details;
923 }
924 return res;
925}
926
927int QgsStyleManagerDialog::copyItems(
928 const QList<QgsStyleManagerDialog::ItemDetails> &items,
929 QgsStyle *src,
930 QgsStyle *dst,
931 QWidget *parentWidget,
932 std::unique_ptr<QgsTemporaryCursorOverride> &cursorOverride,
933 bool isImport,
934 const QStringList &importTags,
935 bool addToFavorites,
936 bool ignoreSourceTags
937)
938{
939 bool prompt = true;
940 bool overwriteAll = true;
941 int count = 0;
942
943 const QStringList favoriteSymbols = src->symbolsOfFavorite( QgsStyle::SymbolEntity );
944 const QStringList favoriteColorramps = src->symbolsOfFavorite( QgsStyle::ColorrampEntity );
945 const QStringList favoriteTextFormats = src->symbolsOfFavorite( QgsStyle::TextFormatEntity );
946 const QStringList favoriteLabelSettings = src->symbolsOfFavorite( QgsStyle::LabelSettingsEntity );
947 const QStringList favoriteLegendPatchShapes = src->symbolsOfFavorite( QgsStyle::LegendPatchShapeEntity );
948 const QStringList favorite3dSymbols = src->symbolsOfFavorite( QgsStyle::Symbol3DEntity );
949 const QStringList favoriteMaterialSettings = src->symbolsOfFavorite( QgsStyle::MaterialSettingsEntity );
950
951 for ( auto &details : items )
952 {
953 QStringList symbolTags;
954 if ( !ignoreSourceTags )
955 {
956 symbolTags = src->tagsOfSymbol( details.entityType, details.name );
957 }
958
959 bool addItemToFavorites = false;
960 if ( isImport )
961 {
962 symbolTags << importTags;
963 addItemToFavorites = addToFavorites;
964 }
965
966 switch ( details.entityType )
967 {
969 {
970 std::unique_ptr<QgsSymbol> symbol( src->symbol( details.name ) );
971 if ( !symbol )
972 continue;
973
974 const bool hasDuplicateName = dst->symbolNames().contains( details.name );
975 bool overwriteThis = false;
976 if ( isImport )
977 addItemToFavorites = favoriteSymbols.contains( details.name );
978
979 if ( hasDuplicateName && prompt )
980 {
981 cursorOverride.reset();
982 int res = QMessageBox::warning(
983 parentWidget,
984 isImport ? tr( "Import Symbol" ) : tr( "Export Symbol" ),
985 tr( "A symbol with the name “%1” already exists.\nOverwrite?" ).arg( details.name ),
986 QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll | QMessageBox::Cancel
987 );
988 cursorOverride = std::make_unique<QgsTemporaryCursorOverride>( Qt::WaitCursor );
989 switch ( res )
990 {
991 case QMessageBox::Cancel:
992 return count;
993
994 case QMessageBox::No:
995 continue;
996
997 case QMessageBox::Yes:
998 overwriteThis = true;
999 break;
1000
1001 case QMessageBox::YesToAll:
1002 prompt = false;
1003 overwriteAll = true;
1004 break;
1005
1006 case QMessageBox::NoToAll:
1007 prompt = false;
1008 overwriteAll = false;
1009 break;
1010 default:
1011 break;
1012 }
1013 }
1014
1015 if ( !hasDuplicateName || overwriteAll || overwriteThis )
1016 {
1017 QgsSymbol *newSymbol = symbol.get();
1018 dst->addSymbol( details.name, symbol.release() );
1019 dst->saveSymbol( details.name, newSymbol, addItemToFavorites, symbolTags );
1020 count++;
1021 }
1022 break;
1023 }
1024
1026 {
1027 std::unique_ptr<QgsColorRamp> ramp( src->colorRamp( details.name ) );
1028 if ( !ramp )
1029 continue;
1030
1031 const bool hasDuplicateName = dst->colorRampNames().contains( details.name );
1032 bool overwriteThis = false;
1033 if ( isImport )
1034 addItemToFavorites = favoriteColorramps.contains( details.name );
1035
1036 if ( hasDuplicateName && prompt )
1037 {
1038 cursorOverride.reset();
1039 int res = QMessageBox::warning(
1040 parentWidget,
1041 isImport ? tr( "Import Color Ramp" ) : tr( "Export Color Ramp" ),
1042 tr( "A color ramp with the name “%1” already exists.\nOverwrite?" ).arg( details.name ),
1043 QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll | QMessageBox::Cancel
1044 );
1045 cursorOverride = std::make_unique<QgsTemporaryCursorOverride>( Qt::WaitCursor );
1046 switch ( res )
1047 {
1048 case QMessageBox::Cancel:
1049 return count;
1050
1051 case QMessageBox::No:
1052 continue;
1053
1054 case QMessageBox::Yes:
1055 overwriteThis = true;
1056 break;
1057
1058 case QMessageBox::YesToAll:
1059 prompt = false;
1060 overwriteAll = true;
1061 break;
1062
1063 case QMessageBox::NoToAll:
1064 prompt = false;
1065 overwriteAll = false;
1066 break;
1067 default:
1068 break;
1069 }
1070 }
1071
1072 if ( !hasDuplicateName || overwriteAll || overwriteThis )
1073 {
1074 QgsColorRamp *newRamp = ramp.get();
1075 dst->addColorRamp( details.name, ramp.release() );
1076 dst->saveColorRamp( details.name, newRamp, addItemToFavorites, symbolTags );
1077 count++;
1078 }
1079 break;
1080 }
1081
1083 {
1084 const QgsTextFormat format( src->textFormat( details.name ) );
1085
1086 const bool hasDuplicateName = dst->textFormatNames().contains( details.name );
1087 bool overwriteThis = false;
1088 if ( isImport )
1089 addItemToFavorites = favoriteTextFormats.contains( details.name );
1090
1091 if ( hasDuplicateName && prompt )
1092 {
1093 cursorOverride.reset();
1094 int res = QMessageBox::warning(
1095 parentWidget,
1096 isImport ? tr( "Import Text Format" ) : tr( "Export Text Format" ),
1097 tr( "A text format with the name “%1” already exists.\nOverwrite?" ).arg( details.name ),
1098 QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll | QMessageBox::Cancel
1099 );
1100 cursorOverride = std::make_unique<QgsTemporaryCursorOverride>( Qt::WaitCursor );
1101 switch ( res )
1102 {
1103 case QMessageBox::Cancel:
1104 return count;
1105
1106 case QMessageBox::No:
1107 continue;
1108
1109 case QMessageBox::Yes:
1110 overwriteThis = true;
1111 break;
1112
1113 case QMessageBox::YesToAll:
1114 prompt = false;
1115 overwriteAll = true;
1116 break;
1117
1118 case QMessageBox::NoToAll:
1119 prompt = false;
1120 overwriteAll = false;
1121 break;
1122
1123 default:
1124 break;
1125 }
1126 }
1127
1128 if ( !hasDuplicateName || overwriteAll || overwriteThis )
1129 {
1130 dst->addTextFormat( details.name, format );
1131 dst->saveTextFormat( details.name, format, addItemToFavorites, symbolTags );
1132 count++;
1133 }
1134 break;
1135 }
1136
1138 {
1139 const QgsPalLayerSettings settings( src->labelSettings( details.name ) );
1140
1141 const bool hasDuplicateName = dst->labelSettingsNames().contains( details.name );
1142 bool overwriteThis = false;
1143 if ( isImport )
1144 addItemToFavorites = favoriteLabelSettings.contains( details.name );
1145
1146 if ( hasDuplicateName && prompt )
1147 {
1148 cursorOverride.reset();
1149 int res = QMessageBox::warning(
1150 parentWidget,
1151 isImport ? tr( "Import Label Settings" ) : tr( "Export Label Settings" ),
1152 tr( "Label settings with the name “%1” already exist.\nOverwrite?" ).arg( details.name ),
1153 QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll | QMessageBox::Cancel
1154 );
1155 cursorOverride = std::make_unique<QgsTemporaryCursorOverride>( Qt::WaitCursor );
1156 switch ( res )
1157 {
1158 case QMessageBox::Cancel:
1159 return count;
1160
1161 case QMessageBox::No:
1162 continue;
1163
1164 case QMessageBox::Yes:
1165 overwriteThis = true;
1166 break;
1167
1168 case QMessageBox::YesToAll:
1169 prompt = false;
1170 overwriteAll = true;
1171 break;
1172
1173 case QMessageBox::NoToAll:
1174 prompt = false;
1175 overwriteAll = false;
1176 break;
1177
1178 default:
1179 break;
1180 }
1181 }
1182
1183 if ( !hasDuplicateName || overwriteAll || overwriteThis )
1184 {
1185 dst->addLabelSettings( details.name, settings );
1186 dst->saveLabelSettings( details.name, settings, addItemToFavorites, symbolTags );
1187 count++;
1188 }
1189 break;
1190 }
1191
1193 {
1194 const QgsLegendPatchShape shape( src->legendPatchShape( details.name ) );
1195
1196 const bool hasDuplicateName = dst->legendPatchShapeNames().contains( details.name );
1197 bool overwriteThis = false;
1198 if ( isImport )
1199 addItemToFavorites = favoriteLegendPatchShapes.contains( details.name );
1200
1201 if ( hasDuplicateName && prompt )
1202 {
1203 cursorOverride.reset();
1204 int res = QMessageBox::warning(
1205 parentWidget,
1206 isImport ? tr( "Import Legend Patch Shape" ) : tr( "Export Legend Patch Shape" ),
1207 tr( "Legend patch shape with the name “%1” already exist.\nOverwrite?" ).arg( details.name ),
1208 QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll | QMessageBox::Cancel
1209 );
1210 cursorOverride = std::make_unique<QgsTemporaryCursorOverride>( Qt::WaitCursor );
1211 switch ( res )
1212 {
1213 case QMessageBox::Cancel:
1214 return count;
1215
1216 case QMessageBox::No:
1217 continue;
1218
1219 case QMessageBox::Yes:
1220 overwriteThis = true;
1221 break;
1222
1223 case QMessageBox::YesToAll:
1224 prompt = false;
1225 overwriteAll = true;
1226 break;
1227
1228 case QMessageBox::NoToAll:
1229 prompt = false;
1230 overwriteAll = false;
1231 break;
1232
1233 default:
1234 break;
1235 }
1236 }
1237
1238 if ( !hasDuplicateName || overwriteAll || overwriteThis )
1239 {
1240 dst->addLegendPatchShape( details.name, shape );
1241 dst->saveLegendPatchShape( details.name, shape, addItemToFavorites, symbolTags );
1242 count++;
1243 }
1244 break;
1245 }
1246
1248 {
1249 std::unique_ptr<QgsAbstract3DSymbol> symbol( src->symbol3D( details.name ) );
1250 if ( !symbol )
1251 continue;
1252
1253 const bool hasDuplicateName = dst->symbol3DNames().contains( details.name );
1254 bool overwriteThis = false;
1255 if ( isImport )
1256 addItemToFavorites = favorite3dSymbols.contains( details.name );
1257
1258 if ( hasDuplicateName && prompt )
1259 {
1260 cursorOverride.reset();
1261 int res = QMessageBox::warning(
1262 parentWidget,
1263 isImport ? tr( "Import 3D Symbol" ) : tr( "Export 3D Symbol" ),
1264 tr( "A 3D symbol with the name “%1” already exists.\nOverwrite?" ).arg( details.name ),
1265 QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll | QMessageBox::Cancel
1266 );
1267 cursorOverride = std::make_unique<QgsTemporaryCursorOverride>( Qt::WaitCursor );
1268 switch ( res )
1269 {
1270 case QMessageBox::Cancel:
1271 return count;
1272
1273 case QMessageBox::No:
1274 continue;
1275
1276 case QMessageBox::Yes:
1277 overwriteThis = true;
1278 break;
1279
1280 case QMessageBox::YesToAll:
1281 prompt = false;
1282 overwriteAll = true;
1283 break;
1284
1285 case QMessageBox::NoToAll:
1286 prompt = false;
1287 overwriteAll = false;
1288 break;
1289
1290 default:
1291 break;
1292 }
1293 }
1294
1295 if ( !hasDuplicateName || overwriteAll || overwriteThis )
1296 {
1297 QgsAbstract3DSymbol *newSymbol = symbol.get();
1298 dst->addSymbol3D( details.name, symbol.release() );
1299 dst->saveSymbol3D( details.name, newSymbol, addItemToFavorites, symbolTags );
1300 count++;
1301 }
1302 break;
1303 }
1304
1306 {
1307 std::unique_ptr<QgsAbstractMaterialSettings > settings( src->materialSettings( details.name ) );
1308 if ( !settings )
1309 continue;
1310
1311 const bool hasDuplicateName = dst->materialSettingsNames().contains( details.name );
1312 bool overwriteThis = false;
1313 if ( isImport )
1314 addItemToFavorites = favoriteMaterialSettings.contains( details.name );
1315
1316 if ( hasDuplicateName && prompt )
1317 {
1318 cursorOverride.reset();
1319 int res = QMessageBox::warning(
1320 parentWidget,
1321 isImport ? tr( "Import Material" ) : tr( "Export Material" ),
1322 tr( "A material with the name “%1” already exists.\nOverwrite?" ).arg( details.name ),
1323 QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll | QMessageBox::Cancel
1324 );
1325 cursorOverride = std::make_unique<QgsTemporaryCursorOverride>( Qt::WaitCursor );
1326 switch ( res )
1327 {
1328 case QMessageBox::Cancel:
1329 return count;
1330
1331 case QMessageBox::No:
1332 continue;
1333
1334 case QMessageBox::Yes:
1335 overwriteThis = true;
1336 break;
1337
1338 case QMessageBox::YesToAll:
1339 prompt = false;
1340 overwriteAll = true;
1341 break;
1342
1343 case QMessageBox::NoToAll:
1344 prompt = false;
1345 overwriteAll = false;
1346 break;
1347
1348 default:
1349 break;
1350 }
1351 }
1352
1353 if ( !hasDuplicateName || overwriteAll || overwriteThis )
1354 {
1355 QgsAbstractMaterialSettings *newSettings = settings.get();
1356 dst->addMaterialSettings( details.name, settings.release() );
1357 dst->saveMaterialSettings( details.name, newSettings, addItemToFavorites, symbolTags );
1358 count++;
1359 }
1360 break;
1361 }
1362
1365 break;
1366 }
1367 }
1368 return count;
1369}
1370
1371bool QgsStyleManagerDialog::addTextFormat()
1372{
1373 QgsTextFormat format;
1374 QgsTextFormatDialog formatDlg( format, nullptr, this );
1375 formatDlg.setWindowTitle( tr( "New Text Format" ) );
1376 if ( !formatDlg.exec() )
1377 return false;
1378 format = formatDlg.format();
1379
1380 QgsStyleSaveDialog saveDlg( this, QgsStyle::TextFormatEntity );
1381 const QString defaultTag = groupTree->currentIndex().isValid() ? groupTree->currentIndex().data( GroupModelRoles::TagName ).toString() : QString();
1382 saveDlg.setDefaultTags( defaultTag );
1383 if ( !saveDlg.exec() )
1384 return false;
1385 QString name = saveDlg.name();
1386
1387 // request valid/unique name
1388 bool nameInvalid = true;
1389 while ( nameInvalid )
1390 {
1391 // validate name
1392 if ( name.isEmpty() )
1393 {
1394 QMessageBox::warning( this, tr( "Save Text Format" ), tr( "Cannot save text format without name. Enter a name." ) );
1395 }
1396 else if ( mStyle->textFormatNames().contains( name ) )
1397 {
1398 int res = QMessageBox::warning( this, tr( "Save Text Format" ), tr( "Text format with name '%1' already exists. Overwrite?" ).arg( name ), QMessageBox::Yes | QMessageBox::No );
1399 if ( res == QMessageBox::Yes )
1400 {
1401 mStyle->removeTextFormat( name );
1402 nameInvalid = false;
1403 }
1404 }
1405 else
1406 {
1407 // valid name
1408 nameInvalid = false;
1409 }
1410 if ( nameInvalid )
1411 {
1412 bool ok;
1413 name = QInputDialog::getText( this, tr( "Text Format Name" ), tr( "Please enter a name for new text format:" ), QLineEdit::Normal, name, &ok );
1414 if ( !ok )
1415 {
1416 return false;
1417 }
1418 }
1419 }
1420
1421 QStringList symbolTags = saveDlg.tags().split( ',' );
1422
1423 // add new format to style and re-populate the list
1424 mStyle->addTextFormat( name, format );
1425 mStyle->saveTextFormat( name, format, saveDlg.isFavorite(), symbolTags );
1426
1427 mModified = true;
1428 return true;
1429}
1430
1432{
1433 groupChanged( groupTree->selectionModel()->currentIndex() );
1434}
1435
1436void QgsStyleManagerDialog::populateSymbols( const QStringList &, bool )
1437{}
1438
1439void QgsStyleManagerDialog::populateColorRamps( const QStringList &, bool )
1440{}
1441
1443{
1444 switch ( tabItemType->currentIndex() )
1445 {
1446 case 1:
1447 return static_cast<int>( Qgis::SymbolType::Marker );
1448 case 2:
1449 return static_cast<int>( Qgis::SymbolType::Line );
1450 case 3:
1451 return static_cast<int>( Qgis::SymbolType::Fill );
1452 case 4:
1453 return 3;
1454 case 5:
1455 return 4;
1456 case 6:
1457 return 5;
1458 case 7:
1459 return 6;
1460 case 8:
1461 return 8;
1462 case 9:
1463 return 7;
1464 default:
1465 return 0;
1466 }
1467}
1468
1470{
1471 QModelIndex index = listItems->selectionModel()->currentIndex();
1472 if ( !index.isValid() )
1473 return QString();
1474
1475 return mModel->data( mModel->index( index.row(), QgsStyleModel::Name, index.parent() ), Qt::DisplayRole ).toString();
1476}
1477
1479{
1480 bool changed = false;
1481 if ( currentItemType() < 3 )
1482 {
1483 changed = addSymbol();
1484 }
1485 else if ( currentItemType() == 3 )
1486 {
1487 changed = addColorRamp();
1488 }
1489 else if ( currentItemType() == 4 )
1490 {
1491 changed = addTextFormat();
1492 }
1493 else if ( currentItemType() == 5 )
1494 {
1495 // actually never hit, because we present a submenu when adding label settings
1496 // changed = addLabelSettings();
1497 }
1498 else if ( currentItemType() == 6 )
1499 {
1500 // actually never hit, because we present a submenu when adding legend patches
1501 // changed = addLegendPatchShape();
1502 }
1503 else if ( currentItemType() == 7 )
1504 {
1505 // actually never hit, because we present a submenu when adding 3d symbols
1506 // changed = addSymbol3D();
1507 }
1508 else if ( currentItemType() == 8 )
1509 {
1510 changed = addMaterialSettings();
1511 }
1512 else
1513 {
1514 Q_ASSERT( false && "not implemented" );
1515 }
1516
1517 if ( changed )
1518 {
1519 populateList();
1520 }
1521}
1522
1524{
1525 // create new symbol with current type
1526 QgsSymbol *symbol = nullptr;
1527 QString name = tr( "new symbol" );
1528 QString dialogTitle;
1529 switch ( symbolType == -1 ? currentItemType() : symbolType )
1530 {
1531 case static_cast<int>( Qgis::SymbolType::Marker ):
1532 symbol = new QgsMarkerSymbol();
1533 name = tr( "new marker" );
1534 dialogTitle = tr( "New Marker Symbol" );
1535 break;
1536 case static_cast<int>( Qgis::SymbolType::Line ):
1537 symbol = new QgsLineSymbol();
1538 name = tr( "new line" );
1539 dialogTitle = tr( "New Line Symbol" );
1540 break;
1541 case static_cast<int>( Qgis::SymbolType::Fill ):
1542 symbol = new QgsFillSymbol();
1543 name = tr( "new fill symbol" );
1544 dialogTitle = tr( "New Fill Symbol" );
1545 break;
1546 default:
1547 Q_ASSERT( false && "unknown symbol type" );
1548 return false;
1549 }
1550
1551 // get symbol design
1552 // NOTE : Set the parent widget as "this" to notify the Symbol selector
1553 // that, it is being called by Style Manager, so recursive calling
1554 // of style manager and symbol selector can be arrested
1555 // See also: editSymbol()
1556 QgsSymbolSelectorDialog dlg( symbol, mStyle, nullptr, this );
1557 dlg.setWindowTitle( dialogTitle );
1558 if ( dlg.exec() == 0 )
1559 {
1560 delete symbol;
1561 return false;
1562 }
1563
1564 QgsStyleSaveDialog saveDlg( this );
1565 const QString defaultTag = groupTree->currentIndex().isValid() ? groupTree->currentIndex().data( GroupModelRoles::TagName ).toString() : QString();
1566 saveDlg.setDefaultTags( defaultTag );
1567 if ( !saveDlg.exec() )
1568 {
1569 delete symbol;
1570 return false;
1571 }
1572
1573 name = saveDlg.name();
1574
1575 // request valid/unique name
1576 bool nameInvalid = true;
1577 while ( nameInvalid )
1578 {
1579 // validate name
1580 if ( name.isEmpty() )
1581 {
1582 QMessageBox::warning( this, tr( "Save Symbol" ), tr( "Cannot save symbol without name. Enter a name." ) );
1583 }
1584 else if ( mStyle->symbolNames().contains( name ) )
1585 {
1586 int res = QMessageBox::warning( this, tr( "Save Symbol" ), tr( "Symbol with name '%1' already exists. Overwrite?" ).arg( name ), QMessageBox::Yes | QMessageBox::No );
1587 if ( res == QMessageBox::Yes )
1588 {
1589 mStyle->removeSymbol( name );
1590 nameInvalid = false;
1591 }
1592 }
1593 else
1594 {
1595 // valid name
1596 nameInvalid = false;
1597 }
1598 if ( nameInvalid )
1599 {
1600 bool ok;
1601 name = QInputDialog::getText( this, tr( "Symbol Name" ), tr( "Please enter a name for new symbol:" ), QLineEdit::Normal, name, &ok );
1602 if ( !ok )
1603 {
1604 delete symbol;
1605 return false;
1606 }
1607 }
1608 }
1609
1610 QStringList symbolTags = saveDlg.tags().split( ',' );
1611
1612 // add new symbol to style and re-populate the list
1613 mStyle->addSymbol( name, symbol );
1614 mStyle->saveSymbol( name, symbol, saveDlg.isFavorite(), symbolTags );
1615
1616 mModified = true;
1617 return true;
1618}
1619
1620
1621QString QgsStyleManagerDialog::addColorRampStatic( QWidget *parent, QgsStyle *style, const QString &type )
1622{
1623 QString rampType = type;
1624
1625 if ( rampType.isEmpty() )
1626 {
1627 // let the user choose the color ramp type if rampType is not given
1628 bool ok = true;
1629 const QList<QPair<QString, QString>> rampTypes = QgsColorRamp::rampTypes();
1630 QStringList rampTypeNames;
1631 rampTypeNames.reserve( rampTypes.size() );
1632 for ( const QPair<QString, QString> &type : rampTypes )
1633 rampTypeNames << type.second;
1634 const QString selectedRampTypeName = QInputDialog::getItem( parent, tr( "Color Ramp Type" ), tr( "Please select color ramp type:" ), rampTypeNames, 0, false, &ok );
1635 if ( !ok || selectedRampTypeName.isEmpty() )
1636 return QString();
1637
1638 rampType = rampTypes.value( rampTypeNames.indexOf( selectedRampTypeName ) ).first;
1639 }
1640
1641 QString name = tr( "new ramp" );
1642
1643 std::unique_ptr<QgsColorRamp> ramp;
1644 if ( rampType == QgsGradientColorRamp::typeString() )
1645 {
1647 dlg.setWindowTitle( tr( "New Gradient Color Ramp" ) );
1648 if ( !dlg.exec() )
1649 {
1650 return QString();
1651 }
1652 ramp.reset( dlg.ramp().clone() );
1653 name = tr( "new gradient ramp" );
1654 }
1655 else if ( rampType == QgsLimitedRandomColorRamp::typeString() )
1656 {
1658 dlg.setWindowTitle( tr( "New Random Color Ramp" ) );
1659 if ( !dlg.exec() )
1660 {
1661 return QString();
1662 }
1663 ramp.reset( dlg.ramp().clone() );
1664 name = tr( "new random ramp" );
1665 }
1666 else if ( rampType == QgsColorBrewerColorRamp::typeString() )
1667 {
1669 dlg.setWindowTitle( tr( "New ColorBrewer Ramp" ) );
1670 if ( !dlg.exec() )
1671 {
1672 return QString();
1673 }
1674 ramp.reset( dlg.ramp().clone() );
1675 name = dlg.ramp().schemeName() + QString::number( dlg.ramp().colors() );
1676 }
1677 else if ( rampType == QgsPresetSchemeColorRamp::typeString() )
1678 {
1680 dlg.setWindowTitle( tr( "New Preset Color Ramp" ) );
1681 if ( !dlg.exec() )
1682 {
1683 return QString();
1684 }
1685 ramp.reset( dlg.ramp().clone() );
1686 name = tr( "new preset ramp" );
1687 }
1688 else if ( rampType == QgsCptCityColorRamp::typeString() )
1689 {
1690 QgsCptCityColorRampDialog dlg( QgsCptCityColorRamp( QString(), QString() ), parent );
1691 dlg.setWindowTitle( tr( "New cpt-city Color Ramp" ) );
1692 if ( !dlg.exec() )
1693 {
1694 return QString();
1695 }
1696 // name = dlg.selectedName();
1697 name = QFileInfo( dlg.ramp().schemeName() ).baseName() + dlg.ramp().variantName();
1698 if ( dlg.saveAsGradientRamp() )
1699 {
1700 ramp.reset( dlg.ramp().cloneGradientRamp() );
1701 }
1702 else
1703 {
1704 ramp.reset( dlg.ramp().clone() );
1705 }
1706 }
1707 else
1708 {
1709 // Q_ASSERT( 0 && "invalid ramp type" );
1710 // bailing out is rather harsh!
1711 QgsDebugError( u"invalid ramp type %1"_s.arg( rampType ) );
1712 return QString();
1713 }
1714
1716 if ( !saveDlg.exec() )
1717 {
1718 return QString();
1719 }
1720
1721 name = saveDlg.name();
1722
1723 // get valid/unique name
1724 bool nameInvalid = true;
1725 while ( nameInvalid )
1726 {
1727 // validate name
1728 if ( name.isEmpty() )
1729 {
1730 QMessageBox::warning( parent, tr( "Save Color Ramp" ), tr( "Cannot save color ramp without name. Enter a name." ) );
1731 }
1732 else if ( style->colorRampNames().contains( name ) )
1733 {
1734 int res = QMessageBox::warning( parent, tr( "Save Color Ramp" ), tr( "Color ramp with name '%1' already exists. Overwrite?" ).arg( name ), QMessageBox::Yes | QMessageBox::No );
1735 if ( res == QMessageBox::Yes )
1736 {
1737 nameInvalid = false;
1738 }
1739 }
1740 else
1741 {
1742 // valid name
1743 nameInvalid = false;
1744 }
1745 if ( nameInvalid )
1746 {
1747 bool ok;
1748 name = QInputDialog::getText( parent, tr( "Color Ramp Name" ), tr( "Please enter a name for new color ramp:" ), QLineEdit::Normal, name, &ok );
1749 if ( !ok )
1750 {
1751 return QString();
1752 }
1753 }
1754 }
1755
1756 QStringList colorRampTags = saveDlg.tags().split( ',' );
1757 QgsColorRamp *r = ramp.release();
1758
1759 // add new symbol to style and re-populate the list
1760 style->addColorRamp( name, r );
1761 style->saveColorRamp( name, r, saveDlg.isFavorite(), colorRampTags );
1762
1763 return name;
1764}
1765
1767{
1768 mFavoritesGroupVisible = show;
1770}
1771
1773{
1774 mSmartGroupVisible = show;
1776}
1777
1779{
1780 mBaseName = name;
1781}
1782
1784{
1785 raise();
1786 setWindowState( windowState() & ~Qt::WindowMinimized );
1787 activateWindow();
1788}
1789
1790bool QgsStyleManagerDialog::addColorRamp( const QString &type )
1791{
1792 // pass the action text, which is the color ramp type
1793 QString rampName = addColorRampStatic( this, mStyle, type );
1794 if ( !rampName.isEmpty() )
1795 {
1796 mModified = true;
1797 populateList();
1798 return true;
1799 }
1800
1801 return false;
1802}
1803
1805{
1806 if ( selectedItemType() < 3 )
1807 {
1808 editSymbol();
1809 }
1810 else if ( selectedItemType() == 3 )
1811 {
1812 editColorRamp();
1813 }
1814 else if ( selectedItemType() == 4 )
1815 {
1816 editTextFormat();
1817 }
1818 else if ( selectedItemType() == 5 )
1819 {
1820 editLabelSettings();
1821 }
1822 else if ( selectedItemType() == 6 )
1823 {
1824 editLegendPatchShape();
1825 }
1826 else if ( selectedItemType() == 7 )
1827 {
1828 editSymbol3D();
1829 }
1830 else if ( selectedItemType() == 8 )
1831 {
1832 editMaterialSettings();
1833 }
1834 else
1835 {
1836 Q_ASSERT( false && "not implemented" );
1837 }
1838}
1839
1841{
1842 QString symbolName = currentItemName();
1843 if ( symbolName.isEmpty() )
1844 return false;
1845
1846 std::unique_ptr<QgsSymbol> symbol( mStyle->symbol( symbolName ) );
1847
1848 // let the user edit the symbol and update list when done
1849 QgsSymbolSelectorDialog dlg( symbol.get(), mStyle, nullptr, this );
1850 dlg.setWindowTitle( symbolName );
1851 if ( isReadOnly() )
1852 dlg.buttonBox()->button( QDialogButtonBox::Ok )->setEnabled( false );
1853
1854 if ( !dlg.exec() )
1855 return false;
1856
1857 // by adding symbol to style with the same name the old effectively gets overwritten
1858 mStyle->addSymbol( symbolName, symbol.release(), true );
1859 mModified = true;
1860 return true;
1861}
1862
1864{
1865 QString name = currentItemName();
1866 if ( name.isEmpty() )
1867 return false;
1868
1869 std::unique_ptr<QgsColorRamp> ramp( mStyle->colorRamp( name ) );
1870
1871 if ( ramp->type() == QgsGradientColorRamp::typeString() )
1872 {
1873 QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( ramp.get() );
1874 QgsGradientColorRampDialog dlg( *gradRamp, this );
1875 dlg.setWindowTitle( name );
1876 if ( isReadOnly() )
1877 dlg.buttonBox()->button( QDialogButtonBox::Ok )->setEnabled( false );
1878
1879 if ( !dlg.exec() )
1880 {
1881 return false;
1882 }
1883 ramp.reset( dlg.ramp().clone() );
1884 }
1885 else if ( ramp->type() == QgsLimitedRandomColorRamp::typeString() )
1886 {
1887 QgsLimitedRandomColorRamp *randRamp = static_cast<QgsLimitedRandomColorRamp *>( ramp.get() );
1888 QgsLimitedRandomColorRampDialog dlg( *randRamp, this );
1889 dlg.setWindowTitle( name );
1890 if ( isReadOnly() )
1891 dlg.buttonBox()->button( QDialogButtonBox::Ok )->setEnabled( false );
1892
1893 if ( !dlg.exec() )
1894 {
1895 return false;
1896 }
1897 ramp.reset( dlg.ramp().clone() );
1898 }
1899 else if ( ramp->type() == QgsColorBrewerColorRamp::typeString() )
1900 {
1901 QgsColorBrewerColorRamp *brewerRamp = static_cast<QgsColorBrewerColorRamp *>( ramp.get() );
1902 QgsColorBrewerColorRampDialog dlg( *brewerRamp, this );
1903 dlg.setWindowTitle( name );
1904 if ( isReadOnly() )
1905 dlg.buttonBox()->button( QDialogButtonBox::Ok )->setEnabled( false );
1906
1907 if ( !dlg.exec() )
1908 {
1909 return false;
1910 }
1911 ramp.reset( dlg.ramp().clone() );
1912 }
1913 else if ( ramp->type() == QgsPresetSchemeColorRamp::typeString() )
1914 {
1915 QgsPresetSchemeColorRamp *presetRamp = static_cast<QgsPresetSchemeColorRamp *>( ramp.get() );
1916 QgsPresetColorRampDialog dlg( *presetRamp, this );
1917 dlg.setWindowTitle( name );
1918 if ( isReadOnly() )
1919 dlg.buttonBox()->button( QDialogButtonBox::Ok )->setEnabled( false );
1920
1921 if ( !dlg.exec() )
1922 {
1923 return false;
1924 }
1925 ramp.reset( dlg.ramp().clone() );
1926 }
1927 else if ( ramp->type() == QgsCptCityColorRamp::typeString() )
1928 {
1929 QgsCptCityColorRamp *cptCityRamp = static_cast<QgsCptCityColorRamp *>( ramp.get() );
1930 QgsCptCityColorRampDialog dlg( *cptCityRamp, this );
1931 dlg.setWindowTitle( name );
1932 if ( isReadOnly() )
1933 dlg.buttonBox()->button( QDialogButtonBox::Ok )->setEnabled( false );
1934
1935 if ( !dlg.exec() )
1936 {
1937 return false;
1938 }
1939 if ( dlg.saveAsGradientRamp() )
1940 {
1941 ramp.reset( dlg.ramp().cloneGradientRamp() );
1942 }
1943 else
1944 {
1945 ramp.reset( dlg.ramp().clone() );
1946 }
1947 }
1948 else
1949 {
1950 Q_ASSERT( false && "invalid ramp type" );
1951 }
1952
1953 mStyle->addColorRamp( name, ramp.release(), true );
1954 mModified = true;
1955 return true;
1956}
1957
1958bool QgsStyleManagerDialog::editTextFormat()
1959{
1960 const QString formatName = currentItemName();
1961 if ( formatName.isEmpty() )
1962 return false;
1963
1964 QgsTextFormat format = mStyle->textFormat( formatName );
1965
1966 // let the user edit the format and update list when done
1967 QgsTextFormatDialog dlg( format, nullptr, this );
1968 dlg.setWindowTitle( formatName );
1969 if ( isReadOnly() )
1970 dlg.buttonBox()->button( QDialogButtonBox::Ok )->setEnabled( false );
1971
1972 if ( !dlg.exec() )
1973 return false;
1974
1975 // by adding format to style with the same name the old effectively gets overwritten
1976 mStyle->addTextFormat( formatName, dlg.format(), true );
1977 mModified = true;
1978 return true;
1979}
1980
1981bool QgsStyleManagerDialog::addLabelSettings( Qgis::GeometryType type )
1982{
1983 QgsPalLayerSettings settings;
1984 QgsLabelSettingsDialog settingsDlg( settings, nullptr, nullptr, this, type );
1985 settingsDlg.setWindowTitle( tr( "New Label Settings" ) );
1986 if ( isReadOnly() )
1987 settingsDlg.buttonBox()->button( QDialogButtonBox::Ok )->setEnabled( false );
1988
1989 if ( !settingsDlg.exec() )
1990 return false;
1991
1992 settings = settingsDlg.settings();
1993 settings.layerType = type;
1994
1995 QgsStyleSaveDialog saveDlg( this, QgsStyle::LabelSettingsEntity );
1996 const QString defaultTag = groupTree->currentIndex().isValid() ? groupTree->currentIndex().data( GroupModelRoles::TagName ).toString() : QString();
1997 saveDlg.setDefaultTags( defaultTag );
1998 if ( !saveDlg.exec() )
1999 return false;
2000 QString name = saveDlg.name();
2001
2002 // request valid/unique name
2003 bool nameInvalid = true;
2004 while ( nameInvalid )
2005 {
2006 // validate name
2007 if ( name.isEmpty() )
2008 {
2009 QMessageBox::warning( this, tr( "Save Label Settings" ), tr( "Cannot save label settings without a name. Enter a name." ) );
2010 }
2011 else if ( mStyle->labelSettingsNames().contains( name ) )
2012 {
2013 int res = QMessageBox::warning( this, tr( "Save Label Settings" ), tr( "Label settings with the name '%1' already exist. Overwrite?" ).arg( name ), QMessageBox::Yes | QMessageBox::No );
2014 if ( res == QMessageBox::Yes )
2015 {
2016 mStyle->removeLabelSettings( name );
2017 nameInvalid = false;
2018 }
2019 }
2020 else
2021 {
2022 // valid name
2023 nameInvalid = false;
2024 }
2025 if ( nameInvalid )
2026 {
2027 bool ok;
2028 name = QInputDialog::getText( this, tr( "Label Settings Name" ), tr( "Please enter a name for the new label settings:" ), QLineEdit::Normal, name, &ok );
2029 if ( !ok )
2030 {
2031 return false;
2032 }
2033 }
2034 }
2035
2036 QStringList symbolTags = saveDlg.tags().split( ',' );
2037
2038 // add new format to style and re-populate the list
2039 mStyle->addLabelSettings( name, settings );
2040 mStyle->saveLabelSettings( name, settings, saveDlg.isFavorite(), symbolTags );
2041
2042 mModified = true;
2043 return true;
2044}
2045
2046bool QgsStyleManagerDialog::editLabelSettings()
2047{
2048 const QString formatName = currentItemName();
2049 if ( formatName.isEmpty() )
2050 return false;
2051
2052 QgsPalLayerSettings settings = mStyle->labelSettings( formatName );
2053 Qgis::GeometryType geomType = settings.layerType;
2054
2055 // let the user edit the settings and update list when done
2056 QgsLabelSettingsDialog dlg( settings, nullptr, nullptr, this, geomType );
2057 dlg.setWindowTitle( formatName );
2058 if ( !dlg.exec() )
2059 return false;
2060
2061 settings = dlg.settings();
2062 settings.layerType = geomType;
2063
2064 // by adding format to style with the same name the old effectively gets overwritten
2065 mStyle->addLabelSettings( formatName, settings, true );
2066 mModified = true;
2067 return true;
2068}
2069
2070bool QgsStyleManagerDialog::addLegendPatchShape( Qgis::SymbolType type )
2071{
2072 QgsLegendPatchShape shape = mStyle->defaultPatch( type, QSizeF( 10, 5 ) );
2073 QgsLegendPatchShapeDialog dialog( shape, this );
2074 dialog.setWindowTitle( tr( "New Legend Patch Shape" ) );
2075 if ( isReadOnly() )
2076 dialog.buttonBox()->button( QDialogButtonBox::Ok )->setEnabled( false );
2077
2078 if ( !dialog.exec() )
2079 return false;
2080
2081 shape = dialog.shape();
2082
2083 QgsStyleSaveDialog saveDlg( this, QgsStyle::LegendPatchShapeEntity );
2084 const QString defaultTag = groupTree->currentIndex().isValid() ? groupTree->currentIndex().data( GroupModelRoles::TagName ).toString() : QString();
2085 saveDlg.setDefaultTags( defaultTag );
2086 if ( !saveDlg.exec() )
2087 return false;
2088 QString name = saveDlg.name();
2089
2090 // request valid/unique name
2091 bool nameInvalid = true;
2092 while ( nameInvalid )
2093 {
2094 // validate name
2095 if ( name.isEmpty() )
2096 {
2097 QMessageBox::warning( this, tr( "Save Legend Patch Shape" ), tr( "Cannot save legend patch shapes without a name. Enter a name." ) );
2098 }
2099 else if ( mStyle->legendPatchShapeNames().contains( name ) )
2100 {
2101 int res = QMessageBox::warning( this, tr( "Save Legend Patch Shape" ), tr( "A legend patch shape with the name '%1' already exists. Overwrite?" ).arg( name ), QMessageBox::Yes | QMessageBox::No );
2102 if ( res == QMessageBox::Yes )
2103 {
2104 mStyle->removeEntityByName( QgsStyle::LegendPatchShapeEntity, name );
2105 nameInvalid = false;
2106 }
2107 }
2108 else
2109 {
2110 // valid name
2111 nameInvalid = false;
2112 }
2113 if ( nameInvalid )
2114 {
2115 bool ok;
2116 name = QInputDialog::getText( this, tr( "Legend Patch Shape Name" ), tr( "Please enter a name for the new legend patch shape:" ), QLineEdit::Normal, name, &ok );
2117 if ( !ok )
2118 {
2119 return false;
2120 }
2121 }
2122 }
2123
2124 QStringList symbolTags = saveDlg.tags().split( ',' );
2125
2126 // add new shape to style and re-populate the list
2127 mStyle->addLegendPatchShape( name, shape );
2128 mStyle->saveLegendPatchShape( name, shape, saveDlg.isFavorite(), symbolTags );
2129
2130 mModified = true;
2131 return true;
2132}
2133
2134bool QgsStyleManagerDialog::editLegendPatchShape()
2135{
2136 const QString shapeName = currentItemName();
2137 if ( shapeName.isEmpty() )
2138 return false;
2139
2140 QgsLegendPatchShape shape = mStyle->legendPatchShape( shapeName );
2141 if ( shape.isNull() )
2142 return false;
2143
2144 // let the user edit the shape and update list when done
2145 QgsLegendPatchShapeDialog dlg( shape, this );
2146 dlg.setWindowTitle( shapeName );
2147 if ( !dlg.exec() )
2148 return false;
2149
2150 shape = dlg.shape();
2151
2152 // by adding shape to style with the same name the old effectively gets overwritten
2153 mStyle->addLegendPatchShape( shapeName, shape, true );
2154 mModified = true;
2155 return true;
2156}
2157
2158bool QgsStyleManagerDialog::addSymbol3D( const QString &type )
2159{
2160 std::unique_ptr<QgsAbstract3DSymbol> symbol( QgsApplication::symbol3DRegistry()->createSymbol( type ) );
2161 if ( !symbol )
2162 return false;
2163
2164 Qgs3DSymbolDialog dialog( symbol.get(), this );
2165 dialog.setWindowTitle( tr( "New 3D Symbol" ) );
2166 if ( isReadOnly() )
2167 dialog.buttonBox()->button( QDialogButtonBox::Ok )->setEnabled( false );
2168
2169 if ( !dialog.exec() )
2170 return false;
2171
2172 symbol.reset( dialog.symbol() );
2173 if ( !symbol )
2174 return false;
2175
2176 QgsStyleSaveDialog saveDlg( this, QgsStyle::Symbol3DEntity );
2177 const QString defaultTag = groupTree->currentIndex().isValid() ? groupTree->currentIndex().data( GroupModelRoles::TagName ).toString() : QString();
2178 saveDlg.setDefaultTags( defaultTag );
2179 if ( !saveDlg.exec() )
2180 return false;
2181 QString name = saveDlg.name();
2182
2183 // request valid/unique name
2184 bool nameInvalid = true;
2185 while ( nameInvalid )
2186 {
2187 // validate name
2188 if ( name.isEmpty() )
2189 {
2190 QMessageBox::warning( this, tr( "Save 3D Symbol" ), tr( "Cannot save 3D symbols without a name. Enter a name." ) );
2191 }
2192 else if ( mStyle->symbol3DNames().contains( name ) )
2193 {
2194 int res = QMessageBox::warning( this, tr( "Save 3D Symbol" ), tr( "A 3D symbol with the name '%1' already exists. Overwrite?" ).arg( name ), QMessageBox::Yes | QMessageBox::No );
2195 if ( res == QMessageBox::Yes )
2196 {
2197 mStyle->removeEntityByName( QgsStyle::Symbol3DEntity, name );
2198 nameInvalid = false;
2199 }
2200 }
2201 else
2202 {
2203 // valid name
2204 nameInvalid = false;
2205 }
2206 if ( nameInvalid )
2207 {
2208 bool ok;
2209 name = QInputDialog::getText( this, tr( "3D Symbol Name" ), tr( "Please enter a name for the new 3D symbol:" ), QLineEdit::Normal, name, &ok );
2210 if ( !ok )
2211 {
2212 return false;
2213 }
2214 }
2215 }
2216
2217 QStringList symbolTags = saveDlg.tags().split( ',' );
2218
2219 // add new shape to style and re-populate the list
2220 QgsAbstract3DSymbol *newSymbol = symbol.get();
2221 mStyle->addSymbol3D( name, symbol.release() );
2222 mStyle->saveSymbol3D( name, newSymbol, saveDlg.isFavorite(), symbolTags );
2223
2224 mModified = true;
2225 return true;
2226}
2227
2228bool QgsStyleManagerDialog::editSymbol3D()
2229{
2230 const QString symbolName = currentItemName();
2231 if ( symbolName.isEmpty() )
2232 return false;
2233
2234 std::unique_ptr<QgsAbstract3DSymbol> symbol( mStyle->symbol3D( symbolName ) );
2235 if ( !symbol )
2236 return false;
2237
2238 // let the user edit the symbol and update list when done
2239 Qgs3DSymbolDialog dlg( symbol.get(), this );
2240 dlg.setWindowTitle( symbolName );
2241 if ( !dlg.exec() )
2242 return false;
2243
2244 symbol.reset( dlg.symbol() );
2245 if ( !symbol )
2246 return false;
2247
2248 // by adding symbol to style with the same name the old effectively gets overwritten
2249 mStyle->addSymbol3D( symbolName, symbol.release(), true );
2250 mModified = true;
2251 return true;
2252}
2253
2254bool QgsStyleManagerDialog::addMaterialSettings()
2255{
2256 QgsMaterialWidgetDialog dialog( nullptr, this );
2257 dialog.setWindowTitle( tr( "New Material" ) );
2258 if ( isReadOnly() )
2259 dialog.buttonBox()->button( QDialogButtonBox::Ok )->setEnabled( false );
2260
2261 if ( !dialog.exec() )
2262 return false;
2263
2264 std::unique_ptr< QgsAbstractMaterialSettings > settings = dialog.settings();
2265 if ( !settings )
2266 return false;
2267
2268 QgsStyleSaveDialog saveDlg( this, QgsStyle::MaterialSettingsEntity );
2269 const QString defaultTag = groupTree->currentIndex().isValid() ? groupTree->currentIndex().data( GroupModelRoles::TagName ).toString() : QString();
2270 saveDlg.setDefaultTags( defaultTag );
2271 if ( !saveDlg.exec() )
2272 return false;
2273 QString name = saveDlg.name();
2274
2275 // request valid/unique name
2276 bool nameInvalid = true;
2277 while ( nameInvalid )
2278 {
2279 // validate name
2280 if ( name.isEmpty() )
2281 {
2282 QMessageBox::warning( this, tr( "Save Material" ), tr( "Cannot save materials without a name. Enter a name." ) );
2283 }
2284 else if ( mStyle->materialSettingsNames().contains( name ) )
2285 {
2286 int res = QMessageBox::warning( this, tr( "Save Material" ), tr( "A material with the name '%1' already exists. Overwrite?" ).arg( name ), QMessageBox::Yes | QMessageBox::No );
2287 if ( res == QMessageBox::Yes )
2288 {
2289 mStyle->removeEntityByName( QgsStyle::MaterialSettingsEntity, name );
2290 nameInvalid = false;
2291 }
2292 }
2293 else
2294 {
2295 // valid name
2296 nameInvalid = false;
2297 }
2298 if ( nameInvalid )
2299 {
2300 bool ok;
2301 name = QInputDialog::getText( this, tr( "Material Name" ), tr( "Please enter a name for the new material:" ), QLineEdit::Normal, name, &ok );
2302 if ( !ok )
2303 {
2304 return false;
2305 }
2306 }
2307 }
2308
2309 QStringList symbolTags = saveDlg.tags().split( ',' );
2310
2311 // add new material to style and re-populate the list
2312 QgsAbstractMaterialSettings *newSettings = settings.get();
2313 mStyle->addMaterialSettings( name, settings.release() );
2314 mStyle->saveMaterialSettings( name, newSettings, saveDlg.isFavorite(), symbolTags );
2315
2316 mModified = true;
2317 return true;
2318}
2319
2320bool QgsStyleManagerDialog::editMaterialSettings()
2321{
2322 const QString settingsName = currentItemName();
2323 if ( settingsName.isEmpty() )
2324 return false;
2325
2326 std::unique_ptr<QgsAbstractMaterialSettings> settings( mStyle->materialSettings( settingsName ) );
2327 if ( !settings )
2328 return false;
2329
2330 // let the user edit the settings and update list when done
2331 QgsMaterialWidgetDialog dlg( settings.get(), this );
2332 dlg.setWindowTitle( settingsName );
2333 if ( !dlg.exec() )
2334 return false;
2335
2336 settings = dlg.settings();
2337 if ( !settings )
2338 return false;
2339
2340 // by adding the setting to style with the same name the old effectively gets overwritten
2341 mStyle->addMaterialSettings( settingsName, settings.release(), true );
2342 mModified = true;
2343 return true;
2344}
2345
2346void QgsStyleManagerDialog::addStyleDatabase( bool createNew )
2347{
2349 if ( initialFolder.isEmpty() )
2350 initialFolder = QDir::homePath();
2351
2352 QString databasePath = createNew ? QFileDialog::getSaveFileName( this, tr( "Create Style Database" ), initialFolder, tr( "Style databases" ) + " (*.db)" )
2353 : QFileDialog::getOpenFileName( this, tr( "Add Style Database" ), initialFolder, tr( "Style databases" ) + " (*.db *.xml)" );
2354 // return dialog focus on Mac
2355 activateWindow();
2356 raise();
2357 if ( !databasePath.isEmpty() )
2358 {
2359 QgsStyleManagerDialog::settingLastStyleDatabaseFolder->setValue( QFileInfo( databasePath ).path() );
2360
2361 if ( createNew )
2362 {
2363 databasePath = QgsFileUtils::ensureFileNameHasExtension( databasePath, { u"db"_s } );
2364 if ( QFile::exists( databasePath ) )
2365 {
2366 QFile::remove( databasePath );
2367 }
2368 QgsStyle s;
2369 if ( !s.createDatabase( databasePath ) )
2370 {
2371 QMessageBox::warning( this, tr( "Create Style Database" ), tr( "The style database could not be created" ) );
2372 return;
2373 }
2374 }
2375
2377 setCurrentStyle( QgsProject::instance()->styleSettings()->styleAtPath( databasePath ) );
2378 }
2379}
2380
2382{
2383 const QList<ItemDetails> items = selectedItems();
2384
2385 if ( allTypesSelected() )
2386 {
2387 if ( QMessageBox::Yes
2388 != QMessageBox::question( this, tr( "Remove Items" ), QString( tr( "Do you really want to remove %n item(s)?", nullptr, static_cast< int >( items.count() ) ) ), QMessageBox::Yes, QMessageBox::No ) )
2389 return;
2390 }
2391 else
2392 {
2393 if ( currentItemType() < 3 )
2394 {
2395 if ( QMessageBox::Yes
2396 != QMessageBox::question( this, tr( "Remove Symbol" ), QString( tr( "Do you really want to remove %n symbol(s)?", nullptr, static_cast< int >( items.count() ) ) ), QMessageBox::Yes, QMessageBox::No ) )
2397 return;
2398 }
2399 else if ( currentItemType() == 3 )
2400 {
2401 if ( QMessageBox::Yes
2402 != QMessageBox::question( this, tr( "Remove Color Ramp" ), QString( tr( "Do you really want to remove %n ramp(s)?", nullptr, static_cast< int >( items.count() ) ) ), QMessageBox::Yes, QMessageBox::No ) )
2403 return;
2404 }
2405 else if ( currentItemType() == 4 )
2406 {
2407 if ( QMessageBox::Yes
2408 != QMessageBox::
2409 question( this, tr( "Remove Text Formats" ), QString( tr( "Do you really want to remove %n text format(s)?", nullptr, static_cast< int >( items.count() ) ) ), QMessageBox::Yes, QMessageBox::No ) )
2410 return;
2411 }
2412 else if ( currentItemType() == 5 )
2413 {
2414 if ( QMessageBox::Yes
2415 != QMessageBox::
2416 question( this, tr( "Remove Label Settings" ), QString( tr( "Do you really want to remove %n label setting(s)?", nullptr, static_cast< int >( items.count() ) ) ), QMessageBox::Yes, QMessageBox::No ) )
2417 return;
2418 }
2419 else if ( currentItemType() == 6 )
2420 {
2421 if ( QMessageBox::Yes
2422 != QMessageBox::
2423 question( this, tr( "Remove Legend Patch Shapes" ), QString( tr( "Do you really want to remove %n legend patch shape(s)?", nullptr, static_cast< int >( items.count() ) ) ), QMessageBox::Yes, QMessageBox::No ) )
2424 return;
2425 }
2426 else if ( currentItemType() == 7 )
2427 {
2428 if ( QMessageBox::Yes
2429 != QMessageBox::
2430 question( this, tr( "Remove 3D Symbols" ), QString( tr( "Do you really want to remove %n 3D symbol(s)?", nullptr, static_cast< int >( items.count() ) ) ), QMessageBox::Yes, QMessageBox::No ) )
2431 return;
2432 }
2433 else if ( currentItemType() == 8 )
2434 {
2435 if ( QMessageBox::Yes
2436 != QMessageBox::
2437 question( this, tr( "Remove Material" ), QString( tr( "Do you really want to remove %n material(s)?", nullptr, static_cast< int >( items.count() ) ) ), QMessageBox::Yes, QMessageBox::No ) )
2438 return;
2439 }
2440 }
2441
2442 QgsTemporaryCursorOverride override( Qt::WaitCursor );
2443
2444 for ( const ItemDetails &details : items )
2445 {
2446 if ( details.name.isEmpty() )
2447 continue;
2448
2449 mStyle->removeEntityByName( details.entityType, details.name );
2450 }
2451
2452 mModified = true;
2453}
2454
2456{
2457 return false;
2458}
2459
2461{
2462 return false;
2463}
2464
2466{}
2467
2469{
2470 QString dir = QFileDialog::getExistingDirectory( this, tr( "Export Selected Symbols as PNG" ), QDir::home().absolutePath(), QFileDialog::DontResolveSymlinks );
2471 exportSelectedItemsImages( dir, u"png"_s, QSize( 32, 32 ) );
2472}
2473
2475{
2476 QString dir = QFileDialog::getExistingDirectory( this, tr( "Export Selected Symbols as SVG" ), QDir::home().absolutePath(), QFileDialog::DontResolveSymlinks );
2477 exportSelectedItemsImages( dir, u"svg"_s, QSize( 32, 32 ) );
2478}
2479
2480
2481void QgsStyleManagerDialog::exportSelectedItemsImages( const QString &dir, const QString &format, QSize size )
2482{
2483 if ( dir.isEmpty() )
2484 return;
2485
2486 const QList<ItemDetails> items = selectedItems();
2487 for ( const ItemDetails &details : items )
2488 {
2489 if ( details.entityType != QgsStyle::SymbolEntity )
2490 continue;
2491
2492 QString path = dir + '/' + details.name + '.' + format;
2493 std::unique_ptr<QgsSymbol> sym( mStyle->symbol( details.name ) );
2494 if ( sym )
2495 sym->exportImage( path, format, size );
2496 }
2497}
2498
2500{
2502 dlg.exec();
2503}
2504
2506{
2508 dlg.exec();
2509 populateList();
2511}
2512
2513void QgsStyleManagerDialog::setBold( QStandardItem *item )
2514{
2515 QFont font = item->font();
2516 font.setBold( true );
2517 item->setFont( font );
2518}
2519
2521{
2522 if ( mBlockGroupUpdates )
2523 return;
2524
2525 QStandardItemModel *model = qobject_cast<QStandardItemModel *>( groupTree->model() );
2526 model->clear();
2527
2528 const bool readOnly = isReadOnly();
2529
2530 if ( mFavoritesGroupVisible )
2531 {
2532 QStandardItem *favoriteSymbols = new QStandardItem( tr( "Favorites" ) );
2533 favoriteSymbols->setData( "favorite" );
2534 favoriteSymbols->setEditable( false );
2535 setBold( favoriteSymbols );
2536 model->appendRow( favoriteSymbols );
2537 }
2538
2539 QStandardItem *allSymbols = new QStandardItem( tr( "All" ) );
2540 allSymbols->setData( "all" );
2541 allSymbols->setEditable( false );
2542 setBold( allSymbols );
2543 model->appendRow( allSymbols );
2544
2545 QStandardItem *taggroup = new QStandardItem( QString() ); //require empty name to get first order groups
2546 taggroup->setData( "tags" );
2547 taggroup->setEditable( false );
2548 QStringList tags = mStyle->tags();
2549 tags.sort();
2550 for ( const QString &tag : std::as_const( tags ) )
2551 {
2552 QStandardItem *item = new QStandardItem( tag );
2553 item->setData( mStyle->tagId( tag ) );
2554 item->setData( tag, GroupModelRoles::TagName );
2555 item->setEditable( !readOnly );
2556 taggroup->appendRow( item );
2557 }
2558 taggroup->setText( tr( "Tags" ) ); //set title later
2559 setBold( taggroup );
2560 model->appendRow( taggroup );
2561
2562 if ( mSmartGroupVisible )
2563 {
2564 QStandardItem *smart = new QStandardItem( tr( "Smart Groups" ) );
2565 smart->setData( "smartgroups" );
2566 smart->setEditable( false );
2567 setBold( smart );
2568 QgsSymbolGroupMap sgMap = mStyle->smartgroupsListMap();
2569 QgsSymbolGroupMap::const_iterator i = sgMap.constBegin();
2570 while ( i != sgMap.constEnd() )
2571 {
2572 QStandardItem *item = new QStandardItem( i.value() );
2573 item->setData( i.key() );
2574 item->setEditable( !readOnly );
2575 smart->appendRow( item );
2576 ++i;
2577 }
2578 model->appendRow( smart );
2579 }
2580
2581 // expand things in the group tree
2582 int rows = model->rowCount( model->indexFromItem( model->invisibleRootItem() ) );
2583 for ( int i = 0; i < rows; i++ )
2584 {
2585 groupTree->setExpanded( model->indexFromItem( model->item( i ) ), true );
2586 }
2587}
2588
2589void QgsStyleManagerDialog::groupChanged( const QModelIndex &index )
2590{
2591 const QString category = index.data( Qt::UserRole + 1 ).toString();
2592 sPreviousTag = category;
2593
2594 const bool readOnly = isReadOnly();
2595
2596 if ( mGroupingMode && mModel )
2597 {
2598 mModel->setTagId( -1 );
2599 mModel->setSmartGroupId( -1 );
2600 mModel->setFavoritesOnly( false );
2601 mModel->setCheckTag( index.data( Qt::DisplayRole ).toString() );
2602 }
2603 else if ( category == "all"_L1 || category == "tags"_L1 || category == "smartgroups"_L1 )
2604 {
2605 enableGroupInputs( false );
2606 if ( category == "tags"_L1 )
2607 {
2608 actnAddTag->setEnabled( !readOnly );
2609 actnAddSmartgroup->setEnabled( false );
2610 }
2611 else if ( category == "smartgroups"_L1 )
2612 {
2613 actnAddTag->setEnabled( false );
2614 actnAddSmartgroup->setEnabled( !readOnly );
2615 }
2616
2617 if ( mModel )
2618 {
2619 mModel->setTagId( -1 );
2620 mModel->setSmartGroupId( -1 );
2621 mModel->setFavoritesOnly( false );
2622 }
2623 }
2624 else if ( category == "favorite"_L1 )
2625 {
2626 enableGroupInputs( false );
2627 if ( mModel )
2628 {
2629 mModel->setTagId( -1 );
2630 mModel->setSmartGroupId( -1 );
2631 mModel->setFavoritesOnly( true );
2632 }
2633 }
2634 else if ( index.parent().data( Qt::UserRole + 1 ) == "smartgroups" )
2635 {
2636 actnRemoveGroup->setEnabled( !readOnly );
2637 btnManageGroups->setEnabled( !readOnly );
2638 const int groupId = index.data( Qt::UserRole + 1 ).toInt();
2639 if ( mModel )
2640 {
2641 mModel->setTagId( -1 );
2642 mModel->setSmartGroupId( groupId );
2643 mModel->setFavoritesOnly( false );
2644 }
2645 }
2646 else // tags
2647 {
2648 enableGroupInputs( true );
2649 int tagId = index.data( Qt::UserRole + 1 ).toInt();
2650 if ( mModel )
2651 {
2652 mModel->setTagId( tagId );
2653 mModel->setSmartGroupId( -1 );
2654 mModel->setFavoritesOnly( false );
2655 }
2656 }
2657
2658 actnEditSmartGroup->setVisible( false );
2659 actnAddTag->setVisible( false );
2660 actnAddSmartgroup->setVisible( false );
2661 actnRemoveGroup->setVisible( false );
2662 actnTagSymbols->setVisible( false );
2663 actnFinishTagging->setVisible( false );
2664
2665 if ( index.parent().isValid() )
2666 {
2667 if ( index.parent().data( Qt::UserRole + 1 ).toString() == "smartgroups"_L1 )
2668 {
2669 actnEditSmartGroup->setVisible( !mGroupingMode && !readOnly );
2670 }
2671 else if ( index.parent().data( Qt::UserRole + 1 ).toString() == "tags"_L1 )
2672 {
2673 actnAddTag->setVisible( !mGroupingMode && !readOnly );
2674 actnTagSymbols->setVisible( !mGroupingMode && !readOnly );
2675 actnFinishTagging->setVisible( mGroupingMode && !readOnly );
2676 }
2677 actnRemoveGroup->setVisible( !readOnly );
2678 }
2679 else if ( index.data( Qt::UserRole + 1 ) == "smartgroups" )
2680 {
2681 actnAddSmartgroup->setVisible( !mGroupingMode && !readOnly );
2682 }
2683 else if ( index.data( Qt::UserRole + 1 ) == "tags" )
2684 {
2685 actnAddTag->setVisible( !mGroupingMode && !readOnly );
2686 }
2687}
2688
2690{
2691 if ( isReadOnly() )
2692 return 0;
2693
2694 QStandardItemModel *model = qobject_cast<QStandardItemModel *>( groupTree->model() );
2695 QModelIndex index;
2696 for ( int i = 0; i < groupTree->model()->rowCount(); i++ )
2697 {
2698 index = groupTree->model()->index( i, 0 );
2699 QString data = index.data( Qt::UserRole + 1 ).toString();
2700 if ( data == "tags"_L1 )
2701 {
2702 break;
2703 }
2704 }
2705
2706 QString itemName;
2707 int id;
2708 bool ok;
2709 itemName = QInputDialog::getText( this, tr( "Add Tag" ), tr( "Please enter name for the new tag:" ), QLineEdit::Normal, tr( "New tag" ), &ok ).trimmed();
2710 if ( !ok || itemName.isEmpty() )
2711 return 0;
2712
2713 int check = mStyle->tagId( itemName );
2714 if ( check > 0 )
2715 {
2716 mMessageBar->pushCritical( tr( "Add Tag" ), tr( "The tag “%1” already exists." ).arg( itemName ) );
2717 return 0;
2718 }
2719
2720 // block the auto-repopulation of groups when the style emits groupsModified
2721 // instead, we manually update the model items for better state retention
2722 mBlockGroupUpdates++;
2723 id = mStyle->addTag( itemName );
2724 mBlockGroupUpdates--;
2725
2726 if ( !id )
2727 {
2728 mMessageBar->pushCritical( tr( "Add Tag" ), tr( "New tag could not be created — There was a problem with the symbol database." ) );
2729 return 0;
2730 }
2731
2732 QStandardItem *parentItem = model->itemFromIndex( index );
2733 QStandardItem *childItem = new QStandardItem( itemName );
2734 childItem->setData( id );
2735 childItem->setData( itemName, GroupModelRoles::TagName );
2736 parentItem->appendRow( childItem );
2737
2738 return id;
2739}
2740
2742{
2743 if ( isReadOnly() )
2744 return 0;
2745
2746 QStandardItemModel *model = qobject_cast<QStandardItemModel *>( groupTree->model() );
2747 QModelIndex index;
2748 for ( int i = 0; i < groupTree->model()->rowCount(); i++ )
2749 {
2750 index = groupTree->model()->index( i, 0 );
2751 QString data = index.data( Qt::UserRole + 1 ).toString();
2752 if ( data == "smartgroups"_L1 )
2753 {
2754 break;
2755 }
2756 }
2757
2758 QString itemName;
2759 int id;
2760 QgsSmartGroupEditorDialog dlg( mStyle, this );
2761 if ( dlg.exec() == QDialog::Rejected )
2762 return 0;
2763
2764 // block the auto-repopulation of groups when the style emits groupsModified
2765 // instead, we manually update the model items for better state retention
2766 mBlockGroupUpdates++;
2767 id = mStyle->addSmartgroup( dlg.smartgroupName(), dlg.conditionOperator(), dlg.conditionMap() );
2768 mBlockGroupUpdates--;
2769
2770 if ( !id )
2771 return 0;
2772 itemName = dlg.smartgroupName();
2773
2774 QStandardItem *parentItem = model->itemFromIndex( index );
2775 QStandardItem *childItem = new QStandardItem( itemName );
2776 childItem->setData( id );
2777 parentItem->appendRow( childItem );
2778
2779 return id;
2780}
2781
2783{
2784 if ( isReadOnly() )
2785 return;
2786
2787 QStandardItemModel *model = qobject_cast<QStandardItemModel *>( groupTree->model() );
2788 QModelIndex index = groupTree->currentIndex();
2789
2790 // do not allow removal of system-defined groupings
2791 QString data = index.data( Qt::UserRole + 1 ).toString();
2792 if ( data == "all"_L1 || data == "favorite"_L1 || data == "tags"_L1 || index.data() == "smartgroups" )
2793 {
2794 // should never appear -- blocked by GUI
2795 int err = QMessageBox::critical(
2796 this,
2797 tr( "Remove Group" ),
2798 tr(
2799 "Invalid selection. Cannot delete system defined categories.\n"
2800 "Kindly select a group or smart group you might want to delete."
2801 )
2802 );
2803 if ( err )
2804 return;
2805 }
2806
2807 QStandardItem *parentItem = model->itemFromIndex( index.parent() );
2808
2809 // block the auto-repopulation of groups when the style emits groupsModified
2810 // instead, we manually update the model items for better state retention
2811 mBlockGroupUpdates++;
2812
2813 if ( parentItem->data( Qt::UserRole + 1 ).toString() == "smartgroups"_L1 )
2814 {
2815 mStyle->remove( QgsStyle::SmartgroupEntity, index.data( Qt::UserRole + 1 ).toInt() );
2816 }
2817 else
2818 {
2819 mStyle->remove( QgsStyle::TagEntity, index.data( Qt::UserRole + 1 ).toInt() );
2820 }
2821
2822 mBlockGroupUpdates--;
2823 parentItem->removeRow( index.row() );
2824}
2825
2826void QgsStyleManagerDialog::groupRenamed( QStandardItem *item )
2827{
2828 if ( isReadOnly() )
2829 return;
2830
2831 QgsDebugMsgLevel( u"Symbol group edited: data=%1 text=%2"_s.arg( item->data( Qt::UserRole + 1 ).toString(), item->text() ), 2 );
2832 int id = item->data( Qt::UserRole + 1 ).toInt();
2833 QString name = item->text();
2834 mBlockGroupUpdates++;
2835 if ( item->parent()->data( Qt::UserRole + 1 ) == "smartgroups" )
2836 {
2837 mStyle->rename( QgsStyle::SmartgroupEntity, id, name );
2838 }
2839 else
2840 {
2841 mStyle->rename( QgsStyle::TagEntity, id, name );
2842 }
2843 mBlockGroupUpdates--;
2844}
2845
2847{
2848 if ( isReadOnly() )
2849 return;
2850
2851 QStandardItemModel *treeModel = qobject_cast<QStandardItemModel *>( groupTree->model() );
2852
2853 if ( mGroupingMode )
2854 {
2855 mGroupingMode = false;
2856 mModel->setCheckable( false );
2857 actnTagSymbols->setVisible( true );
2858 actnFinishTagging->setVisible( false );
2859 // disconnect slot which handles regrouping
2860
2861 // disable all items except groups in groupTree
2863 groupChanged( groupTree->currentIndex() );
2864
2865 // Finally: Reconnect all Symbol editing functionalities
2866 connect( treeModel, &QStandardItemModel::itemChanged, this, &QgsStyleManagerDialog::groupRenamed );
2867
2868 // Reset the selection mode
2869 listItems->setSelectionMode( QAbstractItemView::ExtendedSelection );
2870 mSymbolTreeView->setSelectionMode( QAbstractItemView::ExtendedSelection );
2871 }
2872 else
2873 {
2874 bool validGroup = false;
2875 // determine whether it is a valid group
2876 QModelIndex present = groupTree->currentIndex();
2877 while ( present.parent().isValid() )
2878 {
2879 if ( present.parent().data() == "Tags" )
2880 {
2881 validGroup = true;
2882 break;
2883 }
2884 present = present.parent();
2885 }
2886 if ( !validGroup )
2887 return;
2888
2889 mGroupingMode = true;
2890 // Change visibility of actions
2891 actnTagSymbols->setVisible( false );
2892 actnFinishTagging->setVisible( true );
2893 // Remove all Symbol editing functionalities
2894 disconnect( treeModel, &QStandardItemModel::itemChanged, this, &QgsStyleManagerDialog::groupRenamed );
2895
2896 // disable all items except groups in groupTree
2898 groupChanged( groupTree->currentIndex() );
2899 btnManageGroups->setEnabled( true );
2900
2901 mModel->setCheckable( true );
2902
2903 // No selection should be possible
2904 listItems->setSelectionMode( QAbstractItemView::NoSelection );
2905 mSymbolTreeView->setSelectionMode( QAbstractItemView::NoSelection );
2906 }
2907}
2908
2910{}
2911
2913{}
2914
2915void QgsStyleManagerDialog::filterSymbols( const QString &qword )
2916{
2917 mModel->setFilterString( qword );
2918}
2919
2920void QgsStyleManagerDialog::symbolSelected( const QModelIndex &index )
2921{
2922 actnEditItem->setEnabled( index.isValid() && !mGroupingMode && !isReadOnly() );
2923}
2924
2925void QgsStyleManagerDialog::selectedSymbolsChanged( const QItemSelection &selected, const QItemSelection &deselected )
2926{
2927 Q_UNUSED( selected )
2928 Q_UNUSED( deselected )
2929 const bool nothingSelected = listItems->selectionModel()->selectedIndexes().empty();
2930 const bool readOnly = isReadOnly();
2931 actnRemoveItem->setDisabled( nothingSelected || readOnly );
2932 actnAddFavorite->setDisabled( nothingSelected || readOnly );
2933 actnRemoveFavorite->setDisabled( nothingSelected || readOnly );
2934 mGroupListMenu->setDisabled( nothingSelected || readOnly );
2935 actnDetag->setDisabled( nothingSelected || readOnly );
2936 actnExportAsPNG->setDisabled( nothingSelected );
2937 actnExportAsSVG->setDisabled( nothingSelected );
2938 if ( mActionCopyToDefault )
2939 mActionCopyToDefault->setDisabled( nothingSelected );
2940 mCopyToDefaultButton->setDisabled( nothingSelected );
2941 actnEditItem->setDisabled( nothingSelected || readOnly );
2942}
2943
2945{
2946 const bool readOnly = isReadOnly();
2947 groupTree->setEnabled( enable );
2948 btnAddTag->setEnabled( enable && !readOnly );
2949 btnAddSmartgroup->setEnabled( enable && !readOnly );
2950 actnAddTag->setEnabled( enable && !readOnly );
2951 actnAddSmartgroup->setEnabled( enable && !readOnly );
2952 actnRemoveGroup->setEnabled( enable && !readOnly );
2953 btnManageGroups->setEnabled( !readOnly && ( enable || mGroupingMode ) ); // always enabled in grouping mode, as it is the only way to leave grouping mode
2954 searchBox->setEnabled( enable );
2955}
2956
2958{
2959 const bool readOnly = isReadOnly();
2960 actnRemoveGroup->setEnabled( enable && !readOnly );
2961 btnManageGroups->setEnabled( !readOnly && ( enable || mGroupingMode ) ); // always enabled in grouping mode, as it is the only way to leave grouping mode
2962}
2963
2965{
2966 QStandardItemModel *treeModel = qobject_cast<QStandardItemModel *>( groupTree->model() );
2967 for ( int i = 0; i < treeModel->rowCount(); i++ )
2968 {
2969 treeModel->item( i )->setEnabled( enable );
2970
2971 if ( treeModel->item( i )->data() == "smartgroups" )
2972 {
2973 for ( int j = 0; j < treeModel->item( i )->rowCount(); j++ )
2974 {
2975 treeModel->item( i )->child( j )->setEnabled( enable );
2976 }
2977 }
2978 }
2979
2980 // The buttons
2981 // NOTE: if you ever change the layout name in the .ui file edit here too
2982 for ( int i = 0; i < symbolBtnsLayout->count(); i++ )
2983 {
2984 QWidget *w = symbolBtnsLayout->itemAt( i )->widget();
2985 if ( w )
2986 w->setEnabled( enable );
2987 }
2988
2989 // The actions
2990 actnRemoveItem->setEnabled( enable );
2991 actnEditItem->setEnabled( enable );
2992 mActionCopyItem->setEnabled( enable );
2993 mActionPasteItem->setEnabled( enable );
2994}
2995
2997{
2998 QPoint globalPos = groupTree->viewport()->mapToGlobal( point );
2999
3000 QModelIndex index = groupTree->indexAt( point );
3001 if ( index.isValid() && !mGroupingMode )
3002 mGroupTreeContextMenu->popup( globalPos );
3003}
3004
3006{
3007 QPoint globalPos = mSymbolViewStackedWidget->currentIndex() == 0 ? listItems->viewport()->mapToGlobal( point ) : mSymbolTreeView->viewport()->mapToGlobal( point );
3008
3009 // Clear all actions and create new actions for every group
3010 mGroupListMenu->clear();
3011
3012 const QModelIndexList indices = listItems->selectionModel()->selectedRows();
3013
3014 if ( !isReadOnly() )
3015 {
3016 const QStringList currentTags = indices.count() == 1 ? indices.at( 0 ).data( static_cast<int>( QgsStyleModel::CustomRole::Tag ) ).toStringList() : QStringList();
3017 QAction *a = nullptr;
3018 QStringList tags = mStyle->tags();
3019 tags.sort();
3020 for ( const QString &tag : std::as_const( tags ) )
3021 {
3022 a = new QAction( tag, mGroupListMenu );
3023 a->setData( tag );
3024 if ( indices.count() == 1 )
3025 {
3026 a->setCheckable( true );
3027 a->setChecked( currentTags.contains( tag ) );
3028 }
3029 connect( a, &QAction::triggered, this, [this]( bool ) { tagSelectedSymbols(); } );
3030 mGroupListMenu->addAction( a );
3031 }
3032
3033 if ( tags.count() > 0 )
3034 {
3035 mGroupListMenu->addSeparator();
3036 }
3037 a = new QAction( tr( "Create New Tag…" ), mGroupListMenu );
3038 connect( a, &QAction::triggered, this, [this]( bool ) { tagSelectedSymbols( true ); } );
3039 mGroupListMenu->addAction( a );
3040 }
3041
3042 const QList<ItemDetails> items = selectedItems();
3043 mActionCopyItem->setEnabled( !items.isEmpty() && ( items.at( 0 ).entityType != QgsStyle::ColorrampEntity && items.at( 0 ).entityType != QgsStyle::MaterialSettingsEntity ) );
3044
3045 bool enablePaste = false;
3046 std::unique_ptr<QgsSymbol> tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
3047 if ( tempSymbol )
3048 enablePaste = true;
3049 else
3050 {
3051 const QMimeData *mimeData = QApplication::clipboard()->mimeData();
3052 if ( mimeData->hasFormat( "application/qgis.labelsettings"_L1 ) )
3053 {
3054 ( void ) QgsPalLayerSettings::fromMimeData( mimeData, &enablePaste );
3055 }
3056 else
3057 {
3058 ( void ) QgsTextFormat::fromMimeData( mimeData, &enablePaste );
3059 }
3060 }
3061 mActionPasteItem->setEnabled( enablePaste );
3062
3063 mGroupMenu->popup( globalPos );
3064}
3065
3067{
3068 if ( isReadOnly() )
3069 return;
3070
3071 const QList<ItemDetails> items = selectedItems();
3072 for ( const ItemDetails &details : items )
3073 {
3074 mStyle->addFavorite( details.entityType, details.name );
3075 }
3076}
3077
3079{
3080 if ( isReadOnly() )
3081 return;
3082
3083 const QList<ItemDetails> items = selectedItems();
3084 for ( const ItemDetails &details : items )
3085 {
3086 mStyle->removeFavorite( details.entityType, details.name );
3087 }
3088}
3089
3091{
3092 QAction *selectedItem = qobject_cast<QAction *>( sender() );
3093 if ( selectedItem )
3094 {
3095 const QList<ItemDetails> items = selectedItems();
3096 QString tag;
3097 if ( newTag )
3098 {
3099 int id = addTag();
3100 if ( id == 0 )
3101 {
3102 return;
3103 }
3104
3105 tag = mStyle->tag( id );
3106 }
3107 else
3108 {
3109 tag = selectedItem->data().toString();
3110 }
3111
3112 for ( const ItemDetails &details : items )
3113 {
3114 mStyle->tagSymbol( details.entityType, details.name, QStringList( tag ) );
3115 }
3116 }
3117}
3118
3120{
3121 if ( isReadOnly() )
3122 return;
3123
3124 QAction *selectedItem = qobject_cast<QAction *>( sender() );
3125
3126 if ( selectedItem )
3127 {
3128 const QList<ItemDetails> items = selectedItems();
3129 for ( const ItemDetails &details : items )
3130 {
3131 mStyle->detagSymbol( details.entityType, details.name );
3132 }
3133 }
3134}
3135
3137{
3138 if ( isReadOnly() )
3139 return;
3140
3141 QStandardItemModel *treeModel = qobject_cast<QStandardItemModel *>( groupTree->model() );
3142
3143 // determine whether it is a valid group
3144 QModelIndex present = groupTree->currentIndex();
3145 if ( present.parent().data( Qt::UserRole + 1 ) != "smartgroups" )
3146 {
3147 // should never appear - blocked by GUI logic
3148 QMessageBox::critical( this, tr( "Edit Smart Group" ), tr( "You have not selected a Smart Group. Kindly select a Smart Group to edit." ) );
3149 return;
3150 }
3151 QStandardItem *item = treeModel->itemFromIndex( present );
3152
3153 QgsSmartGroupEditorDialog dlg( mStyle, this );
3154 QgsSmartConditionMap map = mStyle->smartgroup( present.data( Qt::UserRole + 1 ).toInt() );
3155 dlg.setSmartgroupName( item->text() );
3156 dlg.setOperator( mStyle->smartgroupOperator( item->data().toInt() ) );
3157 dlg.setConditionMap( map );
3158
3159 if ( dlg.exec() == QDialog::Rejected )
3160 return;
3161
3162 mBlockGroupUpdates++;
3163 mStyle->remove( QgsStyle::SmartgroupEntity, item->data().toInt() );
3164 int id = mStyle->addSmartgroup( dlg.smartgroupName(), dlg.conditionOperator(), dlg.conditionMap() );
3165 mBlockGroupUpdates--;
3166 if ( !id )
3167 {
3168 mMessageBar->pushCritical( tr( "Edit Smart Group" ), tr( "There was an error while editing the smart group." ) );
3169 return;
3170 }
3171 item->setText( dlg.smartgroupName() );
3172 item->setData( id );
3173
3174 groupChanged( present );
3175}
3176
3178{
3179 reject();
3180}
3181
3183{
3184 QgsHelp::openHelp( u"style_library/style_manager.html"_s );
3185}
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:379
@ Point
Points.
Definition qgis.h:380
@ Line
Lines.
Definition qgis.h:381
@ Polygon
Polygons.
Definition qgis.h:382
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
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:6690
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsStyleModel * defaultStyleModel()
Returns a shared QgsStyleModel containing the default style library (see QgsStyle::defaultStyle()).
static Qgs3DSymbolRegistry * symbol3DRegistry()
Returns registry of available 3D symbols.
static QString iconPath(const QString &iconFile)
Returns path to the desired icon file.
A dialog which allows users to modify the properties of a QgsColorBrewerColorRamp.
QDialogButtonBox * buttonBox() const
Returns a reference to the dialog's button box.
Color ramp utilising "Color Brewer" preset color schemes.
QgsColorBrewerColorRamp * clone() const override
Creates a clone of the color ramp.
static QString typeString()
Returns the string identifier for QgsColorBrewerColorRamp.
QString schemeName() const
Returns the name of the color brewer color scheme.
int colors() const
Returns the number of colors in the ramp.
Abstract base class for color ramps.
static QList< QPair< QString, QString > > rampTypes()
Returns a list of available ramp types, where the first value in each item is the QgsColorRamp::type(...
A dialog which allows users to modify the properties of a QgsCptCityColorRamp.
bool saveAsGradientRamp() const
Returns true if the ramp should be converted to a QgsGradientColorRamp.
QDialogButtonBox * buttonBox() const
Returns a reference to the dialog's button box.
A color ramp from the CPT City collection.
QgsCptCityColorRamp * clone() const override
Creates a clone of the color ramp.
static QString typeString()
Returns the string identifier for QgsCptCityColorRamp.
QgsGradientColorRamp * cloneGradientRamp() const
QString schemeName() const
QString variantName() const
static QString ensureFileNameHasExtension(const QString &fileName, const QStringList &extensions)
Ensures that a fileName ends with an extension from the provided list of extensions.
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
A dialog which allows users to modify the properties of a QgsGradientColorRamp.
QDialogButtonBox * buttonBox() const
Returns a reference to the dialog's button box.
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
static QString typeString()
Returns the string identifier for QgsGradientColorRamp.
QgsGradientColorRamp * clone() const override
Creates a clone of the color ramp.
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
static QIcon iconLine()
Returns an icon representing line geometries.
static QIcon iconPolygon()
Returns an icon representing polygon geometries.
static QIcon iconPoint()
Returns an icon representing point geometries.
bool isNull() const
Returns true if the patch shape is a null QgsLegendPatchShape, which indicates that the default legen...
A dialog which allows users to modify the properties of a QgsLimitedRandomColorRamp.
QDialogButtonBox * buttonBox() const
Returns a reference to the dialog's button box.
Constrained random color ramp, which returns random colors based on preset parameters.
static QString typeString()
Returns the string identifier for QgsLimitedRandomColorRamp.
QgsLimitedRandomColorRamp * clone() const override
Creates a clone of the color ramp.
A line symbol type, for rendering LineString and MultiLineString geometries.
A marker symbol type, for rendering Point and MultiPoint geometries.
static QgsPalLayerSettings fromMimeData(const QMimeData *data, bool *ok=nullptr)
Attempts to parse the provided mime data as a QgsPalLayerSettings.
Qgis::GeometryType layerType
Geometry type of layers associated with these settings.
A dialog which allows users to modify the properties of a QgsPresetSchemeColorRamp.
QDialogButtonBox * buttonBox() const
Returns a reference to the dialog's button box.
QgsPresetSchemeColorRamp ramp
A scheme based color ramp consisting of a list of predefined colors.
static QString typeString()
Returns the string identifier for QgsPresetSchemeColorRamp.
QgsPresetSchemeColorRamp * clone() const override
Creates a clone of the color ramp.
List model representing the style databases associated with a QgsProject.
void addStyleDatabasePath(const QString &path)
Adds a style database path to the project.
static QgsProject * instance()
Returns the QgsProject singleton instance.
const QgsProjectStyleSettings * styleSettings() const
Returns the project's style settings, which contains settings and properties relating to how a QgsPro...
T value(const QString &dynamicKeyPart=QString()) const
Returns settings value.
bool setValue(const T &value, const QString &dynamicKeyPart=QString()) const
Set settings value.
A string settings entry.
Stores settings for use within QGIS.
Definition qgssettings.h:68
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
static QgsSettingsProxy get()
Returns a proxy for a QgsSettings object.
A dialog for configuring a style smart group.
void setConditionMap(const QgsSmartConditionMap &)
sets up the GUI for the given conditionmap
QgsSmartConditionMap conditionMap()
returns the condition map
QString smartgroupName()
returns the value from mNameLineEdit
void setSmartgroupName(const QString &)
sets the smart group Name
void setOperator(const QString &)
sets the operator AND/OR
QString conditionOperator()
returns the AND/OR condition
@ Export
Export existing symbols mode.
Q_DECL_DEPRECATED bool removeSymbol()
void onClose()
Closes the dialog.
void groupRenamed(QStandardItem *item)
Triggered when a group item is renamed.
static const QgsSettingsEntryString * settingLastStyleDatabaseFolder
Last used folder for generic style database actions.
void addFavoriteSelectedSymbols()
Add selected symbols to favorites.
void selectedSymbolsChanged(const QItemSelection &selected, const QItemSelection &deselected)
Perform tasks when the selected symbols change.
void removeGroup()
Removes the selected tag or smartgroup.
void exportItems()
Triggers the dialog to export items.
void setFavoritesGroupVisible(bool show)
Sets whether the favorites group should be shown.
void grouptreeContextMenu(QPoint)
Context menu for the groupTree.
void setBold(QStandardItem *)
sets the text of the item with bold font
void filterSymbols(const QString &filter)
Sets the filter string to filter symbols by.
void addItem()
Triggers the dialog for adding a new item, based on the currently selected item type tab.
void tagSymbolsAction()
Toggles the interactive item tagging mode.
void editSmartgroupAction()
Triggers the dialog for editing the selected smart group.
void showHelp()
Opens the associated help.
void detagSelectedSymbols()
Remove all tags from selected symbols.
void enableSymbolInputs(bool)
Enables or disbables the symbol specific inputs.
bool addSymbol(int symbolType=-1)
add a new symbol to style
Q_DECL_DEPRECATED void populateTypes()
Populate combo box with known style items (symbols, color ramps).
void populateList()
Refreshes the list of items.
void removeItem()
Removes the current selected item.
void groupChanged(const QModelIndex &)
Triggered when the current group (or tag) is changed.
QgsStyleManagerDialog(QgsStyle *style, QWidget *parent=nullptr, Qt::WindowFlags flags=Qt::WindowFlags(), bool readOnly=false)
Constructor for QgsStyleManagerDialog, with the specified parent widget and window flags.
void enableGroupInputs(bool)
Enables or disables the groupTree specific inputs.
int addTag()
Triggers the dialog to add a new tag.
void exportItemsSVG()
Triggers the dialog to export selected items as SVG files.
Q_DECL_DEPRECATED void populateSymbols(const QStringList &symbolNames, bool checkable=false)
Populates the list view with symbols of the current type with the given names.
void populateGroups()
populate the groups
Q_DECL_DEPRECATED bool removeColorRamp()
void importItems()
Triggers the dialog to import items.
void setBaseStyleName(const QString &name)
Sets the base name for the style, which is used by the dialog to reflect the original style/XML file ...
Q_DECL_DEPRECATED void regrouped(QStandardItem *)
Q_DECL_DEPRECATED void itemChanged(QStandardItem *item)
void exportItemsPNG()
Triggers the dialog to export selected items as PNG files.
void activate()
Raises, unminimizes and activates this window.
bool addColorRamp(const QString &type=QString())
Triggers adding a new color ramp.
void exportSelectedItemsImages(const QString &dir, const QString &format, QSize size)
Triggers the dialog to export selected items as images of the specified format and size.
void enableItemsForGroupingMode(bool)
Enables or disables the groupTree items for grouping mode.
Q_DECL_DEPRECATED void setSymbolsChecked(const QStringList &)
void onFinished()
Called when the dialog is going to be closed.
void listitemsContextMenu(QPoint)
Context menu for the listItems ( symbols list ).
void setSmartGroupsVisible(bool show)
Sets whether smart groups should be shown.
static QString addColorRampStatic(QWidget *parent, QgsStyle *style, const QString &RampType=QString())
Opens the add color ramp dialog, returning the new color ramp's name if the ramp has been added.
void symbolSelected(const QModelIndex &)
Perform symbol specific tasks when selected.
void editItem()
Triggers the dialog for editing the current item.
void removeFavoriteSelectedSymbols()
Remove selected symbols from favorites.
int addSmartgroup()
Triggers the dialog to add a new smart group.
void tagSelectedSymbols(bool newTag=false)
Tag selected symbols using menu item selection.
Q_DECL_DEPRECATED void populateColorRamps(const QStringList &colorRamps, bool checkable=false)
Populates the list view with color ramps of the current type with the given names.
A QAbstractItemModel subclass for showing symbol and color ramp entities contained within a QgsStyle ...
@ SymbolType
Symbol type (for symbol or legend patch shape entities).
@ Type
Style entity type, see QgsStyle::StyleEntity.
@ Tag
String list of tags.
@ Name
Name column.
A QSortFilterProxyModel subclass for showing filtered symbol and color ramps entries from a QgsStyle ...
A dialog for setting properties of a newly saved style.
bool isFavorite() const
Returns true if the favorite is checked for the symbol.
QString name() const
Returns the entered name for the new symbol.
void setDefaultTags(const QString &tags)
Sets the default tags for the newly created item.
QString tags() const
Returns any tags entered for the new symbol (as a comma separated value list).
A database of saved style entities, including symbols, color ramps, text formats and others.
Definition qgsstyle.h:91
QgsTextFormat textFormat(const QString &name) const
Returns the text format with the specified name.
QStringList tags() const
Returns a list of all tags in the style database.
QStringList symbol3DNames() const
Returns a list of names of 3d symbols in the style.
bool saveLabelSettings(const QString &name, const QgsPalLayerSettings &settings, bool favorite, const QStringList &tags)
Adds label settings to the database.
void aboutToBeDestroyed()
Emitted just before the style object is destroyed.
bool createDatabase(const QString &filename)
Creates an on-disk database.
Definition qgsstyle.cpp:589
QStringList textFormatNames() const
Returns a list of names of text formats in the style.
bool addColorRamp(const QString &name, QgsColorRamp *colorRamp, bool update=false)
Adds a color ramp to the style.
Definition qgsstyle.cpp:363
QgsSymbol * symbol(const QString &name)
Returns a NEW copy of symbol.
Definition qgsstyle.cpp:336
StyleEntity
Enum for Entities involved in a style.
Definition qgsstyle.h:206
@ LabelSettingsEntity
Label settings.
Definition qgsstyle.h:212
@ TextFormatEntity
Text formats.
Definition qgsstyle.h:211
@ SmartgroupEntity
Smart groups.
Definition qgsstyle.h:210
@ Symbol3DEntity
3D symbol entity
Definition qgsstyle.h:214
@ SymbolEntity
Symbols.
Definition qgsstyle.h:207
@ TagEntity
Tags.
Definition qgsstyle.h:208
@ ColorrampEntity
Color ramps.
Definition qgsstyle.h:209
@ LegendPatchShapeEntity
Legend patch shape.
Definition qgsstyle.h:213
@ MaterialSettingsEntity
Material settings.
Definition qgsstyle.h:215
QStringList tagsOfSymbol(StyleEntity type, const QString &symbol)
Returns the tags associated with the symbol.
void groupsModified()
Emitted every time a tag or smartgroup has been added, removed, or renamed.
bool saveMaterialSettings(const QString &name, QgsAbstractMaterialSettings *settings, bool favorite, const QStringList &tags)
Adds 3D material settings to the database.
QStringList colorRampNames() const
Returns a list of names of color ramps.
Definition qgsstyle.cpp:551
std::unique_ptr< QgsAbstractMaterialSettings > materialSettings(const QString &name) const
Returns a new copy of the 3D material settings with the specified name.
bool addSymbol3D(const QString &name, QgsAbstract3DSymbol *symbol, bool update=false)
Adds a 3d symbol with the specified name to the style.
Definition qgsstyle.cpp:454
QStringList legendPatchShapeNames() const
Returns a list of names of legend patch shapes in the style.
void symbolSaved(const QString &name, const QgsSymbol *symbol)
Emitted every time a new symbol has been added to the database.
bool addLegendPatchShape(const QString &name, const QgsLegendPatchShape &shape, bool update=false)
Adds a legend patch shape with the specified name to the style.
Definition qgsstyle.cpp:432
QgsColorRamp * colorRamp(const QString &name) const
Returns a new copy of the specified color ramp.
Definition qgsstyle.cpp:535
bool addMaterialSettings(const QString &name, QgsAbstractMaterialSettings *settings, bool update=false)
Adds a 3D material settings with the specified name to the style.
Definition qgsstyle.cpp:476
static QgsStyle * defaultStyle(bool initialize=true)
Returns the default application-wide style.
Definition qgsstyle.cpp:164
QStringList materialSettingsNames() const
Returns a list of names of 3D material settings in the style.
bool saveSymbol(const QString &name, const QgsSymbol *symbol, bool favorite, const QStringList &tags)
Adds the symbol to the database with tags.
Definition qgsstyle.cpp:267
QStringList labelSettingsNames() const
Returns a list of names of label settings in the style.
bool saveLegendPatchShape(const QString &name, const QgsLegendPatchShape &shape, bool favorite, const QStringList &tags)
Adds a legend patch shape to the database.
bool addTextFormat(const QString &name, const QgsTextFormat &format, bool update=false)
Adds a text format with the specified name to the style.
Definition qgsstyle.cpp:388
bool saveColorRamp(const QString &name, const QgsColorRamp *ramp, bool favorite, const QStringList &tags)
Adds the colorramp to the database.
Definition qgsstyle.cpp:498
QStringList symbolsOfFavorite(StyleEntity type) const
Returns the symbol names which are flagged as favorite.
bool saveSymbol3D(const QString &name, QgsAbstract3DSymbol *symbol, bool favorite, const QStringList &tags)
Adds a 3d symbol to the database.
QgsPalLayerSettings labelSettings(const QString &name) const
Returns the label settings with the specified name.
QgsAbstract3DSymbol * symbol3D(const QString &name) const
Returns a new copy of the 3D symbol with the specified name.
bool addSymbol(const QString &name, QgsSymbol *symbol, bool update=false)
Adds a symbol to style and takes symbol's ownership.
Definition qgsstyle.cpp:242
QgsLegendPatchShape legendPatchShape(const QString &name) const
Returns the legend patch shape with the specified name.
QStringList symbolNames() const
Returns a list of names of symbols.
Definition qgsstyle.cpp:358
bool saveTextFormat(const QString &name, const QgsTextFormat &format, bool favorite, const QStringList &tags)
Adds a text format to the database.
bool addLabelSettings(const QString &name, const QgsPalLayerSettings &settings, bool update=false)
Adds label settings with the specified name to the style.
Definition qgsstyle.cpp:410
static std::unique_ptr< QgsSymbol > symbolFromMimeData(const QMimeData *data)
Attempts to parse mime data as a symbol.
static QMimeData * symbolToMimeData(const QgsSymbol *symbol)
Creates new mime data from a symbol.
A dialog that can be used to select and build a symbol.
QDialogButtonBox * buttonBox() const
Returns a reference to the dialog's button box.
Abstract base class for all rendered symbols.
Definition qgssymbol.h:227
Temporarily sets a cursor override for the QApplication for the lifetime of the object.
A simple dialog for customizing text formatting settings.
Container for all settings relating to text rendering.
static QgsTextFormat fromMimeData(const QMimeData *data, bool *ok=nullptr)
Attempts to parse the provided mime data as a QgsTextFormat.
QMultiMap< QString, QString > QgsSmartConditionMap
A multimap to hold the smart group conditions as constraint and parameter pairs.
Definition qgsstyle.h:82
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59
QMap< int, QString > QgsSymbolGroupMap
Definition qgsstyle.h:43