QGIS API Documentation 3.32.0-Lima (311a8cb8a6)
qgscategorizedsymbolrendererwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgscategorizedsymbolrendererwidget.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#include "qgspanelwidget.h"
18
20
23#include "qgssymbol.h"
24#include "qgssymbollayerutils.h"
25#include "qgscolorrampimpl.h"
26#include "qgscolorrampbutton.h"
27#include "qgsstyle.h"
28#include "qgslogger.h"
32#include "qgsvectorlayer.h"
33#include "qgsfeatureiterator.h"
34#include "qgsproject.h"
36#include "qgsexpression.h"
37#include "qgsmapcanvas.h"
38#include "qgssettings.h"
39#include "qgsguiutils.h"
40#include "qgsmarkersymbol.h"
41
42#include <QKeyEvent>
43#include <QMenu>
44#include <QMessageBox>
45#include <QStandardItemModel>
46#include <QStandardItem>
47#include <QPen>
48#include <QPainter>
49#include <QFileDialog>
50#include <QClipboard>
51#include <QPointer>
52#include <QScreen>
53
55
56QgsCategorizedSymbolRendererModel::QgsCategorizedSymbolRendererModel( QObject *parent, QScreen *screen )
57 : QAbstractItemModel( parent )
58 , mMimeFormat( QStringLiteral( "application/x-qgscategorizedsymbolrendererv2model" ) )
59 , mScreen( screen )
60{
61}
62
63void QgsCategorizedSymbolRendererModel::setRenderer( QgsCategorizedSymbolRenderer *renderer )
64{
65 if ( mRenderer )
66 {
67 beginRemoveRows( QModelIndex(), 0, std::max< int >( mRenderer->categories().size() - 1, 0 ) );
68 mRenderer = nullptr;
69 endRemoveRows();
70 }
71 if ( renderer )
72 {
73 mRenderer = renderer;
74 if ( renderer->categories().size() > 0 )
75 {
76 beginInsertRows( QModelIndex(), 0, renderer->categories().size() - 1 );
77 endInsertRows();
78 }
79 }
80}
81
82void QgsCategorizedSymbolRendererModel::addCategory( const QgsRendererCategory &cat )
83{
84 if ( !mRenderer ) return;
85 const int idx = mRenderer->categories().size();
86 beginInsertRows( QModelIndex(), idx, idx );
87 mRenderer->addCategory( cat );
88 endInsertRows();
89}
90
91QgsRendererCategory QgsCategorizedSymbolRendererModel::category( const QModelIndex &index )
92{
93 if ( !mRenderer )
94 {
95 return QgsRendererCategory();
96 }
97 const QgsCategoryList &catList = mRenderer->categories();
98 const int row = index.row();
99 if ( row >= catList.size() )
100 {
101 return QgsRendererCategory();
102 }
103 return catList.at( row );
104}
105
106
107Qt::ItemFlags QgsCategorizedSymbolRendererModel::flags( const QModelIndex &index ) const
108{
109 if ( !index.isValid() || !mRenderer )
110 {
111 return Qt::ItemIsDropEnabled;
112 }
113
114 Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable;
115 if ( index.column() == 1 )
116 {
117 const QgsRendererCategory category = mRenderer->categories().value( index.row() );
118 if ( category.value().type() != QVariant::List )
119 {
120 flags |= Qt::ItemIsEditable;
121 }
122 }
123 else if ( index.column() == 2 )
124 {
125 flags |= Qt::ItemIsEditable;
126 }
127 return flags;
128}
129
130Qt::DropActions QgsCategorizedSymbolRendererModel::supportedDropActions() const
131{
132 return Qt::MoveAction;
133}
134
135QVariant QgsCategorizedSymbolRendererModel::data( const QModelIndex &index, int role ) const
136{
137 if ( !index.isValid() || !mRenderer )
138 return QVariant();
139
140 const QgsRendererCategory category = mRenderer->categories().value( index.row() );
141
142 switch ( role )
143 {
144 case Qt::CheckStateRole:
145 {
146 if ( index.column() == 0 )
147 {
148 return category.renderState() ? Qt::Checked : Qt::Unchecked;
149 }
150 break;
151 }
152
153 case Qt::DisplayRole:
154 case Qt::ToolTipRole:
155 {
156 switch ( index.column() )
157 {
158 case 1:
159 {
160 if ( category.value().type() == QVariant::List )
161 {
162 QStringList res;
163 const QVariantList list = category.value().toList();
164 res.reserve( list.size() );
165 for ( const QVariant &v : list )
167
168 if ( role == Qt::DisplayRole )
169 return res.join( ';' );
170 else // tooltip
171 return res.join( '\n' );
172 }
173 else if ( QgsVariantUtils::isNull( category.value() ) || category.value().toString().isEmpty() )
174 {
175 return tr( "all other values" );
176 }
177 else
178 {
180 }
181 }
182 case 2:
183 return category.label();
184 }
185 break;
186 }
187
188 case Qt::FontRole:
189 {
190 if ( index.column() == 1 && category.value().type() != QVariant::List && ( QgsVariantUtils::isNull( category.value() ) || category.value().toString().isEmpty() ) )
191 {
192 QFont italicFont;
193 italicFont.setItalic( true );
194 return italicFont;
195 }
196 return QVariant();
197 }
198
199 case Qt::DecorationRole:
200 {
201 if ( index.column() == 0 && category.symbol() )
202 {
203 const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
204 return QgsSymbolLayerUtils::symbolPreviewIcon( category.symbol(), QSize( iconSize, iconSize ), 0, nullptr, QgsScreenProperties( mScreen.data() ) );
205 }
206 break;
207 }
208
209 case Qt::ForegroundRole:
210 {
211 QBrush brush( qApp->palette().color( QPalette::Text ), Qt::SolidPattern );
212 if ( index.column() == 1 && ( category.value().type() == QVariant::List
213 || QgsVariantUtils::isNull( category.value() ) || category.value().toString().isEmpty() ) )
214 {
215 QColor fadedTextColor = brush.color();
216 fadedTextColor.setAlpha( 128 );
217 brush.setColor( fadedTextColor );
218 }
219 return brush;
220 }
221
222 case Qt::TextAlignmentRole:
223 {
224 return ( index.column() == 0 ) ? static_cast<Qt::Alignment::Int>( Qt::AlignHCenter ) : static_cast<Qt::Alignment::Int>( Qt::AlignLeft );
225 }
226
227 case Qt::EditRole:
228 {
229 switch ( index.column() )
230 {
231 case 1:
232 {
233 if ( category.value().type() == QVariant::List )
234 {
235 QStringList res;
236 const QVariantList list = category.value().toList();
237 res.reserve( list.size() );
238 for ( const QVariant &v : list )
239 res << v.toString();
240
241 return res.join( ';' );
242 }
243 else
244 {
245 return category.value();
246 }
247 }
248
249 case 2:
250 return category.label();
251 }
252 break;
253 }
254 case QgsCategorizedSymbolRendererWidget::CustomRoles::ValueRole:
255 {
256 if ( index.column() == 1 )
257 return category.value();
258 break;
259 }
260 }
261
262 return QVariant();
263}
264
265bool QgsCategorizedSymbolRendererModel::setData( const QModelIndex &index, const QVariant &value, int role )
266{
267 if ( !index.isValid() )
268 return false;
269
270 if ( index.column() == 0 && role == Qt::CheckStateRole )
271 {
272 mRenderer->updateCategoryRenderState( index.row(), value == Qt::Checked );
273 emit dataChanged( index, index );
274 return true;
275 }
276
277 if ( role != Qt::EditRole )
278 return false;
279
280 switch ( index.column() )
281 {
282 case 1: // value
283 {
284 // try to preserve variant type for this value, unless it was an empty string (other values)
285 QVariant val = value;
286 const QVariant previousValue = mRenderer->categories().value( index.row() ).value();
287 if ( previousValue.type() != QVariant::String && ! previousValue.toString().isEmpty() )
288 {
289 switch ( previousValue.type() )
290 {
291 case QVariant::Int:
292 val = value.toInt();
293 break;
294 case QVariant::Double:
295 val = value.toDouble();
296 break;
297 case QVariant::List:
298 {
299 const QStringList parts = value.toString().split( ';' );
300 QVariantList list;
301 list.reserve( parts.count() );
302 for ( const QString &p : parts )
303 list << p;
304
305 if ( list.count() == 1 )
306 val = list.at( 0 );
307 else
308 val = list;
309 break;
310 }
311 default:
312 val = value.toString();
313 break;
314 }
315 }
316 mRenderer->updateCategoryValue( index.row(), val );
317 break;
318 }
319 case 2: // label
320 mRenderer->updateCategoryLabel( index.row(), value.toString() );
321 break;
322 default:
323 return false;
324 }
325
326 emit dataChanged( index, index );
327 return true;
328}
329
330QVariant QgsCategorizedSymbolRendererModel::headerData( int section, Qt::Orientation orientation, int role ) const
331{
332 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 3 )
333 {
334 QStringList lst;
335 lst << tr( "Symbol" ) << tr( "Value" ) << tr( "Legend" );
336 return lst.value( section );
337 }
338 return QVariant();
339}
340
341int QgsCategorizedSymbolRendererModel::rowCount( const QModelIndex &parent ) const
342{
343 if ( parent.isValid() || !mRenderer )
344 {
345 return 0;
346 }
347 return mRenderer->categories().size();
348}
349
350int QgsCategorizedSymbolRendererModel::columnCount( const QModelIndex &index ) const
351{
352 Q_UNUSED( index )
353 return 3;
354}
355
356QModelIndex QgsCategorizedSymbolRendererModel::index( int row, int column, const QModelIndex &parent ) const
357{
358 if ( hasIndex( row, column, parent ) )
359 {
360 return createIndex( row, column );
361 }
362 return QModelIndex();
363}
364
365QModelIndex QgsCategorizedSymbolRendererModel::parent( const QModelIndex &index ) const
366{
367 Q_UNUSED( index )
368 return QModelIndex();
369}
370
371QStringList QgsCategorizedSymbolRendererModel::mimeTypes() const
372{
373 QStringList types;
374 types << mMimeFormat;
375 return types;
376}
377
378QMimeData *QgsCategorizedSymbolRendererModel::mimeData( const QModelIndexList &indexes ) const
379{
380 QMimeData *mimeData = new QMimeData();
381 QByteArray encodedData;
382
383 QDataStream stream( &encodedData, QIODevice::WriteOnly );
384
385 // Create list of rows
386 const auto constIndexes = indexes;
387 for ( const QModelIndex &index : constIndexes )
388 {
389 if ( !index.isValid() || index.column() != 0 )
390 continue;
391
392 stream << index.row();
393 }
394 mimeData->setData( mMimeFormat, encodedData );
395 return mimeData;
396}
397
398bool QgsCategorizedSymbolRendererModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
399{
400 Q_UNUSED( row )
401 Q_UNUSED( column )
402 if ( action != Qt::MoveAction ) return true;
403
404 if ( !data->hasFormat( mMimeFormat ) ) return false;
405
406 QByteArray encodedData = data->data( mMimeFormat );
407 QDataStream stream( &encodedData, QIODevice::ReadOnly );
408
409 QVector<int> rows;
410 while ( !stream.atEnd() )
411 {
412 int r;
413 stream >> r;
414 rows.append( r );
415 }
416
417 int to = parent.row();
418 // to is -1 if dragged outside items, i.e. below any item,
419 // then move to the last position
420 if ( to == -1 ) to = mRenderer->categories().size(); // out of rang ok, will be decreased
421 for ( int i = rows.size() - 1; i >= 0; i-- )
422 {
423 QgsDebugMsgLevel( QStringLiteral( "move %1 to %2" ).arg( rows[i] ).arg( to ), 2 );
424 int t = to;
425 // moveCategory first removes and then inserts
426 if ( rows[i] < t ) t--;
427 mRenderer->moveCategory( rows[i], t );
428 // current moved under another, shift its index up
429 for ( int j = 0; j < i; j++ )
430 {
431 if ( to < rows[j] && rows[i] > rows[j] ) rows[j] += 1;
432 }
433 // removed under 'to' so the target shifted down
434 if ( rows[i] < to ) to--;
435 }
436 emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
437 emit rowsMoved();
438 return false;
439}
440
441void QgsCategorizedSymbolRendererModel::deleteRows( QList<int> rows )
442{
443 std::sort( rows.begin(), rows.end() ); // list might be unsorted, depending on how the user selected the rows
444 for ( int i = rows.size() - 1; i >= 0; i-- )
445 {
446 beginRemoveRows( QModelIndex(), rows[i], rows[i] );
447 mRenderer->deleteCategory( rows[i] );
448 endRemoveRows();
449 }
450}
451
452void QgsCategorizedSymbolRendererModel::removeAllRows()
453{
454 beginRemoveRows( QModelIndex(), 0, mRenderer->categories().size() - 1 );
455 mRenderer->deleteAllCategories();
456 endRemoveRows();
457}
458
459void QgsCategorizedSymbolRendererModel::sort( int column, Qt::SortOrder order )
460{
461 if ( column == 0 )
462 {
463 return;
464 }
465 if ( column == 1 )
466 {
467 mRenderer->sortByValue( order );
468 }
469 else if ( column == 2 )
470 {
471 mRenderer->sortByLabel( order );
472 }
473 emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
474}
475
476void QgsCategorizedSymbolRendererModel::updateSymbology()
477{
478 emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
479}
480
481// ------------------------------ View style --------------------------------
482QgsCategorizedSymbolRendererViewStyle::QgsCategorizedSymbolRendererViewStyle( QWidget *parent )
483 : QgsProxyStyle( parent )
484{}
485
486void QgsCategorizedSymbolRendererViewStyle::drawPrimitive( PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget ) const
487{
488 if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
489 {
490 QStyleOption opt( *option );
491 opt.rect.setLeft( 0 );
492 // draw always as line above, because we move item to that index
493 opt.rect.setHeight( 0 );
494 if ( widget ) opt.rect.setRight( widget->width() );
495 QProxyStyle::drawPrimitive( element, &opt, painter, widget );
496 return;
497 }
498 QProxyStyle::drawPrimitive( element, option, painter, widget );
499}
500
501
502QgsCategorizedRendererViewItemDelegate::QgsCategorizedRendererViewItemDelegate( QgsFieldExpressionWidget *expressionWidget, QObject *parent )
503 : QStyledItemDelegate( parent )
504 , mFieldExpressionWidget( expressionWidget )
505{
506}
507
508QWidget *QgsCategorizedRendererViewItemDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const
509{
510 QVariant::Type userType { index.data( QgsCategorizedSymbolRendererWidget::CustomRoles::ValueRole ).type() };
511
512 // In case of new values the type is not known
513 if ( userType == QVariant::String && QgsVariantUtils::isNull( index.data( QgsCategorizedSymbolRendererWidget::CustomRoles::ValueRole ) ) )
514 {
515 bool isExpression;
516 bool isValid;
517 const QString fieldName { mFieldExpressionWidget->currentField( &isExpression, &isValid ) };
518 if ( ! fieldName.isEmpty() && mFieldExpressionWidget->layer() && mFieldExpressionWidget->layer()->fields().lookupField( fieldName ) != -1 )
519 {
520 userType = mFieldExpressionWidget->layer()->fields().field( fieldName ).type();
521 }
522 else if ( isExpression && isValid )
523 {
524 // Try to guess the type from the expression return value
525 QgsFeature feat;
526 if ( mFieldExpressionWidget->layer()->getFeatures().nextFeature( feat ) )
527 {
528 QgsExpressionContext expressionContext;
531 expressionContext.appendScope( mFieldExpressionWidget->layer()->createExpressionContextScope() );
532 expressionContext.setFeature( feat );
533 QgsExpression exp { mFieldExpressionWidget->expression() };
534 const QVariant value = exp.evaluate( &expressionContext );
535 if ( !exp.hasEvalError() )
536 {
537 userType = value.type();
538 }
539 }
540 }
541 }
542
543 QgsDoubleSpinBox *editor = nullptr;
544 switch ( userType )
545 {
546 case QVariant::Type::Double:
547 {
548 editor = new QgsDoubleSpinBox( parent );
549 bool ok;
550 const QVariant value = index.data( QgsCategorizedSymbolRendererWidget::CustomRoles::ValueRole );
551 int decimals {2};
552 if ( value.toDouble( &ok ); ok )
553 {
554 const QString strVal { value.toString() };
555 const int dotPosition( strVal.indexOf( '.' ) );
556 if ( dotPosition >= 0 )
557 {
558 decimals = std::max<int>( 2, strVal.length() - dotPosition - 1 );
559 }
560 }
561 editor->setDecimals( decimals );
562 editor->setClearValue( 0 );
563 editor->setMaximum( std::numeric_limits<double>::max() );
564 editor->setMinimum( std::numeric_limits<double>::lowest() );
565 break;
566 }
567 case QVariant::Type::Int:
568 {
569 editor = new QgsDoubleSpinBox( parent );
570 editor->setDecimals( 0 );
571 editor->setClearValue( 0 );
572 editor->setMaximum( std::numeric_limits<int>::max() );
573 editor->setMinimum( std::numeric_limits<int>::min() );
574 break;
575 }
576 case QVariant::Type::Char:
577 {
578 editor = new QgsDoubleSpinBox( parent );
579 editor->setDecimals( 0 );
580 editor->setClearValue( 0 );
581 editor->setMaximum( std::numeric_limits<char>::max() );
582 editor->setMinimum( std::numeric_limits<char>::min() );
583 break;
584 }
585 case QVariant::Type::UInt:
586 {
587 editor = new QgsDoubleSpinBox( parent );
588 editor->setDecimals( 0 );
589 editor->setClearValue( 0 );
590 editor->setMaximum( std::numeric_limits<unsigned int>::max() );
591 editor->setMinimum( 0 );
592 break;
593 }
594 case QVariant::Type::LongLong:
595 {
596 editor = new QgsDoubleSpinBox( parent );
597 editor->setDecimals( 0 );
598 editor->setClearValue( 0 );
599 editor->setMaximum( static_cast<double>( std::numeric_limits<qlonglong>::max() ) );
600 editor->setMinimum( std::numeric_limits<qlonglong>::min() );
601 break;
602 }
603 case QVariant::Type::ULongLong:
604 {
605 editor = new QgsDoubleSpinBox( parent );
606 editor->setDecimals( 0 );
607 editor->setClearValue( 0 );
608 editor->setMaximum( static_cast<double>( std::numeric_limits<unsigned long long>::max() ) );
609 editor->setMinimum( 0 );
610 break;
611 }
612 default:
613 break;
614 }
615 return editor ? editor : QStyledItemDelegate::createEditor( parent, option, index );
616}
617
619
620// ------------------------------ Widget ------------------------------------
622{
623 return new QgsCategorizedSymbolRendererWidget( layer, style, renderer );
624}
625
627 : QgsRendererWidget( layer, style )
628 , mContextMenu( new QMenu( this ) )
629{
630
631 // try to recognize the previous renderer
632 // (null renderer means "no previous renderer")
633 if ( renderer )
634 {
636 }
637 if ( !mRenderer )
638 {
639 mRenderer = std::make_unique< QgsCategorizedSymbolRenderer >( QString(), QgsCategoryList() );
640 if ( renderer )
642 }
643
644 const QString attrName = mRenderer->classAttribute();
645 mOldClassificationAttribute = attrName;
646
647 // setup user interface
648 setupUi( this );
649 layout()->setContentsMargins( 0, 0, 0, 0 );
650
651 mExpressionWidget->setLayer( mLayer );
652 btnChangeCategorizedSymbol->setLayer( mLayer );
653 btnChangeCategorizedSymbol->registerExpressionContextGenerator( this );
654
655 // initiate color ramp button to random
656 btnColorRamp->setShowRandomColorRamp( true );
657
658 // set project default color ramp
659 std::unique_ptr< QgsColorRamp > colorRamp( QgsProject::instance()->styleSettings()->defaultColorRamp() );
660 if ( colorRamp )
661 {
662 btnColorRamp->setColorRamp( colorRamp.get() );
663 }
664 else
665 {
666 btnColorRamp->setRandomColorRamp();
667 }
668
670 if ( mCategorizedSymbol )
671 {
672 btnChangeCategorizedSymbol->setSymbolType( mCategorizedSymbol->type() );
673 btnChangeCategorizedSymbol->setSymbol( mCategorizedSymbol->clone() );
674 }
675
676 mModel = new QgsCategorizedSymbolRendererModel( this, screen() );
677 mModel->setRenderer( mRenderer.get() );
678
679 // update GUI from renderer
681
682 viewCategories->setModel( mModel );
683 viewCategories->resizeColumnToContents( 0 );
684 viewCategories->resizeColumnToContents( 1 );
685 viewCategories->resizeColumnToContents( 2 );
686 viewCategories->setItemDelegateForColumn( 1, new QgsCategorizedRendererViewItemDelegate( mExpressionWidget, viewCategories ) );
687
688 viewCategories->setStyle( new QgsCategorizedSymbolRendererViewStyle( viewCategories ) );
689 connect( viewCategories->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsCategorizedSymbolRendererWidget::selectionChanged );
690
691 connect( mModel, &QgsCategorizedSymbolRendererModel::rowsMoved, this, &QgsCategorizedSymbolRendererWidget::rowsMoved );
692 connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsPanelWidget::widgetChanged );
693
694 connect( mExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsCategorizedSymbolRendererWidget::categoryColumnChanged );
695
696 connect( viewCategories, &QAbstractItemView::doubleClicked, this, &QgsCategorizedSymbolRendererWidget::categoriesDoubleClicked );
697 connect( viewCategories, &QTreeView::customContextMenuRequested, this, &QgsCategorizedSymbolRendererWidget::showContextMenu );
698
699 connect( btnChangeCategorizedSymbol, &QgsSymbolButton::changed, this, &QgsCategorizedSymbolRendererWidget::updateSymbolsFromButton );
700
701 connect( btnAddCategories, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::addCategories );
702 connect( btnDeleteCategories, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::deleteCategories );
703 connect( btnDeleteAllCategories, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::deleteAllCategories );
704 connect( btnAddCategory, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::addCategory );
705
707
708 // menus for data-defined rotation/size
709 QMenu *advMenu = new QMenu;
710
711 advMenu->addAction( tr( "Match to Saved Symbols" ), this, &QgsCategorizedSymbolRendererWidget::matchToSymbolsFromLibrary );
712 advMenu->addAction( tr( "Match to Symbols from File…" ), this, &QgsCategorizedSymbolRendererWidget::matchToSymbolsFromXml );
713 mActionLevels = advMenu->addAction( tr( "Symbol Levels…" ), this, &QgsCategorizedSymbolRendererWidget::showSymbolLevels );
715 {
716 QAction *actionDdsLegend = advMenu->addAction( tr( "Data-defined Size Legend…" ) );
717 // only from Qt 5.6 there is convenience addAction() with new style connection
718 connect( actionDdsLegend, &QAction::triggered, this, &QgsCategorizedSymbolRendererWidget::dataDefinedSizeLegend );
719 }
720
721 btnAdvanced->setMenu( advMenu );
722
723 mExpressionWidget->registerExpressionContextGenerator( this );
724
725 mMergeCategoriesAction = new QAction( tr( "Merge Categories" ), this );
726 connect( mMergeCategoriesAction, &QAction::triggered, this, &QgsCategorizedSymbolRendererWidget::mergeSelectedCategories );
727 mUnmergeCategoriesAction = new QAction( tr( "Unmerge Categories" ), this );
728 connect( mUnmergeCategoriesAction, &QAction::triggered, this, &QgsCategorizedSymbolRendererWidget::unmergeSelectedCategories );
729
730 connect( mContextMenu, &QMenu::aboutToShow, this, [ = ]
731 {
732 const std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
733 mPasteSymbolAction->setEnabled( static_cast< bool >( tempSymbol ) );
734 } );
735}
736
738{
739 delete mModel;
740}
741
743{
744 // Note: This assumes that the signals for UI element changes have not
745 // yet been connected, so that the updates to color ramp, symbol, etc
746 // don't override existing customizations.
747
748 //mModel->setRenderer ( mRenderer ); // necessary?
749
750 // set column
751 const QString attrName = mRenderer->classAttribute();
752 mExpressionWidget->setField( attrName );
753
754 // set source symbol
755 if ( mRenderer->sourceSymbol() )
756 {
757 mCategorizedSymbol.reset( mRenderer->sourceSymbol()->clone() );
758 whileBlocking( btnChangeCategorizedSymbol )->setSymbol( mCategorizedSymbol->clone() );
759 }
760
761 // if a color ramp attached to the renderer, enable the color ramp button
762 if ( mRenderer->sourceColorRamp() )
763 {
764 btnColorRamp->setColorRamp( mRenderer->sourceColorRamp() );
765 }
766}
767
769{
770 return mRenderer.get();
771}
772
774{
776 btnChangeCategorizedSymbol->setMapCanvas( context.mapCanvas() );
777 btnChangeCategorizedSymbol->setMessageBar( context.messageBar() );
778}
779
781{
782 delete mActionLevels;
783 mActionLevels = nullptr;
784}
785
787{
788 const QList<int> selectedCats = selectedCategories();
789
790 if ( !selectedCats.isEmpty() )
791 {
792 QgsSymbol *newSymbol = mCategorizedSymbol->clone();
793 QgsSymbolSelectorDialog dlg( newSymbol, mStyle, mLayer, this );
794 dlg.setContext( context() );
795 if ( !dlg.exec() )
796 {
797 delete newSymbol;
798 return;
799 }
800
801 const auto constSelectedCats = selectedCats;
802 for ( const int idx : constSelectedCats )
803 {
804 const QgsRendererCategory category = mRenderer->categories().value( idx );
805
806 QgsSymbol *newCatSymbol = newSymbol->clone();
807 newCatSymbol->setColor( mRenderer->categories()[idx].symbol()->color() );
808 mRenderer->updateCategorySymbol( idx, newCatSymbol );
809 }
810 }
811}
812
814{
816 std::unique_ptr<QgsSymbol> newSymbol( mCategorizedSymbol->clone() );
817 if ( panel && panel->dockMode() )
818 {
819 // bit tricky here - the widget doesn't take ownership of the symbol. So we need it to last for the duration of the
820 // panel's existence. Accordingly, just kinda give it ownership here, and clean up in cleanUpSymbolSelector
821 QgsSymbolSelectorWidget *dlg = new QgsSymbolSelectorWidget( newSymbol.release(), mStyle, mLayer, panel );
822 dlg->setContext( mContext );
823 connect( dlg, &QgsPanelWidget::widgetChanged, this, &QgsCategorizedSymbolRendererWidget::updateSymbolsFromWidget );
824 connect( dlg, &QgsPanelWidget::panelAccepted, this, &QgsCategorizedSymbolRendererWidget::cleanUpSymbolSelector );
825 openPanel( dlg );
826 }
827 else
828 {
829 QgsSymbolSelectorDialog dlg( newSymbol.get(), mStyle, mLayer, panel );
830 dlg.setContext( mContext );
831 if ( !dlg.exec() || !newSymbol )
832 {
833 return;
834 }
835
836 mCategorizedSymbol = std::move( newSymbol );
838 }
839}
840
841
843{
844}
845
847{
848 mRenderer->setClassAttribute( field );
849 emit widgetChanged();
850}
851
853{
854 if ( idx.isValid() && idx.column() == 0 )
856}
857
859{
860 const QgsRendererCategory category = mRenderer->categories().value( currentCategoryRow() );
861
862 std::unique_ptr< QgsSymbol > symbol;
863
864 if ( auto *lSymbol = category.symbol() )
865 {
866 symbol.reset( lSymbol->clone() );
867 }
868 else
869 {
870 symbol.reset( QgsSymbol::defaultSymbol( mLayer->geometryType() ) );
871 }
872
874 if ( panel && panel->dockMode() )
875 {
876 QgsSymbolSelectorWidget *dlg = new QgsSymbolSelectorWidget( symbol.release(), mStyle, mLayer, panel );
877 dlg->setContext( mContext );
878 dlg->setPanelTitle( category.label() );
879 connect( dlg, &QgsPanelWidget::widgetChanged, this, &QgsCategorizedSymbolRendererWidget::updateSymbolsFromWidget );
880 connect( dlg, &QgsPanelWidget::panelAccepted, this, &QgsCategorizedSymbolRendererWidget::cleanUpSymbolSelector );
881 openPanel( dlg );
882 }
883 else
884 {
885 QgsSymbolSelectorDialog dlg( symbol.get(), mStyle, mLayer, panel );
886 dlg.setContext( mContext );
887 if ( !dlg.exec() || !symbol )
888 {
889 return;
890 }
891
892 mCategorizedSymbol = std::move( symbol );
894 }
895}
896
897
899{
900 const QString attrName = mExpressionWidget->currentField();
901 const int idx = mLayer->fields().lookupField( attrName );
902 QList<QVariant> uniqueValues;
903 if ( idx == -1 )
904 {
905 // Lets assume it's an expression
906 QgsExpression *expression = new QgsExpression( attrName );
912
913 expression->prepare( &context );
915 QgsFeature feature;
916 while ( fit.nextFeature( feature ) )
917 {
918 context.setFeature( feature );
919 const QVariant value = expression->evaluate( &context );
920 if ( uniqueValues.contains( value ) )
921 continue;
922 uniqueValues << value;
923 }
924 }
925 else
926 {
927 uniqueValues = qgis::setToList( mLayer->uniqueValues( idx ) );
928 }
929
930 // ask to abort if too many classes
931 if ( uniqueValues.size() >= 1000 )
932 {
933 const int res = QMessageBox::warning( nullptr, tr( "Classify Categories" ),
934 tr( "High number of classes. Classification would yield %n entries which might not be expected. Continue?", nullptr, uniqueValues.size() ),
935 QMessageBox::Ok | QMessageBox::Cancel,
936 QMessageBox::Cancel );
937 if ( res == QMessageBox::Cancel )
938 {
939 return;
940 }
941 }
942
943#if 0
944 DlgAddCategories dlg( mStyle, createDefaultSymbol(), unique_vals, this );
945 if ( !dlg.exec() )
946 return;
947#endif
948
950 bool deleteExisting = false;
951
952 if ( !mOldClassificationAttribute.isEmpty() &&
953 attrName != mOldClassificationAttribute &&
954 !mRenderer->categories().isEmpty() )
955 {
956 const int res = QMessageBox::question( this,
957 tr( "Delete Classification" ),
958 tr( "The classification field was changed from '%1' to '%2'.\n"
959 "Should the existing classes be deleted before classification?" )
960 .arg( mOldClassificationAttribute, attrName ),
961 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel );
962 if ( res == QMessageBox::Cancel )
963 {
964 return;
965 }
966
967 deleteExisting = ( res == QMessageBox::Yes );
968 }
969
970 // First element to apply coloring to
971 bool keepExistingColors = false;
972 if ( !deleteExisting )
973 {
974 QgsCategoryList prevCats = mRenderer->categories();
975 keepExistingColors = !prevCats.isEmpty();
976 QgsRandomColorRamp randomColors;
977 if ( keepExistingColors && btnColorRamp->isRandomColorRamp() )
978 randomColors.setTotalColorCount( cats.size() );
979 for ( int i = 0; i < cats.size(); ++i )
980 {
981 bool contains = false;
982 const QVariant value = cats.at( i ).value();
983 for ( int j = 0; j < prevCats.size() && !contains; ++j )
984 {
985 const QVariant prevCatValue = prevCats.at( j ).value();
986 if ( prevCatValue.type() == QVariant::List )
987 {
988 const QVariantList list = prevCatValue.toList();
989 for ( const QVariant &v : list )
990 {
991 if ( v == value )
992 {
993 contains = true;
994 break;
995 }
996 }
997 }
998 else
999 {
1000 if ( prevCats.at( j ).value() == value )
1001 {
1002 contains = true;
1003 }
1004 }
1005 if ( contains )
1006 break;
1007 }
1008
1009 if ( !contains )
1010 {
1011 if ( keepExistingColors && btnColorRamp->isRandomColorRamp() )
1012 {
1013 // insure that append symbols have random colors
1014 cats.at( i ).symbol()->setColor( randomColors.color( i ) );
1015 }
1016 prevCats.append( cats.at( i ) );
1017 }
1018 }
1019 cats = prevCats;
1020 }
1021
1022 mOldClassificationAttribute = attrName;
1023
1024 // TODO: if not all categories are desired, delete some!
1025 /*
1026 if (not dlg.readAllCats.isChecked())
1027 {
1028 cats2 = {}
1029 for item in dlg.listCategories.selectedItems():
1030 for k,c in cats.iteritems():
1031 if item.text() == k.toString():
1032 break
1033 cats2[k] = c
1034 cats = cats2
1035 }
1036 */
1037
1038 // recreate renderer
1039 std::unique_ptr< QgsCategorizedSymbolRenderer > r = std::make_unique< QgsCategorizedSymbolRenderer >( attrName, cats );
1040 r->setSourceSymbol( mCategorizedSymbol->clone() );
1041 std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
1042 if ( ramp )
1043 r->setSourceColorRamp( ramp->clone() );
1044
1045 if ( mModel )
1046 {
1047 mModel->setRenderer( r.get() );
1048 }
1049 mRenderer = std::move( r );
1050 if ( ! keepExistingColors && ramp )
1052 emit widgetChanged();
1053}
1054
1056{
1057 if ( !btnColorRamp->isNull() )
1058 {
1059 mRenderer->updateColorRamp( btnColorRamp->colorRamp() );
1060 }
1061 mModel->updateSymbology();
1062}
1063
1065{
1066 const QModelIndex idx = viewCategories->selectionModel()->currentIndex();
1067 if ( !idx.isValid() )
1068 return -1;
1069 return idx.row();
1070}
1071
1073{
1074 QList<int> rows;
1075 const QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
1076
1077 const auto constSelectedRows = selectedRows;
1078 for ( const QModelIndex &r : constSelectedRows )
1079 {
1080 if ( r.isValid() )
1081 {
1082 rows.append( r.row() );
1083 }
1084 }
1085 return rows;
1086}
1087
1089{
1090 const QList<int> categoryIndexes = selectedCategories();
1091 mModel->deleteRows( categoryIndexes );
1092 emit widgetChanged();
1093}
1094
1096{
1097 mModel->removeAllRows();
1098 emit widgetChanged();
1099}
1100
1102{
1103 if ( !mModel ) return;
1105 const QgsRendererCategory cat( QString(), symbol, QString(), true );
1106 mModel->addCategory( cat );
1107 emit widgetChanged();
1108}
1109
1111{
1112 QList<QgsSymbol *> selectedSymbols;
1113
1114 QItemSelectionModel *m = viewCategories->selectionModel();
1115 const QModelIndexList selectedIndexes = m->selectedRows( 1 );
1116
1117 if ( !selectedIndexes.isEmpty() )
1118 {
1119 const QgsCategoryList &categories = mRenderer->categories();
1120 QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
1121 for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
1122 {
1123 const int row = ( *indexIt ).row();
1124 QgsSymbol *s = categories[row].symbol();
1125 if ( s )
1126 {
1127 selectedSymbols.append( s );
1128 }
1129 }
1130 }
1131 return selectedSymbols;
1132}
1133
1135{
1136 QgsCategoryList cl;
1137
1138 QItemSelectionModel *m = viewCategories->selectionModel();
1139 const QModelIndexList selectedIndexes = m->selectedRows( 1 );
1140
1141 if ( !selectedIndexes.isEmpty() )
1142 {
1143 QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
1144 for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
1145 {
1146 cl.append( mModel->category( *indexIt ) );
1147 }
1148 }
1149 return cl;
1150}
1151
1153{
1155 emit widgetChanged();
1156}
1157
1159{
1161}
1162
1164{
1165 viewCategories->selectionModel()->clear();
1166}
1167
1169{
1170 const int matched = matchToSymbols( QgsStyle::defaultStyle() );
1171 if ( matched > 0 )
1172 {
1173 QMessageBox::information( this, tr( "Matched Symbols" ),
1174 tr( "Matched %n categories to symbols.", nullptr, matched ) );
1175 }
1176 else
1177 {
1178 QMessageBox::warning( this, tr( "Matched Symbols" ),
1179 tr( "No categories could be matched to symbols in library." ) );
1180 }
1181}
1182
1184{
1185 if ( !mLayer || !style )
1186 return 0;
1187
1188 const Qgis::SymbolType type = mLayer->geometryType() == Qgis::GeometryType::Point ? Qgis::SymbolType::Marker
1189 : mLayer->geometryType() == Qgis::GeometryType::Line ? Qgis::SymbolType::Line
1191
1192 QVariantList unmatchedCategories;
1193 QStringList unmatchedSymbols;
1194 const int matched = mRenderer->matchToSymbols( style, type, unmatchedCategories, unmatchedSymbols );
1195
1196 mModel->updateSymbology();
1197 return matched;
1198}
1199
1201{
1202 QgsSettings settings;
1203 const QString openFileDir = settings.value( QStringLiteral( "UI/lastMatchToSymbolsDir" ), QDir::homePath() ).toString();
1204
1205 const QString fileName = QFileDialog::getOpenFileName( this, tr( "Match to Symbols from File" ), openFileDir,
1206 tr( "XML files (*.xml *.XML)" ) );
1207 if ( fileName.isEmpty() )
1208 {
1209 return;
1210 }
1211
1212 const QFileInfo openFileInfo( fileName );
1213 settings.setValue( QStringLiteral( "UI/lastMatchToSymbolsDir" ), openFileInfo.absolutePath() );
1214
1215 QgsStyle importedStyle;
1216 if ( !importedStyle.importXml( fileName ) )
1217 {
1218 QMessageBox::warning( this, tr( "Match to Symbols from File" ),
1219 tr( "An error occurred while reading file:\n%1" ).arg( importedStyle.errorString() ) );
1220 return;
1221 }
1222
1223 const int matched = matchToSymbols( &importedStyle );
1224 if ( matched > 0 )
1225 {
1226 QMessageBox::information( this, tr( "Match to Symbols from File" ),
1227 tr( "Matched %n categories to symbols from file.", nullptr, matched ) );
1228 }
1229 else
1230 {
1231 QMessageBox::warning( this, tr( "Match to Symbols from File" ),
1232 tr( "No categories could be matched to symbols in file." ) );
1233 }
1234}
1235
1237{
1238 for ( const QgsLegendSymbolItem &legendSymbol : levels )
1239 {
1240 QgsSymbol *sym = legendSymbol.symbol();
1241 for ( int layer = 0; layer < sym->symbolLayerCount(); layer++ )
1242 {
1243 mRenderer->setLegendSymbolItem( legendSymbol.ruleKey(), sym->clone() );
1244 }
1245 }
1246 mRenderer->setUsingSymbolLevels( enabled );
1247 mModel->updateSymbology();
1248 emit widgetChanged();
1249}
1250
1252{
1253 std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
1254 if ( !tempSymbol )
1255 return;
1256
1257 const QList<int> selectedCats = selectedCategories();
1258 if ( !selectedCats.isEmpty() )
1259 {
1260 for ( const int idx : selectedCats )
1261 {
1262 if ( mRenderer->categories().at( idx ).symbol()->type() != tempSymbol->type() )
1263 continue;
1264
1265 std::unique_ptr< QgsSymbol > newCatSymbol( tempSymbol->clone() );
1266 if ( selectedCats.count() > 1 )
1267 {
1268 //if updating multiple categories, retain the existing category colors
1269 newCatSymbol->setColor( mRenderer->categories().at( idx ).symbol()->color() );
1270 }
1271 mRenderer->updateCategorySymbol( idx, newCatSymbol.release() );
1272 }
1273 emit widgetChanged();
1274 }
1275}
1276
1277void QgsCategorizedSymbolRendererWidget::cleanUpSymbolSelector( QgsPanelWidget *container )
1278{
1279 QgsSymbolSelectorWidget *dlg = qobject_cast<QgsSymbolSelectorWidget *>( container );
1280 if ( !dlg )
1281 return;
1282
1283 delete dlg->symbol();
1284}
1285
1286void QgsCategorizedSymbolRendererWidget::updateSymbolsFromWidget()
1287{
1288 QgsSymbolSelectorWidget *dlg = qobject_cast<QgsSymbolSelectorWidget *>( sender() );
1289 mCategorizedSymbol.reset( dlg->symbol()->clone() );
1290
1292}
1293
1294void QgsCategorizedSymbolRendererWidget::updateSymbolsFromButton()
1295{
1296 mCategorizedSymbol.reset( btnChangeCategorizedSymbol->symbol()->clone() );
1297
1299}
1300
1302{
1303 // When there is a selection, change the selected symbols only
1304 QItemSelectionModel *m = viewCategories->selectionModel();
1305 const QModelIndexList i = m->selectedRows();
1306
1307 if ( !i.isEmpty() )
1308 {
1309 const QList<int> selectedCats = selectedCategories();
1310
1311 if ( !selectedCats.isEmpty() )
1312 {
1313 const auto constSelectedCats = selectedCats;
1314 for ( const int idx : constSelectedCats )
1315 {
1316 QgsSymbol *newCatSymbol = mCategorizedSymbol->clone();
1317 if ( selectedCats.count() > 1 )
1318 {
1319 //if updating multiple categories, retain the existing category colors
1320 newCatSymbol->setColor( mRenderer->categories().at( idx ).symbol()->color() );
1321 }
1322 mRenderer->updateCategorySymbol( idx, newCatSymbol );
1323 }
1324 }
1325 }
1326 else
1327 {
1328 mRenderer->updateSymbols( mCategorizedSymbol.get() );
1329 }
1330
1331 mModel->updateSymbology();
1332 emit widgetChanged();
1333}
1334
1336{
1337 if ( !event )
1338 {
1339 return;
1340 }
1341
1342 if ( event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier )
1343 {
1344 mCopyBuffer.clear();
1345 mCopyBuffer = selectedCategoryList();
1346 }
1347 else if ( event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier )
1348 {
1349 QgsCategoryList::const_iterator rIt = mCopyBuffer.constBegin();
1350 for ( ; rIt != mCopyBuffer.constEnd(); ++rIt )
1351 {
1352 mModel->addCategory( *rIt );
1353 }
1354 }
1355}
1356
1357QgsExpressionContext QgsCategorizedSymbolRendererWidget::createExpressionContext() const
1358{
1359 QgsExpressionContext expContext;
1363
1364 if ( auto *lMapCanvas = mContext.mapCanvas() )
1365 {
1366 expContext << QgsExpressionContextUtils::mapSettingsScope( lMapCanvas->mapSettings() )
1367 << new QgsExpressionContextScope( lMapCanvas->expressionContextScope() );
1368 if ( const QgsExpressionContextScopeGenerator *generator = dynamic_cast< const QgsExpressionContextScopeGenerator * >( lMapCanvas->temporalController() ) )
1369 {
1370 expContext << generator->createExpressionContextScope();
1371 }
1372 }
1373 else
1374 {
1376 }
1377
1378 if ( auto *lVectorLayer = vectorLayer() )
1379 expContext << QgsExpressionContextUtils::layerScope( lVectorLayer );
1380
1381 // additional scopes
1382 const auto constAdditionalExpressionContextScopes = mContext.additionalExpressionContextScopes();
1383 for ( const QgsExpressionContextScope &scope : constAdditionalExpressionContextScopes )
1384 {
1385 expContext.appendScope( new QgsExpressionContextScope( scope ) );
1386 }
1387
1388 return expContext;
1389}
1390
1391void QgsCategorizedSymbolRendererWidget::dataDefinedSizeLegend()
1392{
1393 QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( mCategorizedSymbol.get() ); // this should be only enabled for marker symbols
1394 QgsDataDefinedSizeLegendWidget *panel = createDataDefinedSizeLegendWidget( s, mRenderer->dataDefinedSizeLegend() );
1395 if ( panel )
1396 {
1397 connect( panel, &QgsPanelWidget::widgetChanged, this, [ = ]
1398 {
1399 mRenderer->setDataDefinedSizeLegend( panel->dataDefinedSizeLegend() );
1400 emit widgetChanged();
1401 } );
1402 openPanel( panel ); // takes ownership of the panel
1403 }
1404}
1405
1406void QgsCategorizedSymbolRendererWidget::mergeSelectedCategories()
1407{
1408 const QgsCategoryList &categories = mRenderer->categories();
1409
1410 const QList<int> selectedCategoryIndexes = selectedCategories();
1411 QList< int > categoryIndexes;
1412
1413 // filter out "" entry
1414 for ( const int i : selectedCategoryIndexes )
1415 {
1416 const QVariant v = categories.at( i ).value();
1417
1418 if ( !v.isValid() || v == "" )
1419 {
1420 continue;
1421 }
1422
1423 categoryIndexes.append( i );
1424 }
1425
1426 if ( categoryIndexes.count() < 2 )
1427 return;
1428
1429 QStringList labels;
1430 QVariantList values;
1431 values.reserve( categoryIndexes.count() );
1432 labels.reserve( categoryIndexes.count() );
1433 for ( const int i : categoryIndexes )
1434 {
1435 const QVariant v = categories.at( i ).value();
1436
1437 if ( v.type() == QVariant::List )
1438 {
1439 values.append( v.toList() );
1440 }
1441 else
1442 values << v;
1443
1444 labels << categories.at( i ).label();
1445 }
1446
1447 // modify first category (basically we "merge up" into the first selected category)
1448 mRenderer->updateCategoryLabel( categoryIndexes.at( 0 ), labels.join( ',' ) );
1449 mRenderer->updateCategoryValue( categoryIndexes.at( 0 ), values );
1450
1451 categoryIndexes.pop_front();
1452 mModel->deleteRows( categoryIndexes );
1453
1454 emit widgetChanged();
1455}
1456
1457void QgsCategorizedSymbolRendererWidget::unmergeSelectedCategories()
1458{
1459 const QList<int> categoryIndexes = selectedCategories();
1460 if ( categoryIndexes.isEmpty() )
1461 return;
1462
1463 const QgsCategoryList &categories = mRenderer->categories();
1464 for ( const int i : categoryIndexes )
1465 {
1466 const QVariant v = categories.at( i ).value();
1467 if ( v.type() != QVariant::List )
1468 continue;
1469
1470 const QVariantList list = v.toList();
1471 for ( int j = 1; j < list.count(); ++j )
1472 {
1473 mModel->addCategory( QgsRendererCategory( list.at( j ), categories.at( i ).symbol()->clone(), list.at( j ).toString(), categories.at( i ).renderState() ) );
1474 }
1475 mRenderer->updateCategoryValue( i, list.at( 0 ) );
1476 mRenderer->updateCategoryLabel( i, list.at( 0 ).toString() );
1477 }
1478
1479 emit widgetChanged();
1480}
1481
1482void QgsCategorizedSymbolRendererWidget::showContextMenu( QPoint )
1483{
1484 mContextMenu->clear();
1485 const QList< QAction * > actions = contextMenu->actions();
1486 for ( QAction *act : actions )
1487 {
1488 mContextMenu->addAction( act );
1489 }
1490
1491 mContextMenu->addSeparator();
1492
1493 if ( viewCategories->selectionModel()->selectedRows().count() > 1 )
1494 {
1495 mContextMenu->addAction( mMergeCategoriesAction );
1496 }
1497 if ( viewCategories->selectionModel()->selectedRows().count() == 1 )
1498 {
1499 const QList<int> categoryIndexes = selectedCategories();
1500 const QgsCategoryList &categories = mRenderer->categories();
1501 const QVariant v = categories.at( categoryIndexes.at( 0 ) ).value();
1502 if ( v.type() == QVariant::List )
1503 mContextMenu->addAction( mUnmergeCategoriesAction );
1504 }
1505 else if ( viewCategories->selectionModel()->selectedRows().count() > 1 )
1506 {
1507 mContextMenu->addAction( mUnmergeCategoriesAction );
1508 }
1509
1510 mContextMenu->exec( QCursor::pos() );
1511}
1512
1513void QgsCategorizedSymbolRendererWidget::selectionChanged( const QItemSelection &, const QItemSelection & )
1514{
1515 const QList<int> selectedCats = selectedCategories();
1516 if ( !selectedCats.isEmpty() )
1517 {
1518 whileBlocking( btnChangeCategorizedSymbol )->setSymbol( mRenderer->categories().at( selectedCats.at( 0 ) ).symbol()->clone() );
1519 }
1520 else if ( mRenderer->sourceSymbol() )
1521 {
1522 whileBlocking( btnChangeCategorizedSymbol )->setSymbol( mRenderer->sourceSymbol()->clone() );
1523 }
1524 btnChangeCategorizedSymbol->setDialogTitle( selectedCats.size() == 1 ? mRenderer->categories().at( selectedCats.at( 0 ) ).label() : tr( "Symbol Settings" ) );
1525}
SymbolType
Attribute editing capabilities which may be supported by vector data providers.
Definition: qgis.h:340
@ Marker
Marker symbol.
@ Line
Line symbol.
@ Fill
Fill symbol.
void changeSelectedSymbols()
Changes the selected symbols alone for the change button, if there is a selection.
void applyColorRamp()
Applies the color ramp passed on by the color ramp button.
QgsFeatureRenderer * renderer() override
Returns pointer to the renderer (no transfer of ownership)
void disableSymbolLevels() override
Disables symbol level modification on the widget.
void matchToSymbolsFromXml()
Prompts for selection of an xml file, then replaces category symbols with the symbols from the XML fi...
std::unique_ptr< QgsCategorizedSymbolRenderer > mRenderer
int currentCategoryRow()
Returns row index for the currently selected category (-1 if on no selection)
void matchToSymbolsFromLibrary()
Replaces category symbols with the symbols from the users' symbol library that have a matching name.
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 setSymbolLevels(const QgsLegendSymbolList &levels, bool enabled) override
Sets the symbol levels for the renderer defined in the widget.
void applyChangeToSymbol()
Applies current symbol to selected categories, or to all categories if none is selected.
static QgsRendererWidget * create(QgsVectorLayer *layer, QgsStyle *style, QgsFeatureRenderer *renderer)
QList< int > selectedCategories()
Returns a list of indexes for the categories under selection.
QList< QgsSymbol * > selectedSymbols() override
Subclasses may provide the capability of changing multiple symbols at once by implementing the follow...
QgsCategorizedSymbolRendererWidget(QgsVectorLayer *layer, QgsStyle *style, QgsFeatureRenderer *renderer)
int matchToSymbols(QgsStyle *style)
Replaces category symbols with the symbols from a style that have a matching name.
const QgsCategoryList & categories() const
Returns a list of all categories recognized by the renderer.
static QgsCategorizedSymbolRenderer * convertFromRenderer(const QgsFeatureRenderer *renderer, QgsVectorLayer *layer=nullptr)
Creates a new QgsCategorizedSymbolRenderer from an existing renderer.
static QgsCategoryList createCategories(const QVariantList &values, const QgsSymbol *symbol, QgsVectorLayer *layer=nullptr, const QString &fieldName=QString())
Create categories for a list of values.
static QString displayString(const QVariant &value, int precision=-1)
Returns a localized representation of value with the given precision, if precision is -1 then precisi...
void colorRampChanged()
Emitted whenever a new color ramp is set for the button.
Widget for configuration of appearance of legend for marker symbols with data-defined size.
QgsDataDefinedSizeLegend * dataDefinedSizeLegend() const
Returns configuration as set up in the dialog (may be nullptr). Ownership is passed to the caller.
The QgsSpinBox is a spin box with a clear button that will set the value to the defined clear value.
void setClearValue(double customValue, const QString &clearValueText=QString())
Defines the clear value as a custom value and will automatically set the clear value mode to CustomVa...
Abstract interface for generating an expression context scope.
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.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
Class for parsing and evaluation of expressions (formerly called "search strings").
bool prepare(const QgsExpressionContext *context)
Gets the expression ready for evaluation - find out column indexes.
QString expression() const
Returns the original, unmodified expression string.
QVariant evaluate()
Evaluate the feature and return the result.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
void copyRendererData(QgsFeatureRenderer *destRenderer) const
Clones generic renderer data to another renderer.
Definition: qgsrenderer.cpp:46
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
The QgsFieldExpressionWidget class reates a widget to choose fields and edit expressions It contains ...
void fieldChanged(const QString &fieldName)
Emitted when the currently selected field changes.
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:359
The class stores information about one class/rule of a vector layer renderer in a unified way that ca...
The QgsMapSettings class contains configuration for rendering of the map.
A marker symbol type, for rendering Point and MultiPoint geometries.
Base class for any widget that can be shown as a inline panel.
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
void panelAccepted(QgsPanelWidget *panel)
Emitted when the panel is accepted by the user.
void widgetChanged()
Emitted when the widget state changes.
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget.
void setPanelTitle(const QString &panelTitle)
Set the title of the panel when shown in the interface.
bool dockMode()
Returns the dock mode state.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:484
A QProxyStyle subclass which correctly sets the base style to match the QGIS application style,...
Definition: qgsproxystyle.h:31
Totally random color ramp.
virtual void setTotalColorCount(int colorCount)
Sets the desired total number of unique colors for the resultant ramp.
QColor color(double value) const override
Returns the color corresponding to a specified value.
Represents an individual category (class) from a QgsCategorizedSymbolRenderer.
QgsSymbol * symbol() const
Returns the symbol which will be used to render this category.
bool renderState() const
Returns true if the category is currently enabled and should be rendered.
QVariant value() const
Returns the value corresponding to this category.
QString label() const
Returns the label for this category, which is used to represent the category within legends and the l...
Base class for renderer settings widgets.
QAction * mPasteSymbolAction
Paste symbol action.
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.
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.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:63
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
QString errorString() const
Returns the last error from a load() operation.
Definition: qgsstyle.h:876
static QgsStyle * defaultStyle()
Returns default application-wide style.
Definition: qgsstyle.cpp:145
bool importXml(const QString &filename)
Imports the symbols and colorramps into the default style database from the given XML file.
Definition: qgsstyle.cpp:2704
void changed()
Emitted when the symbol's settings are changed.
static QgsSymbol * symbolFromMimeData(const QMimeData *data)
Attempts to parse mime data as a symbol.
static QIcon symbolPreviewIcon(const QgsSymbol *symbol, QSize size, int padding=0, QgsLegendPatchShape *shape=nullptr, const QgsScreenProperties &screen=QgsScreenProperties())
Returns an icon preview for a color ramp.
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.
Contains settings which reflect the context in which a symbol (or renderer) widget is shown,...
QList< QgsExpressionContextScope > additionalExpressionContextScopes() const
Returns the list of additional expression context scopes to show as available within the layer.
QgsMapCanvas * mapCanvas() const
Returns the map canvas associated with the widget.
QgsMessageBar * messageBar() const
Returns the message bar associated with the widget.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:94
void setColor(const QColor &color) const
Sets the color for the symbol.
Definition: qgssymbol.cpp:901
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:216
static QgsSymbol * defaultSymbol(Qgis::GeometryType geomType)
Returns a new default symbol for the specified geometry type.
Definition: qgssymbol.cpp:704
static bool isNull(const QVariant &variant)
Returns true if the specified variant should be considered a NULL value.
Represents a vector layer which manages a vector based data sets.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
QSet< QVariant > uniqueValues(int fieldIndex, int limit=-1) const FINAL
Calculates a list of unique values contained within an attribute in the layer.
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,...
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:3914
QList< QgsRendererCategory > QgsCategoryList
const QgsField & field
Definition: qgsfield.h:554
QList< QgsLegendSymbolItem > QgsLegendSymbolList
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39