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