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