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