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