QGIS API Documentation 3.41.0-Master (3440c17df1d)
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 ) return;
86 const int idx = mRenderer->categories().size();
87 beginInsertRows( QModelIndex(), idx, idx );
88 mRenderer->addCategory( cat );
89 endInsertRows();
90}
91
92QgsRendererCategory QgsCategorizedSymbolRendererModel::category( const QModelIndex &index )
93{
94 if ( !mRenderer )
95 {
96 return QgsRendererCategory();
97 }
98 const QgsCategoryList &catList = mRenderer->categories();
99 const int row = index.row();
100 if ( row >= catList.size() )
101 {
102 return QgsRendererCategory();
103 }
104 return catList.at( row );
105}
106
107
108Qt::ItemFlags QgsCategorizedSymbolRendererModel::flags( const QModelIndex &index ) const
109{
110 if ( !index.isValid() || !mRenderer )
111 {
112 return Qt::ItemIsDropEnabled;
113 }
114
115 Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable;
116 if ( index.column() == 1 )
117 {
118 const QgsRendererCategory category = mRenderer->categories().value( index.row() );
119 if ( category.value().userType() != QMetaType::Type::QVariantList )
120 {
121 flags |= Qt::ItemIsEditable;
122 }
123 }
124 else if ( index.column() == 2 )
125 {
126 flags |= Qt::ItemIsEditable;
127 }
128 return flags;
129}
130
131Qt::DropActions QgsCategorizedSymbolRendererModel::supportedDropActions() const
132{
133 return Qt::MoveAction;
134}
135
136QVariant QgsCategorizedSymbolRendererModel::data( const QModelIndex &index, int role ) const
137{
138 if ( !index.isValid() || !mRenderer )
139 return QVariant();
140
141 const QgsRendererCategory category = mRenderer->categories().value( index.row() );
142
143 switch ( role )
144 {
145 case Qt::CheckStateRole:
146 {
147 if ( index.column() == 0 )
148 {
149 return category.renderState() ? Qt::Checked : Qt::Unchecked;
150 }
151 break;
152 }
153
154 case Qt::DisplayRole:
155 case Qt::ToolTipRole:
156 {
157 switch ( index.column() )
158 {
159 case 1:
160 {
161 if ( category.value().userType() == QMetaType::Type::QVariantList )
162 {
163 QStringList res;
164 const QVariantList list = category.value().toList();
165 res.reserve( list.size() );
166 for ( const QVariant &v : list )
167 res << QgsCategorizedSymbolRenderer::displayString( v );
168
169 if ( role == Qt::DisplayRole )
170 return res.join( ';' );
171 else // tooltip
172 return res.join( '\n' );
173 }
174 else if ( QgsVariantUtils::isNull( category.value() ) || category.value().toString().isEmpty() )
175 {
176 return tr( "all other values" );
177 }
178 else
179 {
181 }
182 }
183 case 2:
184 return category.label();
185 }
186 break;
187 }
188
189 case Qt::FontRole:
190 {
191 if ( index.column() == 1 && category.value().userType() != QMetaType::Type::QVariantList && ( QgsVariantUtils::isNull( category.value() ) || category.value().toString().isEmpty() ) )
192 {
193 QFont italicFont;
194 italicFont.setItalic( true );
195 return italicFont;
196 }
197 return QVariant();
198 }
199
200 case Qt::DecorationRole:
201 {
202 if ( index.column() == 0 && category.symbol() )
203 {
204 const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
205 return QgsSymbolLayerUtils::symbolPreviewIcon( category.symbol(), QSize( iconSize, iconSize ), 0, nullptr, QgsScreenProperties( mScreen.data() ) );
206 }
207 break;
208 }
209
210 case Qt::ForegroundRole:
211 {
212 QBrush brush( qApp->palette().color( QPalette::Text ), Qt::SolidPattern );
213 if ( index.column() == 1 && ( category.value().userType() == QMetaType::Type::QVariantList
214 || 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 ) return true;
404
405 if ( !data->hasFormat( mMimeFormat ) ) return false;
406
407 QByteArray encodedData = data->data( mMimeFormat );
408 QDataStream stream( &encodedData, QIODevice::ReadOnly );
409
410 QVector<int> rows;
411 while ( !stream.atEnd() )
412 {
413 int r;
414 stream >> r;
415 rows.append( r );
416 }
417
418 int to = parent.row();
419 // to is -1 if dragged outside items, i.e. below any item,
420 // then move to the last position
421 if ( to == -1 ) to = mRenderer->categories().size(); // out of rang ok, will be decreased
422 for ( int i = rows.size() - 1; i >= 0; i-- )
423 {
424 QgsDebugMsgLevel( QStringLiteral( "move %1 to %2" ).arg( rows[i] ).arg( to ), 2 );
425 int t = to;
426 // moveCategory first removes and then inserts
427 if ( rows[i] < t ) t--;
428 mRenderer->moveCategory( rows[i], t );
429 // current moved under another, shift its index up
430 for ( int j = 0; j < i; j++ )
431 {
432 if ( to < rows[j] && rows[i] > rows[j] ) rows[j] += 1;
433 }
434 // removed under 'to' so the target shifted down
435 if ( rows[i] < to ) to--;
436 }
437 emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
438 emit rowsMoved();
439 return false;
440}
441
442void QgsCategorizedSymbolRendererModel::deleteRows( QList<int> rows )
443{
444 std::sort( rows.begin(), rows.end() ); // list might be unsorted, depending on how the user selected the rows
445 for ( int i = rows.size() - 1; i >= 0; i-- )
446 {
447 beginRemoveRows( QModelIndex(), rows[i], rows[i] );
448 mRenderer->deleteCategory( rows[i] );
449 endRemoveRows();
450 }
451}
452
453void QgsCategorizedSymbolRendererModel::removeAllRows()
454{
455 beginRemoveRows( QModelIndex(), 0, mRenderer->categories().size() - 1 );
456 mRenderer->deleteAllCategories();
457 endRemoveRows();
458}
459
460void QgsCategorizedSymbolRendererModel::sort( int column, Qt::SortOrder order )
461{
462 if ( column == 0 )
463 {
464 return;
465 }
466 if ( column == 1 )
467 {
468 mRenderer->sortByValue( order );
469 }
470 else if ( column == 2 )
471 {
472 mRenderer->sortByLabel( order );
473 }
474 emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
475}
476
477void QgsCategorizedSymbolRendererModel::updateSymbology()
478{
479 emit dataChanged( createIndex( 0, 0 ), createIndex( mRenderer->categories().size(), 0 ) );
480}
481
482// ------------------------------ View style --------------------------------
483QgsCategorizedSymbolRendererViewStyle::QgsCategorizedSymbolRendererViewStyle( QWidget *parent )
484 : QgsProxyStyle( parent )
485{}
486
487void QgsCategorizedSymbolRendererViewStyle::drawPrimitive( PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget ) const
488{
489 if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
490 {
491 QStyleOption opt( *option );
492 opt.rect.setLeft( 0 );
493 // draw always as line above, because we move item to that index
494 opt.rect.setHeight( 0 );
495 if ( widget ) opt.rect.setRight( widget->width() );
496 QProxyStyle::drawPrimitive( element, &opt, painter, widget );
497 return;
498 }
499 QProxyStyle::drawPrimitive( element, option, painter, widget );
500}
501
502
503QgsCategorizedRendererViewItemDelegate::QgsCategorizedRendererViewItemDelegate( QgsFieldExpressionWidget *expressionWidget, QObject *parent )
504 : QStyledItemDelegate( parent )
505 , mFieldExpressionWidget( expressionWidget )
506{
507}
508
509QWidget *QgsCategorizedRendererViewItemDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const
510{
511 QMetaType::Type userType { static_cast<QMetaType::Type>( index.data( static_cast< int >( QgsCategorizedSymbolRendererWidget::CustomRole::Value ) ).userType() ) };
512
513 // In case of new values the type is not known
514 if ( userType == QMetaType::Type::QString && QgsVariantUtils::isNull( index.data( static_cast< int >( QgsCategorizedSymbolRendererWidget::CustomRole::Value ) ) ) )
515 {
516 bool isExpression;
517 bool isValid;
518 const QString fieldName { mFieldExpressionWidget->currentField( &isExpression, &isValid ) };
519 if ( ! fieldName.isEmpty() && mFieldExpressionWidget->layer() && mFieldExpressionWidget->layer()->fields().lookupField( fieldName ) != -1 )
520 {
521 userType = mFieldExpressionWidget->layer()->fields().field( fieldName ).type();
522 }
523 else if ( isExpression && isValid )
524 {
525 // Try to guess the type from the expression return value
526 QgsFeature feat;
527 if ( mFieldExpressionWidget->layer()->getFeatures().nextFeature( feat ) )
528 {
529 QgsExpressionContext expressionContext;
532 expressionContext.appendScope( mFieldExpressionWidget->layer()->createExpressionContextScope() );
533 expressionContext.setFeature( feat );
534 QgsExpression exp { mFieldExpressionWidget->expression() };
535 const QVariant value = exp.evaluate( &expressionContext );
536 if ( !exp.hasEvalError() )
537 {
538 userType = static_cast<QMetaType::Type>( value.userType() );
539 }
540 }
541 }
542 }
543
544 QgsDoubleSpinBox *editor = nullptr;
545 switch ( userType )
546 {
547 case QMetaType::Type::Double:
548 {
549 editor = new QgsDoubleSpinBox( parent );
550 bool ok;
551 const QVariant value = index.data( static_cast< int >( QgsCategorizedSymbolRendererWidget::CustomRole::Value ) );
552 int decimals {2};
553 if ( value.toDouble( &ok ); ok )
554 {
555 const QString strVal { value.toString() };
556 const int dotPosition( strVal.indexOf( '.' ) );
557 if ( dotPosition >= 0 )
558 {
559 decimals = std::max<int>( 2, strVal.length() - dotPosition - 1 );
560 }
561 }
562 editor->setDecimals( decimals );
563 editor->setClearValue( 0 );
564 editor->setMaximum( std::numeric_limits<double>::max() );
565 editor->setMinimum( std::numeric_limits<double>::lowest() );
566 break;
567 }
568 case QMetaType::Type::Int:
569 {
570 editor = new QgsDoubleSpinBox( parent );
571 editor->setDecimals( 0 );
572 editor->setClearValue( 0 );
573 editor->setMaximum( std::numeric_limits<int>::max() );
574 editor->setMinimum( std::numeric_limits<int>::min() );
575 break;
576 }
577 case QMetaType::Type::QChar:
578 {
579 editor = new QgsDoubleSpinBox( parent );
580 editor->setDecimals( 0 );
581 editor->setClearValue( 0 );
582 editor->setMaximum( std::numeric_limits<char>::max() );
583 editor->setMinimum( std::numeric_limits<char>::min() );
584 break;
585 }
586 case QMetaType::Type::UInt:
587 {
588 editor = new QgsDoubleSpinBox( parent );
589 editor->setDecimals( 0 );
590 editor->setClearValue( 0 );
591 editor->setMaximum( std::numeric_limits<unsigned int>::max() );
592 editor->setMinimum( 0 );
593 break;
594 }
595 case QMetaType::Type::LongLong:
596 {
597 editor = new QgsDoubleSpinBox( parent );
598 editor->setDecimals( 0 );
599 editor->setClearValue( 0 );
600 editor->setMaximum( static_cast<double>( std::numeric_limits<qlonglong>::max() ) );
601 editor->setMinimum( std::numeric_limits<qlonglong>::min() );
602 break;
603 }
604 case QMetaType::Type::ULongLong:
605 {
606 editor = new QgsDoubleSpinBox( parent );
607 editor->setDecimals( 0 );
608 editor->setClearValue( 0 );
609 editor->setMaximum( static_cast<double>( std::numeric_limits<unsigned long long>::max() ) );
610 editor->setMinimum( 0 );
611 break;
612 }
613 default:
614 break;
615 }
616 return editor ? editor : QStyledItemDelegate::createEditor( parent, option, index );
617}
618
620
621// ------------------------------ Widget ------------------------------------
626
628 : QgsRendererWidget( layer, style )
629 , mContextMenu( new QMenu( this ) )
630{
631
632 // try to recognize the previous renderer
633 // (null renderer means "no previous renderer")
634 if ( renderer )
635 {
637 }
638 if ( !mRenderer )
639 {
640 mRenderer = std::make_unique< QgsCategorizedSymbolRenderer >( QString(), QgsCategoryList() );
641 if ( renderer )
643 }
644
645 const QString attrName = mRenderer->classAttribute();
646 mOldClassificationAttribute = attrName;
647
648 // setup user interface
649 setupUi( this );
650 layout()->setContentsMargins( 0, 0, 0, 0 );
651
652 mExpressionWidget->setLayer( mLayer );
653 btnChangeCategorizedSymbol->setLayer( mLayer );
654 btnChangeCategorizedSymbol->registerExpressionContextGenerator( this );
655
656 // initiate color ramp button to random
657 btnColorRamp->setShowRandomColorRamp( true );
658
659 // set project default color ramp
660 std::unique_ptr< QgsColorRamp > colorRamp( QgsProject::instance()->styleSettings()->defaultColorRamp() );
661 if ( colorRamp )
662 {
663 btnColorRamp->setColorRamp( colorRamp.get() );
664 }
665 else
666 {
667 btnColorRamp->setRandomColorRamp();
668 }
669
671 if ( mCategorizedSymbol )
672 {
673 btnChangeCategorizedSymbol->setSymbolType( mCategorizedSymbol->type() );
674 btnChangeCategorizedSymbol->setSymbol( mCategorizedSymbol->clone() );
675 }
676
677 mModel = new QgsCategorizedSymbolRendererModel( this, screen() );
678 mModel->setRenderer( mRenderer.get() );
679
680 // update GUI from renderer
682
683 viewCategories->setModel( mModel );
684 viewCategories->resizeColumnToContents( 0 );
685 viewCategories->resizeColumnToContents( 1 );
686 viewCategories->resizeColumnToContents( 2 );
687 viewCategories->setItemDelegateForColumn( 1, new QgsCategorizedRendererViewItemDelegate( mExpressionWidget, viewCategories ) );
688
689 viewCategories->setStyle( new QgsCategorizedSymbolRendererViewStyle( viewCategories ) );
690 connect( viewCategories->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsCategorizedSymbolRendererWidget::selectionChanged );
691
692 connect( mModel, &QgsCategorizedSymbolRendererModel::rowsMoved, this, &QgsCategorizedSymbolRendererWidget::rowsMoved );
693 connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsPanelWidget::widgetChanged );
694
695 connect( mExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsCategorizedSymbolRendererWidget::categoryColumnChanged );
696
697 connect( viewCategories, &QAbstractItemView::doubleClicked, this, &QgsCategorizedSymbolRendererWidget::categoriesDoubleClicked );
698 connect( viewCategories, &QTreeView::customContextMenuRequested, this, &QgsCategorizedSymbolRendererWidget::showContextMenu );
699
700 connect( btnChangeCategorizedSymbol, &QgsSymbolButton::changed, this, &QgsCategorizedSymbolRendererWidget::updateSymbolsFromButton );
701
702 connect( btnAddCategories, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::addCategories );
703 connect( btnDeleteCategories, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::deleteCategories );
704 connect( btnDeleteAllCategories, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::deleteAllCategories );
705 connect( btnAddCategory, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::addCategory );
706
708
709 // menus for data-defined rotation/size
710 QMenu *advMenu = new QMenu;
711
712 advMenu->addAction( tr( "Match to Saved Symbols" ), this, &QgsCategorizedSymbolRendererWidget::matchToSymbolsFromLibrary );
713 advMenu->addAction( tr( "Match to Symbols from File…" ), this, &QgsCategorizedSymbolRendererWidget::matchToSymbolsFromXml );
714 mActionLevels = advMenu->addAction( tr( "Symbol Levels…" ), this, &QgsCategorizedSymbolRendererWidget::showSymbolLevels );
716 {
717 QAction *actionDdsLegend = advMenu->addAction( tr( "Data-defined Size Legend…" ) );
718 // only from Qt 5.6 there is convenience addAction() with new style connection
719 connect( actionDdsLegend, &QAction::triggered, this, &QgsCategorizedSymbolRendererWidget::dataDefinedSizeLegend );
720 }
721
722 btnAdvanced->setMenu( advMenu );
723
724 mExpressionWidget->registerExpressionContextGenerator( this );
725
726 mMergeCategoriesAction = new QAction( tr( "Merge Categories" ), this );
727 connect( mMergeCategoriesAction, &QAction::triggered, this, &QgsCategorizedSymbolRendererWidget::mergeSelectedCategories );
728 mUnmergeCategoriesAction = new QAction( tr( "Unmerge Categories" ), this );
729 connect( mUnmergeCategoriesAction, &QAction::triggered, this, &QgsCategorizedSymbolRendererWidget::unmergeSelectedCategories );
730
731 connect( mContextMenu, &QMenu::aboutToShow, this, [ = ]
732 {
733 const std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
734 mPasteSymbolAction->setEnabled( static_cast< bool >( tempSymbol ) );
735 } );
736}
737
742
744{
745 // Note: This assumes that the signals for UI element changes have not
746 // yet been connected, so that the updates to color ramp, symbol, etc
747 // don't override existing customizations.
748
749 //mModel->setRenderer ( mRenderer ); // necessary?
750
751 // set column
752 const QString attrName = mRenderer->classAttribute();
753 mExpressionWidget->setField( attrName );
754
755 // set source symbol
756 if ( mRenderer->sourceSymbol() )
757 {
758 mCategorizedSymbol.reset( mRenderer->sourceSymbol()->clone() );
759 whileBlocking( btnChangeCategorizedSymbol )->setSymbol( mCategorizedSymbol->clone() );
760 }
761
762 // if a color ramp attached to the renderer, enable the color ramp button
763 if ( mRenderer->sourceColorRamp() )
764 {
765 btnColorRamp->setColorRamp( mRenderer->sourceColorRamp() );
766 }
767}
768
773
775{
777 btnChangeCategorizedSymbol->setMapCanvas( context.mapCanvas() );
778 btnChangeCategorizedSymbol->setMessageBar( context.messageBar() );
779}
780
782{
783 delete mActionLevels;
784 mActionLevels = nullptr;
785}
786
788{
789 const QList<int> selectedCats = selectedCategories();
790
791 if ( !selectedCats.isEmpty() )
792 {
793 QgsSymbol *newSymbol = mCategorizedSymbol->clone();
794 QgsSymbolSelectorDialog dlg( newSymbol, mStyle, mLayer, this );
795 dlg.setContext( context() );
796 if ( !dlg.exec() )
797 {
798 delete newSymbol;
799 return;
800 }
801
802 const auto constSelectedCats = selectedCats;
803 for ( const int idx : constSelectedCats )
804 {
805 const QgsRendererCategory category = mRenderer->categories().value( idx );
806
807 QgsSymbol *newCatSymbol = newSymbol->clone();
808 newCatSymbol->setColor( mRenderer->categories()[idx].symbol()->color() );
809 mRenderer->updateCategorySymbol( idx, newCatSymbol );
810 }
811 }
812}
813
815{
817 std::unique_ptr<QgsSymbol> newSymbol( mCategorizedSymbol->clone() );
818 if ( panel && panel->dockMode() )
819 {
821 widget->setContext( mContext );
822 connect( widget, &QgsPanelWidget::widgetChanged, this, [ = ] { updateSymbolsFromWidget( widget ); } );
823 openPanel( widget );
824 }
825 else
826 {
827 QgsSymbolSelectorDialog dlg( newSymbol.get(), mStyle, mLayer, panel );
828 dlg.setContext( mContext );
829 if ( !dlg.exec() || !newSymbol )
830 {
831 return;
832 }
833
834 mCategorizedSymbol = std::move( newSymbol );
836 }
837}
838
839
843
845{
846 mRenderer->setClassAttribute( field );
847 emit widgetChanged();
848}
849
851{
852 if ( idx.isValid() && idx.column() == 0 )
854}
855
857{
858 const QgsRendererCategory category = mRenderer->categories().value( currentCategoryRow() );
859
860 std::unique_ptr< QgsSymbol > symbol;
861
862 if ( auto *lSymbol = category.symbol() )
863 {
864 symbol.reset( lSymbol->clone() );
865 }
866 else
867 {
868 symbol.reset( QgsSymbol::defaultSymbol( mLayer->geometryType() ) );
869 }
870
872 if ( panel && panel->dockMode() )
873 {
875 widget->setContext( mContext );
876 widget->setPanelTitle( category.label() );
877 connect( widget, &QgsPanelWidget::widgetChanged, this, [ = ] { updateSymbolsFromWidget( widget ); } );
878 openPanel( widget );
879 }
880 else
881 {
882 QgsSymbolSelectorDialog dlg( symbol.get(), mStyle, mLayer, panel );
883 dlg.setContext( mContext );
884 if ( !dlg.exec() || !symbol )
885 {
886 return;
887 }
888
889 mCategorizedSymbol = std::move( symbol );
891 }
892}
893
894
896{
897 const QString attrName = mExpressionWidget->currentField();
898 const int idx = mLayer->fields().lookupField( attrName );
899 QList<QVariant> uniqueValues;
900 if ( idx == -1 )
901 {
902 // Lets assume it's an expression
903 QgsExpression *expression = new QgsExpression( attrName );
909
910 expression->prepare( &context );
912 QgsFeature feature;
913 while ( fit.nextFeature( feature ) )
914 {
915 context.setFeature( feature );
916 const QVariant value = expression->evaluate( &context );
917 if ( uniqueValues.contains( value ) )
918 continue;
919 uniqueValues << value;
920 }
921 }
922 else
923 {
924 uniqueValues = qgis::setToList( mLayer->uniqueValues( idx ) );
925 }
926
927 // ask to abort if too many classes
928 if ( uniqueValues.size() >= 1000 )
929 {
930 const int res = QMessageBox::warning( nullptr, tr( "Classify Categories" ),
931 tr( "High number of classes. Classification would yield %n entries which might not be expected. Continue?", nullptr, uniqueValues.size() ),
932 QMessageBox::Ok | QMessageBox::Cancel,
933 QMessageBox::Cancel );
934 if ( res == QMessageBox::Cancel )
935 {
936 return;
937 }
938 }
939
940#if 0
941 DlgAddCategories dlg( mStyle, createDefaultSymbol(), unique_vals, this );
942 if ( !dlg.exec() )
943 return;
944#endif
945
947 bool deleteExisting = false;
948
949 if ( !mOldClassificationAttribute.isEmpty() &&
950 attrName != mOldClassificationAttribute &&
951 !mRenderer->categories().isEmpty() )
952 {
953 const int res = QMessageBox::question( this,
954 tr( "Delete Classification" ),
955 tr( "The classification field was changed from '%1' to '%2'.\n"
956 "Should the existing classes be deleted before classification?" )
957 .arg( mOldClassificationAttribute, attrName ),
958 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel );
959 if ( res == QMessageBox::Cancel )
960 {
961 return;
962 }
963
964 deleteExisting = ( res == QMessageBox::Yes );
965 }
966
967 // First element to apply coloring to
968 bool keepExistingColors = false;
969 if ( !deleteExisting )
970 {
971 QgsCategoryList prevCats = mRenderer->categories();
972 keepExistingColors = !prevCats.isEmpty();
973 QgsRandomColorRamp randomColors;
974 if ( keepExistingColors && btnColorRamp->isRandomColorRamp() )
975 randomColors.setTotalColorCount( cats.size() );
976 for ( int i = 0; i < cats.size(); ++i )
977 {
978 bool contains = false;
979 const QVariant value = cats.at( i ).value();
980 for ( int j = 0; j < prevCats.size() && !contains; ++j )
981 {
982 const QVariant prevCatValue = prevCats.at( j ).value();
983 if ( prevCatValue.userType() == QMetaType::Type::QVariantList )
984 {
985 const QVariantList list = prevCatValue.toList();
986 for ( const QVariant &v : list )
987 {
988 if ( v == value )
989 {
990 contains = true;
991 break;
992 }
993 }
994 }
995 else
996 {
997 if ( prevCats.at( j ).value() == value )
998 {
999 contains = true;
1000 }
1001 }
1002 if ( contains )
1003 break;
1004 }
1005
1006 if ( !contains )
1007 {
1008 if ( keepExistingColors && btnColorRamp->isRandomColorRamp() )
1009 {
1010 // insure that append symbols have random colors
1011 cats.at( i ).symbol()->setColor( randomColors.color( i ) );
1012 }
1013 prevCats.append( cats.at( i ) );
1014 }
1015 }
1016 cats = prevCats;
1017 }
1018
1019 mOldClassificationAttribute = attrName;
1020
1021 // TODO: if not all categories are desired, delete some!
1022 /*
1023 if (not dlg.readAllCats.isChecked())
1024 {
1025 cats2 = {}
1026 for item in dlg.listCategories.selectedItems():
1027 for k,c in cats.iteritems():
1028 if item.text() == k.toString():
1029 break
1030 cats2[k] = c
1031 cats = cats2
1032 }
1033 */
1034
1035 // recreate renderer
1036 std::unique_ptr< QgsCategorizedSymbolRenderer > r = std::make_unique< QgsCategorizedSymbolRenderer >( attrName, cats );
1037 r->setSourceSymbol( mCategorizedSymbol->clone() );
1038 std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
1039 if ( ramp )
1040 r->setSourceColorRamp( ramp->clone() );
1041
1042 if ( mModel )
1043 {
1044 mModel->setRenderer( r.get() );
1045 }
1046 mRenderer = std::move( r );
1047 if ( ! keepExistingColors && ramp )
1049 emit widgetChanged();
1050}
1051
1053{
1054 if ( !btnColorRamp->isNull() )
1055 {
1056 mRenderer->updateColorRamp( btnColorRamp->colorRamp() );
1057 }
1058 mModel->updateSymbology();
1059}
1060
1062{
1063 const QModelIndex idx = viewCategories->selectionModel()->currentIndex();
1064 if ( !idx.isValid() )
1065 return -1;
1066 return idx.row();
1067}
1068
1070{
1071 QList<int> rows;
1072 const QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
1073
1074 const auto constSelectedRows = selectedRows;
1075 for ( const QModelIndex &r : constSelectedRows )
1076 {
1077 if ( r.isValid() )
1078 {
1079 rows.append( r.row() );
1080 }
1081 }
1082 return rows;
1083}
1084
1086{
1087 const QList<int> categoryIndexes = selectedCategories();
1088 mModel->deleteRows( categoryIndexes );
1089 emit widgetChanged();
1090}
1091
1093{
1094 mModel->removeAllRows();
1095 emit widgetChanged();
1096}
1097
1099{
1100 if ( !mModel ) return;
1102 const QgsRendererCategory cat( QString(), symbol, QString(), true );
1103 mModel->addCategory( cat );
1104 emit widgetChanged();
1105}
1106
1108{
1109 QList<QgsSymbol *> selectedSymbols;
1110
1111 QItemSelectionModel *m = viewCategories->selectionModel();
1112 const QModelIndexList selectedIndexes = m->selectedRows( 1 );
1113
1114 if ( !selectedIndexes.isEmpty() )
1115 {
1116 const QgsCategoryList &categories = mRenderer->categories();
1117 QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
1118 for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
1119 {
1120 const int row = ( *indexIt ).row();
1121 QgsSymbol *s = categories[row].symbol();
1122 if ( s )
1123 {
1124 selectedSymbols.append( s );
1125 }
1126 }
1127 }
1128 return selectedSymbols;
1129}
1130
1132{
1133 QgsCategoryList cl;
1134
1135 QItemSelectionModel *m = viewCategories->selectionModel();
1136 const QModelIndexList selectedIndexes = m->selectedRows( 1 );
1137
1138 if ( !selectedIndexes.isEmpty() )
1139 {
1140 QModelIndexList::const_iterator indexIt = selectedIndexes.constBegin();
1141 for ( ; indexIt != selectedIndexes.constEnd(); ++indexIt )
1142 {
1143 cl.append( mModel->category( *indexIt ) );
1144 }
1145 }
1146 return cl;
1147}
1148
1154
1159
1161{
1162 viewCategories->selectionModel()->clear();
1163}
1164
1166{
1167 const int matched = matchToSymbols( QgsStyle::defaultStyle() );
1168 if ( matched > 0 )
1169 {
1170 QMessageBox::information( this, tr( "Matched Symbols" ),
1171 tr( "Matched %n categories to symbols.", nullptr, matched ) );
1172 }
1173 else
1174 {
1175 QMessageBox::warning( this, tr( "Matched Symbols" ),
1176 tr( "No categories could be matched to symbols in library." ) );
1177 }
1178}
1179
1181{
1182 if ( !mLayer || !style )
1183 return 0;
1184
1188
1189 QVariantList unmatchedCategories;
1190 QStringList unmatchedSymbols;
1191 const int matched = mRenderer->matchToSymbols( style, type, unmatchedCategories, unmatchedSymbols );
1192
1193 mModel->updateSymbology();
1194 return matched;
1195}
1196
1198{
1199 QgsSettings settings;
1200 const QString openFileDir = settings.value( QStringLiteral( "UI/lastMatchToSymbolsDir" ), QDir::homePath() ).toString();
1201
1202 const QString fileName = QFileDialog::getOpenFileName( this, tr( "Match to Symbols from File" ), openFileDir,
1203 tr( "XML files (*.xml *.XML)" ) );
1204 if ( fileName.isEmpty() )
1205 {
1206 return;
1207 }
1208
1209 const QFileInfo openFileInfo( fileName );
1210 settings.setValue( QStringLiteral( "UI/lastMatchToSymbolsDir" ), openFileInfo.absolutePath() );
1211
1212 QgsStyle importedStyle;
1213 if ( !importedStyle.importXml( fileName ) )
1214 {
1215 QMessageBox::warning( this, tr( "Match to Symbols from File" ),
1216 tr( "An error occurred while reading file:\n%1" ).arg( importedStyle.errorString() ) );
1217 return;
1218 }
1219
1220 const int matched = matchToSymbols( &importedStyle );
1221 if ( matched > 0 )
1222 {
1223 QMessageBox::information( this, tr( "Match to Symbols from File" ),
1224 tr( "Matched %n categories to symbols from file.", nullptr, matched ) );
1225 }
1226 else
1227 {
1228 QMessageBox::warning( this, tr( "Match to Symbols from File" ),
1229 tr( "No categories could be matched to symbols in file." ) );
1230 }
1231}
1232
1234{
1235 for ( const QgsLegendSymbolItem &legendSymbol : levels )
1236 {
1237 QgsSymbol *sym = legendSymbol.symbol();
1238 for ( int layer = 0; layer < sym->symbolLayerCount(); layer++ )
1239 {
1240 mRenderer->setLegendSymbolItem( legendSymbol.ruleKey(), sym->clone() );
1241 }
1242 }
1243 mRenderer->setUsingSymbolLevels( enabled );
1244 mModel->updateSymbology();
1245 emit widgetChanged();
1246}
1247
1249{
1250 std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
1251 if ( !tempSymbol )
1252 return;
1253
1254 const QList<int> selectedCats = selectedCategories();
1255 if ( !selectedCats.isEmpty() )
1256 {
1257 for ( const int idx : selectedCats )
1258 {
1259 if ( mRenderer->categories().at( idx ).symbol()->type() != tempSymbol->type() )
1260 continue;
1261
1262 std::unique_ptr< QgsSymbol > newCatSymbol( tempSymbol->clone() );
1263 if ( selectedCats.count() > 1 )
1264 {
1265 //if updating multiple categories, retain the existing category colors
1266 newCatSymbol->setColor( mRenderer->categories().at( idx ).symbol()->color() );
1267 }
1268 mRenderer->updateCategorySymbol( idx, newCatSymbol.release() );
1269 }
1270 emit widgetChanged();
1271 }
1272}
1273
1274void QgsCategorizedSymbolRendererWidget::updateSymbolsFromWidget( QgsSymbolSelectorWidget *widget )
1275{
1276 mCategorizedSymbol.reset( widget->symbol()->clone() );
1277
1279}
1280
1281void QgsCategorizedSymbolRendererWidget::updateSymbolsFromButton()
1282{
1283 mCategorizedSymbol.reset( btnChangeCategorizedSymbol->symbol()->clone() );
1284
1286}
1287
1289{
1290 // When there is a selection, change the selected symbols only
1291 QItemSelectionModel *m = viewCategories->selectionModel();
1292 const QModelIndexList i = m->selectedRows();
1293
1294 if ( !i.isEmpty() )
1295 {
1296 const QList<int> selectedCats = selectedCategories();
1297
1298 if ( !selectedCats.isEmpty() )
1299 {
1300 const auto constSelectedCats = selectedCats;
1301 for ( const int idx : constSelectedCats )
1302 {
1303 QgsSymbol *newCatSymbol = mCategorizedSymbol->clone();
1304 if ( selectedCats.count() > 1 )
1305 {
1306 //if updating multiple categories, retain the existing category colors
1307 newCatSymbol->setColor( mRenderer->categories().at( idx ).symbol()->color() );
1308 }
1309 mRenderer->updateCategorySymbol( idx, newCatSymbol );
1310 }
1311 }
1312 }
1313 else
1314 {
1315 mRenderer->updateSymbols( mCategorizedSymbol.get() );
1316 }
1317
1318 mModel->updateSymbology();
1319 emit widgetChanged();
1320}
1321
1323{
1324 if ( !event )
1325 {
1326 return;
1327 }
1328
1329 if ( event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier )
1330 {
1331 mCopyBuffer.clear();
1332 mCopyBuffer = selectedCategoryList();
1333 }
1334 else if ( event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier )
1335 {
1336 QgsCategoryList::const_iterator rIt = mCopyBuffer.constBegin();
1337 for ( ; rIt != mCopyBuffer.constEnd(); ++rIt )
1338 {
1339 mModel->addCategory( *rIt );
1340 }
1341 }
1342}
1343
1345{
1346 QgsExpressionContext expContext;
1347 if ( auto *lMapCanvas = mContext.mapCanvas() )
1348 {
1349 expContext = lMapCanvas->createExpressionContext();
1350 }
1351 else
1352 {
1357 }
1358
1359 if ( auto *lVectorLayer = vectorLayer() )
1360 expContext << QgsExpressionContextUtils::layerScope( lVectorLayer );
1361
1362 // additional scopes
1363 const auto constAdditionalExpressionContextScopes = mContext.additionalExpressionContextScopes();
1364 for ( const QgsExpressionContextScope &scope : constAdditionalExpressionContextScopes )
1365 {
1366 expContext.appendScope( new QgsExpressionContextScope( scope ) );
1367 }
1368
1369 return expContext;
1370}
1371
1372void QgsCategorizedSymbolRendererWidget::dataDefinedSizeLegend()
1373{
1374 QgsMarkerSymbol *s = static_cast<QgsMarkerSymbol *>( mCategorizedSymbol.get() ); // this should be only enabled for marker symbols
1375 QgsDataDefinedSizeLegendWidget *panel = createDataDefinedSizeLegendWidget( s, mRenderer->dataDefinedSizeLegend() );
1376 if ( panel )
1377 {
1378 connect( panel, &QgsPanelWidget::widgetChanged, this, [ = ]
1379 {
1380 mRenderer->setDataDefinedSizeLegend( panel->dataDefinedSizeLegend() );
1381 emit widgetChanged();
1382 } );
1383 openPanel( panel ); // takes ownership of the panel
1384 }
1385}
1386
1387void QgsCategorizedSymbolRendererWidget::mergeSelectedCategories()
1388{
1389 const QgsCategoryList &categories = mRenderer->categories();
1390
1391 const QList<int> selectedCategoryIndexes = selectedCategories();
1392 QList< int > categoryIndexes;
1393
1394 // filter out "" entry
1395 for ( const int i : selectedCategoryIndexes )
1396 {
1397 const QVariant v = categories.at( i ).value();
1398
1399 if ( !v.isValid() || v == "" )
1400 {
1401 continue;
1402 }
1403
1404 categoryIndexes.append( i );
1405 }
1406
1407 if ( categoryIndexes.count() < 2 )
1408 return;
1409
1410 QStringList labels;
1411 QVariantList values;
1412 values.reserve( categoryIndexes.count() );
1413 labels.reserve( categoryIndexes.count() );
1414 for ( const int i : categoryIndexes )
1415 {
1416 const QVariant v = categories.at( i ).value();
1417
1418 if ( v.userType() == QMetaType::Type::QVariantList )
1419 {
1420 values.append( v.toList() );
1421 }
1422 else
1423 values << v;
1424
1425 labels << categories.at( i ).label();
1426 }
1427
1428 // modify first category (basically we "merge up" into the first selected category)
1429 mRenderer->updateCategoryLabel( categoryIndexes.at( 0 ), labels.join( ',' ) );
1430 mRenderer->updateCategoryValue( categoryIndexes.at( 0 ), values );
1431
1432 categoryIndexes.pop_front();
1433 mModel->deleteRows( categoryIndexes );
1434
1435 emit widgetChanged();
1436}
1437
1438void QgsCategorizedSymbolRendererWidget::unmergeSelectedCategories()
1439{
1440 const QList<int> categoryIndexes = selectedCategories();
1441 if ( categoryIndexes.isEmpty() )
1442 return;
1443
1444 const QgsCategoryList &categories = mRenderer->categories();
1445 for ( const int i : categoryIndexes )
1446 {
1447 const QVariant v = categories.at( i ).value();
1448 if ( v.userType() != QMetaType::Type::QVariantList )
1449 continue;
1450
1451 const QVariantList list = v.toList();
1452 for ( int j = 1; j < list.count(); ++j )
1453 {
1454 mModel->addCategory( QgsRendererCategory( list.at( j ), categories.at( i ).symbol()->clone(), list.at( j ).toString(), categories.at( i ).renderState() ) );
1455 }
1456 mRenderer->updateCategoryValue( i, list.at( 0 ) );
1457 mRenderer->updateCategoryLabel( i, list.at( 0 ).toString() );
1458 }
1459
1460 emit widgetChanged();
1461}
1462
1463void QgsCategorizedSymbolRendererWidget::showContextMenu( QPoint )
1464{
1465 mContextMenu->clear();
1466 const QList< QAction * > actions = contextMenu->actions();
1467 for ( QAction *act : actions )
1468 {
1469 mContextMenu->addAction( act );
1470 }
1471
1472 mContextMenu->addSeparator();
1473
1474 if ( viewCategories->selectionModel()->selectedRows().count() > 1 )
1475 {
1476 mContextMenu->addAction( mMergeCategoriesAction );
1477 }
1478 if ( viewCategories->selectionModel()->selectedRows().count() == 1 )
1479 {
1480 const QList<int> categoryIndexes = selectedCategories();
1481 const QgsCategoryList &categories = mRenderer->categories();
1482 const QVariant v = categories.at( categoryIndexes.at( 0 ) ).value();
1483 if ( v.userType() == QMetaType::Type::QVariantList )
1484 mContextMenu->addAction( mUnmergeCategoriesAction );
1485 }
1486 else if ( viewCategories->selectionModel()->selectedRows().count() > 1 )
1487 {
1488 mContextMenu->addAction( mUnmergeCategoriesAction );
1489 }
1490
1491 mContextMenu->exec( QCursor::pos() );
1492}
1493
1494void QgsCategorizedSymbolRendererWidget::selectionChanged( const QItemSelection &, const QItemSelection & )
1495{
1496 const QList<int> selectedCats = selectedCategories();
1497 if ( !selectedCats.isEmpty() )
1498 {
1499 whileBlocking( btnChangeCategorizedSymbol )->setSymbol( mRenderer->categories().at( selectedCats.at( 0 ) ).symbol()->clone() );
1500 }
1501 else if ( mRenderer->sourceSymbol() )
1502 {
1503 whileBlocking( btnChangeCategorizedSymbol )->setSymbol( mRenderer->sourceSymbol()->clone() );
1504 }
1505 btnChangeCategorizedSymbol->setDialogTitle( selectedCats.size() == 1 ? mRenderer->categories().at( selectedCats.at( 0 ) ).label() : tr( "Symbol Settings" ) );
1506}
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.
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...
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:352
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:5862
QList< QgsRendererCategory > QgsCategoryList
QList< QgsLegendSymbolItem > QgsLegendSymbolList
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39