QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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 "qgsstyle.h"
20 #include "qgssymbol.h"
21 #include "qgssymbollayerutils.h"
22 #include "qgscolorramp.h"
23 
32 #include "qgssettings.h"
33 
34 #include <QAction>
35 #include <QFile>
36 #include <QFileDialog>
37 #include <QInputDialog>
38 #include <QMessageBox>
39 #include <QPushButton>
40 #include <QStandardItemModel>
41 #include <QMenu>
42 
43 #include "qgsapplication.h"
44 #include "qgslogger.h"
45 
46 QgsStyleManagerDialog::QgsStyleManagerDialog( QgsStyle *style, QWidget *parent, Qt::WindowFlags flags )
47  : QDialog( parent, flags )
48  , mStyle( style )
49 {
50  setupUi( this );
51  connect( tabItemType, &QTabWidget::currentChanged, this, &QgsStyleManagerDialog::tabItemType_currentChanged );
52  connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsStyleManagerDialog::showHelp );
53  connect( buttonBox, &QDialogButtonBox::rejected, this, &QgsStyleManagerDialog::onClose );
54 
55 #ifdef Q_OS_MAC
56  setWindowModality( Qt::WindowModal );
57 #endif
58 
59  QgsSettings settings;
60 
61  restoreGeometry( settings.value( QStringLiteral( "Windows/StyleV2Manager/geometry" ) ).toByteArray() );
62  mSplitter->setSizes( QList<int>() << 170 << 540 );
63  mSplitter->restoreState( settings.value( QStringLiteral( "Windows/StyleV2Manager/splitter" ) ).toByteArray() );
64 
65  tabItemType->setDocumentMode( true );
66  searchBox->setShowSearchIcon( true );
67  searchBox->setPlaceholderText( tr( "Filter symbols…" ) );
68 
69  connect( this, &QDialog::finished, this, &QgsStyleManagerDialog::onFinished );
70 
71  connect( listItems, &QAbstractItemView::doubleClicked, this, &QgsStyleManagerDialog::editItem );
72 
73  connect( btnAddItem, &QPushButton::clicked, this, [ = ]( bool ) { addItem(); }
74  );
75  connect( btnEditItem, &QPushButton::clicked, this, [ = ]( bool ) { editItem(); }
76  );
77  connect( actnEditItem, &QAction::triggered, this, [ = ]( bool ) { editItem(); }
78  );
79  connect( btnRemoveItem, &QPushButton::clicked, this, [ = ]( bool ) { removeItem(); }
80  );
81  connect( actnRemoveItem, &QAction::triggered, this, [ = ]( bool ) { removeItem(); }
82  );
83 
84  QMenu *shareMenu = new QMenu( tr( "Share Menu" ), this );
85  QAction *exportAction = new QAction( tr( "Export Item(s)…" ), this );
86  exportAction->setIcon( QIcon( QgsApplication::iconPath( "mActionFileSave.svg" ) ) );
87  shareMenu->addAction( exportAction );
88  QAction *importAction = new QAction( tr( "Import Item(s)…" ), this );
89  importAction->setIcon( QIcon( QgsApplication::iconPath( "mActionFileOpen.svg" ) ) );
90  shareMenu->addAction( importAction );
91  shareMenu->addSeparator();
92  shareMenu->addAction( actnExportAsPNG );
93  shareMenu->addAction( actnExportAsSVG );
94  connect( actnExportAsPNG, &QAction::triggered, this, &QgsStyleManagerDialog::exportItemsPNG );
95  connect( actnExportAsSVG, &QAction::triggered, this, &QgsStyleManagerDialog::exportItemsSVG );
96  connect( exportAction, &QAction::triggered, this, &QgsStyleManagerDialog::exportItems );
97  connect( importAction, &QAction::triggered, this, &QgsStyleManagerDialog::importItems );
98  btnShare->setMenu( shareMenu );
99 
100  QStandardItemModel *model = new QStandardItemModel( listItems );
101  listItems->setModel( model );
102  listItems->setSelectionMode( QAbstractItemView::ExtendedSelection );
103 
104  connect( model, &QStandardItemModel::itemChanged, this, &QgsStyleManagerDialog::itemChanged );
105  connect( listItems->selectionModel(), &QItemSelectionModel::currentChanged,
107  connect( listItems->selectionModel(), &QItemSelectionModel::selectionChanged,
109 
110  populateTypes();
111 
112  QStandardItemModel *groupModel = new QStandardItemModel( groupTree );
113  groupTree->setModel( groupModel );
114  groupTree->setHeaderHidden( true );
115  populateGroups();
116  groupTree->setCurrentIndex( groupTree->model()->index( 0, 0 ) );
117 
118  connect( groupTree->selectionModel(), &QItemSelectionModel::currentChanged,
120  connect( groupModel, &QStandardItemModel::itemChanged,
122 
123  QMenu *groupMenu = new QMenu( tr( "Group Actions" ), this );
124  connect( actnTagSymbols, &QAction::triggered, this, &QgsStyleManagerDialog::tagSymbolsAction );
125  groupMenu->addAction( actnTagSymbols );
126  connect( actnFinishTagging, &QAction::triggered, this, &QgsStyleManagerDialog::tagSymbolsAction );
127  actnFinishTagging->setVisible( false );
128  groupMenu->addAction( actnFinishTagging );
129  groupMenu->addAction( actnEditSmartGroup );
130  btnManageGroups->setMenu( groupMenu );
131 
132  connect( searchBox, &QLineEdit::textChanged, this, &QgsStyleManagerDialog::filterSymbols );
133 
134  // Context menu for groupTree
135  groupTree->setContextMenuPolicy( Qt::CustomContextMenu );
136  connect( groupTree, &QWidget::customContextMenuRequested,
138 
139  // Context menu for listItems
140  listItems->setContextMenuPolicy( Qt::CustomContextMenu );
141  connect( listItems, &QWidget::customContextMenuRequested,
143 
144  // Menu for the "Add item" toolbutton when in colorramp mode
145  QStringList rampTypes;
146  rampTypes << tr( "Gradient" ) << tr( "Color presets" ) << tr( "Random" ) << tr( "Catalog: cpt-city" );
147  rampTypes << tr( "Catalog: ColorBrewer" );
148  mMenuBtnAddItemColorRamp = new QMenu( this );
149  for ( const QString &rampType : qgis::as_const( rampTypes ) )
150  mMenuBtnAddItemColorRamp->addAction( new QAction( rampType, this ) );
151  connect( mMenuBtnAddItemColorRamp, &QMenu::triggered,
152  this, static_cast<bool ( QgsStyleManagerDialog::* )( QAction * )>( &QgsStyleManagerDialog::addColorRamp ) );
153 
154  // Context menu for symbols/colorramps. The menu entries for every group are created when displaying the menu.
155  mGroupMenu = new QMenu( this );
156  connect( actnAddFavorite, &QAction::triggered, this, &QgsStyleManagerDialog::addFavoriteSelectedSymbols );
157  mGroupMenu->addAction( actnAddFavorite );
158  connect( actnRemoveFavorite, &QAction::triggered, this, &QgsStyleManagerDialog::removeFavoriteSelectedSymbols );
159  mGroupMenu->addAction( actnRemoveFavorite );
160  mGroupMenu->addSeparator()->setParent( this );
161  mGroupListMenu = new QMenu( mGroupMenu );
162  mGroupListMenu->setTitle( tr( "Add to Tag" ) );
163  mGroupListMenu->setEnabled( false );
164  mGroupMenu->addMenu( mGroupListMenu );
165  actnDetag->setData( 0 );
166  connect( actnDetag, &QAction::triggered, this, &QgsStyleManagerDialog::detagSelectedSymbols );
167  mGroupMenu->addAction( actnDetag );
168  mGroupMenu->addSeparator()->setParent( this );
169  mGroupMenu->addAction( actnRemoveItem );
170  mGroupMenu->addAction( actnEditItem );
171  mGroupMenu->addSeparator()->setParent( this );
172  mGroupMenu->addAction( actnExportAsPNG );
173  mGroupMenu->addAction( actnExportAsSVG );
174 
175  // Context menu for the group tree
176  mGroupTreeContextMenu = new QMenu( this );
177  connect( actnEditSmartGroup, &QAction::triggered, this, &QgsStyleManagerDialog::editSmartgroupAction );
178  mGroupTreeContextMenu->addAction( actnEditSmartGroup );
179  connect( actnAddTag, &QAction::triggered, this, [ = ]( bool ) { addTag(); }
180  );
181  mGroupTreeContextMenu->addAction( actnAddTag );
182  connect( actnAddSmartgroup, &QAction::triggered, this, [ = ]( bool ) { addSmartgroup(); }
183  );
184  mGroupTreeContextMenu->addAction( actnAddSmartgroup );
185  connect( actnRemoveGroup, &QAction::triggered, this, &QgsStyleManagerDialog::removeGroup );
186  mGroupTreeContextMenu->addAction( actnRemoveGroup );
187 
188  tabItemType_currentChanged( 0 );
189 
192 }
193 
195 {
196  if ( mModified )
197  {
198  mStyle->save();
199  }
200 
201  QgsSettings settings;
202  settings.setValue( QStringLiteral( "Windows/StyleV2Manager/geometry" ), saveGeometry() );
203  settings.setValue( QStringLiteral( "Windows/StyleV2Manager/splitter" ), mSplitter->saveState() );
204 }
205 
207 {
208 #if 0
209  // save current selection index in types combo
210  int current = ( tabItemType->count() > 0 ? tabItemType->currentIndex() : 0 );
211 
212 // no counting of style items
213  int markerCount = 0, lineCount = 0, fillCount = 0;
214 
215  QStringList symbolNames = mStyle->symbolNames();
216  for ( int i = 0; i < symbolNames.count(); ++i )
217  {
218  switch ( mStyle->symbolRef( symbolNames[i] )->type() )
219  {
220  case QgsSymbol::Marker:
221  markerCount++;
222  break;
223  case QgsSymbol::Line:
224  lineCount++;
225  break;
226  case QgsSymbol::Fill:
227  fillCount++;
228  break;
229  default:
230  Q_ASSERT( 0 && "unknown symbol type" );
231  break;
232  }
233  }
234 
235  cboItemType->clear();
236  cboItemType->addItem( tr( "Marker symbol (%1)" ).arg( markerCount ), QVariant( QgsSymbol::Marker ) );
237  cboItemType->addItem( tr( "Line symbol (%1)" ).arg( lineCount ), QVariant( QgsSymbol::Line ) );
238  cboItemType->addItem( tr( "Fill symbol (%1)" ).arg( fillCount ), QVariant( QgsSymbol::Fill ) );
239 
240  cboItemType->addItem( tr( "Color ramp (%1)" ).arg( mStyle->colorRampCount() ), QVariant( 3 ) );
241 
242  // update current index to previous selection
243  cboItemType->setCurrentIndex( current );
244 #endif
245 }
246 
247 void QgsStyleManagerDialog::tabItemType_currentChanged( int )
248 {
249  // when in Color Ramp tab, add menu to add item button and hide "Export symbols as PNG/SVG"
250  bool flag = currentItemType() != 3;
251  searchBox->setPlaceholderText( flag ? tr( "Filter symbols…" ) : tr( "Filter color ramps…" ) );
252  btnAddItem->setMenu( flag ? nullptr : mMenuBtnAddItemColorRamp );
253  actnExportAsPNG->setVisible( flag );
254  actnExportAsSVG->setVisible( flag );
255 
256  double iconSize = Qgis::UI_SCALE_FACTOR * fontMetrics().width( 'X' ) * 10;
257  listItems->setIconSize( QSize( static_cast< int >( iconSize ), static_cast< int >( iconSize * 0.9 ) ) ); // ~100, 90 on low dpi
258 
259  populateList();
260 }
261 
263 {
264  if ( currentItemType() > 3 )
265  {
266  Q_ASSERT( false && "not implemented" );
267  return;
268  }
269  groupChanged( groupTree->selectionModel()->currentIndex() );
270 }
271 
272 void QgsStyleManagerDialog::populateSymbols( const QStringList &symbolNames, bool check )
273 {
274  QStandardItemModel *model = qobject_cast<QStandardItemModel *>( listItems->model() );
275  model->clear();
276 
277  int type = currentItemType();
278  for ( int i = 0; i < symbolNames.count(); ++i )
279  {
280  QString name = symbolNames[i];
281  std::unique_ptr< QgsSymbol > symbol( mStyle->symbol( name ) );
282  if ( symbol && symbol->type() == type )
283  {
284  QStringList tags = mStyle->tagsOfSymbol( QgsStyle::SymbolEntity, name );
285  QStandardItem *item = new QStandardItem( name );
286  QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( symbol.get(), listItems->iconSize(), static_cast< int >( listItems->iconSize().width() * 0.16 ) );
287  item->setIcon( icon );
288  item->setData( name ); // used to find out original name when user edited the name
289  QFont f = item->data( Qt::FontRole ).value< QFont >();
290  f.setPointSize( 9 );
291  item->setData( f, Qt::FontRole );
292  item->setCheckable( check );
293  item->setToolTip( QStringLiteral( "<b>%1</b><br><i>%2</i>" ).arg( name, tags.count() > 0 ? tags.join( QStringLiteral( ", " ) ) : tr( "Not tagged" ) ) );
294  // add to model
295  model->appendRow( item );
296  }
297  }
298  selectedSymbolsChanged( QItemSelection(), QItemSelection() );
299  symbolSelected( listItems->currentIndex() );
300 }
301 
302 
303 void QgsStyleManagerDialog::populateColorRamps( const QStringList &colorRamps, bool check )
304 {
305  QStandardItemModel *model = qobject_cast<QStandardItemModel *>( listItems->model() );
306  model->clear();
307 
308  for ( int i = 0; i < colorRamps.count(); ++i )
309  {
310  QString name = colorRamps[i];
311  std::unique_ptr< QgsColorRamp > ramp( mStyle->colorRamp( name ) );
312 
313  QStandardItem *item = new QStandardItem( name );
314  QIcon icon = QgsSymbolLayerUtils::colorRampPreviewIcon( ramp.get(), listItems->iconSize(), static_cast< int >( listItems->iconSize().width() * 0.16 ) );
315  item->setIcon( icon );
316  item->setData( name ); // used to find out original name when user edited the name
317  item->setCheckable( check );
318  item->setToolTip( name );
319  model->appendRow( item );
320  }
321  selectedSymbolsChanged( QItemSelection(), QItemSelection() );
322  symbolSelected( listItems->currentIndex() );
323 }
324 
326 {
327  switch ( tabItemType->currentIndex() )
328  {
329  case 0:
330  return QgsSymbol::Marker;
331  case 1:
332  return QgsSymbol::Line;
333  case 2:
334  return QgsSymbol::Fill;
335  case 3:
336  return 3;
337  default:
338  return 0;
339  }
340 }
341 
343 {
344  QModelIndex index = listItems->selectionModel()->currentIndex();
345  if ( !index.isValid() )
346  return QString();
347  return index.model()->data( index, 0 ).toString();
348 }
349 
351 {
352  bool changed = false;
353  if ( currentItemType() < 3 )
354  {
355  changed = addSymbol();
356  }
357  else if ( currentItemType() == 3 )
358  {
359  changed = addColorRamp();
360  }
361  else
362  {
363  Q_ASSERT( false && "not implemented" );
364  }
365 
366  if ( changed )
367  {
368  populateList();
369  populateTypes();
370  }
371 }
372 
374 {
375  // create new symbol with current type
376  QgsSymbol *symbol = nullptr;
377  QString name = tr( "new symbol" );
378  switch ( currentItemType() )
379  {
380  case QgsSymbol::Marker:
381  symbol = new QgsMarkerSymbol();
382  name = tr( "new marker" );
383  break;
384  case QgsSymbol::Line:
385  symbol = new QgsLineSymbol();
386  name = tr( "new line" );
387  break;
388  case QgsSymbol::Fill:
389  symbol = new QgsFillSymbol();
390  name = tr( "new fill symbol" );
391  break;
392  default:
393  Q_ASSERT( false && "unknown symbol type" );
394  return false;
395  }
396 
397  // get symbol design
398  // NOTE : Set the parent widget as "this" to notify the Symbol selector
399  // that, it is being called by Style Manager, so recursive calling
400  // of style manager and symbol selector can be arrested
401  // See also: editSymbol()
402  QgsSymbolSelectorDialog dlg( symbol, mStyle, nullptr, this );
403  if ( dlg.exec() == 0 )
404  {
405  delete symbol;
406  return false;
407  }
408 
409  QgsStyleSaveDialog saveDlg( this );
410  if ( !saveDlg.exec() )
411  {
412  delete symbol;
413  return false;
414  }
415 
416  name = saveDlg.name();
417 
418  // request valid/unique name
419  bool nameInvalid = true;
420  while ( nameInvalid )
421  {
422  // validate name
423  if ( name.isEmpty() )
424  {
425  QMessageBox::warning( this, tr( "Save Symbol" ),
426  tr( "Cannot save symbol without name. Enter a name." ) );
427  }
428  else if ( mStyle->symbolNames().contains( name ) )
429  {
430  int res = QMessageBox::warning( this, tr( "Save Symbol" ),
431  tr( "Symbol with name '%1' already exists. Overwrite?" )
432  .arg( name ),
433  QMessageBox::Yes | QMessageBox::No );
434  if ( res == QMessageBox::Yes )
435  {
436  mStyle->removeSymbol( name );
437  nameInvalid = false;
438  }
439  }
440  else
441  {
442  // valid name
443  nameInvalid = false;
444  }
445  if ( nameInvalid )
446  {
447  bool ok;
448  name = QInputDialog::getText( this, tr( "Symbol Name" ),
449  tr( "Please enter a name for new symbol:" ),
450  QLineEdit::Normal, name, &ok );
451  if ( !ok )
452  {
453  delete symbol;
454  return false;
455  }
456  }
457  }
458 
459  QStringList symbolTags = saveDlg.tags().split( ',' );
460 
461  // add new symbol to style and re-populate the list
462  mStyle->addSymbol( name, symbol );
463  mStyle->saveSymbol( name, symbol, saveDlg.isFavorite(), symbolTags );
464 
465  mModified = true;
466  return true;
467 }
468 
469 
470 QString QgsStyleManagerDialog::addColorRampStatic( QWidget *parent, QgsStyle *style, QString rampType )
471 {
472  // let the user choose the color ramp type if rampType is not given
473  bool ok = true;
474  if ( rampType.isEmpty() )
475  {
476  QStringList rampTypes;
477  rampTypes << tr( "Gradient" ) << tr( "Color presets" ) << tr( "Random" ) << tr( "Catalog: cpt-city" );
478  rampTypes << tr( "Catalog: ColorBrewer" );
479  rampType = QInputDialog::getItem( parent, tr( "Color Ramp Type" ),
480  tr( "Please select color ramp type:" ), rampTypes, 0, false, &ok );
481  }
482  if ( !ok || rampType.isEmpty() )
483  return QString();
484 
485  QString name = tr( "new ramp" );
486 
487  std::unique_ptr< QgsColorRamp > ramp;
488  if ( rampType == tr( "Gradient" ) )
489  {
491  if ( !dlg.exec() )
492  {
493  return QString();
494  }
495  ramp.reset( dlg.ramp().clone() );
496  name = tr( "new gradient ramp" );
497  }
498  else if ( rampType == tr( "Random" ) )
499  {
501  if ( !dlg.exec() )
502  {
503  return QString();
504  }
505  ramp.reset( dlg.ramp().clone() );
506  name = tr( "new random ramp" );
507  }
508  else if ( rampType == tr( "Catalog: ColorBrewer" ) )
509  {
511  if ( !dlg.exec() )
512  {
513  return QString();
514  }
515  ramp.reset( dlg.ramp().clone() );
516  name = dlg.ramp().schemeName() + QString::number( dlg.ramp().colors() );
517  }
518  else if ( rampType == tr( "Color presets" ) )
519  {
521  if ( !dlg.exec() )
522  {
523  return QString();
524  }
525  ramp.reset( dlg.ramp().clone() );
526  name = tr( "new preset ramp" );
527  }
528  else if ( rampType == tr( "Catalog: cpt-city" ) )
529  {
530  QgsCptCityColorRampDialog dlg( QgsCptCityColorRamp( QString(), QString() ), parent );
531  if ( !dlg.exec() )
532  {
533  return QString();
534  }
535  // name = dlg.selectedName();
536  name = QFileInfo( dlg.ramp().schemeName() ).baseName() + dlg.ramp().variantName();
537  if ( dlg.saveAsGradientRamp() )
538  {
539  ramp.reset( dlg.ramp().cloneGradientRamp() );
540  }
541  else
542  {
543  ramp.reset( dlg.ramp().clone() );
544  }
545  }
546  else
547  {
548  // Q_ASSERT( 0 && "invalid ramp type" );
549  // bailing out is rather harsh!
550  QgsDebugMsg( QStringLiteral( "invalid ramp type %1" ).arg( rampType ) );
551  return QString();
552  }
553 
555  if ( !saveDlg.exec() )
556  {
557  return QString();
558  }
559 
560  name = saveDlg.name();
561 
562  // get valid/unique name
563  bool nameInvalid = true;
564  while ( nameInvalid )
565  {
566  // validate name
567  if ( name.isEmpty() )
568  {
569  QMessageBox::warning( parent, tr( "Save Color Ramp" ),
570  tr( "Cannot save color ramp without name. Enter a name." ) );
571  }
572  else if ( style->colorRampNames().contains( name ) )
573  {
574  int res = QMessageBox::warning( parent, tr( "Save Color Ramp" ),
575  tr( "Color ramp with name '%1' already exists. Overwrite?" )
576  .arg( name ),
577  QMessageBox::Yes | QMessageBox::No );
578  if ( res == QMessageBox::Yes )
579  {
580  nameInvalid = false;
581  }
582  }
583  else
584  {
585  // valid name
586  nameInvalid = false;
587  }
588  if ( nameInvalid )
589  {
590  bool ok;
591  name = QInputDialog::getText( parent, tr( "Color Ramp Name" ),
592  tr( "Please enter a name for new color ramp:" ),
593  QLineEdit::Normal, name, &ok );
594  if ( !ok )
595  {
596  return QString();
597  }
598  }
599  }
600 
601  QStringList colorRampTags = saveDlg.tags().split( ',' );
602  QgsColorRamp *r = ramp.release();
603 
604  // add new symbol to style and re-populate the list
605  style->addColorRamp( name, r );
606  style->saveColorRamp( name, r, saveDlg.isFavorite(), colorRampTags );
607 
608  return name;
609 }
610 
612 {
613  raise();
614  setWindowState( windowState() & ~Qt::WindowMinimized );
615  activateWindow();
616 }
617 
619 {
620  return addColorRamp( nullptr );
621 }
622 
623 bool QgsStyleManagerDialog::addColorRamp( QAction *action )
624 {
625  // pass the action text, which is the color ramp type
626  QString rampName = addColorRampStatic( this, mStyle,
627  action ? action->text() : QString() );
628  if ( !rampName.isEmpty() )
629  {
630  mModified = true;
631  populateList();
632  return true;
633  }
634 
635  return false;
636 }
637 
639 {
640  bool changed = false;
641  if ( currentItemType() < 3 )
642  {
643  changed = editSymbol();
644  }
645  else if ( currentItemType() == 3 )
646  {
647  changed = editColorRamp();
648  }
649  else
650  {
651  Q_ASSERT( false && "not implemented" );
652  }
653 
654  if ( changed )
655  populateList();
656 }
657 
659 {
660  QString symbolName = currentItemName();
661  if ( symbolName.isEmpty() )
662  return false;
663 
664  std::unique_ptr< QgsSymbol > symbol( mStyle->symbol( symbolName ) );
665 
666  // let the user edit the symbol and update list when done
667  QgsSymbolSelectorDialog dlg( symbol.get(), mStyle, nullptr, this );
668  if ( dlg.exec() == 0 )
669  {
670  return false;
671  }
672 
673  // by adding symbol to style with the same name the old effectively gets overwritten
674  mStyle->addSymbol( symbolName, symbol.release(), true );
675  mModified = true;
676  return true;
677 }
678 
680 {
681  QString name = currentItemName();
682  if ( name.isEmpty() )
683  return false;
684 
685  std::unique_ptr< QgsColorRamp > ramp( mStyle->colorRamp( name ) );
686 
687  if ( ramp->type() == QLatin1String( "gradient" ) )
688  {
689  QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( ramp.get() );
690  QgsGradientColorRampDialog dlg( *gradRamp, this );
691  if ( !dlg.exec() )
692  {
693  return false;
694  }
695  ramp.reset( dlg.ramp().clone() );
696  }
697  else if ( ramp->type() == QLatin1String( "random" ) )
698  {
699  QgsLimitedRandomColorRamp *randRamp = static_cast<QgsLimitedRandomColorRamp *>( ramp.get() );
700  QgsLimitedRandomColorRampDialog dlg( *randRamp, this );
701  if ( !dlg.exec() )
702  {
703  return false;
704  }
705  ramp.reset( dlg.ramp().clone() );
706  }
707  else if ( ramp->type() == QLatin1String( "colorbrewer" ) )
708  {
709  QgsColorBrewerColorRamp *brewerRamp = static_cast<QgsColorBrewerColorRamp *>( ramp.get() );
710  QgsColorBrewerColorRampDialog dlg( *brewerRamp, this );
711  if ( !dlg.exec() )
712  {
713  return false;
714  }
715  ramp.reset( dlg.ramp().clone() );
716  }
717  else if ( ramp->type() == QLatin1String( "preset" ) )
718  {
719  QgsPresetSchemeColorRamp *presetRamp = static_cast<QgsPresetSchemeColorRamp *>( ramp.get() );
720  QgsPresetColorRampDialog dlg( *presetRamp, this );
721  if ( !dlg.exec() )
722  {
723  return false;
724  }
725  ramp.reset( dlg.ramp().clone() );
726  }
727  else if ( ramp->type() == QLatin1String( "cpt-city" ) )
728  {
729  QgsCptCityColorRamp *cptCityRamp = static_cast<QgsCptCityColorRamp *>( ramp.get() );
730  QgsCptCityColorRampDialog dlg( *cptCityRamp, this );
731  if ( !dlg.exec() )
732  {
733  return false;
734  }
735  if ( dlg.saveAsGradientRamp() )
736  {
737  ramp.reset( dlg.ramp().cloneGradientRamp() );
738  }
739  else
740  {
741  ramp.reset( dlg.ramp().clone() );
742  }
743  }
744  else
745  {
746  Q_ASSERT( false && "invalid ramp type" );
747  }
748 
749  mStyle->addColorRamp( name, ramp.release(), true );
750  mModified = true;
751  return true;
752 }
753 
754 
756 {
757  bool changed = false;
758  if ( currentItemType() < 3 )
759  {
760  changed = removeSymbol();
761  }
762  else if ( currentItemType() == 3 )
763  {
764  changed = removeColorRamp();
765  }
766  else
767  {
768  Q_ASSERT( false && "not implemented" );
769  }
770 
771  if ( changed )
772  {
773  populateList();
774  populateTypes();
775  }
776 }
777 
779 {
780  const QModelIndexList indexes = listItems->selectionModel()->selectedIndexes();
781  if ( QMessageBox::Yes != QMessageBox::question( this, tr( "Remove Symbol" ),
782  QString( tr( "Do you really want to remove %n symbol(s)?", nullptr, indexes.count() ) ),
783  QMessageBox::Yes,
784  QMessageBox::No ) )
785  return false;
786 
787  QgsTemporaryCursorOverride override( Qt::WaitCursor );
788 
789  for ( const QModelIndex &index : indexes )
790  {
791  QString symbolName = index.data().toString();
792  // delete from style and update list
793  if ( !symbolName.isEmpty() )
794  mStyle->removeSymbol( symbolName );
795  }
796  mModified = true;
797  return true;
798 }
799 
801 {
802  const QModelIndexList indexes = listItems->selectionModel()->selectedIndexes();
803  if ( QMessageBox::Yes != QMessageBox::question( this, tr( "Remove Color Ramp" ),
804  QString( tr( "Do you really want to remove %n ramp(s)?", nullptr, indexes.count() ) ),
805  QMessageBox::Yes,
806  QMessageBox::No ) )
807  return false;
808 
809  QgsTemporaryCursorOverride override( Qt::WaitCursor );
810 
811  for ( const QModelIndex &index : indexes )
812  {
813  QString rampName = index.data().toString();
814  // delete from style and update list
815  if ( !rampName.isEmpty() )
816  mStyle->removeColorRamp( rampName );
817  }
818  mModified = true;
819  return true;
820 }
821 
822 void QgsStyleManagerDialog::itemChanged( QStandardItem *item )
823 {
824  // an item has been edited
825  QString oldName = item->data().toString();
826 
827  bool changed = false;
828  if ( currentItemType() < 3 )
829  {
830  changed = mStyle->renameSymbol( oldName, item->text() );
831  }
832  else if ( currentItemType() == 3 )
833  {
834  changed = mStyle->renameColorRamp( oldName, item->text() );
835  }
836 
837  if ( changed )
838  {
839  populateList();
840  mModified = true;
841  }
842  else
843  {
844  QMessageBox::critical( this, tr( "Save Item" ),
845  tr( "Name is already taken by another item. Choose a different name." ) );
846  item->setText( oldName );
847  }
848 }
849 
851 {
852  QString dir = QFileDialog::getExistingDirectory( this, tr( "Export Selected Symbols as PNG" ),
853  QDir::home().absolutePath(),
854  QFileDialog::ShowDirsOnly
855  | QFileDialog::DontResolveSymlinks );
856  exportSelectedItemsImages( dir, QStringLiteral( "png" ), QSize( 32, 32 ) );
857 }
858 
860 {
861  QString dir = QFileDialog::getExistingDirectory( this, tr( "Export Selected Symbols as SVG" ),
862  QDir::home().absolutePath(),
863  QFileDialog::ShowDirsOnly
864  | QFileDialog::DontResolveSymlinks );
865  exportSelectedItemsImages( dir, QStringLiteral( "svg" ), QSize( 32, 32 ) );
866 }
867 
868 
869 void QgsStyleManagerDialog::exportSelectedItemsImages( const QString &dir, const QString &format, QSize size )
870 {
871  if ( dir.isEmpty() )
872  return;
873 
874  const QModelIndexList indexes = listItems->selectionModel()->selection().indexes();
875  for ( const QModelIndex &index : indexes )
876  {
877  QString name = index.data().toString();
878  QString path = dir + '/' + name + '.' + format;
879  std::unique_ptr< QgsSymbol > sym( mStyle->symbol( name ) );
880  if ( sym )
881  sym->exportImage( path, format, size );
882  }
883 }
884 
886 {
888  dlg.exec();
889 }
890 
892 {
894  dlg.exec();
895  populateList();
896  populateGroups();
897 }
898 
899 void QgsStyleManagerDialog::setBold( QStandardItem *item )
900 {
901  QFont font = item->font();
902  font.setBold( true );
903  item->setFont( font );
904 }
905 
907 {
908  if ( mBlockGroupUpdates )
909  return;
910 
911  QStandardItemModel *model = qobject_cast<QStandardItemModel *>( groupTree->model() );
912  model->clear();
913 
914  QStandardItem *favoriteSymbols = new QStandardItem( tr( "Favorites" ) );
915  favoriteSymbols->setData( "favorite" );
916  favoriteSymbols->setEditable( false );
917  setBold( favoriteSymbols );
918  model->appendRow( favoriteSymbols );
919 
920  QStandardItem *allSymbols = new QStandardItem( tr( "All" ) );
921  allSymbols->setData( "all" );
922  allSymbols->setEditable( false );
923  setBold( allSymbols );
924  model->appendRow( allSymbols );
925 
926  QStandardItem *taggroup = new QStandardItem( QString() ); //require empty name to get first order groups
927  taggroup->setData( "tags" );
928  taggroup->setEditable( false );
929  QStringList tags = mStyle->tags();
930  tags.sort();
931  for ( const QString &tag : qgis::as_const( tags ) )
932  {
933  QStandardItem *item = new QStandardItem( tag );
934  item->setData( mStyle->tagId( tag ) );
935  taggroup->appendRow( item );
936  }
937  taggroup->setText( tr( "Tags" ) );//set title later
938  setBold( taggroup );
939  model->appendRow( taggroup );
940 
941  QStandardItem *smart = new QStandardItem( tr( "Smart Groups" ) );
942  smart->setData( "smartgroups" );
943  smart->setEditable( false );
944  setBold( smart );
945  QgsSymbolGroupMap sgMap = mStyle->smartgroupsListMap();
946  QgsSymbolGroupMap::const_iterator i = sgMap.constBegin();
947  while ( i != sgMap.constEnd() )
948  {
949  QStandardItem *item = new QStandardItem( i.value() );
950  item->setData( i.key() );
951  smart->appendRow( item );
952  ++i;
953  }
954  model->appendRow( smart );
955 
956  // expand things in the group tree
957  int rows = model->rowCount( model->indexFromItem( model->invisibleRootItem() ) );
958  for ( int i = 0; i < rows; i++ )
959  {
960  groupTree->setExpanded( model->indexFromItem( model->item( i ) ), true );
961  }
962 }
963 
964 void QgsStyleManagerDialog::groupChanged( const QModelIndex &index )
965 {
966  QStringList symbolNames;
967  QStringList groupSymbols;
968 
970  if ( currentItemType() > 3 )
971  {
972  QgsDebugMsg( QStringLiteral( "Entity not implemented" ) );
973  return;
974  }
975 
976  QString category = index.data( Qt::UserRole + 1 ).toString();
977  if ( category == QLatin1String( "all" ) || category == QLatin1String( "tags" ) || category == QLatin1String( "smartgroups" ) )
978  {
979  enableGroupInputs( false );
980  if ( category == QLatin1String( "tags" ) )
981  {
982  actnAddTag->setEnabled( true );
983  actnAddSmartgroup->setEnabled( false );
984  }
985  else if ( category == QLatin1String( "smartgroups" ) )
986  {
987  actnAddTag->setEnabled( false );
988  actnAddSmartgroup->setEnabled( true );
989  }
990  symbolNames = currentItemType() < 3 ? mStyle->symbolNames() : mStyle->colorRampNames();
991  }
992  else if ( category == QLatin1String( "favorite" ) )
993  {
994  enableGroupInputs( false );
995  symbolNames = mStyle->symbolsOfFavorite( type );
996  }
997  else if ( index.parent().data( Qt::UserRole + 1 ) == "smartgroups" )
998  {
999  actnRemoveGroup->setEnabled( true );
1000  btnManageGroups->setEnabled( true );
1001  int groupId = index.data( Qt::UserRole + 1 ).toInt();
1002  symbolNames = mStyle->symbolsOfSmartgroup( type, groupId );
1003  }
1004  else // tags
1005  {
1006  enableGroupInputs( true );
1007  int tagId = index.data( Qt::UserRole + 1 ).toInt();
1008  symbolNames = mStyle->symbolsWithTag( type, tagId );
1009  if ( mGrouppingMode && tagId )
1010  {
1011  groupSymbols = symbolNames;
1012  symbolNames = type == QgsStyle::SymbolEntity ? mStyle->symbolNames() : mStyle->colorRampNames();
1013  }
1014  }
1015 
1016  symbolNames.sort();
1017  if ( currentItemType() < 3 )
1018  {
1019  populateSymbols( symbolNames, mGrouppingMode );
1020  }
1021  else if ( currentItemType() == 3 )
1022  {
1023  populateColorRamps( symbolNames, mGrouppingMode );
1024  }
1025 
1026  if ( mGrouppingMode )
1027  {
1028  setSymbolsChecked( groupSymbols );
1029  }
1030 
1031  actnEditSmartGroup->setVisible( false );
1032  actnAddTag->setVisible( false );
1033  actnAddSmartgroup->setVisible( false );
1034  actnRemoveGroup->setVisible( false );
1035  actnTagSymbols->setVisible( false );
1036  actnFinishTagging->setVisible( false );
1037 
1038  if ( index.parent().isValid() )
1039  {
1040  if ( index.parent().data( Qt::UserRole + 1 ).toString() == QLatin1String( "smartgroups" ) )
1041  {
1042  actnEditSmartGroup->setVisible( !mGrouppingMode );
1043  }
1044  else if ( index.parent().data( Qt::UserRole + 1 ).toString() == QLatin1String( "tags" ) )
1045  {
1046  actnAddTag->setVisible( !mGrouppingMode );
1047  actnTagSymbols->setVisible( !mGrouppingMode );
1048  actnFinishTagging->setVisible( mGrouppingMode );
1049  }
1050  actnRemoveGroup->setVisible( true );
1051  }
1052  else if ( index.data( Qt::UserRole + 1 ) == "smartgroups" )
1053  {
1054  actnAddSmartgroup->setVisible( !mGrouppingMode );
1055  }
1056  else if ( index.data( Qt::UserRole + 1 ) == "tags" )
1057  {
1058  actnAddTag->setVisible( !mGrouppingMode );
1059  }
1060 }
1061 
1063 {
1064  QStandardItemModel *model = qobject_cast<QStandardItemModel *>( groupTree->model() );
1065  QModelIndex index;
1066  for ( int i = 0; i < groupTree->model()->rowCount(); i++ )
1067  {
1068  index = groupTree->model()->index( i, 0 );
1069  QString data = index.data( Qt::UserRole + 1 ).toString();
1070  if ( data == QLatin1String( "tags" ) )
1071  {
1072  break;
1073  }
1074  }
1075 
1076  QString itemName;
1077  int id;
1078  bool ok;
1079  itemName = QInputDialog::getText( this, tr( "Add Tag" ),
1080  tr( "Please enter name for the new tag:" ), QLineEdit::Normal, tr( "New tag" ), &ok ).trimmed();
1081  if ( !ok || itemName.isEmpty() )
1082  return 0;
1083 
1084  int check = mStyle->tagId( itemName );
1085  if ( check > 0 )
1086  {
1087  QMessageBox::critical( this, tr( "Add Tag" ),
1088  tr( "Tag name already exists in your symbol database." ) );
1089  return 0;
1090  }
1091 
1092  // block the auto-repopulation of groups when the style emits groupsModified
1093  // instead, we manually update the model items for better state retention
1094  mBlockGroupUpdates++;
1095  id = mStyle->addTag( itemName );
1096  mBlockGroupUpdates--;
1097 
1098  if ( !id )
1099  {
1100  QMessageBox::critical( this, tr( "Add Tag" ),
1101  tr( "New tag could not be created.\n"
1102  "There was a problem with your symbol database." ) );
1103  return 0;
1104  }
1105 
1106  QStandardItem *parentItem = model->itemFromIndex( index );
1107  QStandardItem *childItem = new QStandardItem( itemName );
1108  childItem->setData( id );
1109  parentItem->appendRow( childItem );
1110 
1111  return id;
1112 }
1113 
1115 {
1116  QStandardItemModel *model = qobject_cast<QStandardItemModel *>( groupTree->model() );
1117  QModelIndex index;
1118  for ( int i = 0; i < groupTree->model()->rowCount(); i++ )
1119  {
1120  index = groupTree->model()->index( i, 0 );
1121  QString data = index.data( Qt::UserRole + 1 ).toString();
1122  if ( data == QLatin1String( "smartgroups" ) )
1123  {
1124  break;
1125  }
1126  }
1127 
1128  QString itemName;
1129  int id;
1130  QgsSmartGroupEditorDialog dlg( mStyle, this );
1131  if ( dlg.exec() == QDialog::Rejected )
1132  return 0;
1133 
1134  // block the auto-repopulation of groups when the style emits groupsModified
1135  // instead, we manually update the model items for better state retention
1136  mBlockGroupUpdates++;
1137  id = mStyle->addSmartgroup( dlg.smartgroupName(), dlg.conditionOperator(), dlg.conditionMap() );
1138  mBlockGroupUpdates--;
1139 
1140  if ( !id )
1141  return 0;
1142  itemName = dlg.smartgroupName();
1143 
1144  QStandardItem *parentItem = model->itemFromIndex( index );
1145  QStandardItem *childItem = new QStandardItem( itemName );
1146  childItem->setData( id );
1147  parentItem->appendRow( childItem );
1148 
1149  return id;
1150 }
1151 
1153 {
1154  QStandardItemModel *model = qobject_cast<QStandardItemModel *>( groupTree->model() );
1155  QModelIndex index = groupTree->currentIndex();
1156 
1157  // do not allow removal of system-defined groupings
1158  QString data = index.data( Qt::UserRole + 1 ).toString();
1159  if ( data == QLatin1String( "all" ) || data == QLatin1String( "favorite" ) || data == QLatin1String( "tags" ) || index.data() == "smartgroups" )
1160  {
1161  int err = QMessageBox::critical( this, tr( "Remove Group" ),
1162  tr( "Invalid selection. Cannot delete system defined categories.\n"
1163  "Kindly select a group or smart group you might want to delete." ) );
1164  if ( err )
1165  return;
1166  }
1167 
1168  QStandardItem *parentItem = model->itemFromIndex( index.parent() );
1169 
1170  // block the auto-repopulation of groups when the style emits groupsModified
1171  // instead, we manually update the model items for better state retention
1172  mBlockGroupUpdates++;
1173 
1174  if ( parentItem->data( Qt::UserRole + 1 ).toString() == QLatin1String( "smartgroups" ) )
1175  {
1176  mStyle->remove( QgsStyle::SmartgroupEntity, index.data( Qt::UserRole + 1 ).toInt() );
1177  }
1178  else
1179  {
1180  mStyle->remove( QgsStyle::TagEntity, index.data( Qt::UserRole + 1 ).toInt() );
1181  }
1182 
1183  mBlockGroupUpdates--;
1184  parentItem->removeRow( index.row() );
1185 }
1186 
1187 void QgsStyleManagerDialog::groupRenamed( QStandardItem *item )
1188 {
1189  QgsDebugMsg( QStringLiteral( "Symbol group edited: data=%1 text=%2" ).arg( item->data( Qt::UserRole + 1 ).toString(), item->text() ) );
1190  int id = item->data( Qt::UserRole + 1 ).toInt();
1191  QString name = item->text();
1192  mBlockGroupUpdates++;
1193  if ( item->parent()->data( Qt::UserRole + 1 ) == "smartgroups" )
1194  {
1195  mStyle->rename( QgsStyle::SmartgroupEntity, id, name );
1196  }
1197  else
1198  {
1199  mStyle->rename( QgsStyle::TagEntity, id, name );
1200  }
1201  mBlockGroupUpdates--;
1202 }
1203 
1205 {
1206 
1207  QStandardItemModel *treeModel = qobject_cast<QStandardItemModel *>( groupTree->model() );
1208  QStandardItemModel *model = qobject_cast<QStandardItemModel *>( listItems->model() );
1209 
1210  if ( mGrouppingMode )
1211  {
1212  mGrouppingMode = false;
1213  actnTagSymbols->setVisible( true );
1214  actnFinishTagging->setVisible( false );
1215  // disconnect slot which handles regrouping
1216  disconnect( model, &QStandardItemModel::itemChanged,
1218 
1219  // disabel all items except groups in groupTree
1221  groupChanged( groupTree->currentIndex() );
1222 
1223  // Finally: Reconnect all Symbol editing functionalities
1224  connect( treeModel, &QStandardItemModel::itemChanged,
1226  connect( model, &QStandardItemModel::itemChanged,
1228  // Reset the selection mode
1229  listItems->setSelectionMode( QAbstractItemView::ExtendedSelection );
1230  }
1231  else
1232  {
1233  bool validGroup = false;
1234  // determine whether it is a valid group
1235  QModelIndex present = groupTree->currentIndex();
1236  while ( present.parent().isValid() )
1237  {
1238  if ( present.parent().data() == "Tags" )
1239  {
1240  validGroup = true;
1241  break;
1242  }
1243  present = present.parent();
1244  }
1245  if ( !validGroup )
1246  return;
1247 
1248  mGrouppingMode = true;
1249  // Change visibility of actions
1250  actnTagSymbols->setVisible( false );
1251  actnFinishTagging->setVisible( true );
1252  // Remove all Symbol editing functionalities
1253  disconnect( treeModel, &QStandardItemModel::itemChanged,
1255  disconnect( model, &QStandardItemModel::itemChanged,
1257 
1258  // disabel all items except groups in groupTree
1259  enableItemsForGroupingMode( false );
1260  groupChanged( groupTree->currentIndex() );
1261  btnManageGroups->setEnabled( true );
1262 
1263 
1264  // Connect to slot which handles regrouping
1265  connect( model, &QStandardItemModel::itemChanged,
1267 
1268  // No selection should be possible
1269  listItems->setSelectionMode( QAbstractItemView::NoSelection );
1270  }
1271 }
1272 
1273 void QgsStyleManagerDialog::regrouped( QStandardItem *item )
1274 {
1276  if ( currentItemType() > 3 )
1277  {
1278  QgsDebugMsg( QStringLiteral( "Unknown style entity" ) );
1279  return;
1280  }
1281 
1282  QStandardItemModel *treeModel = qobject_cast<QStandardItemModel *>( groupTree->model() );
1283  QString tag = treeModel->itemFromIndex( groupTree->currentIndex() )->text();
1284 
1285  QString symbolName = item->text();
1286  bool regrouped;
1287  if ( item->checkState() == Qt::Checked )
1288  regrouped = mStyle->tagSymbol( type, symbolName, QStringList( tag ) );
1289  else
1290  regrouped = mStyle->detagSymbol( type, symbolName, QStringList( tag ) );
1291  if ( !regrouped )
1292  {
1293  int er = QMessageBox::critical( this, tr( "Group Items" ),
1294  tr( "There was a problem with the symbols database while regrouping." ) );
1295  // call the slot again to get back to normal
1296  if ( er )
1297  tagSymbolsAction();
1298  }
1299 }
1300 
1301 void QgsStyleManagerDialog::setSymbolsChecked( const QStringList &symbols )
1302 {
1303  QStandardItemModel *model = qobject_cast<QStandardItemModel *>( listItems->model() );
1304  for ( const QString &symbol : symbols )
1305  {
1306  const QList<QStandardItem *> items = model->findItems( symbol );
1307  for ( QStandardItem *item : items )
1308  item->setCheckState( Qt::Checked );
1309  }
1310 }
1311 
1312 void QgsStyleManagerDialog::filterSymbols( const QString &qword )
1313 {
1314  QStringList items;
1316  items.sort();
1317  if ( currentItemType() == 3 )
1318  {
1319  populateColorRamps( items );
1320  }
1321  else
1322  {
1323  populateSymbols( items );
1324  }
1325 }
1326 
1327 void QgsStyleManagerDialog::symbolSelected( const QModelIndex &index )
1328 {
1329  actnEditItem->setEnabled( index.isValid() && !mGrouppingMode );
1330 }
1331 
1332 void QgsStyleManagerDialog::selectedSymbolsChanged( const QItemSelection &selected, const QItemSelection &deselected )
1333 {
1334  Q_UNUSED( selected );
1335  Q_UNUSED( deselected );
1336  bool nothingSelected = listItems->selectionModel()->selectedIndexes().empty();
1337  actnRemoveItem->setDisabled( nothingSelected );
1338  actnAddFavorite->setDisabled( nothingSelected );
1339  actnRemoveFavorite->setDisabled( nothingSelected );
1340  mGroupListMenu->setDisabled( nothingSelected );
1341  actnDetag->setDisabled( nothingSelected );
1342  actnExportAsPNG->setDisabled( nothingSelected );
1343  actnExportAsSVG->setDisabled( nothingSelected );
1344  actnEditItem->setDisabled( nothingSelected );
1345 }
1346 
1348 {
1349  groupTree->setEnabled( enable );
1350  btnAddTag->setEnabled( enable );
1351  btnAddSmartgroup->setEnabled( enable );
1352  actnAddTag->setEnabled( enable );
1353  actnAddSmartgroup->setEnabled( enable );
1354  actnRemoveGroup->setEnabled( enable );
1355  btnManageGroups->setEnabled( enable || mGrouppingMode ); // always enabled in grouping mode, as it is the only way to leave grouping mode
1356  searchBox->setEnabled( enable );
1357 }
1358 
1360 {
1361  actnRemoveGroup->setEnabled( enable );
1362  btnManageGroups->setEnabled( enable || mGrouppingMode ); // always enabled in grouping mode, as it is the only way to leave grouping mode
1363 }
1364 
1366 {
1367  QStandardItemModel *treeModel = qobject_cast<QStandardItemModel *>( groupTree->model() );
1368  for ( int i = 0; i < treeModel->rowCount(); i++ )
1369  {
1370  treeModel->item( i )->setEnabled( enable );
1371 
1372  if ( treeModel->item( i )->data() == "smartgroups" )
1373  {
1374  for ( int j = 0; j < treeModel->item( i )->rowCount(); j++ )
1375  {
1376  treeModel->item( i )->child( j )->setEnabled( enable );
1377  }
1378  }
1379  }
1380 
1381  // The buttons
1382  // NOTE: if you ever change the layout name in the .ui file edit here too
1383  for ( int i = 0; i < symbolBtnsLayout->count(); i++ )
1384  {
1385  QWidget *w = qobject_cast<QWidget *>( symbolBtnsLayout->itemAt( i )->widget() );
1386  if ( w )
1387  w->setEnabled( enable );
1388  }
1389 
1390  // The actions
1391  actnRemoveItem->setEnabled( enable );
1392  actnEditItem->setEnabled( enable );
1393 }
1394 
1396 {
1397  QPoint globalPos = groupTree->viewport()->mapToGlobal( point );
1398 
1399  QModelIndex index = groupTree->indexAt( point );
1400  QgsDebugMsg( QStringLiteral( "Now you clicked: %1" ).arg( index.data().toString() ) );
1401 
1402  if ( index.isValid() && !mGrouppingMode )
1403  mGroupTreeContextMenu->popup( globalPos );
1404 }
1405 
1407 {
1408  QPoint globalPos = listItems->viewport()->mapToGlobal( point );
1409 
1410  // Clear all actions and create new actions for every group
1411  mGroupListMenu->clear();
1412 
1413  QAction *a = nullptr;
1414  QStringList tags = mStyle->tags();
1415  tags.sort();
1416  for ( const QString &tag : qgis::as_const( tags ) )
1417  {
1418  a = new QAction( tag, mGroupListMenu );
1419  a->setData( tag );
1420  connect( a, &QAction::triggered, this, [ = ]( bool ) { tagSelectedSymbols(); }
1421  );
1422  mGroupListMenu->addAction( a );
1423  }
1424 
1425  if ( tags.count() > 0 )
1426  {
1427  mGroupListMenu->addSeparator();
1428  }
1429  a = new QAction( tr( "Create New Tag…" ), mGroupListMenu );
1430  connect( a, &QAction::triggered, this, [ = ]( bool ) { tagSelectedSymbols( true ); }
1431  );
1432  mGroupListMenu->addAction( a );
1433 
1434  mGroupMenu->popup( globalPos );
1435 }
1436 
1438 {
1440  if ( currentItemType() > 3 )
1441  {
1442  QgsDebugMsg( QStringLiteral( "unknown entity type" ) );
1443  return;
1444  }
1445 
1446  const QModelIndexList indexes = listItems->selectionModel()->selectedIndexes();
1447  for ( const QModelIndex &index : indexes )
1448  {
1449  mStyle->addFavorite( type, index.data().toString() );
1450  }
1451  populateList();
1452 }
1453 
1455 {
1457  if ( currentItemType() > 3 )
1458  {
1459  QgsDebugMsg( QStringLiteral( "unknown entity type" ) );
1460  return;
1461  }
1462 
1463  const QModelIndexList indexes = listItems->selectionModel()->selectedIndexes();
1464  for ( const QModelIndex &index : indexes )
1465  {
1466  mStyle->removeFavorite( type, index.data().toString() );
1467  }
1468  populateList();
1469 }
1470 
1472 {
1473  QAction *selectedItem = qobject_cast<QAction *>( sender() );
1474  if ( selectedItem )
1475  {
1477  if ( currentItemType() > 3 )
1478  {
1479  QgsDebugMsg( QStringLiteral( "unknown entity type" ) );
1480  return;
1481  }
1482 
1483  QString tag;
1484  if ( newTag )
1485  {
1486  int id = addTag();
1487  if ( id == 0 )
1488  {
1489  return;
1490  }
1491 
1492  tag = mStyle->tag( id );
1493  }
1494  else
1495  {
1496  tag = selectedItem->data().toString();
1497  }
1498 
1499  const QModelIndexList indexes = listItems->selectionModel()->selectedIndexes();
1500  for ( const QModelIndex &index : indexes )
1501  {
1502  mStyle->tagSymbol( type, index.data().toString(), QStringList( tag ) );
1503  }
1504  populateList();
1505 
1506  QgsDebugMsg( QStringLiteral( "Selected Action: %1" ).arg( selectedItem->text() ) );
1507  }
1508 }
1509 
1511 {
1512  QAction *selectedItem = qobject_cast<QAction *>( sender() );
1513 
1514  if ( selectedItem )
1515  {
1517  if ( currentItemType() > 3 )
1518  {
1519  QgsDebugMsg( QStringLiteral( "unknown entity type" ) );
1520  return;
1521  }
1522  const QModelIndexList indexes = listItems->selectionModel()->selectedIndexes();
1523  for ( const QModelIndex &index : indexes )
1524  {
1525  mStyle->detagSymbol( type, index.data().toString() );
1526  }
1527  populateList();
1528 
1529  QgsDebugMsg( QStringLiteral( "Selected Action: %1" ).arg( selectedItem->text() ) );
1530  }
1531 }
1532 
1534 {
1535  QStandardItemModel *treeModel = qobject_cast<QStandardItemModel *>( groupTree->model() );
1536 
1537  // determine whether it is a valid group
1538  QModelIndex present = groupTree->currentIndex();
1539  if ( present.parent().data( Qt::UserRole + 1 ) != "smartgroups" )
1540  {
1541  QMessageBox::critical( this, tr( "Edit Smart Group" ),
1542  tr( "You have not selected a Smart Group. Kindly select a Smart Group to edit." ) );
1543  return;
1544  }
1545  QStandardItem *item = treeModel->itemFromIndex( present );
1546 
1547  QgsSmartGroupEditorDialog dlg( mStyle, this );
1548  QgsSmartConditionMap map = mStyle->smartgroup( present.data( Qt::UserRole + 1 ).toInt() );
1549  dlg.setSmartgroupName( item->text() );
1550  dlg.setOperator( mStyle->smartgroupOperator( item->data().toInt() ) );
1551  dlg.setConditionMap( map );
1552 
1553  if ( dlg.exec() == QDialog::Rejected )
1554  return;
1555 
1556  mBlockGroupUpdates++;
1557  mStyle->remove( QgsStyle::SmartgroupEntity, item->data().toInt() );
1558  int id = mStyle->addSmartgroup( dlg.smartgroupName(), dlg.conditionOperator(), dlg.conditionMap() );
1559  mBlockGroupUpdates--;
1560  if ( !id )
1561  {
1562  QMessageBox::critical( this, tr( "Edit Smart Group" ),
1563  tr( "There was some error while editing the smart group." ) );
1564  return;
1565  }
1566  item->setText( dlg.smartgroupName() );
1567  item->setData( id );
1568 
1569  groupChanged( present );
1570 }
1571 
1573 {
1574  reject();
1575 }
1576 
1578 {
1579  QgsHelp::openHelp( QStringLiteral( "working_with_vector/style_library.html#the-style-manager" ) );
1580 }
void groupChanged(const QModelIndex &)
void tagSymbolsAction()
carry out symbol tagging using check boxes
bool isFavorite() const
returns whether the favorite element is checked
void populateTypes()
populate combo box with known style items (symbols, color ramps)
Temporarily sets a cursor override for the QApplication for the lifetime of the object.
Definition: qgsguiutils.h:186
bool addColorRamp(const QString &name, QgsColorRamp *colorRamp, bool update=false)
Adds a color ramp to the style.
Definition: qgsstyle.cpp:197
void symbolSaved(const QString &name, QgsSymbol *symbol)
Emitted every time a new symbol has been added to the database.
void setBold(QStandardItem *)
sets the text of the item with bold font
bool save(QString filename=QString())
Saves style into a file (will use current filename if empty string is passed)
Definition: qgsstyle.cpp:429
A dialog allowing users to customize and populate a QgsStyle.
bool addSymbol()
add a new symbol to style
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:61
a dialog for setting properties of a newly saved style.
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:152
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
QgsColorBrewerColorRamp * clone() const override
Creates a clone of the color ramp.
void populateList()
adds symbols of some type to list
void onFinished()
called when the dialog is going to be closed
QgsColorRamp * colorRamp(const QString &name) const
Returns a new copy of the specified color ramp.
Definition: qgsstyle.cpp:272
QgsSmartConditionMap conditionMap()
returns the condition map
int addSmartgroup()
add a smartgroup
void setSymbolsChecked(const QStringList &)
to set symbols checked when in editing mode
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
void selectedSymbolsChanged(const QItemSelection &selected, const QItemSelection &deselected)
Perform tasks when the selected symbols change.
QStringList tagsOfSymbol(StyleEntity type, const QString &symbol)
Returns the tags associated with the symbol.
Definition: qgsstyle.cpp:1041
Abstract base class for color ramps.
Definition: qgscolorramp.h:31
QMap< int, QString > QgsSymbolGroupMap
Definition: qgsstyle.h:38
static QString iconPath(const QString &iconFile)
Returns path to the desired icon file.
bool saveColorRamp(const QString &name, QgsColorRamp *ramp, bool favorite, const QStringList &tags)
Adds the colorramp to the DB.
Definition: qgsstyle.cpp:221
int colors() const
Returns the number of colors in the ramp.
Definition: qgscolorramp.h:575
void populateGroups()
populate the groups
bool tagSymbol(StyleEntity type, const QString &symbol, const QStringList &tags)
Tags the symbol with the tags in the list.
Definition: qgsstyle.cpp:892
QgsGradientColorRamp * cloneGradientRamp() const
void filterSymbols(const QString &)
filter the symbols based on input search term
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
const QgsSymbol * symbolRef(const QString &name) const
Returns a const pointer to a symbol (doesn&#39;t create new instance)
Definition: qgsstyle.cpp:181
bool addColorRamp()
add a new color ramp to style
QString schemeName() const
Definition: qgscolorramp.h:666
Line symbol.
Definition: qgssymbol.h:86
A dialog which allows users to modify the properties of a QgsColorBrewerColorRamp.
QStringList symbolsWithTag(StyleEntity type, int tagid) const
Returns the symbol names with which have the given tag.
Definition: qgsstyle.cpp:578
void populateSymbols(const QStringList &symbolNames, bool checkable=false)
populate list view with symbols of the current type with the given names
QString smartgroupName()
returns the value from mNameLineEdit
bool removeColorRamp(const QString &name)
Removes color ramp from style (and delete it)
Definition: qgsstyle.cpp:252
bool rename(StyleEntity type, int id, const QString &newName)
Renames the given entity with the specified id.
Definition: qgsstyle.cpp:667
A dialog which allows users to modify the properties of a QgsGradientColorRamp.
A dialog which allows users to modify the properties of a QgsPresetSchemeColorRamp.
A marker symbol type, for rendering Point and MultiPoint geometries.
Definition: qgssymbol.h:732
A line symbol type, for rendering LineString and MultiLineString geometries.
Definition: qgssymbol.h:920
A dialog which allows users to modify the properties of a QgsLimitedRandomColorRamp.
StyleEntity
Enum for Entities involved in a style.
Definition: qgsstyle.h:95
void saveGeometry(QWidget *widget, const QString &keyName)
Save the wigget geometry into settings.
void tagSelectedSymbols(bool newTag=false)
Tag selected symbols using menu item selection.
bool restoreGeometry(QWidget *widget, const QString &keyName)
Restore the wigget geometry from settings.
QgsGradientColorRamp * clone() const override
Creates a clone of the color ramp.
bool renameSymbol(const QString &oldName, const QString &newName)
Renames a symbol from oldName to newName.
Definition: qgsstyle.cpp:475
QString variantName() const
Definition: qgscolorramp.h:667
Constrained random color ramp, which returns random colors based on preset parameters.
Definition: qgscolorramp.h:283
static QIcon symbolPreviewIcon(QgsSymbol *symbol, QSize size, int padding=0)
Returns an icon preview for a color ramp.
QStringList symbolNames()
Returns a list of names of symbols.
Definition: qgsstyle.cpp:191
bool removeFavorite(StyleEntity type, const QString &name)
Removes the specified symbol from favorites.
Definition: qgsstyle.cpp:803
QgsPresetSchemeColorRamp * clone() const override
Creates a clone of the color ramp.
static QIcon colorRampPreviewIcon(QgsColorRamp *ramp, QSize size, int padding=0)
Returns an icon preview for a color ramp.
bool remove(StyleEntity type, int id)
Removes the specified entity from the db.
Definition: qgsstyle.cpp:717
QStringList findSymbols(StyleEntity type, const QString &qword)
Returns the names of the symbols which have a matching &#39;substring&#39; in its definition.
Definition: qgsstyle.cpp:828
void groupsModified()
Is emitted every time a tag or smartgroup has been added, removed, or renamed.
void enableItemsForGroupingMode(bool)
Enables or disables the groupTree items for grouping mode.
void editSmartgroupAction()
edit the selected smart group
void exportSelectedItemsImages(const QString &dir, const QString &format, QSize size)
int addTag(const QString &tagName)
Adds a new tag and returns the tag&#39;s id.
Definition: qgsstyle.cpp:627
QStringList symbolsOfFavorite(StyleEntity type) const
Returns the symbol names which are flagged as favorite.
Definition: qgsstyle.cpp:542
void symbolSelected(const QModelIndex &)
Perform symbol specific tasks when selected.
QString smartgroupOperator(int id)
Returns the operator for the smartgroup clumsy implementation TODO create a class for smartgroups...
Definition: qgsstyle.cpp:1466
void grouptreeContextMenu(QPoint)
Context menu for the groupTree.
static QString addColorRampStatic(QWidget *parent, QgsStyle *style, QString RampType=QString())
Opens the add color ramp dialog, returning the new color ramp&#39;s name if the ramp has been added...
void removeFavoriteSelectedSymbols()
Remove selected symbols from favorites.
A scheme based color ramp consisting of a list of predefined colors.
Definition: qgscolorramp.h:471
void itemChanged(QStandardItem *item)
QgsSmartConditionMap smartgroup(int id)
Returns the QgsSmartConditionMap for the given id.
Definition: qgsstyle.cpp:1426
QStringList colorRampNames()
Returns a list of names of color ramps.
Definition: qgsstyle.cpp:288
QgsLimitedRandomColorRamp * clone() const override
Creates a clone of the color ramp.
QStringList symbolsOfSmartgroup(StyleEntity type, int id)
Returns the symbols for the smartgroup.
Definition: qgsstyle.cpp:1327
void enableSymbolInputs(bool)
Enables or disbables the symbol specific inputs.
int tagId(const QString &tag)
Returns the DB id for the given tag name.
Definition: qgsstyle.cpp:1216
void addFavoriteSelectedSymbols()
Add selected symbols to favorites.
Marker symbol.
Definition: qgssymbol.h:85
bool addSymbol(const QString &name, QgsSymbol *symbol, bool update=false)
Adds a symbol to style and takes symbol&#39;s ownership.
Definition: qgsstyle.cpp:88
Fill symbol.
Definition: qgssymbol.h:87
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:1226
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
void setOperator(const QString &)
sets the operator AND/OR
QgsCptCityColorRamp * clone() const override
Creates a clone of the color ramp.
void activate()
Raises, unminimizes and activates this window.
QgsPresetSchemeColorRamp ramp
QString name() const
returns the text value of the name element
void removeGroup()
remove a tag or smartgroup
SymbolType type() const
Returns the symbol&#39;s type.
Definition: qgssymbol.h:120
bool saveAsGradientRamp() const
Returns true if the ramp should be converted to a QgsGradientColorRamp.
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition: qgshelp.cpp:35
void listitemsContextMenu(QPoint)
Context menu for the listItems ( symbols list )
Color ramp utilising "Color Brewer" preset color schemes.
Definition: qgscolorramp.h:535
bool renameColorRamp(const QString &oldName, const QString &newName)
Changes ramp&#39;s name.
Definition: qgsstyle.cpp:511
int colorRampCount()
Returns count of color ramps.
Definition: qgsstyle.cpp:283
void setSmartgroupName(const QString &)
sets the smart group Name
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
Definition: qgssymbol.h:1003
void showHelp()
Open the associated help.
QgsSymbol * symbol(const QString &name)
Returns a NEW copy of symbol.
Definition: qgsstyle.cpp:175
bool saveSymbol(const QString &name, QgsSymbol *symbol, bool favorite, const QStringList &tags)
Adds the symbol to the DB with the tags.
Definition: qgsstyle.cpp:112
void onClose()
Close the dialog.
bool removeSymbol(const QString &name)
Removes symbol from style (and delete it)
Definition: qgsstyle.cpp:143
A dialog which allows users to modify the properties of a QgsCptCityColorRamp.
void groupRenamed(QStandardItem *)
bool addFavorite(StyleEntity type, const QString &name)
Adds the specified symbol to favorites.
Definition: qgsstyle.cpp:778
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
Definition: qgscolorramp.h:139
QgsStyleManagerDialog(QgsStyle *style, QWidget *parent SIP_TRANSFERTHIS=nullptr, Qt::WindowFlags flags=Qt::WindowFlags())
Constructor for QgsStyleManagerDialog, with the specified parent widget and window flags...
void regrouped(QStandardItem *)
symbol changed from one group
QStringList tags() const
Returns a list of all tags in the style database.
Definition: qgsstyle.cpp:647
void populateColorRamps(const QStringList &colorRamps, bool checkable=false)
populate list view with color ramps
void setConditionMap(const QgsSmartConditionMap &)
sets up the GUI for the given conditionmap
QString tags() const
returns the text value of the tags element
QString schemeName() const
Returns the name of the color brewer color scheme.
Definition: qgscolorramp.h:569
bool detagSymbol(StyleEntity type, const QString &symbol, const QStringList &tags)
Detags the symbol with the given list.
Definition: qgsstyle.cpp:945
QMultiMap< QString, QString > QgsSmartConditionMap
A multimap to hold the smart group conditions as constraint and parameter pairs.
Definition: qgsstyle.h:63
QgsSymbolGroupMap smartgroupsListMap()
Returns the smart groups map with id as key and name as value.
Definition: qgsstyle.cpp:1278
void detagSelectedSymbols()
Remove all tags from selected symbols.
QString conditionOperator()
returns the AND/OR condition
QString tag(int id) const
Returns the tag name for the given id.
Definition: qgsstyle.cpp:1141
void enableGroupInputs(bool)
Enables or disables the groupTree specific inputs.