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