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