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