QGIS API Documentation  2.17.0-Master (8784312)
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 
634  bool hasNull = false;
635 
636  for ( int i = 0; i < num; i++ )
637  {
638  QVariant value = values[i];
639  if ( value.toString().isNull() )
640  {
641  hasNull = true;
642  }
643  QgsSymbolV2* newSymbol = symbol->clone();
644 
645  cats.append( QgsRendererCategoryV2( value, newSymbol, value.toString(), true ) );
646  }
647 
648  // add null (default) value if not exists
649  if ( !hasNull )
650  {
651  QgsSymbolV2* newSymbol = symbol->clone();
652  cats.append( QgsRendererCategoryV2( QVariant( "" ), newSymbol, QString(), true ) );
653  }
654 }
655 
657 {
658  QgsVectorColorRampV2* ramp = cboCategorizedColorRamp->currentColorRamp();
659  if ( !ramp )
660  {
661  if ( cboCategorizedColorRamp->count() == 0 )
662  QMessageBox::critical( this, tr( "Error" ), tr( "There are no available color ramps. You can add them in Style Manager." ) );
663  else if ( !cboCategorizedColorRamp->createNewColorRampSelected() )
664  QMessageBox::critical( this, tr( "Error" ), tr( "The selected color ramp is not available." ) );
665  }
666  return ramp;
667 }
668 
669 
671 {
672  QString attrName = mExpressionWidget->currentField();
673  int idx = mLayer->fieldNameIndex( attrName );
674  QList<QVariant> unique_vals;
675  if ( idx == -1 )
676  {
677  // Lets assume it's an expression
678  QgsExpression* expression = new QgsExpression( attrName );
679  QgsExpressionContext context;
684 
685  expression->prepare( &context );
687  QgsFeature feature;
688  while ( fit.nextFeature( feature ) )
689  {
690  context.setFeature( feature );
691  QVariant value = expression->evaluate( &context );
692  if ( unique_vals.contains( value ) )
693  continue;
694  unique_vals << value;
695  }
696  }
697  else
698  {
699  mLayer->uniqueValues( idx, unique_vals );
700  }
701 
702  // ask to abort if too many classes
703  if ( unique_vals.size() >= 1000 )
704  {
705  int res = QMessageBox::warning( nullptr, tr( "High number of classes!" ),
706  tr( "Classification would yield %1 entries which might not be expected. Continue?" ).arg( unique_vals.size() ),
707  QMessageBox::Ok | QMessageBox::Cancel,
708  QMessageBox::Cancel );
709  if ( res == QMessageBox::Cancel )
710  {
711  return;
712  }
713  }
714 
715 #if 0
716  DlgAddCategories dlg( mStyle, createDefaultSymbol(), unique_vals, this );
717  if ( !dlg.exec() )
718  return;
719 #endif
720 
721  QgsCategoryList cats;
722  _createCategories( cats, unique_vals, mCategorizedSymbol );
723  bool deleteExisting = false;
724 
725  if ( !mOldClassificationAttribute.isEmpty() &&
726  attrName != mOldClassificationAttribute &&
728  {
729  int res = QMessageBox::question( this,
730  tr( "Confirm Delete" ),
731  tr( "The classification field was changed from '%1' to '%2'.\n"
732  "Should the existing classes be deleted before classification?" )
733  .arg( mOldClassificationAttribute, attrName ),
734  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel );
735  if ( res == QMessageBox::Cancel )
736  {
737  return;
738  }
739 
740  deleteExisting = ( res == QMessageBox::Yes );
741  }
742 
743  // First element to apply coloring to
744  bool keepExistingColors = false;
745  if ( !deleteExisting )
746  {
747  QgsCategoryList prevCats = mRenderer->categories();
748  keepExistingColors = !prevCats.isEmpty();
749  for ( int i = 0; i < cats.size(); ++i )
750  {
751  bool contains = false;
752  QVariant value = cats.at( i ).value();
753  for ( int j = 0; j < prevCats.size() && !contains; ++j )
754  {
755  if ( prevCats.at( j ).value() == value )
756  {
757  contains = true;
758  break;
759  }
760  }
761 
762  if ( !contains )
763  prevCats.append( cats.at( i ) );
764  }
765  cats = prevCats;
766  }
767 
768  mOldClassificationAttribute = attrName;
769 
770  // TODO: if not all categories are desired, delete some!
771  /*
772  if (not dlg.readAllCats.isChecked())
773  {
774  cats2 = {}
775  for item in dlg.listCategories.selectedItems():
776  for k,c in cats.iteritems():
777  if item.text() == k.toString():
778  break
779  cats2[k] = c
780  cats = cats2
781  }
782  */
783 
784  // recreate renderer
789  r->setInvertedColorRamp( cbxInvertedColorRamp->isChecked() );
791  if ( ramp ) r->setSourceColorRamp( ramp->clone() );
792 
793  if ( mModel )
794  {
795  mModel->setRenderer( r );
796  }
797  delete mRenderer;
798  mRenderer = r;
799  if ( ! keepExistingColors && ramp ) applyColorRamp();
800  delete ramp;
801  emit widgetChanged();
802 }
803 
805 {
806  if ( cboCategorizedColorRamp->currentText() == tr( "Random colors" ) )
807  mButtonEditRamp->setEnabled( false );
808  else
809  mButtonEditRamp->setEnabled( true );
810 
812  if ( ramp )
813  {
814  mRenderer->updateColorRamp( ramp, cbxInvertedColorRamp->isChecked() );
815  }
816  mModel->updateSymbology();
817 }
818 
820 {
821  QModelIndex idx = viewCategories->selectionModel()->currentIndex();
822  if ( !idx.isValid() )
823  return -1;
824  return idx.row();
825 }
826 
828 {
829  QList<int> rows;
830  QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
831 
832  Q_FOREACH ( const QModelIndex& r, selectedRows )
833  {
834  if ( r.isValid() )
835  {
836  rows.append( r.row() );
837  }
838  }
839  return rows;
840 }
841 
843 {
844  QList<int> categoryIndexes = selectedCategories();
845  mModel->deleteRows( categoryIndexes );
846  emit widgetChanged();
847 }
848 
850 {
851  mModel->removeAllRows();
852  emit widgetChanged();
853 }
854 
856 {
857  if ( !mModel ) return;
859  QgsRendererCategoryV2 cat( QString(), symbol, QString(), true );
860  mModel->addCategory( cat );
861  emit widgetChanged();
862 }
863 
865 {
866  mRenderer->setSizeScaleField( fldName );
867  emit widgetChanged();
868 }
869 
871 {
872  mRenderer->setScaleMethod( scaleMethod );
873  emit widgetChanged();
874 }
875 
877 {
879 
880  QItemSelectionModel* m = viewCategories->selectionModel();
881  QModelIndexList selectedIndexes = m->selectedRows( 1 );
882 
883  if ( m && !selectedIndexes.isEmpty() )
884  {
885  const QgsCategoryList& categories = mRenderer->categories();
886  QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
887  for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
888  {
889  int row = ( *indexIt ).row();
890  QgsSymbolV2* s = categories[row].symbol();
891  if ( s )
892  {
893  selectedSymbols.append( s );
894  }
895  }
896  }
897  return selectedSymbols;
898 }
899 
901 {
902  QgsCategoryList cl;
903 
904  QItemSelectionModel* m = viewCategories->selectionModel();
905  QModelIndexList selectedIndexes = m->selectedRows( 1 );
906 
907  if ( m && !selectedIndexes.isEmpty() )
908  {
909  QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
910  for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
911  {
912  cl.append( mModel->category( *indexIt ) );
913  }
914  }
915  return cl;
916 }
917 
919 {
921  emit widgetChanged();
922 }
923 
925 {
927 }
928 
930 {
931  viewCategories->selectionModel()->clear();
932 }
933 
935 {
936  int matched = matchToSymbols( QgsStyleV2::defaultStyle() );
937  if ( matched > 0 )
938  {
939  QMessageBox::information( this, tr( "Matched symbols" ),
940  tr( "Matched %1 categories to symbols." ).arg( matched ) );
941  }
942  else
943  {
944  QMessageBox::warning( this, tr( "Matched symbols" ),
945  tr( "No categories could be matched to symbols in library." ) );
946  }
947 }
948 
950 {
951  if ( !mLayer || !style )
952  return 0;
953 
954  int matched = 0;
955  for ( int catIdx = 0; catIdx < mRenderer->categories().count(); ++catIdx )
956  {
957  QString val = mRenderer->categories().at( catIdx ).value().toString();
958  QgsSymbolV2* symbol = style->symbol( val );
959  if ( symbol &&
960  (( symbol->type() == QgsSymbolV2::Marker && mLayer->geometryType() == QGis::Point )
961  || ( symbol->type() == QgsSymbolV2::Line && mLayer->geometryType() == QGis::Line )
962  || ( symbol->type() == QgsSymbolV2::Fill && mLayer->geometryType() == QGis::Polygon ) ) )
963  {
964  matched++;
965  mRenderer->updateCategorySymbol( catIdx, symbol->clone() );
966  }
967  }
968  mModel->updateSymbology();
969  return matched;
970 }
971 
973 {
974  QSettings settings;
975  QString openFileDir = settings.value( "UI/lastMatchToSymbolsDir", QDir::homePath() ).toString();
976 
977  QString fileName = QFileDialog::getOpenFileName( this, tr( "Match to symbols from file" ), openFileDir,
978  tr( "XML files (*.xml *XML)" ) );
979  if ( fileName.isEmpty() )
980  {
981  return;
982  }
983 
984  QFileInfo openFileInfo( fileName );
985  settings.setValue( "UI/lastMatchToSymbolsDir", openFileInfo.absolutePath() );
986 
987  QgsStyleV2 importedStyle;
988  if ( !importedStyle.importXML( fileName ) )
989  {
990  QMessageBox::warning( this, tr( "Matching error" ),
991  tr( "An error occurred reading file:\n%1" ).arg( importedStyle.errorString() ) );
992  return;
993  }
994 
995  int matched = matchToSymbols( &importedStyle );
996  if ( matched > 0 )
997  {
998  QMessageBox::information( this, tr( "Matched symbols" ),
999  tr( "Matched %1 categories to symbols from file." ).arg( matched ) );
1000  }
1001  else
1002  {
1003  QMessageBox::warning( this, tr( "Matched symbols" ),
1004  tr( "No categories could be matched to symbols in file." ) );
1005  }
1006 }
1007 
1008 void QgsCategorizedSymbolRendererV2Widget::cleanUpSymbolSelector( QgsPanelWidget *container )
1009 {
1010  if ( container )
1011  {
1012  QgsSymbolV2SelectorWidget* dlg = qobject_cast<QgsSymbolV2SelectorWidget*>( container );
1013  delete dlg->symbol();
1014  }
1015 }
1016 
1017 void QgsCategorizedSymbolRendererV2Widget::updateSymbolsFromWidget()
1018 {
1019  QgsSymbolV2SelectorWidget* dlg = qobject_cast<QgsSymbolV2SelectorWidget*>( sender() );
1020  delete mCategorizedSymbol;
1021  mCategorizedSymbol = dlg->symbol()->clone();
1022 
1024 
1025  // When there is a slection, change the selected symbols alone
1026  QItemSelectionModel* m = viewCategories->selectionModel();
1027  QModelIndexList i = m->selectedRows();
1028 
1029  if ( m && !i.isEmpty() )
1030  {
1031  QList<int> selectedCats = selectedCategories();
1032 
1033  if ( !selectedCats.isEmpty() )
1034  {
1035  Q_FOREACH ( int idx, selectedCats )
1036  {
1037  QgsSymbolV2* newCatSymbol = mCategorizedSymbol->clone();
1038  if ( selectedCats.count() > 1 )
1039  {
1040  //if updating multiple categories, retain the existing category colors
1041  newCatSymbol->setColor( mRenderer->categories().at( idx ).symbol()->color() );
1042  }
1043  mRenderer->updateCategorySymbol( idx, newCatSymbol );
1044  }
1045  emit widgetChanged();
1046  }
1047  return;
1048  }
1049 
1050  mRenderer->updateSymbols( mCategorizedSymbol );
1051  emit widgetChanged();
1052 }
1053 
1055 {
1056  if ( !event )
1057  {
1058  return;
1059  }
1060 
1061  if ( event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier )
1062  {
1063  mCopyBuffer.clear();
1064  mCopyBuffer = selectedCategoryList();
1065  }
1066  else if ( event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier )
1067  {
1068  QgsCategoryList::const_iterator rIt = mCopyBuffer.constBegin();
1069  for ( ; rIt != mCopyBuffer.constEnd(); ++rIt )
1070  {
1071  mModel->addCategory( *rIt );
1072  }
1073  }
1074 }
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()
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
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.
const QgsCategoryList & categories() const
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)
SymbolType type() const
Definition: qgssymbolv2.h:107
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)
Returns unique values for column.
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
const QgsMapSettings & mapSettings() const
Get access to properties used for map rendering.
int exec()
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
bool isNull() 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
QString readEntry(const QString &scope, const QString &key, const QString &def=QString::null, bool *ok=nullptr) 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
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
const QgsMapCanvas * mapCanvas() const
Returns the map canvas associated with the widget.
QModelIndexList selectedRows(int column) const
int row() const
const QgsVectorLayer * vectorLayer() const
Returns the vector layer associated with the widget.
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)
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
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...
QgsSymbolV2::ScaleMethod scaleMethod() const
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.
int fieldNameIndex(const QString &fieldName) const
Returns the index of a field name or -1 if the field does not exist.
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
typedef ItemFlags