QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
qgscategorizedsymbolrendererv2widget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscategorizedsymbolrendererv2widget.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 
21 #include "qgssymbolv2.h"
22 #include "qgssymbollayerv2utils.h"
23 #include "qgsvectorcolorrampv2.h"
24 #include "qgsstylev2.h"
25 
28 
29 #include "qgsvectorlayer.h"
30 
31 #include "qgsproject.h"
32 #include "qgsexpression.h"
33 #include "qgsmapcanvas.h"
34 
35 #include <QKeyEvent>
36 #include <QMenu>
37 #include <QMessageBox>
38 #include <QStandardItemModel>
39 #include <QStandardItem>
40 #include <QPen>
41 #include <QPainter>
42 #include <QFileDialog>
43 
45 
46 QgsCategorizedSymbolRendererV2Model::QgsCategorizedSymbolRendererV2Model( QObject * parent ) : QAbstractItemModel( parent )
47  , mRenderer( nullptr )
48  , mMimeFormat( "application/x-qgscategorizedsymbolrendererv2model" )
49 {
50 }
51 
52 void QgsCategorizedSymbolRendererV2Model::setRenderer( QgsCategorizedSymbolRendererV2* renderer )
53 {
54  if ( mRenderer )
55  {
56  beginRemoveRows( QModelIndex(), 0, mRenderer->categories().size() - 1 );
57  mRenderer = nullptr;
58  endRemoveRows();
59  }
60  if ( renderer )
61  {
62  beginInsertRows( QModelIndex(), 0, renderer->categories().size() - 1 );
63  mRenderer = renderer;
64  endInsertRows();
65  }
66 }
67 
68 void QgsCategorizedSymbolRendererV2Model::addCategory( const QgsRendererCategoryV2 &cat )
69 {
70  if ( !mRenderer ) return;
71  int idx = mRenderer->categories().size();
72  beginInsertRows( QModelIndex(), idx, idx );
73  mRenderer->addCategory( cat );
74  endInsertRows();
75 }
76 
77 QgsRendererCategoryV2 QgsCategorizedSymbolRendererV2Model::category( const QModelIndex &index )
78 {
79  if ( !mRenderer )
80  {
81  return QgsRendererCategoryV2();
82  }
83  const QgsCategoryList& catList = mRenderer->categories();
84  int row = index.row();
85  if ( row >= catList.size() )
86  {
87  return QgsRendererCategoryV2();
88  }
89  return catList.at( row );
90 }
91 
92 
93 Qt::ItemFlags QgsCategorizedSymbolRendererV2Model::flags( const QModelIndex & index ) const
94 {
95  if ( !index.isValid() )
96  {
97  return Qt::ItemIsDropEnabled;
98  }
99 
100  Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable;
101  if ( index.column() == 1 || index.column() == 2 )
102  {
103  flags |= Qt::ItemIsEditable;
104  }
105  return flags;
106 }
107 
108 Qt::DropActions QgsCategorizedSymbolRendererV2Model::supportedDropActions() const
109 {
110  return Qt::MoveAction;
111 }
112 
113 QVariant QgsCategorizedSymbolRendererV2Model::data( const QModelIndex &index, int role ) const
114 {
115  if ( !index.isValid() || !mRenderer )
116  return QVariant();
117 
118  const QgsRendererCategoryV2 category = mRenderer->categories().value( index.row() );
119 
120  if ( role == Qt::CheckStateRole && index.column() == 0 )
121  {
122  return category.renderState() ? Qt::Checked : Qt::Unchecked;
123  }
124  else if ( role == Qt::DisplayRole || role == Qt::ToolTipRole )
125  {
126  switch ( index.column() )
127  {
128  case 1:
129  return category.value().toString();
130  case 2:
131  return category.label();
132  default:
133  return QVariant();
134  }
135  }
136  else if ( role == Qt::DecorationRole && index.column() == 0 && category.symbol() )
137  {
138  return QgsSymbolLayerV2Utils::symbolPreviewIcon( category.symbol(), QSize( 16, 16 ) );
139  }
140  else if ( role == Qt::TextAlignmentRole )
141  {
142  return ( index.column() == 0 ) ? Qt::AlignHCenter : Qt::AlignLeft;
143  }
144  else if ( role == Qt::EditRole )
145  {
146  switch ( index.column() )
147  {
148  case 1:
149  return category.value();
150  case 2:
151  return category.label();
152  default:
153  return QVariant();
154  }
155  }
156 
157  return QVariant();
158 }
159 
160 bool QgsCategorizedSymbolRendererV2Model::setData( const QModelIndex & index, const QVariant & value, int role )
161 {
162  if ( !index.isValid() )
163  return false;
164 
165  if ( index.column() == 0 && role == Qt::CheckStateRole )
166  {
167  mRenderer->updateCategoryRenderState( index.row(), value == Qt::Checked );
168  emit dataChanged( index, index );
169  return true;
170  }
171 
172  if ( role != Qt::EditRole )
173  return false;
174 
175  switch ( index.column() )
176  {
177  case 1: // value
178  {
179  // try to preserve variant type for this value
180  QVariant val;
181  switch ( mRenderer->categories().value( index.row() ).value().type() )
182  {
183  case QVariant::Int:
184  val = value.toInt();
185  break;
186  case QVariant::Double:
187  val = value.toDouble();
188  break;
189  default:
190  val = value.toString();
191  break;
192  }
193  mRenderer->updateCategoryValue( index.row(), val );
194  break;
195  }
196  case 2: // label
197  mRenderer->updateCategoryLabel( index.row(), value.toString() );
198  break;
199  default:
200  return false;
201  }
202 
203  emit dataChanged( index, index );
204  return true;
205 }
206 
207 QVariant QgsCategorizedSymbolRendererV2Model::headerData( int section, Qt::Orientation orientation, int role ) const
208 {
209  if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 3 )
210  {
211  QStringList lst;
212  lst << tr( "Symbol" ) << tr( "Value" ) << tr( "Legend" );
213  return lst.value( section );
214  }
215  return QVariant();
216 }
217 
218 int QgsCategorizedSymbolRendererV2Model::rowCount( const QModelIndex &parent ) const
219 {
220  if ( parent.isValid() || !mRenderer )
221  {
222  return 0;
223  }
224  return mRenderer->categories().size();
225 }
226 
227 int QgsCategorizedSymbolRendererV2Model::columnCount( const QModelIndex & index ) const
228 {
229  Q_UNUSED( index );
230  return 3;
231 }
232 
233 QModelIndex QgsCategorizedSymbolRendererV2Model::index( int row, int column, const QModelIndex &parent ) const
234 {
235  if ( hasIndex( row, column, parent ) )
236  {
237  return createIndex( row, column );
238  }
239  return QModelIndex();
240 }
241 
242 QModelIndex QgsCategorizedSymbolRendererV2Model::parent( const QModelIndex &index ) const
243 {
244  Q_UNUSED( index );
245  return QModelIndex();
246 }
247 
248 QStringList QgsCategorizedSymbolRendererV2Model::mimeTypes() const
249 {
250  QStringList types;
251  types << mMimeFormat;
252  return types;
253 }
254 
255 QMimeData *QgsCategorizedSymbolRendererV2Model::mimeData( const QModelIndexList &indexes ) const
256 {
257  QMimeData *mimeData = new QMimeData();
258  QByteArray encodedData;
259 
260  QDataStream stream( &encodedData, QIODevice::WriteOnly );
261 
262  // Create list of rows
263  Q_FOREACH ( const QModelIndex &index, indexes )
264  {
265  if ( !index.isValid() || index.column() != 0 )
266  continue;
267 
268  stream << index.row();
269  }
270  mimeData->setData( mMimeFormat, encodedData );
271  return mimeData;
272 }
273 
274 bool QgsCategorizedSymbolRendererV2Model::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
275 {
276  Q_UNUSED( row );
277  Q_UNUSED( column );
278  if ( action != Qt::MoveAction ) return true;
279 
280  if ( !data->hasFormat( mMimeFormat ) ) return false;
281 
282  QByteArray encodedData = data->data( mMimeFormat );
283  QDataStream stream( &encodedData, QIODevice::ReadOnly );
284 
285  QVector<int> rows;
286  while ( !stream.atEnd() )
287  {
288  int r;
289  stream >> r;
290  rows.append( r );
291  }
292 
293  int to = parent.row();
294  // to is -1 if dragged outside items, i.e. below any item,
295  // then move to the last position
296  if ( to == -1 ) to = mRenderer->categories().size(); // out of rang ok, will be decreased
297  for ( int i = rows.size() - 1; i >= 0; i-- )
298  {
299  QgsDebugMsg( QString( "move %1 to %2" ).arg( rows[i] ).arg( to ) );
300  int t = to;
301  // moveCategory first removes and then inserts
302  if ( rows[i] < t ) t--;
303  mRenderer->moveCategory( rows[i], t );
304  // current moved under another, shift its index up
305  for ( int j = 0; j < i; j++ )
306  {
307  if ( to < rows[j] && rows[i] > rows[j] ) rows[j] += 1;
308  }
309  // removed under 'to' so the target shifted down
310  if ( rows[i] < to ) to--;
311  }
312  emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
313  emit rowsMoved();
314  return false;
315 }
316 
317 void QgsCategorizedSymbolRendererV2Model::deleteRows( QList<int> rows )
318 {
319  qSort( rows ); // list might be unsorted, depending on how the user selected the rows
320  for ( int i = rows.size() - 1; i >= 0; i-- )
321  {
322  beginRemoveRows( QModelIndex(), rows[i], rows[i] );
323  mRenderer->deleteCategory( rows[i] );
324  endRemoveRows();
325  }
326 }
327 
328 void QgsCategorizedSymbolRendererV2Model::removeAllRows()
329 {
330  beginRemoveRows( QModelIndex(), 0, mRenderer->categories().size() - 1 );
331  mRenderer->deleteAllCategories();
332  endRemoveRows();
333 }
334 
335 void QgsCategorizedSymbolRendererV2Model::sort( int column, Qt::SortOrder order )
336 {
337  if ( column == 0 )
338  {
339  return;
340  }
341  if ( column == 1 )
342  {
343  mRenderer->sortByValue( order );
344  }
345  else if ( column == 2 )
346  {
347  mRenderer->sortByLabel( order );
348  }
349  emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
350  QgsDebugMsg( "Done" );
351 }
352 
353 void QgsCategorizedSymbolRendererV2Model::updateSymbology()
354 {
355  emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
356 }
357 
358 // ------------------------------ View style --------------------------------
359 QgsCategorizedSymbolRendererV2ViewStyle::QgsCategorizedSymbolRendererV2ViewStyle( QStyle* style )
360  : QProxyStyle( style )
361 {}
362 
363 void QgsCategorizedSymbolRendererV2ViewStyle::drawPrimitive( PrimitiveElement element, const QStyleOption * option, QPainter * painter, const QWidget * widget ) const
364 {
365  if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
366  {
367  QStyleOption opt( *option );
368  opt.rect.setLeft( 0 );
369  // draw always as line above, because we move item to that index
370  opt.rect.setHeight( 0 );
371  if ( widget ) opt.rect.setRight( widget->width() );
372  QProxyStyle::drawPrimitive( element, &opt, painter, widget );
373  return;
374  }
375  QProxyStyle::drawPrimitive( element, option, painter, widget );
376 }
377 
379 
380 // ------------------------------ Widget ------------------------------------
382 {
383  return new QgsCategorizedSymbolRendererV2Widget( layer, style, renderer );
384 }
385 
386 static QgsExpressionContext _getExpressionContext( const void* context )
387 {
389 
390  QgsExpressionContext expContext;
394 
395  if ( widget->mapCanvas() )
396  {
399  }
400  else
401  {
403  }
404 
405  if ( widget->vectorLayer() )
406  expContext << QgsExpressionContextUtils::layerScope( widget->vectorLayer() );
407 
408  return expContext;
409 }
410 
412  : QgsRendererV2Widget( layer, style )
413  , mRenderer( nullptr )
414  , mModel( nullptr )
415 {
416 
417  // try to recognize the previous renderer
418  // (null renderer means "no previous renderer")
419  if ( renderer )
420  {
422  }
423  if ( !mRenderer )
424  {
426  }
427 
428  QString attrName = mRenderer->classAttribute();
429  mOldClassificationAttribute = attrName;
430 
431  // setup user interface
432  setupUi( this );
433 
434  mExpressionWidget->setLayer( mLayer );
435 
436  cboCategorizedColorRamp->populate( mStyle );
437  int randomIndex = cboCategorizedColorRamp->findText( tr( "Random colors" ) );
438  if ( randomIndex != -1 )
439  {
440  cboCategorizedColorRamp->setCurrentIndex( randomIndex );
441  mButtonEditRamp->setEnabled( false );
442  }
443 
444  // set project default color ramp
445  QString defaultColorRamp = QgsProject::instance()->readEntry( "DefaultStyles", "/ColorRamp", "" );
446  if ( defaultColorRamp != "" )
447  {
448  int index = cboCategorizedColorRamp->findText( defaultColorRamp, Qt::MatchCaseSensitive );
449  if ( index >= 0 )
450  cboCategorizedColorRamp->setCurrentIndex( index );
451  }
452 
454 
455  mModel = new QgsCategorizedSymbolRendererV2Model( this );
456  mModel->setRenderer( mRenderer );
457 
458  // update GUI from renderer
460 
461  viewCategories->setModel( mModel );
462  viewCategories->resizeColumnToContents( 0 );
463  viewCategories->resizeColumnToContents( 1 );
464  viewCategories->resizeColumnToContents( 2 );
465 
466  viewCategories->setStyle( new QgsCategorizedSymbolRendererV2ViewStyle( viewCategories->style() ) );
467 
468  connect( mModel, SIGNAL( rowsMoved() ), this, SLOT( rowsMoved() ) );
469  connect( mModel, SIGNAL( dataChanged( QModelIndex, QModelIndex ) ), this, SIGNAL( widgetChanged() ) );
470 
471  connect( mExpressionWidget, SIGNAL( fieldChanged( QString ) ), this, SLOT( categoryColumnChanged( QString ) ) );
472 
473  connect( viewCategories, SIGNAL( doubleClicked( const QModelIndex & ) ), this, SLOT( categoriesDoubleClicked( const QModelIndex & ) ) );
474  connect( viewCategories, SIGNAL( customContextMenuRequested( const QPoint& ) ), this, SLOT( contextMenuViewCategories( const QPoint& ) ) );
475 
476  connect( btnChangeCategorizedSymbol, SIGNAL( clicked() ), this, SLOT( changeCategorizedSymbol() ) );
477  connect( btnAddCategories, SIGNAL( clicked() ), this, SLOT( addCategories() ) );
478  connect( btnDeleteCategories, SIGNAL( clicked() ), this, SLOT( deleteCategories() ) );
479  connect( btnDeleteAllCategories, SIGNAL( clicked() ), this, SLOT( deleteAllCategories() ) );
480  connect( btnAddCategory, SIGNAL( clicked() ), this, SLOT( addCategory() ) );
481  connect( cbxInvertedColorRamp, SIGNAL( toggled( bool ) ), this, SLOT( applyColorRamp() ) );
482  connect( cboCategorizedColorRamp, SIGNAL( currentIndexChanged( int ) ), this, SLOT( applyColorRamp() ) );
483  connect( cboCategorizedColorRamp, SIGNAL( sourceRampEdited() ), this, SLOT( applyColorRamp() ) );
484  connect( mButtonEditRamp, SIGNAL( clicked() ), cboCategorizedColorRamp, SLOT( editSourceRamp() ) );
485 
486  // menus for data-defined rotation/size
487  QMenu* advMenu = new QMenu;
488 
489  advMenu->addAction( tr( "Match to saved symbols" ), this, SLOT( matchToSymbolsFromLibrary() ) );
490  advMenu->addAction( tr( "Match to symbols from file..." ), this, SLOT( matchToSymbolsFromXml() ) );
491  advMenu->addAction( tr( "Symbol levels..." ), this, SLOT( showSymbolLevels() ) );
492 
493  btnAdvanced->setMenu( advMenu );
494 
495  mExpressionWidget->registerGetExpressionContextCallback( &_getExpressionContext, this );
496 }
497 
499 {
500  delete mRenderer;
501  delete mModel;
502  delete mCategorizedSymbol;
503 }
504 
506 {
507  // Note: This assumes that the signals for UI element changes have not
508  // yet been connected, so that the updates to color ramp, symbol, etc
509  // don't override existing customisations.
510 
512 
513  //mModel->setRenderer ( mRenderer ); // necessary?
514 
515  // set column
516  QString attrName = mRenderer->classAttribute();
517  mExpressionWidget->setField( attrName );
518 
519  // set source symbol
520  if ( mRenderer->sourceSymbol() )
521  {
522  delete mCategorizedSymbol;
525  }
526 
527  // set source color ramp
528  if ( mRenderer->sourceColorRamp() )
529  {
530  cboCategorizedColorRamp->setSourceColorRamp( mRenderer->sourceColorRamp() );
531  cbxInvertedColorRamp->setChecked( mRenderer->invertedColorRamp() );
532  }
533 
534  if ( cboCategorizedColorRamp->currentText() == tr( "Random colors" ) )
535  mButtonEditRamp->setEnabled( false );
536  else
537  mButtonEditRamp->setEnabled( true );
538 }
539 
541 {
542  return mRenderer;
543 }
544 
546 {
547  QList<int> selectedCats = selectedCategories();
548 
549  if ( !selectedCats.isEmpty() )
550  {
551  QgsSymbolV2* newSymbol = mCategorizedSymbol->clone();
552  QgsSymbolV2SelectorDialog dlg( newSymbol, mStyle, mLayer, this );
553  dlg.setMapCanvas( mMapCanvas );
554  if ( !dlg.exec() )
555  {
556  delete newSymbol;
557  return;
558  }
559 
560  Q_FOREACH ( int idx, selectedCats )
561  {
562  QgsRendererCategoryV2 category = mRenderer->categories().value( idx );
563 
564  QgsSymbolV2* newCatSymbol = newSymbol->clone();
565  newCatSymbol->setColor( mRenderer->categories()[idx].symbol()->color() );
566  mRenderer->updateCategorySymbol( idx, newCatSymbol );
567  }
568  }
569 }
570 
572 {
573  QgsSymbolV2* newSymbol = mCategorizedSymbol->clone();
574  QgsSymbolV2SelectorWidget* dlg = new QgsSymbolV2SelectorWidget( newSymbol, mStyle, mLayer, nullptr );
575  dlg->setDockMode( this->dockMode() );
576  dlg->setMapCanvas( mMapCanvas );
577 
578  connect( dlg, SIGNAL( widgetChanged() ), this, SLOT( updateSymbolsFromWidget() ) );
579  connect( dlg, SIGNAL( panelAccepted( QgsPanelWidget* ) ), this, SLOT( cleanUpSymbolSelector( QgsPanelWidget* ) ) );
580  openPanel( dlg );
581 }
582 
584 {
585  QIcon icon = QgsSymbolLayerV2Utils::symbolPreviewIcon( mCategorizedSymbol, btnChangeCategorizedSymbol->iconSize() );
586  btnChangeCategorizedSymbol->setIcon( icon );
587 }
588 
590 {
591 }
592 
594 {
595  mRenderer->setClassAttribute( field );
596  emit widgetChanged();
597 }
598 
600 {
601  if ( idx.isValid() && idx.column() == 0 )
603 }
604 
606 {
608 
609  QgsSymbolV2 *symbol = category.symbol();
610  if ( symbol )
611  {
612  symbol = symbol->clone();
613  }
614  else
615  {
617  }
618 
619  QgsSymbolV2SelectorWidget* dlg = new QgsSymbolV2SelectorWidget( symbol, mStyle, mLayer, nullptr );
620  dlg->setDockMode( this->dockMode() );
621  dlg->setMapCanvas( mMapCanvas );
622  connect( dlg, SIGNAL( widgetChanged() ), this, SLOT( updateSymbolsFromWidget() ) );
623  connect( dlg, SIGNAL( panelAccepted( QgsPanelWidget* ) ), this, SLOT( cleanUpSymbolSelector( QgsPanelWidget* ) ) );
624  openPanel( dlg );
625 }
626 
627 static void _createCategories( QgsCategoryList& cats, QList<QVariant>& values, QgsSymbolV2* symbol )
628 {
629  // sort the categories first
630  QgsSymbolLayerV2Utils::sortVariantList( values, Qt::AscendingOrder );
631 
632  int num = values.count();
633  for ( int i = 0; i < num; i++ )
634  {
635  QVariant value = values[i];
636  if ( ! value.isNull() )
637  {
638  QgsSymbolV2* newSymbol = symbol->clone();
639  cats.append( QgsRendererCategoryV2( value, newSymbol, value.toString(), true ) );
640  }
641  }
642  // add null (default) value
643  QgsSymbolV2* newSymbol = symbol->clone();
644  cats.append( QgsRendererCategoryV2( QVariant( "" ), newSymbol, QString(), true ) );
645 }
646 
648 {
649  QgsVectorColorRampV2* ramp = cboCategorizedColorRamp->currentColorRamp();
650  if ( !ramp )
651  {
652  if ( cboCategorizedColorRamp->count() == 0 )
653  QMessageBox::critical( this, tr( "Error" ), tr( "There are no available color ramps. You can add them in Style Manager." ) );
654  else if ( !cboCategorizedColorRamp->createNewColorRampSelected() )
655  QMessageBox::critical( this, tr( "Error" ), tr( "The selected color ramp is not available." ) );
656  }
657  return ramp;
658 }
659 
660 
662 {
663  QString attrName = mExpressionWidget->currentField();
664  int idx = mLayer->fieldNameIndex( attrName );
665  QList<QVariant> unique_vals;
666  if ( idx == -1 )
667  {
668  // Lets assume it's an expression
669  QgsExpression* expression = new QgsExpression( attrName );
670  QgsExpressionContext context;
675 
676  expression->prepare( &context );
678  QgsFeature feature;
679  while ( fit.nextFeature( feature ) )
680  {
681  context.setFeature( feature );
682  QVariant value = expression->evaluate( &context );
683  if ( unique_vals.contains( value ) )
684  continue;
685  unique_vals << value;
686  }
687  }
688  else
689  {
690  mLayer->uniqueValues( idx, unique_vals );
691  }
692 
693  // ask to abort if too many classes
694  if ( unique_vals.size() >= 1000 )
695  {
696  int res = QMessageBox::warning( nullptr, tr( "High number of classes!" ),
697  tr( "Classification would yield %1 entries which might not be expected. Continue?" ).arg( unique_vals.size() ),
698  QMessageBox::Ok | QMessageBox::Cancel,
699  QMessageBox::Cancel );
700  if ( res == QMessageBox::Cancel )
701  {
702  return;
703  }
704  }
705 
706 #if 0
707  DlgAddCategories dlg( mStyle, createDefaultSymbol(), unique_vals, this );
708  if ( !dlg.exec() )
709  return;
710 #endif
711 
712  QgsCategoryList cats;
713  _createCategories( cats, unique_vals, mCategorizedSymbol );
714  bool deleteExisting = false;
715 
716  if ( !mOldClassificationAttribute.isEmpty() &&
717  attrName != mOldClassificationAttribute &&
719  {
720  int res = QMessageBox::question( this,
721  tr( "Confirm Delete" ),
722  tr( "The classification field was changed from '%1' to '%2'.\n"
723  "Should the existing classes be deleted before classification?" )
724  .arg( mOldClassificationAttribute, attrName ),
725  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel );
726  if ( res == QMessageBox::Cancel )
727  {
728  return;
729  }
730 
731  deleteExisting = ( res == QMessageBox::Yes );
732  }
733 
734  // First element to apply coloring to
735  bool keepExistingColors = false;
736  if ( !deleteExisting )
737  {
738  QgsCategoryList prevCats = mRenderer->categories();
739  keepExistingColors = !prevCats.isEmpty();
740  for ( int i = 0; i < cats.size(); ++i )
741  {
742  bool contains = false;
743  QVariant value = cats.at( i ).value();
744  for ( int j = 0; j < prevCats.size() && !contains; ++j )
745  {
746  if ( prevCats.at( j ).value() == value )
747  {
748  contains = true;
749  break;
750  }
751  }
752 
753  if ( !contains )
754  prevCats.append( cats.at( i ) );
755  }
756  cats = prevCats;
757  }
758 
759  mOldClassificationAttribute = attrName;
760 
761  // TODO: if not all categories are desired, delete some!
762  /*
763  if (not dlg.readAllCats.isChecked())
764  {
765  cats2 = {}
766  for item in dlg.listCategories.selectedItems():
767  for k,c in cats.iteritems():
768  if item.text() == k.toString():
769  break
770  cats2[k] = c
771  cats = cats2
772  }
773  */
774 
775  // recreate renderer
780  r->setInvertedColorRamp( cbxInvertedColorRamp->isChecked() );
782  if ( ramp ) r->setSourceColorRamp( ramp->clone() );
783 
784  if ( mModel )
785  {
786  mModel->setRenderer( r );
787  }
788  delete mRenderer;
789  mRenderer = r;
790  if ( ! keepExistingColors && ramp ) applyColorRamp();
791  delete ramp;
792  emit widgetChanged();
793 }
794 
796 {
797  if ( cboCategorizedColorRamp->currentText() == tr( "Random colors" ) )
798  mButtonEditRamp->setEnabled( false );
799  else
800  mButtonEditRamp->setEnabled( true );
801 
803  if ( ramp )
804  {
805  mRenderer->updateColorRamp( ramp, cbxInvertedColorRamp->isChecked() );
806  }
807  mModel->updateSymbology();
808 }
809 
811 {
812  QModelIndex idx = viewCategories->selectionModel()->currentIndex();
813  if ( !idx.isValid() )
814  return -1;
815  return idx.row();
816 }
817 
819 {
820  QList<int> rows;
821  QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
822 
823  Q_FOREACH ( const QModelIndex& r, selectedRows )
824  {
825  if ( r.isValid() )
826  {
827  rows.append( r.row() );
828  }
829  }
830  return rows;
831 }
832 
834 {
835  QList<int> categoryIndexes = selectedCategories();
836  mModel->deleteRows( categoryIndexes );
837  emit widgetChanged();
838 }
839 
841 {
842  mModel->removeAllRows();
843  emit widgetChanged();
844 }
845 
847 {
848  if ( !mModel ) return;
850  QgsRendererCategoryV2 cat( QString(), symbol, QString(), true );
851  mModel->addCategory( cat );
852  emit widgetChanged();
853 }
854 
856 {
857  mRenderer->setSizeScaleField( fldName );
858  emit widgetChanged();
859 }
860 
862 {
863  mRenderer->setScaleMethod( scaleMethod );
864  emit widgetChanged();
865 }
866 
868 {
870 
871  QItemSelectionModel* m = viewCategories->selectionModel();
872  QModelIndexList selectedIndexes = m->selectedRows( 1 );
873 
874  if ( m && !selectedIndexes.isEmpty() )
875  {
876  const QgsCategoryList& categories = mRenderer->categories();
877  QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
878  for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
879  {
880  int row = ( *indexIt ).row();
881  QgsSymbolV2* s = categories[row].symbol();
882  if ( s )
883  {
884  selectedSymbols.append( s );
885  }
886  }
887  }
888  return selectedSymbols;
889 }
890 
892 {
893  QgsCategoryList cl;
894 
895  QItemSelectionModel* m = viewCategories->selectionModel();
896  QModelIndexList selectedIndexes = m->selectedRows( 1 );
897 
898  if ( m && !selectedIndexes.isEmpty() )
899  {
900  QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
901  for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
902  {
903  cl.append( mModel->category( *indexIt ) );
904  }
905  }
906  return cl;
907 }
908 
910 {
912  emit widgetChanged();
913 }
914 
916 {
918 }
919 
921 {
922  viewCategories->selectionModel()->clear();
923 }
924 
926 {
927  int matched = matchToSymbols( QgsStyleV2::defaultStyle() );
928  if ( matched > 0 )
929  {
930  QMessageBox::information( this, tr( "Matched symbols" ),
931  tr( "Matched %1 categories to symbols." ).arg( matched ) );
932  }
933  else
934  {
935  QMessageBox::warning( this, tr( "Matched symbols" ),
936  tr( "No categories could be matched to symbols in library." ) );
937  }
938 }
939 
941 {
942  if ( !mLayer || !style )
943  return 0;
944 
945  int matched = 0;
946  for ( int catIdx = 0; catIdx < mRenderer->categories().count(); ++catIdx )
947  {
948  QString val = mRenderer->categories().at( catIdx ).value().toString();
949  QgsSymbolV2* symbol = style->symbol( val );
950  if ( symbol &&
951  (( symbol->type() == QgsSymbolV2::Marker && mLayer->geometryType() == QGis::Point )
952  || ( symbol->type() == QgsSymbolV2::Line && mLayer->geometryType() == QGis::Line )
953  || ( symbol->type() == QgsSymbolV2::Fill && mLayer->geometryType() == QGis::Polygon ) ) )
954  {
955  matched++;
956  mRenderer->updateCategorySymbol( catIdx, symbol->clone() );
957  }
958  }
959  mModel->updateSymbology();
960  return matched;
961 }
962 
964 {
965  QSettings settings;
966  QString openFileDir = settings.value( "UI/lastMatchToSymbolsDir", QDir::homePath() ).toString();
967 
968  QString fileName = QFileDialog::getOpenFileName( this, tr( "Match to symbols from file" ), openFileDir,
969  tr( "XML files (*.xml *XML)" ) );
970  if ( fileName.isEmpty() )
971  {
972  return;
973  }
974 
975  QFileInfo openFileInfo( fileName );
976  settings.setValue( "UI/lastMatchToSymbolsDir", openFileInfo.absolutePath() );
977 
978  QgsStyleV2 importedStyle;
979  if ( !importedStyle.importXML( fileName ) )
980  {
981  QMessageBox::warning( this, tr( "Matching error" ),
982  tr( "An error occurred reading file:\n%1" ).arg( importedStyle.errorString() ) );
983  return;
984  }
985 
986  int matched = matchToSymbols( &importedStyle );
987  if ( matched > 0 )
988  {
989  QMessageBox::information( this, tr( "Matched symbols" ),
990  tr( "Matched %1 categories to symbols from file." ).arg( matched ) );
991  }
992  else
993  {
994  QMessageBox::warning( this, tr( "Matched symbols" ),
995  tr( "No categories could be matched to symbols in file." ) );
996  }
997 }
998 
999 void QgsCategorizedSymbolRendererV2Widget::cleanUpSymbolSelector( QgsPanelWidget *container )
1000 {
1001  QgsSymbolV2SelectorWidget *dlg = qobject_cast<QgsSymbolV2SelectorWidget*>( container );
1002  if ( !dlg )
1003  return;
1004 
1005  dlg->releaseSymbol();
1006 }
1007 
1008 void QgsCategorizedSymbolRendererV2Widget::updateSymbolsFromWidget()
1009 {
1010  QgsSymbolV2SelectorWidget* dlg = qobject_cast<QgsSymbolV2SelectorWidget*>( sender() );
1011  delete mCategorizedSymbol;
1012  mCategorizedSymbol = dlg->symbol()->clone();
1013 
1015 
1016  // When there is a slection, change the selected symbols alone
1017  QItemSelectionModel* m = viewCategories->selectionModel();
1018  QModelIndexList i = m->selectedRows();
1019 
1020  if ( m && !i.isEmpty() )
1021  {
1022  QList<int> selectedCats = selectedCategories();
1023 
1024  if ( !selectedCats.isEmpty() )
1025  {
1026  Q_FOREACH ( int idx, selectedCats )
1027  {
1028  QgsSymbolV2* newCatSymbol = mCategorizedSymbol->clone();
1029  if ( selectedCats.count() > 1 )
1030  {
1031  //if updating multiple categories, retain the existing category colors
1032  newCatSymbol->setColor( mRenderer->categories().at( idx ).symbol()->color() );
1033  }
1034  mRenderer->updateCategorySymbol( idx, newCatSymbol );
1035  }
1036  emit widgetChanged();
1037  }
1038  return;
1039  }
1040 
1041  mRenderer->updateSymbols( mCategorizedSymbol );
1042  emit widgetChanged();
1043 }
1044 
1046 {
1047  if ( !event )
1048  {
1049  return;
1050  }
1051 
1052  if ( event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier )
1053  {
1054  mCopyBuffer.clear();
1055  mCopyBuffer = selectedCategoryList();
1056  }
1057  else if ( event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier )
1058  {
1059  QgsCategoryList::const_iterator rIt = mCopyBuffer.constBegin();
1060  for ( ; rIt != mCopyBuffer.constEnd(); ++rIt )
1061  {
1062  mModel->addCategory( *rIt );
1063  }
1064  }
1065 }
const QgsCategoryList & categories() const
void customContextMenuRequested(const QPoint &pos)
void matchToSymbolsFromLibrary()
Replaces category symbols with the symbols from the users&#39; symbol library that have a matching name...
static void sortVariantList(QList< QVariant > &list, Qt::SortOrder order)
Sorts the passed list in requested order.
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 ...
void showSymbolLevelsDialog(QgsFeatureRendererV2 *r)
show a dialog with renderer&#39;s symbol level settings
void clear()
const QgsMapCanvas * mapCanvas() const
Returns the map canvas associated with the widget.
Wrapper for iterator of features from vector data provider or vector layer.
Symbol selector widget that cna be used to select and build a symbol.
static unsigned index
void setupUi(QWidget *widget)
QByteArray data(const QString &mimeType) const
QString readEntry(const QString &scope, const QString &key, const QString &def=QString::null, bool *ok=nullptr) const
int matchToSymbols(QgsStyleV2 *style)
Replaces category symbols with the symbols from a style that have a matching name.
bool dockMode()
Return the dock mode state.
Q_DECL_DEPRECATED QVariant evaluate(const QgsFeature *f)
Evaluate the feature and return the result.
bool importXML(const QString &filename)
Imports the symbols and colorramps into the default style database from the given XML file...
void setMapCanvas(QgsMapCanvas *canvas)
Sets the map canvas associated with the dialog.
QgsVectorColorRampV2 * sourceColorRamp()
Returns the source color ramp, from which each categories&#39; color is derived.
void append(const T &value)
void releaseSymbol()
Delete the symbol.
static QgsExpressionContextScope * atlasScope(const QgsAtlasComposition *atlas)
Creates a new scope which contains variables and functions relating to a QgsAtlasComposition.
Q_DECL_DEPRECATED bool prepare(const QgsFields &fields)
Get the expression ready for evaluation - find out column indexes.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
virtual bool hasFormat(const QString &mimeType) const
void uniqueValues(int index, QList< QVariant > &uniqueValues, int limit=-1)
Calculates a list of unique values contained within an attribute in the layer.
void setSourceSymbol(QgsSymbolV2 *sym)
Sets the source symbol for the renderer, which is the base symbol used for the each categories&#39; symbo...
QObject * sender() const
virtual QgsSymbolV2 * clone() const =0
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest())
Query the provider for features specified in request.
QStyle * style() const
QgsSymbolV2 * symbol()
Return the symbol that is currently active in the widget.
const T & at(int i) const
void addAction(QAction *action)
static QgsRendererV2Widget * create(QgsVectorLayer *layer, QgsStyleV2 *style, QgsFeatureRendererV2 *renderer)
QgsMapCanvas * mMapCanvas
int exec()
SymbolType type() const
Definition: qgssymbolv2.h:107
Line symbol.
Definition: qgssymbolv2.h:82
Base class for any widget that can be shown as a inline panel.
const QPixmap * icon() const
virtual void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:187
virtual QgsFeatureRendererV2 * renderer() override
return pointer to the renderer (no transfer of ownership)
QString homePath()
void panelAccepted(QgsPanelWidget *panel)
Emitted when the panel is accepted by the user.
QString tr(const char *sourceText, const char *disambiguation, int n)
StandardButton information(QWidget *parent, const QString &title, const QString &text, QFlags< QMessageBox::StandardButton > buttons, StandardButton defaultButton)
void setSourceColorRamp(QgsVectorColorRampV2 *ramp)
Sets the source color ramp.
Marker symbol.
Definition: qgssymbolv2.h:81
int size() const
virtual void setDockMode(bool dockMode)
Set the widget in dock mode which tells the widget to emit panel widgets and not open dialogs...
T value(int i) const
The QgsMapSettings class contains configuration for rendering of the map.
void setValue(const QString &key, const QVariant &value)
void updateSymbols(QgsSymbolV2 *sym)
Update all the symbols but leave categories and colors.
void setColor(const QColor &color)
bool isValid() const
QgsSymbolV2::ScaleMethod scaleMethod() const
QList< QgsRendererCategoryV2 > QgsCategoryList
static QIcon symbolPreviewIcon(QgsSymbolV2 *symbol, QSize size)
int count(const T &value) const
QgsVectorLayer * mLayer
void append(const T &value)
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context...
int toInt(bool *ok) const
bool isNull() const
StandardButton question(QWidget *parent, const QString &title, const QString &text, QFlags< QMessageBox::StandardButton > buttons, StandardButton defaultButton)
virtual QgsVectorColorRampV2 * clone() const =0
Creates a clone of the color ramp.
void changeSelectedSymbols()
change the selected symbols alone for the change button, if there is a selection
bool isEmpty() const
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
static QgsStyleV2 * defaultStyle()
return default application-wide style
Definition: qgsstylev2.cpp:51
bool isEmpty() const
QModelIndexList selectedRows(int column) const
int row() const
QGis::GeometryType geometryType() const
Returns point, line or polygon.
Single scope for storing variables and functions for use within a QgsExpressionContext.
QgsCategorizedSymbolRendererV2Widget(QgsVectorLayer *layer, QgsStyleV2 *style, QgsFeatureRendererV2 *renderer)
void setScaleMethod(QgsSymbolV2::ScaleMethod scaleMethod)
static QgsCategorizedSymbolRendererV2 * convertFromRenderer(const QgsFeatureRendererV2 *renderer)
creates a QgsCategorizedSymbolRendererV2 from an existing renderer.
void widgetChanged()
Emitted when the widget state changes.
void updateColorRamp(QgsVectorColorRampV2 *ramp, bool inverted=false)
Update the color ramp used and all symbols colors.
int key() const
QgsSymbolV2 * symbol(const QString &name)
return a NEW copy of symbol
Definition: qgsstylev2.cpp:164
bool contains(const T &value) const
static void _createCategories(QgsCategoryList &cats, QList< QVariant > &values, QgsSymbolV2 *symbol)
const QgsMapSettings & mapSettings() const
Get access to properties used for map rendering.
void contextMenuViewCategories(QPoint p)
QList< int > selectedCategories()
return a list of indexes for the categories unders selection
QVariant value(const QString &key, const QVariant &defaultValue) const
const QgsVectorLayer * vectorLayer() const
Returns the vector layer associated with the widget.
QgsSymbolV2 * sourceSymbol()
Returns the renderer&#39;s source symbol, which is the base symbol used for the each categories&#39; symbol b...
static QgsExpressionContextScope * mapSettingsScope(const QgsMapSettings &mapSettings)
Creates a new scope which contains variables and functions relating to a QgsMapSettings object...
QgsExpressionContextScope & expressionContextScope()
Returns a reference to the expression context scope for the map canvas.
Definition: qgsmapcanvas.h:455
static QgsSymbolV2 * defaultSymbol(QGis::GeometryType geomType)
return new default symbol for specified geometry type
ScaleMethod
Scale method.
Definition: qgssymbolv2.h:90
typedef DropActions
void scaleMethodChanged(QgsSymbolV2::ScaleMethod scaleMethod)
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:382
bool updateCategorySymbol(int catIndex, QgsSymbolV2 *symbol)
void setMapCanvas(QgsMapCanvas *canvas)
Sets the map canvas associated with the dialog.
Fill symbol.
Definition: qgssymbolv2.h:83
void keyPressEvent(QKeyEvent *event) override
Overridden key press event to handle the esc event on the widget.
StandardButton critical(QWidget *parent, const QString &title, const QString &text, QFlags< QMessageBox::StandardButton > buttons, StandardButton defaultButton)
int column() const
Base class for renderer settings widgets.
static QgsExpressionContext _getExpressionContext(const void *context)
QList< QgsSymbolV2 * > selectedSymbols() override
Subclasses may provide the capability of changing multiple symbols at once by implementing the follow...
StandardButton warning(QWidget *parent, const QString &title, const QString &text, QFlags< QMessageBox::StandardButton > buttons, StandardButton defaultButton)
double toDouble(bool *ok) const
int currentCategoryRow()
return row index for the currently selected category (-1 if on no selection)
void setData(const QString &mimeType, const QByteArray &data)
QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFlags< QFileDialog::Option > options)
static QgsExpressionContextScope * projectScope()
Creates a new scope which contains variables and functions relating to the current QGIS project...
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
const_iterator constEnd() const
bool nextFeature(QgsFeature &f)
const_iterator constBegin() const
void matchToSymbolsFromXml()
Prompts for selection of an xml file, then replaces category symbols with the symbols from the XML fi...
Type type() const
QString absolutePath() const
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const
int size() const
Represents a vector layer which manages a vector based data sets.
Abstract base class for color ramps.
QString toString() const
virtual bool event(QEvent *event)
void setSizeScaleField(const QString &fieldOrExpression)
QString errorString()
return last error from load/save operation
Definition: qgsstylev2.h:288
int fieldNameIndex(const QString &fieldName) const
Returns the index of a field name or -1 if the field does not exist.
typedef ItemFlags