QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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 
50 // ------------------------------ Model ------------------------------------
51 
53 
54 QgsGraduatedSymbolRendererModel::QgsGraduatedSymbolRendererModel( QObject *parent ) : QAbstractItemModel( parent )
55  , mMimeFormat( QStringLiteral( "application/x-qgsgraduatedsymbolrendererv2model" ) )
56 {
57 }
58 
59 void QgsGraduatedSymbolRendererModel::setRenderer( QgsGraduatedSymbolRenderer *renderer )
60 {
61  if ( mRenderer )
62  {
63  if ( !mRenderer->ranges().isEmpty() )
64  {
65  beginRemoveRows( QModelIndex(), 0, mRenderer->ranges().size() - 1 );
66  mRenderer = nullptr;
67  endRemoveRows();
68  }
69  else
70  {
71  mRenderer = nullptr;
72  }
73  }
74  if ( renderer )
75  {
76  if ( !renderer->ranges().isEmpty() )
77  {
78  beginInsertRows( QModelIndex(), 0, renderer->ranges().size() - 1 );
79  mRenderer = renderer;
80  endInsertRows();
81  }
82  else
83  {
84  mRenderer = renderer;
85  }
86  }
87 }
88 
89 void QgsGraduatedSymbolRendererModel::addClass( QgsSymbol *symbol )
90 {
91  if ( !mRenderer ) return;
92  int idx = mRenderer->ranges().size();
93  beginInsertRows( QModelIndex(), idx, idx );
94  mRenderer->addClass( symbol );
95  endInsertRows();
96 }
97 
98 void QgsGraduatedSymbolRendererModel::addClass( const QgsRendererRange &range )
99 {
100  if ( !mRenderer )
101  {
102  return;
103  }
104  int idx = mRenderer->ranges().size();
105  beginInsertRows( QModelIndex(), idx, idx );
106  mRenderer->addClass( range );
107  endInsertRows();
108 }
109 
110 QgsRendererRange QgsGraduatedSymbolRendererModel::rendererRange( const QModelIndex &index )
111 {
112  if ( !index.isValid() || !mRenderer || mRenderer->ranges().size() <= index.row() )
113  {
114  return QgsRendererRange();
115  }
116 
117  return mRenderer->ranges().value( index.row() );
118 }
119 
120 Qt::ItemFlags QgsGraduatedSymbolRendererModel::flags( const QModelIndex &index ) const
121 {
122  if ( !index.isValid() )
123  {
124  return Qt::ItemIsDropEnabled;
125  }
126 
127  Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable;
128 
129  if ( index.column() == 2 )
130  {
131  flags |= Qt::ItemIsEditable;
132  }
133 
134  return flags;
135 }
136 
137 Qt::DropActions QgsGraduatedSymbolRendererModel::supportedDropActions() const
138 {
139  return Qt::MoveAction;
140 }
141 
142 QVariant QgsGraduatedSymbolRendererModel::data( const QModelIndex &index, int role ) const
143 {
144  if ( !index.isValid() || !mRenderer ) return QVariant();
145 
146  const QgsRendererRange range = mRenderer->ranges().value( index.row() );
147 
148  if ( role == Qt::CheckStateRole && index.column() == 0 )
149  {
150  return range.renderState() ? Qt::Checked : Qt::Unchecked;
151  }
152  else if ( role == Qt::DisplayRole || role == Qt::ToolTipRole )
153  {
154  switch ( index.column() )
155  {
156  case 1:
157  {
158  int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
159  if ( decimalPlaces < 0 ) decimalPlaces = 0;
160  return QLocale().toString( range.lowerValue(), 'f', decimalPlaces ) + " - " + QLocale().toString( range.upperValue(), 'f', decimalPlaces );
161  }
162  case 2:
163  return range.label();
164  default:
165  return QVariant();
166  }
167  }
168  else if ( role == Qt::DecorationRole && index.column() == 0 && range.symbol() )
169  {
170  const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
171  return QgsSymbolLayerUtils::symbolPreviewIcon( range.symbol(), QSize( iconSize, iconSize ) );
172  }
173  else if ( role == Qt::TextAlignmentRole )
174  {
175  return ( index.column() == 0 ) ? Qt::AlignHCenter : Qt::AlignLeft;
176  }
177  else if ( role == Qt::EditRole )
178  {
179  switch ( index.column() )
180  {
181  // case 1: return rangeStr;
182  case 2:
183  return range.label();
184  default:
185  return QVariant();
186  }
187  }
188 
189  return QVariant();
190 }
191 
192 bool QgsGraduatedSymbolRendererModel::setData( const QModelIndex &index, const QVariant &value, int role )
193 {
194  if ( !index.isValid() )
195  return false;
196 
197  if ( index.column() == 0 && role == Qt::CheckStateRole )
198  {
199  mRenderer->updateRangeRenderState( index.row(), value == Qt::Checked );
200  emit dataChanged( index, index );
201  return true;
202  }
203 
204  if ( role != Qt::EditRole )
205  return false;
206 
207  switch ( index.column() )
208  {
209  case 1: // range
210  return false; // range is edited in popup dialog
211  case 2: // label
212  mRenderer->updateRangeLabel( index.row(), value.toString() );
213  break;
214  default:
215  return false;
216  }
217 
218  emit dataChanged( index, index );
219  return true;
220 }
221 
222 QVariant QgsGraduatedSymbolRendererModel::headerData( int section, Qt::Orientation orientation, int role ) const
223 {
224  if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 3 )
225  {
226  QStringList lst;
227  lst << tr( "Symbol" ) << tr( "Values" ) << tr( "Legend" );
228  return lst.value( section );
229  }
230  return QVariant();
231 }
232 
233 int QgsGraduatedSymbolRendererModel::rowCount( const QModelIndex &parent ) const
234 {
235  if ( parent.isValid() || !mRenderer )
236  {
237  return 0;
238  }
239  return mRenderer->ranges().size();
240 }
241 
242 int QgsGraduatedSymbolRendererModel::columnCount( const QModelIndex &index ) const
243 {
244  Q_UNUSED( index )
245  return 3;
246 }
247 
248 QModelIndex QgsGraduatedSymbolRendererModel::index( int row, int column, const QModelIndex &parent ) const
249 {
250  if ( hasIndex( row, column, parent ) )
251  {
252  return createIndex( row, column );
253  }
254  return QModelIndex();
255 }
256 
257 QModelIndex QgsGraduatedSymbolRendererModel::parent( const QModelIndex &index ) const
258 {
259  Q_UNUSED( index )
260  return QModelIndex();
261 }
262 
263 QStringList QgsGraduatedSymbolRendererModel::mimeTypes() const
264 {
265  QStringList types;
266  types << mMimeFormat;
267  return types;
268 }
269 
270 QMimeData *QgsGraduatedSymbolRendererModel::mimeData( const QModelIndexList &indexes ) const
271 {
272  QMimeData *mimeData = new QMimeData();
273  QByteArray encodedData;
274 
275  QDataStream stream( &encodedData, QIODevice::WriteOnly );
276 
277  // Create list of rows
278  const auto constIndexes = indexes;
279  for ( const QModelIndex &index : constIndexes )
280  {
281  if ( !index.isValid() || index.column() != 0 )
282  continue;
283 
284  stream << index.row();
285  }
286  mimeData->setData( mMimeFormat, encodedData );
287  return mimeData;
288 }
289 
290 bool QgsGraduatedSymbolRendererModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
291 {
292  Q_UNUSED( row )
293  Q_UNUSED( column )
294  if ( action != Qt::MoveAction ) return true;
295 
296  if ( !data->hasFormat( mMimeFormat ) ) return false;
297 
298  QByteArray encodedData = data->data( mMimeFormat );
299  QDataStream stream( &encodedData, QIODevice::ReadOnly );
300 
301  QVector<int> rows;
302  while ( !stream.atEnd() )
303  {
304  int r;
305  stream >> r;
306  rows.append( r );
307  }
308 
309  int to = parent.row();
310  // to is -1 if dragged outside items, i.e. below any item,
311  // then move to the last position
312  if ( to == -1 ) to = mRenderer->ranges().size(); // out of rang ok, will be decreased
313  for ( int i = rows.size() - 1; i >= 0; i-- )
314  {
315  QgsDebugMsg( QStringLiteral( "move %1 to %2" ).arg( rows[i] ).arg( to ) );
316  int t = to;
317  // moveCategory first removes and then inserts
318  if ( rows[i] < t ) t--;
319  mRenderer->moveClass( rows[i], t );
320  // current moved under another, shift its index up
321  for ( int j = 0; j < i; j++ )
322  {
323  if ( to < rows[j] && rows[i] > rows[j] ) rows[j] += 1;
324  }
325  // removed under 'to' so the target shifted down
326  if ( rows[i] < to ) to--;
327  }
328  emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->ranges().size(), 0 ) );
329  emit rowsMoved();
330  return false;
331 }
332 
333 void QgsGraduatedSymbolRendererModel::deleteRows( QList<int> rows )
334 {
335  for ( int i = rows.size() - 1; i >= 0; i-- )
336  {
337  beginRemoveRows( QModelIndex(), rows[i], rows[i] );
338  mRenderer->deleteClass( rows[i] );
339  endRemoveRows();
340  }
341 }
342 
343 void QgsGraduatedSymbolRendererModel::removeAllRows()
344 {
345  beginRemoveRows( QModelIndex(), 0, mRenderer->ranges().size() - 1 );
346  mRenderer->deleteAllClasses();
347  endRemoveRows();
348 }
349 
350 void QgsGraduatedSymbolRendererModel::sort( int column, Qt::SortOrder order )
351 {
352  if ( column == 0 )
353  {
354  return;
355  }
356  if ( column == 1 )
357  {
358  mRenderer->sortByValue( order );
359  }
360  else if ( column == 2 )
361  {
362  mRenderer->sortByLabel( order );
363  }
364  emit rowsMoved();
365  emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->ranges().size(), 0 ) );
366 }
367 
368 void QgsGraduatedSymbolRendererModel::updateSymbology( bool resetModel )
369 {
370  if ( resetModel )
371  {
372  reset();
373  }
374  else
375  {
376  emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->ranges().size(), 0 ) );
377  }
378 }
379 
380 void QgsGraduatedSymbolRendererModel::updateLabels()
381 {
382  emit dataChanged( createIndex( 0, 2 ), createIndex( mRenderer->ranges().size(), 2 ) );
383 }
384 
385 // ------------------------------ View style --------------------------------
386 QgsGraduatedSymbolRendererViewStyle::QgsGraduatedSymbolRendererViewStyle( QWidget *parent )
387  : QgsProxyStyle( parent )
388 {}
389 
390 void QgsGraduatedSymbolRendererViewStyle::drawPrimitive( PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget ) const
391 {
392  if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
393  {
394  QStyleOption opt( *option );
395  opt.rect.setLeft( 0 );
396  // draw always as line above, because we move item to that index
397  opt.rect.setHeight( 0 );
398  if ( widget ) opt.rect.setRight( widget->width() );
399  QProxyStyle::drawPrimitive( element, &opt, painter, widget );
400  return;
401  }
402  QProxyStyle::drawPrimitive( element, option, painter, widget );
403 }
404 
406 
407 // ------------------------------ Widget ------------------------------------
408 
410 {
411  return new QgsGraduatedSymbolRendererWidget( layer, style, renderer );
412 }
413 
414 QgsExpressionContext QgsGraduatedSymbolRendererWidget::createExpressionContext() const
415 {
416  QgsExpressionContext expContext;
420 
421  if ( mContext.mapCanvas() )
422  {
423  expContext << QgsExpressionContextUtils::mapSettingsScope( mContext.mapCanvas()->mapSettings() )
424  << new QgsExpressionContextScope( mContext.mapCanvas()->expressionContextScope() );
425  }
426  else
427  {
429  }
430 
431  if ( vectorLayer() )
432  expContext << QgsExpressionContextUtils::layerScope( vectorLayer() );
433 
434  // additional scopes
435  const auto constAdditionalExpressionContextScopes = mContext.additionalExpressionContextScopes();
436  for ( const QgsExpressionContextScope &scope : constAdditionalExpressionContextScopes )
437  {
438  expContext.appendScope( new QgsExpressionContextScope( scope ) );
439  }
440 
441  return expContext;
442 }
443 
445  : QgsRendererWidget( layer, style )
446 {
447  // try to recognize the previous renderer
448  // (null renderer means "no previous renderer")
449  if ( renderer )
450  {
451  mRenderer.reset( QgsGraduatedSymbolRenderer::convertFromRenderer( renderer ) );
452  }
453  if ( !mRenderer )
454  {
455  mRenderer = qgis::make_unique< QgsGraduatedSymbolRenderer >( QString(), QgsRangeList() );
456  }
457 
458  // setup user interface
459  setupUi( this );
460 
461  mSymmetryPointValidator = new QDoubleValidator();
462  cboSymmetryPoint->setEditable( true );
463  cboSymmetryPoint->setValidator( mSymmetryPointValidator );
464 
465  const QMap<QString, QString> methods = QgsApplication::classificationMethodRegistry()->methodNames();
466  for ( QMap<QString, QString>::const_iterator it = methods.constBegin(); it != methods.constEnd(); ++it )
467  {
468  QIcon icon = QgsApplication::classificationMethodRegistry()->icon( it.value() );
469  cboGraduatedMode->addItem( icon, it.key(), it.value() );
470  }
471 
472  connect( methodComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::methodComboBox_currentIndexChanged );
473  this->layout()->setContentsMargins( 0, 0, 0, 0 );
474 
475  mModel = new QgsGraduatedSymbolRendererModel( this );
476 
477  mExpressionWidget->setFilters( QgsFieldProxyModel::Numeric | QgsFieldProxyModel::Date );
478  mExpressionWidget->setLayer( mLayer );
479 
480  btnChangeGraduatedSymbol->setLayer( mLayer );
481  btnChangeGraduatedSymbol->registerExpressionContextGenerator( this );
482 
485 
486  spinPrecision->setMinimum( QgsClassificationMethod::MIN_PRECISION );
487  spinPrecision->setMaximum( QgsClassificationMethod::MAX_PRECISION );
488 
489  spinGraduatedClasses->setShowClearButton( false );
490 
491  btnColorRamp->setShowRandomColorRamp( true );
492 
493  // set project default color ramp
494  QString defaultColorRamp = QgsProject::instance()->readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/ColorRamp" ), QString() );
495  if ( !defaultColorRamp.isEmpty() )
496  {
497  btnColorRamp->setColorRampFromName( defaultColorRamp );
498  }
499  else
500  {
501  QgsColorRamp *ramp = new QgsGradientColorRamp( QColor( 255, 255, 255 ), QColor( 255, 0, 0 ) );
502  btnColorRamp->setColorRamp( ramp );
503  delete ramp;
504  }
505 
506 
507  viewGraduated->setStyle( new QgsGraduatedSymbolRendererViewStyle( viewGraduated ) );
508 
509  mGraduatedSymbol.reset( QgsSymbol::defaultSymbol( mLayer->geometryType() ) );
510  btnChangeGraduatedSymbol->setSymbolType( mGraduatedSymbol->type() );
511  btnChangeGraduatedSymbol->setSymbol( mGraduatedSymbol->clone() );
512 
513  methodComboBox->blockSignals( true );
514  methodComboBox->addItem( tr( "Color" ), ColorMode );
515  switch ( mGraduatedSymbol->type() )
516  {
517  case QgsSymbol::Marker:
518  {
519  methodComboBox->addItem( tr( "Size" ), SizeMode );
520  minSizeSpinBox->setValue( 1 );
521  maxSizeSpinBox->setValue( 8 );
522  break;
523  }
524  case QgsSymbol::Line:
525  {
526  methodComboBox->addItem( tr( "Size" ), SizeMode );
527  minSizeSpinBox->setValue( .1 );
528  maxSizeSpinBox->setValue( 2 );
529  break;
530  }
531  case QgsSymbol::Fill:
532  {
533  //set button and label invisible to avoid display of a single item combobox
534  methodComboBox->hide();
535  labelMethod->hide();
536  break;
537  }
538  case QgsSymbol::Hybrid:
539  break;
540  }
541  methodComboBox->blockSignals( false );
542 
543  connect( mExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsGraduatedSymbolRendererWidget::graduatedColumnChanged );
544  connect( viewGraduated, &QAbstractItemView::doubleClicked, this, &QgsGraduatedSymbolRendererWidget::rangesDoubleClicked );
545  connect( viewGraduated, &QAbstractItemView::clicked, this, &QgsGraduatedSymbolRendererWidget::rangesClicked );
546  connect( viewGraduated, &QTreeView::customContextMenuRequested, this, &QgsGraduatedSymbolRendererWidget::contextMenuViewCategories );
547 
548  connect( btnGraduatedClassify, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
549  connect( btnChangeGraduatedSymbol, &QgsSymbolButton::changed, this, &QgsGraduatedSymbolRendererWidget::changeGraduatedSymbol );
550  connect( btnGraduatedDelete, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::deleteClasses );
551  connect( btnDeleteAllClasses, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::deleteAllClasses );
552  connect( btnGraduatedAdd, &QAbstractButton::clicked, this, &QgsGraduatedSymbolRendererWidget::addClass );
553  connect( cbxLinkBoundaries, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::toggleBoundariesLink );
554  connect( mSizeUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsGraduatedSymbolRendererWidget::mSizeUnitWidget_changed );
555 
557 
558  // initialize from previously set renderer
560 
561  // default to collapsed symmetric group for ui simplicity
562  mGroupBoxSymmetric->setCollapsed( true ); //
563 
564  // menus for data-defined rotation/size
565  QMenu *advMenu = new QMenu( this );
566 
567  advMenu->addAction( tr( "Symbol Levels…" ), this, SLOT( showSymbolLevels() ) );
568  if ( mGraduatedSymbol->type() == QgsSymbol::Marker )
569  {
570  QAction *actionDdsLegend = advMenu->addAction( tr( "Data-defined Size Legend…" ) );
571  // only from Qt 5.6 there is convenience addAction() with new style connection
572  connect( actionDdsLegend, &QAction::triggered, this, &QgsGraduatedSymbolRendererWidget::dataDefinedSizeLegend );
573  }
574 
575  btnAdvanced->setMenu( advMenu );
576 
577  mHistogramWidget->setLayer( mLayer );
578  mHistogramWidget->setRenderer( mRenderer.get() );
580  connect( mExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ), mHistogramWidget, &QgsHistogramWidget::setSourceFieldExp );
581 
582  mExpressionWidget->registerExpressionContextGenerator( this );
583 }
584 
585 void QgsGraduatedSymbolRendererWidget::mSizeUnitWidget_changed()
586 {
587  if ( !mGraduatedSymbol )
588  return;
589  mGraduatedSymbol->setOutputUnit( mSizeUnitWidget->unit() );
590  mGraduatedSymbol->setMapUnitScale( mSizeUnitWidget->getMapUnitScale() );
591  mRenderer->updateSymbols( mGraduatedSymbol.get() );
593 }
594 
596 {
597  delete mModel;
598 }
599 
601 {
602  return mRenderer.get();
603 }
604 
606 {
608  btnChangeGraduatedSymbol->setMapCanvas( context.mapCanvas() );
609  btnChangeGraduatedSymbol->setMessageBar( context.messageBar() );
610 }
611 
612 // Connect/disconnect event handlers which trigger updating renderer
614 {
615  connect( spinGraduatedClasses, qgis::overload<int>::of( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
616  connect( cboGraduatedMode, qgis::overload<int>::of( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
618  connect( spinPrecision, qgis::overload<int>::of( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
619  connect( cbxTrimTrailingZeroes, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
620  connect( txtLegendFormat, &QLineEdit::textChanged, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
621  connect( minSizeSpinBox, qgis::overload<double>::of( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
622  connect( maxSizeSpinBox, qgis::overload<double>::of( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
623 
624  connect( mModel, &QgsGraduatedSymbolRendererModel::rowsMoved, this, &QgsGraduatedSymbolRendererWidget::rowsMoved );
625  connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsGraduatedSymbolRendererWidget::modelDataChanged );
626 
627  connect( mGroupBoxSymmetric, &QGroupBox::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
628  connect( cbxAstride, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
629  connect( cboSymmetryPoint, qgis::overload<int>::of( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
630  connect( cboSymmetryPoint->lineEdit(), &QLineEdit::editingFinished, this, &QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished );
631 }
632 
633 // Connect/disconnect event handlers which trigger updating renderer
635 {
636  disconnect( spinGraduatedClasses, qgis::overload<int>::of( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
637  disconnect( cboGraduatedMode, qgis::overload<int>::of( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
639  disconnect( spinPrecision, qgis::overload<int>::of( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
640  disconnect( cbxTrimTrailingZeroes, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
641  disconnect( txtLegendFormat, &QLineEdit::textChanged, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
642  disconnect( minSizeSpinBox, qgis::overload<double>::of( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
643  disconnect( maxSizeSpinBox, qgis::overload<double>::of( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
644 
645  disconnect( mModel, &QgsGraduatedSymbolRendererModel::rowsMoved, this, &QgsGraduatedSymbolRendererWidget::rowsMoved );
646  disconnect( mModel, &QAbstractItemModel::dataChanged, this, &QgsGraduatedSymbolRendererWidget::modelDataChanged );
647 
648  disconnect( mGroupBoxSymmetric, &QGroupBox::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
649  disconnect( cbxAstride, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
650  disconnect( cboSymmetryPoint, qgis::overload<int>::of( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
651  disconnect( cboSymmetryPoint->lineEdit(), &QLineEdit::editingFinished, this, &QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished );
652 }
653 
655 {
657 
658  const QgsClassificationMethod *method = mRenderer->classificationMethod();
659 
660  const QgsRangeList ranges = mRenderer->ranges();
661 
662  // use the breaks for symmetry point
663  int precision = spinPrecision->value() + 2;
664  while ( cboSymmetryPoint->count() )
665  cboSymmetryPoint->removeItem( 0 );
666  for ( int i = 0; i < ranges.count() - 1; i++ )
667  cboSymmetryPoint->addItem( QString::number( ranges.at( i ).upperValue(), 'f', precision ), ranges.at( i ).upperValue() );
668 
669  if ( method )
670  {
671  int idx = cboGraduatedMode->findData( method->id() );
672  if ( idx >= 0 )
673  cboGraduatedMode->setCurrentIndex( idx );
674 
675  mGroupBoxSymmetric->setVisible( method->symmetricModeAvailable() );
676  mGroupBoxSymmetric->setChecked( method->symmetricModeEnabled() );
677  cbxAstride->setChecked( method->symmetryAstride() );
678  if ( method->symmetricModeEnabled() )
679  cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), QString::number( method->symmetryPoint(), 'f', method->labelPrecision() + 2 ) );
680 
681  txtLegendFormat->setText( method->labelFormat() );
682  spinPrecision->setValue( method->labelPrecision() );
683  cbxTrimTrailingZeroes->setChecked( method->labelTrimTrailingZeroes() );
684  }
685 
686  // Only update class count if different - otherwise typing value gets very messy
687  int nclasses = ranges.count();
688  if ( nclasses && updateCount )
689  {
690  spinGraduatedClasses->setValue( ranges.count() );
691  }
692 
693  // set column
694  QString attrName = mRenderer->classAttribute();
695  mExpressionWidget->setField( attrName );
696  mHistogramWidget->setSourceFieldExp( attrName );
697 
698  // set source symbol
699  if ( mRenderer->sourceSymbol() )
700  {
701  mGraduatedSymbol.reset( mRenderer->sourceSymbol()->clone() );
702  whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mGraduatedSymbol->clone() );
703  }
704 
705  mModel->setRenderer( mRenderer.get() );
706  viewGraduated->setModel( mModel );
707 
708  connect( viewGraduated->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsGraduatedSymbolRendererWidget::selectionChanged );
709 
710  if ( mGraduatedSymbol )
711  {
712  mSizeUnitWidget->blockSignals( true );
713  mSizeUnitWidget->setUnit( mGraduatedSymbol->outputUnit() );
714  mSizeUnitWidget->setMapUnitScale( mGraduatedSymbol->mapUnitScale() );
715  mSizeUnitWidget->blockSignals( false );
716  }
717 
718  // set source color ramp
719  methodComboBox->blockSignals( true );
720  switch ( mRenderer->graduatedMethod() )
721  {
723  {
724  methodComboBox->setCurrentIndex( methodComboBox->findData( ColorMode ) );
725  if ( mRenderer->sourceColorRamp() )
726  {
727  btnColorRamp->setColorRamp( mRenderer->sourceColorRamp() );
728  }
729  break;
730  }
732  {
733  methodComboBox->setCurrentIndex( methodComboBox->findData( SizeMode ) );
734  if ( !mRenderer->ranges().isEmpty() ) // avoid overriding default size with zeros
735  {
736  minSizeSpinBox->setValue( mRenderer->minSymbolSize() );
737  maxSizeSpinBox->setValue( mRenderer->maxSymbolSize() );
738  }
739  break;
740  }
741  }
742  toggleMethodWidgets( static_cast< MethodMode>( methodComboBox->currentData().toInt() ) );
743  methodComboBox->blockSignals( false );
744 
745  viewGraduated->resizeColumnToContents( 0 );
746  viewGraduated->resizeColumnToContents( 1 );
747  viewGraduated->resizeColumnToContents( 2 );
748 
749  mHistogramWidget->refresh();
750 
752  emit widgetChanged();
753 }
754 
756 {
757  mRenderer->setClassAttribute( field );
758 }
759 
760 void QgsGraduatedSymbolRendererWidget::methodComboBox_currentIndexChanged( int )
761 {
762  const MethodMode newMethod = static_cast< MethodMode >( methodComboBox->currentData().toInt() );
763  toggleMethodWidgets( newMethod );
764  switch ( newMethod )
765  {
766  case ColorMode:
767  {
768  mRenderer->setGraduatedMethod( QgsGraduatedSymbolRenderer::GraduatedColor );
769  QgsColorRamp *ramp = btnColorRamp->colorRamp();
770 
771  if ( !ramp )
772  {
773  QMessageBox::critical( this, tr( "Select Method" ), tr( "No color ramp defined." ) );
774  return;
775  }
776  mRenderer->setSourceColorRamp( ramp );
778  break;
779  }
780 
781  case SizeMode:
782  {
783  lblColorRamp->setVisible( false );
784  btnColorRamp->setVisible( false );
785  lblSize->setVisible( true );
786  minSizeSpinBox->setVisible( true );
787  lblSize->setVisible( true );
788  maxSizeSpinBox->setVisible( true );
789  mSizeUnitWidget->setVisible( true );
790 
791  mRenderer->setGraduatedMethod( QgsGraduatedSymbolRenderer::GraduatedSize );
792  reapplySizes();
793  break;
794  }
795  }
796 }
797 
798 void QgsGraduatedSymbolRendererWidget::toggleMethodWidgets( MethodMode mode )
799 {
800  switch ( mode )
801  {
802  case ColorMode:
803  {
804  lblColorRamp->setVisible( true );
805  btnColorRamp->setVisible( true );
806  lblSize->setVisible( false );
807  minSizeSpinBox->setVisible( false );
808  lblSizeTo->setVisible( false );
809  maxSizeSpinBox->setVisible( false );
810  mSizeUnitWidget->setVisible( false );
811  break;
812  }
813 
814  case SizeMode:
815  {
816  lblColorRamp->setVisible( false );
817  btnColorRamp->setVisible( false );
818  lblSize->setVisible( true );
819  minSizeSpinBox->setVisible( true );
820  lblSizeTo->setVisible( true );
821  maxSizeSpinBox->setVisible( true );
822  mSizeUnitWidget->setVisible( true );
823  break;
824  }
825  }
826 }
827 
829 {
830  if ( !mModel )
831  return;
832 
833  mModel->updateSymbology( reset );
834 
836  spinGraduatedClasses->setValue( mRenderer->ranges().count() );
838 
839  emit widgetChanged();
840 }
841 
842 void QgsGraduatedSymbolRendererWidget::cleanUpSymbolSelector( QgsPanelWidget *container )
843 {
844  QgsSymbolSelectorWidget *dlg = qobject_cast<QgsSymbolSelectorWidget *>( container );
845  if ( !dlg )
846  return;
847 
848  delete dlg->symbol();
849 }
850 
851 void QgsGraduatedSymbolRendererWidget::updateSymbolsFromWidget()
852 {
853  QgsSymbolSelectorWidget *dlg = qobject_cast<QgsSymbolSelectorWidget *>( sender() );
854  mGraduatedSymbol.reset( dlg->symbol()->clone() );
855 
857 }
858 
860 {
861  mSizeUnitWidget->blockSignals( true );
862  mSizeUnitWidget->setUnit( mGraduatedSymbol->outputUnit() );
863  mSizeUnitWidget->setMapUnitScale( mGraduatedSymbol->mapUnitScale() );
864  mSizeUnitWidget->blockSignals( false );
865 
866  QItemSelectionModel *m = viewGraduated->selectionModel();
867  QModelIndexList selectedIndexes = m->selectedRows( 1 );
868  if ( m && !selectedIndexes.isEmpty() )
869  {
870  const auto constSelectedIndexes = selectedIndexes;
871  for ( const QModelIndex &idx : constSelectedIndexes )
872  {
873  if ( idx.isValid() )
874  {
875  int rangeIdx = idx.row();
876  QgsSymbol *newRangeSymbol = mGraduatedSymbol->clone();
877  if ( selectedIndexes.count() > 1 )
878  {
879  //if updating multiple ranges, retain the existing range colors
880  newRangeSymbol->setColor( mRenderer->ranges().at( rangeIdx ).symbol()->color() );
881  }
882  mRenderer->updateRangeSymbol( rangeIdx, newRangeSymbol );
883  }
884  }
885  }
886  else
887  {
888  mRenderer->updateSymbols( mGraduatedSymbol.get() );
889  }
890 
892  emit widgetChanged();
893 }
894 
895 void QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished( )
896 {
897  const QString text = cboSymmetryPoint->lineEdit()->text();
898  int index = cboSymmetryPoint->findText( text );
899  if ( index != -1 )
900  {
901  cboSymmetryPoint->setCurrentIndex( index );
902  }
903  else
904  {
905  cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), text );
907  }
908 }
909 
910 
912 {
913 
914  QgsTemporaryCursorOverride override( Qt::WaitCursor );
915  QString attrName = mExpressionWidget->currentField();
916  int nclasses = spinGraduatedClasses->value();
917 
918  const QString methodId = cboGraduatedMode->currentData().toString();
920  Q_ASSERT( method );
921 
922  int attrNum = mLayer->fields().lookupField( attrName );
923  double minimum = mLayer->minimumValue( attrNum ).toDouble();
924  double maximum = mLayer->maximumValue( attrNum ).toDouble();
925  mSymmetryPointValidator->setBottom( minimum );
926  mSymmetryPointValidator->setTop( maximum );
927  mSymmetryPointValidator->setDecimals( spinPrecision->value() );
928 
929  if ( method->id() == QgsClassificationEqualInterval::METHOD_ID ||
931  {
932  // knowing that spinSymmetryPointForOtherMethods->value() is automatically put at minimum when out of min-max
933  // using "(maximum-minimum)/100)" to avoid direct comparison of doubles
934  double currentValue = cboSymmetryPoint->currentText().toDouble();
935  if ( currentValue < ( minimum + ( maximum - minimum ) / 100. ) || currentValue > ( maximum - ( maximum - minimum ) / 100. ) )
936  cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), QString::number( minimum + ( maximum - minimum ) / 2., 'f', method->labelPrecision() + 2 ) );
937  }
938 
939  if ( mGroupBoxSymmetric->isChecked() )
940  {
941  double symmetryPoint = cboSymmetryPoint->currentText().toDouble();
942  bool astride = cbxAstride->isChecked();
943  method->setSymmetricMode( true, symmetryPoint, astride );
944  }
945 
946  // set method to renderer
947  mRenderer->setClassificationMethod( method );
948 
949  // create and set new renderer
950  mRenderer->setClassAttribute( attrName );
951 
952  // If complexity >= oN^2, warn for big dataset (more than 50k records)
953  // and give the user the chance to cancel
954  if ( method && method->codeComplexity() > 1 && mLayer->featureCount() > 50000 )
955  {
956  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 ) )
957  {
958  return;
959  }
960  }
961 
962  if ( methodComboBox->currentData() == ColorMode )
963  {
964  std::unique_ptr<QgsColorRamp> ramp( btnColorRamp->colorRamp() );
965  if ( !ramp )
966  {
967  QMessageBox::critical( this, tr( "Apply Classification" ), tr( "No color ramp defined." ) );
968  return;
969  }
970  mRenderer->setSourceColorRamp( ramp.release() );
971  }
972  else
973  {
974  mRenderer->setSourceColorRamp( nullptr );
975  }
976 
977  mRenderer->updateClasses( mLayer, nclasses );
978 
979  if ( methodComboBox->currentData() == SizeMode )
980  mRenderer->setSymbolSizes( minSizeSpinBox->value(), maxSizeSpinBox->value() );
981 
982  mRenderer->calculateLabelPrecision();
983  // PrettyBreaks and StdDev calculation don't generate exact
984  // number of classes - leave user interface unchanged for these
985  updateUiFromRenderer( false );
986 }
987 
989 {
990  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
991  if ( !ramp )
992  return;
993 
994  mRenderer->updateColorRamp( ramp.release() );
995  mRenderer->updateSymbols( mGraduatedSymbol.get() );
997 }
998 
1000 {
1001  mRenderer->setSymbolSizes( minSizeSpinBox->value(), maxSizeSpinBox->value() );
1002  mRenderer->updateSymbols( mGraduatedSymbol.get() );
1004 }
1005 
1006 #if 0
1007 int QgsRendererPropertiesDialog::currentRangeRow()
1008 {
1009  QModelIndex idx = viewGraduated->selectionModel()->currentIndex();
1010  if ( !idx.isValid() )
1011  return -1;
1012  return idx.row();
1013 }
1014 #endif
1015 
1017 {
1018  QList<int> rows;
1019  QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1020 
1021  const auto constSelectedRows = selectedRows;
1022  for ( const QModelIndex &r : constSelectedRows )
1023  {
1024  if ( r.isValid() )
1025  {
1026  rows.append( r.row() );
1027  }
1028  }
1029  return rows;
1030 }
1031 
1033 {
1035  QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1036  QModelIndexList::const_iterator sIt = selectedRows.constBegin();
1037 
1038  for ( ; sIt != selectedRows.constEnd(); ++sIt )
1039  {
1040  selectedRanges.append( mModel->rendererRange( *sIt ) );
1041  }
1042  return selectedRanges;
1043 }
1044 
1046 {
1047  if ( idx.isValid() && idx.column() == 0 )
1048  changeRangeSymbol( idx.row() );
1049  if ( idx.isValid() && idx.column() == 1 )
1050  changeRange( idx.row() );
1051 }
1052 
1054 {
1055  if ( !idx.isValid() )
1056  mRowSelected = -1;
1057  else
1058  mRowSelected = idx.row();
1059 }
1060 
1062 {
1063 }
1064 
1066 {
1067  const QgsRendererRange &range = mRenderer->ranges()[rangeIdx];
1068  std::unique_ptr< QgsSymbol > newSymbol( range.symbol()->clone() );
1070  if ( panel && panel->dockMode() )
1071  {
1072  // bit tricky here - the widget doesn't take ownership of the symbol. So we need it to last for the duration of the
1073  // panel's existence. Accordingly, just kinda give it ownership here, and clean up in cleanUpSymbolSelector
1074  QgsSymbolSelectorWidget *dlg = new QgsSymbolSelectorWidget( newSymbol.release(), mStyle, mLayer, panel );
1075  dlg->setContext( mContext );
1076  dlg->setPanelTitle( range.label() );
1077  connect( dlg, &QgsPanelWidget::widgetChanged, this, &QgsGraduatedSymbolRendererWidget::updateSymbolsFromWidget );
1078  connect( dlg, &QgsPanelWidget::panelAccepted, this, &QgsGraduatedSymbolRendererWidget::cleanUpSymbolSelector );
1079  openPanel( dlg );
1080  }
1081  else
1082  {
1083  QgsSymbolSelectorDialog dlg( newSymbol.get(), mStyle, mLayer, panel );
1084  dlg.setContext( mContext );
1085  if ( !dlg.exec() || !newSymbol )
1086  {
1087  return;
1088  }
1089 
1090  mGraduatedSymbol = std::move( newSymbol );
1091  whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mGraduatedSymbol->clone() );
1093  }
1094 }
1095 
1097 {
1098  QgsLUDialog dialog( this );
1099 
1100  const QgsRendererRange &range = mRenderer->ranges()[rangeIdx];
1101  // Add arbitrary 2 to number of decimal places to retain a bit extra.
1102  // Ensures users can see if legend is not completely honest!
1103  int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
1104  if ( decimalPlaces < 0 ) decimalPlaces = 0;
1105  dialog.setLowerValue( QLocale().toString( range.lowerValue(), 'f', decimalPlaces ) );
1106  dialog.setUpperValue( QLocale().toString( range.upperValue(), 'f', decimalPlaces ) );
1107 
1108  if ( dialog.exec() == QDialog::Accepted )
1109  {
1110  bool ok = false;
1111  double lowerValue = qgsPermissiveToDouble( dialog.lowerValue(), ok );
1112  if ( ! ok )
1113  lowerValue = 0.0;
1114  double upperValue = qgsPermissiveToDouble( dialog.upperValue(), ok );
1115  if ( ! ok )
1116  upperValue = 0.0;
1117  mRenderer->updateRangeUpperValue( rangeIdx, upperValue );
1118  mRenderer->updateRangeLowerValue( rangeIdx, lowerValue );
1119 
1120  //If the boundaries have to stay linked, we update the ranges above and below, as well as their label if needed
1121  if ( cbxLinkBoundaries->isChecked() )
1122  {
1123  if ( rangeIdx > 0 )
1124  {
1125  mRenderer->updateRangeUpperValue( rangeIdx - 1, lowerValue );
1126  }
1127 
1128  if ( rangeIdx < mRenderer->ranges().size() - 1 )
1129  {
1130  mRenderer->updateRangeLowerValue( rangeIdx + 1, upperValue );
1131  }
1132  }
1133  }
1134  mHistogramWidget->refresh();
1135  emit widgetChanged();
1136 }
1137 
1139 {
1140  mModel->addClass( mGraduatedSymbol.get() );
1141  mHistogramWidget->refresh();
1142  emit widgetChanged();
1143 
1144 }
1145 
1147 {
1148  QList<int> classIndexes = selectedClasses();
1149  mModel->deleteRows( classIndexes );
1150  mHistogramWidget->refresh();
1151  emit widgetChanged();
1152 }
1153 
1155 {
1156  mModel->removeAllRows();
1157  mHistogramWidget->refresh();
1158  emit widgetChanged();
1159 }
1160 
1162 {
1163  const QgsRangeList &ranges = mRenderer->ranges();
1164  bool ordered = true;
1165  for ( int i = 1; i < ranges.size(); ++i )
1166  {
1167  if ( ranges[i] < ranges[i - 1] )
1168  {
1169  ordered = false;
1170  break;
1171  }
1172  }
1173  return ordered;
1174 }
1175 
1177 {
1178  //If the checkbox controlling the link between boundaries was unchecked and we check it, we have to link the boundaries
1179  //This is done by updating all lower ranges to the upper value of the range above
1180  if ( linked )
1181  {
1182  if ( ! rowsOrdered() )
1183  {
1184  int result = QMessageBox::warning(
1185  this,
1186  tr( "Link Class Boundaries" ),
1187  tr( "Rows will be reordered before linking boundaries. Continue?" ),
1188  QMessageBox::Ok | QMessageBox::Cancel );
1189  if ( result != QMessageBox::Ok )
1190  {
1191  cbxLinkBoundaries->setChecked( false );
1192  return;
1193  }
1194  mRenderer->sortByValue();
1195  }
1196 
1197  // Ok to proceed
1198  for ( int i = 1; i < mRenderer->ranges().size(); ++i )
1199  {
1200  mRenderer->updateRangeLowerValue( i, mRenderer->ranges()[i - 1].upperValue() );
1201  }
1203  }
1204 }
1205 
1207 {
1208  if ( item->column() == 2 )
1209  {
1210  QString label = item->text();
1211  int idx = item->row();
1212  mRenderer->updateRangeLabel( idx, label );
1213  }
1214 }
1215 
1217 {
1218  mRenderer->classificationMethod()->setLabelFormat( txtLegendFormat->text() );
1219  mRenderer->classificationMethod()->setLabelPrecision( spinPrecision->value() );
1220  mRenderer->classificationMethod()->setLabelTrimTrailingZeroes( cbxTrimTrailingZeroes->isChecked() );
1221  mRenderer->updateRangeLabels();
1222  mModel->updateLabels();
1223 }
1224 
1225 
1227 {
1228  QList<QgsSymbol *> selectedSymbols;
1229 
1230  QItemSelectionModel *m = viewGraduated->selectionModel();
1231  QModelIndexList selectedIndexes = m->selectedRows( 1 );
1232  if ( m && !selectedIndexes.isEmpty() )
1233  {
1234  const QgsRangeList &ranges = mRenderer->ranges();
1235  QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
1236  for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
1237  {
1238  QStringList list = m->model()->data( *indexIt ).toString().split( ' ' );
1239  if ( list.size() < 3 )
1240  {
1241  continue;
1242  }
1243  // Not strictly necessary because the range should have been sanitized already
1244  // after user input, but being permissive never hurts
1245  bool ok = false;
1246  double lowerBound = qgsPermissiveToDouble( list.at( 0 ), ok );
1247  if ( ! ok )
1248  lowerBound = 0.0;
1249  double upperBound = qgsPermissiveToDouble( list.at( 2 ), ok );
1250  if ( ! ok )
1251  upperBound = 0.0;
1252  QgsSymbol *s = findSymbolForRange( lowerBound, upperBound, ranges );
1253  if ( s )
1254  {
1255  selectedSymbols.append( s );
1256  }
1257  }
1258  }
1259  return selectedSymbols;
1260 }
1261 
1262 QgsSymbol *QgsGraduatedSymbolRendererWidget::findSymbolForRange( double lowerBound, double upperBound, const QgsRangeList &ranges ) const
1263 {
1264  int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
1265  if ( decimalPlaces < 0 )
1266  decimalPlaces = 0;
1267  double precision = 1.0 / std::pow( 10, decimalPlaces );
1268 
1269  for ( QgsRangeList::const_iterator it = ranges.begin(); it != ranges.end(); ++it )
1270  {
1271  if ( qgsDoubleNear( lowerBound, it->lowerValue(), precision ) && qgsDoubleNear( upperBound, it->upperValue(), precision ) )
1272  {
1273  return it->symbol();
1274  }
1275  }
1276  return nullptr;
1277 }
1278 
1280 {
1281  if ( mModel )
1282  {
1283  mModel->updateSymbology();
1284  }
1285  mHistogramWidget->refresh();
1286  emit widgetChanged();
1287 }
1288 
1290 {
1291  showSymbolLevelsDialog( mRenderer.get() );
1292 }
1293 
1295 {
1296  viewGraduated->selectionModel()->clear();
1297  if ( ! rowsOrdered() )
1298  {
1299  cbxLinkBoundaries->setChecked( false );
1300  }
1301  emit widgetChanged();
1302 }
1303 
1305 {
1306  emit widgetChanged();
1307 }
1308 
1310 {
1311  if ( !event )
1312  {
1313  return;
1314  }
1315 
1316  if ( event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier )
1317  {
1318  mCopyBuffer.clear();
1319  mCopyBuffer = selectedRanges();
1320  }
1321  else if ( event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier )
1322  {
1323  QgsRangeList::const_iterator rIt = mCopyBuffer.constBegin();
1324  for ( ; rIt != mCopyBuffer.constEnd(); ++rIt )
1325  {
1326  mModel->addClass( *rIt );
1327  }
1328  emit widgetChanged();
1329  }
1330 }
1331 
1332 void QgsGraduatedSymbolRendererWidget::selectionChanged( const QItemSelection &, const QItemSelection & )
1333 {
1334  const QgsRangeList ranges = selectedRanges();
1335  if ( !ranges.isEmpty() )
1336  {
1337  whileBlocking( btnChangeGraduatedSymbol )->setSymbol( ranges.at( 0 ).symbol()->clone() );
1338  }
1339  else if ( mRenderer->sourceSymbol() )
1340  {
1341  whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mRenderer->sourceSymbol()->clone() );
1342  }
1343  btnChangeGraduatedSymbol->setDialogTitle( ranges.size() == 1 ? ranges.at( 0 ).label() : tr( "Symbol Settings" ) );
1344 }
1345 
1346 void QgsGraduatedSymbolRendererWidget::dataDefinedSizeLegend()
1347 {
1348  QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( mGraduatedSymbol.get() ); // this should be only enabled for marker symbols
1349  QgsDataDefinedSizeLegendWidget *panel = createDataDefinedSizeLegendWidget( s, mRenderer->dataDefinedSizeLegend() );
1350  if ( panel )
1351  {
1352  connect( panel, &QgsPanelWidget::widgetChanged, this, [ = ]
1353  {
1354  mRenderer->setDataDefinedSizeLegend( panel->dataDefinedSizeLegend() );
1355  emit widgetChanged();
1356  } );
1357  openPanel( panel ); // takes ownership of the panel
1358  }
1359 }
1360 
1361 void QgsGraduatedSymbolRendererWidget::changeGraduatedSymbol()
1362 {
1363  mGraduatedSymbol.reset( btnChangeGraduatedSymbol->symbol()->clone() );
1365 }
1366 
1368 {
1369  std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
1370  if ( !tempSymbol )
1371  return;
1372 
1373  const QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1374  for ( const QModelIndex &index : selectedRows )
1375  {
1376  if ( !index.isValid() )
1377  continue;
1378 
1379  const int row = index.row();
1380  if ( !mRenderer || mRenderer->ranges().size() <= row )
1381  continue;
1382 
1383  if ( mRenderer->ranges().at( row ).symbol()->type() != tempSymbol->type() )
1384  continue;
1385 
1386  std::unique_ptr< QgsSymbol > newCatSymbol( tempSymbol->clone() );
1387  if ( selectedRows.count() > 1 )
1388  {
1389  //if updating multiple ranges, retain the existing category colors
1390  newCatSymbol->setColor( mRenderer->ranges().at( row ).symbol()->color() );
1391  }
1392 
1393  mRenderer->updateRangeSymbol( row, newCatSymbol.release() );
1394  }
1395  emit widgetChanged();
1396 }
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
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...
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:61
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:280
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:98
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.
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:86
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:860
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)
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...
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.
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 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:85
Fill symbol.
Definition: qgssymbol.h:87
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:227
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:442
void toggleBoundariesLink(bool linked)
Toggle the link between classes boundaries.
void contextMenuViewCategories(QPoint p)
void deleteAllClasses()
Removes all classes from the classification.
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)
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.
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:88
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:474