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