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