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