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