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