QGIS API Documentation 4.1.0-Master (467af3bbe65)
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 mParameterWidgetWrappers.clear();
636}
637
639{
640 return mRenderer.get();
641}
642
644{
646 btnChangeGraduatedSymbol->setMapCanvas( context.mapCanvas() );
647 btnChangeGraduatedSymbol->setMessageBar( context.messageBar() );
648}
649
651{
652 delete mActionLevels;
653 mActionLevels = nullptr;
654}
655
656// Connect/disconnect event handlers which trigger updating renderer
658{
659 connect( spinGraduatedClasses, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
660 connect( cboGraduatedMode, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
662 connect( spinPrecision, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
663 connect( cbxTrimTrailingZeroes, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
664 connect( txtLegendFormat, &QLineEdit::textChanged, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
665 connect( minSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
666 connect( maxSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
667
668 connect( mModel, &QgsGraduatedSymbolRendererModel::rowsMoved, this, &QgsGraduatedSymbolRendererWidget::rowsMoved );
669 connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsGraduatedSymbolRendererWidget::modelDataChanged );
670
671 connect( mGroupBoxSymmetric, &QGroupBox::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
672 connect( cbxAstride, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
673 connect( cboSymmetryPoint, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
674 connect( cboSymmetryPoint->lineEdit(), &QLineEdit::editingFinished, this, &QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished );
675
676 for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
677 {
679 }
680}
681
682// Connect/disconnect event handlers which trigger updating renderer
684{
685 disconnect( spinGraduatedClasses, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
686 disconnect( cboGraduatedMode, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
688 disconnect( spinPrecision, qOverload<int>( &QSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
689 disconnect( cbxTrimTrailingZeroes, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
690 disconnect( txtLegendFormat, &QLineEdit::textChanged, this, &QgsGraduatedSymbolRendererWidget::labelFormatChanged );
691 disconnect( minSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
692 disconnect( maxSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsGraduatedSymbolRendererWidget::reapplySizes );
693
694 disconnect( mModel, &QgsGraduatedSymbolRendererModel::rowsMoved, this, &QgsGraduatedSymbolRendererWidget::rowsMoved );
695 disconnect( mModel, &QAbstractItemModel::dataChanged, this, &QgsGraduatedSymbolRendererWidget::modelDataChanged );
696
697 disconnect( mGroupBoxSymmetric, &QGroupBox::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
698 disconnect( cbxAstride, &QAbstractButton::toggled, this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
699 disconnect( cboSymmetryPoint, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsGraduatedSymbolRendererWidget::classifyGraduated );
700 disconnect( cboSymmetryPoint->lineEdit(), &QLineEdit::editingFinished, this, &QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished );
701
702 for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
703 {
705 }
706}
707
709{
711 mBlockUpdates++;
712
713 const QgsClassificationMethod *method = mRenderer->classificationMethod();
714
715 const QgsRangeList ranges = mRenderer->ranges();
716
717 // use the breaks for symmetry point
718 int precision = spinPrecision->value() + 2;
719 while ( cboSymmetryPoint->count() )
720 cboSymmetryPoint->removeItem( 0 );
721 for ( int i = 0; i < ranges.count() - 1; i++ )
722 cboSymmetryPoint->addItem( QLocale().toString( ranges.at( i ).upperValue(), 'f', precision ), ranges.at( i ).upperValue() );
723
724 if ( method )
725 {
726 int idx = cboGraduatedMode->findData( method->id() );
727 if ( idx >= 0 )
728 cboGraduatedMode->setCurrentIndex( idx );
729
730 mGroupBoxSymmetric->setVisible( method->symmetricModeAvailable() );
731 mGroupBoxSymmetric->setChecked( method->symmetricModeEnabled() );
732 cbxAstride->setChecked( method->symmetryAstride() );
733 if ( method->symmetricModeEnabled() )
734 cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), QLocale().toString( method->symmetryPoint(), 'f', method->labelPrecision() + 2 ) );
735
736 txtLegendFormat->setText( method->labelFormat() );
737 spinPrecision->setValue( method->labelPrecision() );
738 cbxTrimTrailingZeroes->setChecked( method->labelTrimTrailingZeroes() );
739
741 for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
742 {
743 const QgsProcessingParameterDefinition *def = ppww->parameterDefinition();
744 QVariant value = method->parameterValues().value( def->name(), def->defaultValueForGui() );
745 ppww->setParameterValue( value, context );
746 }
747 }
748
749 // Only update class count if different - otherwise typing value gets very messy
750 int nclasses = ranges.count();
751 if ( nclasses && ( updateCount || ( method && ( method->flags() & QgsClassificationMethod::MethodProperty::IgnoresClassCount ) ) ) )
752 {
753 spinGraduatedClasses->setValue( ranges.count() );
754 }
755 if ( method )
756 {
757 spinGraduatedClasses->setEnabled( !( method->flags() & QgsClassificationMethod::MethodProperty::IgnoresClassCount ) );
758 }
759 else
760 {
761 spinGraduatedClasses->setEnabled( true );
762 }
763
764 // set column
765 QString attrName = mRenderer->classAttribute();
766 mExpressionWidget->setField( attrName );
767 mHistogramWidget->setSourceFieldExp( attrName );
768
769 // set source symbol
770 if ( mRenderer->sourceSymbol() )
771 {
772 mGraduatedSymbol.reset( mRenderer->sourceSymbol()->clone() );
773 whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mGraduatedSymbol->clone() );
774 }
775
776 mModel->setRenderer( mRenderer.get() );
777 viewGraduated->setModel( mModel );
778
779 connect( viewGraduated->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsGraduatedSymbolRendererWidget::selectionChanged );
780
781 if ( mGraduatedSymbol )
782 {
783 mSizeUnitWidget->blockSignals( true );
784 mSizeUnitWidget->setUnit( mGraduatedSymbol->outputUnit() );
785 mSizeUnitWidget->setMapUnitScale( mGraduatedSymbol->mapUnitScale() );
786 mSizeUnitWidget->blockSignals( false );
787 }
788
789 // set source color ramp
790 methodComboBox->blockSignals( true );
791 switch ( mRenderer->graduatedMethod() )
792 {
794 {
795 methodComboBox->setCurrentIndex( methodComboBox->findData( ColorMode ) );
796 if ( mRenderer->sourceColorRamp() )
797 {
798 btnColorRamp->setColorRamp( mRenderer->sourceColorRamp() );
799 }
800 break;
801 }
803 {
804 methodComboBox->setCurrentIndex( methodComboBox->findData( SizeMode ) );
805 if ( !mRenderer->ranges().isEmpty() ) // avoid overriding default size with zeros
806 {
807 minSizeSpinBox->setValue( mRenderer->minSymbolSize() );
808 maxSizeSpinBox->setValue( mRenderer->maxSymbolSize() );
809 }
810 break;
811 }
812 }
813 toggleMethodWidgets( static_cast<MethodMode>( methodComboBox->currentData().toInt() ) );
814 methodComboBox->blockSignals( false );
815
816 viewGraduated->resizeColumnToContents( 0 );
817 viewGraduated->resizeColumnToContents( 1 );
818 viewGraduated->resizeColumnToContents( 2 );
819
820 mHistogramWidget->refresh();
821
823 mBlockUpdates--;
824
825 emit widgetChanged();
826}
827
829{
830 mRenderer->setClassAttribute( field );
831 emit widgetChanged();
832}
833
834void QgsGraduatedSymbolRendererWidget::methodComboBox_currentIndexChanged( int )
835{
836 const MethodMode newMethod = static_cast<MethodMode>( methodComboBox->currentData().toInt() );
837 toggleMethodWidgets( newMethod );
838 switch ( newMethod )
839 {
840 case ColorMode:
841 {
842 mRenderer->setGraduatedMethod( Qgis::GraduatedMethod::Color );
843 QgsColorRamp *ramp = btnColorRamp->colorRamp();
844
845 if ( !ramp )
846 {
847 QMessageBox::critical( this, tr( "Select Method" ), tr( "No color ramp defined." ) );
848 return;
849 }
850 mRenderer->setSourceColorRamp( ramp );
852 break;
853 }
854
855 case SizeMode:
856 {
857 lblColorRamp->setVisible( false );
858 btnColorRamp->setVisible( false );
859 lblSize->setVisible( true );
860 minSizeSpinBox->setVisible( true );
861 lblSize->setVisible( true );
862 maxSizeSpinBox->setVisible( true );
863 mSizeUnitWidget->setVisible( true );
864
865 mRenderer->setGraduatedMethod( Qgis::GraduatedMethod::Size );
866 reapplySizes();
867 break;
868 }
869 }
870}
871
872void QgsGraduatedSymbolRendererWidget::updateMethodParameters()
873{
874 clearParameterWidgets();
875
876 const QString methodId = cboGraduatedMode->currentData().toString();
877 mClassificationMethod = QgsApplication::classificationMethodRegistry()->method( methodId );
878 Q_ASSERT( mClassificationMethod.get() );
879
880 // need more context?
881 QgsProcessingContext context;
882
883 for ( const QgsProcessingParameterDefinition *def : mClassificationMethod->parameterDefinitions() )
884 {
885 QgsAbstractProcessingParameterWidgetWrapper *ppww = QgsGui::processingGuiRegistry()->createParameterWidgetWrapper( def, Qgis::ProcessingMode::Standard );
886 mParametersLayout->addRow( ppww->createWrappedLabel(), ppww->createWrappedWidget( context ) );
887
888 QVariant value = mClassificationMethod->parameterValues().value( def->name(), def->defaultValueForGui() );
889 ppww->setParameterValue( value, context );
890
892
893 mParameterWidgetWrappers.push_back( std::unique_ptr<QgsAbstractProcessingParameterWidgetWrapper>( ppww ) );
894 }
895
896 spinGraduatedClasses->setEnabled( !( mClassificationMethod->flags() & QgsClassificationMethod::MethodProperty::IgnoresClassCount ) );
897}
898
899void QgsGraduatedSymbolRendererWidget::toggleMethodWidgets( MethodMode mode )
900{
901 switch ( mode )
902 {
903 case ColorMode:
904 {
905 lblColorRamp->setVisible( true );
906 btnColorRamp->setVisible( true );
907 lblSize->setVisible( false );
908 minSizeSpinBox->setVisible( false );
909 lblSizeTo->setVisible( false );
910 maxSizeSpinBox->setVisible( false );
911 mSizeUnitWidget->setVisible( false );
912 break;
913 }
914
915 case SizeMode:
916 {
917 lblColorRamp->setVisible( false );
918 btnColorRamp->setVisible( false );
919 lblSize->setVisible( true );
920 minSizeSpinBox->setVisible( true );
921 lblSizeTo->setVisible( true );
922 maxSizeSpinBox->setVisible( true );
923 mSizeUnitWidget->setVisible( true );
924 break;
925 }
926 }
927}
928
929void QgsGraduatedSymbolRendererWidget::clearParameterWidgets()
930{
931 while ( mParametersLayout->rowCount() )
932 {
933 QFormLayout::TakeRowResult row = mParametersLayout->takeRow( 0 );
934 for ( QLayoutItem *item : { row.labelItem, row.fieldItem } )
935 if ( item )
936 {
937 QWidget *widget = item->widget();
938 delete item;
939 if ( widget )
940 delete widget;
941 }
942 }
943 mParameterWidgetWrappers.clear();
944}
945
947{
948 if ( !mModel )
949 return;
950
951 mModel->updateSymbology();
952
954 spinGraduatedClasses->setValue( mRenderer->ranges().count() );
956
957 emit widgetChanged();
958}
959
961{
962 for ( const QgsLegendSymbolItem &legendSymbol : levels )
963 {
964 QgsSymbol *sym = legendSymbol.symbol();
965 for ( int layer = 0; layer < sym->symbolLayerCount(); layer++ )
966 {
967 mRenderer->setLegendSymbolItem( legendSymbol.ruleKey(), sym->clone() );
968 }
969 }
970 mRenderer->setUsingSymbolLevels( enabled );
971 mModel->updateSymbology();
972 emit widgetChanged();
973}
974
975void QgsGraduatedSymbolRendererWidget::updateSymbolsFromWidget( QgsSymbolSelectorWidget *widget )
976{
977 mGraduatedSymbol.reset( widget->symbol()->clone() );
978
980}
981
983{
984 mSizeUnitWidget->blockSignals( true );
985 mSizeUnitWidget->setUnit( mGraduatedSymbol->outputUnit() );
986 mSizeUnitWidget->setMapUnitScale( mGraduatedSymbol->mapUnitScale() );
987 mSizeUnitWidget->blockSignals( false );
988
989 QItemSelectionModel *m = viewGraduated->selectionModel();
990 QModelIndexList selectedIndexes = m->selectedRows( 1 );
991 if ( !selectedIndexes.isEmpty() )
992 {
993 const auto constSelectedIndexes = selectedIndexes;
994 for ( const QModelIndex &idx : constSelectedIndexes )
995 {
996 if ( idx.isValid() )
997 {
998 int rangeIdx = idx.row();
999 QgsSymbol *newRangeSymbol = mGraduatedSymbol->clone();
1000 if ( selectedIndexes.count() > 1 )
1001 {
1002 //if updating multiple ranges, retain the existing range colors
1003 newRangeSymbol->setColor( mRenderer->ranges().at( rangeIdx ).symbol()->color() );
1004 }
1005 mRenderer->updateRangeSymbol( rangeIdx, newRangeSymbol );
1006 }
1007 }
1008 }
1009 else
1010 {
1011 mRenderer->updateSymbols( mGraduatedSymbol.get() );
1012 }
1013
1015 emit widgetChanged();
1016}
1017
1018void QgsGraduatedSymbolRendererWidget::symmetryPointEditingFinished()
1019{
1020 const QString text = cboSymmetryPoint->lineEdit()->text();
1021 int index = cboSymmetryPoint->findText( text );
1022 if ( index != -1 )
1023 {
1024 cboSymmetryPoint->setCurrentIndex( index );
1025 }
1026 else
1027 {
1028 cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), text );
1030 }
1031}
1032
1033
1035{
1036 mUpdateTimer.start( 500 );
1037}
1038
1039void QgsGraduatedSymbolRendererWidget::classifyGraduatedImpl()
1040{
1041 if ( mBlockUpdates || !mClassificationMethod )
1042 return;
1043
1044 QgsTemporaryCursorOverride override( Qt::WaitCursor );
1045 QString attrName = mExpressionWidget->currentField();
1046 int nclasses = spinGraduatedClasses->value();
1047
1048 int attrNum = mLayer->fields().lookupField( attrName );
1049
1050 QVariant minVal;
1051 QVariant maxVal;
1052 mLayer->minimumAndMaximumValue( attrNum, minVal, maxVal );
1053
1054 double minimum = minVal.toDouble();
1055 double maximum = maxVal.toDouble();
1056 mSymmetryPointValidator->setBottom( minimum );
1057 mSymmetryPointValidator->setTop( maximum );
1058 mSymmetryPointValidator->setMaxDecimals( spinPrecision->value() );
1059
1060 if ( mClassificationMethod->id() == QgsClassificationEqualInterval::METHOD_ID || mClassificationMethod->id() == QgsClassificationStandardDeviation::METHOD_ID )
1061 {
1062 // knowing that spinSymmetryPointForOtherMethods->value() is automatically put at minimum when out of min-max
1063 // using "(maximum-minimum)/100)" to avoid direct comparison of doubles
1064 double currentValue = QgsDoubleValidator::toDouble( cboSymmetryPoint->currentText() );
1065 if ( currentValue < ( minimum + ( maximum - minimum ) / 100. ) || currentValue > ( maximum - ( maximum - minimum ) / 100. ) )
1066 cboSymmetryPoint->setItemText( cboSymmetryPoint->currentIndex(), QLocale().toString( minimum + ( maximum - minimum ) / 2., 'f', mClassificationMethod->labelPrecision() + 2 ) );
1067 }
1068
1069 if ( mGroupBoxSymmetric->isChecked() )
1070 {
1071 double symmetryPoint = QgsDoubleValidator::toDouble( cboSymmetryPoint->currentText() );
1072 bool astride = cbxAstride->isChecked();
1073 mClassificationMethod->setSymmetricMode( true, symmetryPoint, astride );
1074 }
1075
1076 QVariantMap parameterValues;
1077 for ( const auto &ppww : std::as_const( mParameterWidgetWrappers ) )
1078 parameterValues.insert( ppww->parameterDefinition()->name(), ppww->parameterValue() );
1079 mClassificationMethod->setParameterValues( parameterValues );
1080
1081 // set method to renderer
1082 mRenderer->setClassificationMethod( mClassificationMethod->clone().release() );
1083
1084 // create and set new renderer
1085 mRenderer->setClassAttribute( attrName );
1086
1087 // If complexity >= oN^2, warn for big dataset (more than 50k records)
1088 // and give the user the chance to cancel
1089 if ( mRenderer->classificationMethod()->codeComplexity() > 1 && mLayer->featureCount() > 50000 )
1090 {
1091 if ( QMessageBox::Cancel
1092 == QMessageBox::question(
1093 this,
1094 tr( "Apply Classification" ),
1095 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." ),
1096 QMessageBox::Cancel,
1097 QMessageBox::Ok
1098 ) )
1099 {
1100 return;
1101 }
1102 }
1103
1104 if ( methodComboBox->currentData() == ColorMode )
1105 {
1106 std::unique_ptr<QgsColorRamp> ramp( btnColorRamp->colorRamp() );
1107 if ( !ramp )
1108 {
1109 QMessageBox::critical( this, tr( "Apply Classification" ), tr( "No color ramp defined." ) );
1110 return;
1111 }
1112 mRenderer->setSourceColorRamp( ramp.release() );
1113 }
1114 else
1115 {
1116 mRenderer->setSourceColorRamp( nullptr );
1117 }
1118
1119 QString error;
1120 mRenderer->updateClasses( mLayer, nclasses, error );
1121
1122 if ( !error.isEmpty() )
1123 QMessageBox::critical( this, tr( "Apply Classification" ), error );
1124
1125 if ( methodComboBox->currentData() == SizeMode )
1126 mRenderer->setSymbolSizes( minSizeSpinBox->value(), maxSizeSpinBox->value() );
1127
1128 mRenderer->calculateLabelPrecision();
1129 // PrettyBreaks and StdDev calculation don't generate exact
1130 // number of classes - leave user interface unchanged for these
1131 updateUiFromRenderer( false );
1132}
1133
1135{
1136 std::unique_ptr<QgsColorRamp> ramp( btnColorRamp->colorRamp() );
1137 if ( !ramp )
1138 return;
1139
1140 mRenderer->updateColorRamp( ramp.release() );
1141 mRenderer->updateSymbols( mGraduatedSymbol.get() );
1143}
1144
1146{
1147 mRenderer->setSymbolSizes( minSizeSpinBox->value(), maxSizeSpinBox->value() );
1148 mRenderer->updateSymbols( mGraduatedSymbol.get() );
1150}
1151
1152#if 0
1153int QgsRendererPropertiesDialog::currentRangeRow()
1154{
1155 QModelIndex idx = viewGraduated->selectionModel()->currentIndex();
1156 if ( !idx.isValid() )
1157 return -1;
1158 return idx.row();
1159}
1160#endif
1161
1163{
1164 QList<int> rows;
1165 QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1166
1167 const auto constSelectedRows = selectedRows;
1168 for ( const QModelIndex &r : constSelectedRows )
1169 {
1170 if ( r.isValid() )
1171 {
1172 rows.append( r.row() );
1173 }
1174 }
1175 return rows;
1176}
1177
1179{
1181 QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1182 QModelIndexList::const_iterator sIt = selectedRows.constBegin();
1183
1184 for ( ; sIt != selectedRows.constEnd(); ++sIt )
1185 {
1186 selectedRanges.append( mModel->rendererRange( *sIt ) );
1187 }
1188 return selectedRanges;
1189}
1190
1192{
1193 if ( idx.isValid() && idx.column() == 0 )
1194 changeRangeSymbol( idx.row() );
1195 if ( idx.isValid() && idx.column() == 1 )
1196 changeRange( idx.row() );
1197}
1198
1200{
1201 if ( !idx.isValid() )
1202 mRowSelected = -1;
1203 else
1204 mRowSelected = idx.row();
1205}
1206
1209
1211{
1212 const QgsRendererRange &range = mRenderer->ranges()[rangeIdx];
1213 std::unique_ptr<QgsSymbol> newSymbol( range.symbol()->clone() );
1215 if ( panel && panel->dockMode() )
1216 {
1218 widget->setContext( mContext );
1219 widget->setPanelTitle( range.label() );
1220 connect( widget, &QgsPanelWidget::widgetChanged, this, [this, widget] { updateSymbolsFromWidget( widget ); } );
1221 openPanel( widget );
1222 }
1223 else
1224 {
1225 QgsSymbolSelectorDialog dlg( newSymbol.get(), mStyle, mLayer, panel );
1226 dlg.setContext( mContext );
1227 if ( !dlg.exec() || !newSymbol )
1228 {
1229 return;
1230 }
1231
1232 mGraduatedSymbol = std::move( newSymbol );
1233 whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mGraduatedSymbol->clone() );
1235 }
1236}
1237
1239{
1240 QgsLUDialog dialog( this );
1241
1242 const QgsRendererRange &range = mRenderer->ranges()[rangeIdx];
1243 // Add arbitrary 2 to number of decimal places to retain a bit extra.
1244 // Ensures users can see if legend is not completely honest!
1245 int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
1246 if ( decimalPlaces < 0 )
1247 decimalPlaces = 0;
1248 dialog.setLowerValue( QLocale().toString( range.lowerValue(), 'f', decimalPlaces ) );
1249 dialog.setUpperValue( QLocale().toString( range.upperValue(), 'f', decimalPlaces ) );
1250
1251 if ( dialog.exec() == QDialog::Accepted )
1252 {
1253 mRenderer->updateRangeUpperValue( rangeIdx, dialog.upperValueDouble() );
1254 mRenderer->updateRangeLowerValue( rangeIdx, dialog.lowerValueDouble() );
1255
1256 //If the boundaries have to stay linked, we update the ranges above and below, as well as their label if needed
1257 if ( cbxLinkBoundaries->isChecked() )
1258 {
1259 if ( rangeIdx > 0 )
1260 {
1261 mRenderer->updateRangeUpperValue( rangeIdx - 1, dialog.lowerValueDouble() );
1262 }
1263
1264 if ( rangeIdx < mRenderer->ranges().size() - 1 )
1265 {
1266 mRenderer->updateRangeLowerValue( rangeIdx + 1, dialog.upperValueDouble() );
1267 }
1268 }
1269 }
1270 mHistogramWidget->refresh();
1271 emit widgetChanged();
1272}
1273
1275{
1276 mModel->addClass( mGraduatedSymbol.get() );
1277 mHistogramWidget->refresh();
1278 emit widgetChanged();
1279}
1280
1282{
1283 QList<int> classIndexes = selectedClasses();
1284 mModel->deleteRows( classIndexes );
1285 mHistogramWidget->refresh();
1286 emit widgetChanged();
1287}
1288
1290{
1291 mModel->removeAllRows();
1292 mHistogramWidget->refresh();
1293 emit widgetChanged();
1294}
1295
1297{
1298 const QgsRangeList &ranges = mRenderer->ranges();
1299 bool ordered = true;
1300 for ( int i = 1; i < ranges.size(); ++i )
1301 {
1302 if ( ranges[i] < ranges[i - 1] )
1303 {
1304 ordered = false;
1305 break;
1306 }
1307 }
1308 return ordered;
1309}
1310
1312{
1313 //If the checkbox controlling the link between boundaries was unchecked and we check it, we have to link the boundaries
1314 //This is done by updating all lower ranges to the upper value of the range above
1315 if ( linked )
1316 {
1317 if ( !rowsOrdered() )
1318 {
1319 int result = QMessageBox::warning( this, tr( "Link Class Boundaries" ), tr( "Rows will be reordered before linking boundaries. Continue?" ), QMessageBox::Ok | QMessageBox::Cancel );
1320 if ( result != QMessageBox::Ok )
1321 {
1322 cbxLinkBoundaries->setChecked( false );
1323 return;
1324 }
1325 mRenderer->sortByValue();
1326 }
1327
1328 // Ok to proceed
1329 for ( int i = 1; i < mRenderer->ranges().size(); ++i )
1330 {
1331 mRenderer->updateRangeLowerValue( i, mRenderer->ranges()[i - 1].upperValue() );
1332 }
1334 }
1335}
1336
1338{
1339 if ( item->column() == 2 )
1340 {
1341 QString label = item->text();
1342 int idx = item->row();
1343 mRenderer->updateRangeLabel( idx, label );
1344 }
1345}
1346
1348{
1349 mRenderer->classificationMethod()->setLabelFormat( txtLegendFormat->text() );
1350 mRenderer->classificationMethod()->setLabelPrecision( spinPrecision->value() );
1351 mRenderer->classificationMethod()->setLabelTrimTrailingZeroes( cbxTrimTrailingZeroes->isChecked() );
1352 mRenderer->updateRangeLabels();
1353 mModel->updateLabels();
1354}
1355
1356
1358{
1359 QList<QgsSymbol *> selectedSymbols;
1360
1361 QItemSelectionModel *m = viewGraduated->selectionModel();
1362 QModelIndexList selectedIndexes = m->selectedRows( 1 );
1363 if ( !selectedIndexes.isEmpty() )
1364 {
1365 const QgsRangeList &ranges = mRenderer->ranges();
1366 QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
1367 for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
1368 {
1369 QStringList list = m->model()->data( *indexIt ).toString().split( ' ' );
1370 if ( list.size() < 3 )
1371 {
1372 continue;
1373 }
1374 // Not strictly necessary because the range should have been sanitized already
1375 // after user input, but being permissive never hurts
1376 bool ok = false;
1377 double lowerBound = qgsPermissiveToDouble( list.at( 0 ), ok );
1378 if ( !ok )
1379 lowerBound = 0.0;
1380 double upperBound = qgsPermissiveToDouble( list.at( 2 ), ok );
1381 if ( !ok )
1382 upperBound = 0.0;
1383 QgsSymbol *s = findSymbolForRange( lowerBound, upperBound, ranges );
1384 if ( s )
1385 {
1386 selectedSymbols.append( s );
1387 }
1388 }
1389 }
1390 return selectedSymbols;
1391}
1392
1393QgsSymbol *QgsGraduatedSymbolRendererWidget::findSymbolForRange( double lowerBound, double upperBound, const QgsRangeList &ranges ) const
1394{
1395 int decimalPlaces = mRenderer->classificationMethod()->labelPrecision() + 2;
1396 if ( decimalPlaces < 0 )
1397 decimalPlaces = 0;
1398 double precision = 1.0 / std::pow( 10, decimalPlaces );
1399
1400 for ( QgsRangeList::const_iterator it = ranges.begin(); it != ranges.end(); ++it )
1401 {
1402 if ( qgsDoubleNear( lowerBound, it->lowerValue(), precision ) && qgsDoubleNear( upperBound, it->upperValue(), precision ) )
1403 {
1404 return it->symbol();
1405 }
1406 }
1407 return nullptr;
1408}
1409
1411{
1412 if ( mModel )
1413 {
1414 mModel->updateSymbology();
1415 }
1416 mHistogramWidget->refresh();
1417 emit widgetChanged();
1418}
1419
1424
1426{
1427 viewGraduated->selectionModel()->clear();
1428 if ( !rowsOrdered() )
1429 {
1430 cbxLinkBoundaries->setChecked( false );
1431 }
1432 emit widgetChanged();
1433}
1434
1439
1441{
1442 if ( !event )
1443 {
1444 return;
1445 }
1446
1447 if ( event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier )
1448 {
1449 mCopyBuffer.clear();
1450 mCopyBuffer = selectedRanges();
1451 }
1452 else if ( event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier )
1453 {
1454 QgsRangeList::iterator rIt = mCopyBuffer.begin();
1455 for ( ; rIt != mCopyBuffer.end(); ++rIt )
1456 {
1457 rIt->mUuid = QUuid::createUuid().toString();
1458 mModel->addClass( *rIt );
1459 }
1460 emit widgetChanged();
1461 }
1462}
1463
1464void QgsGraduatedSymbolRendererWidget::selectionChanged( const QItemSelection &, const QItemSelection & )
1465{
1466 const QgsRangeList ranges = selectedRanges();
1467 if ( !ranges.isEmpty() )
1468 {
1469 whileBlocking( btnChangeGraduatedSymbol )->setSymbol( ranges.at( 0 ).symbol()->clone() );
1470 }
1471 else if ( mRenderer->sourceSymbol() )
1472 {
1473 whileBlocking( btnChangeGraduatedSymbol )->setSymbol( mRenderer->sourceSymbol()->clone() );
1474 }
1475 btnChangeGraduatedSymbol->setDialogTitle( ranges.size() == 1 ? ranges.at( 0 ).label() : tr( "Symbol Settings" ) );
1476}
1477
1478void QgsGraduatedSymbolRendererWidget::dataDefinedSizeLegend()
1479{
1480 QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( mGraduatedSymbol.get() ); // this should be only enabled for marker symbols
1481 QgsDataDefinedSizeLegendWidget *panel = createDataDefinedSizeLegendWidget( s, mRenderer->dataDefinedSizeLegend() );
1482 if ( panel )
1483 {
1484 connect( panel, &QgsPanelWidget::widgetChanged, this, [this, panel] {
1485 mRenderer->setDataDefinedSizeLegend( panel->dataDefinedSizeLegend() );
1486 emit widgetChanged();
1487 } );
1488 openPanel( panel ); // takes ownership of the panel
1489 }
1490}
1491
1492void QgsGraduatedSymbolRendererWidget::changeGraduatedSymbol()
1493{
1494 mGraduatedSymbol.reset( btnChangeGraduatedSymbol->symbol()->clone() );
1496}
1497
1499{
1500 std::unique_ptr<QgsSymbol> tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
1501 if ( !tempSymbol )
1502 return;
1503
1504 const QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
1505 for ( const QModelIndex &index : selectedRows )
1506 {
1507 if ( !index.isValid() )
1508 continue;
1509
1510 const int row = index.row();
1511 if ( !mRenderer || mRenderer->ranges().size() <= row )
1512 continue;
1513
1514 if ( mRenderer->ranges().at( row ).symbol()->type() != tempSymbol->type() )
1515 continue;
1516
1517 std::unique_ptr<QgsSymbol> newCatSymbol( tempSymbol->clone() );
1518 if ( selectedRows.count() > 1 )
1519 {
1520 //if updating multiple ranges, retain the existing category colors
1521 newCatSymbol->setColor( mRenderer->ranges().at( row ).symbol()->color() );
1522 }
1523
1524 mRenderer->updateRangeSymbol( row, newCatSymbol.release() );
1525 }
1526 emit widgetChanged();
1527}
@ Standard
Standard (single-run) algorithm mode.
Definition qgis.h:3839
@ Size
Alter size of symbols.
Definition qgis.h:3474
@ Color
Alter color of symbols.
Definition qgis.h:3473
@ Millimeters
Millimeters.
Definition qgis.h:5440
@ Points
Points (e.g., for font sizes).
Definition qgis.h:5444
@ MapUnits
Map units.
Definition qgis.h:5441
@ Pixels
Pixels.
Definition qgis.h:5442
@ Inches
Inches.
Definition qgis.h:5445
@ 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:91
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:7077
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:6982
QList< QgsLegendSymbolItem > QgsLegendSymbolList
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
QList< QgsRendererRange > QgsRangeList