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