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