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