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