QGIS API Documentation  3.25.0-Master (dec16ba68b)
qgscategorizedsymbolrendererwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscategorizedsymbolrendererwidget.cpp
3  ---------------------
4  begin : November 2009
5  copyright : (C) 2009 by Martin Dobias
6  email : wonder dot sk at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
17 #include "qgspanelwidget.h"
18 
20 
23 #include "qgssymbol.h"
24 #include "qgssymbollayerutils.h"
25 #include "qgscolorrampimpl.h"
26 #include "qgscolorrampbutton.h"
27 #include "qgsstyle.h"
28 #include "qgslogger.h"
30 #include "qgstemporalcontroller.h"
31 
34 
35 #include "qgsvectorlayer.h"
36 #include "qgsfeatureiterator.h"
37 
38 #include "qgsproject.h"
40 #include "qgsexpression.h"
41 #include "qgsmapcanvas.h"
42 #include "qgssettings.h"
43 #include "qgsguiutils.h"
44 #include "qgsmarkersymbol.h"
45 
46 #include <QKeyEvent>
47 #include <QMenu>
48 #include <QMessageBox>
49 #include <QStandardItemModel>
50 #include <QStandardItem>
51 #include <QPen>
52 #include <QPainter>
53 #include <QFileDialog>
54 #include <QClipboard>
55 
57 
58 QgsCategorizedSymbolRendererModel::QgsCategorizedSymbolRendererModel( QObject *parent ) : QAbstractItemModel( parent )
59  , mMimeFormat( QStringLiteral( "application/x-qgscategorizedsymbolrendererv2model" ) )
60 {
61 }
62 
63 void QgsCategorizedSymbolRendererModel::setRenderer( QgsCategorizedSymbolRenderer *renderer )
64 {
65  if ( mRenderer )
66  {
67  beginRemoveRows( QModelIndex(), 0, std::max< int >( mRenderer->categories().size() - 1, 0 ) );
68  mRenderer = nullptr;
69  endRemoveRows();
70  }
71  if ( renderer )
72  {
73  mRenderer = renderer;
74  if ( renderer->categories().size() > 0 )
75  {
76  beginInsertRows( QModelIndex(), 0, renderer->categories().size() - 1 );
77  endInsertRows();
78  }
79  }
80 }
81 
82 void QgsCategorizedSymbolRendererModel::addCategory( const QgsRendererCategory &cat )
83 {
84  if ( !mRenderer ) return;
85  const int idx = mRenderer->categories().size();
86  beginInsertRows( QModelIndex(), idx, idx );
87  mRenderer->addCategory( cat );
88  endInsertRows();
89 }
90 
91 QgsRendererCategory QgsCategorizedSymbolRendererModel::category( const QModelIndex &index )
92 {
93  if ( !mRenderer )
94  {
95  return QgsRendererCategory();
96  }
97  const QgsCategoryList &catList = mRenderer->categories();
98  const int row = index.row();
99  if ( row >= catList.size() )
100  {
101  return QgsRendererCategory();
102  }
103  return catList.at( row );
104 }
105 
106 
107 Qt::ItemFlags QgsCategorizedSymbolRendererModel::flags( const QModelIndex &index ) const
108 {
109  if ( !index.isValid() || !mRenderer )
110  {
111  return Qt::ItemIsDropEnabled;
112  }
113 
114  Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable;
115  if ( index.column() == 1 )
116  {
117  const QgsRendererCategory category = mRenderer->categories().value( index.row() );
118  if ( category.value().type() != QVariant::List )
119  {
120  flags |= Qt::ItemIsEditable;
121  }
122  }
123  else if ( index.column() == 2 )
124  {
125  flags |= Qt::ItemIsEditable;
126  }
127  return flags;
128 }
129 
130 Qt::DropActions QgsCategorizedSymbolRendererModel::supportedDropActions() const
131 {
132  return Qt::MoveAction;
133 }
134 
135 QVariant QgsCategorizedSymbolRendererModel::data( const QModelIndex &index, int role ) const
136 {
137  if ( !index.isValid() || !mRenderer )
138  return QVariant();
139 
140  const QgsRendererCategory category = mRenderer->categories().value( index.row() );
141 
142  switch ( role )
143  {
144  case Qt::CheckStateRole:
145  {
146  if ( index.column() == 0 )
147  {
148  return category.renderState() ? Qt::Checked : Qt::Unchecked;
149  }
150  break;
151  }
152 
153  case Qt::DisplayRole:
154  case Qt::ToolTipRole:
155  {
156  switch ( index.column() )
157  {
158  case 1:
159  {
160  if ( category.value().type() == QVariant::List )
161  {
162  QStringList res;
163  const QVariantList list = category.value().toList();
164  res.reserve( list.size() );
165  for ( const QVariant &v : list )
167 
168  if ( role == Qt::DisplayRole )
169  return res.join( ';' );
170  else // tooltip
171  return res.join( '\n' );
172  }
173  else if ( !category.value().isValid() || category.value().isNull() || category.value().toString().isEmpty() )
174  {
175  return tr( "all other values" );
176  }
177  else
178  {
180  }
181  }
182  case 2:
183  return category.label();
184  }
185  break;
186  }
187 
188  case Qt::FontRole:
189  {
190  if ( index.column() == 1 && category.value().type() != QVariant::List && ( !category.value().isValid() || category.value().isNull() || category.value().toString().isEmpty() ) )
191  {
192  QFont italicFont;
193  italicFont.setItalic( true );
194  return italicFont;
195  }
196  return QVariant();
197  }
198 
199  case Qt::DecorationRole:
200  {
201  if ( index.column() == 0 && category.symbol() )
202  {
203  const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
204  return QgsSymbolLayerUtils::symbolPreviewIcon( category.symbol(), QSize( iconSize, iconSize ) );
205  }
206  break;
207  }
208 
209  case Qt::ForegroundRole:
210  {
211  QBrush brush( qApp->palette().color( QPalette::Text ), Qt::SolidPattern );
212  if ( index.column() == 1 && ( category.value().type() == QVariant::List
213  || !category.value().isValid() || category.value().isNull() || category.value().toString().isEmpty() ) )
214  {
215  QColor fadedTextColor = brush.color();
216  fadedTextColor.setAlpha( 128 );
217  brush.setColor( fadedTextColor );
218  }
219  return brush;
220  }
221 
222  case Qt::TextAlignmentRole:
223  {
224  return ( index.column() == 0 ) ? static_cast<Qt::Alignment::Int>( Qt::AlignHCenter ) : static_cast<Qt::Alignment::Int>( Qt::AlignLeft );
225  }
226 
227  case Qt::EditRole:
228  {
229  switch ( index.column() )
230  {
231  case 1:
232  {
233  if ( category.value().type() == QVariant::List )
234  {
235  QStringList res;
236  const QVariantList list = category.value().toList();
237  res.reserve( list.size() );
238  for ( const QVariant &v : list )
239  res << v.toString();
240 
241  return res.join( ';' );
242  }
243  else
244  {
245  return category.value();
246  }
247  }
248 
249  case 2:
250  return category.label();
251  }
252  break;
253  }
254  case QgsCategorizedSymbolRendererWidget::CustomRoles::ValueRole:
255  {
256  if ( index.column() == 1 )
257  return category.value();
258  break;
259  }
260  }
261 
262  return QVariant();
263 }
264 
265 bool QgsCategorizedSymbolRendererModel::setData( const QModelIndex &index, const QVariant &value, int role )
266 {
267  if ( !index.isValid() )
268  return false;
269 
270  if ( index.column() == 0 && role == Qt::CheckStateRole )
271  {
272  mRenderer->updateCategoryRenderState( index.row(), value == Qt::Checked );
273  emit dataChanged( index, index );
274  return true;
275  }
276 
277  if ( role != Qt::EditRole )
278  return false;
279 
280  switch ( index.column() )
281  {
282  case 1: // value
283  {
284  // try to preserve variant type for this value, unless it was an empty string (other values)
285  QVariant val = value;
286  const QVariant previousValue = mRenderer->categories().value( index.row() ).value();
287  if ( previousValue.type() != QVariant::String && ! previousValue.toString().isEmpty() )
288  {
289  switch ( previousValue.type() )
290  {
291  case QVariant::Int:
292  val = value.toInt();
293  break;
294  case QVariant::Double:
295  val = value.toDouble();
296  break;
297  case QVariant::List:
298  {
299  const QStringList parts = value.toString().split( ';' );
300  QVariantList list;
301  list.reserve( parts.count() );
302  for ( const QString &p : parts )
303  list << p;
304 
305  if ( list.count() == 1 )
306  val = list.at( 0 );
307  else
308  val = list;
309  break;
310  }
311  default:
312  val = value.toString();
313  break;
314  }
315  }
316  mRenderer->updateCategoryValue( index.row(), val );
317  break;
318  }
319  case 2: // label
320  mRenderer->updateCategoryLabel( index.row(), value.toString() );
321  break;
322  default:
323  return false;
324  }
325 
326  emit dataChanged( index, index );
327  return true;
328 }
329 
330 QVariant QgsCategorizedSymbolRendererModel::headerData( int section, Qt::Orientation orientation, int role ) const
331 {
332  if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 3 )
333  {
334  QStringList lst;
335  lst << tr( "Symbol" ) << tr( "Value" ) << tr( "Legend" );
336  return lst.value( section );
337  }
338  return QVariant();
339 }
340 
341 int QgsCategorizedSymbolRendererModel::rowCount( const QModelIndex &parent ) const
342 {
343  if ( parent.isValid() || !mRenderer )
344  {
345  return 0;
346  }
347  return mRenderer->categories().size();
348 }
349 
350 int QgsCategorizedSymbolRendererModel::columnCount( const QModelIndex &index ) const
351 {
352  Q_UNUSED( index )
353  return 3;
354 }
355 
356 QModelIndex QgsCategorizedSymbolRendererModel::index( int row, int column, const QModelIndex &parent ) const
357 {
358  if ( hasIndex( row, column, parent ) )
359  {
360  return createIndex( row, column );
361  }
362  return QModelIndex();
363 }
364 
365 QModelIndex QgsCategorizedSymbolRendererModel::parent( const QModelIndex &index ) const
366 {
367  Q_UNUSED( index )
368  return QModelIndex();
369 }
370 
371 QStringList QgsCategorizedSymbolRendererModel::mimeTypes() const
372 {
373  QStringList types;
374  types << mMimeFormat;
375  return types;
376 }
377 
378 QMimeData *QgsCategorizedSymbolRendererModel::mimeData( const QModelIndexList &indexes ) const
379 {
380  QMimeData *mimeData = new QMimeData();
381  QByteArray encodedData;
382 
383  QDataStream stream( &encodedData, QIODevice::WriteOnly );
384 
385  // Create list of rows
386  const auto constIndexes = indexes;
387  for ( const QModelIndex &index : constIndexes )
388  {
389  if ( !index.isValid() || index.column() != 0 )
390  continue;
391 
392  stream << index.row();
393  }
394  mimeData->setData( mMimeFormat, encodedData );
395  return mimeData;
396 }
397 
398 bool QgsCategorizedSymbolRendererModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
399 {
400  Q_UNUSED( row )
401  Q_UNUSED( column )
402  if ( action != Qt::MoveAction ) return true;
403 
404  if ( !data->hasFormat( mMimeFormat ) ) return false;
405 
406  QByteArray encodedData = data->data( mMimeFormat );
407  QDataStream stream( &encodedData, QIODevice::ReadOnly );
408 
409  QVector<int> rows;
410  while ( !stream.atEnd() )
411  {
412  int r;
413  stream >> r;
414  rows.append( r );
415  }
416 
417  int to = parent.row();
418  // to is -1 if dragged outside items, i.e. below any item,
419  // then move to the last position
420  if ( to == -1 ) to = mRenderer->categories().size(); // out of rang ok, will be decreased
421  for ( int i = rows.size() - 1; i >= 0; i-- )
422  {
423  QgsDebugMsg( QStringLiteral( "move %1 to %2" ).arg( rows[i] ).arg( to ) );
424  int t = to;
425  // moveCategory first removes and then inserts
426  if ( rows[i] < t ) t--;
427  mRenderer->moveCategory( rows[i], t );
428  // current moved under another, shift its index up
429  for ( int j = 0; j < i; j++ )
430  {
431  if ( to < rows[j] && rows[i] > rows[j] ) rows[j] += 1;
432  }
433  // removed under 'to' so the target shifted down
434  if ( rows[i] < to ) to--;
435  }
436  emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
437  emit rowsMoved();
438  return false;
439 }
440 
441 void QgsCategorizedSymbolRendererModel::deleteRows( QList<int> rows )
442 {
443  std::sort( rows.begin(), rows.end() ); // list might be unsorted, depending on how the user selected the rows
444  for ( int i = rows.size() - 1; i >= 0; i-- )
445  {
446  beginRemoveRows( QModelIndex(), rows[i], rows[i] );
447  mRenderer->deleteCategory( rows[i] );
448  endRemoveRows();
449  }
450 }
451 
452 void QgsCategorizedSymbolRendererModel::removeAllRows()
453 {
454  beginRemoveRows( QModelIndex(), 0, mRenderer->categories().size() - 1 );
455  mRenderer->deleteAllCategories();
456  endRemoveRows();
457 }
458 
459 void QgsCategorizedSymbolRendererModel::sort( int column, Qt::SortOrder order )
460 {
461  if ( column == 0 )
462  {
463  return;
464  }
465  if ( column == 1 )
466  {
467  mRenderer->sortByValue( order );
468  }
469  else if ( column == 2 )
470  {
471  mRenderer->sortByLabel( order );
472  }
473  emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
474 }
475 
476 void QgsCategorizedSymbolRendererModel::updateSymbology()
477 {
478  emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
479 }
480 
481 // ------------------------------ View style --------------------------------
482 QgsCategorizedSymbolRendererViewStyle::QgsCategorizedSymbolRendererViewStyle( QWidget *parent )
483  : QgsProxyStyle( parent )
484 {}
485 
486 void QgsCategorizedSymbolRendererViewStyle::drawPrimitive( PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget ) const
487 {
488  if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
489  {
490  QStyleOption opt( *option );
491  opt.rect.setLeft( 0 );
492  // draw always as line above, because we move item to that index
493  opt.rect.setHeight( 0 );
494  if ( widget ) opt.rect.setRight( widget->width() );
495  QProxyStyle::drawPrimitive( element, &opt, painter, widget );
496  return;
497  }
498  QProxyStyle::drawPrimitive( element, option, painter, widget );
499 }
500 
501 
502 QgsCategorizedRendererViewItemDelegate::QgsCategorizedRendererViewItemDelegate( QgsFieldExpressionWidget *expressionWidget, QObject *parent )
503  : QStyledItemDelegate( parent )
504  , mFieldExpressionWidget( expressionWidget )
505 {
506 }
507 
508 QWidget *QgsCategorizedRendererViewItemDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const
509 {
510  QVariant::Type userType { index.data( QgsCategorizedSymbolRendererWidget::CustomRoles::ValueRole ).type() };
511 
512  // In case of new values the type is not known
513  if ( userType == QVariant::String && index.data( QgsCategorizedSymbolRendererWidget::CustomRoles::ValueRole ).isNull() )
514  {
515  bool isExpression;
516  bool isValid;
517  const QString fieldName { mFieldExpressionWidget->currentField( &isExpression, &isValid ) };
518  if ( ! fieldName.isEmpty() && mFieldExpressionWidget->layer() && mFieldExpressionWidget->layer()->fields().lookupField( fieldName ) != -1 )
519  {
520  userType = mFieldExpressionWidget->layer()->fields().field( fieldName ).type();
521  }
522  else if ( isExpression && isValid )
523  {
524  // Try to guess the type from the expression return value
525  QgsFeature feat;
526  if ( mFieldExpressionWidget->layer()->getFeatures().nextFeature( feat ) )
527  {
528  QgsExpressionContext expressionContext;
531  expressionContext.appendScope( mFieldExpressionWidget->layer()->createExpressionContextScope() );
532  expressionContext.setFeature( feat );
533  QgsExpression exp { mFieldExpressionWidget->expression() };
534  const QVariant value = exp.evaluate( &expressionContext );
535  if ( !exp.hasEvalError() )
536  {
537  userType = value.type();
538  }
539  }
540  }
541  }
542 
543  QgsDoubleSpinBox *editor = nullptr;
544  switch ( userType )
545  {
546  case QVariant::Type::Double:
547  {
548  editor = new QgsDoubleSpinBox( parent );
549  bool ok;
550  const QVariant value = index.data( QgsCategorizedSymbolRendererWidget::CustomRoles::ValueRole );
551  int decimals {2};
552  if ( value.toDouble( &ok ); ok )
553  {
554  const QString strVal { value.toString() };
555  const int dotPosition( strVal.indexOf( '.' ) );
556  if ( dotPosition >= 0 )
557  {
558  decimals = std::max<int>( 2, strVal.length() - dotPosition - 1 );
559  }
560  }
561  editor->setDecimals( decimals );
562  editor->setClearValue( 0 );
563  editor->setMaximum( std::numeric_limits<double>::max() );
564  editor->setMinimum( std::numeric_limits<double>::lowest() );
565  break;
566  }
567  case QVariant::Type::Int:
568  {
569  editor = new QgsDoubleSpinBox( parent );
570  editor->setDecimals( 0 );
571  editor->setClearValue( 0 );
572  editor->setMaximum( std::numeric_limits<int>::max() );
573  editor->setMinimum( std::numeric_limits<int>::min() );
574  break;
575  }
576  case QVariant::Type::Char:
577  {
578  editor = new QgsDoubleSpinBox( parent );
579  editor->setDecimals( 0 );
580  editor->setClearValue( 0 );
581  editor->setMaximum( std::numeric_limits<char>::max() );
582  editor->setMinimum( std::numeric_limits<char>::min() );
583  break;
584  }
585  case QVariant::Type::UInt:
586  {
587  editor = new QgsDoubleSpinBox( parent );
588  editor->setDecimals( 0 );
589  editor->setClearValue( 0 );
590  editor->setMaximum( std::numeric_limits<unsigned int>::max() );
591  editor->setMinimum( 0 );
592  break;
593  }
594  case QVariant::Type::LongLong:
595  {
596  editor = new QgsDoubleSpinBox( parent );
597  editor->setDecimals( 0 );
598  editor->setClearValue( 0 );
599  editor->setMaximum( static_cast<double>( std::numeric_limits<qlonglong>::max() ) );
600  editor->setMinimum( std::numeric_limits<qlonglong>::min() );
601  break;
602  }
603  case QVariant::Type::ULongLong:
604  {
605  editor = new QgsDoubleSpinBox( parent );
606  editor->setDecimals( 0 );
607  editor->setClearValue( 0 );
608  editor->setMaximum( static_cast<double>( std::numeric_limits<unsigned long long>::max() ) );
609  editor->setMinimum( 0 );
610  break;
611  }
612  default:
613  break;
614  }
615  return editor ? editor : QStyledItemDelegate::createEditor( parent, option, index );
616 }
617 
619 
620 // ------------------------------ Widget ------------------------------------
622 {
623  return new QgsCategorizedSymbolRendererWidget( layer, style, renderer );
624 }
625 
627  : QgsRendererWidget( layer, style )
628  , mContextMenu( new QMenu( this ) )
629 {
630 
631  // try to recognize the previous renderer
632  // (null renderer means "no previous renderer")
633  if ( renderer )
634  {
636  }
637  if ( !mRenderer )
638  {
639  mRenderer = std::make_unique< QgsCategorizedSymbolRenderer >( QString(), QgsCategoryList() );
640  if ( renderer )
642  }
643 
644  const QString attrName = mRenderer->classAttribute();
645  mOldClassificationAttribute = attrName;
646 
647  // setup user interface
648  setupUi( this );
649  layout()->setContentsMargins( 0, 0, 0, 0 );
650 
651  mExpressionWidget->setLayer( mLayer );
652  btnChangeCategorizedSymbol->setLayer( mLayer );
653  btnChangeCategorizedSymbol->registerExpressionContextGenerator( this );
654 
655  // initiate color ramp button to random
656  btnColorRamp->setShowRandomColorRamp( true );
657 
658  // set project default color ramp
659  std::unique_ptr< QgsColorRamp > colorRamp( QgsProject::instance()->styleSettings()->defaultColorRamp() );
660  if ( colorRamp )
661  {
662  btnColorRamp->setColorRamp( colorRamp.get() );
663  }
664  else
665  {
666  btnColorRamp->setRandomColorRamp();
667  }
668 
670  if ( mCategorizedSymbol )
671  {
672  btnChangeCategorizedSymbol->setSymbolType( mCategorizedSymbol->type() );
673  btnChangeCategorizedSymbol->setSymbol( mCategorizedSymbol->clone() );
674  }
675 
676  mModel = new QgsCategorizedSymbolRendererModel( this );
677  mModel->setRenderer( mRenderer.get() );
678 
679  // update GUI from renderer
681 
682  viewCategories->setModel( mModel );
683  viewCategories->resizeColumnToContents( 0 );
684  viewCategories->resizeColumnToContents( 1 );
685  viewCategories->resizeColumnToContents( 2 );
686  viewCategories->setItemDelegateForColumn( 1, new QgsCategorizedRendererViewItemDelegate( mExpressionWidget, viewCategories ) );
687 
688  viewCategories->setStyle( new QgsCategorizedSymbolRendererViewStyle( viewCategories ) );
689  connect( viewCategories->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsCategorizedSymbolRendererWidget::selectionChanged );
690 
691  connect( mModel, &QgsCategorizedSymbolRendererModel::rowsMoved, this, &QgsCategorizedSymbolRendererWidget::rowsMoved );
692  connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsPanelWidget::widgetChanged );
693 
694  connect( mExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsCategorizedSymbolRendererWidget::categoryColumnChanged );
695 
696  connect( viewCategories, &QAbstractItemView::doubleClicked, this, &QgsCategorizedSymbolRendererWidget::categoriesDoubleClicked );
697  connect( viewCategories, &QTreeView::customContextMenuRequested, this, &QgsCategorizedSymbolRendererWidget::showContextMenu );
698 
699  connect( btnChangeCategorizedSymbol, &QgsSymbolButton::changed, this, &QgsCategorizedSymbolRendererWidget::updateSymbolsFromButton );
700 
701  connect( btnAddCategories, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::addCategories );
702  connect( btnDeleteCategories, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::deleteCategories );
703  connect( btnDeleteAllCategories, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::deleteAllCategories );
704  connect( btnAddCategory, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::addCategory );
705 
707 
708  // menus for data-defined rotation/size
709  QMenu *advMenu = new QMenu;
710 
711  advMenu->addAction( tr( "Match to Saved Symbols" ), this, &QgsCategorizedSymbolRendererWidget::matchToSymbolsFromLibrary );
712  advMenu->addAction( tr( "Match to Symbols from File…" ), this, &QgsCategorizedSymbolRendererWidget::matchToSymbolsFromXml );
713  mActionLevels = advMenu->addAction( tr( "Symbol Levels…" ), this, &QgsCategorizedSymbolRendererWidget::showSymbolLevels );
715  {
716  QAction *actionDdsLegend = advMenu->addAction( tr( "Data-defined Size Legend…" ) );
717  // only from Qt 5.6 there is convenience addAction() with new style connection
718  connect( actionDdsLegend, &QAction::triggered, this, &QgsCategorizedSymbolRendererWidget::dataDefinedSizeLegend );
719  }
720 
721  btnAdvanced->setMenu( advMenu );
722 
723  mExpressionWidget->registerExpressionContextGenerator( this );
724 
725  mMergeCategoriesAction = new QAction( tr( "Merge Categories" ), this );
726  connect( mMergeCategoriesAction, &QAction::triggered, this, &QgsCategorizedSymbolRendererWidget::mergeSelectedCategories );
727  mUnmergeCategoriesAction = new QAction( tr( "Unmerge Categories" ), this );
728  connect( mUnmergeCategoriesAction, &QAction::triggered, this, &QgsCategorizedSymbolRendererWidget::unmergeSelectedCategories );
729 
730  connect( mContextMenu, &QMenu::aboutToShow, this, [ = ]
731  {
732  const std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
733  mPasteSymbolAction->setEnabled( static_cast< bool >( tempSymbol ) );
734  } );
735 }
736 
738 {
739  delete mModel;
740 }
741 
743 {
744  // Note: This assumes that the signals for UI element changes have not
745  // yet been connected, so that the updates to color ramp, symbol, etc
746  // don't override existing customizations.
747 
748  //mModel->setRenderer ( mRenderer ); // necessary?
749 
750  // set column
751  const QString attrName = mRenderer->classAttribute();
752  mExpressionWidget->setField( attrName );
753 
754  // set source symbol
755  if ( mRenderer->sourceSymbol() )
756  {
757  mCategorizedSymbol.reset( mRenderer->sourceSymbol()->clone() );
758  whileBlocking( btnChangeCategorizedSymbol )->setSymbol( mCategorizedSymbol->clone() );
759  }
760 
761  // if a color ramp attached to the renderer, enable the color ramp button
762  if ( mRenderer->sourceColorRamp() )
763  {
764  btnColorRamp->setColorRamp( mRenderer->sourceColorRamp() );
765  }
766 }
767 
769 {
770  return mRenderer.get();
771 }
772 
774 {
776  btnChangeCategorizedSymbol->setMapCanvas( context.mapCanvas() );
777  btnChangeCategorizedSymbol->setMessageBar( context.messageBar() );
778 }
779 
781 {
782  delete mActionLevels;
783  mActionLevels = nullptr;
784 }
785 
787 {
788  const QList<int> selectedCats = selectedCategories();
789 
790  if ( !selectedCats.isEmpty() )
791  {
792  QgsSymbol *newSymbol = mCategorizedSymbol->clone();
793  QgsSymbolSelectorDialog dlg( newSymbol, mStyle, mLayer, this );
794  dlg.setContext( context() );
795  if ( !dlg.exec() )
796  {
797  delete newSymbol;
798  return;
799  }
800 
801  const auto constSelectedCats = selectedCats;
802  for ( const int idx : constSelectedCats )
803  {
804  const QgsRendererCategory category = mRenderer->categories().value( idx );
805 
806  QgsSymbol *newCatSymbol = newSymbol->clone();
807  newCatSymbol->setColor( mRenderer->categories()[idx].symbol()->color() );
808  mRenderer->updateCategorySymbol( idx, newCatSymbol );
809  }
810  }
811 }
812 
814 {
816  std::unique_ptr<QgsSymbol> newSymbol( mCategorizedSymbol->clone() );
817  if ( panel && panel->dockMode() )
818  {
819  // bit tricky here - the widget doesn't take ownership of the symbol. So we need it to last for the duration of the
820  // panel's existence. Accordingly, just kinda give it ownership here, and clean up in cleanUpSymbolSelector
821  QgsSymbolSelectorWidget *dlg = new QgsSymbolSelectorWidget( newSymbol.release(), mStyle, mLayer, panel );
822  dlg->setContext( mContext );
823  connect( dlg, &QgsPanelWidget::widgetChanged, this, &QgsCategorizedSymbolRendererWidget::updateSymbolsFromWidget );
824  connect( dlg, &QgsPanelWidget::panelAccepted, this, &QgsCategorizedSymbolRendererWidget::cleanUpSymbolSelector );
825  openPanel( dlg );
826  }
827  else
828  {
829  QgsSymbolSelectorDialog dlg( newSymbol.get(), mStyle, mLayer, panel );
830  dlg.setContext( mContext );
831  if ( !dlg.exec() || !newSymbol )
832  {
833  return;
834  }
835 
836  mCategorizedSymbol = std::move( newSymbol );
838  }
839 }
840 
841 
843 {
844 }
845 
847 {
848  mRenderer->setClassAttribute( field );
849  emit widgetChanged();
850 }
851 
853 {
854  if ( idx.isValid() && idx.column() == 0 )
856 }
857 
859 {
860  const QgsRendererCategory category = mRenderer->categories().value( currentCategoryRow() );
861 
862  std::unique_ptr< QgsSymbol > symbol;
863 
864  if ( auto *lSymbol = category.symbol() )
865  {
866  symbol.reset( lSymbol->clone() );
867  }
868  else
869  {
870  symbol.reset( QgsSymbol::defaultSymbol( mLayer->geometryType() ) );
871  }
872 
874  if ( panel && panel->dockMode() )
875  {
876  QgsSymbolSelectorWidget *dlg = new QgsSymbolSelectorWidget( symbol.release(), mStyle, mLayer, panel );
877  dlg->setContext( mContext );
878  dlg->setPanelTitle( category.label() );
879  connect( dlg, &QgsPanelWidget::widgetChanged, this, &QgsCategorizedSymbolRendererWidget::updateSymbolsFromWidget );
880  connect( dlg, &QgsPanelWidget::panelAccepted, this, &QgsCategorizedSymbolRendererWidget::cleanUpSymbolSelector );
881  openPanel( dlg );
882  }
883  else
884  {
885  QgsSymbolSelectorDialog dlg( symbol.get(), mStyle, mLayer, panel );
886  dlg.setContext( mContext );
887  if ( !dlg.exec() || !symbol )
888  {
889  return;
890  }
891 
892  mCategorizedSymbol = std::move( symbol );
894  }
895 }
896 
897 
899 {
900  const QString attrName = mExpressionWidget->currentField();
901  const int idx = mLayer->fields().lookupField( attrName );
902  QList<QVariant> uniqueValues;
903  if ( idx == -1 )
904  {
905  // Lets assume it's an expression
906  QgsExpression *expression = new QgsExpression( attrName );
912 
913  expression->prepare( &context );
915  QgsFeature feature;
916  while ( fit.nextFeature( feature ) )
917  {
918  context.setFeature( feature );
919  const QVariant value = expression->evaluate( &context );
920  if ( uniqueValues.contains( value ) )
921  continue;
922  uniqueValues << value;
923  }
924  }
925  else
926  {
927  uniqueValues = qgis::setToList( mLayer->uniqueValues( idx ) );
928  }
929 
930  // ask to abort if too many classes
931  if ( uniqueValues.size() >= 1000 )
932  {
933  const int res = QMessageBox::warning( nullptr, tr( "Classify Categories" ),
934  tr( "High number of classes. Classification would yield %n entries which might not be expected. Continue?", nullptr, uniqueValues.size() ),
935  QMessageBox::Ok | QMessageBox::Cancel,
936  QMessageBox::Cancel );
937  if ( res == QMessageBox::Cancel )
938  {
939  return;
940  }
941  }
942 
943 #if 0
944  DlgAddCategories dlg( mStyle, createDefaultSymbol(), unique_vals, this );
945  if ( !dlg.exec() )
946  return;
947 #endif
948 
950  bool deleteExisting = false;
951 
952  if ( !mOldClassificationAttribute.isEmpty() &&
953  attrName != mOldClassificationAttribute &&
954  !mRenderer->categories().isEmpty() )
955  {
956  const int res = QMessageBox::question( this,
957  tr( "Delete Classification" ),
958  tr( "The classification field was changed from '%1' to '%2'.\n"
959  "Should the existing classes be deleted before classification?" )
960  .arg( mOldClassificationAttribute, attrName ),
961  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel );
962  if ( res == QMessageBox::Cancel )
963  {
964  return;
965  }
966 
967  deleteExisting = ( res == QMessageBox::Yes );
968  }
969 
970  // First element to apply coloring to
971  bool keepExistingColors = false;
972  if ( !deleteExisting )
973  {
974  QgsCategoryList prevCats = mRenderer->categories();
975  keepExistingColors = !prevCats.isEmpty();
976  QgsRandomColorRamp randomColors;
977  if ( keepExistingColors && btnColorRamp->isRandomColorRamp() )
978  randomColors.setTotalColorCount( cats.size() );
979  for ( int i = 0; i < cats.size(); ++i )
980  {
981  bool contains = false;
982  const QVariant value = cats.at( i ).value();
983  for ( int j = 0; j < prevCats.size() && !contains; ++j )
984  {
985  const QVariant prevCatValue = prevCats.at( j ).value();
986  if ( prevCatValue.type() == QVariant::List )
987  {
988  const QVariantList list = prevCatValue.toList();
989  for ( const QVariant &v : list )
990  {
991  if ( v == value )
992  {
993  contains = true;
994  break;
995  }
996  }
997  }
998  else
999  {
1000  if ( prevCats.at( j ).value() == value )
1001  {
1002  contains = true;
1003  }
1004  }
1005  if ( contains )
1006  break;
1007  }
1008 
1009  if ( !contains )
1010  {
1011  if ( keepExistingColors && btnColorRamp->isRandomColorRamp() )
1012  {
1013  // insure that append symbols have random colors
1014  cats.at( i ).symbol()->setColor( randomColors.color( i ) );
1015  }
1016  prevCats.append( cats.at( i ) );
1017  }
1018  }
1019  cats = prevCats;
1020  }
1021 
1022  mOldClassificationAttribute = attrName;
1023 
1024  // TODO: if not all categories are desired, delete some!
1025  /*
1026  if (not dlg.readAllCats.isChecked())
1027  {
1028  cats2 = {}
1029  for item in dlg.listCategories.selectedItems():
1030  for k,c in cats.iteritems():
1031  if item.text() == k.toString():
1032  break
1033  cats2[k] = c
1034  cats = cats2
1035  }
1036  */
1037 
1038  // recreate renderer
1039  std::unique_ptr< QgsCategorizedSymbolRenderer > r = std::make_unique< QgsCategorizedSymbolRenderer >( attrName, cats );
1040  r->setSourceSymbol( mCategorizedSymbol->clone() );
1041  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
1042  if ( ramp )
1043  r->setSourceColorRamp( ramp->clone() );
1044 
1045  if ( mModel )
1046  {
1047  mModel->setRenderer( r.get() );
1048  }
1049  mRenderer = std::move( r );
1050  if ( ! keepExistingColors && ramp )
1051  applyColorRamp();
1052  emit widgetChanged();
1053 }
1054 
1056 {
1057  if ( !btnColorRamp->isNull() )
1058  {
1059  mRenderer->updateColorRamp( btnColorRamp->colorRamp() );
1060  }
1061  mModel->updateSymbology();
1062 }
1063 
1065 {
1066  const QModelIndex idx = viewCategories->selectionModel()->currentIndex();
1067  if ( !idx.isValid() )
1068  return -1;
1069  return idx.row();
1070 }
1071 
1073 {
1074  QList<int> rows;
1075  const QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
1076 
1077  const auto constSelectedRows = selectedRows;
1078  for ( const QModelIndex &r : constSelectedRows )
1079  {
1080  if ( r.isValid() )
1081  {
1082  rows.append( r.row() );
1083  }
1084  }
1085  return rows;
1086 }
1087 
1089 {
1090  const QList<int> categoryIndexes = selectedCategories();
1091  mModel->deleteRows( categoryIndexes );
1092  emit widgetChanged();
1093 }
1094 
1096 {
1097  mModel->removeAllRows();
1098  emit widgetChanged();
1099 }
1100 
1102 {
1103  if ( !mModel ) return;
1105  const QgsRendererCategory cat( QString(), symbol, QString(), true );
1106  mModel->addCategory( cat );
1107  emit widgetChanged();
1108 }
1109 
1111 {
1112  QList<QgsSymbol *> selectedSymbols;
1113 
1114  QItemSelectionModel *m = viewCategories->selectionModel();
1115  const QModelIndexList selectedIndexes = m->selectedRows( 1 );
1116 
1117  if ( !selectedIndexes.isEmpty() )
1118  {
1119  const QgsCategoryList &categories = mRenderer->categories();
1120  QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
1121  for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
1122  {
1123  const int row = ( *indexIt ).row();
1124  QgsSymbol *s = categories[row].symbol();
1125  if ( s )
1126  {
1127  selectedSymbols.append( s );
1128  }
1129  }
1130  }
1131  return selectedSymbols;
1132 }
1133 
1135 {
1136  QgsCategoryList cl;
1137 
1138  QItemSelectionModel *m = viewCategories->selectionModel();
1139  const QModelIndexList selectedIndexes = m->selectedRows( 1 );
1140 
1141  if ( !selectedIndexes.isEmpty() )
1142  {
1143  QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
1144  for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
1145  {
1146  cl.append( mModel->category( *indexIt ) );
1147  }
1148  }
1149  return cl;
1150 }
1151 
1153 {
1155  emit widgetChanged();
1156 }
1157 
1159 {
1161 }
1162 
1164 {
1165  viewCategories->selectionModel()->clear();
1166 }
1167 
1169 {
1170  const int matched = matchToSymbols( QgsStyle::defaultStyle() );
1171  if ( matched > 0 )
1172  {
1173  QMessageBox::information( this, tr( "Matched Symbols" ),
1174  tr( "Matched %n categories to symbols.", nullptr, matched ) );
1175  }
1176  else
1177  {
1178  QMessageBox::warning( this, tr( "Matched Symbols" ),
1179  tr( "No categories could be matched to symbols in library." ) );
1180  }
1181 }
1182 
1184 {
1185  if ( !mLayer || !style )
1186  return 0;
1187 
1191 
1192  QVariantList unmatchedCategories;
1193  QStringList unmatchedSymbols;
1194  const int matched = mRenderer->matchToSymbols( style, type, unmatchedCategories, unmatchedSymbols );
1195 
1196  mModel->updateSymbology();
1197  return matched;
1198 }
1199 
1201 {
1202  QgsSettings settings;
1203  const QString openFileDir = settings.value( QStringLiteral( "UI/lastMatchToSymbolsDir" ), QDir::homePath() ).toString();
1204 
1205  const QString fileName = QFileDialog::getOpenFileName( this, tr( "Match to Symbols from File" ), openFileDir,
1206  tr( "XML files (*.xml *.XML)" ) );
1207  if ( fileName.isEmpty() )
1208  {
1209  return;
1210  }
1211 
1212  const QFileInfo openFileInfo( fileName );
1213  settings.setValue( QStringLiteral( "UI/lastMatchToSymbolsDir" ), openFileInfo.absolutePath() );
1214 
1215  QgsStyle importedStyle;
1216  if ( !importedStyle.importXml( fileName ) )
1217  {
1218  QMessageBox::warning( this, tr( "Match to Symbols from File" ),
1219  tr( "An error occurred while reading file:\n%1" ).arg( importedStyle.errorString() ) );
1220  return;
1221  }
1222 
1223  const int matched = matchToSymbols( &importedStyle );
1224  if ( matched > 0 )
1225  {
1226  QMessageBox::information( this, tr( "Match to Symbols from File" ),
1227  tr( "Matched %n categories to symbols from file.", nullptr, matched ) );
1228  }
1229  else
1230  {
1231  QMessageBox::warning( this, tr( "Match to Symbols from File" ),
1232  tr( "No categories could be matched to symbols in file." ) );
1233  }
1234 }
1235 
1237 {
1238  for ( const QgsLegendSymbolItem &legendSymbol : levels )
1239  {
1240  QgsSymbol *sym = legendSymbol.symbol();
1241  for ( int layer = 0; layer < sym->symbolLayerCount(); layer++ )
1242  {
1243  mRenderer->setLegendSymbolItem( legendSymbol.ruleKey(), sym->clone() );
1244  }
1245  }
1246  mRenderer->setUsingSymbolLevels( enabled );
1247  mModel->updateSymbology();
1248  emit widgetChanged();
1249 }
1250 
1252 {
1253  std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
1254  if ( !tempSymbol )
1255  return;
1256 
1257  const QList<int> selectedCats = selectedCategories();
1258  if ( !selectedCats.isEmpty() )
1259  {
1260  for ( const int idx : selectedCats )
1261  {
1262  if ( mRenderer->categories().at( idx ).symbol()->type() != tempSymbol->type() )
1263  continue;
1264 
1265  std::unique_ptr< QgsSymbol > newCatSymbol( tempSymbol->clone() );
1266  if ( selectedCats.count() > 1 )
1267  {
1268  //if updating multiple categories, retain the existing category colors
1269  newCatSymbol->setColor( mRenderer->categories().at( idx ).symbol()->color() );
1270  }
1271  mRenderer->updateCategorySymbol( idx, newCatSymbol.release() );
1272  }
1273  emit widgetChanged();
1274  }
1275 }
1276 
1277 void QgsCategorizedSymbolRendererWidget::cleanUpSymbolSelector( QgsPanelWidget *container )
1278 {
1279  QgsSymbolSelectorWidget *dlg = qobject_cast<QgsSymbolSelectorWidget *>( container );
1280  if ( !dlg )
1281  return;
1282 
1283  delete dlg->symbol();
1284 }
1285 
1286 void QgsCategorizedSymbolRendererWidget::updateSymbolsFromWidget()
1287 {
1288  QgsSymbolSelectorWidget *dlg = qobject_cast<QgsSymbolSelectorWidget *>( sender() );
1289  mCategorizedSymbol.reset( dlg->symbol()->clone() );
1290 
1292 }
1293 
1294 void QgsCategorizedSymbolRendererWidget::updateSymbolsFromButton()
1295 {
1296  mCategorizedSymbol.reset( btnChangeCategorizedSymbol->symbol()->clone() );
1297 
1299 }
1300 
1302 {
1303  // When there is a selection, change the selected symbols only
1304  QItemSelectionModel *m = viewCategories->selectionModel();
1305  const QModelIndexList i = m->selectedRows();
1306 
1307  if ( !i.isEmpty() )
1308  {
1309  const QList<int> selectedCats = selectedCategories();
1310 
1311  if ( !selectedCats.isEmpty() )
1312  {
1313  const auto constSelectedCats = selectedCats;
1314  for ( const int idx : constSelectedCats )
1315  {
1316  QgsSymbol *newCatSymbol = mCategorizedSymbol->clone();
1317  if ( selectedCats.count() > 1 )
1318  {
1319  //if updating multiple categories, retain the existing category colors
1320  newCatSymbol->setColor( mRenderer->categories().at( idx ).symbol()->color() );
1321  }
1322  mRenderer->updateCategorySymbol( idx, newCatSymbol );
1323  }
1324  }
1325  }
1326  else
1327  {
1328  mRenderer->updateSymbols( mCategorizedSymbol.get() );
1329  }
1330 
1331  mModel->updateSymbology();
1332  emit widgetChanged();
1333 }
1334 
1336 {
1337  if ( !event )
1338  {
1339  return;
1340  }
1341 
1342  if ( event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier )
1343  {
1344  mCopyBuffer.clear();
1345  mCopyBuffer = selectedCategoryList();
1346  }
1347  else if ( event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier )
1348  {
1349  QgsCategoryList::const_iterator rIt = mCopyBuffer.constBegin();
1350  for ( ; rIt != mCopyBuffer.constEnd(); ++rIt )
1351  {
1352  mModel->addCategory( *rIt );
1353  }
1354  }
1355 }
1356 
1357 QgsExpressionContext QgsCategorizedSymbolRendererWidget::createExpressionContext() const
1358 {
1359  QgsExpressionContext expContext;
1363 
1364  if ( auto *lMapCanvas = mContext.mapCanvas() )
1365  {
1366  expContext << QgsExpressionContextUtils::mapSettingsScope( lMapCanvas->mapSettings() )
1367  << new QgsExpressionContextScope( lMapCanvas->expressionContextScope() );
1368  if ( const QgsExpressionContextScopeGenerator *generator = dynamic_cast< const QgsExpressionContextScopeGenerator * >( lMapCanvas->temporalController() ) )
1369  {
1370  expContext << generator->createExpressionContextScope();
1371  }
1372  }
1373  else
1374  {
1376  }
1377 
1378  if ( auto *lVectorLayer = vectorLayer() )
1379  expContext << QgsExpressionContextUtils::layerScope( lVectorLayer );
1380 
1381  // additional scopes
1382  const auto constAdditionalExpressionContextScopes = mContext.additionalExpressionContextScopes();
1383  for ( const QgsExpressionContextScope &scope : constAdditionalExpressionContextScopes )
1384  {
1385  expContext.appendScope( new QgsExpressionContextScope( scope ) );
1386  }
1387 
1388  return expContext;
1389 }
1390 
1391 void QgsCategorizedSymbolRendererWidget::dataDefinedSizeLegend()
1392 {
1393  QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( mCategorizedSymbol.get() ); // this should be only enabled for marker symbols
1394  QgsDataDefinedSizeLegendWidget *panel = createDataDefinedSizeLegendWidget( s, mRenderer->dataDefinedSizeLegend() );
1395  if ( panel )
1396  {
1397  connect( panel, &QgsPanelWidget::widgetChanged, this, [ = ]
1398  {
1399  mRenderer->setDataDefinedSizeLegend( panel->dataDefinedSizeLegend() );
1400  emit widgetChanged();
1401  } );
1402  openPanel( panel ); // takes ownership of the panel
1403  }
1404 }
1405 
1406 void QgsCategorizedSymbolRendererWidget::mergeSelectedCategories()
1407 {
1408  const QgsCategoryList &categories = mRenderer->categories();
1409 
1410  const QList<int> selectedCategoryIndexes = selectedCategories();
1411  QList< int > categoryIndexes;
1412 
1413  // filter out "" entry
1414  for ( const int i : selectedCategoryIndexes )
1415  {
1416  const QVariant v = categories.at( i ).value();
1417 
1418  if ( !v.isValid() || v == "" )
1419  {
1420  continue;
1421  }
1422 
1423  categoryIndexes.append( i );
1424  }
1425 
1426  if ( categoryIndexes.count() < 2 )
1427  return;
1428 
1429  QStringList labels;
1430  QVariantList values;
1431  values.reserve( categoryIndexes.count() );
1432  labels.reserve( categoryIndexes.count() );
1433  for ( const int i : categoryIndexes )
1434  {
1435  const QVariant v = categories.at( i ).value();
1436 
1437  if ( v.type() == QVariant::List )
1438  {
1439  values.append( v.toList() );
1440  }
1441  else
1442  values << v;
1443 
1444  labels << categories.at( i ).label();
1445  }
1446 
1447  // modify first category (basically we "merge up" into the first selected category)
1448  mRenderer->updateCategoryLabel( categoryIndexes.at( 0 ), labels.join( ',' ) );
1449  mRenderer->updateCategoryValue( categoryIndexes.at( 0 ), values );
1450 
1451  categoryIndexes.pop_front();
1452  mModel->deleteRows( categoryIndexes );
1453 
1454  emit widgetChanged();
1455 }
1456 
1457 void QgsCategorizedSymbolRendererWidget::unmergeSelectedCategories()
1458 {
1459  const QList<int> categoryIndexes = selectedCategories();
1460  if ( categoryIndexes.isEmpty() )
1461  return;
1462 
1463  const QgsCategoryList &categories = mRenderer->categories();
1464  for ( const int i : categoryIndexes )
1465  {
1466  const QVariant v = categories.at( i ).value();
1467  if ( v.type() != QVariant::List )
1468  continue;
1469 
1470  const QVariantList list = v.toList();
1471  for ( int j = 1; j < list.count(); ++j )
1472  {
1473  mModel->addCategory( QgsRendererCategory( list.at( j ), categories.at( i ).symbol()->clone(), list.at( j ).toString(), categories.at( i ).renderState() ) );
1474  }
1475  mRenderer->updateCategoryValue( i, list.at( 0 ) );
1476  mRenderer->updateCategoryLabel( i, list.at( 0 ).toString() );
1477  }
1478 
1479  emit widgetChanged();
1480 }
1481 
1482 void QgsCategorizedSymbolRendererWidget::showContextMenu( QPoint )
1483 {
1484  mContextMenu->clear();
1485  const QList< QAction * > actions = contextMenu->actions();
1486  for ( QAction *act : actions )
1487  {
1488  mContextMenu->addAction( act );
1489  }
1490 
1491  mContextMenu->addSeparator();
1492 
1493  if ( viewCategories->selectionModel()->selectedRows().count() > 1 )
1494  {
1495  mContextMenu->addAction( mMergeCategoriesAction );
1496  }
1497  if ( viewCategories->selectionModel()->selectedRows().count() == 1 )
1498  {
1499  const QList<int> categoryIndexes = selectedCategories();
1500  const QgsCategoryList &categories = mRenderer->categories();
1501  const QVariant v = categories.at( categoryIndexes.at( 0 ) ).value();
1502  if ( v.type() == QVariant::List )
1503  mContextMenu->addAction( mUnmergeCategoriesAction );
1504  }
1505  else if ( viewCategories->selectionModel()->selectedRows().count() > 1 )
1506  {
1507  mContextMenu->addAction( mUnmergeCategoriesAction );
1508  }
1509 
1510  mContextMenu->exec( QCursor::pos() );
1511 }
1512 
1513 void QgsCategorizedSymbolRendererWidget::selectionChanged( const QItemSelection &, const QItemSelection & )
1514 {
1515  const QList<int> selectedCats = selectedCategories();
1516  if ( !selectedCats.isEmpty() )
1517  {
1518  whileBlocking( btnChangeCategorizedSymbol )->setSymbol( mRenderer->categories().at( selectedCats.at( 0 ) ).symbol()->clone() );
1519  }
1520  else if ( mRenderer->sourceSymbol() )
1521  {
1522  whileBlocking( btnChangeCategorizedSymbol )->setSymbol( mRenderer->sourceSymbol()->clone() );
1523  }
1524  btnChangeCategorizedSymbol->setDialogTitle( selectedCats.size() == 1 ? mRenderer->categories().at( selectedCats.at( 0 ) ).label() : tr( "Symbol Settings" ) );
1525 }
SymbolType
Symbol types.
Definition: qgis.h:205
@ Marker
Marker symbol.
@ Line
Line symbol.
@ Fill
Fill symbol.
void changeSelectedSymbols()
Changes the selected symbols alone for the change button, if there is a selection.
void applyColorRamp()
Applies the color ramp passed on by the color ramp button.
QgsFeatureRenderer * renderer() override
Returns pointer to the renderer (no transfer of ownership)
void disableSymbolLevels() override
Disables symbol level modification on the widget.
void matchToSymbolsFromXml()
Prompts for selection of an xml file, then replaces category symbols with the symbols from the XML fi...
std::unique_ptr< QgsCategorizedSymbolRenderer > mRenderer
int currentCategoryRow()
Returns row index for the currently selected category (-1 if on no selection)
void matchToSymbolsFromLibrary()
Replaces category symbols with the symbols from the users' symbol library that have a matching name.
void setContext(const QgsSymbolWidgetContext &context) override
Sets the context in which the renderer widget is shown, e.g., the associated map canvas and expressio...
void setSymbolLevels(const QgsLegendSymbolList &levels, bool enabled) override
Sets the symbol levels for the renderer defined in the widget.
void applyChangeToSymbol()
Applies current symbol to selected categories, or to all categories if none is selected.
static QgsRendererWidget * create(QgsVectorLayer *layer, QgsStyle *style, QgsFeatureRenderer *renderer)
QList< int > selectedCategories()
Returns a list of indexes for the categories under selection.
QList< QgsSymbol * > selectedSymbols() override
Subclasses may provide the capability of changing multiple symbols at once by implementing the follow...
QgsCategorizedSymbolRendererWidget(QgsVectorLayer *layer, QgsStyle *style, QgsFeatureRenderer *renderer)
int matchToSymbols(QgsStyle *style)
Replaces category symbols with the symbols from a style that have a matching name.
const QgsCategoryList & categories() const
Returns a list of all categories recognized by the renderer.
static QgsCategorizedSymbolRenderer * convertFromRenderer(const QgsFeatureRenderer *renderer, QgsVectorLayer *layer=nullptr)
Creates a new QgsCategorizedSymbolRenderer from an existing renderer.
static QgsCategoryList createCategories(const QVariantList &values, const QgsSymbol *symbol, QgsVectorLayer *layer=nullptr, const QString &fieldName=QString())
Create categories for a list of values.
static QString displayString(const QVariant &value, int precision=-1)
Returns a localized representation of value with the given precision, if precision is -1 then precisi...
void colorRampChanged()
Emitted whenever a new color ramp is set for the button.
Widget for configuration of appearance of legend for marker symbols with data-defined size.
QgsDataDefinedSizeLegend * dataDefinedSizeLegend() const
Returns configuration as set up in the dialog (may be nullptr). Ownership is passed to the caller.
The QgsSpinBox is a spin box with a clear button that will set the value to the defined clear value.
void setClearValue(double customValue, const QString &clearValueText=QString())
Defines the clear value as a custom value and will automatically set the clear value mode to CustomVa...
Abstract interface for generating an expression context scope.
Single scope for storing variables and functions for use within a QgsExpressionContext.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * atlasScope(const QgsLayoutAtlas *atlas)
Creates a new scope which contains variables and functions relating to a QgsLayoutAtlas.
static QgsExpressionContextScope * mapSettingsScope(const QgsMapSettings &mapSettings)
Creates a new scope which contains variables and functions relating to a QgsMapSettings object.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
Class for parsing and evaluation of expressions (formerly called "search strings").
bool prepare(const QgsExpressionContext *context)
Gets the expression ready for evaluation - find out column indexes.
QString expression() const
Returns the original, unmodified expression string.
QVariant evaluate()
Evaluate the feature and return the result.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
void copyRendererData(QgsFeatureRenderer *destRenderer) const
Clones generic renderer data to another renderer.
Definition: qgsrenderer.cpp:52
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
The QgsFieldExpressionWidget class reates a widget to choose fields and edit expressions It contains ...
void fieldChanged(const QString &fieldName)
Emitted when the currently selected field changes.
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:349
The class stores information about one class/rule of a vector layer renderer in a unified way that ca...
The QgsMapSettings class contains configuration for rendering of the map.
A marker symbol type, for rendering Point and MultiPoint geometries.
Base class for any widget that can be shown as a inline panel.
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
void panelAccepted(QgsPanelWidget *panel)
Emitted when the panel is accepted by the user.
void widgetChanged()
Emitted when the widget state changes.
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget.
void setPanelTitle(const QString &panelTitle)
Set the title of the panel when shown in the interface.
bool dockMode()
Returns the dock mode state.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:474
A QProxyStyle subclass which correctly sets the base style to match the QGIS application style,...
Definition: qgsproxystyle.h:31
Totally random color ramp.
virtual void setTotalColorCount(int colorCount)
Sets the desired total number of unique colors for the resultant ramp.
QColor color(double value) const override
Returns the color corresponding to a specified value.
Represents an individual category (class) from a QgsCategorizedSymbolRenderer.
QgsSymbol * symbol() const
Returns the symbol which will be used to render this category.
bool renderState() const
Returns true if the category is currently enabled and should be rendered.
QVariant value() const
Returns the value corresponding to this category.
QString label() const
Returns the label for this category, which is used to represent the category within legends and the l...
Base class for renderer settings widgets.
QAction * mPasteSymbolAction
Paste symbol action.
void showSymbolLevelsDialog(QgsFeatureRenderer *r)
Show a dialog with renderer's symbol level settings.
QgsSymbolWidgetContext mContext
Context in which widget is shown.
virtual void setContext(const QgsSymbolWidgetContext &context)
Sets the context in which the renderer widget is shown, e.g., the associated map canvas and expressio...
QgsDataDefinedSizeLegendWidget * createDataDefinedSizeLegendWidget(const QgsMarkerSymbol *symbol, const QgsDataDefinedSizeLegend *ddsLegend)
Creates widget to setup data-defined size legend.
QgsSymbolWidgetContext context() const
Returns the context in which the renderer widget is shown, e.g., the associated map canvas and expres...
const QgsVectorLayer * vectorLayer() const
Returns the vector layer associated with the widget.
QgsVectorLayer * mLayer
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.
QString errorString()
Returns last error from load/save operation.
Definition: qgsstyle.h:828
static QgsStyle * defaultStyle()
Returns default application-wide style.
Definition: qgsstyle.cpp:143
bool importXml(const QString &filename)
Imports the symbols and colorramps into the default style database from the given XML file.
Definition: qgsstyle.cpp:2722
void changed()
Emitted when the symbol's settings are changed.
static QgsSymbol * symbolFromMimeData(const QMimeData *data)
Attempts to parse mime data as a symbol.
static QIcon symbolPreviewIcon(const QgsSymbol *symbol, QSize size, int padding=0, QgsLegendPatchShape *shape=nullptr)
Returns an icon preview for a color ramp.
void setContext(const QgsSymbolWidgetContext &context)
Sets the context in which the symbol widget is shown, e.g., the associated map canvas and expression ...
Symbol selector widget that can be used to select and build a symbol.
QgsSymbol * symbol()
Returns the symbol that is currently active in the widget.
void setContext(const QgsSymbolWidgetContext &context)
Sets the context in which the symbol widget is shown, e.g., the associated map canvas and expression ...
Contains settings which reflect the context in which a symbol (or renderer) widget is shown,...
QList< QgsExpressionContextScope > additionalExpressionContextScopes() const
Returns the list of additional expression context scopes to show as available within the layer.
QgsMapCanvas * mapCanvas() const
Returns the map canvas associated with the widget.
QgsMessageBar * messageBar() const
Returns the message bar associated with the widget.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:93
static QgsSymbol * defaultSymbol(QgsWkbTypes::GeometryType geomType)
Returns a new default symbol for the specified geometry type.
Definition: qgssymbol.cpp:371
void setColor(const QColor &color) const
Sets the color for the symbol.
Definition: qgssymbol.cpp:565
int symbolLayerCount() const
Returns the total number of symbol layers contained in the symbol.
Definition: qgssymbol.h:215
virtual QgsSymbol * clone() const =0
Returns a deep copy of this symbol.
Represents a vector layer which manages a vector based data sets.
Q_INVOKABLE QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QSet< QVariant > uniqueValues(int fieldIndex, int limit=-1) const FINAL
Calculates a list of unique values contained within an attribute in the layer.
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:1929
QList< QgsRendererCategory > QgsCategoryList
const QgsField & field
Definition: qgsfield.h:463
QList< QgsLegendSymbolItem > QgsLegendSymbolList
#define QgsDebugMsg(str)
Definition: qgslogger.h:38