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