Skip to content

Commit 7b245d1

Browse files
committedJan 8, 2019
[FEATURE] Add option to merge categories in categorized renderer
This allows users to select multiple existing categories and group them into a single category, which applies to any of the values from the selection. This allows simpler styling of layers with a large number of categories, where it may be possible to group numerous distinct categories into a smaller, more managable set of categories which apply to multiple values. The option is available from the right click context menu in the categories list view, whenever multiple categories are selected. Sponsored by SMEC/SJ
1 parent 224df0a commit 7b245d1

File tree

5 files changed

+476
-37
lines changed

5 files changed

+476
-37
lines changed
 

‎src/gui/symbology/qgscategorizedsymbolrendererwidget.cpp

Lines changed: 180 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -125,40 +125,97 @@ QVariant QgsCategorizedSymbolRendererModel::data( const QModelIndex &index, int
125125

126126
const QgsRendererCategory category = mRenderer->categories().value( index.row() );
127127

128-
if ( role == Qt::CheckStateRole && index.column() == 0 )
128+
switch ( role )
129129
{
130-
return category.renderState() ? Qt::Checked : Qt::Unchecked;
131-
}
132-
else if ( role == Qt::DisplayRole || role == Qt::ToolTipRole )
133-
{
134-
switch ( index.column() )
130+
case Qt::CheckStateRole:
135131
{
136-
case 1:
137-
return category.value().toString();
138-
case 2:
139-
return category.label();
140-
default:
141-
return QVariant();
132+
if ( index.column() == 0 )
133+
{
134+
return category.renderState() ? Qt::Checked : Qt::Unchecked;
135+
}
136+
break;
142137
}
143-
}
144-
else if ( role == Qt::DecorationRole && index.column() == 0 && category.symbol() )
145-
{
146-
return QgsSymbolLayerUtils::symbolPreviewIcon( category.symbol(), QSize( 16, 16 ) );
147-
}
148-
else if ( role == Qt::TextAlignmentRole )
149-
{
150-
return ( index.column() == 0 ) ? Qt::AlignHCenter : Qt::AlignLeft;
151-
}
152-
else if ( role == Qt::EditRole )
153-
{
154-
switch ( index.column() )
138+
139+
case Qt::DisplayRole:
140+
case Qt::ToolTipRole:
141+
{
142+
switch ( index.column() )
143+
{
144+
case 1:
145+
{
146+
if ( category.value().type() == QVariant::List )
147+
{
148+
QStringList res;
149+
const QVariantList list = category.value().toList();
150+
res.reserve( list.size() );
151+
for ( const QVariant &v : list )
152+
res << v.toString();
153+
154+
return res.join( ';' );
155+
}
156+
else
157+
{
158+
return category.value().toString();
159+
}
160+
}
161+
case 2:
162+
return category.label();
163+
}
164+
break;
165+
}
166+
167+
case Qt::DecorationRole:
168+
{
169+
if ( index.column() == 0 && category.symbol() )
170+
{
171+
return QgsSymbolLayerUtils::symbolPreviewIcon( category.symbol(), QSize( 16, 16 ) );
172+
}
173+
break;
174+
}
175+
176+
case Qt::ForegroundRole:
155177
{
156-
case 1:
157-
return category.value();
158-
case 2:
159-
return category.label();
160-
default:
161-
return QVariant();
178+
QBrush brush( qApp->palette().color( QPalette::Text ), Qt::SolidPattern );
179+
if ( index.column() == 1 && category.value().type() == QVariant::List )
180+
{
181+
QColor fadedTextColor = brush.color();
182+
fadedTextColor.setAlpha( 128 );
183+
brush.setColor( fadedTextColor );
184+
}
185+
return brush;
186+
}
187+
188+
case Qt::TextAlignmentRole:
189+
{
190+
return ( index.column() == 0 ) ? Qt::AlignHCenter : Qt::AlignLeft;
191+
}
192+
193+
case Qt::EditRole:
194+
{
195+
switch ( index.column() )
196+
{
197+
case 1:
198+
{
199+
if ( category.value().type() == QVariant::List )
200+
{
201+
QStringList res;
202+
const QVariantList list = category.value().toList();
203+
res.reserve( list.size() );
204+
for ( const QVariant &v : list )
205+
res << v.toString();
206+
207+
return res.join( ';' );
208+
}
209+
else
210+
{
211+
return category.value();
212+
}
213+
}
214+
215+
case 2:
216+
return category.label();
217+
}
218+
break;
162219
}
163220
}
164221

@@ -194,6 +251,20 @@ bool QgsCategorizedSymbolRendererModel::setData( const QModelIndex &index, const
194251
case QVariant::Double:
195252
val = value.toDouble();
196253
break;
254+
case QVariant::List:
255+
{
256+
const QStringList parts = value.toString().split( ';' );
257+
QVariantList list;
258+
list.reserve( parts.count() );
259+
for ( const QString &p : parts )
260+
list << p;
261+
262+
if ( list.count() == 1 )
263+
val = list.at( 0 );
264+
else
265+
val = list;
266+
break;
267+
}
197268
default:
198269
val = value.toString();
199270
break;
@@ -392,7 +463,7 @@ QgsRendererWidget *QgsCategorizedSymbolRendererWidget::create( QgsVectorLayer *l
392463

393464
QgsCategorizedSymbolRendererWidget::QgsCategorizedSymbolRendererWidget( QgsVectorLayer *layer, QgsStyle *style, QgsFeatureRenderer *renderer )
394465
: QgsRendererWidget( layer, style )
395-
466+
, mContextMenu( new QMenu( this ) )
396467
{
397468

398469
// try to recognize the previous renderer
@@ -450,7 +521,7 @@ QgsCategorizedSymbolRendererWidget::QgsCategorizedSymbolRendererWidget( QgsVecto
450521
connect( mExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsCategorizedSymbolRendererWidget::categoryColumnChanged );
451522

452523
connect( viewCategories, &QAbstractItemView::doubleClicked, this, &QgsCategorizedSymbolRendererWidget::categoriesDoubleClicked );
453-
connect( viewCategories, &QTreeView::customContextMenuRequested, this, &QgsCategorizedSymbolRendererWidget::contextMenuViewCategories );
524+
connect( viewCategories, &QTreeView::customContextMenuRequested, this, &QgsCategorizedSymbolRendererWidget::showContextMenu );
454525

455526
connect( btnChangeCategorizedSymbol, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::changeCategorizedSymbol );
456527
connect( btnAddCategories, &QAbstractButton::clicked, this, &QgsCategorizedSymbolRendererWidget::addCategories );
@@ -476,6 +547,9 @@ QgsCategorizedSymbolRendererWidget::QgsCategorizedSymbolRendererWidget( QgsVecto
476547
btnAdvanced->setMenu( advMenu );
477548

478549
mExpressionWidget->registerExpressionContextGenerator( this );
550+
551+
mMergeCategoriesAction = new QAction( tr( "Merge Categories" ), this );
552+
connect( mMergeCategoriesAction, &QAction::triggered, this, &QgsCategorizedSymbolRendererWidget::mergeClicked );
479553
}
480554

481555
QgsCategorizedSymbolRendererWidget::~QgsCategorizedSymbolRendererWidget()
@@ -720,11 +794,28 @@ void QgsCategorizedSymbolRendererWidget::addCategories()
720794
QVariant value = cats.at( i ).value();
721795
for ( int j = 0; j < prevCats.size() && !contains; ++j )
722796
{
723-
if ( prevCats.at( j ).value() == value )
797+
const QVariant prevCatValue = prevCats.at( j ).value();
798+
if ( prevCatValue.type() == QVariant::List )
724799
{
725-
contains = true;
726-
break;
800+
const QVariantList list = prevCatValue.toList();
801+
for ( const QVariant &v : list )
802+
{
803+
if ( v == value )
804+
{
805+
contains = true;
806+
break;
807+
}
808+
}
809+
}
810+
else
811+
{
812+
if ( prevCats.at( j ).value() == value )
813+
{
814+
contains = true;
815+
}
727816
}
817+
if ( contains )
818+
break;
728819
}
729820

730821
if ( !contains )
@@ -1061,3 +1152,57 @@ void QgsCategorizedSymbolRendererWidget::dataDefinedSizeLegend()
10611152
openPanel( panel ); // takes ownership of the panel
10621153
}
10631154
}
1155+
1156+
void QgsCategorizedSymbolRendererWidget::mergeClicked()
1157+
{
1158+
QList<int> categoryIndexes = selectedCategories();
1159+
if ( categoryIndexes.count() < 2 )
1160+
return;
1161+
1162+
const QgsCategoryList &categories = mRenderer->categories();
1163+
1164+
QStringList labels;
1165+
QVariantList values;
1166+
values.reserve( categoryIndexes.count() );
1167+
labels.reserve( categoryIndexes.count() );
1168+
for ( int i : categoryIndexes )
1169+
{
1170+
QVariant v = categories.at( i ).value();
1171+
if ( v.type() == QVariant::List )
1172+
{
1173+
values.append( v.toList() );
1174+
}
1175+
else
1176+
values << v;
1177+
1178+
labels << categories.at( i ).label();
1179+
}
1180+
1181+
// modify first category (basically we "merge up" into the first selected category)
1182+
mRenderer->updateCategoryLabel( categoryIndexes.at( 0 ), labels.join( ',' ) );
1183+
mRenderer->updateCategoryValue( categoryIndexes.at( 0 ), values );
1184+
1185+
categoryIndexes.pop_front();
1186+
mModel->deleteRows( categoryIndexes );
1187+
1188+
emit widgetChanged();
1189+
}
1190+
1191+
void QgsCategorizedSymbolRendererWidget::showContextMenu( QPoint )
1192+
{
1193+
mContextMenu->clear();
1194+
const QList< QAction * > actions = contextMenu->actions();
1195+
for ( QAction *act : actions )
1196+
{
1197+
mContextMenu->addAction( act );
1198+
}
1199+
1200+
mContextMenu->addSeparator();
1201+
1202+
if ( viewCategories->selectionModel()->selectedRows().count() > 1 )
1203+
{
1204+
mContextMenu->addAction( mMergeCategoriesAction );
1205+
}
1206+
1207+
mContextMenu->exec( QCursor::pos() );
1208+
}

‎src/gui/symbology/qgscategorizedsymbolrendererwidget.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ class GUI_EXPORT QgsCategorizedSymbolRendererWidget : public QgsRendererWidget,
153153
void cleanUpSymbolSelector( QgsPanelWidget *container );
154154
void updateSymbolsFromWidget();
155155
void dataDefinedSizeLegend();
156+
void mergeClicked();
157+
void showContextMenu( QPoint p );
156158

157159
protected:
158160

@@ -191,8 +193,12 @@ class GUI_EXPORT QgsCategorizedSymbolRendererWidget : public QgsRendererWidget,
191193
private:
192194
QString mOldClassificationAttribute;
193195
QgsCategoryList mCopyBuffer;
196+
QMenu *mContextMenu = nullptr;
197+
QAction *mMergeCategoriesAction = nullptr;
194198

195199
QgsExpressionContext createExpressionContext() const override;
200+
201+
friend class TestQgsCategorizedRendererWidget;
196202
};
197203

198204
#endif // QGSCATEGORIZEDSYMBOLRENDERERWIDGET_H

‎src/ui/qgscategorizedsymbolrendererwidget.ui

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<rect>
77
<x>0</x>
88
<y>0</y>
9-
<width>378</width>
9+
<width>424</width>
1010
<height>368</height>
1111
</rect>
1212
</property>
@@ -216,15 +216,47 @@
216216
<tabstop>mExpressionWidget</tabstop>
217217
<tabstop>btnChangeCategorizedSymbol</tabstop>
218218
<tabstop>btnColorRamp</tabstop>
219+
<tabstop>viewCategories</tabstop>
219220
<tabstop>btnAddCategories</tabstop>
220221
<tabstop>btnAddCategory</tabstop>
221222
<tabstop>btnDeleteCategories</tabstop>
222223
<tabstop>btnDeleteAllCategories</tabstop>
223224
<tabstop>btnAdvanced</tabstop>
224-
<tabstop>viewCategories</tabstop>
225225
</tabstops>
226226
<resources>
227227
<include location="../../images/images.qrc"/>
228+
<include location="../../images/images.qrc"/>
229+
<include location="../../images/images.qrc"/>
230+
<include location="../../images/images.qrc"/>
231+
<include location="../../images/images.qrc"/>
232+
<include location="../../images/images.qrc"/>
233+
<include location="../../images/images.qrc"/>
234+
<include location="../../images/images.qrc"/>
235+
<include location="../../images/images.qrc"/>
236+
<include location="../../images/images.qrc"/>
237+
<include location="../../images/images.qrc"/>
238+
<include location="../../images/images.qrc"/>
239+
<include location="../../images/images.qrc"/>
240+
<include location="../../images/images.qrc"/>
241+
<include location="../../images/images.qrc"/>
242+
<include location="../../images/images.qrc"/>
243+
<include location="../../images/images.qrc"/>
244+
<include location="../../images/images.qrc"/>
245+
<include location="../../images/images.qrc"/>
246+
<include location="../../images/images.qrc"/>
247+
<include location="../../images/images.qrc"/>
248+
<include location="../../images/images.qrc"/>
249+
<include location="../../images/images.qrc"/>
250+
<include location="../../images/images.qrc"/>
251+
<include location="../../images/images.qrc"/>
252+
<include location="../../images/images.qrc"/>
253+
<include location="../../images/images.qrc"/>
254+
<include location="../../images/images.qrc"/>
255+
<include location="../../images/images.qrc"/>
256+
<include location="../../images/images.qrc"/>
257+
<include location="../../images/images.qrc"/>
258+
<include location="../../images/images.qrc"/>
259+
<include location="../../images/images.qrc"/>
228260
</resources>
229261
<connections/>
230262
</ui>

‎tests/src/gui/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ ADD_QGIS_TEST(edittooltest testqgsmaptooledit.cpp)
121121
#ADD_EXECUTABLE(qgis_rendererv2gui ${rendererv2gui_SRCS} ${rendererv2gui_MOC_SRCS})
122122

123123
#ADD_QGIS_TEST(histogramtest testqgsrasterhistogram.cpp)
124+
ADD_QGIS_TEST(categorizedrendererwidget testqgscategorizedrendererwidget.cpp)
124125
ADD_QGIS_TEST(doublespinbox testqgsdoublespinbox.cpp)
125126
ADD_QGIS_TEST(dualviewtest testqgsdualview.cpp)
126127
ADD_QGIS_TEST(attributeformtest testqgsattributeform.cpp)
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
/***************************************************************************
2+
testqgscategorizedrendererwidget.cpp
3+
---------------------------
4+
begin : January 2019
5+
copyright : (C) 2019 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#include "qgstest.h"
19+
20+
#include "qgscategorizedsymbolrenderer.h"
21+
#include "qgscategorizedsymbolrendererwidget.h"
22+
#include "qgsapplication.h"
23+
#include "qgsvectorlayer.h"
24+
#include "qgsfeature.h"
25+
#include "qgsvectordataprovider.h"
26+
27+
#include <memory>
28+
29+
class TestQgsCategorizedRendererWidget : public QObject
30+
{
31+
Q_OBJECT
32+
private slots:
33+
void initTestCase();// will be called before the first testfunction is executed.
34+
void cleanupTestCase();// will be called after the last testfunction was executed.
35+
void init();// will be called before each testfunction is executed.
36+
void cleanup();// will be called after every testfunction.
37+
void testAddMissingCategories();
38+
void merge();
39+
void model();
40+
41+
};
42+
43+
void TestQgsCategorizedRendererWidget::initTestCase()
44+
{
45+
QgsApplication::init();
46+
QgsApplication::initQgis();
47+
}
48+
49+
void TestQgsCategorizedRendererWidget::cleanupTestCase()
50+
{
51+
QgsApplication::exitQgis();
52+
}
53+
54+
void TestQgsCategorizedRendererWidget::init()
55+
{
56+
}
57+
58+
void TestQgsCategorizedRendererWidget::cleanup()
59+
{
60+
}
61+
62+
void TestQgsCategorizedRendererWidget::testAddMissingCategories()
63+
{
64+
std::unique_ptr< QgsVectorLayer > vl = qgis::make_unique< QgsVectorLayer >( "Point?crs=EPSG:4326&field=idx:integer&field=name:string", QString(), QStringLiteral( "memory" ) );
65+
QVERIFY( vl->isValid() );
66+
67+
QgsFeature f;
68+
f.setAttributes( QgsAttributes() << 1 << "a" );
69+
QVERIFY( vl->dataProvider()->addFeature( f ) );
70+
f.setAttributes( QgsAttributes() << 2 << "b" );
71+
vl->dataProvider()->addFeature( f );
72+
f.setAttributes( QgsAttributes() << 3 << "c" );
73+
vl->dataProvider()->addFeature( f );
74+
f.setAttributes( QgsAttributes() << 4 << "d" );
75+
vl->dataProvider()->addFeature( f );
76+
77+
QgsCategorizedSymbolRenderer *renderer = new QgsCategorizedSymbolRenderer( QStringLiteral( "name" ) );
78+
vl->setRenderer( renderer );
79+
80+
std::unique_ptr< QgsCategorizedSymbolRendererWidget > widget = qgis::make_unique< QgsCategorizedSymbolRendererWidget >( vl.get(), nullptr, renderer );
81+
QVERIFY( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().isEmpty() );
82+
83+
widget->addCategories();
84+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().count(), 5 );
85+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 0 ).value().toString(), QStringLiteral( "a" ) );
86+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 1 ).value().toString(), QStringLiteral( "b" ) );
87+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 2 ).value().toString(), QStringLiteral( "c" ) );
88+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 3 ).value().toString(), QStringLiteral( "d" ) );
89+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 4 ).value().toString(), QString() );
90+
91+
// add a new value
92+
f.setAttributes( QgsAttributes() << 4 << "e" );
93+
vl->dataProvider()->addFeature( f );
94+
95+
widget->addCategories();
96+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().count(), 6 );
97+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 5 ).value().toString(), QStringLiteral( "e" ) );
98+
99+
// test with a value list category
100+
widget.reset();
101+
renderer = new QgsCategorizedSymbolRenderer( QStringLiteral( "name" ) );
102+
renderer->addCategory( QgsRendererCategory( QStringLiteral( "b" ), QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry ), QString() ) );
103+
renderer->addCategory( QgsRendererCategory( QVariantList() << QStringLiteral( "a" ) << QStringLiteral( "c" ), QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry ), QString() ) );
104+
105+
vl->setRenderer( renderer );
106+
107+
widget = qgis::make_unique< QgsCategorizedSymbolRendererWidget >( vl.get(), nullptr, renderer );
108+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().count(), 2 );
109+
110+
// values inside list categories should not be re-added
111+
widget->addCategories();
112+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().count(), 5 );
113+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 0 ).value().toString(), QStringLiteral( "b" ) );
114+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 1 ).value().toList().at( 0 ).toString(), QStringLiteral( "a" ) );
115+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 1 ).value().toList().at( 1 ).toString(), QStringLiteral( "c" ) );
116+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 2 ).value().toString(), QStringLiteral( "d" ) );
117+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 3 ).value().toString(), QStringLiteral( "e" ) );
118+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 4 ).value().toString(), QString() );
119+
}
120+
121+
void TestQgsCategorizedRendererWidget::merge()
122+
{
123+
// test merging categories
124+
125+
std::unique_ptr< QgsVectorLayer > vl = qgis::make_unique< QgsVectorLayer >( "Point?crs=EPSG:4326&field=idx:integer&field=name:string", QString(), QStringLiteral( "memory" ) );
126+
QVERIFY( vl->isValid() );
127+
128+
QgsFeature f;
129+
f.setAttributes( QgsAttributes() << 1 << "a" );
130+
QVERIFY( vl->dataProvider()->addFeature( f ) );
131+
f.setAttributes( QgsAttributes() << 2 << "b" );
132+
vl->dataProvider()->addFeature( f );
133+
f.setAttributes( QgsAttributes() << 3 << "c" );
134+
vl->dataProvider()->addFeature( f );
135+
f.setAttributes( QgsAttributes() << 4 << "d" );
136+
vl->dataProvider()->addFeature( f );
137+
f.setAttributes( QgsAttributes() << 5 << "e" );
138+
vl->dataProvider()->addFeature( f );
139+
140+
QgsCategorizedSymbolRenderer *renderer = new QgsCategorizedSymbolRenderer( QStringLiteral( "name" ) );
141+
vl->setRenderer( renderer );
142+
143+
std::unique_ptr< QgsCategorizedSymbolRendererWidget > widget = qgis::make_unique< QgsCategorizedSymbolRendererWidget >( vl.get(), nullptr, renderer );
144+
widget->addCategories();
145+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().count(), 6 );
146+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 0 ).value().toString(), QStringLiteral( "a" ) );
147+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 1 ).value().toString(), QStringLiteral( "b" ) );
148+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 2 ).value().toString(), QStringLiteral( "c" ) );
149+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 3 ).value().toString(), QStringLiteral( "d" ) );
150+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 4 ).value().toString(), QStringLiteral( "e" ) );
151+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 5 ).value().toString(), QString() );
152+
153+
// no selection, should have no effect
154+
widget->mergeClicked();
155+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().count(), 6 );
156+
157+
widget->viewCategories->selectionModel()->select( widget->viewCategories->model()->index( 1, 0 ), QItemSelectionModel::Select | QItemSelectionModel::Rows );
158+
// one selection, should have no effect
159+
widget->mergeClicked();
160+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().count(), 6 );
161+
162+
widget->viewCategories->selectionModel()->select( widget->viewCategories->model()->index( 3, 0 ), QItemSelectionModel::Select | QItemSelectionModel::Rows );
163+
widget->viewCategories->selectionModel()->select( widget->viewCategories->model()->index( 4, 0 ), QItemSelectionModel::Select | QItemSelectionModel::Rows );
164+
widget->mergeClicked();
165+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().count(), 4 );
166+
167+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 0 ).value().toString(), QStringLiteral( "a" ) );
168+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 1 ).value().toList().at( 0 ).toString(), QStringLiteral( "b" ) );
169+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 1 ).value().toList().at( 1 ).toString(), QStringLiteral( "d" ) );
170+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 1 ).value().toList().at( 2 ).toString(), QStringLiteral( "e" ) );
171+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 2 ).value().toString(), QStringLiteral( "c" ) );
172+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 3 ).value().toString(), QString() );
173+
174+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 0 ).label(), QStringLiteral( "a" ) );
175+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 1 ).label(), QStringLiteral( "b,d,e" ) );
176+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 2 ).label(), QStringLiteral( "c" ) );
177+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 3 ).label(), QString() );
178+
179+
// selection should always "merge into" first selected item
180+
widget->viewCategories->selectionModel()->select( widget->viewCategories->model()->index( 2, 0 ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
181+
widget->viewCategories->selectionModel()->select( widget->viewCategories->model()->index( 0, 0 ), QItemSelectionModel::Select | QItemSelectionModel::Rows );
182+
widget->mergeClicked();
183+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().count(), 3 );
184+
185+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 0 ).value().toList().at( 0 ).toString(), QStringLiteral( "b" ) );
186+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 0 ).value().toList().at( 1 ).toString(), QStringLiteral( "d" ) );
187+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 0 ).value().toList().at( 2 ).toString(), QStringLiteral( "e" ) );
188+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 1 ).value().toList().at( 0 ).toString(), QStringLiteral( "c" ) );
189+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 1 ).value().toList().at( 1 ).toString(), QStringLiteral( "a" ) );
190+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 2 ).value().toString(), QString() );
191+
192+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 0 ).label(), QStringLiteral( "b,d,e" ) );
193+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 1 ).label(), QStringLiteral( "c,a" ) );
194+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 2 ).label(), QString() );
195+
196+
// merging categories which are already lists
197+
widget->viewCategories->selectionModel()->select( widget->viewCategories->model()->index( 0, 0 ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
198+
widget->viewCategories->selectionModel()->select( widget->viewCategories->model()->index( 1, 0 ), QItemSelectionModel::Select | QItemSelectionModel::Rows );
199+
widget->mergeClicked();
200+
201+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().count(), 2 );
202+
203+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 0 ).value().toList().at( 0 ).toString(), QStringLiteral( "b" ) );
204+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 0 ).value().toList().at( 1 ).toString(), QStringLiteral( "d" ) );
205+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 0 ).value().toList().at( 2 ).toString(), QStringLiteral( "e" ) );
206+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 0 ).value().toList().at( 3 ).toString(), QStringLiteral( "c" ) );
207+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 0 ).value().toList().at( 4 ).toString(), QStringLiteral( "a" ) );
208+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 1 ).value().toString(), QString() );
209+
210+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 0 ).label(), QStringLiteral( "b,d,e,c,a" ) );
211+
QCOMPARE( static_cast< QgsCategorizedSymbolRenderer * >( widget->renderer() )->categories().at( 1 ).label(), QString() );
212+
}
213+
214+
void TestQgsCategorizedRendererWidget::model()
215+
{
216+
std::unique_ptr< QgsVectorLayer > vl = qgis::make_unique< QgsVectorLayer >( "Point?crs=EPSG:4326&field=idx:integer&field=name:string", QString(), QStringLiteral( "memory" ) );
217+
QVERIFY( vl->isValid() );
218+
219+
QgsFeature f;
220+
f.setAttributes( QgsAttributes() << 1 << "a" );
221+
QVERIFY( vl->dataProvider()->addFeature( f ) );
222+
f.setAttributes( QgsAttributes() << 2 << "b" );
223+
vl->dataProvider()->addFeature( f );
224+
f.setAttributes( QgsAttributes() << 3 << "c" );
225+
vl->dataProvider()->addFeature( f );
226+
f.setAttributes( QgsAttributes() << 4 << "d" );
227+
vl->dataProvider()->addFeature( f );
228+
f.setAttributes( QgsAttributes() << 5 << "e" );
229+
vl->dataProvider()->addFeature( f );
230+
231+
QgsCategorizedSymbolRenderer *renderer = new QgsCategorizedSymbolRenderer( QStringLiteral( "name" ) );
232+
renderer = new QgsCategorizedSymbolRenderer( QStringLiteral( "name" ) );
233+
renderer->addCategory( QgsRendererCategory( QStringLiteral( "b" ), QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry ), QStringLiteral( "aa" ) ) );
234+
renderer->addCategory( QgsRendererCategory( QVariantList() << QStringLiteral( "a" ) << QStringLiteral( "c" ), QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry ), QStringLiteral( "list" ) ) );
235+
renderer->addCategory( QgsRendererCategory( QStringLiteral( "d" ), QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry ), QStringLiteral( "dd" ), false ) );
236+
237+
vl->setRenderer( renderer );
238+
239+
std::unique_ptr< QgsCategorizedSymbolRendererWidget > widget = qgis::make_unique< QgsCategorizedSymbolRendererWidget >( vl.get(), nullptr, renderer );
240+
QgsCategorizedSymbolRendererModel *model = widget->mModel;
241+
QCOMPARE( model->rowCount(), 3 );
242+
QCOMPARE( model->data( model->index( 0, 1 ), Qt::DisplayRole ).toString(), QStringLiteral( "b" ) );
243+
QCOMPARE( model->data( model->index( 1, 1 ), Qt::DisplayRole ).toString(), QStringLiteral( "a;c" ) );
244+
QCOMPARE( model->data( model->index( 2, 1 ), Qt::DisplayRole ).toString(), QStringLiteral( "d" ) );
245+
QCOMPARE( model->data( model->index( 0, 2 ), Qt::DisplayRole ).toString(), QStringLiteral( "aa" ) );
246+
QCOMPARE( model->data( model->index( 1, 2 ), Qt::DisplayRole ).toString(), QStringLiteral( "list" ) );
247+
QCOMPARE( model->data( model->index( 2, 2 ), Qt::DisplayRole ).toString(), QStringLiteral( "dd" ) );
248+
249+
QCOMPARE( model->data( model->index( 0, 0 ), Qt::CheckStateRole ).toInt(), Qt::Checked );
250+
QCOMPARE( model->data( model->index( 1, 0 ), Qt::CheckStateRole ).toInt(), Qt::Checked );
251+
QCOMPARE( model->data( model->index( 2, 0 ), Qt::CheckStateRole ).toInt(), Qt::Unchecked );
252+
}
253+
254+
QGSTEST_MAIN( TestQgsCategorizedRendererWidget )
255+
#include "testqgscategorizedrendererwidget.moc"

0 commit comments

Comments
 (0)
Please sign in to comment.