QGIS API Documentation  3.0.2-Girona (307d082)
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( QString( "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  QgsDebugMsg( "Done" );
359 }
360 
361 void QgsCategorizedSymbolRendererModel::updateSymbology()
362 {
363  emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
364 }
365 
366 // ------------------------------ View style --------------------------------
367 QgsCategorizedSymbolRendererViewStyle::QgsCategorizedSymbolRendererViewStyle( QStyle *style )
368  : QProxyStyle( style )
369 {}
370 
371 void QgsCategorizedSymbolRendererViewStyle::drawPrimitive( PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget ) const
372 {
373  if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
374  {
375  QStyleOption opt( *option );
376  opt.rect.setLeft( 0 );
377  // draw always as line above, because we move item to that index
378  opt.rect.setHeight( 0 );
379  if ( widget ) opt.rect.setRight( widget->width() );
380  QProxyStyle::drawPrimitive( element, &opt, painter, widget );
381  return;
382  }
383  QProxyStyle::drawPrimitive( element, option, painter, widget );
384 }
385 
387 
388 // ------------------------------ Widget ------------------------------------
390 {
391  return new QgsCategorizedSymbolRendererWidget( layer, style, renderer );
392 }
393 
395  : QgsRendererWidget( layer, style )
396 
397 {
398 
399  // try to recognize the previous renderer
400  // (null renderer means "no previous renderer")
401  if ( renderer )
402  {
404  }
405  if ( !mRenderer )
406  {
407  mRenderer = new QgsCategorizedSymbolRenderer( QLatin1String( "" ), QgsCategoryList() );
408  }
409 
410  QString attrName = mRenderer->classAttribute();
411  mOldClassificationAttribute = attrName;
412 
413  // setup user interface
414  setupUi( this );
415  this->layout()->setContentsMargins( 0, 0, 0, 0 );
416 
417  mExpressionWidget->setLayer( mLayer );
418 
419  // initiate color ramp button to random
420  btnColorRamp->setShowRandomColorRamp( true );
421 
422  // set project default color ramp
423  QString defaultColorRamp = QgsProject::instance()->readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/ColorRamp" ), QLatin1String( "" ) );
424  if ( !defaultColorRamp.isEmpty() )
425  {
426  btnColorRamp->setColorRampFromName( defaultColorRamp );
427  }
428  else
429  {
430  btnColorRamp->setRandomColorRamp();
431  }
432 
434 
435  mModel = new QgsCategorizedSymbolRendererModel( this );
436  mModel->setRenderer( mRenderer );
437 
438  // update GUI from renderer
440 
441  viewCategories->setModel( mModel );
442  viewCategories->resizeColumnToContents( 0 );
443  viewCategories->resizeColumnToContents( 1 );
444  viewCategories->resizeColumnToContents( 2 );
445 
446  viewCategories->setStyle( new QgsCategorizedSymbolRendererViewStyle( viewCategories->style() ) );
447 
448  connect( mModel, &QgsCategorizedSymbolRendererModel::rowsMoved, this, &QgsCategorizedSymbolRendererWidget::rowsMoved );
449  connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsPanelWidget::widgetChanged );
450 
451  connect( mExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsCategorizedSymbolRendererWidget::categoryColumnChanged );
452 
453  connect( viewCategories, &QAbstractItemView::doubleClicked, this, &QgsCategorizedSymbolRendererWidget::categoriesDoubleClicked );
454  connect( viewCategories, &QTreeView::customContextMenuRequested, this, &QgsCategorizedSymbolRendererWidget::contextMenuViewCategories );
455 
456  connect( btnChangeCategorizedSymbol, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::changeCategorizedSymbol );
457  connect( btnAddCategories, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::addCategories );
458  connect( btnDeleteCategories, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::deleteCategories );
459  connect( btnDeleteAllCategories, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::deleteAllCategories );
460  connect( btnAddCategory, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::addCategory );
461 
463 
464  // menus for data-defined rotation/size
465  QMenu *advMenu = new QMenu;
466 
467  advMenu->addAction( tr( "Match to saved symbols" ), this, SLOT( matchToSymbolsFromLibrary() ) );
468  advMenu->addAction( tr( "Match to symbols from file…" ), this, SLOT( matchToSymbolsFromXml() ) );
469  advMenu->addAction( tr( "Symbol levels…" ), this, SLOT( showSymbolLevels() ) );
471  {
472  QAction *actionDdsLegend = advMenu->addAction( tr( "Data-defined size legend…" ) );
473  // only from Qt 5.6 there is convenience addAction() with new style connection
474  connect( actionDdsLegend, &QAction::triggered, this, &QgsCategorizedSymbolRendererWidget::dataDefinedSizeLegend );
475  }
476 
477  btnAdvanced->setMenu( advMenu );
478 
479  mExpressionWidget->registerExpressionContextGenerator( this );
480 }
481 
483 {
484  delete mRenderer;
485  delete mModel;
486  delete mCategorizedSymbol;
487 }
488 
490 {
491  // Note: This assumes that the signals for UI element changes have not
492  // yet been connected, so that the updates to color ramp, symbol, etc
493  // don't override existing customisations.
494 
496 
497  //mModel->setRenderer ( mRenderer ); // necessary?
498 
499  // set column
500  QString attrName = mRenderer->classAttribute();
501  mExpressionWidget->setField( attrName );
502 
503  // set source symbol
504  if ( mRenderer->sourceSymbol() )
505  {
506  delete mCategorizedSymbol;
509  }
510 
511  // if a color ramp attached to the renderer, enable the color ramp button
512  if ( mRenderer->sourceColorRamp() )
513  {
514  btnColorRamp->setColorRamp( mRenderer->sourceColorRamp() );
515  }
516 }
517 
519 {
520  return mRenderer;
521 }
522 
524 {
525  QList<int> selectedCats = selectedCategories();
526 
527  if ( !selectedCats.isEmpty() )
528  {
529  QgsSymbol *newSymbol = mCategorizedSymbol->clone();
530  QgsSymbolSelectorDialog dlg( newSymbol, mStyle, mLayer, this );
531  dlg.setContext( context() );
532  if ( !dlg.exec() )
533  {
534  delete newSymbol;
535  return;
536  }
537 
538  Q_FOREACH ( int idx, selectedCats )
539  {
540  QgsRendererCategory category = mRenderer->categories().value( idx );
541 
542  QgsSymbol *newCatSymbol = newSymbol->clone();
543  newCatSymbol->setColor( mRenderer->categories()[idx].symbol()->color() );
544  mRenderer->updateCategorySymbol( idx, newCatSymbol );
545  }
546  }
547 }
548 
550 {
551  QgsSymbol *newSymbol = mCategorizedSymbol->clone();
552  QgsSymbolSelectorWidget *dlg = new QgsSymbolSelectorWidget( newSymbol, mStyle, mLayer, nullptr );
553  dlg->setContext( mContext );
554 
555  connect( dlg, &QgsPanelWidget::widgetChanged, this, &QgsCategorizedSymbolRendererWidget::updateSymbolsFromWidget );
556  connect( dlg, &QgsPanelWidget::panelAccepted, this, &QgsCategorizedSymbolRendererWidget::cleanUpSymbolSelector );
557  openPanel( dlg );
558 }
559 
561 {
562  QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( mCategorizedSymbol, btnChangeCategorizedSymbol->iconSize() );
563  btnChangeCategorizedSymbol->setIcon( icon );
564 }
565 
567 {
568 }
569 
571 {
572  mRenderer->setClassAttribute( field );
573  emit widgetChanged();
574 }
575 
577 {
578  if ( idx.isValid() && idx.column() == 0 )
580 }
581 
583 {
585 
586  QgsSymbol *symbol = category.symbol();
587  if ( symbol )
588  {
589  symbol = symbol->clone();
590  }
591  else
592  {
594  }
595 
596  QgsSymbolSelectorWidget *dlg = new QgsSymbolSelectorWidget( symbol, mStyle, mLayer, nullptr );
597  dlg->setContext( mContext );
598  connect( dlg, &QgsPanelWidget::widgetChanged, this, &QgsCategorizedSymbolRendererWidget::updateSymbolsFromWidget );
599  connect( dlg, &QgsPanelWidget::panelAccepted, this, &QgsCategorizedSymbolRendererWidget::cleanUpSymbolSelector );
600  openPanel( dlg );
601 }
602 
603 static void _createCategories( QgsCategoryList &cats, QList<QVariant> &values, QgsSymbol *symbol )
604 {
605  // sort the categories first
606  QgsSymbolLayerUtils::sortVariantList( values, Qt::AscendingOrder );
607 
608  int num = values.count();
609 
610  for ( int i = 0; i < num; i++ )
611  {
612  QVariant value = values[i];
613  QgsSymbol *newSymbol = symbol->clone();
614  if ( ! value.isNull() )
615  {
616  cats.append( QgsRendererCategory( value, newSymbol, value.toString(), true ) );
617  }
618  }
619 
620  // add null (default) value
621  QgsSymbol *newSymbol = symbol->clone();
622  cats.append( QgsRendererCategory( QVariant( "" ), newSymbol, QString(), true ) );
623 }
624 
625 
627 {
628  QString attrName = mExpressionWidget->currentField();
629  int idx = mLayer->fields().lookupField( attrName );
630  QList<QVariant> unique_vals;
631  if ( idx == -1 )
632  {
633  // Lets assume it's an expression
634  QgsExpression *expression = new QgsExpression( attrName );
640 
641  expression->prepare( &context );
643  QgsFeature feature;
644  while ( fit.nextFeature( feature ) )
645  {
646  context.setFeature( feature );
647  QVariant value = expression->evaluate( &context );
648  if ( unique_vals.contains( value ) )
649  continue;
650  unique_vals << value;
651  }
652  }
653  else
654  {
655  unique_vals = mLayer->uniqueValues( idx ).toList();
656  }
657 
658  // ask to abort if too many classes
659  if ( unique_vals.size() >= 1000 )
660  {
661  int res = QMessageBox::warning( nullptr, tr( "Classify Categories" ),
662  tr( "High number of classes. Classification would yield %1 entries which might not be expected. Continue?" ).arg( unique_vals.size() ),
663  QMessageBox::Ok | QMessageBox::Cancel,
664  QMessageBox::Cancel );
665  if ( res == QMessageBox::Cancel )
666  {
667  return;
668  }
669  }
670 
671 #if 0
672  DlgAddCategories dlg( mStyle, createDefaultSymbol(), unique_vals, this );
673  if ( !dlg.exec() )
674  return;
675 #endif
676 
677  QgsCategoryList cats;
678  _createCategories( cats, unique_vals, mCategorizedSymbol );
679  bool deleteExisting = false;
680 
681  if ( !mOldClassificationAttribute.isEmpty() &&
682  attrName != mOldClassificationAttribute &&
683  !mRenderer->categories().isEmpty() )
684  {
685  int res = QMessageBox::question( this,
686  tr( "Delete Classification" ),
687  tr( "The classification field was changed from '%1' to '%2'.\n"
688  "Should the existing classes be deleted before classification?" )
689  .arg( mOldClassificationAttribute, attrName ),
690  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel );
691  if ( res == QMessageBox::Cancel )
692  {
693  return;
694  }
695 
696  deleteExisting = ( res == QMessageBox::Yes );
697  }
698 
699  // First element to apply coloring to
700  bool keepExistingColors = false;
701  if ( !deleteExisting )
702  {
703  QgsCategoryList prevCats = mRenderer->categories();
704  keepExistingColors = !prevCats.isEmpty();
705  for ( int i = 0; i < cats.size(); ++i )
706  {
707  bool contains = false;
708  QVariant value = cats.at( i ).value();
709  for ( int j = 0; j < prevCats.size() && !contains; ++j )
710  {
711  if ( prevCats.at( j ).value() == value )
712  {
713  contains = true;
714  break;
715  }
716  }
717 
718  if ( !contains )
719  prevCats.append( cats.at( i ) );
720  }
721  cats = prevCats;
722  }
723 
724  mOldClassificationAttribute = attrName;
725 
726  // TODO: if not all categories are desired, delete some!
727  /*
728  if (not dlg.readAllCats.isChecked())
729  {
730  cats2 = {}
731  for item in dlg.listCategories.selectedItems():
732  for k,c in cats.iteritems():
733  if item.text() == k.toString():
734  break
735  cats2[k] = c
736  cats = cats2
737  }
738  */
739 
740  // recreate renderer
743  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
744  if ( ramp )
745  r->setSourceColorRamp( ramp->clone() );
746 
747  if ( mModel )
748  {
749  mModel->setRenderer( r );
750  }
751  delete mRenderer;
752  mRenderer = r;
753  if ( ! keepExistingColors && ramp )
754  applyColorRamp();
755  emit widgetChanged();
756 }
757 
759 {
760  if ( !btnColorRamp->isNull() )
761  {
762  mRenderer->updateColorRamp( btnColorRamp->colorRamp() );
763  }
764  mModel->updateSymbology();
765 }
766 
768 {
769  QModelIndex idx = viewCategories->selectionModel()->currentIndex();
770  if ( !idx.isValid() )
771  return -1;
772  return idx.row();
773 }
774 
776 {
777  QList<int> rows;
778  QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
779 
780  Q_FOREACH ( const QModelIndex &r, selectedRows )
781  {
782  if ( r.isValid() )
783  {
784  rows.append( r.row() );
785  }
786  }
787  return rows;
788 }
789 
791 {
792  QList<int> categoryIndexes = selectedCategories();
793  mModel->deleteRows( categoryIndexes );
794  emit widgetChanged();
795 }
796 
798 {
799  mModel->removeAllRows();
800  emit widgetChanged();
801 }
802 
804 {
805  if ( !mModel ) return;
807  QgsRendererCategory cat( QString(), symbol, QString(), true );
808  mModel->addCategory( cat );
809  emit widgetChanged();
810 }
811 
813 {
814  QList<QgsSymbol *> selectedSymbols;
815 
816  QItemSelectionModel *m = viewCategories->selectionModel();
817  QModelIndexList selectedIndexes = m->selectedRows( 1 );
818 
819  if ( m && !selectedIndexes.isEmpty() )
820  {
821  const QgsCategoryList &categories = mRenderer->categories();
822  QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
823  for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
824  {
825  int row = ( *indexIt ).row();
826  QgsSymbol *s = categories[row].symbol();
827  if ( s )
828  {
829  selectedSymbols.append( s );
830  }
831  }
832  }
833  return selectedSymbols;
834 }
835 
837 {
838  QgsCategoryList cl;
839 
840  QItemSelectionModel *m = viewCategories->selectionModel();
841  QModelIndexList selectedIndexes = m->selectedRows( 1 );
842 
843  if ( m && !selectedIndexes.isEmpty() )
844  {
845  QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
846  for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
847  {
848  cl.append( mModel->category( *indexIt ) );
849  }
850  }
851  return cl;
852 }
853 
855 {
857  emit widgetChanged();
858 }
859 
861 {
863 }
864 
866 {
867  viewCategories->selectionModel()->clear();
868 }
869 
871 {
872  int matched = matchToSymbols( QgsStyle::defaultStyle() );
873  if ( matched > 0 )
874  {
875  QMessageBox::information( this, tr( "Matched Symbols" ),
876  tr( "Matched %1 categories to symbols." ).arg( matched ) );
877  }
878  else
879  {
880  QMessageBox::warning( this, tr( "Matched Symbols" ),
881  tr( "No categories could be matched to symbols in library." ) );
882  }
883 }
884 
886 {
887  if ( !mLayer || !style )
888  return 0;
889 
890  int matched = 0;
891  for ( int catIdx = 0; catIdx < mRenderer->categories().count(); ++catIdx )
892  {
893  QString val = mRenderer->categories().at( catIdx ).value().toString();
894  QgsSymbol *symbol = style->symbol( val );
895  if ( symbol &&
898  || ( symbol->type() == QgsSymbol::Fill && mLayer->geometryType() == QgsWkbTypes::PolygonGeometry ) ) )
899  {
900  matched++;
901  mRenderer->updateCategorySymbol( catIdx, symbol->clone() );
902  }
903  }
904  mModel->updateSymbology();
905  return matched;
906 }
907 
909 {
910  QgsSettings settings;
911  QString openFileDir = settings.value( QStringLiteral( "UI/lastMatchToSymbolsDir" ), QDir::homePath() ).toString();
912 
913  QString fileName = QFileDialog::getOpenFileName( this, tr( "Match to Symbols from File" ), openFileDir,
914  tr( "XML files (*.xml *XML)" ) );
915  if ( fileName.isEmpty() )
916  {
917  return;
918  }
919 
920  QFileInfo openFileInfo( fileName );
921  settings.setValue( QStringLiteral( "UI/lastMatchToSymbolsDir" ), openFileInfo.absolutePath() );
922 
923  QgsStyle importedStyle;
924  if ( !importedStyle.importXml( fileName ) )
925  {
926  QMessageBox::warning( this, tr( "Match to Symbols from File" ),
927  tr( "An error occurred while reading file:\n%1" ).arg( importedStyle.errorString() ) );
928  return;
929  }
930 
931  int matched = matchToSymbols( &importedStyle );
932  if ( matched > 0 )
933  {
934  QMessageBox::information( this, tr( "Match to Symbols from File" ),
935  tr( "Matched %1 categories to symbols from file." ).arg( matched ) );
936  }
937  else
938  {
939  QMessageBox::warning( this, tr( "Match to Symbols from File" ),
940  tr( "No categories could be matched to symbols in file." ) );
941  }
942 }
943 
944 void QgsCategorizedSymbolRendererWidget::cleanUpSymbolSelector( QgsPanelWidget *container )
945 {
946  QgsSymbolSelectorWidget *dlg = qobject_cast<QgsSymbolSelectorWidget *>( container );
947  if ( !dlg )
948  return;
949 
950  delete dlg->symbol();
951 }
952 
953 void QgsCategorizedSymbolRendererWidget::updateSymbolsFromWidget()
954 {
955  QgsSymbolSelectorWidget *dlg = qobject_cast<QgsSymbolSelectorWidget *>( sender() );
956  delete mCategorizedSymbol;
957  mCategorizedSymbol = dlg->symbol()->clone();
958 
960 
961  // When there is a slection, change the selected symbols alone
962  QItemSelectionModel *m = viewCategories->selectionModel();
963  QModelIndexList i = m->selectedRows();
964 
965  if ( m && !i.isEmpty() )
966  {
967  QList<int> selectedCats = selectedCategories();
968 
969  if ( !selectedCats.isEmpty() )
970  {
971  Q_FOREACH ( int idx, selectedCats )
972  {
973  QgsSymbol *newCatSymbol = mCategorizedSymbol->clone();
974  if ( selectedCats.count() > 1 )
975  {
976  //if updating multiple categories, retain the existing category colors
977  newCatSymbol->setColor( mRenderer->categories().at( idx ).symbol()->color() );
978  }
979  mRenderer->updateCategorySymbol( idx, newCatSymbol );
980  }
981  emit widgetChanged();
982  }
983  return;
984  }
985 
986  mRenderer->updateSymbols( mCategorizedSymbol );
987  emit widgetChanged();
988 }
989 
991 {
992  if ( !event )
993  {
994  return;
995  }
996 
997  if ( event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier )
998  {
999  mCopyBuffer.clear();
1000  mCopyBuffer = selectedCategoryList();
1001  }
1002  else if ( event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier )
1003  {
1004  QgsCategoryList::const_iterator rIt = mCopyBuffer.constBegin();
1005  for ( ; rIt != mCopyBuffer.constEnd(); ++rIt )
1006  {
1007  mModel->addCategory( *rIt );
1008  }
1009  }
1010 }
1011 
1012 QgsExpressionContext QgsCategorizedSymbolRendererWidget::createExpressionContext() const
1013 {
1014  QgsExpressionContext expContext;
1018 
1019  if ( mContext.mapCanvas() )
1020  {
1023  }
1024  else
1025  {
1027  }
1028 
1029  if ( vectorLayer() )
1031 
1032  // additional scopes
1034  {
1035  expContext.appendScope( new QgsExpressionContextScope( scope ) );
1036  }
1037 
1038  return expContext;
1039 }
1040 
1041 void QgsCategorizedSymbolRendererWidget::dataDefinedSizeLegend()
1042 {
1043  QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( mCategorizedSymbol ); // this should be only enabled for marker symbols
1045  if ( panel )
1046  {
1047  connect( panel, &QgsPanelWidget::widgetChanged, this, [ = ]
1048  {
1050  emit widgetChanged();
1051  } );
1052  openPanel( panel ); // takes ownership of the panel
1053  }
1054 }
int lookupField(const QString &fieldName) const
Look up field&#39;s index from the field name.
Definition: qgsfields.cpp:299
QgsFeatureRenderer * renderer() override
return pointer to the renderer (no transfer of ownership)
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.
QString readEntry(const QString &scope, const QString &key, const QString &def=QString(), bool *ok=nullptr) const
void updateColorRamp(QgsColorRamp *ramp)
Update the color ramp used and all symbols colors.
QgsSymbol * symbol()
Return the symbol that is currently active in the widget.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:57
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.
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()
change the selected symbols alone for the change button, if there is a selection
QgsVectorLayer * mLayer
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()
return 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
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
void setValue(const QString &key, const QVariant &value, const QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
QgsDataDefinedSizeLegend * dataDefinedSizeLegend() const
Returns configuration of appearance of legend when using data-defined size for marker symbols...
static QgsSymbol * defaultSymbol(QgsWkbTypes::GeometryType geomType)
return new default symbol for specified geometry type
Definition: qgssymbol.cpp:266
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:1468
const QgsCategoryList & categories() const
Widget for configuration of appearance of legend for marker symbols with data-defined size...
QList< int > selectedCategories()
return a list of indexes for the categories unders 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.
void setClassAttribute(const QString &attr)
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.
QgsSymbol * sourceSymbol()
Returns the renderer&#39;s source symbol, which is the base symbol used for the each categories&#39; symbol b...
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
void setDataDefinedSizeLegend(QgsDataDefinedSizeLegend *settings)
Configures appearance of legend when renderer is configured to use data-defined size for marker symbo...
const QgsMapSettings & mapSettings() const
Get 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 ...
void setSourceColorRamp(QgsColorRamp *ramp)
Sets the source color ramp.
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:563
QgsColorRamp * sourceColorRamp()
Returns the source color ramp, from which each categories&#39; color is derived.
SymbolType type() const
Definition: qgssymbol.h:113
QgsDataDefinedSizeLegend * dataDefinedSizeLegend() const
Returns configuration as set up in the dialog (may be null). Ownership is passed to the caller...
virtual QgsSymbol * clone() const =0
Get a deep copy of this symbol.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), const Section section=NoSection) const
Returns the value for setting key.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:383
void contextMenuViewCategories(QPoint p)
bool updateCategorySymbol(int catIndex, QgsSymbol *symbol)
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:353
QgsSymbol * symbol(const QString &name)
Returns a NEW copy of symbol.
Definition: qgsstyle.cpp:164
void updateSymbols(QgsSymbol *sym)
Update all the symbols but leave categories and colors.
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.
void setSourceSymbol(QgsSymbol *sym)
Sets the source symbol for the renderer, which is the base symbol used for the each categories&#39; symbo...
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:445