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