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