QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 ) ? Qt::AlignHCenter : Qt::AlignLeft;
185  }
186  else if ( role == Qt::EditRole )
187  {
188  switch ( index.column() )
189  {
190  // case 1: return rangeStr;
191  case 2:
192  return range.label();
193  default:
194  return QVariant();
195  }
196  }
197 
198  return QVariant();
199 }
200 
201 bool QgsGraduatedSymbolRendererModel::setData( const QModelIndex &index, const QVariant &value, int role )
202 {
203  if ( !index.isValid() )
204  return false;
205 
206  if ( index.column() == 0 && role == Qt::CheckStateRole )
207  {
208  mRenderer->updateRangeRenderState( index.row(), value == Qt::Checked );
209  emit dataChanged( index, index );
210  return true;
211  }
212 
213  if ( role != Qt::EditRole )
214  return false;
215 
216  switch ( index.column() )
217  {
218  case 1: // range
219  return false; // range is edited in popup dialog
220  case 2: // label
221  mRenderer->updateRangeLabel( index.row(), value.toString() );
222  break;
223  default:
224  return false;
225  }
226 
227  emit dataChanged( index, index );
228  return true;
229 }
230 
231 QVariant QgsGraduatedSymbolRendererModel::headerData( int section, Qt::Orientation orientation, int role ) const
232 {
233  if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 3 )
234  {
235  QStringList lst;
236  lst << tr( "Symbol" ) << tr( "Values" ) << tr( "Legend" );
237  return lst.value( section );
238  }
239  return QVariant();
240 }
241 
242 int QgsGraduatedSymbolRendererModel::rowCount( const QModelIndex &parent ) const
243 {
244  if ( parent.isValid() || !mRenderer )
245  {
246  return 0;
247  }
248  return mRenderer->ranges().size();
249 }
250 
251 int QgsGraduatedSymbolRendererModel::columnCount( const QModelIndex &index ) const
252 {
253  Q_UNUSED( index )
254  return 3;
255 }
256 
257 QModelIndex QgsGraduatedSymbolRendererModel::index( int row, int column, const QModelIndex &parent ) const
258 {
259  if ( hasIndex( row, column, parent ) )
260  {
261  return createIndex( row, column );
262  }
263  return QModelIndex();
264 }
265 
266 QModelIndex QgsGraduatedSymbolRendererModel::parent( const QModelIndex &index ) const
267 {
268  Q_UNUSED( index )
269  return QModelIndex();
270 }
271 
272 QStringList QgsGraduatedSymbolRendererModel::mimeTypes() const
273 {
274  QStringList types;
275  types << mMimeFormat;
276  return types;
277 }
278 
279 QMimeData *QgsGraduatedSymbolRendererModel::mimeData( const QModelIndexList &indexes ) const
280 {
281  QMimeData *mimeData = new QMimeData();
282  QByteArray encodedData;
283 
284  QDataStream stream( &encodedData, QIODevice::WriteOnly );
285 
286  // Create list of rows
287  const auto constIndexes = indexes;
288  for ( const QModelIndex &index : constIndexes )
289  {
290  if ( !index.isValid() || index.column() != 0 )
291  continue;
292 
293  stream << index.row();
294  }
295  mimeData->setData( mMimeFormat, encodedData );
296  return mimeData;
297 }
298 
299 bool QgsGraduatedSymbolRendererModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
300 {
301  Q_UNUSED( row )
302  Q_UNUSED( column )
303  if ( action != Qt::MoveAction ) return true;
304 
305  if ( !data->hasFormat( mMimeFormat ) ) return false;
306 
307  QByteArray encodedData = data->data( mMimeFormat );
308  QDataStream stream( &encodedData, QIODevice::ReadOnly );
309 
310  QVector<int> rows;
311  while ( !stream.atEnd() )
312  {
313  int r;
314  stream >> r;
315  rows.append( r );
316  }
317 
318  int to = parent.row();
319  // to is -1 if dragged outside items, i.e. below any item,
320  // then move to the last position
321  if ( to == -1 ) to = mRenderer->ranges().size(); // out of rang ok, will be decreased
322  for ( int i = rows.size() - 1; i >= 0; i-- )
323  {
324  QgsDebugMsg( QStringLiteral( "move %1 to %2" ).arg( rows[i] ).arg( to ) );
325  int t = to;
326  // moveCategory first removes and then inserts
327  if ( rows[i] < t ) t--;
328  mRenderer->moveClass( rows[i], t );
329  // current moved under another, shift its index up
330  for ( int j = 0; j < i; j++ )
331  {
332  if ( to < rows[j] && rows[i] > rows[j] ) rows[j] += 1;
333  }
334  // removed under 'to' so the target shifted down
335  if ( rows[i] < to ) to--;
336  }
337  emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->ranges().size(), 0 ) );
338  emit rowsMoved();
339  return false;
340 }
341 
342 void QgsGraduatedSymbolRendererModel::deleteRows( QList<int> rows )
343 {
344  for ( int i = rows.size() - 1; i >= 0; i-- )
345  {
346  beginRemoveRows( QModelIndex(), rows[i], rows[i] );
347  mRenderer->deleteClass( rows[i] );
348  endRemoveRows();
349  }
350 }
351 
352 void QgsGraduatedSymbolRendererModel::removeAllRows()
353 {
354  beginRemoveRows( QModelIndex(), 0, mRenderer->ranges().size() - 1 );
355  mRenderer->deleteAllClasses();
356  endRemoveRows();
357 }
358 
359 void QgsGraduatedSymbolRendererModel::sort( int column, Qt::SortOrder order )
360 {
361  if ( column == 0 )
362  {
363  return;
364  }
365  if ( column == 1 )
366  {
367  mRenderer->sortByValue( order );
368  }
369  else if ( column == 2 )
370  {
371  mRenderer->sortByLabel( order );
372  }
373  emit rowsMoved();
374  emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->ranges().size(), 0 ) );
375 }
376 
377 void QgsGraduatedSymbolRendererModel::updateSymbology()
378 {
379  emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->ranges().size(), 0 ) );
380 }
381 
382 void QgsGraduatedSymbolRendererModel::updateLabels()
383 {
384  emit dataChanged( createIndex( 0, 2 ), createIndex( mRenderer->ranges().size(), 2 ) );
385 }
386 
387 // ------------------------------ View style --------------------------------
388 QgsGraduatedSymbolRendererViewStyle::QgsGraduatedSymbolRendererViewStyle( QWidget *parent )
389  : QgsProxyStyle( parent )
390 {}
391 
392 void QgsGraduatedSymbolRendererViewStyle::drawPrimitive( PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget ) const
393 {
394  if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
395  {
396  QStyleOption opt( *option );
397  opt.rect.setLeft( 0 );
398  // draw always as line above, because we move item to that index
399  opt.rect.setHeight( 0 );
400  if ( widget ) opt.rect.setRight( widget->width() );
401  QProxyStyle::drawPrimitive( element, &opt, painter, widget );
402  return;
403  }
404  QProxyStyle::drawPrimitive( element, option, painter, widget );
405 }
406 
408 
409 // ------------------------------ Widget ------------------------------------
410 
412 {
413  return new QgsGraduatedSymbolRendererWidget( layer, style, renderer );
414 }
415 
416 QgsExpressionContext QgsGraduatedSymbolRendererWidget::createExpressionContext() const
417 {
418  QgsExpressionContext expContext;
422 
423  if ( auto *lMapCanvas = mContext.mapCanvas() )
424  {
425  expContext << QgsExpressionContextUtils::mapSettingsScope( lMapCanvas->mapSettings() )
426  << new QgsExpressionContextScope( lMapCanvas->expressionContextScope() );
427  if ( const QgsExpressionContextScopeGenerator *generator = dynamic_cast< const QgsExpressionContextScopeGenerator * >( lMapCanvas->temporalController() ) )
428  {
429  expContext << generator->createExpressionContextScope();
430  }
431  }
432  else
433  {
435  }
436 
437  if ( auto *lVectorLayer = vectorLayer() )
438  expContext << QgsExpressionContextUtils::layerScope( lVectorLayer );
439 
440  // additional scopes
441  const auto constAdditionalExpressionContextScopes = mContext.additionalExpressionContextScopes();
442  for ( const QgsExpressionContextScope &scope : constAdditionalExpressionContextScopes )
443  {
444  expContext.appendScope( new QgsExpressionContextScope( scope ) );
445  }
446 
447  return expContext;
448 }
449 
451  : QgsRendererWidget( layer, style )
452 {
453  // try to recognize the previous renderer
454  // (null renderer means "no previous renderer")
455  if ( renderer )
456  {
458  }
459  if ( !mRenderer )
460  {
461  mRenderer = std::make_unique< QgsGraduatedSymbolRenderer >( QString(), QgsRangeList() );
462  }
463 
464  // setup user interface
465  setupUi( this );
466 
467  mSymmetryPointValidator = new QDoubleValidator( this );
468  cboSymmetryPoint->setEditable( true );
469  cboSymmetryPoint->setValidator( mSymmetryPointValidator );
470 
471  const QMap<QString, QString> methods = QgsApplication::classificationMethodRegistry()->methodNames();
472  for ( QMap<QString, QString>::const_iterator it = methods.constBegin(); it != methods.constEnd(); ++it )
473  {
474  QIcon icon = QgsApplication::classificationMethodRegistry()->icon( it.value() );
475  cboGraduatedMode->addItem( icon, it.key(), it.value() );
476  }
477 
478  connect( methodComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::methodComboBox_currentIndexChanged );
479  this->layout()->setContentsMargins( 0, 0, 0, 0 );
480 
481  mModel = new QgsGraduatedSymbolRendererModel( this );
482 
483  mExpressionWidget->setFilters( QgsFieldProxyModel::Numeric | QgsFieldProxyModel::Date );
484  mExpressionWidget->setLayer( mLayer );
485 
486  btnChangeGraduatedSymbol->setLayer( mLayer );
487  btnChangeGraduatedSymbol->registerExpressionContextGenerator( this );
488 
491 
492  spinPrecision->setMinimum( QgsClassificationMethod::MIN_PRECISION );
493  spinPrecision->setMaximum( QgsClassificationMethod::MAX_PRECISION );
494  spinPrecision->setClearValue( 4 );
495 
496  spinGraduatedClasses->setShowClearButton( false );
497 
498  btnColorRamp->setShowRandomColorRamp( true );
499 
500  // set project default color ramp
501  QString defaultColorRamp = QgsProject::instance()->readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/ColorRamp" ), QString() );
502  if ( !defaultColorRamp.isEmpty() )
503  {
504  btnColorRamp->setColorRampFromName( defaultColorRamp );
505  }
506  else
507  {
508  QgsColorRamp *ramp = new QgsGradientColorRamp( QColor( 255, 255, 255 ), QColor( 255, 0, 0 ) );
509  btnColorRamp->setColorRamp( ramp );
510  delete ramp;
511  }
512 
513 
514  viewGraduated->setStyle( new QgsGraduatedSymbolRendererViewStyle( viewGraduated ) );
515 
516  mGraduatedSymbol.reset( QgsSymbol::defaultSymbol( mLayer->geometryType() ) );
517  if ( mGraduatedSymbol )
518  {
519  btnChangeGraduatedSymbol->setSymbolType( mGraduatedSymbol->type() );
520  btnChangeGraduatedSymbol->setSymbol( mGraduatedSymbol->clone() );
521 
522  methodComboBox->blockSignals( true );
523  methodComboBox->addItem( tr( "Color" ), ColorMode );
524  switch ( mGraduatedSymbol->type() )
525  {
527  {
528  methodComboBox->addItem( tr( "Size" ), SizeMode );
529  minSizeSpinBox->setValue( 1 );
530  maxSizeSpinBox->setValue( 8 );
531  break;
532  }
534  {
535  methodComboBox->addItem( tr( "Size" ), SizeMode );
536  minSizeSpinBox->setValue( .1 );
537  maxSizeSpinBox->setValue( 2 );
538  break;
539  }
541  {
542  //set button and label invisible to avoid display of a single item combobox
543  methodComboBox->hide();
544  labelMethod->hide();
545  break;
546  }
548  break;
549  }
550  methodComboBox->blockSignals( false );
551  }
552 
553  connect( mExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsGraduatedSymbolRendererWidget::graduatedColumnChanged );
554  connect( viewGraduated, &QAbstractItemView::doubleClicked, this, &QgsGraduatedSymbolRendererWidget::rangesDoubleClicked );
555  connect( viewGraduated, &QAbstractItemView::clicked, this, &QgsGraduatedSymbolRendererWidget::rangesClicked );
556  connect( viewGraduated, &QTreeView::customContextMenuRequested, this, &QgsGraduatedSymbolRendererWidget::contextMenuViewCategories );
557 
558  connect( btnGraduatedClassify, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
559  connect( btnChangeGraduatedSymbol, &QgsSymbolButton::changed, this, &QgsGraduatedSymbolRendererWidget::changeGraduatedSymbol );
560  connect( btnGraduatedDelete, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::deleteClasses );
561  connect( btnDeleteAllClasses, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::deleteAllClasses );
562  connect( btnGraduatedAdd, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::addClass );
563  connect( cbxLinkBoundaries, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::toggleBoundariesLink );
564  connect( mSizeUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsGraduatedSymbolRendererWidget::mSizeUnitWidget_changed );
565 
566  connect( cboGraduatedMode, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::updateMethodParameters );
567 
569 
570  // initialize from previously set renderer
572 
573  // default to collapsed symmetric group for ui simplicity
574  mGroupBoxSymmetric->setCollapsed( true ); //
575 
576  // menus for data-defined rotation/size
577  QMenu *advMenu = new QMenu( this );
578 
579  mActionLevels = advMenu->addAction( tr( "Symbol Levels…" ), this, &QgsGraduatedSymbolRendererWidget::showSymbolLevels );
580  if ( mGraduatedSymbol && mGraduatedSymbol->type() == Qgis::SymbolType::Marker )
581  {
582  QAction *actionDdsLegend = advMenu->addAction( tr( "Data-defined Size Legend…" ) );
583  // only from Qt 5.6 there is convenience addAction() with new style connection
584  connect( actionDdsLegend, &QAction::triggered, this, &QgsGraduatedSymbolRendererWidget::dataDefinedSizeLegend );
585  }
586 
587  btnAdvanced->setMenu( advMenu );
588 
589  mHistogramWidget->setLayer( mLayer );
590  mHistogramWidget->setRenderer( mRenderer.get() );
592  connect( mExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ), mHistogramWidget, &QgsHistogramWidget::setSourceFieldExp );
593 
594  mExpressionWidget->registerExpressionContextGenerator( this );
595 }
596 
597 void QgsGraduatedSymbolRendererWidget::mSizeUnitWidget_changed()
598 {
599  if ( !mGraduatedSymbol )
600  return;
601  mGraduatedSymbol->setOutputUnit( mSizeUnitWidget->unit() );
602  mGraduatedSymbol->setMapUnitScale( mSizeUnitWidget->getMapUnitScale() );
603  mRenderer->updateSymbols( mGraduatedSymbol.get() );
605 }
606 
608 {
609  delete mModel;
610  mParameterWidgetWrappers.clear();
611 }
612 
614 {
615  return mRenderer.get();
616 }
617 
619 {
621  btnChangeGraduatedSymbol->setMapCanvas( context.mapCanvas() );
622  btnChangeGraduatedSymbol->setMessageBar( context.messageBar() );
623 }
624 
626 {
627  delete mActionLevels;
628  mActionLevels = nullptr;
629 }
630 
631 // Connect/disconnect event handlers which trigger updating renderer
633 {
634  connect( spinGraduatedClasses, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
635  connect( cboGraduatedMode, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
637  connect( spinPrecision, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
638  connect( cbxTrimTrailingZeroes, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
639  connect( txtLegendFormat, &QLineEdit::textChanged, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
640  connect( minSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
641  connect( maxSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
642 
643  connect( mModel, &QgsGraduatedSymbolRendererModel::rowsMoved, this, &QgsGraduatedSymbolRendererWidget::rowsMoved );
644  connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsGraduatedSymbolRendererWidget::modelDataChanged );
645 
646  connect( mGroupBoxSymmetric, &QGroupBox::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
647  connect( cbxAstride, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
648  connect( cboSymmetryPoint, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
649  connect( cboSymmetryPoint->lineEdit(), &QLineEdit::editingFinished, this, &QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished );
650 
651  for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
652  {
654  }
655 }
656 
657 // Connect/disconnect event handlers which trigger updating renderer
659 {
660  disconnect( spinGraduatedClasses, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
661  disconnect( cboGraduatedMode, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
663  disconnect( spinPrecision, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
664  disconnect( cbxTrimTrailingZeroes, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
665  disconnect( txtLegendFormat, &QLineEdit::textChanged, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
666  disconnect( minSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
667  disconnect( maxSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
668 
669  disconnect( mModel, &QgsGraduatedSymbolRendererModel::rowsMoved, this, &QgsGraduatedSymbolRendererWidget::rowsMoved );
670  disconnect( mModel, &QAbstractItemModel::dataChanged, this, &QgsGraduatedSymbolRendererWidget::modelDataChanged );
671 
672  disconnect( mGroupBoxSymmetric, &QGroupBox::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
673  disconnect( cbxAstride, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
674  disconnect( cboSymmetryPoint, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
675  disconnect( cboSymmetryPoint->lineEdit(), &QLineEdit::editingFinished, this, &QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished );
676 
677  for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
678  {
680  }
681 }
682 
684 {
686 
687  const QgsClassificationMethod *method = mRenderer->classificationMethod();
688 
689  const QgsRangeList ranges = mRenderer->ranges();
690 
691  // use the breaks for symmetry point
692  int precision = spinPrecision->value() + 2;
693  while ( cboSymmetryPoint->count() )
694  cboSymmetryPoint->removeItem( 0 );
695  for ( int i = 0; i < ranges.count() - 1; i++ )
696  cboSymmetryPoint->addItem( QString::number( ranges.at( i ).upperValue(), 'f', precision ), ranges.at( i ).upperValue() );
697 
698  if ( method )
699  {
700  int idx = cboGraduatedMode->findData( method->id() );
701  if ( idx >= 0 )
702  cboGraduatedMode->setCurrentIndex( idx );
703 
704  mGroupBoxSymmetric->setVisible( method->symmetricModeAvailable() );
705  mGroupBoxSymmetric->setChecked( method->symmetricModeEnabled() );
706  cbxAstride->setChecked( method->symmetryAstride() );
707  if ( method->symmetricModeEnabled() )
708  cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), QLocale().toString( method->symmetryPoint(), 'f', method->labelPrecision() + 2 ) );
709 
710  txtLegendFormat->setText( method->labelFormat() );
711  spinPrecision->setValue( method->labelPrecision() );
712  cbxTrimTrailingZeroes->setChecked( method->labelTrimTrailingZeroes() );
713 
715  for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
716  {
717  const QgsProcessingParameterDefinition *def = ppww->parameterDefinition();
718  QVariant value = method->parameterValues().value( def->name(), def->defaultValueForGui() );
719  ppww->setParameterValue( value, context );
720  }
721  }
722 
723  // Only update class count if different - otherwise typing value gets very messy
724  int nclasses = ranges.count();
725  if ( nclasses && updateCount )
726  {
727  spinGraduatedClasses->setValue( ranges.count() );
728  }
729 
730  // set column
731  QString attrName = mRenderer->classAttribute();
732  mExpressionWidget->setField( attrName );
733  mHistogramWidget->setSourceFieldExp( attrName );
734 
735  // set source symbol
736  if ( mRenderer->sourceSymbol() )
737  {
738  mGraduatedSymbol.reset( mRenderer->sourceSymbol()->clone() );
739  whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mGraduatedSymbol->clone() );
740  }
741 
742  mModel->setRenderer( mRenderer.get() );
743  viewGraduated->setModel( mModel );
744 
745  connect( viewGraduated->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsGraduatedSymbolRendererWidget::selectionChanged );
746 
747  if ( mGraduatedSymbol )
748  {
749  mSizeUnitWidget->blockSignals( true );
750  mSizeUnitWidget->setUnit( mGraduatedSymbol->outputUnit() );
751  mSizeUnitWidget->setMapUnitScale( mGraduatedSymbol->mapUnitScale() );
752  mSizeUnitWidget->blockSignals( false );
753  }
754 
755  // set source color ramp
756  methodComboBox->blockSignals( true );
757  switch ( mRenderer->graduatedMethod() )
758  {
760  {
761  methodComboBox->setCurrentIndex( methodComboBox->findData( ColorMode ) );
762  if ( mRenderer->sourceColorRamp() )
763  {
764  btnColorRamp->setColorRamp( mRenderer->sourceColorRamp() );
765  }
766  break;
767  }
769  {
770  methodComboBox->setCurrentIndex( methodComboBox->findData( SizeMode ) );
771  if ( !mRenderer->ranges().isEmpty() ) // avoid overriding default size with zeros
772  {
773  minSizeSpinBox->setValue( mRenderer->minSymbolSize() );
774  maxSizeSpinBox->setValue( mRenderer->maxSymbolSize() );
775  }
776  break;
777  }
778  }
779  toggleMethodWidgets( static_cast< MethodMode>( methodComboBox->currentData().toInt() ) );
780  methodComboBox->blockSignals( false );
781 
782  viewGraduated->resizeColumnToContents( 0 );
783  viewGraduated->resizeColumnToContents( 1 );
784  viewGraduated->resizeColumnToContents( 2 );
785 
786  mHistogramWidget->refresh();
787 
789  emit widgetChanged();
790 }
791 
793 {
794  mRenderer->setClassAttribute( field );
795 }
796 
797 void QgsGraduatedSymbolRendererWidget::methodComboBox_currentIndexChanged( int )
798 {
799  const MethodMode newMethod = static_cast< MethodMode >( methodComboBox->currentData().toInt() );
800  toggleMethodWidgets( newMethod );
801  switch ( newMethod )
802  {
803  case ColorMode:
804  {
805  mRenderer->setGraduatedMethod( QgsGraduatedSymbolRenderer::GraduatedColor );
806  QgsColorRamp *ramp = btnColorRamp->colorRamp();
807 
808  if ( !ramp )
809  {
810  QMessageBox::critical( this, tr( "Select Method" ), tr( "No color ramp defined." ) );
811  return;
812  }
813  mRenderer->setSourceColorRamp( ramp );
815  break;
816  }
817 
818  case SizeMode:
819  {
820  lblColorRamp->setVisible( false );
821  btnColorRamp->setVisible( false );
822  lblSize->setVisible( true );
823  minSizeSpinBox->setVisible( true );
824  lblSize->setVisible( true );
825  maxSizeSpinBox->setVisible( true );
826  mSizeUnitWidget->setVisible( true );
827 
828  mRenderer->setGraduatedMethod( QgsGraduatedSymbolRenderer::GraduatedSize );
829  reapplySizes();
830  break;
831  }
832  }
833 }
834 
835 void QgsGraduatedSymbolRendererWidget::updateMethodParameters()
836 {
837  clearParameterWidgets();
838 
839  const QString methodId = cboGraduatedMode->currentData().toString();
841  Q_ASSERT( method );
842 
843  // need more context?
845 
846  for ( const QgsProcessingParameterDefinition *def : method->parameterDefinitions() )
847  {
849  mParametersLayout->addRow( ppww->createWrappedLabel(), ppww->createWrappedWidget( context ) );
850 
851  QVariant value = method->parameterValues().value( def->name(), def->defaultValueForGui() );
852  ppww->setParameterValue( value, context );
853 
855 
856  mParameterWidgetWrappers.push_back( std::unique_ptr<QgsAbstractProcessingParameterWidgetWrapper>( ppww ) );
857  }
858 }
859 
860 void QgsGraduatedSymbolRendererWidget::toggleMethodWidgets( MethodMode mode )
861 {
862  switch ( mode )
863  {
864  case ColorMode:
865  {
866  lblColorRamp->setVisible( true );
867  btnColorRamp->setVisible( true );
868  lblSize->setVisible( false );
869  minSizeSpinBox->setVisible( false );
870  lblSizeTo->setVisible( false );
871  maxSizeSpinBox->setVisible( false );
872  mSizeUnitWidget->setVisible( false );
873  break;
874  }
875 
876  case SizeMode:
877  {
878  lblColorRamp->setVisible( false );
879  btnColorRamp->setVisible( false );
880  lblSize->setVisible( true );
881  minSizeSpinBox->setVisible( true );
882  lblSizeTo->setVisible( true );
883  maxSizeSpinBox->setVisible( true );
884  mSizeUnitWidget->setVisible( true );
885  break;
886  }
887  }
888 }
889 
890 void QgsGraduatedSymbolRendererWidget::clearParameterWidgets()
891 {
892  while ( mParametersLayout->rowCount() )
893  {
894  QFormLayout::TakeRowResult row = mParametersLayout->takeRow( 0 );
895  for ( QLayoutItem *item : QList<QLayoutItem *>( {row.labelItem, row.fieldItem} ) )
896  if ( item )
897  {
898  if ( item->widget() )
899  item->widget()->deleteLater();
900  delete item;
901  }
902  }
903  mParameterWidgetWrappers.clear();
904 }
905 
907 {
908  if ( !mModel )
909  return;
910 
911  mModel->updateSymbology();
912 
914  spinGraduatedClasses->setValue( mRenderer->ranges().count() );
916 
917  emit widgetChanged();
918 }
919 
921 {
922  for ( const QgsLegendSymbolItem &legendSymbol : levels )
923  {
924  QgsSymbol *sym = legendSymbol.symbol();
925  for ( int layer = 0; layer < sym->symbolLayerCount(); layer++ )
926  {
927  mRenderer->setLegendSymbolItem( legendSymbol.ruleKey(), sym->clone() );
928  }
929  }
930  mRenderer->setUsingSymbolLevels( enabled );
931  mModel->updateSymbology();
932  emit widgetChanged();
933 }
934 
935 void QgsGraduatedSymbolRendererWidget::cleanUpSymbolSelector( QgsPanelWidget *container )
936 {
937  QgsSymbolSelectorWidget *dlg = qobject_cast<QgsSymbolSelectorWidget *>( container );
938  if ( !dlg )
939  return;
940 
941  delete dlg->symbol();
942 }
943 
944 void QgsGraduatedSymbolRendererWidget::updateSymbolsFromWidget()
945 {
946  QgsSymbolSelectorWidget *dlg = qobject_cast<QgsSymbolSelectorWidget *>( sender() );
947  mGraduatedSymbol.reset( dlg->symbol()->clone() );
948 
950 }
951 
953 {
954  mSizeUnitWidget->blockSignals( true );
955  mSizeUnitWidget->setUnit( mGraduatedSymbol->outputUnit() );
956  mSizeUnitWidget->setMapUnitScale( mGraduatedSymbol->mapUnitScale() );
957  mSizeUnitWidget->blockSignals( false );
958 
959  QItemSelectionModel *m = viewGraduated->selectionModel();
960  QModelIndexList selectedIndexes = m->selectedRows( 1 );
961  if ( !selectedIndexes.isEmpty() )
962  {
963  const auto constSelectedIndexes = selectedIndexes;
964  for ( const QModelIndex &idx : constSelectedIndexes )
965  {
966  if ( idx.isValid() )
967  {
968  int rangeIdx = idx.row();
969  QgsSymbol *newRangeSymbol = mGraduatedSymbol->clone();
970  if ( selectedIndexes.count() > 1 )
971  {
972  //if updating multiple ranges, retain the existing range colors
973  newRangeSymbol->setColor( mRenderer->ranges().at( rangeIdx ).symbol()->color() );
974  }
975  mRenderer->updateRangeSymbol( rangeIdx, newRangeSymbol );
976  }
977  }
978  }
979  else
980  {
981  mRenderer->updateSymbols( mGraduatedSymbol.get() );
982  }
983 
985  emit widgetChanged();
986 }
987 
988 void QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished( )
989 {
990  const QString text = cboSymmetryPoint->lineEdit()->text();
991  int index = cboSymmetryPoint->findText( text );
992  if ( index != -1 )
993  {
994  cboSymmetryPoint->setCurrentIndex( index );
995  }
996  else
997  {
998  cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), text );
1000  }
1001 }
1002 
1003 
1005 {
1006 
1007  QgsTemporaryCursorOverride override( Qt::WaitCursor );
1008  QString attrName = mExpressionWidget->currentField();
1009  int nclasses = spinGraduatedClasses->value();
1010 
1011  const QString methodId = cboGraduatedMode->currentData().toString();
1013  Q_ASSERT( method );
1014 
1015  int attrNum = mLayer->fields().lookupField( attrName );
1016 
1017  QVariant minVal;
1018  QVariant maxVal;
1019  mLayer->minimumAndMaximumValue( attrNum, minVal, maxVal );
1020 
1021  double minimum = minVal.toDouble();
1022  double maximum = maxVal.toDouble();
1023  mSymmetryPointValidator->setBottom( minimum );
1024  mSymmetryPointValidator->setTop( maximum );
1025  mSymmetryPointValidator->setDecimals( spinPrecision->value() );
1026 
1027  if ( method->id() == QgsClassificationEqualInterval::METHOD_ID ||
1029  {
1030  // knowing that spinSymmetryPointForOtherMethods->value() is automatically put at minimum when out of min-max
1031  // using "(maximum-minimum)/100)" to avoid direct comparison of doubles
1032  double currentValue = QgsDoubleValidator::toDouble( cboSymmetryPoint->currentText() );
1033  if ( currentValue < ( minimum + ( maximum - minimum ) / 100. ) || currentValue > ( maximum - ( maximum - minimum ) / 100. ) )
1034  cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), QLocale().toString( minimum + ( maximum - minimum ) / 2., 'f', method->labelPrecision() + 2 ) );
1035  }
1036 
1037  if ( mGroupBoxSymmetric->isChecked() )
1038  {
1039  double symmetryPoint = QgsDoubleValidator::toDouble( cboSymmetryPoint->currentText() );
1040  bool astride = cbxAstride->isChecked();
1041  method->setSymmetricMode( true, symmetryPoint, astride );
1042  }
1043 
1044  QVariantMap parameterValues;
1045  for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
1046  parameterValues.insert( ppww->parameterDefinition()->name(), ppww->parameterValue() );
1047  method->setParameterValues( parameterValues );
1048 
1049  // set method to renderer
1050  mRenderer->setClassificationMethod( method );
1051 
1052  // create and set new renderer
1053  mRenderer->setClassAttribute( attrName );
1054 
1055  // If complexity >= oN^2, warn for big dataset (more than 50k records)
1056  // and give the user the chance to cancel
1057  if ( method->codeComplexity() > 1 && mLayer->featureCount() > 50000 )
1058  {
1059  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 ) )
1060  {
1061  return;
1062  }
1063  }
1064 
1065  if ( methodComboBox->currentData() == ColorMode )
1066  {
1067  std::unique_ptr<QgsColorRamp> ramp( btnColorRamp->colorRamp() );
1068  if ( !ramp )
1069  {
1070  QMessageBox::critical( this, tr( "Apply Classification" ), tr( "No color ramp defined." ) );
1071  return;
1072  }
1073  mRenderer->setSourceColorRamp( ramp.release() );
1074  }
1075  else
1076  {
1077  mRenderer->setSourceColorRamp( nullptr );
1078  }
1079 
1080  mRenderer->updateClasses( mLayer, nclasses );
1081 
1082  if ( methodComboBox->currentData() == SizeMode )
1083  mRenderer->setSymbolSizes( minSizeSpinBox->value(), maxSizeSpinBox->value() );
1084 
1085  mRenderer->calculateLabelPrecision();
1086  // PrettyBreaks and StdDev calculation don't generate exact
1087  // number of classes - leave user interface unchanged for these
1088  updateUiFromRenderer( false );
1089 }
1090 
1092 {
1093  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
1094  if ( !ramp )
1095  return;
1096 
1097  mRenderer->updateColorRamp( ramp.release() );
1098  mRenderer->updateSymbols( mGraduatedSymbol.get() );
1100 }
1101 
1103 {
1104  mRenderer->setSymbolSizes( minSizeSpinBox->value(), maxSizeSpinBox->value() );
1105  mRenderer->updateSymbols( mGraduatedSymbol.get() );
1107 }
1108 
1109 #if 0
1110 int QgsRendererPropertiesDialog::currentRangeRow()
1111 {
1112  QModelIndex idx = viewGraduated->selectionModel()->currentIndex();
1113  if ( !idx.isValid() )
1114  return -1;
1115  return idx.row();
1116 }
1117 #endif
1118 
1120 {
1121  QList<int> rows;
1122  QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1123 
1124  const auto constSelectedRows = selectedRows;
1125  for ( const QModelIndex &r : constSelectedRows )
1126  {
1127  if ( r.isValid() )
1128  {
1129  rows.append( r.row() );
1130  }
1131  }
1132  return rows;
1133 }
1134 
1136 {
1138  QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1139  QModelIndexList::const_iterator sIt = selectedRows.constBegin();
1140 
1141  for ( ; sIt != selectedRows.constEnd(); ++sIt )
1142  {
1143  selectedRanges.append( mModel->rendererRange( *sIt ) );
1144  }
1145  return selectedRanges;
1146 }
1147 
1149 {
1150  if ( idx.isValid() && idx.column() == 0 )
1151  changeRangeSymbol( idx.row() );
1152  if ( idx.isValid() && idx.column() == 1 )
1153  changeRange( idx.row() );
1154 }
1155 
1157 {
1158  if ( !idx.isValid() )
1159  mRowSelected = -1;
1160  else
1161  mRowSelected = idx.row();
1162 }
1163 
1165 {
1166 }
1167 
1169 {
1170  const QgsRendererRange &range = mRenderer->ranges()[rangeIdx];
1171  std::unique_ptr< QgsSymbol > newSymbol( range.symbol()->clone() );
1173  if ( panel && panel->dockMode() )
1174  {
1175  // bit tricky here - the widget doesn't take ownership of the symbol. So we need it to last for the duration of the
1176  // panel's existence. Accordingly, just kinda give it ownership here, and clean up in cleanUpSymbolSelector
1177  QgsSymbolSelectorWidget *dlg = new QgsSymbolSelectorWidget( newSymbol.release(), mStyle, mLayer, panel );
1178  dlg->setContext( mContext );
1179  dlg->setPanelTitle( range.label() );
1180  connect( dlg, &QgsPanelWidget::widgetChanged, this, &QgsGraduatedSymbolRendererWidget::updateSymbolsFromWidget );
1181  connect( dlg, &QgsPanelWidget::panelAccepted, this, &QgsGraduatedSymbolRendererWidget::cleanUpSymbolSelector );
1182  openPanel( dlg );
1183  }
1184  else
1185  {
1186  QgsSymbolSelectorDialog dlg( newSymbol.get(), mStyle, mLayer, panel );
1187  dlg.setContext( mContext );
1188  if ( !dlg.exec() || !newSymbol )
1189  {
1190  return;
1191  }
1192 
1193  mGraduatedSymbol = std::move( newSymbol );
1194  whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mGraduatedSymbol->clone() );
1196  }
1197 }
1198 
1200 {
1201  QgsLUDialog dialog( this );
1202 
1203  const QgsRendererRange &range = mRenderer->ranges()[rangeIdx];
1204  // Add arbitrary 2 to number of decimal places to retain a bit extra.
1205  // Ensures users can see if legend is not completely honest!
1206  int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
1207  if ( decimalPlaces < 0 ) decimalPlaces = 0;
1208  dialog.setLowerValue( QLocale().toString( range.lowerValue(), 'f', decimalPlaces ) );
1209  dialog.setUpperValue( QLocale().toString( range.upperValue(), 'f', decimalPlaces ) );
1210 
1211  if ( dialog.exec() == QDialog::Accepted )
1212  {
1213  bool ok = false;
1214  double lowerValue = qgsPermissiveToDouble( dialog.lowerValue(), ok );
1215  if ( ! ok )
1216  lowerValue = 0.0;
1217  double upperValue = qgsPermissiveToDouble( dialog.upperValue(), ok );
1218  if ( ! ok )
1219  upperValue = 0.0;
1220  mRenderer->updateRangeUpperValue( rangeIdx, upperValue );
1221  mRenderer->updateRangeLowerValue( rangeIdx, lowerValue );
1222 
1223  //If the boundaries have to stay linked, we update the ranges above and below, as well as their label if needed
1224  if ( cbxLinkBoundaries->isChecked() )
1225  {
1226  if ( rangeIdx > 0 )
1227  {
1228  mRenderer->updateRangeUpperValue( rangeIdx - 1, lowerValue );
1229  }
1230 
1231  if ( rangeIdx < mRenderer->ranges().size() - 1 )
1232  {
1233  mRenderer->updateRangeLowerValue( rangeIdx + 1, upperValue );
1234  }
1235  }
1236  }
1237  mHistogramWidget->refresh();
1238  emit widgetChanged();
1239 }
1240 
1242 {
1243  mModel->addClass( mGraduatedSymbol.get() );
1244  mHistogramWidget->refresh();
1245  emit widgetChanged();
1246 
1247 }
1248 
1250 {
1251  QList<int> classIndexes = selectedClasses();
1252  mModel->deleteRows( classIndexes );
1253  mHistogramWidget->refresh();
1254  emit widgetChanged();
1255 }
1256 
1258 {
1259  mModel->removeAllRows();
1260  mHistogramWidget->refresh();
1261  emit widgetChanged();
1262 }
1263 
1265 {
1266  const QgsRangeList &ranges = mRenderer->ranges();
1267  bool ordered = true;
1268  for ( int i = 1; i < ranges.size(); ++i )
1269  {
1270  if ( ranges[i] < ranges[i - 1] )
1271  {
1272  ordered = false;
1273  break;
1274  }
1275  }
1276  return ordered;
1277 }
1278 
1280 {
1281  //If the checkbox controlling the link between boundaries was unchecked and we check it, we have to link the boundaries
1282  //This is done by updating all lower ranges to the upper value of the range above
1283  if ( linked )
1284  {
1285  if ( ! rowsOrdered() )
1286  {
1287  int result = QMessageBox::warning(
1288  this,
1289  tr( "Link Class Boundaries" ),
1290  tr( "Rows will be reordered before linking boundaries. Continue?" ),
1291  QMessageBox::Ok | QMessageBox::Cancel );
1292  if ( result != QMessageBox::Ok )
1293  {
1294  cbxLinkBoundaries->setChecked( false );
1295  return;
1296  }
1297  mRenderer->sortByValue();
1298  }
1299 
1300  // Ok to proceed
1301  for ( int i = 1; i < mRenderer->ranges().size(); ++i )
1302  {
1303  mRenderer->updateRangeLowerValue( i, mRenderer->ranges()[i - 1].upperValue() );
1304  }
1306  }
1307 }
1308 
1310 {
1311  if ( item->column() == 2 )
1312  {
1313  QString label = item->text();
1314  int idx = item->row();
1315  mRenderer->updateRangeLabel( idx, label );
1316  }
1317 }
1318 
1320 {
1321  mRenderer->classificationMethod()->setLabelFormat( txtLegendFormat->text() );
1322  mRenderer->classificationMethod()->setLabelPrecision( spinPrecision->value() );
1323  mRenderer->classificationMethod()->setLabelTrimTrailingZeroes( cbxTrimTrailingZeroes->isChecked() );
1324  mRenderer->updateRangeLabels();
1325  mModel->updateLabels();
1326 }
1327 
1328 
1330 {
1331  QList<QgsSymbol *> selectedSymbols;
1332 
1333  QItemSelectionModel *m = viewGraduated->selectionModel();
1334  QModelIndexList selectedIndexes = m->selectedRows( 1 );
1335  if ( !selectedIndexes.isEmpty() )
1336  {
1337  const QgsRangeList &ranges = mRenderer->ranges();
1338  QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
1339  for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
1340  {
1341  QStringList list = m->model()->data( *indexIt ).toString().split( ' ' );
1342  if ( list.size() < 3 )
1343  {
1344  continue;
1345  }
1346  // Not strictly necessary because the range should have been sanitized already
1347  // after user input, but being permissive never hurts
1348  bool ok = false;
1349  double lowerBound = qgsPermissiveToDouble( list.at( 0 ), ok );
1350  if ( ! ok )
1351  lowerBound = 0.0;
1352  double upperBound = qgsPermissiveToDouble( list.at( 2 ), ok );
1353  if ( ! ok )
1354  upperBound = 0.0;
1355  QgsSymbol *s = findSymbolForRange( lowerBound, upperBound, ranges );
1356  if ( s )
1357  {
1358  selectedSymbols.append( s );
1359  }
1360  }
1361  }
1362  return selectedSymbols;
1363 }
1364 
1365 QgsSymbol *QgsGraduatedSymbolRendererWidget::findSymbolForRange( double lowerBound, double upperBound, const QgsRangeList &ranges ) const
1366 {
1367  int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
1368  if ( decimalPlaces < 0 )
1369  decimalPlaces = 0;
1370  double precision = 1.0 / std::pow( 10, decimalPlaces );
1371 
1372  for ( QgsRangeList::const_iterator it = ranges.begin(); it != ranges.end(); ++it )
1373  {
1374  if ( qgsDoubleNear( lowerBound, it->lowerValue(), precision ) && qgsDoubleNear( upperBound, it->upperValue(), precision ) )
1375  {
1376  return it->symbol();
1377  }
1378  }
1379  return nullptr;
1380 }
1381 
1383 {
1384  if ( mModel )
1385  {
1386  mModel->updateSymbology();
1387  }
1388  mHistogramWidget->refresh();
1389  emit widgetChanged();
1390 }
1391 
1393 {
1394  showSymbolLevelsDialog( mRenderer.get() );
1395 }
1396 
1398 {
1399  viewGraduated->selectionModel()->clear();
1400  if ( ! rowsOrdered() )
1401  {
1402  cbxLinkBoundaries->setChecked( false );
1403  }
1404  emit widgetChanged();
1405 }
1406 
1408 {
1409  emit widgetChanged();
1410 }
1411 
1413 {
1414  if ( !event )
1415  {
1416  return;
1417  }
1418 
1419  if ( event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier )
1420  {
1421  mCopyBuffer.clear();
1422  mCopyBuffer = selectedRanges();
1423  }
1424  else if ( event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier )
1425  {
1426  QgsRangeList::const_iterator rIt = mCopyBuffer.constBegin();
1427  for ( ; rIt != mCopyBuffer.constEnd(); ++rIt )
1428  {
1429  mModel->addClass( *rIt );
1430  }
1431  emit widgetChanged();
1432  }
1433 }
1434 
1435 void QgsGraduatedSymbolRendererWidget::selectionChanged( const QItemSelection &, const QItemSelection & )
1436 {
1437  const QgsRangeList ranges = selectedRanges();
1438  if ( !ranges.isEmpty() )
1439  {
1440  whileBlocking( btnChangeGraduatedSymbol )->setSymbol( ranges.at( 0 ).symbol()->clone() );
1441  }
1442  else if ( mRenderer->sourceSymbol() )
1443  {
1444  whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mRenderer->sourceSymbol()->clone() );
1445  }
1446  btnChangeGraduatedSymbol->setDialogTitle( ranges.size() == 1 ? ranges.at( 0 ).label() : tr( "Symbol Settings" ) );
1447 }
1448 
1449 void QgsGraduatedSymbolRendererWidget::dataDefinedSizeLegend()
1450 {
1451  QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( mGraduatedSymbol.get() ); // this should be only enabled for marker symbols
1452  QgsDataDefinedSizeLegendWidget *panel = createDataDefinedSizeLegendWidget( s, mRenderer->dataDefinedSizeLegend() );
1453  if ( panel )
1454  {
1455  connect( panel, &QgsPanelWidget::widgetChanged, this, [ = ]
1456  {
1457  mRenderer->setDataDefinedSizeLegend( panel->dataDefinedSizeLegend() );
1458  emit widgetChanged();
1459  } );
1460  openPanel( panel ); // takes ownership of the panel
1461  }
1462 }
1463 
1464 void QgsGraduatedSymbolRendererWidget::changeGraduatedSymbol()
1465 {
1466  mGraduatedSymbol.reset( btnChangeGraduatedSymbol->symbol()->clone() );
1468 }
1469 
1471 {
1472  std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
1473  if ( !tempSymbol )
1474  return;
1475 
1476  const QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1477  for ( const QModelIndex &index : selectedRows )
1478  {
1479  if ( !index.isValid() )
1480  continue;
1481 
1482  const int row = index.row();
1483  if ( !mRenderer || mRenderer->ranges().size() <= row )
1484  continue;
1485 
1486  if ( mRenderer->ranges().at( row ).symbol()->type() != tempSymbol->type() )
1487  continue;
1488 
1489  std::unique_ptr< QgsSymbol > newCatSymbol( tempSymbol->clone() );
1490  if ( selectedRows.count() > 1 )
1491  {
1492  //if updating multiple ranges, retain the existing category colors
1493  newCatSymbol->setColor( mRenderer->ranges().at( row ).symbol()->color() );
1494  }
1495 
1496  mRenderer->updateRangeSymbol( row, newCatSymbol.release() );
1497  }
1498  emit widgetChanged();
1499 }
@ 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.
static double toDouble(const QString &input, bool *ok)
Converts input string to double value.
Abstract interface for generating an expression context scope.
Single scope for storing variables and functions for use within a QgsExpressionContext.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * atlasScope(const QgsLayoutAtlas *atlas)
Creates a new scope which contains variables and functions relating to a QgsLayoutAtlas.
static QgsExpressionContextScope * mapSettingsScope(const QgsMapSettings &mapSettings)
Creates a new scope which contains variables and functions relating to a QgsMapSettings object.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
The QgsFieldExpressionWidget class reates a widget to choose fields and edit expressions It contains ...
void fieldChanged(const QString &fieldName)
Emitted when the currently selected field changes.
@ Date
Date or datetime fields.
@ Numeric
All numeric fields.
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:344
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
Definition: qgscolorramp.h:151
void rangesModified(bool rangesAdded)
Emitted when the user modifies the graduated ranges using the histogram widget.
void deleteClasses()
Removes currently selected classes.
QList< QgsSymbol * > selectedSymbols() override
Subclasses may provide the capability of changing multiple symbols at once by implementing the follow...
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:121
void setSourceFieldExp(const QString &fieldOrExp)
Sets the source field or expression to use for values in the histogram.
QString upperValue() const
Definition: qgsludialog.cpp:32
QString lowerValue() const
Definition: qgsludialog.cpp:27
void setLowerValue(const QString &val)
Definition: qgsludialog.cpp:37
void setUpperValue(const QString &val)
Definition: qgsludialog.cpp:42
The 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:598
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:537
const QgsField & field
Definition: qgsfield.h:463
QList< QgsLegendSymbolItem > QgsLegendSymbolList
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QList< QgsRendererRange > QgsRangeList
int precision