QGIS API Documentation 4.1.0-Master (d6fb7a379fb)
Loading...
Searching...
No Matches
qgstemplatedcategorizedrendererwidget_p.h
Go to the documentation of this file.
1/***************************************************************************
2 qgstemplatedcategorizedrendererwidget_p.h
3 ---------------------
4 begin : November 2025
5 copyright : (C) 2025 by Jean Felder
6 email : jean dot felder at oslandia 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#ifndef QGSTEMPLATEDCATEGORIZEDRENDERERMODEL_H
16#define QGSTEMPLATEDCATEGORIZEDRENDERERMODEL_H
17
18
19#include <algorithm>
20
21#include "qgslogger.h"
22#include "qgsvariantutils.h"
23
24#include <QAbstractItemModel>
25#include <QIODevice>
26#include <QMimeData>
27#include <QPointer>
28#include <QScreen>
29#include <QString>
30
31#define SIP_NO_FILE
32
33using namespace Qt::StringLiterals;
34
35
37
45template<typename RendererType> class QgsTemplatedCategorizedRendererModel : public QAbstractItemModel
46{
47 public:
48 using Category = typename RendererType::Category;
49
56 QgsTemplatedCategorizedRendererModel( QObject *parent = nullptr, QScreen *screen = nullptr )
57 : QAbstractItemModel( parent )
58 , mMimeFormat( u"application/x-qgscategorizedsymbolrendererv2model"_s )
59 , mScreen( screen )
60 {}
61
62 virtual int symbolColumn() const { return 0; }
63
64 virtual int valueColumn() const { return 1; }
65
66 virtual QVariant headerData( int section, Qt::Orientation orientation, int role ) const override = 0;
67
68 virtual void sort( int column, Qt::SortOrder order = Qt::AscendingOrder ) override = 0;
69
70 virtual Qt::ItemFlags flags( const QModelIndex &index ) const override
71 {
72 // Flat list, to ease drop handling valid indexes are not dropEnabled
73 if ( !index.isValid() || !mRenderer )
74 {
75 return Qt::ItemIsDropEnabled;
76 }
77
78 Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsUserCheckable;
79 if ( index.column() == valueColumn() )
80 {
81 const Category category = mRenderer->categories().value( index.row() );
82 if ( category.value().userType() != QMetaType::Type::QVariantList )
83 {
84 flags |= Qt::ItemIsEditable;
85 }
86 }
87
88 // allow subclasses to extend flags
89 flags |= extraFlags( index );
90
91 return flags;
92 }
93
94 virtual QVariant data( const QModelIndex &index, int role ) const override
95 {
96 if ( !index.isValid() || !mRenderer )
97 {
98 return QVariant();
99 }
100
101 const Category category = mRenderer->categories().value( index.row() );
102
103 switch ( role )
104 {
105 case Qt::CheckStateRole:
106 {
107 if ( index.column() == symbolColumn() )
108 {
109 return category.renderState() ? Qt::Checked : Qt::Unchecked;
110 }
111 break;
112 }
113
114 case Qt::DisplayRole:
115 case Qt::ToolTipRole:
116 {
117 if ( index.column() == valueColumn() )
118 {
119 if ( category.value().userType() == QMetaType::Type::QVariantList )
120 {
121 QStringList res;
122 const QVariantList list = category.value().toList();
123 res.reserve( list.size() );
124 for ( const QVariant &variant : list )
125 res << QgsVariantUtils::displayString( variant );
126
127 if ( role == Qt::DisplayRole )
128 return res.join( ';' );
129 else // tooltip
130 return res.join( '\n' );
131 }
132 else if ( QgsVariantUtils::isNull( category.value() ) || category.value().toString().isEmpty() )
133 {
134 return tr( "all other values" );
135 }
136 else
137 {
138 return QgsVariantUtils::displayString( category.value() );
139 }
140 }
141 break;
142 }
143
144 case Qt::FontRole:
145 {
146 if ( index.column() == valueColumn() && category.value().userType() != QMetaType::Type::QVariantList && ( QgsVariantUtils::isNull( category.value() ) || category.value().toString().isEmpty() ) )
147 {
148 QFont italicFont;
149 italicFont.setItalic( true );
150 return italicFont;
151 }
152 return QVariant();
153 }
154
155 case Qt::DecorationRole:
156 {
157 if ( index.column() == symbolColumn() && category.symbol() )
158 {
159 return symbolIcon( category );
160 }
161 break;
162 }
163
164 case Qt::ForegroundRole:
165 {
166 QBrush brush( qApp->palette().color( QPalette::Text ), Qt::SolidPattern );
167 if ( index.column() == valueColumn() && ( category.value().userType() == QMetaType::Type::QVariantList || QgsVariantUtils::isNull( category.value() ) || category.value().toString().isEmpty() ) )
168 {
169 QColor fadedTextColor = brush.color();
170 fadedTextColor.setAlpha( 128 );
171 brush.setColor( fadedTextColor );
172 }
173 return brush;
174 }
175
176 case Qt::TextAlignmentRole:
177 {
178 return ( index.column() == valueColumn() ) ? static_cast<Qt::Alignment::Int>( Qt::AlignHCenter ) : static_cast<Qt::Alignment::Int>( Qt::AlignLeft );
179 }
180
181 case Qt::EditRole:
182 {
183 if ( index.column() == valueColumn() )
184 {
185 if ( category.value().userType() == QMetaType::Type::QVariantList )
186 {
187 QStringList res;
188 const QVariantList list = category.value().toList();
189 res.reserve( list.size() );
190 for ( const QVariant &variant : list )
191 res << variant.toString();
192
193 return res.join( ';' );
194 }
195 else
196 {
197 return category.value();
198 }
199 }
200 break;
201 }
202 case static_cast<int>( Qt::UserRole + 1 ):
203 {
204 if ( index.column() == valueColumn() )
205 {
206 return category.value();
207 }
208 break;
209 }
210 default:
211 break;
212 }
213
214 // allow subclasses to return data
215 return extraData( index, role );
216 }
217
218 virtual bool setData( const QModelIndex &index, const QVariant &value, int role ) override
219 {
220 if ( !index.isValid() )
221 return false;
222
223 if ( index.column() == symbolColumn() && role == Qt::CheckStateRole )
224 {
225 mRenderer->updateCategoryRenderState( index.row(), value == Qt::Checked );
226 emit dataChanged( index, index );
227 return true;
228 }
229
230 if ( role != Qt::EditRole )
231 {
232 return false;
233 }
234
235 if ( index.column() == valueColumn() )
236 {
237 // try to preserve variant type for this value, unless it was an empty string (other values)
238 QVariant val = value;
239 const QVariant previousValue = mRenderer->categories().value( index.row() ).value();
240 if ( previousValue.userType() != QMetaType::Type::QString && !previousValue.toString().isEmpty() )
241 {
242 switch ( previousValue.userType() )
243 {
244 case QMetaType::Type::Int:
245 val = value.toInt();
246 break;
247 case QMetaType::Type::Double:
248 val = value.toDouble();
249 break;
250 case QMetaType::Type::QVariantList:
251 {
252 const QStringList parts = value.toString().split( ';' );
253 QVariantList list;
254 list.reserve( parts.count() );
255 for ( const QString &part : parts )
256 list << part;
257
258 if ( list.count() == 1 )
259 val = list.at( 0 );
260 else
261 val = list;
262 break;
263 }
264 default:
265 val = value.toString();
266 break;
267 }
268 }
269 mRenderer->updateCategoryValue( index.row(), val );
270 emit dataChanged( index, index );
271 return true;
272 }
273
274 return setExtraData( index, value );
275 }
276
277 int rowCount( const QModelIndex &parent = QModelIndex() ) const override
278 {
279 if ( parent.isValid() || !mRenderer )
280 {
281 return 0;
282 }
283 return static_cast<int>( mRenderer->categories().size() );
284 }
285
286 int columnCount( const QModelIndex & = QModelIndex() ) const override = 0;
287
288 Qt::DropActions supportedDropActions() const override { return Qt::MoveAction; }
289
290 QModelIndex index( int row, int column, const QModelIndex &parent = QModelIndex() ) const override
291 {
292 if ( hasIndex( row, column, parent ) )
293 {
294 return createIndex( row, column );
295 }
296 return QModelIndex();
297 }
298
299 QModelIndex parent( const QModelIndex &index ) const override
300 {
301 Q_UNUSED( index )
302 return QModelIndex();
303 }
304
305 QStringList mimeTypes() const override
306 {
307 QStringList types;
308 types << mMimeFormat;
309 return types;
310 }
311
312 QMimeData *mimeData( const QModelIndexList &indexes ) const override
313 {
314 QMimeData *mimeData = new QMimeData();
315 QByteArray encodedData;
316
317 QDataStream stream( &encodedData, QIODevice::WriteOnly );
318
319 const auto constIndexes = indexes;
320 for ( const QModelIndex &index : constIndexes )
321 {
322 if ( !index.isValid() || index.column() != 0 )
323 continue;
324
325 stream << index.row();
326 }
327 mimeData->setData( mMimeFormat, encodedData );
328 return mimeData;
329 }
330
331 bool dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent ) override
332 {
333 Q_UNUSED( column )
334 Q_UNUSED( parent )
335 if ( action != Qt::MoveAction )
336 return true;
337
338 if ( !data->hasFormat( mMimeFormat ) )
339 return false;
340
341 QByteArray encodedData = data->data( mMimeFormat );
342 QDataStream stream( &encodedData, QIODevice::ReadOnly );
343
344 QVector<int> rows;
345 while ( !stream.atEnd() )
346 {
347 int r;
348 stream >> r;
349 rows.append( r );
350 }
351
352 std::sort( rows.begin(), rows.end() );
353
354 int to = row;
355
356 if ( to == -1 )
357 to = static_cast<int>( mRenderer->categories().size() );
358 for ( int i = static_cast<int>( rows.size() ) - 1; i >= 0; i-- )
359 {
360 QgsDebugMsgLevel( u"move %1 to %2"_s.arg( rows[i] ).arg( to ), 2 );
361 int t = to;
362 if ( rows[i] < t )
363 t--;
364 mRenderer->moveCategory( rows[i], t );
365 for ( int j = 0; j < i; j++ )
366 {
367 if ( to < rows[j] && rows[i] > rows[j] )
368 rows[j] += 1;
369 }
370 if ( rows[i] < to )
371 to--;
372 }
373 emit dataChanged( createIndex( 0, 0 ), createIndex( static_cast<int>( mRenderer->categories().size() ), 0 ) );
374 onRowsMoved();
375 return false;
376 }
377
383 void setRenderer( RendererType *renderer )
384 {
385 if ( mRenderer )
386 {
387 beginRemoveRows( QModelIndex(), 0, std::max<int>( static_cast<int>( mRenderer->categories().size() ) - 1, 0 ) );
388 mRenderer = nullptr;
389 endRemoveRows();
390 }
391 if ( renderer )
392 {
393 mRenderer = renderer;
394 if ( renderer->categories().size() > 0 )
395 {
396 beginInsertRows( QModelIndex(), 0, static_cast<int>( renderer->categories().size() ) - 1 );
397 endInsertRows();
398 }
399 }
400 }
401
407 void addCategory( const Category &cat )
408 {
409 if ( !mRenderer )
410 return;
411 const int idx = static_cast<int>( mRenderer->categories().size() );
412 beginInsertRows( QModelIndex(), idx, idx );
413 mRenderer->addCategory( cat );
414 endInsertRows();
415 }
416
423 Category category( const QModelIndex &index )
424 {
425 if ( !mRenderer )
426 {
427 return typename RendererType::Category();
428 }
429 const auto &catList = mRenderer->categories();
430 const int row = index.row();
431 if ( row >= catList.size() )
432 {
433 return typename RendererType::Category();
434 }
435 return catList.at( row );
436 }
437
443 void deleteRows( QList<int> rows )
444 {
445 std::sort( rows.begin(), rows.end() );
446 for ( int i = static_cast<int>( rows.size() ) - 1; i >= 0; i-- )
447 {
448 beginRemoveRows( QModelIndex(), rows[i], rows[i] );
449 mRenderer->deleteCategory( rows[i] );
450 endRemoveRows();
451 }
452 }
453
457 void removeAllRows()
458 {
459 beginRemoveRows( QModelIndex(), 0, static_cast<int>( mRenderer->categories().size() ) - 1 );
460 mRenderer->deleteAllCategories();
461 endRemoveRows();
462 }
463
467 void updateSymbology() { emit dataChanged( createIndex( 0, 0 ), createIndex( static_cast<int>( mRenderer->categories().size() ), 0 ) ); }
468
469 protected:
473 virtual void onRowsMoved() = 0;
474
478 virtual Qt::ItemFlags extraFlags( const QModelIndex &index ) const
479 {
480 Q_UNUSED( index )
481 return Qt::NoItemFlags;
482 }
483
484 virtual QIcon symbolIcon( const RendererType::Category &category ) const = 0;
485
490 virtual bool setExtraData( const QModelIndex &index, const QVariant &value )
491 {
492 Q_UNUSED( index )
493 Q_UNUSED( value )
494 return false;
495 }
496
501 virtual QVariant extraData( const QModelIndex &index, int role ) const
502 {
503 Q_UNUSED( index )
504 Q_UNUSED( role )
505 return QVariant();
506 }
507
508 protected:
509 RendererType *mRenderer = nullptr;
510 QString mMimeFormat;
511 QPointer<QScreen> mScreen;
512};
513
514
516
517#endif // QGSTEMPLATEDCATEGORIZEDRENDERERMODEL_H
static QString displayString(const QVariant &variant, int precision=-1)
Returns a localized representation of value with the given precision, if precision is -1 then precisi...
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63