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