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