QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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"
29 
32 
33 #include "qgsvectorlayer.h"
34 #include "qgsfeatureiterator.h"
35 
36 #include "qgsproject.h"
37 #include "qgsexpression.h"
38 #include "qgsmapcanvas.h"
39 #include "qgssettings.h"
40 #include "qgsguiutils.h"
41 
42 #include <QKeyEvent>
43 #include <QMenu>
44 #include <QMessageBox>
45 #include <QStandardItemModel>
46 #include <QStandardItem>
47 #include <QPen>
48 #include <QPainter>
49 #include <QFileDialog>
50 
52 
53 QgsCategorizedSymbolRendererModel::QgsCategorizedSymbolRendererModel( QObject *parent ) : QAbstractItemModel( parent )
54  , mMimeFormat( QStringLiteral( "application/x-qgscategorizedsymbolrendererv2model" ) )
55 {
56 }
57 
58 void QgsCategorizedSymbolRendererModel::setRenderer( QgsCategorizedSymbolRenderer *renderer )
59 {
60  if ( mRenderer )
61  {
62  beginRemoveRows( QModelIndex(), 0, std::max( mRenderer->categories().size() - 1, 0 ) );
63  mRenderer = nullptr;
64  endRemoveRows();
65  }
66  if ( renderer )
67  {
68  mRenderer = renderer;
69  if ( renderer->categories().size() > 0 )
70  {
71  beginInsertRows( QModelIndex(), 0, renderer->categories().size() - 1 );
72  endInsertRows();
73  }
74  }
75 }
76 
77 void QgsCategorizedSymbolRendererModel::addCategory( const QgsRendererCategory &cat )
78 {
79  if ( !mRenderer ) return;
80  int idx = mRenderer->categories().size();
81  beginInsertRows( QModelIndex(), idx, idx );
82  mRenderer->addCategory( cat );
83  endInsertRows();
84 }
85 
86 QgsRendererCategory QgsCategorizedSymbolRendererModel::category( const QModelIndex &index )
87 {
88  if ( !mRenderer )
89  {
90  return QgsRendererCategory();
91  }
92  const QgsCategoryList &catList = mRenderer->categories();
93  int row = index.row();
94  if ( row >= catList.size() )
95  {
96  return QgsRendererCategory();
97  }
98  return catList.at( row );
99 }
100 
101 
102 Qt::ItemFlags QgsCategorizedSymbolRendererModel::flags( const QModelIndex &index ) const
103 {
104  if ( !index.isValid() )
105  {
106  return Qt::ItemIsDropEnabled;
107  }
108 
109  Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable;
110  if ( index.column() == 1 || index.column() == 2 )
111  {
112  flags |= Qt::ItemIsEditable;
113  }
114  return flags;
115 }
116 
117 Qt::DropActions QgsCategorizedSymbolRendererModel::supportedDropActions() const
118 {
119  return Qt::MoveAction;
120 }
121 
122 QVariant QgsCategorizedSymbolRendererModel::data( const QModelIndex &index, int role ) const
123 {
124  if ( !index.isValid() || !mRenderer )
125  return QVariant();
126 
127  const QgsRendererCategory category = mRenderer->categories().value( index.row() );
128 
129  if ( role == Qt::CheckStateRole && index.column() == 0 )
130  {
131  return category.renderState() ? Qt::Checked : Qt::Unchecked;
132  }
133  else if ( role == Qt::DisplayRole || role == Qt::ToolTipRole )
134  {
135  switch ( index.column() )
136  {
137  case 1:
138  return category.value().toString();
139  case 2:
140  return category.label();
141  default:
142  return QVariant();
143  }
144  }
145  else if ( role == Qt::DecorationRole && index.column() == 0 && category.symbol() )
146  {
147  const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
148  return QgsSymbolLayerUtils::symbolPreviewIcon( category.symbol(), QSize( iconSize, iconSize ) );
149  }
150  else if ( role == Qt::TextAlignmentRole )
151  {
152  return ( index.column() == 0 ) ? Qt::AlignHCenter : Qt::AlignLeft;
153  }
154  else if ( role == Qt::EditRole )
155  {
156  switch ( index.column() )
157  {
158  case 1:
159  return category.value();
160  case 2:
161  return category.label();
162  default:
163  return QVariant();
164  }
165  }
166 
167  return QVariant();
168 }
169 
170 bool QgsCategorizedSymbolRendererModel::setData( const QModelIndex &index, const QVariant &value, int role )
171 {
172  if ( !index.isValid() )
173  return false;
174 
175  if ( index.column() == 0 && role == Qt::CheckStateRole )
176  {
177  mRenderer->updateCategoryRenderState( index.row(), value == Qt::Checked );
178  emit dataChanged( index, index );
179  return true;
180  }
181 
182  if ( role != Qt::EditRole )
183  return false;
184 
185  switch ( index.column() )
186  {
187  case 1: // value
188  {
189  // try to preserve variant type for this value
190  QVariant val;
191  switch ( mRenderer->categories().value( index.row() ).value().type() )
192  {
193  case QVariant::Int:
194  val = value.toInt();
195  break;
196  case QVariant::Double:
197  val = value.toDouble();
198  break;
199  default:
200  val = value.toString();
201  break;
202  }
203  mRenderer->updateCategoryValue( index.row(), val );
204  break;
205  }
206  case 2: // label
207  mRenderer->updateCategoryLabel( index.row(), value.toString() );
208  break;
209  default:
210  return false;
211  }
212 
213  emit dataChanged( index, index );
214  return true;
215 }
216 
217 QVariant QgsCategorizedSymbolRendererModel::headerData( int section, Qt::Orientation orientation, int role ) const
218 {
219  if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 3 )
220  {
221  QStringList lst;
222  lst << tr( "Symbol" ) << tr( "Value" ) << tr( "Legend" );
223  return lst.value( section );
224  }
225  return QVariant();
226 }
227 
228 int QgsCategorizedSymbolRendererModel::rowCount( const QModelIndex &parent ) const
229 {
230  if ( parent.isValid() || !mRenderer )
231  {
232  return 0;
233  }
234  return mRenderer->categories().size();
235 }
236 
237 int QgsCategorizedSymbolRendererModel::columnCount( const QModelIndex &index ) const
238 {
239  Q_UNUSED( index );
240  return 3;
241 }
242 
243 QModelIndex QgsCategorizedSymbolRendererModel::index( int row, int column, const QModelIndex &parent ) const
244 {
245  if ( hasIndex( row, column, parent ) )
246  {
247  return createIndex( row, column );
248  }
249  return QModelIndex();
250 }
251 
252 QModelIndex QgsCategorizedSymbolRendererModel::parent( const QModelIndex &index ) const
253 {
254  Q_UNUSED( index );
255  return QModelIndex();
256 }
257 
258 QStringList QgsCategorizedSymbolRendererModel::mimeTypes() const
259 {
260  QStringList types;
261  types << mMimeFormat;
262  return types;
263 }
264 
265 QMimeData *QgsCategorizedSymbolRendererModel::mimeData( const QModelIndexList &indexes ) const
266 {
267  QMimeData *mimeData = new QMimeData();
268  QByteArray encodedData;
269 
270  QDataStream stream( &encodedData, QIODevice::WriteOnly );
271 
272  // Create list of rows
273  Q_FOREACH ( const QModelIndex &index, indexes )
274  {
275  if ( !index.isValid() || index.column() != 0 )
276  continue;
277 
278  stream << index.row();
279  }
280  mimeData->setData( mMimeFormat, encodedData );
281  return mimeData;
282 }
283 
284 bool QgsCategorizedSymbolRendererModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
285 {
286  Q_UNUSED( row );
287  Q_UNUSED( column );
288  if ( action != Qt::MoveAction ) return true;
289 
290  if ( !data->hasFormat( mMimeFormat ) ) return false;
291 
292  QByteArray encodedData = data->data( mMimeFormat );
293  QDataStream stream( &encodedData, QIODevice::ReadOnly );
294 
295  QVector<int> rows;
296  while ( !stream.atEnd() )
297  {
298  int r;
299  stream >> r;
300  rows.append( r );
301  }
302 
303  int to = parent.row();
304  // to is -1 if dragged outside items, i.e. below any item,
305  // then move to the last position
306  if ( to == -1 ) to = mRenderer->categories().size(); // out of rang ok, will be decreased
307  for ( int i = rows.size() - 1; i >= 0; i-- )
308  {
309  QgsDebugMsg( QStringLiteral( "move %1 to %2" ).arg( rows[i] ).arg( to ) );
310  int t = to;
311  // moveCategory first removes and then inserts
312  if ( rows[i] < t ) t--;
313  mRenderer->moveCategory( rows[i], t );
314  // current moved under another, shift its index up
315  for ( int j = 0; j < i; j++ )
316  {
317  if ( to < rows[j] && rows[i] > rows[j] ) rows[j] += 1;
318  }
319  // removed under 'to' so the target shifted down
320  if ( rows[i] < to ) to--;
321  }
322  emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
323  emit rowsMoved();
324  return false;
325 }
326 
327 void QgsCategorizedSymbolRendererModel::deleteRows( QList<int> rows )
328 {
329  std::sort( rows.begin(), rows.end() ); // list might be unsorted, depending on how the user selected the rows
330  for ( int i = rows.size() - 1; i >= 0; i-- )
331  {
332  beginRemoveRows( QModelIndex(), rows[i], rows[i] );
333  mRenderer->deleteCategory( rows[i] );
334  endRemoveRows();
335  }
336 }
337 
338 void QgsCategorizedSymbolRendererModel::removeAllRows()
339 {
340  beginRemoveRows( QModelIndex(), 0, mRenderer->categories().size() - 1 );
341  mRenderer->deleteAllCategories();
342  endRemoveRows();
343 }
344 
345 void QgsCategorizedSymbolRendererModel::sort( int column, Qt::SortOrder order )
346 {
347  if ( column == 0 )
348  {
349  return;
350  }
351  if ( column == 1 )
352  {
353  mRenderer->sortByValue( order );
354  }
355  else if ( column == 2 )
356  {
357  mRenderer->sortByLabel( order );
358  }
359  emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
360 }
361 
362 void QgsCategorizedSymbolRendererModel::updateSymbology()
363 {
364  emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
365 }
366 
367 // ------------------------------ View style --------------------------------
368 QgsCategorizedSymbolRendererViewStyle::QgsCategorizedSymbolRendererViewStyle( QWidget *parent )
369  : QgsProxyStyle( parent )
370 {}
371 
372 void QgsCategorizedSymbolRendererViewStyle::drawPrimitive( PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget ) const
373 {
374  if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
375  {
376  QStyleOption opt( *option );
377  opt.rect.setLeft( 0 );
378  // draw always as line above, because we move item to that index
379  opt.rect.setHeight( 0 );
380  if ( widget ) opt.rect.setRight( widget->width() );
381  QProxyStyle::drawPrimitive( element, &opt, painter, widget );
382  return;
383  }
384  QProxyStyle::drawPrimitive( element, option, painter, widget );
385 }
386 
388 
389 // ------------------------------ Widget ------------------------------------
391 {
392  return new QgsCategorizedSymbolRendererWidget( layer, style, renderer );
393 }
394 
396  : QgsRendererWidget( layer, style )
397 
398 {
399 
400  // try to recognize the previous renderer
401  // (null renderer means "no previous renderer")
402  if ( renderer )
403  {
405  }
406  if ( !mRenderer )
407  {
408  mRenderer = qgis::make_unique< QgsCategorizedSymbolRenderer >( QString(), QgsCategoryList() );
409  }
410 
411  QString attrName = mRenderer->classAttribute();
412  mOldClassificationAttribute = attrName;
413 
414  // setup user interface
415  setupUi( this );
416  this->layout()->setContentsMargins( 0, 0, 0, 0 );
417 
418  mExpressionWidget->setLayer( mLayer );
419 
420  // initiate color ramp button to random
421  btnColorRamp->setShowRandomColorRamp( true );
422 
423  // set project default color ramp
424  QString defaultColorRamp = QgsProject::instance()->readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/ColorRamp" ), QString() );
425  if ( !defaultColorRamp.isEmpty() )
426  {
427  btnColorRamp->setColorRampFromName( defaultColorRamp );
428  }
429  else
430  {
431  btnColorRamp->setRandomColorRamp();
432  }
433 
435 
436  mModel = new QgsCategorizedSymbolRendererModel( this );
437  mModel->setRenderer( mRenderer.get() );
438 
439  // update GUI from renderer
441 
442  viewCategories->setModel( mModel );
443  viewCategories->resizeColumnToContents( 0 );
444  viewCategories->resizeColumnToContents( 1 );
445  viewCategories->resizeColumnToContents( 2 );
446 
447  viewCategories->setStyle( new QgsCategorizedSymbolRendererViewStyle( viewCategories ) );
448 
449  connect( mModel, &QgsCategorizedSymbolRendererModel::rowsMoved, this, &QgsCategorizedSymbolRendererWidget::rowsMoved );
450  connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsPanelWidget::widgetChanged );
451 
452  connect( mExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsCategorizedSymbolRendererWidget::categoryColumnChanged );
453 
454  connect( viewCategories, &QAbstractItemView::doubleClicked, this, &QgsCategorizedSymbolRendererWidget::categoriesDoubleClicked );
455  connect( viewCategories, &QTreeView::customContextMenuRequested, this, &QgsCategorizedSymbolRendererWidget::contextMenuViewCategories );
456 
457  connect( btnChangeCategorizedSymbol, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::changeCategorizedSymbol );
458  connect( btnAddCategories, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::addCategories );
459  connect( btnDeleteCategories, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::deleteCategories );
460  connect( btnDeleteAllCategories, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::deleteAllCategories );
461  connect( btnAddCategory, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::addCategory );
462 
464 
465  // menus for data-defined rotation/size
466  QMenu *advMenu = new QMenu;
467 
468  advMenu->addAction( tr( "Match to Saved Symbols" ), this, SLOT( matchToSymbolsFromLibrary() ) );
469  advMenu->addAction( tr( "Match to Symbols from File…" ), this, SLOT( matchToSymbolsFromXml() ) );
470  advMenu->addAction( tr( "Symbol Levels…" ), this, SLOT( showSymbolLevels() ) );
471  if ( mCategorizedSymbol->type() == QgsSymbol::Marker )
472  {
473  QAction *actionDdsLegend = advMenu->addAction( tr( "Data-defined Size Legend…" ) );
474  // only from Qt 5.6 there is convenience addAction() with new style connection
475  connect( actionDdsLegend, &QAction::triggered, this, &QgsCategorizedSymbolRendererWidget::dataDefinedSizeLegend );
476  }
477 
478  btnAdvanced->setMenu( advMenu );
479 
480  mExpressionWidget->registerExpressionContextGenerator( this );
481 }
482 
484 {
485  delete mModel;
486 }
487 
489 {
490  // Note: This assumes that the signals for UI element changes have not
491  // yet been connected, so that the updates to color ramp, symbol, etc
492  // don't override existing customizations.
493 
495 
496  //mModel->setRenderer ( mRenderer ); // necessary?
497 
498  // set column
499  QString attrName = mRenderer->classAttribute();
500  mExpressionWidget->setField( attrName );
501 
502  // set source symbol
503  if ( mRenderer->sourceSymbol() )
504  {
505  mCategorizedSymbol.reset( mRenderer->sourceSymbol()->clone() );
507  }
508 
509  // if a color ramp attached to the renderer, enable the color ramp button
510  if ( mRenderer->sourceColorRamp() )
511  {
512  btnColorRamp->setColorRamp( mRenderer->sourceColorRamp() );
513  }
514 }
515 
517 {
518  return mRenderer.get();
519 }
520 
522 {
523  QList<int> selectedCats = selectedCategories();
524 
525  if ( !selectedCats.isEmpty() )
526  {
527  QgsSymbol *newSymbol = mCategorizedSymbol->clone();
528  QgsSymbolSelectorDialog dlg( newSymbol, mStyle, mLayer, this );
529  dlg.setContext( context() );
530  if ( !dlg.exec() )
531  {
532  delete newSymbol;
533  return;
534  }
535 
536  Q_FOREACH ( int idx, selectedCats )
537  {
538  QgsRendererCategory category = mRenderer->categories().value( idx );
539 
540  QgsSymbol *newCatSymbol = newSymbol->clone();
541  newCatSymbol->setColor( mRenderer->categories()[idx].symbol()->color() );
542  mRenderer->updateCategorySymbol( idx, newCatSymbol );
543  }
544  }
545 }
546 
548 {
550  std::unique_ptr<QgsSymbol> newSymbol( mCategorizedSymbol->clone() );
551  if ( panel && panel->dockMode() )
552  {
553  // bit tricky here - the widget doesn't take ownership of the symbol. So we need it to last for the duration of the
554  // panel's existence. Accordingly, just kinda give it ownership here, and clean up in cleanUpSymbolSelector
555  QgsSymbolSelectorWidget *dlg = new QgsSymbolSelectorWidget( newSymbol.release(), mStyle, mLayer, panel );
556  dlg->setContext( mContext );
557  connect( dlg, &QgsPanelWidget::widgetChanged, this, &QgsCategorizedSymbolRendererWidget::updateSymbolsFromWidget );
558  connect( dlg, &QgsPanelWidget::panelAccepted, this, &QgsCategorizedSymbolRendererWidget::cleanUpSymbolSelector );
560  openPanel( dlg );
561  }
562  else
563  {
564  QgsSymbolSelectorDialog dlg( newSymbol.get(), mStyle, mLayer, panel );
565  dlg.setContext( mContext );
566  if ( !dlg.exec() || !newSymbol )
567  {
568  return;
569  }
570 
571  mCategorizedSymbol = std::move( newSymbol );
574  }
575 }
576 
578 {
579  if ( !mCategorizedSymbol )
580  return;
581 
582  QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( mCategorizedSymbol.get(), btnChangeCategorizedSymbol->iconSize() );
583  btnChangeCategorizedSymbol->setIcon( icon );
584 }
585 
587 {
588 }
589 
591 {
592  mRenderer->setClassAttribute( field );
593  emit widgetChanged();
594 }
595 
597 {
598  if ( idx.isValid() && idx.column() == 0 )
600 }
601 
603 {
604  QgsRendererCategory category = mRenderer->categories().value( currentCategoryRow() );
605 
606  std::unique_ptr< QgsSymbol > symbol;
607 
608  if ( category.symbol() )
609  {
610  symbol.reset( category.symbol()->clone() );
611  }
612  else
613  {
614  symbol.reset( QgsSymbol::defaultSymbol( mLayer->geometryType() ) );
615  }
616 
618  if ( panel && panel->dockMode() )
619  {
620  QgsSymbolSelectorWidget *dlg = new QgsSymbolSelectorWidget( symbol.release(), mStyle, mLayer, panel );
621  dlg->setContext( mContext );
622  connect( dlg, &QgsPanelWidget::widgetChanged, this, &QgsCategorizedSymbolRendererWidget::updateSymbolsFromWidget );
623  connect( dlg, &QgsPanelWidget::panelAccepted, this, &QgsCategorizedSymbolRendererWidget::cleanUpSymbolSelector );
624  openPanel( dlg );
625  }
626  else
627  {
628  QgsSymbolSelectorDialog dlg( symbol.get(), mStyle, mLayer, panel );
629  dlg.setContext( mContext );
630  if ( !dlg.exec() || !symbol )
631  {
632  return;
633  }
634 
635  mCategorizedSymbol = std::move( symbol );
637  }
638 }
639 
640 static void _createCategories( QgsCategoryList &cats, QList<QVariant> &values, QgsSymbol *symbol )
641 {
642  // sort the categories first
643  QgsSymbolLayerUtils::sortVariantList( values, Qt::AscendingOrder );
644 
645  int num = values.count();
646 
647  for ( int i = 0; i < num; i++ )
648  {
649  QVariant value = values[i];
650  QgsSymbol *newSymbol = symbol->clone();
651  if ( ! value.isNull() )
652  {
653  cats.append( QgsRendererCategory( value, newSymbol, value.toString(), true ) );
654  }
655  }
656 
657  // add null (default) value
658  QgsSymbol *newSymbol = symbol->clone();
659  cats.append( QgsRendererCategory( QVariant( "" ), newSymbol, QString(), true ) );
660 }
661 
662 
664 {
665  QString attrName = mExpressionWidget->currentField();
666  int idx = mLayer->fields().lookupField( attrName );
667  QList<QVariant> unique_vals;
668  if ( idx == -1 )
669  {
670  // Lets assume it's an expression
671  QgsExpression *expression = new QgsExpression( attrName );
677 
678  expression->prepare( &context );
680  QgsFeature feature;
681  while ( fit.nextFeature( feature ) )
682  {
683  context.setFeature( feature );
684  QVariant value = expression->evaluate( &context );
685  if ( unique_vals.contains( value ) )
686  continue;
687  unique_vals << value;
688  }
689  }
690  else
691  {
692  unique_vals = mLayer->uniqueValues( idx ).toList();
693  }
694 
695  // ask to abort if too many classes
696  if ( unique_vals.size() >= 1000 )
697  {
698  int res = QMessageBox::warning( nullptr, tr( "Classify Categories" ),
699  tr( "High number of classes. Classification would yield %1 entries which might not be expected. Continue?" ).arg( unique_vals.size() ),
700  QMessageBox::Ok | QMessageBox::Cancel,
701  QMessageBox::Cancel );
702  if ( res == QMessageBox::Cancel )
703  {
704  return;
705  }
706  }
707 
708 #if 0
709  DlgAddCategories dlg( mStyle, createDefaultSymbol(), unique_vals, this );
710  if ( !dlg.exec() )
711  return;
712 #endif
713 
714  QgsCategoryList cats;
715  _createCategories( cats, unique_vals, mCategorizedSymbol.get() );
716  bool deleteExisting = false;
717 
718  if ( !mOldClassificationAttribute.isEmpty() &&
719  attrName != mOldClassificationAttribute &&
720  !mRenderer->categories().isEmpty() )
721  {
722  int res = QMessageBox::question( this,
723  tr( "Delete Classification" ),
724  tr( "The classification field was changed from '%1' to '%2'.\n"
725  "Should the existing classes be deleted before classification?" )
726  .arg( mOldClassificationAttribute, attrName ),
727  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel );
728  if ( res == QMessageBox::Cancel )
729  {
730  return;
731  }
732 
733  deleteExisting = ( res == QMessageBox::Yes );
734  }
735 
736  // First element to apply coloring to
737  bool keepExistingColors = false;
738  if ( !deleteExisting )
739  {
740  QgsCategoryList prevCats = mRenderer->categories();
741  keepExistingColors = !prevCats.isEmpty();
742  QgsRandomColorRamp randomColors;
743  if ( keepExistingColors && btnColorRamp->isRandomColorRamp() )
744  randomColors.setTotalColorCount( cats.size() );
745  for ( int i = 0; i < cats.size(); ++i )
746  {
747  bool contains = false;
748  QVariant value = cats.at( i ).value();
749  for ( int j = 0; j < prevCats.size() && !contains; ++j )
750  {
751  if ( prevCats.at( j ).value() == value )
752  {
753  contains = true;
754  break;
755  }
756  }
757 
758  if ( !contains )
759  {
760  if ( keepExistingColors && btnColorRamp->isRandomColorRamp() )
761  {
762  // insure that append symbols have random colors
763  cats.at( i ).symbol()->setColor( randomColors.color( i ) );
764  }
765  prevCats.append( cats.at( i ) );
766  }
767  }
768  cats = prevCats;
769  }
770 
771  mOldClassificationAttribute = attrName;
772 
773  // TODO: if not all categories are desired, delete some!
774  /*
775  if (not dlg.readAllCats.isChecked())
776  {
777  cats2 = {}
778  for item in dlg.listCategories.selectedItems():
779  for k,c in cats.iteritems():
780  if item.text() == k.toString():
781  break
782  cats2[k] = c
783  cats = cats2
784  }
785  */
786 
787  // recreate renderer
788  std::unique_ptr< QgsCategorizedSymbolRenderer > r = qgis::make_unique< QgsCategorizedSymbolRenderer >( attrName, cats );
789  r->setSourceSymbol( mCategorizedSymbol->clone() );
790  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
791  if ( ramp )
792  r->setSourceColorRamp( ramp->clone() );
793 
794  if ( mModel )
795  {
796  mModel->setRenderer( r.get() );
797  }
798  mRenderer = std::move( r );
799  if ( ! keepExistingColors && ramp )
800  applyColorRamp();
801  emit widgetChanged();
802 }
803 
805 {
806  if ( !btnColorRamp->isNull() )
807  {
808  mRenderer->updateColorRamp( btnColorRamp->colorRamp() );
809  }
810  mModel->updateSymbology();
811 }
812 
814 {
815  QModelIndex idx = viewCategories->selectionModel()->currentIndex();
816  if ( !idx.isValid() )
817  return -1;
818  return idx.row();
819 }
820 
822 {
823  QList<int> rows;
824  QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
825 
826  Q_FOREACH ( const QModelIndex &r, selectedRows )
827  {
828  if ( r.isValid() )
829  {
830  rows.append( r.row() );
831  }
832  }
833  return rows;
834 }
835 
837 {
838  QList<int> categoryIndexes = selectedCategories();
839  mModel->deleteRows( categoryIndexes );
840  emit widgetChanged();
841 }
842 
844 {
845  mModel->removeAllRows();
846  emit widgetChanged();
847 }
848 
850 {
851  if ( !mModel ) return;
853  QgsRendererCategory cat( QString(), symbol, QString(), true );
854  mModel->addCategory( cat );
855  emit widgetChanged();
856 }
857 
859 {
860  QList<QgsSymbol *> selectedSymbols;
861 
862  QItemSelectionModel *m = viewCategories->selectionModel();
863  QModelIndexList selectedIndexes = m->selectedRows( 1 );
864 
865  if ( m && !selectedIndexes.isEmpty() )
866  {
867  const QgsCategoryList &categories = mRenderer->categories();
868  QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
869  for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
870  {
871  int row = ( *indexIt ).row();
872  QgsSymbol *s = categories[row].symbol();
873  if ( s )
874  {
875  selectedSymbols.append( s );
876  }
877  }
878  }
879  return selectedSymbols;
880 }
881 
883 {
884  QgsCategoryList cl;
885 
886  QItemSelectionModel *m = viewCategories->selectionModel();
887  QModelIndexList selectedIndexes = m->selectedRows( 1 );
888 
889  if ( m && !selectedIndexes.isEmpty() )
890  {
891  QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
892  for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
893  {
894  cl.append( mModel->category( *indexIt ) );
895  }
896  }
897  return cl;
898 }
899 
901 {
903  emit widgetChanged();
904 }
905 
907 {
909 }
910 
912 {
913  viewCategories->selectionModel()->clear();
914 }
915 
917 {
918  int matched = matchToSymbols( QgsStyle::defaultStyle() );
919  if ( matched > 0 )
920  {
921  QMessageBox::information( this, tr( "Matched Symbols" ),
922  tr( "Matched %1 categories to symbols." ).arg( matched ) );
923  }
924  else
925  {
926  QMessageBox::warning( this, tr( "Matched Symbols" ),
927  tr( "No categories could be matched to symbols in library." ) );
928  }
929 }
930 
932 {
933  if ( !mLayer || !style )
934  return 0;
935 
938  : QgsSymbol::Fill;
939 
940  QVariantList unmatchedCategories;
941  QStringList unmatchedSymbols;
942  const int matched = mRenderer->matchToSymbols( style, type, unmatchedCategories, unmatchedSymbols );
943 
944  mModel->updateSymbology();
945  return matched;
946 }
947 
949 {
950  QgsSettings settings;
951  QString openFileDir = settings.value( QStringLiteral( "UI/lastMatchToSymbolsDir" ), QDir::homePath() ).toString();
952 
953  QString fileName = QFileDialog::getOpenFileName( this, tr( "Match to Symbols from File" ), openFileDir,
954  tr( "XML files (*.xml *.XML)" ) );
955  if ( fileName.isEmpty() )
956  {
957  return;
958  }
959 
960  QFileInfo openFileInfo( fileName );
961  settings.setValue( QStringLiteral( "UI/lastMatchToSymbolsDir" ), openFileInfo.absolutePath() );
962 
963  QgsStyle importedStyle;
964  if ( !importedStyle.importXml( fileName ) )
965  {
966  QMessageBox::warning( this, tr( "Match to Symbols from File" ),
967  tr( "An error occurred while reading file:\n%1" ).arg( importedStyle.errorString() ) );
968  return;
969  }
970 
971  int matched = matchToSymbols( &importedStyle );
972  if ( matched > 0 )
973  {
974  QMessageBox::information( this, tr( "Match to Symbols from File" ),
975  tr( "Matched %1 categories to symbols from file." ).arg( matched ) );
976  }
977  else
978  {
979  QMessageBox::warning( this, tr( "Match to Symbols from File" ),
980  tr( "No categories could be matched to symbols in file." ) );
981  }
982 }
983 
984 void QgsCategorizedSymbolRendererWidget::cleanUpSymbolSelector( QgsPanelWidget *container )
985 {
986  QgsSymbolSelectorWidget *dlg = qobject_cast<QgsSymbolSelectorWidget *>( container );
987  if ( !dlg )
988  return;
989 
990  delete dlg->symbol();
991 }
992 
993 void QgsCategorizedSymbolRendererWidget::updateSymbolsFromWidget()
994 {
995  QgsSymbolSelectorWidget *dlg = qobject_cast<QgsSymbolSelectorWidget *>( sender() );
996  mCategorizedSymbol.reset( dlg->symbol()->clone() );
997 
999 }
1000 
1002 {
1003  // When there is a selection, change the selected symbols only
1004  QItemSelectionModel *m = viewCategories->selectionModel();
1005  QModelIndexList i = m->selectedRows();
1006 
1007  if ( m && !i.isEmpty() )
1008  {
1009  QList<int> selectedCats = selectedCategories();
1010 
1011  if ( !selectedCats.isEmpty() )
1012  {
1013  Q_FOREACH ( int idx, selectedCats )
1014  {
1015  QgsSymbol *newCatSymbol = mCategorizedSymbol->clone();
1016  if ( selectedCats.count() > 1 )
1017  {
1018  //if updating multiple categories, retain the existing category colors
1019  newCatSymbol->setColor( mRenderer->categories().at( idx ).symbol()->color() );
1020  }
1021  mRenderer->updateCategorySymbol( idx, newCatSymbol );
1022  }
1023  emit widgetChanged();
1024  }
1025  }
1026  else
1027  {
1028  mRenderer->updateSymbols( mCategorizedSymbol.get() );
1029  }
1030 
1031  emit widgetChanged();
1032 }
1033 
1035 {
1036  if ( !event )
1037  {
1038  return;
1039  }
1040 
1041  if ( event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier )
1042  {
1043  mCopyBuffer.clear();
1044  mCopyBuffer = selectedCategoryList();
1045  }
1046  else if ( event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier )
1047  {
1048  QgsCategoryList::const_iterator rIt = mCopyBuffer.constBegin();
1049  for ( ; rIt != mCopyBuffer.constEnd(); ++rIt )
1050  {
1051  mModel->addCategory( *rIt );
1052  }
1053  }
1054 }
1055 
1056 QgsExpressionContext QgsCategorizedSymbolRendererWidget::createExpressionContext() const
1057 {
1058  QgsExpressionContext expContext;
1062 
1063  if ( mContext.mapCanvas() )
1064  {
1067  }
1068  else
1069  {
1071  }
1072 
1073  if ( vectorLayer() )
1075 
1076  // additional scopes
1078  {
1079  expContext.appendScope( new QgsExpressionContextScope( scope ) );
1080  }
1081 
1082  return expContext;
1083 }
1084 
1085 void QgsCategorizedSymbolRendererWidget::dataDefinedSizeLegend()
1086 {
1087  QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( mCategorizedSymbol.get() ); // this should be only enabled for marker symbols
1088  QgsDataDefinedSizeLegendWidget *panel = createDataDefinedSizeLegendWidget( s, mRenderer->dataDefinedSizeLegend() );
1089  if ( panel )
1090  {
1091  connect( panel, &QgsPanelWidget::widgetChanged, this, [ = ]
1092  {
1093  mRenderer->setDataDefinedSizeLegend( panel->dataDefinedSizeLegend() );
1094  emit widgetChanged();
1095  } );
1096  openPanel( panel ); // takes ownership of the panel
1097  }
1098 }
QgsFeatureRenderer * renderer() override
Returns pointer to the renderer (no transfer of ownership)
Class for parsing and evaluation of expressions (formerly called "search strings").
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
Wrapper for iterator of features from vector data provider or vector layer.
The QgsFieldExpressionWidget class reates a widget to choose fields and edit expressions It contains ...
void applyColorRamp()
Applies the color ramp passed on by the color ramp button.
const QgsVectorLayer * vectorLayer() const
Returns the vector layer associated with the widget.
bool dockMode()
Returns the dock mode state.
Represents an individual category (class) from a QgsCategorizedSymbolRenderer.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:61
QgsSymbol * symbol()
Returns the symbol that is currently active in the widget.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
void applyChangeToSymbol()
Applies current symbol to selected categories, or to all categories if none is selected.
void showSymbolLevelsDialog(QgsFeatureRenderer *r)
show a dialog with renderer&#39;s symbol level settings
void colorRampChanged()
Emitted whenever a new color ramp is set for the button.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly...
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
Base class for renderer settings widgets.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
void changeSelectedSymbols()
Changes the selected symbols alone for the change button, if there is a selection.
QgsVectorLayer * mLayer
QVariant evaluate()
Evaluate the feature and return the result.
void matchToSymbolsFromLibrary()
Replaces category symbols with the symbols from the users&#39; symbol library that have a matching name...
QColor color(double value) const override
Returns the color corresponding to a specified value.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
void matchToSymbolsFromXml()
Prompts for selection of an xml file, then replaces category symbols with the symbols from the XML fi...
Base class for any widget that can be shown as a inline panel.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
int currentCategoryRow()
Returns row index for the currently selected category (-1 if on no selection)
Line symbol.
Definition: qgssymbol.h:86
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
A QProxyStyle subclass which correctly sets the base style to match the QGIS application style...
Definition: qgsproxystyle.h:30
QString readEntry(const QString &scope, const QString &key, const QString &def=QString(), bool *ok=nullptr) const
void panelAccepted(QgsPanelWidget *panel)
Emitted when the panel is accepted by the user.
QgsMapCanvas * mapCanvas() const
Returns the map canvas associated with the widget.
A marker symbol type, for rendering Point and MultiPoint geometries.
Definition: qgssymbol.h:732
static QgsStyle * defaultStyle()
Returns default application-wide style.
Definition: qgsstyle.cpp:46
void setContext(const QgsSymbolWidgetContext &context)
Sets the context in which the symbol widget is shown, e.g., the associated map canvas and expression ...
The QgsMapSettings class contains configuration for rendering of the map.
QList< QgsRendererCategory > QgsCategoryList
SymbolType
Type of the symbol.
Definition: qgssymbol.h:83
const QgsCategoryList & categories() const
Returns a list of all categories recognized by the renderer.
static QgsSymbol * defaultSymbol(QgsWkbTypes::GeometryType geomType)
Returns a new default symbol for the specified geometry type.
Definition: qgssymbol.cpp:275
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context...
QgsFields fields() const FINAL
Returns the list of fields of this layer.
static QIcon symbolPreviewIcon(QgsSymbol *symbol, QSize size, int padding=0)
Returns an icon preview for a color ramp.
bool importXml(const QString &filename)
Imports the symbols and colorramps into the default style database from the given XML file...
Definition: qgsstyle.cpp:1568
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget...
Widget for configuration of appearance of legend for marker symbols with data-defined size...
QList< int > selectedCategories()
Returns a list of indexes for the categories under selection.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
std::unique_ptr< QgsCategorizedSymbolRenderer > mRenderer
QgsDataDefinedSizeLegendWidget * createDataDefinedSizeLegendWidget(const QgsMarkerSymbol *symbol, const QgsDataDefinedSizeLegend *ddsLegend)
Creates widget to setup data-defined size legend.
Symbol selector widget that can be used to select and build a symbol.
int lookupField(const QString &fieldName) const
Looks up field&#39;s index from the field name.
Definition: qgsfields.cpp:320
Single scope for storing variables and functions for use within a QgsExpressionContext.
QgsDataDefinedSizeLegend * dataDefinedSizeLegend() const
Returns configuration as set up in the dialog (may be null). Ownership is passed to the caller...
virtual void setTotalColorCount(int colorCount)
Sets the desired total number of unique colors for the resultant ramp.
void widgetChanged()
Emitted when the widget state changes.
QList< QgsSymbol * > selectedSymbols() override
Subclasses may provide the capability of changing multiple symbols at once by implementing the follow...
Totally random color ramp.
Definition: qgscolorramp.h:427
int matchToSymbols(QgsStyle *style)
Replaces category symbols with the symbols from a style that have a matching name.
static QgsRendererWidget * create(QgsVectorLayer *layer, QgsStyle *style, QgsFeatureRenderer *renderer)
void fieldChanged(const QString &fieldName)
the signal is emitted when the currently selected field changes
static void sortVariantList(QList< QVariant > &list, Qt::SortOrder order)
Sorts the passed list in requested order.
Marker symbol.
Definition: qgssymbol.h:85
Fill symbol.
Definition: qgssymbol.h:87
void setContext(const QgsSymbolWidgetContext &context)
Sets the context in which the symbol widget is shown, e.g., the associated map canvas and expression ...
static QgsExpressionContextScope * atlasScope(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...
QgsSymbolWidgetContext mContext
Context in which widget is shown.
QgsCategorizedSymbolRendererWidget(QgsVectorLayer *layer, QgsStyle *style, QgsFeatureRenderer *renderer)
QgsExpressionContextScope & expressionContextScope()
Returns a reference to the expression context scope for the map canvas.
Definition: qgsmapcanvas.h:560
QSet< QVariant > uniqueValues(int fieldIndex, int limit=-1) const FINAL
Calculates a list of unique values contained within an attribute in the layer.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
bool prepare(const QgsExpressionContext *context)
Gets the expression ready for evaluation - find out column indexes.
virtual QgsSymbol * clone() const =0
Returns a deep copy of this symbol.
QgsSymbol * symbol() const
Returns the symbol which will be used to render this category.
QVariant value() const
Returns the value corresponding to this category.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:411
void contextMenuViewCategories(QPoint p)
QString label() const
Returns the label for this category, which is used to represent the category within legends and the l...
static QgsCategorizedSymbolRenderer * convertFromRenderer(const QgsFeatureRenderer *renderer)
creates a QgsCategorizedSymbolRenderer from an existing renderer.
QList< QgsExpressionContextScope > additionalExpressionContextScopes() const
Returns the list of additional expression context scopes to show as available within the layer...
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Query the layer for features specified in request.
QString errorString()
Returns last error from load/save operation.
Definition: qgsstyle.h:384
QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
bool nextFeature(QgsFeature &f)
QgsSymbolWidgetContext context() const
Returns the context in which the renderer widget is shown, e.g., the associated map canvas and expres...
Represents a vector layer which manages a vector based data sets.
bool renderState() const
Returns true if the category is currently enabled and should be rendered.
void setColor(const QColor &color)
Sets the color for the symbol.
Definition: qgssymbol.cpp:450