QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsrasterattributetablemodel.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsrasterattributetablemodel.cpp - QgsRasterAttributeTableModel
3
4 ---------------------
5 begin : 29.9.2022
6 copyright : (C) 2022 by ale
7 email : [your-email-here]
8 ***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
17#include <QColor>
18#include <QFont>
19
20
22 : QAbstractTableModel( parent )
23 , mRat( rat )
24{
25}
26
28{
29 return mEditable;
30}
31
33{
34 mEditable = editable;
35}
36
38{
39 return mRat && mRat->hasColor();
40}
41
43{
44 return mRat && mRat->hasRamp();
45}
46
48{
49 QStringList headers;
50 if ( mRat )
51 {
52 const QList<QgsRasterAttributeTable::Field> &ratFields { mRat->fields() };
53 for ( const QgsRasterAttributeTable::Field &f : std::as_const( ratFields ) )
54 {
55 headers.push_back( f.name );
56 }
57
58 if ( hasColor() || hasRamp() )
59 {
60 headers.append( ratColorHeaderName() );
61 }
62 }
63 return headers;
64}
65
66QString QgsRasterAttributeTableModel::headerTooltip( const int section ) const
67{
68 const QStringList hNames { headerNames() };
69 if ( section < 0 || section >= hNames.count( ) )
70 {
71 return QString( );
72 }
73
74 const QString fieldName { hNames.at( section ) };
75 const bool isColor { hasColor() && section == hNames.count( ) - 1 }; // *NOPAD*
76
77 if ( isColor )
78 {
79 return tr( "Virtual color field generated from the values in RGB(A) data columns" );
80 }
81
82 bool ok;
83 const QgsRasterAttributeTable::Field field { mRat->fieldByName( fieldName, &ok ) };
84
85 if ( ! ok )
86 {
87 return QString();
88 }
89
90 return QStringLiteral( R"HTML(
91 <dl>
92 <dt>Role</dt><dd>%1</dd>
93 <dt>Type</dt><dd>%2</dd>
94 <dt>Description</dt><dd>%3</dd>
95 </dl>
96 )HTML" ).arg( QgsRasterAttributeTable::usageName( field.usage ),
97 QVariant::typeToName( field.type ),
98 QgsRasterAttributeTable::usageInformation().value( field.usage ).description ) ;
99}
100
101bool QgsRasterAttributeTableModel::isValid( QString *errorMessage )
102{
103 if ( ! mRat )
104 {
105 if ( errorMessage )
106 {
107 *errorMessage = tr( "Raster Attribute Table is not set for this model." );
108 }
109 return false;
110 }
111 return mRat->isValid( errorMessage );
112}
113
115{
116 return mRat && mRat->isDirty( );
117}
118
119bool QgsRasterAttributeTableModel::insertField( const int position, const QString &name, const Qgis::RasterAttributeTableFieldUsage usage, const QVariant::Type type, QString *errorMessage )
120{
121
122 if ( ! editChecks( errorMessage ) )
123 {
124 return false;
125 }
126
127 if ( position < 0 )
128 {
129 if ( errorMessage )
130 {
131 *errorMessage = QObject::tr( "Invalid position '%1' for field insertion." ).arg( position );
132 }
133 return false;
134 }
135
136 const int newPosition { std::clamp( position, 0, static_cast<int>( mRat->fields().count( ) ) ) };
137 const QgsRasterAttributeTable::Field field { name, usage, type };
138 beginResetModel( );
139 const bool retVal { mRat->insertField( newPosition, field, errorMessage ) };
140 endResetModel();
141 return retVal;
142}
143
144bool QgsRasterAttributeTableModel::removeField( const int position, QString *errorMessage )
145{
146
147 if ( ! editChecks( errorMessage ) )
148 {
149 return false;
150 }
151
152 if ( position < 0 || position >= mRat->fields().count() )
153 {
154 if ( errorMessage )
155 {
156 *errorMessage = QObject::tr( "Invalid position '%1' for field removal." ).arg( position );
157 }
158 return false;
159 }
160
161 beginResetModel( );
162 const bool retVal { mRat->removeField( mRat->fields().at( position ).name, errorMessage ) };
163 endResetModel();
164 return retVal;
165}
166
168{
169
170 if ( ! editChecks( errorMessage ) )
171 {
172 return false;
173 }
174
175 if ( ! mRat->hasColor() && ! mRat->hasRamp( ) )
176 {
177 if ( errorMessage )
178 {
179 *errorMessage = tr( "Raster attribute table does not have color or ramp information." );
180 }
181 return false;
182 }
183
184 bool ret { true };
185 beginResetModel();
186 const QList<QgsRasterAttributeTable::Field> ratFields { mRat->fields() };
187 for ( const QgsRasterAttributeTable::Field &f : std::as_const( ratFields ) )
188 {
189 if ( f.isColor() || f.isRamp() )
190 {
191 ret &= mRat->removeField( f.name, errorMessage );
192 if ( ! ret )
193 {
194 break;
195 }
196 }
197 }
198 endResetModel();
199 return ret;
200}
201
202bool QgsRasterAttributeTableModel::insertRow( const int position, const QVariantList &rowData, QString *errorMessage )
203{
204 if ( ! editChecks( errorMessage ) )
205 {
206 return false;
207 }
208
209 if ( position < 0 || position > mRat->data().count( ) )
210 {
211 if ( errorMessage )
212 {
213 *errorMessage = tr( "Position is not valid or the table is empty." );
214 }
215 return false;
216 }
217
218 beginResetModel();
219 const bool retVal { mRat->insertRow( position, rowData, errorMessage ) };
220 endResetModel();
221 return retVal;
222}
223
224bool QgsRasterAttributeTableModel::insertColor( int position, QString *errorMessage )
225{
226 if ( ! editChecks( errorMessage ) )
227 {
228 return false;
229 }
230
231 if ( position < 0 )
232 {
233 if ( errorMessage )
234 {
235 *errorMessage = QObject::tr( "Invalid position '%1' for color insertion." ).arg( position );
236 }
237 return false;
238 }
239
240 beginResetModel();
241 const bool retVal { mRat->insertColor( position, errorMessage ) };
242 endResetModel();
243 return retVal;
244}
245
246bool QgsRasterAttributeTableModel::insertRamp( int position, QString *errorMessage )
247{
248 if ( ! editChecks( errorMessage ) )
249 {
250 return false;
251 }
252
253 if ( position < 0 )
254 {
255 if ( errorMessage )
256 {
257 *errorMessage = QObject::tr( "Invalid position '%1' for color ramp insertion." ).arg( position );
258 }
259 return false;
260 }
261
262 beginResetModel();
263 const bool retVal { mRat->insertRamp( position, errorMessage ) };
264 endResetModel();
265 return retVal;
266}
267
268bool QgsRasterAttributeTableModel::removeRow( const int position, QString *errorMessage )
269{
270 if ( ! editChecks( errorMessage ) )
271 {
272 return false;
273 }
274
275 if ( position < 0 || position >= mRat->data().count( ) )
276 {
277 if ( errorMessage )
278 {
279 *errorMessage = tr( "Position is not valid or the table is empty." );
280 }
281 return false;
282 }
283
284 beginResetModel();
285 const bool retVal { mRat->removeRow( position, errorMessage ) };
286 endResetModel();
287 return retVal;
288}
289
290bool QgsRasterAttributeTableModel::editChecks( QString *errorMessage )
291{
292 if ( ! mRat )
293 {
294 if ( errorMessage )
295 {
296 *errorMessage = QObject::tr( "Raster Attribute Table is not set for this model." );
297 }
298 return false;
299 }
300
301 if ( ! mEditable )
302 {
303 if ( errorMessage )
304 {
305 *errorMessage = QObject::tr( "Raster Attribute Table is not editable." );
306 }
307 return false;
308 }
309
310 return true;
311}
312
313QString QgsRasterAttributeTableModel::ratColorHeaderName() const
314{
315 return tr( "Color" );
316}
317
318int QgsRasterAttributeTableModel::rowCount( const QModelIndex &parent ) const
319{
320 return ( !parent.isValid() && mRat ) ? mRat->data().count() : 0;
321}
322
323int QgsRasterAttributeTableModel::columnCount( const QModelIndex &parent ) const
324{
325 return ( ! parent.isValid() && mRat ) ? ( mRat->fields().count() + ( mRat->hasColor() || mRat->hasRamp() ? 1 : 0 ) ) : 0;
326}
327
328QVariant QgsRasterAttributeTableModel::data( const QModelIndex &index, int role ) const
329{
330 if ( mRat && index.isValid() && index.row() < rowCount( QModelIndex() ) && index.column() < columnCount( QModelIndex() ) )
331 {
332 const QString fieldName { headerNames().at( index.column() ) };
333 const bool isColorOrRamp { ( hasColor() || hasRamp() ) && index.column() == columnCount( QModelIndex() ) - 1 }; // *NOPAD*
334 bool ok;
335 const QgsRasterAttributeTable::Field field { mRat->fieldByName( fieldName, &ok ) };
336 if ( ! isColorOrRamp && ! ok )
337 {
338 return QVariant();
339 }
340 if ( isColorOrRamp && hasColor() )
341 {
342 switch ( role )
343 {
344 case Qt::ItemDataRole::ForegroundRole:
345 {
346 // Choose black or white for a decent contrast.
347 const QColor tempColor { mRat->color( index.row() ) };
348 const double darkness { 1 - ( 0.299 * tempColor.red() + 0.587 * tempColor.green() + 0.114 * tempColor.blue() ) / 255};
349 return darkness > 0.5 ? QColor( Qt::GlobalColor::white ) : QColor( Qt::GlobalColor::black );
350 }
351 case Qt::ItemDataRole::EditRole:
352 case Qt::ItemDataRole::BackgroundRole:
353 return mRat->color( index.row() );
354 case Qt::ItemDataRole::DisplayRole:
355 return mRat->color( index.row() ).name();
356 default:
357 return QVariant();
358 }
359 }
360 else if ( isColorOrRamp && hasRamp() )
361 {
362 switch ( role )
363 {
364 case Qt::ItemDataRole::BackgroundRole:
365 {
366 return QVariant();
367 // This doesn't work (even if it should), so after a large amount
368 // of wasted hours I had to settle for ColorRampDelegate::paint override
369 /*
370 const QgsGradientColorRamp ramp { mRat->ramp( index.row() )};
371 QLinearGradient gradient( QPointF(0, 0), QPointF(1, 0) );
372 gradient.setCoordinateMode( QGradient::CoordinateMode::ObjectBoundingMode );
373 gradient.setColorAt(0, ramp.color1() );
374 gradient.setColorAt(1, ramp.color2() );
375 return QBrush{ gradient };
376 */
377 }
378 case Qt::ItemDataRole::EditRole:
379 {
380 return QVariant::fromValue( mRat->ramp( index.row() ) );
381 }
382 default:
383 return QVariant();
384 }
385 }
386 else if ( role == Qt::ItemDataRole::TextAlignmentRole && field.type != QVariant::String )
387 {
388 return QVariant( Qt::AlignmentFlag::AlignRight | Qt::AlignmentFlag::AlignVCenter );
389 }
390 else if ( role == Qt::ItemDataRole::ToolTipRole && ( isColorOrRamp ) )
391 {
392 return tr( "This data is part of a color definition: click on '%1' column to edit." ).arg( ratColorHeaderName() );
393 }
394 else if ( role == Qt::ItemDataRole::DisplayRole || role == Qt::ItemDataRole::EditRole )
395 {
396 return mRat->data().at( index.row() ).at( index.column() );
397 }
398 else if ( role == Qt::ItemDataRole::FontRole && ( isColorOrRamp ) )
399 {
400 QFont font;
401 font.setItalic( true );
402 return font;
403 }
404 }
405 return QVariant();
406}
407
408bool QgsRasterAttributeTableModel::setData( const QModelIndex &index, const QVariant &value, int role )
409{
410 if ( mRat && index.isValid() && role == Qt::ItemDataRole::EditRole )
411 {
412 const QString fieldName { headerNames().at( index.column() ) };
413 const bool isColorOrRamp { ( hasColor() || hasRamp() ) &&index.column() == columnCount( QModelIndex( ) ) - 1 };
414 bool ok;
415 const QgsRasterAttributeTable::Field field { mRat->fieldByName( fieldName, &ok ) };
416 if ( ! isColorOrRamp && ! ok )
417 {
418 return false;
419 }
420 if ( hasColor() && isColorOrRamp )
421 {
422 if ( ! value.canConvert( QVariant::Type::Color ) || ! mRat->setColor( index.row(), value.value<QColor>( ) ) )
423 {
424 return false;
425 }
426 const QModelIndex colorColIdx { QgsRasterAttributeTableModel::index( index.row(), columnCount( QModelIndex() ) - 1, QModelIndex() )};
427 emit dataChanged( colorColIdx, colorColIdx );
428 // Change all color columns
429 const QList<QgsRasterAttributeTable::Field> &ratFields { mRat->fields() };
430 for ( int fIdx = 0; fIdx < ratFields.count(); ++fIdx )
431 {
432 if ( ratFields[ fIdx ].isColor() )
433 {
434 const QModelIndex fieldColIdx { QgsRasterAttributeTableModel::index( index.row(), fIdx, QModelIndex() )};
435 emit dataChanged( fieldColIdx, fieldColIdx );
436 }
437 }
438 return true;
439 }
440 else if ( hasRamp() && isColorOrRamp )
441 {
442 const QgsGradientColorRamp ramp { qvariant_cast<QgsGradientColorRamp>( value ) };
443 if ( ! mRat->setRamp( index.row(), ramp.color1(), ramp.color2() ) )
444 {
445 return false;
446 }
447 const QModelIndex colorColIdx { QgsRasterAttributeTableModel::index( index.row(), columnCount( QModelIndex() ) - 1, QModelIndex() )};
448 emit dataChanged( colorColIdx, colorColIdx );
449 // Change all ramp columns
450 const QList<QgsRasterAttributeTable::Field> &ratFields { mRat->fields() };
451 for ( int fIdx = 0; fIdx < ratFields.count(); ++fIdx )
452 {
453 if ( ratFields[ fIdx ].isRamp() )
454 {
455 const QModelIndex fieldColIdx { QgsRasterAttributeTableModel::index( index.row(), fIdx, QModelIndex() )};
456 emit dataChanged( fieldColIdx, fieldColIdx );
457 }
458 }
459 return true;
460 }
461 else if ( ok )
462 {
463 const bool retVal { mRat->setValue( index.row(), index.column(), value ) };
464 if ( retVal )
465 {
466 const QModelIndex fieldColIdx { QgsRasterAttributeTableModel::index( index.row(), index.column(), QModelIndex() )};
467 emit dataChanged( fieldColIdx, fieldColIdx );
468 }
469 return retVal;
470 }
471 }
472 return false;
473}
474
475QVariant QgsRasterAttributeTableModel::headerData( int section, Qt::Orientation orientation, int role ) const
476{
477 if ( orientation == Qt::Orientation::Horizontal )
478 {
479 const QStringList hNames { headerNames( ) };
480 if ( section < hNames.length() )
481 {
482 switch ( role )
483 {
484 case Qt::ItemDataRole::DisplayRole:
485 {
486 return hNames.at( section );
487 }
488 case Qt::ItemDataRole::ToolTipRole:
489 {
490 return headerTooltip( section );
491 }
492 default:
493 return QAbstractTableModel::headerData( section, orientation, role );
494 }
495 }
496 }
497 return QAbstractTableModel::headerData( section, orientation, role );
498}
499
500Qt::ItemFlags QgsRasterAttributeTableModel::flags( const QModelIndex &index ) const
501{
502 if ( mRat )
503 {
504 Qt::ItemFlags flags;
505 if ( index.isValid() )
506 {
507 flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
508 if ( mEditable )
509 {
510 if ( index.column() < mRat->fields().count( ) )
511 {
512 const QList<QgsRasterAttributeTable::Field> fields = mRat->fields();
513 const QgsRasterAttributeTable::Field &field { fields.at( index.column() ) };
514 if ( ! field.isColor() && ! field.isRamp() )
515 {
516 flags |= Qt::ItemIsEditable;
517 }
518 }
519 else // Must be the color column
520 {
521 flags |= Qt::ItemIsEditable;
522 }
523 }
524 }
525 return flags;
526 }
527 return Qt::NoItemFlags;
528}
RasterAttributeTableFieldUsage
The RasterAttributeTableFieldUsage enum represents the usage of a Raster Attribute Table field.
Definition: qgis.h:1191
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
QStringList headerNames() const
Returns all the header names, including the "virtual" color header if the Raster Attribute Table has ...
bool removeColorOrRamp(QString *errorMessage=nullptr)
Removes all color or ramp information, optionally reporting any error in errorMessage,...
bool hasRamp() const
Returns true if the Raster Attribute Table has ramp information.
int rowCount(const QModelIndex &parent) const override
bool isDirty()
Returns true if the Raster Attribute Table was modified since it was last saved or read.
bool hasColor() const
Returns true if the Raster Attribute Table has color information.
bool removeField(const int position, QString *errorMessage=nullptr)
Remove the field at given position, optionally reporting any error in errorMessage,...
bool insertColor(int position, QString *errorMessage=nullptr)
Create RGBA fields and inserts them at position, optionally reporting any error in errorMessage,...
bool insertRow(const int position, const QVariantList &rowData, QString *errorMessage=nullptr)
Inserts a new row before position, optionally reporting any error in errorMessage,...
int columnCount(const QModelIndex &parent) const override
bool editable() const
Returns true if the Raster Attribute Table is editable.
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
QString headerTooltip(const int section) const
Returns the tooltip for the given section.
bool insertField(const int position, const QString &name, const Qgis::RasterAttributeTableFieldUsage usage, const QVariant::Type type, QString *errorMessage=nullptr)
Inserts a field at the given position.
bool setData(const QModelIndex &index, const QVariant &value, int role) override
void setEditable(bool editable)
Sets the Raster Attribute Table editable state to editable.
QgsRasterAttributeTableModel(QgsRasterAttributeTable *rat, QObject *parent=nullptr)
Creates a new QgsRasterAttributeTableModel from raster attribute table rat and optional parent.
bool isValid(QString *errorMessage=nullptr)
Checks if the Raster Attribute Table is valid, optionally returns validation errors in errorMessage.
QVariant data(const QModelIndex &index, int role) const override
bool insertRamp(int position, QString *errorMessage=nullptr)
Create RGBA minimum and maximum fields and inserts them at position, optionally reporting any error i...
Qt::ItemFlags flags(const QModelIndex &index) const override
bool removeRow(const int position, QString *errorMessage=nullptr)
Removes the row at position, optionally reporting any error in errorMessage, returns true on success.
The Field class represents a Raster Attribute Table field, including its name, usage and type.
The QgsRasterAttributeTable class represents a Raster Attribute Table (RAT).
const QgsRasterAttributeTable::Field fieldByName(const QString name, bool *ok=nullptr) const
Returns a field by name or a default constructed field with empty name if the field is not found.
bool isDirty() const
Returns true if the Raster Attribute Table was modified from its last reading from the storage.
bool setColor(const int row, const QColor &color)
Sets the color for the row at rowIndex to color.
QgsGradientColorRamp ramp(int row) const
Returns the gradient color ramp of the rat row or a default constructed gradient if row does not exis...
bool insertField(int position, const QgsRasterAttributeTable::Field &field, QString *errorMessage=nullptr)
Inserts a new field at position, optionally reporting any error in errorMessage, returns true on succ...
bool hasColor() const
Returns true if the Raster Attribute Table has color RGBA information.
bool setValue(const int row, const int column, const QVariant &value)
Sets the value for row and column.
QList< QgsRasterAttributeTable::Field > fields() const
Returns the Raster Attribute Table fields.
PRIVATE QColor color(int row) const
Returns the color of the rat row or an invalid color if row does not exist or if there is no color de...
bool removeRow(int position=0, QString *errorMessage=nullptr)
Removes the row in the Raster Attribute Table at position, optionally reporting any error in errorMes...
static QHash< Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation > usageInformation()
Returns information about supported Raster Attribute Table usages.
bool insertRow(int position, const QVariantList &rowData, QString *errorMessage=nullptr)
Inserts a row of rowData in the Raster Attribute Table at position, optionally reporting any error in...
bool insertColor(int position, QString *errorMessage=nullptr)
Create RGBA fields and inserts them at position, optionally reporting any error in errorMessage,...
bool isValid(QString *errorMessage=nullptr) const
Returns true if the Raster Attribute Table is valid, optionally reporting validity checks results in ...
const QList< QList< QVariant > > data() const
Returns the Raster Attribute Table rows.
bool setRamp(const int row, const QColor &colorMin, const QColor &colorMax)
Sets the color ramp for the row at rowIndex to colorMin and colorMax.
static QString usageName(const Qgis::RasterAttributeTableFieldUsage fieldusage)
Returns the translated human readable name of fieldUsage.
bool hasRamp() const
Returns true if the Raster Attribute Table has ramp RGBA information.
bool insertRamp(int position, QString *errorMessage=nullptr)
Create RGBA minimum and maximum fields and inserts them at position, optionally reporting any error i...
bool removeField(const QString &name, QString *errorMessage=nullptr)
Removes the field with name, optionally reporting any error in errorMessage, returns true on success.