QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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 #include "qgsmarkersymbol.h"
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 ) ? static_cast<Qt::Alignment::Int>( Qt::AlignHCenter ) : static_cast<Qt::Alignment::Int>( 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 = std::make_unique< QgsGraduatedSymbolRenderer >( QString(), QgsRangeList() );
462  if ( renderer )
463  renderer->copyRendererData( mRenderer.get() );
464  }
465 
466  // setup user interface
467  setupUi( this );
468 
469  mSymmetryPointValidator = new QgsDoubleValidator( this );
470  cboSymmetryPoint->setEditable( true );
471  cboSymmetryPoint->setValidator( mSymmetryPointValidator );
472 
473  const QMap<QString, QString> methods = QgsApplication::classificationMethodRegistry()->methodNames();
474  for ( QMap<QString, QString>::const_iterator it = methods.constBegin(); it != methods.constEnd(); ++it )
475  {
476  QIcon icon = QgsApplication::classificationMethodRegistry()->icon( it.value() );
477  cboGraduatedMode->addItem( icon, it.key(), it.value() );
478  }
479 
480  connect( methodComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::methodComboBox_currentIndexChanged );
481  this->layout()->setContentsMargins( 0, 0, 0, 0 );
482 
483  mModel = new QgsGraduatedSymbolRendererModel( this );
484 
485  mExpressionWidget->setFilters( QgsFieldProxyModel::Numeric | QgsFieldProxyModel::Date );
486  mExpressionWidget->setLayer( mLayer );
487 
488  btnChangeGraduatedSymbol->setLayer( mLayer );
489  btnChangeGraduatedSymbol->registerExpressionContextGenerator( this );
490 
493 
494  spinPrecision->setMinimum( QgsClassificationMethod::MIN_PRECISION );
495  spinPrecision->setMaximum( QgsClassificationMethod::MAX_PRECISION );
496  spinPrecision->setClearValue( 4 );
497 
498  spinGraduatedClasses->setShowClearButton( false );
499 
500  btnColorRamp->setShowRandomColorRamp( true );
501 
502  // set project default color ramp
503  QString defaultColorRamp = QgsProject::instance()->readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/ColorRamp" ), QString() );
504  if ( !defaultColorRamp.isEmpty() )
505  {
506  btnColorRamp->setColorRampFromName( defaultColorRamp );
507  }
508  else
509  {
510  QgsColorRamp *ramp = new QgsGradientColorRamp( QColor( 255, 255, 255 ), QColor( 255, 0, 0 ) );
511  btnColorRamp->setColorRamp( ramp );
512  delete ramp;
513  }
514 
515 
516  viewGraduated->setStyle( new QgsGraduatedSymbolRendererViewStyle( viewGraduated ) );
517 
518  mGraduatedSymbol.reset( QgsSymbol::defaultSymbol( mLayer->geometryType() ) );
519  if ( mGraduatedSymbol )
520  {
521  btnChangeGraduatedSymbol->setSymbolType( mGraduatedSymbol->type() );
522  btnChangeGraduatedSymbol->setSymbol( mGraduatedSymbol->clone() );
523 
524  methodComboBox->blockSignals( true );
525  methodComboBox->addItem( tr( "Color" ), ColorMode );
526  switch ( mGraduatedSymbol->type() )
527  {
529  {
530  methodComboBox->addItem( tr( "Size" ), SizeMode );
531  minSizeSpinBox->setValue( 1 );
532  maxSizeSpinBox->setValue( 8 );
533  break;
534  }
536  {
537  methodComboBox->addItem( tr( "Size" ), SizeMode );
538  minSizeSpinBox->setValue( .1 );
539  maxSizeSpinBox->setValue( 2 );
540  break;
541  }
543  {
544  //set button and label invisible to avoid display of a single item combobox
545  methodComboBox->hide();
546  labelMethod->hide();
547  break;
548  }
550  break;
551  }
552  methodComboBox->blockSignals( false );
553  }
554 
555  connect( mExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsGraduatedSymbolRendererWidget::graduatedColumnChanged );
556  connect( viewGraduated, &QAbstractItemView::doubleClicked, this, &QgsGraduatedSymbolRendererWidget::rangesDoubleClicked );
557  connect( viewGraduated, &QAbstractItemView::clicked, this, &QgsGraduatedSymbolRendererWidget::rangesClicked );
558  connect( viewGraduated, &QTreeView::customContextMenuRequested, this, &QgsGraduatedSymbolRendererWidget::contextMenuViewCategories );
559 
560  connect( btnGraduatedClassify, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
561  connect( btnChangeGraduatedSymbol, &QgsSymbolButton::changed, this, &QgsGraduatedSymbolRendererWidget::changeGraduatedSymbol );
562  connect( btnGraduatedDelete, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::deleteClasses );
563  connect( btnDeleteAllClasses, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::deleteAllClasses );
564  connect( btnGraduatedAdd, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::addClass );
565  connect( cbxLinkBoundaries, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::toggleBoundariesLink );
566  connect( mSizeUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsGraduatedSymbolRendererWidget::mSizeUnitWidget_changed );
567 
568  connect( cboGraduatedMode, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::updateMethodParameters );
569 
571 
572  // initialize from previously set renderer
574 
575  // default to collapsed symmetric group for ui simplicity
576  mGroupBoxSymmetric->setCollapsed( true ); //
577 
578  // menus for data-defined rotation/size
579  QMenu *advMenu = new QMenu( this );
580 
581  mActionLevels = advMenu->addAction( tr( "Symbol Levels…" ), this, &QgsGraduatedSymbolRendererWidget::showSymbolLevels );
582  if ( mGraduatedSymbol && mGraduatedSymbol->type() == Qgis::SymbolType::Marker )
583  {
584  QAction *actionDdsLegend = advMenu->addAction( tr( "Data-defined Size Legend…" ) );
585  // only from Qt 5.6 there is convenience addAction() with new style connection
586  connect( actionDdsLegend, &QAction::triggered, this, &QgsGraduatedSymbolRendererWidget::dataDefinedSizeLegend );
587  }
588 
589  btnAdvanced->setMenu( advMenu );
590 
591  mHistogramWidget->setLayer( mLayer );
592  mHistogramWidget->setRenderer( mRenderer.get() );
594  connect( mExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ), mHistogramWidget, &QgsHistogramWidget::setSourceFieldExp );
595 
596  mExpressionWidget->registerExpressionContextGenerator( this );
597 }
598 
599 void QgsGraduatedSymbolRendererWidget::mSizeUnitWidget_changed()
600 {
601  if ( !mGraduatedSymbol )
602  return;
603  mGraduatedSymbol->setOutputUnit( mSizeUnitWidget->unit() );
604  mGraduatedSymbol->setMapUnitScale( mSizeUnitWidget->getMapUnitScale() );
605  mRenderer->updateSymbols( mGraduatedSymbol.get() );
607 }
608 
610 {
611  delete mModel;
612  mParameterWidgetWrappers.clear();
613 }
614 
616 {
617  return mRenderer.get();
618 }
619 
621 {
623  btnChangeGraduatedSymbol->setMapCanvas( context.mapCanvas() );
624  btnChangeGraduatedSymbol->setMessageBar( context.messageBar() );
625 }
626 
628 {
629  delete mActionLevels;
630  mActionLevels = nullptr;
631 }
632 
633 // Connect/disconnect event handlers which trigger updating renderer
635 {
636  connect( spinGraduatedClasses, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
637  connect( cboGraduatedMode, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
639  connect( spinPrecision, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
640  connect( cbxTrimTrailingZeroes, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
641  connect( txtLegendFormat, &QLineEdit::textChanged, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
642  connect( minSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
643  connect( maxSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
644 
645  connect( mModel, &QgsGraduatedSymbolRendererModel::rowsMoved, this, &QgsGraduatedSymbolRendererWidget::rowsMoved );
646  connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsGraduatedSymbolRendererWidget::modelDataChanged );
647 
648  connect( mGroupBoxSymmetric, &QGroupBox::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
649  connect( cbxAstride, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
650  connect( cboSymmetryPoint, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
651  connect( cboSymmetryPoint->lineEdit(), &QLineEdit::editingFinished, this, &QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished );
652 
653  for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
654  {
656  }
657 }
658 
659 // Connect/disconnect event handlers which trigger updating renderer
661 {
662  disconnect( spinGraduatedClasses, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
663  disconnect( cboGraduatedMode, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
665  disconnect( spinPrecision, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
666  disconnect( cbxTrimTrailingZeroes, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
667  disconnect( txtLegendFormat, &QLineEdit::textChanged, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
668  disconnect( minSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
669  disconnect( maxSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
670 
671  disconnect( mModel, &QgsGraduatedSymbolRendererModel::rowsMoved, this, &QgsGraduatedSymbolRendererWidget::rowsMoved );
672  disconnect( mModel, &QAbstractItemModel::dataChanged, this, &QgsGraduatedSymbolRendererWidget::modelDataChanged );
673 
674  disconnect( mGroupBoxSymmetric, &QGroupBox::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
675  disconnect( cbxAstride, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
676  disconnect( cboSymmetryPoint, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
677  disconnect( cboSymmetryPoint->lineEdit(), &QLineEdit::editingFinished, this, &QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished );
678 
679  for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
680  {
682  }
683 }
684 
686 {
688 
689  const QgsClassificationMethod *method = mRenderer->classificationMethod();
690 
691  const QgsRangeList ranges = mRenderer->ranges();
692 
693  // use the breaks for symmetry point
694  int precision = spinPrecision->value() + 2;
695  while ( cboSymmetryPoint->count() )
696  cboSymmetryPoint->removeItem( 0 );
697  for ( int i = 0; i < ranges.count() - 1; i++ )
698  cboSymmetryPoint->addItem( QLocale().toString( ranges.at( i ).upperValue(), 'f', precision ), ranges.at( i ).upperValue() );
699 
700  if ( method )
701  {
702  int idx = cboGraduatedMode->findData( method->id() );
703  if ( idx >= 0 )
704  cboGraduatedMode->setCurrentIndex( idx );
705 
706  mGroupBoxSymmetric->setVisible( method->symmetricModeAvailable() );
707  mGroupBoxSymmetric->setChecked( method->symmetricModeEnabled() );
708  cbxAstride->setChecked( method->symmetryAstride() );
709  if ( method->symmetricModeEnabled() )
710  cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), QLocale().toString( method->symmetryPoint(), 'f', method->labelPrecision() + 2 ) );
711 
712  txtLegendFormat->setText( method->labelFormat() );
713  spinPrecision->setValue( method->labelPrecision() );
714  cbxTrimTrailingZeroes->setChecked( method->labelTrimTrailingZeroes() );
715 
717  for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
718  {
719  const QgsProcessingParameterDefinition *def = ppww->parameterDefinition();
720  QVariant value = method->parameterValues().value( def->name(), def->defaultValueForGui() );
721  ppww->setParameterValue( value, context );
722  }
723  }
724 
725  // Only update class count if different - otherwise typing value gets very messy
726  int nclasses = ranges.count();
727  if ( nclasses && updateCount )
728  {
729  spinGraduatedClasses->setValue( ranges.count() );
730  }
731 
732  // set column
733  QString attrName = mRenderer->classAttribute();
734  mExpressionWidget->setField( attrName );
735  mHistogramWidget->setSourceFieldExp( attrName );
736 
737  // set source symbol
738  if ( mRenderer->sourceSymbol() )
739  {
740  mGraduatedSymbol.reset( mRenderer->sourceSymbol()->clone() );
741  whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mGraduatedSymbol->clone() );
742  }
743 
744  mModel->setRenderer( mRenderer.get() );
745  viewGraduated->setModel( mModel );
746 
747  connect( viewGraduated->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsGraduatedSymbolRendererWidget::selectionChanged );
748 
749  if ( mGraduatedSymbol )
750  {
751  mSizeUnitWidget->blockSignals( true );
752  mSizeUnitWidget->setUnit( mGraduatedSymbol->outputUnit() );
753  mSizeUnitWidget->setMapUnitScale( mGraduatedSymbol->mapUnitScale() );
754  mSizeUnitWidget->blockSignals( false );
755  }
756 
757  // set source color ramp
758  methodComboBox->blockSignals( true );
759  switch ( mRenderer->graduatedMethod() )
760  {
762  {
763  methodComboBox->setCurrentIndex( methodComboBox->findData( ColorMode ) );
764  if ( mRenderer->sourceColorRamp() )
765  {
766  btnColorRamp->setColorRamp( mRenderer->sourceColorRamp() );
767  }
768  break;
769  }
771  {
772  methodComboBox->setCurrentIndex( methodComboBox->findData( SizeMode ) );
773  if ( !mRenderer->ranges().isEmpty() ) // avoid overriding default size with zeros
774  {
775  minSizeSpinBox->setValue( mRenderer->minSymbolSize() );
776  maxSizeSpinBox->setValue( mRenderer->maxSymbolSize() );
777  }
778  break;
779  }
780  }
781  toggleMethodWidgets( static_cast< MethodMode>( methodComboBox->currentData().toInt() ) );
782  methodComboBox->blockSignals( false );
783 
784  viewGraduated->resizeColumnToContents( 0 );
785  viewGraduated->resizeColumnToContents( 1 );
786  viewGraduated->resizeColumnToContents( 2 );
787 
788  mHistogramWidget->refresh();
789 
791  emit widgetChanged();
792 }
793 
795 {
796  mRenderer->setClassAttribute( field );
797 }
798 
799 void QgsGraduatedSymbolRendererWidget::methodComboBox_currentIndexChanged( int )
800 {
801  const MethodMode newMethod = static_cast< MethodMode >( methodComboBox->currentData().toInt() );
802  toggleMethodWidgets( newMethod );
803  switch ( newMethod )
804  {
805  case ColorMode:
806  {
807  mRenderer->setGraduatedMethod( QgsGraduatedSymbolRenderer::GraduatedColor );
808  QgsColorRamp *ramp = btnColorRamp->colorRamp();
809 
810  if ( !ramp )
811  {
812  QMessageBox::critical( this, tr( "Select Method" ), tr( "No color ramp defined." ) );
813  return;
814  }
815  mRenderer->setSourceColorRamp( ramp );
817  break;
818  }
819 
820  case SizeMode:
821  {
822  lblColorRamp->setVisible( false );
823  btnColorRamp->setVisible( false );
824  lblSize->setVisible( true );
825  minSizeSpinBox->setVisible( true );
826  lblSize->setVisible( true );
827  maxSizeSpinBox->setVisible( true );
828  mSizeUnitWidget->setVisible( true );
829 
830  mRenderer->setGraduatedMethod( QgsGraduatedSymbolRenderer::GraduatedSize );
831  reapplySizes();
832  break;
833  }
834  }
835 }
836 
837 void QgsGraduatedSymbolRendererWidget::updateMethodParameters()
838 {
839  clearParameterWidgets();
840 
841  const QString methodId = cboGraduatedMode->currentData().toString();
843  Q_ASSERT( method );
844 
845  // need more context?
847 
848  for ( const QgsProcessingParameterDefinition *def : method->parameterDefinitions() )
849  {
851  mParametersLayout->addRow( ppww->createWrappedLabel(), ppww->createWrappedWidget( context ) );
852 
853  QVariant value = method->parameterValues().value( def->name(), def->defaultValueForGui() );
854  ppww->setParameterValue( value, context );
855 
857 
858  mParameterWidgetWrappers.push_back( std::unique_ptr<QgsAbstractProcessingParameterWidgetWrapper>( ppww ) );
859  }
860 }
861 
862 void QgsGraduatedSymbolRendererWidget::toggleMethodWidgets( MethodMode mode )
863 {
864  switch ( mode )
865  {
866  case ColorMode:
867  {
868  lblColorRamp->setVisible( true );
869  btnColorRamp->setVisible( true );
870  lblSize->setVisible( false );
871  minSizeSpinBox->setVisible( false );
872  lblSizeTo->setVisible( false );
873  maxSizeSpinBox->setVisible( false );
874  mSizeUnitWidget->setVisible( false );
875  break;
876  }
877 
878  case SizeMode:
879  {
880  lblColorRamp->setVisible( false );
881  btnColorRamp->setVisible( false );
882  lblSize->setVisible( true );
883  minSizeSpinBox->setVisible( true );
884  lblSizeTo->setVisible( true );
885  maxSizeSpinBox->setVisible( true );
886  mSizeUnitWidget->setVisible( true );
887  break;
888  }
889  }
890 }
891 
892 void QgsGraduatedSymbolRendererWidget::clearParameterWidgets()
893 {
894  while ( mParametersLayout->rowCount() )
895  {
896  QFormLayout::TakeRowResult row = mParametersLayout->takeRow( 0 );
897  for ( QLayoutItem *item : QList<QLayoutItem *>( {row.labelItem, row.fieldItem} ) )
898  if ( item )
899  {
900  if ( item->widget() )
901  item->widget()->deleteLater();
902  delete item;
903  }
904  }
905  mParameterWidgetWrappers.clear();
906 }
907 
909 {
910  if ( !mModel )
911  return;
912 
913  mModel->updateSymbology();
914 
916  spinGraduatedClasses->setValue( mRenderer->ranges().count() );
918 
919  emit widgetChanged();
920 }
921 
923 {
924  for ( const QgsLegendSymbolItem &legendSymbol : levels )
925  {
926  QgsSymbol *sym = legendSymbol.symbol();
927  for ( int layer = 0; layer < sym->symbolLayerCount(); layer++ )
928  {
929  mRenderer->setLegendSymbolItem( legendSymbol.ruleKey(), sym->clone() );
930  }
931  }
932  mRenderer->setUsingSymbolLevels( enabled );
933  mModel->updateSymbology();
934  emit widgetChanged();
935 }
936 
937 void QgsGraduatedSymbolRendererWidget::cleanUpSymbolSelector( QgsPanelWidget *container )
938 {
939  QgsSymbolSelectorWidget *dlg = qobject_cast<QgsSymbolSelectorWidget *>( container );
940  if ( !dlg )
941  return;
942 
943  delete dlg->symbol();
944 }
945 
946 void QgsGraduatedSymbolRendererWidget::updateSymbolsFromWidget()
947 {
948  QgsSymbolSelectorWidget *dlg = qobject_cast<QgsSymbolSelectorWidget *>( sender() );
949  mGraduatedSymbol.reset( dlg->symbol()->clone() );
950 
952 }
953 
955 {
956  mSizeUnitWidget->blockSignals( true );
957  mSizeUnitWidget->setUnit( mGraduatedSymbol->outputUnit() );
958  mSizeUnitWidget->setMapUnitScale( mGraduatedSymbol->mapUnitScale() );
959  mSizeUnitWidget->blockSignals( false );
960 
961  QItemSelectionModel *m = viewGraduated->selectionModel();
962  QModelIndexList selectedIndexes = m->selectedRows( 1 );
963  if ( !selectedIndexes.isEmpty() )
964  {
965  const auto constSelectedIndexes = selectedIndexes;
966  for ( const QModelIndex &idx : constSelectedIndexes )
967  {
968  if ( idx.isValid() )
969  {
970  int rangeIdx = idx.row();
971  QgsSymbol *newRangeSymbol = mGraduatedSymbol->clone();
972  if ( selectedIndexes.count() > 1 )
973  {
974  //if updating multiple ranges, retain the existing range colors
975  newRangeSymbol->setColor( mRenderer->ranges().at( rangeIdx ).symbol()->color() );
976  }
977  mRenderer->updateRangeSymbol( rangeIdx, newRangeSymbol );
978  }
979  }
980  }
981  else
982  {
983  mRenderer->updateSymbols( mGraduatedSymbol.get() );
984  }
985 
987  emit widgetChanged();
988 }
989 
990 void QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished( )
991 {
992  const QString text = cboSymmetryPoint->lineEdit()->text();
993  int index = cboSymmetryPoint->findText( text );
994  if ( index != -1 )
995  {
996  cboSymmetryPoint->setCurrentIndex( index );
997  }
998  else
999  {
1000  cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), text );
1002  }
1003 }
1004 
1005 
1007 {
1008 
1009  QgsTemporaryCursorOverride override( Qt::WaitCursor );
1010  QString attrName = mExpressionWidget->currentField();
1011  int nclasses = spinGraduatedClasses->value();
1012 
1013  const QString methodId = cboGraduatedMode->currentData().toString();
1015  Q_ASSERT( method );
1016 
1017  int attrNum = mLayer->fields().lookupField( attrName );
1018 
1019  QVariant minVal;
1020  QVariant maxVal;
1021  mLayer->minimumAndMaximumValue( attrNum, minVal, maxVal );
1022 
1023  double minimum = minVal.toDouble();
1024  double maximum = maxVal.toDouble();
1025  mSymmetryPointValidator->setBottom( minimum );
1026  mSymmetryPointValidator->setTop( maximum );
1027  mSymmetryPointValidator->setMaxDecimals( spinPrecision->value() );
1028 
1029  if ( method->id() == QgsClassificationEqualInterval::METHOD_ID ||
1031  {
1032  // knowing that spinSymmetryPointForOtherMethods->value() is automatically put at minimum when out of min-max
1033  // using "(maximum-minimum)/100)" to avoid direct comparison of doubles
1034  double currentValue = QgsDoubleValidator::toDouble( cboSymmetryPoint->currentText() );
1035  if ( currentValue < ( minimum + ( maximum - minimum ) / 100. ) || currentValue > ( maximum - ( maximum - minimum ) / 100. ) )
1036  cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), QLocale().toString( minimum + ( maximum - minimum ) / 2., 'f', method->labelPrecision() + 2 ) );
1037  }
1038 
1039  if ( mGroupBoxSymmetric->isChecked() )
1040  {
1041  double symmetryPoint = QgsDoubleValidator::toDouble( cboSymmetryPoint->currentText() );
1042  bool astride = cbxAstride->isChecked();
1043  method->setSymmetricMode( true, symmetryPoint, astride );
1044  }
1045 
1046  QVariantMap parameterValues;
1047  for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
1048  parameterValues.insert( ppww->parameterDefinition()->name(), ppww->parameterValue() );
1049  method->setParameterValues( parameterValues );
1050 
1051  // set method to renderer
1052  mRenderer->setClassificationMethod( method );
1053 
1054  // create and set new renderer
1055  mRenderer->setClassAttribute( attrName );
1056 
1057  // If complexity >= oN^2, warn for big dataset (more than 50k records)
1058  // and give the user the chance to cancel
1059  if ( method->codeComplexity() > 1 && mLayer->featureCount() > 50000 )
1060  {
1061  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 ) )
1062  {
1063  return;
1064  }
1065  }
1066 
1067  if ( methodComboBox->currentData() == ColorMode )
1068  {
1069  std::unique_ptr<QgsColorRamp> ramp( btnColorRamp->colorRamp() );
1070  if ( !ramp )
1071  {
1072  QMessageBox::critical( this, tr( "Apply Classification" ), tr( "No color ramp defined." ) );
1073  return;
1074  }
1075  mRenderer->setSourceColorRamp( ramp.release() );
1076  }
1077  else
1078  {
1079  mRenderer->setSourceColorRamp( nullptr );
1080  }
1081 
1082  mRenderer->updateClasses( mLayer, nclasses );
1083 
1084  if ( methodComboBox->currentData() == SizeMode )
1085  mRenderer->setSymbolSizes( minSizeSpinBox->value(), maxSizeSpinBox->value() );
1086 
1087  mRenderer->calculateLabelPrecision();
1088  // PrettyBreaks and StdDev calculation don't generate exact
1089  // number of classes - leave user interface unchanged for these
1090  updateUiFromRenderer( false );
1091 }
1092 
1094 {
1095  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
1096  if ( !ramp )
1097  return;
1098 
1099  mRenderer->updateColorRamp( ramp.release() );
1100  mRenderer->updateSymbols( mGraduatedSymbol.get() );
1102 }
1103 
1105 {
1106  mRenderer->setSymbolSizes( minSizeSpinBox->value(), maxSizeSpinBox->value() );
1107  mRenderer->updateSymbols( mGraduatedSymbol.get() );
1109 }
1110 
1111 #if 0
1112 int QgsRendererPropertiesDialog::currentRangeRow()
1113 {
1114  QModelIndex idx = viewGraduated->selectionModel()->currentIndex();
1115  if ( !idx.isValid() )
1116  return -1;
1117  return idx.row();
1118 }
1119 #endif
1120 
1122 {
1123  QList<int> rows;
1124  QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1125 
1126  const auto constSelectedRows = selectedRows;
1127  for ( const QModelIndex &r : constSelectedRows )
1128  {
1129  if ( r.isValid() )
1130  {
1131  rows.append( r.row() );
1132  }
1133  }
1134  return rows;
1135 }
1136 
1138 {
1140  QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1141  QModelIndexList::const_iterator sIt = selectedRows.constBegin();
1142 
1143  for ( ; sIt != selectedRows.constEnd(); ++sIt )
1144  {
1145  selectedRanges.append( mModel->rendererRange( *sIt ) );
1146  }
1147  return selectedRanges;
1148 }
1149 
1151 {
1152  if ( idx.isValid() && idx.column() == 0 )
1153  changeRangeSymbol( idx.row() );
1154  if ( idx.isValid() && idx.column() == 1 )
1155  changeRange( idx.row() );
1156 }
1157 
1159 {
1160  if ( !idx.isValid() )
1161  mRowSelected = -1;
1162  else
1163  mRowSelected = idx.row();
1164 }
1165 
1167 {
1168 }
1169 
1171 {
1172  const QgsRendererRange &range = mRenderer->ranges()[rangeIdx];
1173  std::unique_ptr< QgsSymbol > newSymbol( range.symbol()->clone() );
1175  if ( panel && panel->dockMode() )
1176  {
1177  // bit tricky here - the widget doesn't take ownership of the symbol. So we need it to last for the duration of the
1178  // panel's existence. Accordingly, just kinda give it ownership here, and clean up in cleanUpSymbolSelector
1179  QgsSymbolSelectorWidget *dlg = new QgsSymbolSelectorWidget( newSymbol.release(), mStyle, mLayer, panel );
1180  dlg->setContext( mContext );
1181  dlg->setPanelTitle( range.label() );
1182  connect( dlg, &QgsPanelWidget::widgetChanged, this, &QgsGraduatedSymbolRendererWidget::updateSymbolsFromWidget );
1183  connect( dlg, &QgsPanelWidget::panelAccepted, this, &QgsGraduatedSymbolRendererWidget::cleanUpSymbolSelector );
1184  openPanel( dlg );
1185  }
1186  else
1187  {
1188  QgsSymbolSelectorDialog dlg( newSymbol.get(), mStyle, mLayer, panel );
1189  dlg.setContext( mContext );
1190  if ( !dlg.exec() || !newSymbol )
1191  {
1192  return;
1193  }
1194 
1195  mGraduatedSymbol = std::move( newSymbol );
1196  whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mGraduatedSymbol->clone() );
1198  }
1199 }
1200 
1202 {
1203  QgsLUDialog dialog( this );
1204 
1205  const QgsRendererRange &range = mRenderer->ranges()[rangeIdx];
1206  // Add arbitrary 2 to number of decimal places to retain a bit extra.
1207  // Ensures users can see if legend is not completely honest!
1208  int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
1209  if ( decimalPlaces < 0 ) decimalPlaces = 0;
1210  dialog.setLowerValue( QLocale().toString( range.lowerValue(), 'f', decimalPlaces ) );
1211  dialog.setUpperValue( QLocale().toString( range.upperValue(), 'f', decimalPlaces ) );
1212 
1213  if ( dialog.exec() == QDialog::Accepted )
1214  {
1215  bool ok = false;
1216  double lowerValue = qgsPermissiveToDouble( dialog.lowerValue(), ok );
1217  if ( ! ok )
1218  lowerValue = 0.0;
1219  double upperValue = qgsPermissiveToDouble( dialog.upperValue(), ok );
1220  if ( ! ok )
1221  upperValue = 0.0;
1222  mRenderer->updateRangeUpperValue( rangeIdx, upperValue );
1223  mRenderer->updateRangeLowerValue( rangeIdx, lowerValue );
1224 
1225  //If the boundaries have to stay linked, we update the ranges above and below, as well as their label if needed
1226  if ( cbxLinkBoundaries->isChecked() )
1227  {
1228  if ( rangeIdx > 0 )
1229  {
1230  mRenderer->updateRangeUpperValue( rangeIdx - 1, lowerValue );
1231  }
1232 
1233  if ( rangeIdx < mRenderer->ranges().size() - 1 )
1234  {
1235  mRenderer->updateRangeLowerValue( rangeIdx + 1, upperValue );
1236  }
1237  }
1238  }
1239  mHistogramWidget->refresh();
1240  emit widgetChanged();
1241 }
1242 
1244 {
1245  mModel->addClass( mGraduatedSymbol.get() );
1246  mHistogramWidget->refresh();
1247  emit widgetChanged();
1248 
1249 }
1250 
1252 {
1253  QList<int> classIndexes = selectedClasses();
1254  mModel->deleteRows( classIndexes );
1255  mHistogramWidget->refresh();
1256  emit widgetChanged();
1257 }
1258 
1260 {
1261  mModel->removeAllRows();
1262  mHistogramWidget->refresh();
1263  emit widgetChanged();
1264 }
1265 
1267 {
1268  const QgsRangeList &ranges = mRenderer->ranges();
1269  bool ordered = true;
1270  for ( int i = 1; i < ranges.size(); ++i )
1271  {
1272  if ( ranges[i] < ranges[i - 1] )
1273  {
1274  ordered = false;
1275  break;
1276  }
1277  }
1278  return ordered;
1279 }
1280 
1282 {
1283  //If the checkbox controlling the link between boundaries was unchecked and we check it, we have to link the boundaries
1284  //This is done by updating all lower ranges to the upper value of the range above
1285  if ( linked )
1286  {
1287  if ( ! rowsOrdered() )
1288  {
1289  int result = QMessageBox::warning(
1290  this,
1291  tr( "Link Class Boundaries" ),
1292  tr( "Rows will be reordered before linking boundaries. Continue?" ),
1293  QMessageBox::Ok | QMessageBox::Cancel );
1294  if ( result != QMessageBox::Ok )
1295  {
1296  cbxLinkBoundaries->setChecked( false );
1297  return;
1298  }
1299  mRenderer->sortByValue();
1300  }
1301 
1302  // Ok to proceed
1303  for ( int i = 1; i < mRenderer->ranges().size(); ++i )
1304  {
1305  mRenderer->updateRangeLowerValue( i, mRenderer->ranges()[i - 1].upperValue() );
1306  }
1308  }
1309 }
1310 
1312 {
1313  if ( item->column() == 2 )
1314  {
1315  QString label = item->text();
1316  int idx = item->row();
1317  mRenderer->updateRangeLabel( idx, label );
1318  }
1319 }
1320 
1322 {
1323  mRenderer->classificationMethod()->setLabelFormat( txtLegendFormat->text() );
1324  mRenderer->classificationMethod()->setLabelPrecision( spinPrecision->value() );
1325  mRenderer->classificationMethod()->setLabelTrimTrailingZeroes( cbxTrimTrailingZeroes->isChecked() );
1326  mRenderer->updateRangeLabels();
1327  mModel->updateLabels();
1328 }
1329 
1330 
1332 {
1333  QList<QgsSymbol *> selectedSymbols;
1334 
1335  QItemSelectionModel *m = viewGraduated->selectionModel();
1336  QModelIndexList selectedIndexes = m->selectedRows( 1 );
1337  if ( !selectedIndexes.isEmpty() )
1338  {
1339  const QgsRangeList &ranges = mRenderer->ranges();
1340  QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
1341  for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
1342  {
1343  QStringList list = m->model()->data( *indexIt ).toString().split( ' ' );
1344  if ( list.size() < 3 )
1345  {
1346  continue;
1347  }
1348  // Not strictly necessary because the range should have been sanitized already
1349  // after user input, but being permissive never hurts
1350  bool ok = false;
1351  double lowerBound = qgsPermissiveToDouble( list.at( 0 ), ok );
1352  if ( ! ok )
1353  lowerBound = 0.0;
1354  double upperBound = qgsPermissiveToDouble( list.at( 2 ), ok );
1355  if ( ! ok )
1356  upperBound = 0.0;
1357  QgsSymbol *s = findSymbolForRange( lowerBound, upperBound, ranges );
1358  if ( s )
1359  {
1360  selectedSymbols.append( s );
1361  }
1362  }
1363  }
1364  return selectedSymbols;
1365 }
1366 
1367 QgsSymbol *QgsGraduatedSymbolRendererWidget::findSymbolForRange( double lowerBound, double upperBound, const QgsRangeList &ranges ) const
1368 {
1369  int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
1370  if ( decimalPlaces < 0 )
1371  decimalPlaces = 0;
1372  double precision = 1.0 / std::pow( 10, decimalPlaces );
1373 
1374  for ( QgsRangeList::const_iterator it = ranges.begin(); it != ranges.end(); ++it )
1375  {
1376  if ( qgsDoubleNear( lowerBound, it->lowerValue(), precision ) && qgsDoubleNear( upperBound, it->upperValue(), precision ) )
1377  {
1378  return it->symbol();
1379  }
1380  }
1381  return nullptr;
1382 }
1383 
1385 {
1386  if ( mModel )
1387  {
1388  mModel->updateSymbology();
1389  }
1390  mHistogramWidget->refresh();
1391  emit widgetChanged();
1392 }
1393 
1395 {
1396  showSymbolLevelsDialog( mRenderer.get() );
1397 }
1398 
1400 {
1401  viewGraduated->selectionModel()->clear();
1402  if ( ! rowsOrdered() )
1403  {
1404  cbxLinkBoundaries->setChecked( false );
1405  }
1406  emit widgetChanged();
1407 }
1408 
1410 {
1411  emit widgetChanged();
1412 }
1413 
1415 {
1416  if ( !event )
1417  {
1418  return;
1419  }
1420 
1421  if ( event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier )
1422  {
1423  mCopyBuffer.clear();
1424  mCopyBuffer = selectedRanges();
1425  }
1426  else if ( event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier )
1427  {
1428  QgsRangeList::const_iterator rIt = mCopyBuffer.constBegin();
1429  for ( ; rIt != mCopyBuffer.constEnd(); ++rIt )
1430  {
1431  mModel->addClass( *rIt );
1432  }
1433  emit widgetChanged();
1434  }
1435 }
1436 
1437 void QgsGraduatedSymbolRendererWidget::selectionChanged( const QItemSelection &, const QItemSelection & )
1438 {
1439  const QgsRangeList ranges = selectedRanges();
1440  if ( !ranges.isEmpty() )
1441  {
1442  whileBlocking( btnChangeGraduatedSymbol )->setSymbol( ranges.at( 0 ).symbol()->clone() );
1443  }
1444  else if ( mRenderer->sourceSymbol() )
1445  {
1446  whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mRenderer->sourceSymbol()->clone() );
1447  }
1448  btnChangeGraduatedSymbol->setDialogTitle( ranges.size() == 1 ? ranges.at( 0 ).label() : tr( "Symbol Settings" ) );
1449 }
1450 
1451 void QgsGraduatedSymbolRendererWidget::dataDefinedSizeLegend()
1452 {
1453  QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( mGraduatedSymbol.get() ); // this should be only enabled for marker symbols
1454  QgsDataDefinedSizeLegendWidget *panel = createDataDefinedSizeLegendWidget( s, mRenderer->dataDefinedSizeLegend() );
1455  if ( panel )
1456  {
1457  connect( panel, &QgsPanelWidget::widgetChanged, this, [ = ]
1458  {
1459  mRenderer->setDataDefinedSizeLegend( panel->dataDefinedSizeLegend() );
1460  emit widgetChanged();
1461  } );
1462  openPanel( panel ); // takes ownership of the panel
1463  }
1464 }
1465 
1466 void QgsGraduatedSymbolRendererWidget::changeGraduatedSymbol()
1467 {
1468  mGraduatedSymbol.reset( btnChangeGraduatedSymbol->symbol()->clone() );
1470 }
1471 
1473 {
1474  std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
1475  if ( !tempSymbol )
1476  return;
1477 
1478  const QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1479  for ( const QModelIndex &index : selectedRows )
1480  {
1481  if ( !index.isValid() )
1482  continue;
1483 
1484  const int row = index.row();
1485  if ( !mRenderer || mRenderer->ranges().size() <= row )
1486  continue;
1487 
1488  if ( mRenderer->ranges().at( row ).symbol()->type() != tempSymbol->type() )
1489  continue;
1490 
1491  std::unique_ptr< QgsSymbol > newCatSymbol( tempSymbol->clone() );
1492  if ( selectedRows.count() > 1 )
1493  {
1494  //if updating multiple ranges, retain the existing category colors
1495  newCatSymbol->setColor( mRenderer->ranges().at( row ).symbol()->color() );
1496  }
1497 
1498  mRenderer->updateRangeSymbol( row, newCatSymbol.release() );
1499  }
1500  emit widgetChanged();
1501 }
@ 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.
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.
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:344
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
Definition: qgscolorramp.h:153
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
static QgsProcessingGuiRegistry * processingGuiRegistry()
Returns the global processing gui registry, used for registering the GUI behavior of processing algor...
Definition: qgsgui.cpp:133
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 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:467
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:38
static QgsSymbol * defaultSymbol(QgsWkbTypes::GeometryType geomType)
Returns a new default symbol for the specified geometry type.
Definition: qgssymbol.cpp:355
void setColor(const QColor &color)
Sets the color for the symbol.
Definition: qgssymbol.cpp:541
int symbolLayerCount() const
Returns the total number of symbol layers contained in the symbol.
Definition: qgssymbol.h:160
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:1246
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:1185
const QgsField & field
Definition: qgsfield.h:463
QList< QgsLegendSymbolItem > QgsLegendSymbolList
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QList< QgsRendererRange > QgsRangeList
int precision