QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
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
19#include "qgscolorrampbutton.h"
20#include "qgscolorrampimpl.h"
23#include "qgsexpression.h"
25#include "qgsfeatureiterator.h"
26#include "qgsguiutils.h"
27#include "qgslogger.h"
28#include "qgsmapcanvas.h"
29#include "qgsmarkersymbol.h"
30#include "qgspanelwidget.h"
31#include "qgsproject.h"
33#include "qgssettings.h"
34#include "qgsstyle.h"
35#include "qgssymbol.h"
36#include "qgssymbollayerutils.h"
39#include "qgsvectorlayer.h"
40#include "qgsvectorlayerutils.h"
41
42#include <QClipboard>
43#include <QFileDialog>
44#include <QKeyEvent>
45#include <QMenu>
46#include <QMessageBox>
47#include <QPainter>
48#include <QPen>
49#include <QPointer>
50#include <QScreen>
51#include <QStandardItem>
52#include <QStandardItemModel>
53#include <QUuid>
54
55#include "moc_qgscategorizedsymbolrendererwidget.cpp"
56
58
59QgsCategorizedSymbolRendererModel::QgsCategorizedSymbolRendererModel( QObject *parent, QScreen *screen )
60 : QAbstractItemModel( parent )
61 , mMimeFormat( QStringLiteral( "application/x-qgscategorizedsymbolrendererv2model" ) )
62 , mScreen( screen )
63{
64}
65
66void QgsCategorizedSymbolRendererModel::setRenderer( QgsCategorizedSymbolRenderer *renderer )
67{
68 if ( mRenderer )
69 {
70 beginRemoveRows( QModelIndex(), 0, std::max<int>( mRenderer->categories().size() - 1, 0 ) );
71 mRenderer = nullptr;
72 endRemoveRows();
73 }
74 if ( renderer )
75 {
76 mRenderer = renderer;
77 if ( renderer->categories().size() > 0 )
78 {
79 beginInsertRows( QModelIndex(), 0, renderer->categories().size() - 1 );
80 endInsertRows();
81 }
82 }
83}
84
85void QgsCategorizedSymbolRendererModel::addCategory( const QgsRendererCategory &cat )
86{
87 if ( !mRenderer )
88 return;
89 const int idx = mRenderer->categories().size();
90 beginInsertRows( QModelIndex(), idx, idx );
91 mRenderer->addCategory( cat );
92 endInsertRows();
93}
94
95QgsRendererCategory QgsCategorizedSymbolRendererModel::category( const QModelIndex &index )
96{
97 if ( !mRenderer )
98 {
99 return QgsRendererCategory();
100 }
101 const QgsCategoryList &catList = mRenderer->categories();
102 const int row = index.row();
103 if ( row >= catList.size() )
104 {
105 return QgsRendererCategory();
106 }
107 return catList.at( row );
108}
109
110
111Qt::ItemFlags QgsCategorizedSymbolRendererModel::flags( const QModelIndex &index ) const
112{
113 // Flat list, to ease drop handling valid indexes are not dropEnabled
114 if ( !index.isValid() || !mRenderer )
115 {
116 return Qt::ItemIsDropEnabled;
117 }
118
119 Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsUserCheckable;
120 if ( index.column() == 1 )
121 {
122 const QgsRendererCategory category = mRenderer->categories().value( index.row() );
123 if ( category.value().userType() != QMetaType::Type::QVariantList )
124 {
125 flags |= Qt::ItemIsEditable;
126 }
127 }
128 else if ( index.column() == 2 )
129 {
130 flags |= Qt::ItemIsEditable;
131 }
132 return flags;
133}
134
135Qt::DropActions QgsCategorizedSymbolRendererModel::supportedDropActions() const
136{
137 return Qt::MoveAction;
138}
139
140QVariant QgsCategorizedSymbolRendererModel::data( const QModelIndex &index, int role ) const
141{
142 if ( !index.isValid() || !mRenderer )
143 return QVariant();
144
145 const QgsRendererCategory category = mRenderer->categories().value( index.row() );
146
147 switch ( role )
148 {
149 case Qt::CheckStateRole:
150 {
151 if ( index.column() == 0 )
152 {
153 return category.renderState() ? Qt::Checked : Qt::Unchecked;
154 }
155 break;
156 }
157
158 case Qt::DisplayRole:
159 case Qt::ToolTipRole:
160 {
161 switch ( index.column() )
162 {
163 case 1:
164 {
165 if ( category.value().userType() == QMetaType::Type::QVariantList )
166 {
167 QStringList res;
168 const QVariantList list = category.value().toList();
169 res.reserve( list.size() );
170 for ( const QVariant &v : list )
172
173 if ( role == Qt::DisplayRole )
174 return res.join( ';' );
175 else // tooltip
176 return res.join( '\n' );
177 }
178 else if ( QgsVariantUtils::isNull( category.value() ) || category.value().toString().isEmpty() )
179 {
180 return tr( "all other values" );
181 }
182 else
183 {
184 return QgsVariantUtils::displayString( category.value() );
185 }
186 }
187 case 2:
188 return category.label();
189 }
190 break;
191 }
192
193 case Qt::FontRole:
194 {
195 if ( index.column() == 1 && category.value().userType() != QMetaType::Type::QVariantList && ( QgsVariantUtils::isNull( category.value() ) || category.value().toString().isEmpty() ) )
196 {
197 QFont italicFont;
198 italicFont.setItalic( true );
199 return italicFont;
200 }
201 return QVariant();
202 }
203
204 case Qt::DecorationRole:
205 {
206 if ( index.column() == 0 && category.symbol() )
207 {
208 const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
209 return QgsSymbolLayerUtils::symbolPreviewIcon( category.symbol(), QSize( iconSize, iconSize ), 0, nullptr, QgsScreenProperties( mScreen.data() ) );
210 }
211 break;
212 }
213
214 case Qt::ForegroundRole:
215 {
216 QBrush brush( qApp->palette().color( QPalette::Text ), Qt::SolidPattern );
217 if ( index.column() == 1 && ( category.value().userType() == QMetaType::Type::QVariantList || QgsVariantUtils::isNull( category.value() ) || category.value().toString().isEmpty() ) )
218 {
219 QColor fadedTextColor = brush.color();
220 fadedTextColor.setAlpha( 128 );
221 brush.setColor( fadedTextColor );
222 }
223 return brush;
224 }
225
226 case Qt::TextAlignmentRole:
227 {
228 return ( index.column() == 0 ) ? static_cast<Qt::Alignment::Int>( Qt::AlignHCenter ) : static_cast<Qt::Alignment::Int>( Qt::AlignLeft );
229 }
230
231 case Qt::EditRole:
232 {
233 switch ( index.column() )
234 {
235 case 1:
236 {
237 if ( category.value().userType() == QMetaType::Type::QVariantList )
238 {
239 QStringList res;
240 const QVariantList list = category.value().toList();
241 res.reserve( list.size() );
242 for ( const QVariant &v : list )
243 res << v.toString();
244
245 return res.join( ';' );
246 }
247 else
248 {
249 return category.value();
250 }
251 }
252
253 case 2:
254 return category.label();
255 }
256 break;
257 }
259 {
260 if ( index.column() == 1 )
261 return category.value();
262 break;
263 }
264 }
265
266 return QVariant();
267}
268
269bool QgsCategorizedSymbolRendererModel::setData( const QModelIndex &index, const QVariant &value, int role )
270{
271 if ( !index.isValid() )
272 return false;
273
274 if ( index.column() == 0 && role == Qt::CheckStateRole )
275 {
276 mRenderer->updateCategoryRenderState( index.row(), value == Qt::Checked );
277 emit dataChanged( index, index );
278 return true;
279 }
280
281 if ( role != Qt::EditRole )
282 return false;
283
284 switch ( index.column() )
285 {
286 case 1: // value
287 {
288 // try to preserve variant type for this value, unless it was an empty string (other values)
289 QVariant val = value;
290 const QVariant previousValue = mRenderer->categories().value( index.row() ).value();
291 if ( previousValue.userType() != QMetaType::Type::QString && !previousValue.toString().isEmpty() )
292 {
293 switch ( previousValue.userType() )
294 {
295 case QMetaType::Type::Int:
296 val = value.toInt();
297 break;
298 case QMetaType::Type::Double:
299 val = value.toDouble();
300 break;
301 case QMetaType::Type::QVariantList:
302 {
303 const QStringList parts = value.toString().split( ';' );
304 QVariantList list;
305 list.reserve( parts.count() );
306 for ( const QString &p : parts )
307 list << p;
308
309 if ( list.count() == 1 )
310 val = list.at( 0 );
311 else
312 val = list;
313 break;
314 }
315 default:
316 val = value.toString();
317 break;
318 }
319 }
320 mRenderer->updateCategoryValue( index.row(), val );
321 break;
322 }
323 case 2: // label
324 mRenderer->updateCategoryLabel( index.row(), value.toString() );
325 break;
326 default:
327 return false;
328 }
329
330 emit dataChanged( index, index );
331 return true;
332}
333
334QVariant QgsCategorizedSymbolRendererModel::headerData( int section, Qt::Orientation orientation, int role ) const
335{
336 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 3 )
337 {
338 QStringList lst;
339 lst << tr( "Symbol" ) << tr( "Value" ) << tr( "Legend" );
340 return lst.value( section );
341 }
342 return QVariant();
343}
344
345int QgsCategorizedSymbolRendererModel::rowCount( const QModelIndex &parent ) const
346{
347 if ( parent.isValid() || !mRenderer )
348 {
349 return 0;
350 }
351 return mRenderer->categories().size();
352}
353
354int QgsCategorizedSymbolRendererModel::columnCount( const QModelIndex &index ) const
355{
356 Q_UNUSED( index )
357 return 3;
358}
359
360QModelIndex QgsCategorizedSymbolRendererModel::index( int row, int column, const QModelIndex &parent ) const
361{
362 if ( hasIndex( row, column, parent ) )
363 {
364 return createIndex( row, column );
365 }
366 return QModelIndex();
367}
368
369QModelIndex QgsCategorizedSymbolRendererModel::parent( const QModelIndex &index ) const
370{
371 Q_UNUSED( index )
372 return QModelIndex();
373}
374
375QStringList QgsCategorizedSymbolRendererModel::mimeTypes() const
376{
377 QStringList types;
378 types << mMimeFormat;
379 return types;
380}
381
382QMimeData *QgsCategorizedSymbolRendererModel::mimeData( const QModelIndexList &indexes ) const
383{
384 QMimeData *mimeData = new QMimeData();
385 QByteArray encodedData;
386
387 QDataStream stream( &encodedData, QIODevice::WriteOnly );
388
389 // Create list of rows
390 const auto constIndexes = indexes;
391 for ( const QModelIndex &index : constIndexes )
392 {
393 if ( !index.isValid() || index.column() != 0 )
394 continue;
395
396 stream << index.row();
397 }
398 mimeData->setData( mMimeFormat, encodedData );
399 return mimeData;
400}
401
402bool QgsCategorizedSymbolRendererModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
403{
404 Q_UNUSED( column )
405 Q_UNUSED( parent ) // Unused because only invalid indexes have Qt::ItemIsDropEnabled
406 if ( action != Qt::MoveAction )
407 return true;
408
409 if ( !data->hasFormat( mMimeFormat ) )
410 return false;
411
412 QByteArray encodedData = data->data( mMimeFormat );
413 QDataStream stream( &encodedData, QIODevice::ReadOnly );
414
415 QVector<int> rows;
416 while ( !stream.atEnd() )
417 {
418 int r;
419 stream >> r;
420 rows.append( r );
421 }
422
423 // Items may come unsorted depending on selecion order
424 std::sort( rows.begin(), rows.end() );
425
426 int to = row;
427
428 // to is -1 if dragged outside items, i.e. below any item,
429 // then move to the last position
430 if ( to == -1 )
431 to = mRenderer->categories().size(); // out of rang ok, will be decreased
432 for ( int i = rows.size() - 1; i >= 0; i-- )
433 {
434 QgsDebugMsgLevel( QStringLiteral( "move %1 to %2" ).arg( rows[i] ).arg( to ), 2 );
435 int t = to;
436 // moveCategory first removes and then inserts
437 if ( rows[i] < t )
438 t--;
439 mRenderer->moveCategory( rows[i], t );
440 // current moved under another, shift its index up
441 for ( int j = 0; j < i; j++ )
442 {
443 if ( to < rows[j] && rows[i] > rows[j] )
444 rows[j] += 1;
445 }
446 // removed under 'to' so the target shifted down
447 if ( rows[i] < to )
448 to--;
449 }
450 emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
451 emit rowsMoved();
452 return false;
453}
454
455void QgsCategorizedSymbolRendererModel::deleteRows( QList<int> rows )
456{
457 std::sort( rows.begin(), rows.end() ); // list might be unsorted, depending on how the user selected the rows
458 for ( int i = rows.size() - 1; i >= 0; i-- )
459 {
460 beginRemoveRows( QModelIndex(), rows[i], rows[i] );
461 mRenderer->deleteCategory( rows[i] );
462 endRemoveRows();
463 }
464}
465
466void QgsCategorizedSymbolRendererModel::removeAllRows()
467{
468 beginRemoveRows( QModelIndex(), 0, mRenderer->categories().size() - 1 );
469 mRenderer->deleteAllCategories();
470 endRemoveRows();
471}
472
473void QgsCategorizedSymbolRendererModel::sort( int column, Qt::SortOrder order )
474{
475 if ( column == 0 )
476 {
477 return;
478 }
479 if ( column == 1 )
480 {
481 mRenderer->sortByValue( order );
482 }
483 else if ( column == 2 )
484 {
485 mRenderer->sortByLabel( order );
486 }
487 emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
488}
489
490void QgsCategorizedSymbolRendererModel::updateSymbology()
491{
492 emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
493}
494
495// ------------------------------ View style --------------------------------
496QgsCategorizedSymbolRendererViewStyle::QgsCategorizedSymbolRendererViewStyle( QWidget *parent )
497 : QgsProxyStyle( parent )
498{}
499
500void QgsCategorizedSymbolRendererViewStyle::drawPrimitive( PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget ) const
501{
502 if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
503 {
504 QStyleOption opt( *option );
505 opt.rect.setLeft( 0 );
506 // draw always as line above, because we move item to that index
507 opt.rect.setHeight( 0 );
508 if ( widget )
509 opt.rect.setRight( widget->width() );
510 QProxyStyle::drawPrimitive( element, &opt, painter, widget );
511 return;
512 }
513 QProxyStyle::drawPrimitive( element, option, painter, widget );
514}
515
516
517QgsCategorizedRendererViewItemDelegate::QgsCategorizedRendererViewItemDelegate( QgsFieldExpressionWidget *expressionWidget, QObject *parent )
518 : QStyledItemDelegate( parent )
519 , mFieldExpressionWidget( expressionWidget )
520{
521}
522
523QWidget *QgsCategorizedRendererViewItemDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const
524{
525 QMetaType::Type userType { static_cast<QMetaType::Type>( index.data( static_cast<int>( QgsCategorizedSymbolRendererWidget::CustomRole::Value ) ).userType() ) };
526
527 // In case of new values the type is not known
528 if ( userType == QMetaType::Type::QString && QgsVariantUtils::isNull( index.data( static_cast<int>( QgsCategorizedSymbolRendererWidget::CustomRole::Value ) ) ) )
529 {
530 bool isExpression;
531 bool isValid;
532 const QString fieldName { mFieldExpressionWidget->currentField( &isExpression, &isValid ) };
533 if ( !fieldName.isEmpty() && mFieldExpressionWidget->layer() && mFieldExpressionWidget->layer()->fields().lookupField( fieldName ) != -1 )
534 {
535 userType = mFieldExpressionWidget->layer()->fields().field( fieldName ).type();
536 }
537 else if ( isExpression && isValid )
538 {
539 // Try to guess the type from the expression return value
540 QgsFeature feat;
541 if ( mFieldExpressionWidget->layer()->getFeatures().nextFeature( feat ) )
542 {
543 QgsExpressionContext expressionContext;
546 expressionContext.appendScope( mFieldExpressionWidget->layer()->createExpressionContextScope() );
547 expressionContext.setFeature( feat );
548 QgsExpression exp { mFieldExpressionWidget->expression() };
549 const QVariant value = exp.evaluate( &expressionContext );
550 if ( !exp.hasEvalError() )
551 {
552 userType = static_cast<QMetaType::Type>( value.userType() );
553 }
554 }
555 }
556 }
557
558 QgsDoubleSpinBox *editor = nullptr;
559 switch ( userType )
560 {
561 case QMetaType::Type::Double:
562 {
563 editor = new QgsDoubleSpinBox( parent );
564 bool ok;
565 const QVariant value = index.data( static_cast<int>( QgsCategorizedSymbolRendererWidget::CustomRole::Value ) );
566 int decimals { 2 };
567 if ( value.toDouble( &ok ); ok )
568 {
569 const QString strVal { value.toString() };
570 const int dotPosition( strVal.indexOf( '.' ) );
571 if ( dotPosition >= 0 )
572 {
573 decimals = std::max<int>( 2, strVal.length() - dotPosition - 1 );
574 }
575 }
576 editor->setDecimals( decimals );
577 editor->setClearValue( 0 );
578 editor->setMaximum( std::numeric_limits<double>::max() );
579 editor->setMinimum( std::numeric_limits<double>::lowest() );
580 break;
581 }
582 case QMetaType::Type::Int:
583 {
584 editor = new QgsDoubleSpinBox( parent );
585 editor->setDecimals( 0 );
586 editor->setClearValue( 0 );
587 editor->setMaximum( std::numeric_limits<int>::max() );
588 editor->setMinimum( std::numeric_limits<int>::min() );
589 break;
590 }
591 case QMetaType::Type::QChar:
592 {
593 editor = new QgsDoubleSpinBox( parent );
594 editor->setDecimals( 0 );
595 editor->setClearValue( 0 );
596 editor->setMaximum( std::numeric_limits<char>::max() );
597 editor->setMinimum( std::numeric_limits<char>::min() );
598 break;
599 }
600 case QMetaType::Type::UInt:
601 {
602 editor = new QgsDoubleSpinBox( parent );
603 editor->setDecimals( 0 );
604 editor->setClearValue( 0 );
605 editor->setMaximum( std::numeric_limits<unsigned int>::max() );
606 editor->setMinimum( 0 );
607 break;
608 }
609 case QMetaType::Type::LongLong:
610 {
611 editor = new QgsDoubleSpinBox( parent );
612 editor->setDecimals( 0 );
613 editor->setClearValue( 0 );
614 editor->setMaximum( static_cast<double>( std::numeric_limits<qlonglong>::max() ) );
615 editor->setMinimum( std::numeric_limits<qlonglong>::min() );
616 break;
617 }
618 case QMetaType::Type::ULongLong:
619 {
620 editor = new QgsDoubleSpinBox( parent );
621 editor->setDecimals( 0 );
622 editor->setClearValue( 0 );
623 editor->setMaximum( static_cast<double>( std::numeric_limits<unsigned long long>::max() ) );
624 editor->setMinimum( 0 );
625 break;
626 }
627 default:
628 break;
629 }
630 return editor ? editor : QStyledItemDelegate::createEditor( parent, option, index );
631}
632
634
635// ------------------------------ Widget ------------------------------------
640
642 : QgsRendererWidget( layer, style )
643 , mContextMenu( new QMenu( this ) )
644{
645 // try to recognize the previous renderer
646 // (null renderer means "no previous renderer")
647 if ( renderer )
648 {
650 }
651 if ( !mRenderer )
652 {
653 mRenderer = std::make_unique<QgsCategorizedSymbolRenderer>( QString(), QgsCategoryList() );
654 if ( renderer )
655 renderer->copyRendererData( mRenderer.get() );
656 }
657
658 const QString attrName = mRenderer->classAttribute();
659 mOldClassificationAttribute = attrName;
660
661 // setup user interface
662 setupUi( this );
663 layout()->setContentsMargins( 0, 0, 0, 0 );
664
665 mExpressionWidget->setLayer( mLayer );
666 btnChangeCategorizedSymbol->setLayer( mLayer );
667 btnChangeCategorizedSymbol->registerExpressionContextGenerator( this );
668
669 // initiate color ramp button to random
670 btnColorRamp->setShowRandomColorRamp( true );
671
672 // set project default color ramp
673 std::unique_ptr<QgsColorRamp> colorRamp( QgsProject::instance()->styleSettings()->defaultColorRamp() );
674 if ( colorRamp )
675 {
676 btnColorRamp->setColorRamp( colorRamp.get() );
677 }
678 else
679 {
680 btnColorRamp->setRandomColorRamp();
681 }
682
683 mCategorizedSymbol.reset( QgsSymbol::defaultSymbol( mLayer->geometryType() ) );
684 if ( mCategorizedSymbol )
685 {
686 btnChangeCategorizedSymbol->setSymbolType( mCategorizedSymbol->type() );
687 btnChangeCategorizedSymbol->setSymbol( mCategorizedSymbol->clone() );
688 }
689
690 mModel = new QgsCategorizedSymbolRendererModel( this, screen() );
691 mModel->setRenderer( mRenderer.get() );
692
693 // update GUI from renderer
695
696 viewCategories->setModel( mModel );
697 viewCategories->resizeColumnToContents( 0 );
698 viewCategories->resizeColumnToContents( 1 );
699 viewCategories->resizeColumnToContents( 2 );
700 viewCategories->setItemDelegateForColumn( 1, new QgsCategorizedRendererViewItemDelegate( mExpressionWidget, viewCategories ) );
701
702 viewCategories->setStyle( new QgsCategorizedSymbolRendererViewStyle( viewCategories ) );
703 connect( viewCategories->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsCategorizedSymbolRendererWidget::selectionChanged );
704
705 connect( mModel, &QgsCategorizedSymbolRendererModel::rowsMoved, this, &QgsCategorizedSymbolRendererWidget::rowsMoved );
706 connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsPanelWidget::widgetChanged );
707
708 connect( mExpressionWidget, static_cast<void ( QgsFieldExpressionWidget::* )( const QString & )>( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsCategorizedSymbolRendererWidget::categoryColumnChanged );
709
710 connect( viewCategories, &QAbstractItemView::doubleClicked, this, &QgsCategorizedSymbolRendererWidget::categoriesDoubleClicked );
711 connect( viewCategories, &QTreeView::customContextMenuRequested, this, &QgsCategorizedSymbolRendererWidget::showContextMenu );
712
713 connect( btnChangeCategorizedSymbol, &QgsSymbolButton::changed, this, &QgsCategorizedSymbolRendererWidget::updateSymbolsFromButton );
714
715 connect( btnAddCategories, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::addCategories );
716 connect( btnDeleteCategories, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::deleteCategories );
717 connect( btnDeleteAllCategories, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::deleteAllCategories );
718 connect( btnDeleteUnusedCategories, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::deleteUnusedCategories );
719 connect( btnAddCategory, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::addCategory );
720
722
723 // menus for data-defined rotation/size
724 QMenu *advMenu = new QMenu;
725
726 advMenu->addAction( tr( "Match to Saved Symbols" ), this, &QgsCategorizedSymbolRendererWidget::matchToSymbolsFromLibrary );
727 advMenu->addAction( tr( "Match to Symbols from File…" ), this, &QgsCategorizedSymbolRendererWidget::matchToSymbolsFromXml );
728 mActionLevels = advMenu->addAction( tr( "Symbol Levels…" ), this, &QgsCategorizedSymbolRendererWidget::showSymbolLevels );
730 {
731 QAction *actionDdsLegend = advMenu->addAction( tr( "Data-defined Size Legend…" ) );
732 // only from Qt 5.6 there is convenience addAction() with new style connection
733 connect( actionDdsLegend, &QAction::triggered, this, &QgsCategorizedSymbolRendererWidget::dataDefinedSizeLegend );
734 }
735
736 btnAdvanced->setMenu( advMenu );
737
738 mExpressionWidget->registerExpressionContextGenerator( this );
739
740 mMergeCategoriesAction = new QAction( tr( "Merge Categories" ), this );
741 connect( mMergeCategoriesAction, &QAction::triggered, this, &QgsCategorizedSymbolRendererWidget::mergeSelectedCategories );
742 mUnmergeCategoriesAction = new QAction( tr( "Unmerge Categories" ), this );
743 connect( mUnmergeCategoriesAction, &QAction::triggered, this, &QgsCategorizedSymbolRendererWidget::unmergeSelectedCategories );
744
745 connect( mContextMenu, &QMenu::aboutToShow, this, [this] {
746 const std::unique_ptr<QgsSymbol> tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
747 mPasteSymbolAction->setEnabled( static_cast<bool>( tempSymbol ) );
748 } );
749}
750
755
757{
758 // Note: This assumes that the signals for UI element changes have not
759 // yet been connected, so that the updates to color ramp, symbol, etc
760 // don't override existing customizations.
761
762 //mModel->setRenderer ( mRenderer ); // necessary?
763
764 // set column
765 const QString attrName = mRenderer->classAttribute();
766 mExpressionWidget->setField( attrName );
767
768 // set source symbol
769 if ( mRenderer->sourceSymbol() )
770 {
771 mCategorizedSymbol.reset( mRenderer->sourceSymbol()->clone() );
772 whileBlocking( btnChangeCategorizedSymbol )->setSymbol( mCategorizedSymbol->clone() );
773 }
774
775 // if a color ramp attached to the renderer, enable the color ramp button
776 if ( mRenderer->sourceColorRamp() )
777 {
778 btnColorRamp->setColorRamp( mRenderer->sourceColorRamp() );
779 }
780}
781
786
788{
790 btnChangeCategorizedSymbol->setMapCanvas( context.mapCanvas() );
791 btnChangeCategorizedSymbol->setMessageBar( context.messageBar() );
792}
793
795{
796 delete mActionLevels;
797 mActionLevels = nullptr;
798}
799
801{
802 const QList<int> selectedCats = selectedCategories();
803
804 if ( !selectedCats.isEmpty() )
805 {
806 QgsSymbol *newSymbol = mCategorizedSymbol->clone();
807 QgsSymbolSelectorDialog dlg( newSymbol, mStyle, mLayer, this );
808 dlg.setContext( context() );
809 if ( !dlg.exec() )
810 {
811 delete newSymbol;
812 return;
813 }
814
815 const auto constSelectedCats = selectedCats;
816 for ( const int idx : constSelectedCats )
817 {
818 const QgsRendererCategory category = mRenderer->categories().value( idx );
819
820 QgsSymbol *newCatSymbol = newSymbol->clone();
821 newCatSymbol->setColor( mRenderer->categories()[idx].symbol()->color() );
822 mRenderer->updateCategorySymbol( idx, newCatSymbol );
823 }
824 }
825}
826
828{
830 std::unique_ptr<QgsSymbol> newSymbol( mCategorizedSymbol->clone() );
831 if ( panel && panel->dockMode() )
832 {
834 widget->setContext( mContext );
835 connect( widget, &QgsPanelWidget::widgetChanged, this, [this, widget] { updateSymbolsFromWidget( widget ); } );
836 openPanel( widget );
837 }
838 else
839 {
840 QgsSymbolSelectorDialog dlg( newSymbol.get(), mStyle, mLayer, panel );
841 dlg.setContext( mContext );
842 if ( !dlg.exec() || !newSymbol )
843 {
844 return;
845 }
846
847 mCategorizedSymbol = std::move( newSymbol );
849 }
850}
851
852
856
858{
859 mRenderer->setClassAttribute( field );
860 emit widgetChanged();
861}
862
864{
865 if ( idx.isValid() && idx.column() == 0 )
867}
868
870{
871 const QgsRendererCategory category = mRenderer->categories().value( currentCategoryRow() );
872
873 std::unique_ptr<QgsSymbol> symbol;
874
875 if ( auto *lSymbol = category.symbol() )
876 {
877 symbol.reset( lSymbol->clone() );
878 }
879 else
880 {
881 symbol.reset( QgsSymbol::defaultSymbol( mLayer->geometryType() ) );
882 }
883
885 if ( panel && panel->dockMode() )
886 {
888 widget->setContext( mContext );
889 widget->setPanelTitle( category.label() );
890 connect( widget, &QgsPanelWidget::widgetChanged, this, [this, widget] { updateSymbolsFromWidget( widget ); } );
891 openPanel( widget );
892 }
893 else
894 {
895 QgsSymbolSelectorDialog dlg( symbol.get(), mStyle, mLayer, panel );
896 dlg.setContext( mContext );
897 if ( !dlg.exec() || !symbol )
898 {
899 return;
900 }
901
902 mCategorizedSymbol = std::move( symbol );
904 }
905}
906
907
909{
910 const QString attrName = mExpressionWidget->currentField();
911 bool valuesRetrieved;
912 const QList<QVariant> uniqueValues = QgsVectorLayerUtils::uniqueValues( mLayer, attrName, valuesRetrieved );
913 if ( !valuesRetrieved )
914 {
915 QgsDebugMsgLevel( QStringLiteral( "Unable to retrieve values from layer %1 with expression %2" ).arg( mLayer->name() ).arg( attrName ), 2 );
916 return;
917 }
918
919 // ask to abort if too many classes
920 if ( uniqueValues.size() >= 1000 )
921 {
922 const int res = QMessageBox::warning( nullptr, tr( "Classify Categories" ), tr( "High number of classes. Classification would yield %n entries which might not be expected. Continue?", nullptr, uniqueValues.size() ), QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel );
923 if ( res == QMessageBox::Cancel )
924 {
925 return;
926 }
927 }
928
929#if 0
930 DlgAddCategories dlg( mStyle, createDefaultSymbol(), unique_vals, this );
931 if ( !dlg.exec() )
932 return;
933#endif
934
936 bool deleteExisting = false;
937
938 if ( !mOldClassificationAttribute.isEmpty() && attrName != mOldClassificationAttribute && !mRenderer->categories().isEmpty() )
939 {
940 const int res = QMessageBox::question( this, tr( "Delete Classification" ), tr( "The classification field was changed from '%1' to '%2'.\n"
941 "Should the existing classes be deleted before classification?" )
942 .arg( mOldClassificationAttribute, attrName ),
943 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel );
944 if ( res == QMessageBox::Cancel )
945 {
946 return;
947 }
948
949 deleteExisting = ( res == QMessageBox::Yes );
950 }
951
952 // First element to apply coloring to
953 bool keepExistingColors = false;
954 if ( !deleteExisting )
955 {
956 QgsCategoryList prevCats = mRenderer->categories();
957 keepExistingColors = !prevCats.isEmpty();
958 QgsRandomColorRamp randomColors;
959 if ( keepExistingColors && btnColorRamp->isRandomColorRamp() )
960 randomColors.setTotalColorCount( cats.size() );
961 for ( int i = 0; i < cats.size(); ++i )
962 {
963 bool contains = false;
964 const QVariant value = cats.at( i ).value();
965 for ( int j = 0; j < prevCats.size() && !contains; ++j )
966 {
967 const QVariant prevCatValue = prevCats.at( j ).value();
968 if ( prevCatValue.userType() == QMetaType::Type::QVariantList )
969 {
970 const QVariantList list = prevCatValue.toList();
971 for ( const QVariant &v : list )
972 {
973 if ( v == value )
974 {
975 contains = true;
976 break;
977 }
978 }
979 }
980 else
981 {
982 if ( prevCats.at( j ).value() == value )
983 {
984 contains = true;
985 }
986 }
987 if ( contains )
988 break;
989 }
990
991 if ( !contains )
992 {
993 if ( keepExistingColors && btnColorRamp->isRandomColorRamp() )
994 {
995 // insure that append symbols have random colors
996 cats.at( i ).symbol()->setColor( randomColors.color( i ) );
997 }
998 prevCats.append( cats.at( i ) );
999 }
1000 }
1001 cats = prevCats;
1002 }
1003
1004 mOldClassificationAttribute = attrName;
1005
1006 // TODO: if not all categories are desired, delete some!
1007 /*
1008 if (not dlg.readAllCats.isChecked())
1009 {
1010 cats2 = {}
1011 for item in dlg.listCategories.selectedItems():
1012 for k,c in cats.iteritems():
1013 if item.text() == k.toString():
1014 break
1015 cats2[k] = c
1016 cats = cats2
1017 }
1018 */
1019
1020 // recreate renderer
1021 auto r = std::make_unique<QgsCategorizedSymbolRenderer>( attrName, cats );
1022 r->setSourceSymbol( mCategorizedSymbol->clone() );
1023 std::unique_ptr<QgsColorRamp> ramp( btnColorRamp->colorRamp() );
1024 if ( ramp )
1025 r->setSourceColorRamp( ramp->clone() );
1026
1027 if ( mModel )
1028 {
1029 mModel->setRenderer( r.get() );
1030 }
1031 mRenderer = std::move( r );
1032 if ( !keepExistingColors && ramp )
1034 emit widgetChanged();
1035}
1036
1038{
1039 if ( !btnColorRamp->isNull() )
1040 {
1041 mRenderer->updateColorRamp( btnColorRamp->colorRamp() );
1042 }
1043 mModel->updateSymbology();
1044}
1045
1047{
1048 const QModelIndex idx = viewCategories->selectionModel()->currentIndex();
1049 if ( !idx.isValid() )
1050 return -1;
1051 return idx.row();
1052}
1053
1055{
1056 QList<int> rows;
1057 const QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
1058
1059 const auto constSelectedRows = selectedRows;
1060 for ( const QModelIndex &r : constSelectedRows )
1061 {
1062 if ( r.isValid() )
1063 {
1064 rows.append( r.row() );
1065 }
1066 }
1067 return rows;
1068}
1069
1071{
1072 const QList<int> categoryIndexes = selectedCategories();
1073 mModel->deleteRows( categoryIndexes );
1074 emit widgetChanged();
1075}
1076
1078{
1079 mModel->removeAllRows();
1080 emit widgetChanged();
1081}
1082
1084{
1085 if ( !mRenderer )
1086 return;
1087 const QString attrName = mExpressionWidget->currentField();
1088 bool valuesRetrieved;
1089 const QList<QVariant> uniqueValues = QgsVectorLayerUtils::uniqueValues( mLayer, attrName, valuesRetrieved );
1090 if ( !valuesRetrieved )
1091 {
1092 QgsDebugMsgLevel( QStringLiteral( "Unable to retrieve values from layer %1 with expression %2" ).arg( mLayer->name() ).arg( attrName ), 2 );
1093 }
1094
1095 const QgsCategoryList catList = mRenderer->categories();
1096
1097 QList<int> unusedIndexes;
1098
1099 for ( int i = 0; i < catList.size(); ++i )
1100 {
1101 const QgsRendererCategory cat = catList.at( i );
1102 if ( !uniqueValues.contains( cat.value() ) )
1103 {
1104 unusedIndexes.append( i );
1105 }
1106 }
1107 mModel->deleteRows( unusedIndexes );
1108 emit widgetChanged();
1109}
1110
1111QList<QVariant> QgsCategorizedSymbolRendererWidget::layerUniqueValues( const QString &attrName )
1112{
1113 bool valuesRetrieved;
1114 const QList<QVariant> uniqueValues = QgsVectorLayerUtils::uniqueValues( mLayer, attrName, valuesRetrieved );
1115 if ( !valuesRetrieved )
1116 {
1117 QgsDebugMsgLevel( QStringLiteral( "Unable to retrieve values from layer %1 with expression %2" ).arg( mLayer->name() ).arg( attrName ), 2 );
1118 }
1119 return uniqueValues;
1120}
1121
1123{
1124 if ( !mModel )
1125 return;
1126 QgsSymbol *symbol = QgsSymbol::defaultSymbol( mLayer->geometryType() );
1127 const QgsRendererCategory cat( QVariant(), symbol, QString(), true );
1128 mModel->addCategory( cat );
1129 emit widgetChanged();
1130}
1131
1133{
1134 QList<QgsSymbol *> selectedSymbols;
1135
1136 QItemSelectionModel *m = viewCategories->selectionModel();
1137 const QModelIndexList selectedIndexes = m->selectedRows( 1 );
1138
1139 if ( !selectedIndexes.isEmpty() )
1140 {
1141 const QgsCategoryList &categories = mRenderer->categories();
1142 QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
1143 for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
1144 {
1145 const int row = ( *indexIt ).row();
1146 QgsSymbol *s = categories[row].symbol();
1147 if ( s )
1148 {
1149 selectedSymbols.append( s );
1150 }
1151 }
1152 }
1153 return selectedSymbols;
1154}
1155
1157{
1158 QgsCategoryList cl;
1159
1160 QItemSelectionModel *m = viewCategories->selectionModel();
1161 const QModelIndexList selectedIndexes = m->selectedRows( 1 );
1162
1163 if ( !selectedIndexes.isEmpty() )
1164 {
1165 QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
1166 for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
1167 {
1168 cl.append( mModel->category( *indexIt ) );
1169 }
1170 }
1171 return cl;
1172}
1173
1179
1184
1186{
1187 viewCategories->selectionModel()->clear();
1188}
1189
1191{
1192 const int matched = matchToSymbols( QgsStyle::defaultStyle() );
1193 if ( matched > 0 )
1194 {
1195 QMessageBox::information( this, tr( "Matched Symbols" ), tr( "Matched %n categories to symbols.", nullptr, matched ) );
1196 }
1197 else
1198 {
1199 QMessageBox::warning( this, tr( "Matched Symbols" ), tr( "No categories could be matched to symbols in library." ) );
1200 }
1201}
1202
1204{
1205 if ( !mLayer || !style )
1206 return 0;
1207
1211
1212 QVariantList unmatchedCategories;
1213 QStringList unmatchedSymbols;
1214 const int matched = mRenderer->matchToSymbols( style, type, unmatchedCategories, unmatchedSymbols );
1215
1216 mModel->updateSymbology();
1217 return matched;
1218}
1219
1221{
1222 QgsSettings settings;
1223 const QString openFileDir = settings.value( QStringLiteral( "UI/lastMatchToSymbolsDir" ), QDir::homePath() ).toString();
1224
1225 const QString fileName = QFileDialog::getOpenFileName( this, tr( "Match to Symbols from File" ), openFileDir, tr( "XML files (*.xml *.XML)" ) );
1226 if ( fileName.isEmpty() )
1227 {
1228 return;
1229 }
1230
1231 const QFileInfo openFileInfo( fileName );
1232 settings.setValue( QStringLiteral( "UI/lastMatchToSymbolsDir" ), openFileInfo.absolutePath() );
1233
1234 QgsStyle importedStyle;
1235 if ( !importedStyle.importXml( fileName ) )
1236 {
1237 QMessageBox::warning( this, tr( "Match to Symbols from File" ), tr( "An error occurred while reading file:\n%1" ).arg( importedStyle.errorString() ) );
1238 return;
1239 }
1240
1241 const int matched = matchToSymbols( &importedStyle );
1242 if ( matched > 0 )
1243 {
1244 QMessageBox::information( this, tr( "Match to Symbols from File" ), tr( "Matched %n categories to symbols from file.", nullptr, matched ) );
1245 }
1246 else
1247 {
1248 QMessageBox::warning( this, tr( "Match to Symbols from File" ), tr( "No categories could be matched to symbols in file." ) );
1249 }
1250}
1251
1253{
1254 for ( const QgsLegendSymbolItem &legendSymbol : levels )
1255 {
1256 QgsSymbol *sym = legendSymbol.symbol();
1257 for ( int layer = 0; layer < sym->symbolLayerCount(); layer++ )
1258 {
1259 mRenderer->setLegendSymbolItem( legendSymbol.ruleKey(), sym->clone() );
1260 }
1261 }
1262 mRenderer->setUsingSymbolLevels( enabled );
1263 mModel->updateSymbology();
1264 emit widgetChanged();
1265}
1266
1268{
1269 std::unique_ptr<QgsSymbol> tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
1270 if ( !tempSymbol )
1271 return;
1272
1273 const QList<int> selectedCats = selectedCategories();
1274 if ( !selectedCats.isEmpty() )
1275 {
1276 for ( const int idx : selectedCats )
1277 {
1278 if ( mRenderer->categories().at( idx ).symbol()->type() != tempSymbol->type() )
1279 continue;
1280
1281 std::unique_ptr<QgsSymbol> newCatSymbol( tempSymbol->clone() );
1282 if ( selectedCats.count() > 1 )
1283 {
1284 //if updating multiple categories, retain the existing category colors
1285 newCatSymbol->setColor( mRenderer->categories().at( idx ).symbol()->color() );
1286 }
1287 mRenderer->updateCategorySymbol( idx, newCatSymbol.release() );
1288 }
1289 emit widgetChanged();
1290 }
1291}
1292
1293void QgsCategorizedSymbolRendererWidget::updateSymbolsFromWidget( QgsSymbolSelectorWidget *widget )
1294{
1295 mCategorizedSymbol.reset( widget->symbol()->clone() );
1296
1298}
1299
1300void QgsCategorizedSymbolRendererWidget::updateSymbolsFromButton()
1301{
1302 mCategorizedSymbol.reset( btnChangeCategorizedSymbol->symbol()->clone() );
1303
1305}
1306
1308{
1309 // When there is a selection, change the selected symbols only
1310 QItemSelectionModel *m = viewCategories->selectionModel();
1311 const QModelIndexList i = m->selectedRows();
1312
1313 if ( !i.isEmpty() )
1314 {
1315 const QList<int> selectedCats = selectedCategories();
1316
1317 if ( !selectedCats.isEmpty() )
1318 {
1319 const auto constSelectedCats = selectedCats;
1320 for ( const int idx : constSelectedCats )
1321 {
1322 QgsSymbol *newCatSymbol = mCategorizedSymbol->clone();
1323 if ( selectedCats.count() > 1 )
1324 {
1325 //if updating multiple categories, retain the existing category colors
1326 newCatSymbol->setColor( mRenderer->categories().at( idx ).symbol()->color() );
1327 }
1328 mRenderer->updateCategorySymbol( idx, newCatSymbol );
1329 }
1330 }
1331 }
1332 else
1333 {
1334 mRenderer->updateSymbols( mCategorizedSymbol.get() );
1335 }
1336
1337 mModel->updateSymbology();
1338 emit widgetChanged();
1339}
1340
1342{
1343 if ( !event )
1344 {
1345 return;
1346 }
1347
1348 if ( event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier )
1349 {
1350 mCopyBuffer.clear();
1351 mCopyBuffer = selectedCategoryList();
1352 }
1353 else if ( event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier )
1354 {
1355 QgsCategoryList::iterator rIt = mCopyBuffer.begin();
1356 for ( ; rIt != mCopyBuffer.end(); ++rIt )
1357 {
1358 rIt->mUuid = QUuid::createUuid().toString();
1359 mModel->addCategory( *rIt );
1360 }
1361 }
1362}
1363
1365{
1366 QgsExpressionContext expContext;
1367 if ( auto *lMapCanvas = mContext.mapCanvas() )
1368 {
1369 expContext = lMapCanvas->createExpressionContext();
1370 }
1371 else
1372 {
1377 }
1378
1379 if ( auto *lVectorLayer = vectorLayer() )
1380 expContext << QgsExpressionContextUtils::layerScope( lVectorLayer );
1381
1382 // additional scopes
1383 const auto constAdditionalExpressionContextScopes = mContext.additionalExpressionContextScopes();
1384 for ( const QgsExpressionContextScope &scope : constAdditionalExpressionContextScopes )
1385 {
1386 expContext.appendScope( new QgsExpressionContextScope( scope ) );
1387 }
1388
1389 return expContext;
1390}
1391
1392void QgsCategorizedSymbolRendererWidget::dataDefinedSizeLegend()
1393{
1394 QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( mCategorizedSymbol.get() ); // this should be only enabled for marker symbols
1395 QgsDataDefinedSizeLegendWidget *panel = createDataDefinedSizeLegendWidget( s, mRenderer->dataDefinedSizeLegend() );
1396 if ( panel )
1397 {
1398 connect( panel, &QgsPanelWidget::widgetChanged, this, [this, panel] {
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.userType() == QMetaType::Type::QVariantList )
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.userType() != QMetaType::Type::QVariantList )
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.userType() == QMetaType::Type::QVariantList )
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}
@ Point
Points.
Definition qgis.h:359
@ Line
Lines.
Definition qgis.h:360
SymbolType
Symbol types.
Definition qgis.h:610
@ Marker
Marker symbol.
Definition qgis.h:611
@ Line
Line symbol.
Definition qgis.h:612
@ Fill
Fill symbol.
Definition qgis.h:613
void changeSelectedSymbols()
Changes the selected symbols alone for the change button, if there is a selection.
std::unique_ptr< QgsCategorizedSymbolRenderer > mRenderer
void applyColorRamp()
Applies the color ramp passed on by the color ramp button.
void deleteUnusedCategories()
Deletes unused categories from the widget which are not used by the layer renderer.
QgsFeatureRenderer * renderer() override
Returns pointer to the renderer (no transfer of ownership).
Q_DECL_DEPRECATED QList< QVariant > layerUniqueValues(const QString &attrName)
Returns the list of unique values in the current widget's layer for attribute name attrName.
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...
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.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
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.
A feature renderer which represents features using a list of renderer categories.
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.
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...
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.
Handles parsing and evaluation of expressions (formerly called "search strings").
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
QVariant evaluate()
Evaluate the feature and return the result.
Abstract base class for all 2D vector feature renderers.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
A widget for selection of layer fields or expression creation.
void fieldChanged(const QString &fieldName)
Emitted when the currently selected field changes.
Stores information about one class/rule of a vector layer renderer in a unified way that can be used ...
Contains configuration for rendering maps.
A marker symbol type, for rendering Point and MultiPoint geometries.
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
bool dockMode() const
Returns the dock mode state.
QgsPanelWidget(QWidget *parent=nullptr)
Base class for any widget that can be shown as an inline panel.
void widgetChanged()
Emitted when the widget state changes.
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget.
void setPanelTitle(const QString &panelTitle)
Set the title of the panel when shown in the interface.
static QgsProject * instance()
Returns the QgsProject singleton instance.
A QProxyStyle subclass which correctly sets the base style to match the QGIS application style,...
A color ramp consisting of random colors, constrained within component ranges.
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...
QAction * mPasteSymbolAction
Paste symbol action.
QgsRendererWidget(QgsVectorLayer *layer, QgsStyle *style)
void showSymbolLevelsDialog(QgsFeatureRenderer *r)
Show a dialog with renderer's symbol level settings.
QgsSymbolWidgetContext mContext
Context in which widget is shown.
virtual void setContext(const QgsSymbolWidgetContext &context)
Sets the context in which the renderer widget is shown, e.g., the associated map canvas and expressio...
QgsDataDefinedSizeLegendWidget * createDataDefinedSizeLegendWidget(const QgsMarkerSymbol *symbol, const QgsDataDefinedSizeLegend *ddsLegend)
Creates widget to setup data-defined size legend.
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.
Stores settings for use within QGIS.
Definition qgssettings.h:65
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.
A database of saved style entities, including symbols, color ramps, text formats and others.
Definition qgsstyle.h:88
QString errorString() const
Returns the last error from a load() operation.
Definition qgsstyle.h:915
static QgsStyle * defaultStyle(bool initialize=true)
Returns the default application-wide style.
Definition qgsstyle.cpp:147
bool importXml(const QString &filename)
Imports the symbols and colorramps into the default style database from the given XML file.
void changed()
Emitted when the symbol's settings are changed.
static std::unique_ptr< QgsSymbol > symbolFromMimeData(const QMimeData *data)
Attempts to parse mime data as a symbol.
static QIcon symbolPreviewIcon(const QgsSymbol *symbol, QSize size, int padding=0, QgsLegendPatchShape *shape=nullptr, const QgsScreenProperties &screen=QgsScreenProperties())
Returns an icon preview for a color ramp.
A dialog that can be used to select and build a symbol.
void setContext(const QgsSymbolWidgetContext &context)
Sets the context in which the symbol widget is shown, e.g., the associated map canvas and expression ...
Symbol selector widget that can be used to select and build a symbol.
void setContext(const QgsSymbolWidgetContext &context)
Sets the context in which the symbol widget is shown, e.g., the associated map canvas and expression ...
QgsSymbol * symbol()
Returns the symbol that is currently active in the widget.
static QgsSymbolSelectorWidget * createWidgetWithSymbolOwnership(std::unique_ptr< QgsSymbol > symbol, QgsStyle *style, QgsVectorLayer *vl, QWidget *parent=nullptr)
Creates a QgsSymbolSelectorWidget which takes ownership of a symbol and maintains the ownership for t...
Contains settings which reflect the context in which a symbol (or renderer) widget is shown,...
Abstract base class for all rendered symbols.
Definition qgssymbol.h:231
void setColor(const QColor &color) const
Sets the color for the symbol.
virtual QgsSymbol * clone() const =0
Returns a deep copy of this symbol.
int symbolLayerCount() const
Returns the total number of symbol layers contained in the symbol.
Definition qgssymbol.h:353
static QgsSymbol * defaultSymbol(Qgis::GeometryType geomType)
Returns a new default symbol for the specified geometry type.
static QString displayString(const QVariant &variant, int precision=-1)
Returns a localized representation of value with the given precision, if precision is -1 then precisi...
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
static QList< QVariant > uniqueValues(const QgsVectorLayer *layer, const QString &fieldOrExpression, bool &ok, bool selectedOnly=false, int limit=-1, QgsFeedback *feedback=nullptr)
Fetches all unique values from a specified field name or expression.
Represents a vector layer which manages a vector based dataset.
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:6511
QList< QgsRendererCategory > QgsCategoryList
QList< QgsLegendSymbolItem > QgsLegendSymbolList
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:61