QGIS API Documentation 3.28.14-Firenze (exported)
Loading...
Searching...
No Matches
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 );
181 return QgsSymbolLayerUtils::symbolPreviewIcon( range.symbol(), QSize( iconSize, iconSize ) );
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
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
571 // need to update widget according to current graduated mode
572 updateMethodParameters();
573
575
576 // initialize from previously set renderer
578
579 // default to collapsed symmetric group for ui simplicity
580 mGroupBoxSymmetric->setCollapsed( true ); //
581
582 // menus for data-defined rotation/size
583 QMenu *advMenu = new QMenu( this );
584
585 mActionLevels = advMenu->addAction( tr( "Symbol Levels…" ), this, &QgsGraduatedSymbolRendererWidget::showSymbolLevels );
586 if ( mGraduatedSymbol && mGraduatedSymbol->type() == Qgis::SymbolType::Marker )
587 {
588 QAction *actionDdsLegend = advMenu->addAction( tr( "Data-defined Size Legend…" ) );
589 // only from Qt 5.6 there is convenience addAction() with new style connection
590 connect( actionDdsLegend, &QAction::triggered, this, &QgsGraduatedSymbolRendererWidget::dataDefinedSizeLegend );
591 }
592
593 btnAdvanced->setMenu( advMenu );
594
595 mHistogramWidget->setLayer( mLayer );
596 mHistogramWidget->setRenderer( mRenderer.get() );
598 connect( mExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ), mHistogramWidget, &QgsHistogramWidget::setSourceFieldExp );
599
600 mExpressionWidget->registerExpressionContextGenerator( this );
601}
602
603void QgsGraduatedSymbolRendererWidget::mSizeUnitWidget_changed()
604{
605 if ( !mGraduatedSymbol )
606 return;
607 mGraduatedSymbol->setOutputUnit( mSizeUnitWidget->unit() );
608 mGraduatedSymbol->setMapUnitScale( mSizeUnitWidget->getMapUnitScale() );
609 mRenderer->updateSymbols( mGraduatedSymbol.get() );
611}
612
614{
615 delete mModel;
616 mParameterWidgetWrappers.clear();
617}
618
620{
621 return mRenderer.get();
622}
623
625{
627 btnChangeGraduatedSymbol->setMapCanvas( context.mapCanvas() );
628 btnChangeGraduatedSymbol->setMessageBar( context.messageBar() );
629}
630
632{
633 delete mActionLevels;
634 mActionLevels = nullptr;
635}
636
637// Connect/disconnect event handlers which trigger updating renderer
639{
640 connect( spinGraduatedClasses, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
641 connect( cboGraduatedMode, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
643 connect( spinPrecision, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
644 connect( cbxTrimTrailingZeroes, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
645 connect( txtLegendFormat, &QLineEdit::textChanged, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
646 connect( minSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
647 connect( maxSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
648
649 connect( mModel, &QgsGraduatedSymbolRendererModel::rowsMoved, this, &QgsGraduatedSymbolRendererWidget::rowsMoved );
650 connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsGraduatedSymbolRendererWidget::modelDataChanged );
651
652 connect( mGroupBoxSymmetric, &QGroupBox::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
653 connect( cbxAstride, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
654 connect( cboSymmetryPoint, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
655 connect( cboSymmetryPoint->lineEdit(), &QLineEdit::editingFinished, this, &QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished );
656
657 for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
658 {
660 }
661}
662
663// Connect/disconnect event handlers which trigger updating renderer
665{
666 disconnect( spinGraduatedClasses, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
667 disconnect( cboGraduatedMode, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
669 disconnect( spinPrecision, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
670 disconnect( cbxTrimTrailingZeroes, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
671 disconnect( txtLegendFormat, &QLineEdit::textChanged, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
672 disconnect( minSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
673 disconnect( maxSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
674
675 disconnect( mModel, &QgsGraduatedSymbolRendererModel::rowsMoved, this, &QgsGraduatedSymbolRendererWidget::rowsMoved );
676 disconnect( mModel, &QAbstractItemModel::dataChanged, this, &QgsGraduatedSymbolRendererWidget::modelDataChanged );
677
678 disconnect( mGroupBoxSymmetric, &QGroupBox::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
679 disconnect( cbxAstride, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
680 disconnect( cboSymmetryPoint, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
681 disconnect( cboSymmetryPoint->lineEdit(), &QLineEdit::editingFinished, this, &QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished );
682
683 for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
684 {
686 }
687}
688
690{
692 mBlockUpdates++;
693
694 const QgsClassificationMethod *method = mRenderer->classificationMethod();
695
696 const QgsRangeList ranges = mRenderer->ranges();
697
698 // use the breaks for symmetry point
699 int precision = spinPrecision->value() + 2;
700 while ( cboSymmetryPoint->count() )
701 cboSymmetryPoint->removeItem( 0 );
702 for ( int i = 0; i < ranges.count() - 1; i++ )
703 cboSymmetryPoint->addItem( QLocale().toString( ranges.at( i ).upperValue(), 'f', precision ), ranges.at( i ).upperValue() );
704
705 if ( method )
706 {
707 int idx = cboGraduatedMode->findData( method->id() );
708 if ( idx >= 0 )
709 cboGraduatedMode->setCurrentIndex( idx );
710
711 mGroupBoxSymmetric->setVisible( method->symmetricModeAvailable() );
712 mGroupBoxSymmetric->setChecked( method->symmetricModeEnabled() );
713 cbxAstride->setChecked( method->symmetryAstride() );
714 if ( method->symmetricModeEnabled() )
715 cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), QLocale().toString( method->symmetryPoint(), 'f', method->labelPrecision() + 2 ) );
716
717 txtLegendFormat->setText( method->labelFormat() );
718 spinPrecision->setValue( method->labelPrecision() );
719 cbxTrimTrailingZeroes->setChecked( method->labelTrimTrailingZeroes() );
720
722 for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
723 {
724 const QgsProcessingParameterDefinition *def = ppww->parameterDefinition();
725 QVariant value = method->parameterValues().value( def->name(), def->defaultValueForGui() );
726 ppww->setParameterValue( value, context );
727 }
728 }
729
730 // Only update class count if different - otherwise typing value gets very messy
731 int nclasses = ranges.count();
732 if ( nclasses && ( updateCount || ( method && ( method->flags() & QgsClassificationMethod::MethodProperty::IgnoresClassCount ) ) ) )
733 {
734 spinGraduatedClasses->setValue( ranges.count() );
735 }
736 if ( method )
737 {
738 spinGraduatedClasses->setEnabled( !( method->flags() & QgsClassificationMethod::MethodProperty::IgnoresClassCount ) );
739 }
740 else
741 {
742 spinGraduatedClasses->setEnabled( true );
743 }
744
745 // set column
746 QString attrName = mRenderer->classAttribute();
747 mExpressionWidget->setField( attrName );
748 mHistogramWidget->setSourceFieldExp( attrName );
749
750 // set source symbol
751 if ( mRenderer->sourceSymbol() )
752 {
753 mGraduatedSymbol.reset( mRenderer->sourceSymbol()->clone() );
754 whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mGraduatedSymbol->clone() );
755 }
756
757 mModel->setRenderer( mRenderer.get() );
758 viewGraduated->setModel( mModel );
759
760 connect( viewGraduated->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsGraduatedSymbolRendererWidget::selectionChanged );
761
762 if ( mGraduatedSymbol )
763 {
764 mSizeUnitWidget->blockSignals( true );
765 mSizeUnitWidget->setUnit( mGraduatedSymbol->outputUnit() );
766 mSizeUnitWidget->setMapUnitScale( mGraduatedSymbol->mapUnitScale() );
767 mSizeUnitWidget->blockSignals( false );
768 }
769
770 // set source color ramp
771 methodComboBox->blockSignals( true );
772 switch ( mRenderer->graduatedMethod() )
773 {
775 {
776 methodComboBox->setCurrentIndex( methodComboBox->findData( ColorMode ) );
777 if ( mRenderer->sourceColorRamp() )
778 {
779 btnColorRamp->setColorRamp( mRenderer->sourceColorRamp() );
780 }
781 break;
782 }
784 {
785 methodComboBox->setCurrentIndex( methodComboBox->findData( SizeMode ) );
786 if ( !mRenderer->ranges().isEmpty() ) // avoid overriding default size with zeros
787 {
788 minSizeSpinBox->setValue( mRenderer->minSymbolSize() );
789 maxSizeSpinBox->setValue( mRenderer->maxSymbolSize() );
790 }
791 break;
792 }
793 }
794 toggleMethodWidgets( static_cast< MethodMode>( methodComboBox->currentData().toInt() ) );
795 methodComboBox->blockSignals( false );
796
797 viewGraduated->resizeColumnToContents( 0 );
798 viewGraduated->resizeColumnToContents( 1 );
799 viewGraduated->resizeColumnToContents( 2 );
800
801 mHistogramWidget->refresh();
802
804 mBlockUpdates--;
805
806 emit widgetChanged();
807}
808
810{
811 mRenderer->setClassAttribute( field );
812}
813
814void QgsGraduatedSymbolRendererWidget::methodComboBox_currentIndexChanged( int )
815{
816 const MethodMode newMethod = static_cast< MethodMode >( methodComboBox->currentData().toInt() );
817 toggleMethodWidgets( newMethod );
818 switch ( newMethod )
819 {
820 case ColorMode:
821 {
822 mRenderer->setGraduatedMethod( Qgis::GraduatedMethod::Color );
823 QgsColorRamp *ramp = btnColorRamp->colorRamp();
824
825 if ( !ramp )
826 {
827 QMessageBox::critical( this, tr( "Select Method" ), tr( "No color ramp defined." ) );
828 return;
829 }
830 mRenderer->setSourceColorRamp( ramp );
832 break;
833 }
834
835 case SizeMode:
836 {
837 lblColorRamp->setVisible( false );
838 btnColorRamp->setVisible( false );
839 lblSize->setVisible( true );
840 minSizeSpinBox->setVisible( true );
841 lblSize->setVisible( true );
842 maxSizeSpinBox->setVisible( true );
843 mSizeUnitWidget->setVisible( true );
844
845 mRenderer->setGraduatedMethod( Qgis::GraduatedMethod::Size );
846 reapplySizes();
847 break;
848 }
849 }
850}
851
852void QgsGraduatedSymbolRendererWidget::updateMethodParameters()
853{
854 clearParameterWidgets();
855
856 const QString methodId = cboGraduatedMode->currentData().toString();
858 Q_ASSERT( method );
859
860 // need more context?
862
863 for ( const QgsProcessingParameterDefinition *def : method->parameterDefinitions() )
864 {
866 mParametersLayout->addRow( ppww->createWrappedLabel(), ppww->createWrappedWidget( context ) );
867
868 QVariant value = method->parameterValues().value( def->name(), def->defaultValueForGui() );
869 ppww->setParameterValue( value, context );
870
872
873 mParameterWidgetWrappers.push_back( std::unique_ptr<QgsAbstractProcessingParameterWidgetWrapper>( ppww ) );
874 }
875
876 spinGraduatedClasses->setEnabled( !( method->flags() & QgsClassificationMethod::MethodProperty::IgnoresClassCount ) );
877}
878
879void QgsGraduatedSymbolRendererWidget::toggleMethodWidgets( MethodMode mode )
880{
881 switch ( mode )
882 {
883 case ColorMode:
884 {
885 lblColorRamp->setVisible( true );
886 btnColorRamp->setVisible( true );
887 lblSize->setVisible( false );
888 minSizeSpinBox->setVisible( false );
889 lblSizeTo->setVisible( false );
890 maxSizeSpinBox->setVisible( false );
891 mSizeUnitWidget->setVisible( false );
892 break;
893 }
894
895 case SizeMode:
896 {
897 lblColorRamp->setVisible( false );
898 btnColorRamp->setVisible( false );
899 lblSize->setVisible( true );
900 minSizeSpinBox->setVisible( true );
901 lblSizeTo->setVisible( true );
902 maxSizeSpinBox->setVisible( true );
903 mSizeUnitWidget->setVisible( true );
904 break;
905 }
906 }
907}
908
909void QgsGraduatedSymbolRendererWidget::clearParameterWidgets()
910{
911 while ( mParametersLayout->rowCount() )
912 {
913 QFormLayout::TakeRowResult row = mParametersLayout->takeRow( 0 );
914 for ( QLayoutItem *item : QList<QLayoutItem *>( {row.labelItem, row.fieldItem} ) )
915 if ( item )
916 {
917 QWidget *widget = item->widget();
918 delete item;
919 if ( widget )
920 delete widget;
921 }
922 }
923 mParameterWidgetWrappers.clear();
924}
925
927{
928 if ( !mModel )
929 return;
930
931 mModel->updateSymbology();
932
934 spinGraduatedClasses->setValue( mRenderer->ranges().count() );
936
937 emit widgetChanged();
938}
939
941{
942 for ( const QgsLegendSymbolItem &legendSymbol : levels )
943 {
944 QgsSymbol *sym = legendSymbol.symbol();
945 for ( int layer = 0; layer < sym->symbolLayerCount(); layer++ )
946 {
947 mRenderer->setLegendSymbolItem( legendSymbol.ruleKey(), sym->clone() );
948 }
949 }
950 mRenderer->setUsingSymbolLevels( enabled );
951 mModel->updateSymbology();
952 emit widgetChanged();
953}
954
955void QgsGraduatedSymbolRendererWidget::cleanUpSymbolSelector( QgsPanelWidget *container )
956{
957 QgsSymbolSelectorWidget *dlg = qobject_cast<QgsSymbolSelectorWidget *>( container );
958 if ( !dlg )
959 return;
960
961 delete dlg->symbol();
962}
963
964void QgsGraduatedSymbolRendererWidget::updateSymbolsFromWidget()
965{
966 QgsSymbolSelectorWidget *dlg = qobject_cast<QgsSymbolSelectorWidget *>( sender() );
967 mGraduatedSymbol.reset( dlg->symbol()->clone() );
968
970}
971
973{
974 mSizeUnitWidget->blockSignals( true );
975 mSizeUnitWidget->setUnit( mGraduatedSymbol->outputUnit() );
976 mSizeUnitWidget->setMapUnitScale( mGraduatedSymbol->mapUnitScale() );
977 mSizeUnitWidget->blockSignals( false );
978
979 QItemSelectionModel *m = viewGraduated->selectionModel();
980 QModelIndexList selectedIndexes = m->selectedRows( 1 );
981 if ( !selectedIndexes.isEmpty() )
982 {
983 const auto constSelectedIndexes = selectedIndexes;
984 for ( const QModelIndex &idx : constSelectedIndexes )
985 {
986 if ( idx.isValid() )
987 {
988 int rangeIdx = idx.row();
989 QgsSymbol *newRangeSymbol = mGraduatedSymbol->clone();
990 if ( selectedIndexes.count() > 1 )
991 {
992 //if updating multiple ranges, retain the existing range colors
993 newRangeSymbol->setColor( mRenderer->ranges().at( rangeIdx ).symbol()->color() );
994 }
995 mRenderer->updateRangeSymbol( rangeIdx, newRangeSymbol );
996 }
997 }
998 }
999 else
1000 {
1001 mRenderer->updateSymbols( mGraduatedSymbol.get() );
1002 }
1003
1005 emit widgetChanged();
1006}
1007
1008void QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished( )
1009{
1010 const QString text = cboSymmetryPoint->lineEdit()->text();
1011 int index = cboSymmetryPoint->findText( text );
1012 if ( index != -1 )
1013 {
1014 cboSymmetryPoint->setCurrentIndex( index );
1015 }
1016 else
1017 {
1018 cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), text );
1020 }
1021}
1022
1023
1025{
1026 if ( mBlockUpdates )
1027 return;
1028
1029 QgsTemporaryCursorOverride override( Qt::WaitCursor );
1030 QString attrName = mExpressionWidget->currentField();
1031 int nclasses = spinGraduatedClasses->value();
1032
1033 const QString methodId = cboGraduatedMode->currentData().toString();
1035 Q_ASSERT( method );
1036
1037 int attrNum = mLayer->fields().lookupField( attrName );
1038
1039 QVariant minVal;
1040 QVariant maxVal;
1041 mLayer->minimumAndMaximumValue( attrNum, minVal, maxVal );
1042
1043 double minimum = minVal.toDouble();
1044 double maximum = maxVal.toDouble();
1045 mSymmetryPointValidator->setBottom( minimum );
1046 mSymmetryPointValidator->setTop( maximum );
1047 mSymmetryPointValidator->setMaxDecimals( spinPrecision->value() );
1048
1051 {
1052 // knowing that spinSymmetryPointForOtherMethods->value() is automatically put at minimum when out of min-max
1053 // using "(maximum-minimum)/100)" to avoid direct comparison of doubles
1054 double currentValue = QgsDoubleValidator::toDouble( cboSymmetryPoint->currentText() );
1055 if ( currentValue < ( minimum + ( maximum - minimum ) / 100. ) || currentValue > ( maximum - ( maximum - minimum ) / 100. ) )
1056 cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), QLocale().toString( minimum + ( maximum - minimum ) / 2., 'f', method->labelPrecision() + 2 ) );
1057 }
1058
1059 if ( mGroupBoxSymmetric->isChecked() )
1060 {
1061 double symmetryPoint = QgsDoubleValidator::toDouble( cboSymmetryPoint->currentText() );
1062 bool astride = cbxAstride->isChecked();
1063 method->setSymmetricMode( true, symmetryPoint, astride );
1064 }
1065
1066 QVariantMap parameterValues;
1067 for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
1068 parameterValues.insert( ppww->parameterDefinition()->name(), ppww->parameterValue() );
1069 method->setParameterValues( parameterValues );
1070
1071 // set method to renderer
1072 mRenderer->setClassificationMethod( method );
1073
1074 // create and set new renderer
1075 mRenderer->setClassAttribute( attrName );
1076
1077 // If complexity >= oN^2, warn for big dataset (more than 50k records)
1078 // and give the user the chance to cancel
1079 if ( method->codeComplexity() > 1 && mLayer->featureCount() > 50000 )
1080 {
1081 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 ) )
1082 {
1083 return;
1084 }
1085 }
1086
1087 if ( methodComboBox->currentData() == ColorMode )
1088 {
1089 std::unique_ptr<QgsColorRamp> ramp( btnColorRamp->colorRamp() );
1090 if ( !ramp )
1091 {
1092 QMessageBox::critical( this, tr( "Apply Classification" ), tr( "No color ramp defined." ) );
1093 return;
1094 }
1095 mRenderer->setSourceColorRamp( ramp.release() );
1096 }
1097 else
1098 {
1099 mRenderer->setSourceColorRamp( nullptr );
1100 }
1101
1102 mRenderer->updateClasses( mLayer, nclasses );
1103
1104 if ( methodComboBox->currentData() == SizeMode )
1105 mRenderer->setSymbolSizes( minSizeSpinBox->value(), maxSizeSpinBox->value() );
1106
1107 mRenderer->calculateLabelPrecision();
1108 // PrettyBreaks and StdDev calculation don't generate exact
1109 // number of classes - leave user interface unchanged for these
1110 updateUiFromRenderer( false );
1111}
1112
1114{
1115 std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
1116 if ( !ramp )
1117 return;
1118
1119 mRenderer->updateColorRamp( ramp.release() );
1120 mRenderer->updateSymbols( mGraduatedSymbol.get() );
1122}
1123
1125{
1126 mRenderer->setSymbolSizes( minSizeSpinBox->value(), maxSizeSpinBox->value() );
1127 mRenderer->updateSymbols( mGraduatedSymbol.get() );
1129}
1130
1131#if 0
1132int QgsRendererPropertiesDialog::currentRangeRow()
1133{
1134 QModelIndex idx = viewGraduated->selectionModel()->currentIndex();
1135 if ( !idx.isValid() )
1136 return -1;
1137 return idx.row();
1138}
1139#endif
1140
1142{
1143 QList<int> rows;
1144 QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1145
1146 const auto constSelectedRows = selectedRows;
1147 for ( const QModelIndex &r : constSelectedRows )
1148 {
1149 if ( r.isValid() )
1150 {
1151 rows.append( r.row() );
1152 }
1153 }
1154 return rows;
1155}
1156
1158{
1160 QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1161 QModelIndexList::const_iterator sIt = selectedRows.constBegin();
1162
1163 for ( ; sIt != selectedRows.constEnd(); ++sIt )
1164 {
1165 selectedRanges.append( mModel->rendererRange( *sIt ) );
1166 }
1167 return selectedRanges;
1168}
1169
1171{
1172 if ( idx.isValid() && idx.column() == 0 )
1173 changeRangeSymbol( idx.row() );
1174 if ( idx.isValid() && idx.column() == 1 )
1175 changeRange( idx.row() );
1176}
1177
1179{
1180 if ( !idx.isValid() )
1181 mRowSelected = -1;
1182 else
1183 mRowSelected = idx.row();
1184}
1185
1189
1191{
1192 const QgsRendererRange &range = mRenderer->ranges()[rangeIdx];
1193 std::unique_ptr< QgsSymbol > newSymbol( range.symbol()->clone() );
1195 if ( panel && panel->dockMode() )
1196 {
1197 // bit tricky here - the widget doesn't take ownership of the symbol. So we need it to last for the duration of the
1198 // panel's existence. Accordingly, just kinda give it ownership here, and clean up in cleanUpSymbolSelector
1199 QgsSymbolSelectorWidget *dlg = new QgsSymbolSelectorWidget( newSymbol.release(), mStyle, mLayer, panel );
1200 dlg->setContext( mContext );
1201 dlg->setPanelTitle( range.label() );
1202 connect( dlg, &QgsPanelWidget::widgetChanged, this, &QgsGraduatedSymbolRendererWidget::updateSymbolsFromWidget );
1203 connect( dlg, &QgsPanelWidget::panelAccepted, this, &QgsGraduatedSymbolRendererWidget::cleanUpSymbolSelector );
1204 openPanel( dlg );
1205 }
1206 else
1207 {
1208 QgsSymbolSelectorDialog dlg( newSymbol.get(), mStyle, mLayer, panel );
1209 dlg.setContext( mContext );
1210 if ( !dlg.exec() || !newSymbol )
1211 {
1212 return;
1213 }
1214
1215 mGraduatedSymbol = std::move( newSymbol );
1216 whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mGraduatedSymbol->clone() );
1218 }
1219}
1220
1222{
1223 QgsLUDialog dialog( this );
1224
1225 const QgsRendererRange &range = mRenderer->ranges()[rangeIdx];
1226 // Add arbitrary 2 to number of decimal places to retain a bit extra.
1227 // Ensures users can see if legend is not completely honest!
1228 int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
1229 if ( decimalPlaces < 0 ) decimalPlaces = 0;
1230 dialog.setLowerValue( QLocale().toString( range.lowerValue(), 'f', decimalPlaces ) );
1231 dialog.setUpperValue( QLocale().toString( range.upperValue(), 'f', decimalPlaces ) );
1232
1233 if ( dialog.exec() == QDialog::Accepted )
1234 {
1235 mRenderer->updateRangeUpperValue( rangeIdx, dialog.upperValueDouble() );
1236 mRenderer->updateRangeLowerValue( rangeIdx, dialog.lowerValueDouble() );
1237
1238 //If the boundaries have to stay linked, we update the ranges above and below, as well as their label if needed
1239 if ( cbxLinkBoundaries->isChecked() )
1240 {
1241 if ( rangeIdx > 0 )
1242 {
1243 mRenderer->updateRangeUpperValue( rangeIdx - 1, dialog.lowerValueDouble() );
1244 }
1245
1246 if ( rangeIdx < mRenderer->ranges().size() - 1 )
1247 {
1248 mRenderer->updateRangeLowerValue( rangeIdx + 1, dialog.upperValueDouble() );
1249 }
1250 }
1251 }
1252 mHistogramWidget->refresh();
1253 emit widgetChanged();
1254}
1255
1257{
1258 mModel->addClass( mGraduatedSymbol.get() );
1259 mHistogramWidget->refresh();
1260 emit widgetChanged();
1261
1262}
1263
1265{
1266 QList<int> classIndexes = selectedClasses();
1267 mModel->deleteRows( classIndexes );
1268 mHistogramWidget->refresh();
1269 emit widgetChanged();
1270}
1271
1273{
1274 mModel->removeAllRows();
1275 mHistogramWidget->refresh();
1276 emit widgetChanged();
1277}
1278
1280{
1281 const QgsRangeList &ranges = mRenderer->ranges();
1282 bool ordered = true;
1283 for ( int i = 1; i < ranges.size(); ++i )
1284 {
1285 if ( ranges[i] < ranges[i - 1] )
1286 {
1287 ordered = false;
1288 break;
1289 }
1290 }
1291 return ordered;
1292}
1293
1295{
1296 //If the checkbox controlling the link between boundaries was unchecked and we check it, we have to link the boundaries
1297 //This is done by updating all lower ranges to the upper value of the range above
1298 if ( linked )
1299 {
1300 if ( ! rowsOrdered() )
1301 {
1302 int result = QMessageBox::warning(
1303 this,
1304 tr( "Link Class Boundaries" ),
1305 tr( "Rows will be reordered before linking boundaries. Continue?" ),
1306 QMessageBox::Ok | QMessageBox::Cancel );
1307 if ( result != QMessageBox::Ok )
1308 {
1309 cbxLinkBoundaries->setChecked( false );
1310 return;
1311 }
1312 mRenderer->sortByValue();
1313 }
1314
1315 // Ok to proceed
1316 for ( int i = 1; i < mRenderer->ranges().size(); ++i )
1317 {
1318 mRenderer->updateRangeLowerValue( i, mRenderer->ranges()[i - 1].upperValue() );
1319 }
1321 }
1322}
1323
1325{
1326 if ( item->column() == 2 )
1327 {
1328 QString label = item->text();
1329 int idx = item->row();
1330 mRenderer->updateRangeLabel( idx, label );
1331 }
1332}
1333
1335{
1336 mRenderer->classificationMethod()->setLabelFormat( txtLegendFormat->text() );
1337 mRenderer->classificationMethod()->setLabelPrecision( spinPrecision->value() );
1338 mRenderer->classificationMethod()->setLabelTrimTrailingZeroes( cbxTrimTrailingZeroes->isChecked() );
1339 mRenderer->updateRangeLabels();
1340 mModel->updateLabels();
1341}
1342
1343
1345{
1346 QList<QgsSymbol *> selectedSymbols;
1347
1348 QItemSelectionModel *m = viewGraduated->selectionModel();
1349 QModelIndexList selectedIndexes = m->selectedRows( 1 );
1350 if ( !selectedIndexes.isEmpty() )
1351 {
1352 const QgsRangeList &ranges = mRenderer->ranges();
1353 QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
1354 for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
1355 {
1356 QStringList list = m->model()->data( *indexIt ).toString().split( ' ' );
1357 if ( list.size() < 3 )
1358 {
1359 continue;
1360 }
1361 // Not strictly necessary because the range should have been sanitized already
1362 // after user input, but being permissive never hurts
1363 bool ok = false;
1364 double lowerBound = qgsPermissiveToDouble( list.at( 0 ), ok );
1365 if ( ! ok )
1366 lowerBound = 0.0;
1367 double upperBound = qgsPermissiveToDouble( list.at( 2 ), ok );
1368 if ( ! ok )
1369 upperBound = 0.0;
1370 QgsSymbol *s = findSymbolForRange( lowerBound, upperBound, ranges );
1371 if ( s )
1372 {
1373 selectedSymbols.append( s );
1374 }
1375 }
1376 }
1377 return selectedSymbols;
1378}
1379
1380QgsSymbol *QgsGraduatedSymbolRendererWidget::findSymbolForRange( double lowerBound, double upperBound, const QgsRangeList &ranges ) const
1381{
1382 int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
1383 if ( decimalPlaces < 0 )
1384 decimalPlaces = 0;
1385 double precision = 1.0 / std::pow( 10, decimalPlaces );
1386
1387 for ( QgsRangeList::const_iterator it = ranges.begin(); it != ranges.end(); ++it )
1388 {
1389 if ( qgsDoubleNear( lowerBound, it->lowerValue(), precision ) && qgsDoubleNear( upperBound, it->upperValue(), precision ) )
1390 {
1391 return it->symbol();
1392 }
1393 }
1394 return nullptr;
1395}
1396
1398{
1399 if ( mModel )
1400 {
1401 mModel->updateSymbology();
1402 }
1403 mHistogramWidget->refresh();
1404 emit widgetChanged();
1405}
1406
1411
1413{
1414 viewGraduated->selectionModel()->clear();
1415 if ( ! rowsOrdered() )
1416 {
1417 cbxLinkBoundaries->setChecked( false );
1418 }
1419 emit widgetChanged();
1420}
1421
1426
1428{
1429 if ( !event )
1430 {
1431 return;
1432 }
1433
1434 if ( event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier )
1435 {
1436 mCopyBuffer.clear();
1437 mCopyBuffer = selectedRanges();
1438 }
1439 else if ( event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier )
1440 {
1441 QgsRangeList::const_iterator rIt = mCopyBuffer.constBegin();
1442 for ( ; rIt != mCopyBuffer.constEnd(); ++rIt )
1443 {
1444 mModel->addClass( *rIt );
1445 }
1446 emit widgetChanged();
1447 }
1448}
1449
1450void QgsGraduatedSymbolRendererWidget::selectionChanged( const QItemSelection &, const QItemSelection & )
1451{
1452 const QgsRangeList ranges = selectedRanges();
1453 if ( !ranges.isEmpty() )
1454 {
1455 whileBlocking( btnChangeGraduatedSymbol )->setSymbol( ranges.at( 0 ).symbol()->clone() );
1456 }
1457 else if ( mRenderer->sourceSymbol() )
1458 {
1459 whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mRenderer->sourceSymbol()->clone() );
1460 }
1461 btnChangeGraduatedSymbol->setDialogTitle( ranges.size() == 1 ? ranges.at( 0 ).label() : tr( "Symbol Settings" ) );
1462}
1463
1464void QgsGraduatedSymbolRendererWidget::dataDefinedSizeLegend()
1465{
1466 QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( mGraduatedSymbol.get() ); // this should be only enabled for marker symbols
1467 QgsDataDefinedSizeLegendWidget *panel = createDataDefinedSizeLegendWidget( s, mRenderer->dataDefinedSizeLegend() );
1468 if ( panel )
1469 {
1470 connect( panel, &QgsPanelWidget::widgetChanged, this, [ = ]
1471 {
1472 mRenderer->setDataDefinedSizeLegend( panel->dataDefinedSizeLegend() );
1473 emit widgetChanged();
1474 } );
1475 openPanel( panel ); // takes ownership of the panel
1476 }
1477}
1478
1479void QgsGraduatedSymbolRendererWidget::changeGraduatedSymbol()
1480{
1481 mGraduatedSymbol.reset( btnChangeGraduatedSymbol->symbol()->clone() );
1483}
1484
1486{
1487 std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
1488 if ( !tempSymbol )
1489 return;
1490
1491 const QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1492 for ( const QModelIndex &index : selectedRows )
1493 {
1494 if ( !index.isValid() )
1495 continue;
1496
1497 const int row = index.row();
1498 if ( !mRenderer || mRenderer->ranges().size() <= row )
1499 continue;
1500
1501 if ( mRenderer->ranges().at( row ).symbol()->type() != tempSymbol->type() )
1502 continue;
1503
1504 std::unique_ptr< QgsSymbol > newCatSymbol( tempSymbol->clone() );
1505 if ( selectedRows.count() > 1 )
1506 {
1507 //if updating multiple ranges, retain the existing category colors
1508 newCatSymbol->setColor( mRenderer->ranges().at( row ).symbol()->color() );
1509 }
1510
1511 mRenderer->updateRangeSymbol( row, newCatSymbol.release() );
1512 }
1513 emit widgetChanged();
1514}
@ Size
Alter size of symbols.
@ Color
Alter color of symbols.
@ 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.
@ IgnoresClassCount
The classification method does not compute classes based on a class count (since QGIS 3....
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.
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.
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.
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.
double lowerValueDouble() const
Returns the lower value.
void setLowerValue(const QString &val)
void setUpperValue(const QString &val)
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.
A QProxyStyle subclass which correctly sets the base style to match the QGIS application style,...
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.
void setColor(const QColor &color) const
Sets the color for the symbol.
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.
QList< QgsUnitTypes::RenderUnit > RenderUnitList
List of render units.
@ RenderPoints
Points (e.g., for font sizes)
@ RenderPixels
Pixels.
@ RenderInches
Inches.
@ RenderMillimeters
Millimeters.
@ RenderMapUnits
Map units.
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:476
QList< QgsLegendSymbolItem > QgsLegendSymbolList
#define QgsDebugMsg(str)
Definition qgslogger.h:38
QList< QgsRendererRange > QgsRangeList
int precision