QGIS API Documentation  3.4.15-Madeira (e83d02e274)
qgsgraduatedsymbolrendererwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsgraduatedsymbolrendererwidget.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  ***************************************************************************/
16 #include "qgspanelwidget.h"
17 
20 #include "qgssymbol.h"
21 #include "qgssymbollayerutils.h"
22 #include "qgscolorramp.h"
23 #include "qgscolorrampbutton.h"
24 #include "qgsstyle.h"
25 
26 #include "qgsvectorlayer.h"
27 
30 #include "qgslogger.h"
31 
32 #include "qgsludialog.h"
33 
34 #include "qgsproject.h"
35 #include "qgsmapcanvas.h"
36 
37 #include <QKeyEvent>
38 #include <QMenu>
39 #include <QMessageBox>
40 #include <QStandardItemModel>
41 #include <QStandardItem>
42 #include <QPen>
43 #include <QPainter>
44 
45 // ------------------------------ Model ------------------------------------
46 
48 
49 QgsGraduatedSymbolRendererModel::QgsGraduatedSymbolRendererModel( QObject *parent ) : QAbstractItemModel( parent )
50  , mMimeFormat( QStringLiteral( "application/x-qgsgraduatedsymbolrendererv2model" ) )
51 {
52 }
53 
54 void QgsGraduatedSymbolRendererModel::setRenderer( QgsGraduatedSymbolRenderer *renderer )
55 {
56  if ( mRenderer )
57  {
58  if ( mRenderer->ranges().size() )
59  {
60  beginRemoveRows( QModelIndex(), 0, mRenderer->ranges().size() - 1 );
61  mRenderer = nullptr;
62  endRemoveRows();
63  }
64  else
65  {
66  mRenderer = nullptr;
67  }
68  }
69  if ( renderer )
70  {
71  if ( renderer->ranges().size() )
72  {
73  beginInsertRows( QModelIndex(), 0, renderer->ranges().size() - 1 );
74  mRenderer = renderer;
75  endInsertRows();
76  }
77  else
78  {
79  mRenderer = renderer;
80  }
81  }
82 }
83 
84 void QgsGraduatedSymbolRendererModel::addClass( QgsSymbol *symbol )
85 {
86  if ( !mRenderer ) return;
87  int idx = mRenderer->ranges().size();
88  beginInsertRows( QModelIndex(), idx, idx );
89  mRenderer->addClass( symbol );
90  endInsertRows();
91 }
92 
93 void QgsGraduatedSymbolRendererModel::addClass( const QgsRendererRange &range )
94 {
95  if ( !mRenderer )
96  {
97  return;
98  }
99  int idx = mRenderer->ranges().size();
100  beginInsertRows( QModelIndex(), idx, idx );
101  mRenderer->addClass( range );
102  endInsertRows();
103 }
104 
105 QgsRendererRange QgsGraduatedSymbolRendererModel::rendererRange( const QModelIndex &index )
106 {
107  if ( !index.isValid() || !mRenderer || mRenderer->ranges().size() <= index.row() )
108  {
109  return QgsRendererRange();
110  }
111 
112  return mRenderer->ranges().value( index.row() );
113 }
114 
115 Qt::ItemFlags QgsGraduatedSymbolRendererModel::flags( const QModelIndex &index ) const
116 {
117  if ( !index.isValid() )
118  {
119  return Qt::ItemIsDropEnabled;
120  }
121 
122  Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable;
123 
124  if ( index.column() == 2 )
125  {
126  flags |= Qt::ItemIsEditable;
127  }
128 
129  return flags;
130 }
131 
132 Qt::DropActions QgsGraduatedSymbolRendererModel::supportedDropActions() const
133 {
134  return Qt::MoveAction;
135 }
136 
137 QVariant QgsGraduatedSymbolRendererModel::data( const QModelIndex &index, int role ) const
138 {
139  if ( !index.isValid() || !mRenderer ) return QVariant();
140 
141  const QgsRendererRange range = mRenderer->ranges().value( index.row() );
142 
143  if ( role == Qt::CheckStateRole && index.column() == 0 )
144  {
145  return range.renderState() ? Qt::Checked : Qt::Unchecked;
146  }
147  else if ( role == Qt::DisplayRole || role == Qt::ToolTipRole )
148  {
149  switch ( index.column() )
150  {
151  case 1:
152  {
153  int decimalPlaces = mRenderer->labelFormat().precision() + 2;
154  if ( decimalPlaces < 0 ) decimalPlaces = 0;
155  return QLocale().toString( range.lowerValue(), 'f', decimalPlaces ) + " - " + QLocale().toString( range.upperValue(), 'f', decimalPlaces );
156  }
157  case 2:
158  return range.label();
159  default:
160  return QVariant();
161  }
162  }
163  else if ( role == Qt::DecorationRole && index.column() == 0 && range.symbol() )
164  {
165  const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
166  return QgsSymbolLayerUtils::symbolPreviewIcon( range.symbol(), QSize( iconSize, iconSize ) );
167  }
168  else if ( role == Qt::TextAlignmentRole )
169  {
170  return ( index.column() == 0 ) ? Qt::AlignHCenter : Qt::AlignLeft;
171  }
172  else if ( role == Qt::EditRole )
173  {
174  switch ( index.column() )
175  {
176  // case 1: return rangeStr;
177  case 2:
178  return range.label();
179  default:
180  return QVariant();
181  }
182  }
183 
184  return QVariant();
185 }
186 
187 bool QgsGraduatedSymbolRendererModel::setData( const QModelIndex &index, const QVariant &value, int role )
188 {
189  if ( !index.isValid() )
190  return false;
191 
192  if ( index.column() == 0 && role == Qt::CheckStateRole )
193  {
194  mRenderer->updateRangeRenderState( index.row(), value == Qt::Checked );
195  emit dataChanged( index, index );
196  return true;
197  }
198 
199  if ( role != Qt::EditRole )
200  return false;
201 
202  switch ( index.column() )
203  {
204  case 1: // range
205  return false; // range is edited in popup dialog
206  case 2: // label
207  mRenderer->updateRangeLabel( index.row(), value.toString() );
208  break;
209  default:
210  return false;
211  }
212 
213  emit dataChanged( index, index );
214  return true;
215 }
216 
217 QVariant QgsGraduatedSymbolRendererModel::headerData( int section, Qt::Orientation orientation, int role ) const
218 {
219  if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 3 )
220  {
221  QStringList lst;
222  lst << tr( "Symbol" ) << tr( "Values" ) << tr( "Legend" );
223  return lst.value( section );
224  }
225  return QVariant();
226 }
227 
228 int QgsGraduatedSymbolRendererModel::rowCount( const QModelIndex &parent ) const
229 {
230  if ( parent.isValid() || !mRenderer )
231  {
232  return 0;
233  }
234  return mRenderer->ranges().size();
235 }
236 
237 int QgsGraduatedSymbolRendererModel::columnCount( const QModelIndex &index ) const
238 {
239  Q_UNUSED( index );
240  return 3;
241 }
242 
243 QModelIndex QgsGraduatedSymbolRendererModel::index( int row, int column, const QModelIndex &parent ) const
244 {
245  if ( hasIndex( row, column, parent ) )
246  {
247  return createIndex( row, column );
248  }
249  return QModelIndex();
250 }
251 
252 QModelIndex QgsGraduatedSymbolRendererModel::parent( const QModelIndex &index ) const
253 {
254  Q_UNUSED( index );
255  return QModelIndex();
256 }
257 
258 QStringList QgsGraduatedSymbolRendererModel::mimeTypes() const
259 {
260  QStringList types;
261  types << mMimeFormat;
262  return types;
263 }
264 
265 QMimeData *QgsGraduatedSymbolRendererModel::mimeData( const QModelIndexList &indexes ) const
266 {
267  QMimeData *mimeData = new QMimeData();
268  QByteArray encodedData;
269 
270  QDataStream stream( &encodedData, QIODevice::WriteOnly );
271 
272  // Create list of rows
273  Q_FOREACH ( const QModelIndex &index, indexes )
274  {
275  if ( !index.isValid() || index.column() != 0 )
276  continue;
277 
278  stream << index.row();
279  }
280  mimeData->setData( mMimeFormat, encodedData );
281  return mimeData;
282 }
283 
284 bool QgsGraduatedSymbolRendererModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
285 {
286  Q_UNUSED( row );
287  Q_UNUSED( column );
288  if ( action != Qt::MoveAction ) return true;
289 
290  if ( !data->hasFormat( mMimeFormat ) ) return false;
291 
292  QByteArray encodedData = data->data( mMimeFormat );
293  QDataStream stream( &encodedData, QIODevice::ReadOnly );
294 
295  QVector<int> rows;
296  while ( !stream.atEnd() )
297  {
298  int r;
299  stream >> r;
300  rows.append( r );
301  }
302 
303  int to = parent.row();
304  // to is -1 if dragged outside items, i.e. below any item,
305  // then move to the last position
306  if ( to == -1 ) to = mRenderer->ranges().size(); // out of rang ok, will be decreased
307  for ( int i = rows.size() - 1; i >= 0; i-- )
308  {
309  QgsDebugMsg( QStringLiteral( "move %1 to %2" ).arg( rows[i] ).arg( to ) );
310  int t = to;
311  // moveCategory first removes and then inserts
312  if ( rows[i] < t ) t--;
313  mRenderer->moveClass( rows[i], t );
314  // current moved under another, shift its index up
315  for ( int j = 0; j < i; j++ )
316  {
317  if ( to < rows[j] && rows[i] > rows[j] ) rows[j] += 1;
318  }
319  // removed under 'to' so the target shifted down
320  if ( rows[i] < to ) to--;
321  }
322  emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->ranges().size(), 0 ) );
323  emit rowsMoved();
324  return false;
325 }
326 
327 void QgsGraduatedSymbolRendererModel::deleteRows( QList<int> rows )
328 {
329  for ( int i = rows.size() - 1; i >= 0; i-- )
330  {
331  beginRemoveRows( QModelIndex(), rows[i], rows[i] );
332  mRenderer->deleteClass( rows[i] );
333  endRemoveRows();
334  }
335 }
336 
337 void QgsGraduatedSymbolRendererModel::removeAllRows()
338 {
339  beginRemoveRows( QModelIndex(), 0, mRenderer->ranges().size() - 1 );
340  mRenderer->deleteAllClasses();
341  endRemoveRows();
342 }
343 
344 void QgsGraduatedSymbolRendererModel::sort( int column, Qt::SortOrder order )
345 {
346  if ( column == 0 )
347  {
348  return;
349  }
350  if ( column == 1 )
351  {
352  mRenderer->sortByValue( order );
353  }
354  else if ( column == 2 )
355  {
356  mRenderer->sortByLabel( order );
357  }
358  emit rowsMoved();
359  emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->ranges().size(), 0 ) );
360 }
361 
362 void QgsGraduatedSymbolRendererModel::updateSymbology( bool resetModel )
363 {
364  if ( resetModel )
365  {
366  reset();
367  }
368  else
369  {
370  emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->ranges().size(), 0 ) );
371  }
372 }
373 
374 void QgsGraduatedSymbolRendererModel::updateLabels()
375 {
376  emit dataChanged( createIndex( 0, 2 ), createIndex( mRenderer->ranges().size(), 2 ) );
377 }
378 
379 // ------------------------------ View style --------------------------------
380 QgsGraduatedSymbolRendererViewStyle::QgsGraduatedSymbolRendererViewStyle( QWidget *parent )
381  : QgsProxyStyle( parent )
382 {}
383 
384 void QgsGraduatedSymbolRendererViewStyle::drawPrimitive( PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget ) const
385 {
386  if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
387  {
388  QStyleOption opt( *option );
389  opt.rect.setLeft( 0 );
390  // draw always as line above, because we move item to that index
391  opt.rect.setHeight( 0 );
392  if ( widget ) opt.rect.setRight( widget->width() );
393  QProxyStyle::drawPrimitive( element, &opt, painter, widget );
394  return;
395  }
396  QProxyStyle::drawPrimitive( element, option, painter, widget );
397 }
398 
400 
401 // ------------------------------ Widget ------------------------------------
402 
404 {
405  return new QgsGraduatedSymbolRendererWidget( layer, style, renderer );
406 }
407 
408 QgsExpressionContext QgsGraduatedSymbolRendererWidget::createExpressionContext() const
409 {
410  QgsExpressionContext expContext;
414 
415  if ( mContext.mapCanvas() )
416  {
417  expContext << QgsExpressionContextUtils::mapSettingsScope( mContext.mapCanvas()->mapSettings() )
418  << new QgsExpressionContextScope( mContext.mapCanvas()->expressionContextScope() );
419  }
420  else
421  {
423  }
424 
425  if ( vectorLayer() )
426  expContext << QgsExpressionContextUtils::layerScope( vectorLayer() );
427 
428  // additional scopes
429  Q_FOREACH ( const QgsExpressionContextScope &scope, mContext.additionalExpressionContextScopes() )
430  {
431  expContext.appendScope( new QgsExpressionContextScope( scope ) );
432  }
433 
434  return expContext;
435 }
436 
438  : QgsRendererWidget( layer, style )
439 {
440  // try to recognize the previous renderer
441  // (null renderer means "no previous renderer")
442  if ( renderer )
443  {
444  mRenderer.reset( QgsGraduatedSymbolRenderer::convertFromRenderer( renderer ) );
445  }
446  if ( !mRenderer )
447  {
448  mRenderer = qgis::make_unique< QgsGraduatedSymbolRenderer >( QString(), QgsRangeList() );
449  }
450 
451  // setup user interface
452  setupUi( this );
453 
454  cboGraduatedMode->addItem( tr( "Equal Interval" ), QgsGraduatedSymbolRenderer::EqualInterval );
455  cboGraduatedMode->addItem( tr( "Quantile (Equal Count)" ), QgsGraduatedSymbolRenderer::Quantile );
456  cboGraduatedMode->addItem( tr( "Natural Breaks (Jenks)" ), QgsGraduatedSymbolRenderer::Jenks );
457  cboGraduatedMode->addItem( tr( "Standard Deviation" ), QgsGraduatedSymbolRenderer::StdDev );
458  cboGraduatedMode->addItem( tr( "Pretty Breaks" ), QgsGraduatedSymbolRenderer::Pretty );
459 
460  connect( methodComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::methodComboBox_currentIndexChanged );
461  this->layout()->setContentsMargins( 0, 0, 0, 0 );
462 
463  mModel = new QgsGraduatedSymbolRendererModel( this );
464 
465  mExpressionWidget->setFilters( QgsFieldProxyModel::Numeric | QgsFieldProxyModel::Date );
466  mExpressionWidget->setLayer( mLayer );
467 
470 
471  spinPrecision->setMinimum( QgsRendererRangeLabelFormat::MIN_PRECISION );
472  spinPrecision->setMaximum( QgsRendererRangeLabelFormat::MAX_PRECISION );
473 
474  btnColorRamp->setShowRandomColorRamp( true );
475 
476  // set project default color ramp
477  QString defaultColorRamp = QgsProject::instance()->readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/ColorRamp" ), QString() );
478  if ( !defaultColorRamp.isEmpty() )
479  {
480  btnColorRamp->setColorRampFromName( defaultColorRamp );
481  }
482  else
483  {
484  QgsColorRamp *ramp = new QgsGradientColorRamp( QColor( 255, 255, 255 ), QColor( 255, 0, 0 ) );
485  btnColorRamp->setColorRamp( ramp );
486  delete ramp;
487  }
488 
489 
490  viewGraduated->setStyle( new QgsGraduatedSymbolRendererViewStyle( viewGraduated ) );
491 
492  mGraduatedSymbol.reset( QgsSymbol::defaultSymbol( mLayer->geometryType() ) );
493 
494  methodComboBox->blockSignals( true );
495  methodComboBox->addItem( QStringLiteral( "Color" ) );
496  if ( mGraduatedSymbol->type() == QgsSymbol::Marker )
497  {
498  methodComboBox->addItem( QStringLiteral( "Size" ) );
499  minSizeSpinBox->setValue( 1 );
500  maxSizeSpinBox->setValue( 8 );
501  }
502  else if ( mGraduatedSymbol->type() == QgsSymbol::Line )
503  {
504  methodComboBox->addItem( QStringLiteral( "Size" ) );
505  minSizeSpinBox->setValue( .1 );
506  maxSizeSpinBox->setValue( 2 );
507  }
508  methodComboBox->blockSignals( false );
509 
510  connect( mExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsGraduatedSymbolRendererWidget::graduatedColumnChanged );
511  connect( viewGraduated, &QAbstractItemView::doubleClicked, this, &QgsGraduatedSymbolRendererWidget::rangesDoubleClicked );
512  connect( viewGraduated, &QAbstractItemView::clicked, this, &QgsGraduatedSymbolRendererWidget::rangesClicked );
513  connect( viewGraduated, &QTreeView::customContextMenuRequested, this, &QgsGraduatedSymbolRendererWidget::contextMenuViewCategories );
514 
515  connect( btnGraduatedClassify, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
516  connect( btnChangeGraduatedSymbol, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::changeGraduatedSymbol );
517  connect( btnGraduatedDelete, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::deleteClasses );
518  connect( btnDeleteAllClasses, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::deleteAllClasses );
519  connect( btnGraduatedAdd, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::addClass );
520  connect( cbxLinkBoundaries, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::toggleBoundariesLink );
521  connect( mSizeUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsGraduatedSymbolRendererWidget::mSizeUnitWidget_changed );
522 
524 
525  // initialize from previously set renderer
527 
528  // default to collapsed symmetric group for ui simplicity
529  mGroupBoxSymmetric->setCollapsed( true ); //
530 
531  // menus for data-defined rotation/size
532  QMenu *advMenu = new QMenu( this );
533 
534  advMenu->addAction( tr( "Symbol Levels…" ), this, SLOT( showSymbolLevels() ) );
535  if ( mGraduatedSymbol->type() == QgsSymbol::Marker )
536  {
537  QAction *actionDdsLegend = advMenu->addAction( tr( "Data-defined Size Legend…" ) );
538  // only from Qt 5.6 there is convenience addAction() with new style connection
539  connect( actionDdsLegend, &QAction::triggered, this, &QgsGraduatedSymbolRendererWidget::dataDefinedSizeLegend );
540  }
541 
542  btnAdvanced->setMenu( advMenu );
543 
544  mHistogramWidget->setLayer( mLayer );
545  mHistogramWidget->setRenderer( mRenderer.get() );
547  connect( mExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ), mHistogramWidget, &QgsHistogramWidget::setSourceFieldExp );
548 
549  mExpressionWidget->registerExpressionContextGenerator( this );
550 }
551 
552 void QgsGraduatedSymbolRendererWidget::mSizeUnitWidget_changed()
553 {
554  if ( !mGraduatedSymbol ) return;
555  mGraduatedSymbol->setOutputUnit( mSizeUnitWidget->unit() );
556  mGraduatedSymbol->setMapUnitScale( mSizeUnitWidget->getMapUnitScale() );
558  mRenderer->updateSymbols( mGraduatedSymbol.get() );
560 }
561 
563 {
564  delete mModel;
565 }
566 
568 {
569  return mRenderer.get();
570 }
571 
572 // Connect/disconnect event handlers which trigger updating renderer
574 {
575  connect( spinGraduatedClasses, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
576  connect( cboGraduatedMode, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
578  connect( spinPrecision, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
579  connect( cbxTrimTrailingZeroes, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
580  connect( txtLegendFormat, &QLineEdit::textChanged, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
581  connect( minSizeSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
582  connect( maxSizeSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
583 
584  connect( mModel, &QgsGraduatedSymbolRendererModel::rowsMoved, this, &QgsGraduatedSymbolRendererWidget::rowsMoved );
585  connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsGraduatedSymbolRendererWidget::modelDataChanged );
586 
587  connect( mGroupBoxSymmetric, &QGroupBox::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
588  connect( cbxAstride, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
589  connect( cboSymmetryPointForPretty, static_cast<void ( QComboBox::* )( int )>( &QComboBox::activated ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
590  connect( spinSymmetryPointForOtherMethods, static_cast<void( QgsDoubleSpinBox::* )()>( &QgsDoubleSpinBox::editingFinished ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
591 }
592 
593 // Connect/disconnect event handlers which trigger updating renderer
595 {
596  disconnect( spinGraduatedClasses, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
597  disconnect( cboGraduatedMode, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
599  disconnect( spinPrecision, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
600  disconnect( cbxTrimTrailingZeroes, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
601  disconnect( txtLegendFormat, &QLineEdit::textChanged, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
602  disconnect( minSizeSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
603  disconnect( maxSizeSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
604 
605  disconnect( mModel, &QgsGraduatedSymbolRendererModel::rowsMoved, this, &QgsGraduatedSymbolRendererWidget::rowsMoved );
606  disconnect( mModel, &QAbstractItemModel::dataChanged, this, &QgsGraduatedSymbolRendererWidget::modelDataChanged );
607 
608  disconnect( mGroupBoxSymmetric, &QGroupBox::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
609  disconnect( cbxAstride, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
610  disconnect( cboSymmetryPointForPretty, static_cast<void ( QComboBox::* )( int )>( &QComboBox::activated ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
611  disconnect( spinSymmetryPointForOtherMethods, static_cast<void( QgsDoubleSpinBox::* )()>( &QgsDoubleSpinBox::editingFinished ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
612 }
613 
615 {
618  spinSymmetryPointForOtherMethods->setShowClearButton( false );
619 
620  // update UI from the graduated renderer (update combo boxes, view)
621  if ( cboGraduatedMode->findData( mRenderer->mode() ) >= 0 )
622  {
623  cboGraduatedMode->setCurrentIndex( cboGraduatedMode->findData( mRenderer->mode() ) );
624  }
625 
626  // symmetric classification
627  const QgsGraduatedSymbolRenderer::Mode cboMode = static_cast< QgsGraduatedSymbolRenderer::Mode >( cboGraduatedMode->currentData().toInt() );
628  switch ( cboMode )
629  {
632  {
633  mGroupBoxSymmetric->setVisible( true );
634  cbxAstride->setVisible( true );
635  cboSymmetryPointForPretty->setVisible( false );
636  spinSymmetryPointForOtherMethods->setVisible( true );
637  spinSymmetryPointForOtherMethods->setValue( mRenderer->symmetryPoint() );
638  break;
639  }
640 
642  {
643  mGroupBoxSymmetric->setVisible( true );
644  cbxAstride->setVisible( true );
645  spinSymmetryPointForOtherMethods->setVisible( false );
646  cboSymmetryPointForPretty->setVisible( true );
647  cboSymmetryPointForPretty->clear();
648  cboSymmetryPointForPretty->addItems( mRenderer->listForCboPrettyBreaks() );
649  // replace the combobox on the good old value
650  cboSymmetryPointForPretty->setCurrentText( QString::number( mRenderer->symmetryPoint(), 'f', 2 ) );
651  break;
652  }
653 
657  {
658  mGroupBoxSymmetric->setVisible( false );
659  cbxAstride->setVisible( false );
660  cboSymmetryPointForPretty->setVisible( false );
661  spinSymmetryPointForOtherMethods->setVisible( false );
662  spinSymmetryPointForOtherMethods->setValue( mRenderer->symmetryPoint() );
663  break;
664  }
665  }
666 
667  if ( mRenderer->useSymmetricMode() )
668  {
669  mGroupBoxSymmetric->setChecked( true );
670  spinSymmetryPointForOtherMethods->setEnabled( true );
671  cbxAstride->setEnabled( true );
672  cboSymmetryPointForPretty->setEnabled( true );
673  }
674  else
675  {
676  mGroupBoxSymmetric->setChecked( false );
677  spinSymmetryPointForOtherMethods->setEnabled( false );
678  cbxAstride->setEnabled( false );
679  cboSymmetryPointForPretty->setEnabled( false );
680  }
681 
682  if ( mRenderer->astride() )
683  cbxAstride->setChecked( true );
684  else
685  cbxAstride->setChecked( false );
686 
687  // Only update class count if different - otherwise typing value gets very messy
688  int nclasses = mRenderer->ranges().count();
689  if ( nclasses && updateCount )
690  spinGraduatedClasses->setValue( mRenderer->ranges().count() );
691 
692  // set column
693  QString attrName = mRenderer->classAttribute();
694  mExpressionWidget->setField( attrName );
695  mHistogramWidget->setSourceFieldExp( attrName );
696 
697  // set source symbol
698  if ( mRenderer->sourceSymbol() )
699  {
700  mGraduatedSymbol.reset( mRenderer->sourceSymbol()->clone() );
702  }
703 
704  mModel->setRenderer( mRenderer.get() );
705  viewGraduated->setModel( mModel );
706 
707  if ( mGraduatedSymbol )
708  {
709  mSizeUnitWidget->blockSignals( true );
710  mSizeUnitWidget->setUnit( mGraduatedSymbol->outputUnit() );
711  mSizeUnitWidget->setMapUnitScale( mGraduatedSymbol->mapUnitScale() );
712  mSizeUnitWidget->blockSignals( false );
713  }
714 
715  // set source color ramp
716  methodComboBox->blockSignals( true );
717  if ( mRenderer->graduatedMethod() == QgsGraduatedSymbolRenderer::GraduatedColor )
718  {
719  methodComboBox->setCurrentIndex( 0 );
720  if ( mRenderer->sourceColorRamp() )
721  {
722  btnColorRamp->setColorRamp( mRenderer->sourceColorRamp() );
723  }
724  }
725  else
726  {
727  methodComboBox->setCurrentIndex( 1 );
728  if ( !mRenderer->ranges().isEmpty() ) // avoid overriding default size with zeros
729  {
730  minSizeSpinBox->setValue( mRenderer->minSymbolSize() );
731  maxSizeSpinBox->setValue( mRenderer->maxSymbolSize() );
732  }
733  }
734  toggleMethodWidgets( methodComboBox->currentIndex() );
735  methodComboBox->blockSignals( false );
736 
737  QgsRendererRangeLabelFormat labelFormat = mRenderer->labelFormat();
738  txtLegendFormat->setText( labelFormat.format() );
739  spinPrecision->setValue( labelFormat.precision() );
740  cbxTrimTrailingZeroes->setChecked( labelFormat.trimTrailingZeroes() );
741 
742  viewGraduated->resizeColumnToContents( 0 );
743  viewGraduated->resizeColumnToContents( 1 );
744  viewGraduated->resizeColumnToContents( 2 );
745 
746  mHistogramWidget->refresh();
747 
749  emit widgetChanged();
750 }
751 
753 {
754  mRenderer->setClassAttribute( field );
755 }
756 
757 void QgsGraduatedSymbolRendererWidget::methodComboBox_currentIndexChanged( int idx )
758 {
759  toggleMethodWidgets( idx );
760  if ( idx == 0 )
761  {
762  mRenderer->setGraduatedMethod( QgsGraduatedSymbolRenderer::GraduatedColor );
763  QgsColorRamp *ramp = btnColorRamp->colorRamp();
764 
765  if ( !ramp )
766  {
767  QMessageBox::critical( this, tr( "Select Method" ), tr( "No color ramp defined." ) );
768  return;
769  }
770  mRenderer->setSourceColorRamp( ramp );
772  }
773  else
774  {
775  lblColorRamp->setVisible( false );
776  btnColorRamp->setVisible( false );
777  lblSize->setVisible( true );
778  minSizeSpinBox->setVisible( true );
779  lblSize->setVisible( true );
780  maxSizeSpinBox->setVisible( true );
781  mSizeUnitWidget->setVisible( true );
782 
783  mRenderer->setGraduatedMethod( QgsGraduatedSymbolRenderer::GraduatedSize );
784  reapplySizes();
785  }
786 }
787 
788 void QgsGraduatedSymbolRendererWidget::toggleMethodWidgets( int idx )
789 {
790  if ( idx == 0 )
791  {
792  lblColorRamp->setVisible( true );
793  btnColorRamp->setVisible( true );
794  lblSize->setVisible( false );
795  minSizeSpinBox->setVisible( false );
796  lblSizeTo->setVisible( false );
797  maxSizeSpinBox->setVisible( false );
798  mSizeUnitWidget->setVisible( false );
799  }
800  else
801  {
802  lblColorRamp->setVisible( false );
803  btnColorRamp->setVisible( false );
804  lblSize->setVisible( true );
805  minSizeSpinBox->setVisible( true );
806  lblSizeTo->setVisible( true );
807  maxSizeSpinBox->setVisible( true );
808  mSizeUnitWidget->setVisible( true );
809  }
810 }
811 
813 {
814  if ( !mModel )
815  return;
816 
817  mModel->updateSymbology( reset );
818 
820  spinGraduatedClasses->setValue( mRenderer->ranges().count() );
822 
823  emit widgetChanged();
824 }
825 
826 void QgsGraduatedSymbolRendererWidget::cleanUpSymbolSelector( QgsPanelWidget *container )
827 {
828  QgsSymbolSelectorWidget *dlg = qobject_cast<QgsSymbolSelectorWidget *>( container );
829  if ( !dlg )
830  return;
831 
832  delete dlg->symbol();
833 }
834 
835 void QgsGraduatedSymbolRendererWidget::updateSymbolsFromWidget()
836 {
837  QgsSymbolSelectorWidget *dlg = qobject_cast<QgsSymbolSelectorWidget *>( sender() );
838  mGraduatedSymbol.reset( dlg->symbol()->clone() );
839 
841 }
842 
844 {
845  mSizeUnitWidget->blockSignals( true );
846  mSizeUnitWidget->setUnit( mGraduatedSymbol->outputUnit() );
847  mSizeUnitWidget->setMapUnitScale( mGraduatedSymbol->mapUnitScale() );
848  mSizeUnitWidget->blockSignals( false );
849 
850  QItemSelectionModel *m = viewGraduated->selectionModel();
851  QModelIndexList selectedIndexes = m->selectedRows( 1 );
852  if ( m && !selectedIndexes.isEmpty() )
853  {
854  Q_FOREACH ( const QModelIndex &idx, selectedIndexes )
855  {
856  if ( idx.isValid() )
857  {
858  int rangeIdx = idx.row();
859  QgsSymbol *newRangeSymbol = mGraduatedSymbol->clone();
860  if ( selectedIndexes.count() > 1 )
861  {
862  //if updating multiple ranges, retain the existing range colors
863  newRangeSymbol->setColor( mRenderer->ranges().at( rangeIdx ).symbol()->color() );
864  }
865  mRenderer->updateRangeSymbol( rangeIdx, newRangeSymbol );
866  }
867  }
868  }
869  else
870  {
871  mRenderer->updateSymbols( mGraduatedSymbol.get() );
872  }
873 
875  emit widgetChanged();
876 }
877 
878 
880 {
881  QString attrName = mExpressionWidget->currentField();
882  int nclasses = spinGraduatedClasses->value();
883 
884  std::unique_ptr<QgsColorRamp> ramp( btnColorRamp->colorRamp() );
885  if ( !ramp )
886  {
887  QMessageBox::critical( this, tr( "Apply Classification" ), tr( "No color ramp defined." ) );
888  return;
889  }
890 
892  bool useSymmetricMode = false;
893  bool astride = false;
894 
895  int attrNum = mLayer->fields().lookupField( attrName );
896  double minimum = mLayer->minimumValue( attrNum ).toDouble();
897  double maximum = mLayer->maximumValue( attrNum ).toDouble();
898  spinSymmetryPointForOtherMethods->setMinimum( minimum );
899  spinSymmetryPointForOtherMethods->setMaximum( maximum );
900  spinSymmetryPointForOtherMethods->setDecimals( spinPrecision->value() );
901 
902  double symmetryPoint = spinSymmetryPointForOtherMethods->value();
903 
904  const QgsGraduatedSymbolRenderer::Mode cboMode = static_cast< QgsGraduatedSymbolRenderer::Mode >( cboGraduatedMode->currentData().toInt() );
905  switch ( cboMode )
906  {
908  {
910  // knowing that spinSymmetryPointForOtherMethods->value() is automatically put at minimum when out of min-max
911  // using "(maximum-minimum)/100)" to avoid direct comparison of doubles
912  if ( spinSymmetryPointForOtherMethods->value() < ( minimum + ( maximum - minimum ) / 100. ) || spinSymmetryPointForOtherMethods->value() > ( maximum - ( maximum - minimum ) / 100. ) )
913  spinSymmetryPointForOtherMethods->setValue( minimum + ( maximum - minimum ) / 2. );
914 
915  if ( mGroupBoxSymmetric->isChecked() )
916  {
917  useSymmetricMode = true;
918  symmetryPoint = spinSymmetryPointForOtherMethods->value();
919  astride = cbxAstride->isChecked();
920  }
921  break;
922  }
923 
925  {
927  break;
928  }
929 
931  {
933  // knowing that spinSymmetryPointForOtherMethods->value() is automatically put at minimum when out of min-max
934  // using "(maximum-minimum)/100)" to avoid direct comparison of doubles
935  if ( spinSymmetryPointForOtherMethods->value() < ( minimum + ( maximum - minimum ) / 100. ) || spinSymmetryPointForOtherMethods->value() > ( maximum - ( maximum - minimum ) / 100. ) )
936  spinSymmetryPointForOtherMethods->setValue( minimum + ( maximum - minimum ) / 2. );
937 
938  if ( mGroupBoxSymmetric->isChecked() )
939  {
940  useSymmetricMode = true;
941  symmetryPoint = spinSymmetryPointForOtherMethods->value();
942  astride = cbxAstride->isChecked();
943  }
944  break;
945  }
946 
948  {
950  if ( mGroupBoxSymmetric->isChecked() )
951  {
952  useSymmetricMode = true;
953  astride = cbxAstride->isChecked();
954  symmetryPoint = cboSymmetryPointForPretty->currentText().toDouble(); //selected number
955  }
956  break;
957  }
958 
961  {
962  // default should be quantile for now
963  mode = QgsGraduatedSymbolRenderer::Quantile; // Quantile
964  break;
965  }
966  }
967 
968  // Jenks is n^2 complexity, warn for big dataset (more than 50k records)
969  // and give the user the chance to cancel
970  if ( QgsGraduatedSymbolRenderer::Jenks == mode && mLayer->featureCount() > 50000 )
971  {
972  if ( QMessageBox::Cancel == QMessageBox::question( this, tr( "Apply Classification" ), tr( "Natural break classification (Jenks) is O(n2) complexity, your classification may take a long time.\nPress cancel to abort breaks calculation or OK to continue." ), QMessageBox::Cancel, QMessageBox::Ok ) )
973  return;
974  }
975  // create and set new renderer
976  mRenderer->setClassAttribute( attrName );
977  mRenderer->setMode( mode );
978  mRenderer->setUseSymmetricMode( useSymmetricMode );
979  mRenderer->setSymmetryPoint( symmetryPoint );
980  mRenderer->setAstride( astride );
981 
982  if ( methodComboBox->currentIndex() == 0 )
983  {
984  if ( !ramp )
985  {
986  QMessageBox::critical( this, tr( "Apply Classification" ), tr( "No color ramp defined." ) );
987  return;
988  }
989  mRenderer->setSourceColorRamp( ramp.release() );
990  }
991  else
992  {
993  mRenderer->setSourceColorRamp( nullptr );
994  }
995 
996  QApplication::setOverrideCursor( Qt::WaitCursor );
997 
998  mRenderer->updateClasses( mLayer, mode, nclasses, useSymmetricMode, symmetryPoint, astride );
999 
1000  if ( methodComboBox->currentIndex() == 1 )
1001  mRenderer->setSymbolSizes( minSizeSpinBox->value(), maxSizeSpinBox->value() );
1002 
1003  mRenderer->calculateLabelPrecision();
1004  QApplication::restoreOverrideCursor();
1005  // PrettyBreaks and StdDev calculation don't generate exact
1006  // number of classes - leave user interface unchanged for these
1007  updateUiFromRenderer( false );
1008 }
1009 
1011 {
1012  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
1013  if ( !ramp )
1014  return;
1015 
1016  mRenderer->updateColorRamp( ramp.release() );
1017  mRenderer->updateSymbols( mGraduatedSymbol.get() );
1019 }
1020 
1022 {
1023  mRenderer->setSymbolSizes( minSizeSpinBox->value(), maxSizeSpinBox->value() );
1024  mRenderer->updateSymbols( mGraduatedSymbol.get() );
1026 }
1027 
1029 {
1031  std::unique_ptr< QgsSymbol > newSymbol( mGraduatedSymbol->clone() );
1032  if ( panel && panel->dockMode() )
1033  {
1034  QgsSymbolSelectorWidget *dlg = new QgsSymbolSelectorWidget( newSymbol.release(), mStyle, mLayer, panel );
1035  dlg->setContext( mContext );
1036 
1037  connect( dlg, &QgsPanelWidget::widgetChanged, this, &QgsGraduatedSymbolRendererWidget::updateSymbolsFromWidget );
1038  connect( dlg, &QgsPanelWidget::panelAccepted, this, &QgsGraduatedSymbolRendererWidget::cleanUpSymbolSelector );
1040  panel->openPanel( dlg );
1041  }
1042  else
1043  {
1044  QgsSymbolSelectorDialog dlg( newSymbol.get(), mStyle, mLayer, panel );
1045  if ( !dlg.exec() || !newSymbol )
1046  {
1047  return;
1048  }
1049 
1050  mGraduatedSymbol = std::move( newSymbol );
1053  }
1054 }
1055 
1057 {
1058  if ( !mGraduatedSymbol )
1059  return;
1060 
1061  QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( mGraduatedSymbol.get(), btnChangeGraduatedSymbol->iconSize() );
1062  btnChangeGraduatedSymbol->setIcon( icon );
1063 }
1064 
1065 #if 0
1066 int QgsRendererPropertiesDialog::currentRangeRow()
1067 {
1068  QModelIndex idx = viewGraduated->selectionModel()->currentIndex();
1069  if ( !idx.isValid() )
1070  return -1;
1071  return idx.row();
1072 }
1073 #endif
1074 
1076 {
1077  QList<int> rows;
1078  QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1079 
1080  Q_FOREACH ( const QModelIndex &r, selectedRows )
1081  {
1082  if ( r.isValid() )
1083  {
1084  rows.append( r.row() );
1085  }
1086  }
1087  return rows;
1088 }
1089 
1091 {
1093  QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1094  QModelIndexList::const_iterator sIt = selectedRows.constBegin();
1095 
1096  for ( ; sIt != selectedRows.constEnd(); ++sIt )
1097  {
1098  selectedRanges.append( mModel->rendererRange( *sIt ) );
1099  }
1100  return selectedRanges;
1101 }
1102 
1104 {
1105  if ( idx.isValid() && idx.column() == 0 )
1106  changeRangeSymbol( idx.row() );
1107  if ( idx.isValid() && idx.column() == 1 )
1108  changeRange( idx.row() );
1109 }
1110 
1112 {
1113  if ( !idx.isValid() )
1114  mRowSelected = -1;
1115  else
1116  mRowSelected = idx.row();
1117 }
1118 
1120 {
1121 }
1122 
1124 {
1125  std::unique_ptr< QgsSymbol > newSymbol( mRenderer->ranges()[rangeIdx].symbol()->clone() );
1127  if ( panel && panel->dockMode() )
1128  {
1129  // bit tricky here - the widget doesn't take ownership of the symbol. So we need it to last for the duration of the
1130  // panel's existence. Accordingly, just kinda give it ownership here, and clean up in cleanUpSymbolSelector
1131  QgsSymbolSelectorWidget *dlg = new QgsSymbolSelectorWidget( newSymbol.release(), mStyle, mLayer, panel );
1132  dlg->setContext( mContext );
1133  connect( dlg, &QgsPanelWidget::widgetChanged, this, &QgsGraduatedSymbolRendererWidget::updateSymbolsFromWidget );
1134  connect( dlg, &QgsPanelWidget::panelAccepted, this, &QgsGraduatedSymbolRendererWidget::cleanUpSymbolSelector );
1135  openPanel( dlg );
1136  }
1137  else
1138  {
1139  QgsSymbolSelectorDialog dlg( newSymbol.get(), mStyle, mLayer, panel );
1140  dlg.setContext( mContext );
1141  if ( !dlg.exec() || !newSymbol )
1142  {
1143  return;
1144  }
1145 
1146  mGraduatedSymbol = std::move( newSymbol );
1148  }
1149 }
1150 
1152 {
1153  QgsLUDialog dialog( this );
1154 
1155  const QgsRendererRange &range = mRenderer->ranges()[rangeIdx];
1156  // Add arbitrary 2 to number of decimal places to retain a bit extra.
1157  // Ensures users can see if legend is not completely honest!
1158  int decimalPlaces = mRenderer->labelFormat().precision() + 2;
1159  if ( decimalPlaces < 0 ) decimalPlaces = 0;
1160  dialog.setLowerValue( QLocale().toString( range.lowerValue(), 'f', decimalPlaces ) );
1161  dialog.setUpperValue( QLocale().toString( range.upperValue(), 'f', decimalPlaces ) );
1162 
1163  if ( dialog.exec() == QDialog::Accepted )
1164  {
1165  bool ok = false;
1166  double lowerValue = qgsPermissiveToDouble( dialog.lowerValue(), ok );
1167  if ( ! ok )
1168  lowerValue = 0.0;
1169  double upperValue = qgsPermissiveToDouble( dialog.upperValue(), ok );
1170  if ( ! ok )
1171  upperValue = 0.0;
1172  mRenderer->updateRangeUpperValue( rangeIdx, upperValue );
1173  mRenderer->updateRangeLowerValue( rangeIdx, lowerValue );
1174 
1175  //If the boundaries have to stay linked, we update the ranges above and below, as well as their label if needed
1176  if ( cbxLinkBoundaries->isChecked() )
1177  {
1178  if ( rangeIdx > 0 )
1179  {
1180  mRenderer->updateRangeUpperValue( rangeIdx - 1, lowerValue );
1181  }
1182 
1183  if ( rangeIdx < mRenderer->ranges().size() - 1 )
1184  {
1185  mRenderer->updateRangeLowerValue( rangeIdx + 1, upperValue );
1186  }
1187  }
1188  }
1189  mHistogramWidget->refresh();
1190  emit widgetChanged();
1191 }
1192 
1194 {
1195  mModel->addClass( mGraduatedSymbol.get() );
1196  mHistogramWidget->refresh();
1197 }
1198 
1200 {
1201  QList<int> classIndexes = selectedClasses();
1202  mModel->deleteRows( classIndexes );
1203  mHistogramWidget->refresh();
1204 }
1205 
1207 {
1208  mModel->removeAllRows();
1209  mHistogramWidget->refresh();
1210 }
1211 
1213 {
1214  const QgsRangeList &ranges = mRenderer->ranges();
1215  bool ordered = true;
1216  for ( int i = 1; i < ranges.size(); ++i )
1217  {
1218  if ( ranges[i] < ranges[i - 1] )
1219  {
1220  ordered = false;
1221  break;
1222  }
1223  }
1224  return ordered;
1225 }
1226 
1228 {
1229  //If the checkbox controlling the link between boundaries was unchecked and we check it, we have to link the boundaries
1230  //This is done by updating all lower ranges to the upper value of the range above
1231  if ( linked )
1232  {
1233  if ( ! rowsOrdered() )
1234  {
1235  int result = QMessageBox::warning(
1236  this,
1237  tr( "Link Class Boundaries" ),
1238  tr( "Rows will be reordered before linking boundaries. Continue?" ),
1239  QMessageBox::Ok | QMessageBox::Cancel );
1240  if ( result != QMessageBox::Ok )
1241  {
1242  cbxLinkBoundaries->setChecked( false );
1243  return;
1244  }
1245  mRenderer->sortByValue();
1246  }
1247 
1248  // Ok to proceed
1249  for ( int i = 1; i < mRenderer->ranges().size(); ++i )
1250  {
1251  mRenderer->updateRangeLowerValue( i, mRenderer->ranges()[i - 1].upperValue() );
1252  }
1254  }
1255 }
1256 
1258 {
1259  if ( item->column() == 2 )
1260  {
1261  QString label = item->text();
1262  int idx = item->row();
1263  mRenderer->updateRangeLabel( idx, label );
1264  }
1265 }
1266 
1268 {
1270  txtLegendFormat->text(),
1271  spinPrecision->value(),
1272  cbxTrimTrailingZeroes->isChecked() );
1273  mRenderer->setLabelFormat( labelFormat, true );
1274  mModel->updateLabels();
1275 }
1276 
1277 
1279 {
1280  QList<QgsSymbol *> selectedSymbols;
1281 
1282  QItemSelectionModel *m = viewGraduated->selectionModel();
1283  QModelIndexList selectedIndexes = m->selectedRows( 1 );
1284  if ( m && !selectedIndexes.isEmpty() )
1285  {
1286  const QgsRangeList &ranges = mRenderer->ranges();
1287  QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
1288  for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
1289  {
1290  QStringList list = m->model()->data( *indexIt ).toString().split( ' ' );
1291  if ( list.size() < 3 )
1292  {
1293  continue;
1294  }
1295  // Not strictly necessary because the range should have been sanitized already
1296  // after user input, but being permissive never hurts
1297  bool ok = false;
1298  double lowerBound = qgsPermissiveToDouble( list.at( 0 ), ok );
1299  if ( ! ok )
1300  lowerBound = 0.0;
1301  double upperBound = qgsPermissiveToDouble( list.at( 2 ), ok );
1302  if ( ! ok )
1303  upperBound = 0.0;
1304  QgsSymbol *s = findSymbolForRange( lowerBound, upperBound, ranges );
1305  if ( s )
1306  {
1307  selectedSymbols.append( s );
1308  }
1309  }
1310  }
1311  return selectedSymbols;
1312 }
1313 
1314 QgsSymbol *QgsGraduatedSymbolRendererWidget::findSymbolForRange( double lowerBound, double upperBound, const QgsRangeList &ranges ) const
1315 {
1316  int decimalPlaces = mRenderer->labelFormat().precision() + 2;
1317  if ( decimalPlaces < 0 )
1318  decimalPlaces = 0;
1319  double precision = 1.0 / std::pow( 10, decimalPlaces );
1320 
1321  for ( QgsRangeList::const_iterator it = ranges.begin(); it != ranges.end(); ++it )
1322  {
1323  if ( qgsDoubleNear( lowerBound, it->lowerValue(), precision ) && qgsDoubleNear( upperBound, it->upperValue(), precision ) )
1324  {
1325  return it->symbol();
1326  }
1327  }
1328  return nullptr;
1329 }
1330 
1332 {
1333  if ( mModel )
1334  {
1335  mModel->updateSymbology();
1336  }
1337  mHistogramWidget->refresh();
1338  emit widgetChanged();
1339 }
1340 
1342 {
1343  showSymbolLevelsDialog( mRenderer.get() );
1344 }
1345 
1347 {
1348  viewGraduated->selectionModel()->clear();
1349  if ( ! rowsOrdered() )
1350  {
1351  cbxLinkBoundaries->setChecked( false );
1352  }
1353  emit widgetChanged();
1354 }
1355 
1357 {
1358  emit widgetChanged();
1359 }
1360 
1362 {
1363  if ( !event )
1364  {
1365  return;
1366  }
1367 
1368  if ( event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier )
1369  {
1370  mCopyBuffer.clear();
1371  mCopyBuffer = selectedRanges();
1372  }
1373  else if ( event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier )
1374  {
1375  QgsRangeList::const_iterator rIt = mCopyBuffer.constBegin();
1376  for ( ; rIt != mCopyBuffer.constEnd(); ++rIt )
1377  {
1378  mModel->addClass( *rIt );
1379  }
1380  emit widgetChanged();
1381  }
1382 }
1383 
1384 void QgsGraduatedSymbolRendererWidget::dataDefinedSizeLegend()
1385 {
1386  QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( mGraduatedSymbol.get() ); // this should be only enabled for marker symbols
1387  QgsDataDefinedSizeLegendWidget *panel = createDataDefinedSizeLegendWidget( s, mRenderer->dataDefinedSizeLegend() );
1388  if ( panel )
1389  {
1390  connect( panel, &QgsPanelWidget::widgetChanged, this, [ = ]
1391  {
1392  mRenderer->setDataDefinedSizeLegend( panel->dataDefinedSizeLegend() );
1393  emit widgetChanged();
1394  } );
1395  openPanel( panel ); // takes ownership of the panel
1396  }
1397 }
The QgsSpinBox is a spin box with a clear button that will set the value to the defined clear value...
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
static QgsGraduatedSymbolRenderer * convertFromRenderer(const QgsFeatureRenderer *renderer)
creates a QgsGraduatedSymbolRenderer from an existing renderer.
The QgsFieldExpressionWidget class reates a widget to choose fields and edit expressions It contains ...
int precision
QgsSymbol * findSymbolForRange(double lowerBound, double upperBound, const QgsRangeList &ranges) const
bool dockMode()
Returns the dock mode state.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:61
QString upperValue() const
Definition: qgsludialog.cpp:32
QList< QgsRendererRange > QgsRangeList
QgsSymbol * symbol()
Returns the symbol that is currently active in the widget.
void showSymbolLevelsDialog(QgsFeatureRenderer *r)
show a dialog with renderer&#39;s symbol level settings
void colorRampChanged()
Emitted whenever a new color ramp is set for the button.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly...
Base class for renderer settings widgets.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:278
double qgsPermissiveToDouble(QString string, bool &ok)
Converts a string to a double in a permissive way, e.g., allowing for incorrect numbers of digits bet...
Definition: qgis.cpp:97
Abstract base class for color ramps.
Definition: qgscolorramp.h:31
QgsVectorLayer * mLayer
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.
QgsSymbol * symbol() const
Line symbol.
Definition: qgssymbol.h:86
A QProxyStyle subclass which correctly sets the base style to match the QGIS application style...
Definition: qgsproxystyle.h:30
QString readEntry(const QString &scope, const QString &key, const QString &def=QString(), bool *ok=nullptr) const
void setLowerValue(const QString &val)
Definition: qgsludialog.cpp:37
void rangesModified(bool rangesAdded)
Emitted when the user modifies the graduated ranges using the histogram widget.
void panelAccepted(QgsPanelWidget *panel)
Emitted when the panel is accepted by the user.
QList< QgsUnitTypes::RenderUnit > RenderUnitList
List of render units.
Definition: qgsunittypes.h:183
A marker symbol type, for rendering Point and MultiPoint geometries.
Definition: qgssymbol.h:732
static QgsRendererWidget * create(QgsVectorLayer *layer, QgsStyle *style, QgsFeatureRenderer *renderer)
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.
void sortByValue(Qt::SortOrder order=Qt::AscendingOrder)
static QgsSymbol * defaultSymbol(QgsWkbTypes::GeometryType geomType)
Returns a new default symbol for the specified geometry type.
Definition: qgssymbol.cpp:275
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context...
QgsFields fields() const FINAL
Returns the list of fields of this layer.
static QIcon symbolPreviewIcon(QgsSymbol *symbol, QSize size, int padding=0)
Returns an icon preview for a color ramp.
Date or datetime fields.
QgsGraduatedSymbolRendererWidget(QgsVectorLayer *layer, QgsStyle *style, QgsFeatureRenderer *renderer)
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget...
Widget for configuration of appearance of legend for marker symbols with data-defined size...
void applyChangeToSymbol()
Applies current symbol to selected ranges, or to all ranges if none is selected.
void setSourceFieldExp(const QString &fieldOrExp)
Sets the source field or expression to use for values in the histogram.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
points (e.g., for font sizes)
Definition: qgsunittypes.h:117
long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
QList< int > selectedClasses()
Returns a list of indexes for the classes under selection.
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.
QgsFeatureRenderer * renderer() override
Returns pointer to the renderer (no transfer of ownership)
QVariant minimumValue(int index) const FINAL
Returns the minimum value for an attribute column or an invalid variant in case of error...
int lookupField(const QString &fieldName) const
Looks up field&#39;s index from the field name.
Definition: qgsfields.cpp:320
Single scope for storing variables and functions for use within a QgsExpressionContext.
void addClass()
Adds a class manually to the classification.
const QgsRangeList & ranges() const
QgsDataDefinedSizeLegend * dataDefinedSizeLegend() const
Returns configuration as set up in the dialog (may be null). Ownership is passed to the caller...
void widgetChanged()
Emitted when the widget state changes.
void moveClass(int from, int to)
Moves the category at index position from to index position to.
const QgsRendererRangeLabelFormat & labelFormat() const
Returns the label format used to generate default classification labels.
void fieldChanged(const QString &fieldName)
the signal is emitted when the currently selected field changes
Marker symbol.
Definition: qgssymbol.h:85
QList< QgsSymbol * > selectedSymbols() override
Subclasses may provide the capability of changing multiple symbols at once by implementing the follow...
void setContext(const QgsSymbolWidgetContext &context)
Sets the context in which the symbol widget is shown, e.g., the associated map canvas and expression ...
static QgsExpressionContextScope * atlasScope(QgsLayoutAtlas *atlas)
Creates a new scope which contains variables and functions relating to a QgsLayoutAtlas.
static QgsExpressionContextScope * mapSettingsScope(const QgsMapSettings &mapSettings)
Creates a new scope which contains variables and functions relating to a QgsMapSettings object...
QgsSymbolWidgetContext mContext
Context in which widget is shown.
void sortByLabel(Qt::SortOrder order=Qt::AscendingOrder)
virtual QgsSymbol * clone() const =0
Returns a deep copy of this symbol.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:411
void toggleBoundariesLink(bool linked)
Toggle the link between classes boundaries.
void contextMenuViewCategories(QPoint p)
void deleteAllClasses()
Removes all classes from the classification.
void setUpperValue(const QString &val)
Definition: qgsludialog.cpp:42
bool updateRangeRenderState(int rangeIndex, bool render)
QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
bool updateRangeLabel(int rangeIndex, const QString &label)
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
Definition: qgscolorramp.h:139
QVariant maximumValue(int index) const FINAL
Returns the maximum value for an attribute column or an invalid variant in case of error...
Represents a vector layer which manages a vector based data sets.
void deleteClasses()
Removes currently selected classes.
QString lowerValue() const
Definition: qgsludialog.cpp:27
QgsVectorLayer * layer()
Returns the layer currently associated with the widget.
void setColor(const QColor &color)
Sets the color for the symbol.
Definition: qgssymbol.cpp:450