QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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"
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  mRenderer->updateRangeUpperValue( rangeIdx, dialog.upperValueDouble() );
1216  mRenderer->updateRangeLowerValue( rangeIdx, dialog.lowerValueDouble() );
1217 
1218  //If the boundaries have to stay linked, we update the ranges above and below, as well as their label if needed
1219  if ( cbxLinkBoundaries->isChecked() )
1220  {
1221  if ( rangeIdx > 0 )
1222  {
1223  mRenderer->updateRangeUpperValue( rangeIdx - 1, dialog.lowerValueDouble() );
1224  }
1225 
1226  if ( rangeIdx < mRenderer->ranges().size() - 1 )
1227  {
1228  mRenderer->updateRangeLowerValue( rangeIdx + 1, dialog.upperValueDouble() );
1229  }
1230  }
1231  }
1232  mHistogramWidget->refresh();
1233  emit widgetChanged();
1234 }
1235 
1237 {
1238  mModel->addClass( mGraduatedSymbol.get() );
1239  mHistogramWidget->refresh();
1240  emit widgetChanged();
1241 
1242 }
1243 
1245 {
1246  QList<int> classIndexes = selectedClasses();
1247  mModel->deleteRows( classIndexes );
1248  mHistogramWidget->refresh();
1249  emit widgetChanged();
1250 }
1251 
1253 {
1254  mModel->removeAllRows();
1255  mHistogramWidget->refresh();
1256  emit widgetChanged();
1257 }
1258 
1260 {
1261  const QgsRangeList &ranges = mRenderer->ranges();
1262  bool ordered = true;
1263  for ( int i = 1; i < ranges.size(); ++i )
1264  {
1265  if ( ranges[i] < ranges[i - 1] )
1266  {
1267  ordered = false;
1268  break;
1269  }
1270  }
1271  return ordered;
1272 }
1273 
1275 {
1276  //If the checkbox controlling the link between boundaries was unchecked and we check it, we have to link the boundaries
1277  //This is done by updating all lower ranges to the upper value of the range above
1278  if ( linked )
1279  {
1280  if ( ! rowsOrdered() )
1281  {
1282  int result = QMessageBox::warning(
1283  this,
1284  tr( "Link Class Boundaries" ),
1285  tr( "Rows will be reordered before linking boundaries. Continue?" ),
1286  QMessageBox::Ok | QMessageBox::Cancel );
1287  if ( result != QMessageBox::Ok )
1288  {
1289  cbxLinkBoundaries->setChecked( false );
1290  return;
1291  }
1292  mRenderer->sortByValue();
1293  }
1294 
1295  // Ok to proceed
1296  for ( int i = 1; i < mRenderer->ranges().size(); ++i )
1297  {
1298  mRenderer->updateRangeLowerValue( i, mRenderer->ranges()[i - 1].upperValue() );
1299  }
1301  }
1302 }
1303 
1305 {
1306  if ( item->column() == 2 )
1307  {
1308  QString label = item->text();
1309  int idx = item->row();
1310  mRenderer->updateRangeLabel( idx, label );
1311  }
1312 }
1313 
1315 {
1316  mRenderer->classificationMethod()->setLabelFormat( txtLegendFormat->text() );
1317  mRenderer->classificationMethod()->setLabelPrecision( spinPrecision->value() );
1318  mRenderer->classificationMethod()->setLabelTrimTrailingZeroes( cbxTrimTrailingZeroes->isChecked() );
1319  mRenderer->updateRangeLabels();
1320  mModel->updateLabels();
1321 }
1322 
1323 
1325 {
1326  QList<QgsSymbol *> selectedSymbols;
1327 
1328  QItemSelectionModel *m = viewGraduated->selectionModel();
1329  QModelIndexList selectedIndexes = m->selectedRows( 1 );
1330  if ( !selectedIndexes.isEmpty() )
1331  {
1332  const QgsRangeList &ranges = mRenderer->ranges();
1333  QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
1334  for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
1335  {
1336  QStringList list = m->model()->data( *indexIt ).toString().split( ' ' );
1337  if ( list.size() < 3 )
1338  {
1339  continue;
1340  }
1341  // Not strictly necessary because the range should have been sanitized already
1342  // after user input, but being permissive never hurts
1343  bool ok = false;
1344  double lowerBound = qgsPermissiveToDouble( list.at( 0 ), ok );
1345  if ( ! ok )
1346  lowerBound = 0.0;
1347  double upperBound = qgsPermissiveToDouble( list.at( 2 ), ok );
1348  if ( ! ok )
1349  upperBound = 0.0;
1350  QgsSymbol *s = findSymbolForRange( lowerBound, upperBound, ranges );
1351  if ( s )
1352  {
1353  selectedSymbols.append( s );
1354  }
1355  }
1356  }
1357  return selectedSymbols;
1358 }
1359 
1360 QgsSymbol *QgsGraduatedSymbolRendererWidget::findSymbolForRange( double lowerBound, double upperBound, const QgsRangeList &ranges ) const
1361 {
1362  int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
1363  if ( decimalPlaces < 0 )
1364  decimalPlaces = 0;
1365  double precision = 1.0 / std::pow( 10, decimalPlaces );
1366 
1367  for ( QgsRangeList::const_iterator it = ranges.begin(); it != ranges.end(); ++it )
1368  {
1369  if ( qgsDoubleNear( lowerBound, it->lowerValue(), precision ) && qgsDoubleNear( upperBound, it->upperValue(), precision ) )
1370  {
1371  return it->symbol();
1372  }
1373  }
1374  return nullptr;
1375 }
1376 
1378 {
1379  if ( mModel )
1380  {
1381  mModel->updateSymbology();
1382  }
1383  mHistogramWidget->refresh();
1384  emit widgetChanged();
1385 }
1386 
1388 {
1389  showSymbolLevelsDialog( mRenderer.get() );
1390 }
1391 
1393 {
1394  viewGraduated->selectionModel()->clear();
1395  if ( ! rowsOrdered() )
1396  {
1397  cbxLinkBoundaries->setChecked( false );
1398  }
1399  emit widgetChanged();
1400 }
1401 
1403 {
1404  emit widgetChanged();
1405 }
1406 
1408 {
1409  if ( !event )
1410  {
1411  return;
1412  }
1413 
1414  if ( event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier )
1415  {
1416  mCopyBuffer.clear();
1417  mCopyBuffer = selectedRanges();
1418  }
1419  else if ( event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier )
1420  {
1421  QgsRangeList::const_iterator rIt = mCopyBuffer.constBegin();
1422  for ( ; rIt != mCopyBuffer.constEnd(); ++rIt )
1423  {
1424  mModel->addClass( *rIt );
1425  }
1426  emit widgetChanged();
1427  }
1428 }
1429 
1430 void QgsGraduatedSymbolRendererWidget::selectionChanged( const QItemSelection &, const QItemSelection & )
1431 {
1432  const QgsRangeList ranges = selectedRanges();
1433  if ( !ranges.isEmpty() )
1434  {
1435  whileBlocking( btnChangeGraduatedSymbol )->setSymbol( ranges.at( 0 ).symbol()->clone() );
1436  }
1437  else if ( mRenderer->sourceSymbol() )
1438  {
1439  whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mRenderer->sourceSymbol()->clone() );
1440  }
1441  btnChangeGraduatedSymbol->setDialogTitle( ranges.size() == 1 ? ranges.at( 0 ).label() : tr( "Symbol Settings" ) );
1442 }
1443 
1444 void QgsGraduatedSymbolRendererWidget::dataDefinedSizeLegend()
1445 {
1446  QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( mGraduatedSymbol.get() ); // this should be only enabled for marker symbols
1447  QgsDataDefinedSizeLegendWidget *panel = createDataDefinedSizeLegendWidget( s, mRenderer->dataDefinedSizeLegend() );
1448  if ( panel )
1449  {
1450  connect( panel, &QgsPanelWidget::widgetChanged, this, [ = ]
1451  {
1452  mRenderer->setDataDefinedSizeLegend( panel->dataDefinedSizeLegend() );
1453  emit widgetChanged();
1454  } );
1455  openPanel( panel ); // takes ownership of the panel
1456  }
1457 }
1458 
1459 void QgsGraduatedSymbolRendererWidget::changeGraduatedSymbol()
1460 {
1461  mGraduatedSymbol.reset( btnChangeGraduatedSymbol->symbol()->clone() );
1463 }
1464 
1466 {
1467  std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
1468  if ( !tempSymbol )
1469  return;
1470 
1471  const QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1472  for ( const QModelIndex &index : selectedRows )
1473  {
1474  if ( !index.isValid() )
1475  continue;
1476 
1477  const int row = index.row();
1478  if ( !mRenderer || mRenderer->ranges().size() <= row )
1479  continue;
1480 
1481  if ( mRenderer->ranges().at( row ).symbol()->type() != tempSymbol->type() )
1482  continue;
1483 
1484  std::unique_ptr< QgsSymbol > newCatSymbol( tempSymbol->clone() );
1485  if ( selectedRows.count() > 1 )
1486  {
1487  //if updating multiple ranges, retain the existing category colors
1488  newCatSymbol->setColor( mRenderer->ranges().at( row ).symbol()->color() );
1489  }
1490 
1491  mRenderer->updateRangeSymbol( row, newCatSymbol.release() );
1492  }
1493  emit widgetChanged();
1494 }
@ 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: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
static QgsProcessingGuiRegistry * processingGuiRegistry()
Returns the global processing gui registry, used for registering the GUI behavior of processing algor...
Definition: qgsgui.cpp:134
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:470
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:1578
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:1517
const QgsField & field
Definition: qgsfield.h:463
QList< QgsLegendSymbolItem > QgsLegendSymbolList
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QList< QgsRendererRange > QgsRangeList
int precision