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