28#include <QSortFilterProxyModel>
34 , mRasterLayer( rasterLayer )
39 QToolBar *editToolBar =
new QToolBar(
this );
42 mActionToggleEditing =
new QAction(
QgsApplication::getThemeIcon(
"/mActionEditTable.svg" ), tr(
"&Edit Attribute Table" ), editToolBar );
43 mActionToggleEditing->setCheckable(
true );
44 connect( mActionToggleEditing, &QAction::triggered,
this, [ = ](
bool editable )
49 editToolBar->addAction( mActionToggleEditing );
52 connect( mActionAddColumn, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::addColumn );
53 editToolBar->addAction( mActionAddColumn );
56 connect( mActionAddRow, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::addRow );
57 editToolBar->addAction( mActionAddRow );
60 connect( mActionRemoveRow, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::removeRow );
61 editToolBar->addAction( mActionRemoveRow );
64 connect( mActionRemoveColumn, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::removeColumn );
65 editToolBar->addAction( mActionRemoveColumn );
69 editToolBar->addAction( mActionSaveChanges );
71 layout()->setMenuBar( editToolBar );
73 connect( mClassifyButton, &QPushButton::clicked,
this, &QgsRasterAttributeTableWidget::classify );
75 mProxyModel =
new QSortFilterProxyModel(
this );
77 mRATView->setModel( mProxyModel );
79 connect( mRATView->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &QgsRasterAttributeTableWidget::updateButtons );
89 mRasterLayer = rasterLayer;
95 return mAttributeTableBuffer && mAttributeTableBuffer->isDirty();
98void QgsRasterAttributeTableWidget::init(
int bandNumber )
101 disconnect( mRasterBandsComboBox, qOverload<int>( &QComboBox::currentIndexChanged ),
this, &QgsRasterAttributeTableWidget::bandChanged );
102 mAttributeTableBuffer =
nullptr;
104 mRasterBandsComboBox->clear();
106 QList<int> availableRats;
110 for (
int checkBandNumber = 1; checkBandNumber <= mRasterLayer->
bandCount(); ++checkBandNumber )
115 mRasterBandsComboBox->addItem( mRasterLayer->
bandName( checkBandNumber ), checkBandNumber );
116 availableRats.push_back( checkBandNumber );
120 if ( !availableRats.isEmpty() )
122 if ( availableRats.contains( bandNumber ) )
124 mCurrentBand = bandNumber;
126 else if ( ! availableRats.isEmpty() )
128 mCurrentBand = availableRats.first();
131 mAttributeTableBuffer = std::make_unique<QgsRasterAttributeTable>( *mRasterLayer->
attributeTable( mCurrentBand ) );
132 mRasterBandsComboBox->setCurrentIndex( availableRats.indexOf( mCurrentBand ) );
136 if ( mAttributeTableBuffer )
139 mModel->setEditable( mEditable );
141 connect( mModel.get(), &QgsRasterAttributeTableModel::dataChanged,
this, [ = ](
const QModelIndex &,
const QModelIndex &,
const QVector<int> & )
146 connect( mModel.get(), &QgsRasterAttributeTableModel::columnsInserted,
this, [ = ](
const QModelIndex &,
int,
int )
151 connect( mModel.get(), &QgsRasterAttributeTableModel::columnsRemoved,
this, [ = ](
const QModelIndex &,
int,
int )
156 static_cast<QSortFilterProxyModel *
>( mRATView->model() )->setSourceModel( mModel.get() );
161 notify( tr(
"No Attribute Tables Available" ), tr(
"The raster layer has no associated attribute tables, you can create a new attribute table or load one from a VAT.DBF file." ),
Qgis::Warning );
164 connect( mRasterBandsComboBox, qOverload<int>( &QComboBox::currentIndexChanged ),
this, &QgsRasterAttributeTableWidget::bandChanged );
169void QgsRasterAttributeTableWidget::updateButtons()
171 const bool enableEditingButtons(
static_cast<bool>( mAttributeTableBuffer ) && mEditable && mRATView->selectionModel()->currentIndex().isValid() );
172 mActionToggleEditing->setChecked( mEditable );
173 mActionAddColumn->setEnabled( mEditable );
174 mActionRemoveColumn->setEnabled( enableEditingButtons );
175 mActionAddRow->setEnabled( enableEditingButtons );
176 mActionRemoveRow->setEnabled( enableEditingButtons );
177 mActionSaveChanges->setEnabled( mAttributeTableBuffer && mAttributeTableBuffer->isDirty() );
178 mClassifyButton->setEnabled( mAttributeTableBuffer && mRasterLayer );
179 mClassifyComboBox->setEnabled( mAttributeTableBuffer && mRasterLayer );
195 const bool isDirty { mAttributeTableBuffer &&mAttributeTableBuffer->isDirty() &&mCurrentBand > 0 && mRasterLayer->
attributeTable( mCurrentBand ) };
196 bool retVal {
true };
200 QMessageBox::StandardButtons buttons { QMessageBox::Button::Yes | QMessageBox::Button::No };
204 buttons |= QMessageBox::Button::Cancel;
207 switch ( QMessageBox::question(
nullptr, tr(
"Save Attribute Table" ), tr(
"Attribute table contains unsaved changes, do you want to save the changes?" ), buttons ) )
209 case QMessageBox::Button::Cancel:
214 case QMessageBox::Button::Yes:
220 case QMessageBox::Button::No:
224 mAttributeTableBuffer = std::make_unique<QgsRasterAttributeTable>( *mRasterLayer->
attributeTable( mCurrentBand ) );
225 init( mCurrentBand );
234 mEditable = editable;
235 mModel->setEditable( editable );
245 if ( mRasterLayer && mAttributeTableBuffer && mAttributeTableBuffer->isDirty() && mCurrentBand > 0 )
248 if ( ! attributeTable )
250 QgsDebugError( QStringLiteral(
"Error saving RAT: RAT for band %1 is unexpectedly gone!" ).arg( mCurrentBand ) );
254 *attributeTable = *mAttributeTableBuffer;
255 QString errorMessage;
256 QString newPath { attributeTable->filePath() };
257 const bool nativeRatSupported = mRasterLayer->
dataProvider()->
providerCapabilities().testFlag( QgsRasterDataProvider::ProviderCapability::NativeRasterAttributeTable );
258 bool saveToNative {
false };
260 if ( newPath.isEmpty() && ! nativeRatSupported )
262 newPath = QFileDialog::getOpenFileName(
nullptr, tr(
"Save Raster Attribute Table (band %1) To File" ).arg( mCurrentBand ), QFile::exists( mRasterLayer->
dataProvider()->
dataSourceUri( ) ) ? mRasterLayer->
dataProvider()->
dataSourceUri( ) +
".vat.dbf" : QString(), QStringLiteral(
"VAT DBF Files (*.vat.dbf)" ) );
263 if ( newPath.isEmpty() )
269 else if ( newPath.isEmpty() )
274 bool writeSuccess {
false };
277 if ( ! saveToNative && ! newPath.isEmpty() )
279 writeSuccess = attributeTable->writeToFile( attributeTable->filePath(), &errorMessage );
281 else if ( saveToNative )
288 mAttributeTableBuffer->setDirty(
false );
289 notify( tr(
"Attribute Table Write Success" ), tr(
"The raster attribute table has been successfully saved." ), Qgis::MessageLevel::Success );
293 notify( tr(
"Attribute Table Write Error" ), errorMessage, Qgis::MessageLevel::Critical );
308void QgsRasterAttributeTableWidget::classify()
311 if ( ! mAttributeTableBuffer )
313 notify( tr(
"Classification Error" ), tr(
"The raster attribute table is not set." ), Qgis::MessageLevel::Critical );
317 if ( ! mRasterLayer )
319 notify( tr(
"Classification Error" ), tr(
"The raster layer is not set." ), Qgis::MessageLevel::Critical );
323 QString confirmMessage;
324 QString errorMessage;
326 if ( ! mAttributeTableBuffer->isValid( &errorMessage ) )
328 confirmMessage = tr(
"The attribute table does not seem to be valid and it may produce an unusable symbology, validation errors:<br>%1<br>" ).arg( errorMessage );
331 if ( QMessageBox::question(
nullptr, tr(
"Apply Style From Attribute Table" ), confirmMessage.append( tr(
"The existing symbology for the raster will be replaced by a new symbology from the attribute table and any unsaved changes to the current symbology will be lost, do you want to proceed?" ) ) ) == QMessageBox::Yes )
334 if (
QgsRasterRenderer *renderer = mAttributeTableBuffer->createRenderer( mRasterLayer->
dataProvider(), mCurrentBand, mClassifyComboBox->currentData().toInt() ) )
342 notify( tr(
"Classification Error" ), tr(
"The classification returned no classes." ), Qgis::MessageLevel::Critical );
347void QgsRasterAttributeTableWidget::addColumn()
349 if ( mAttributeTableBuffer )
352 if ( dlg.exec() == QDialog::Accepted )
354 QString errorMessage;
357 if ( ! mModel->insertColor( dlg.position(), &errorMessage ) )
359 notify( tr(
"Error adding color column" ), errorMessage, Qgis::MessageLevel::Critical );
362 else if ( dlg.isRamp() )
364 if ( ! mModel->insertRamp( dlg.position(), &errorMessage ) )
366 notify( tr(
"Error adding color ramp column" ), errorMessage, Qgis::MessageLevel::Critical );
371 if ( ! mModel->insertField( dlg.position(), dlg.name(), dlg.usage(), dlg.type(), &errorMessage ) )
373 notify( tr(
"Error adding new column" ), errorMessage, Qgis::MessageLevel::Critical );
381void QgsRasterAttributeTableWidget::removeColumn()
383 const QModelIndex currentIndex { mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ) };
384 if ( mAttributeTableBuffer && currentIndex.isValid() && currentIndex.column() < mAttributeTableBuffer->fields().count() )
386 if ( QMessageBox::question(
nullptr, tr(
"Remove Column" ), tr(
"Do you want to remove the selected column? This action cannot be undone." ) ) == QMessageBox::Yes )
388 QString errorMessage;
389 if ( ! mModel->removeField( currentIndex.column(), &errorMessage ) )
391 notify( tr(
"Error removing column" ), errorMessage, Qgis::MessageLevel::Critical );
397void QgsRasterAttributeTableWidget::addRow()
399 if ( mAttributeTableBuffer )
402 int position { mModel->rowCount( QModelIndex() ) };
403 const QModelIndex currentIndex { mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ) };
406 if ( currentIndex.isValid() )
411 if ( dlg.exec() != QDialog::DialogCode::Accepted )
417 position = currentIndex.row() + ( dlg.
insertAfter() ? 1 : 0 );
421 bool result {
true };
422 QString errorMessage;
424 QVariantList rowData;
426 QList<QgsRasterAttributeTable::Field> fields { mAttributeTableBuffer->fields() };
429 rowData.push_back( QVariant(
field.
type ) );
432 result = mModel->insertRow( position, rowData, &errorMessage );
436 notify( tr(
"Error adding row" ), errorMessage, Qgis::MessageLevel::Critical );
440 mRATView->scrollTo( mRATView->model()->index( position, 0 ) );
445void QgsRasterAttributeTableWidget::removeRow()
447 if ( mAttributeTableBuffer && mRATView->selectionModel()->currentIndex().isValid() )
449 if ( QMessageBox::question(
nullptr, tr(
"Remove Row" ), tr(
"Do you want to remove the selected row? This action cannot be undone." ) ) == QMessageBox::Yes )
451 QString errorMessage;
452 if ( ! mModel->removeRow( mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ).row(), &errorMessage ) )
454 notify( tr(
"Error removing row" ), errorMessage, Qgis::MessageLevel::Critical );
460void QgsRasterAttributeTableWidget::bandChanged(
const int index )
462 const QVariant itemData = mRasterBandsComboBox->itemData( index );
464 if ( itemData.isValid() )
470 init( itemData.toInt( ) );
474void QgsRasterAttributeTableWidget::notify(
const QString &title,
const QString &message,
Qgis::MessageLevel level )
484 case Qgis::MessageLevel::Info:
485 case Qgis::MessageLevel::Success:
486 case Qgis::MessageLevel::NoLevel:
488 QMessageBox::information(
nullptr, title, message );
491 case Qgis::MessageLevel::Warning:
493 QMessageBox::warning(
nullptr, title, message );
496 case Qgis::MessageLevel::Critical:
498 QMessageBox::critical(
nullptr, title, message );
505void QgsRasterAttributeTableWidget::setDelegates()
507 mClassifyComboBox->clear();
508 if ( mAttributeTableBuffer )
510 const QList<QgsRasterAttributeTable::Field> tableFields { mAttributeTableBuffer->fields() };
517 mRATView->setItemDelegateForColumn( fieldIdx,
nullptr );
519 if ( usageInfo[f.usage].maybeClass )
525 if ( ( ! f.isColor() && ! f.isRamp() ) && f.type == QVariant::Type::Double )
527 mRATView->setItemDelegateForColumn( fieldIdx,
new LocalizedDoubleDelegate( mRATView ) );
533 if ( mAttributeTableBuffer->hasColor() )
537 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count( ),
new ColorAlphaDelegate( mRATView ) );
541 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count( ),
new ColorDelegate( mRATView ) );
544 else if ( mAttributeTableBuffer->hasRamp() )
548 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count( ),
new ColorRampAlphaDelegate( mRATView ) );
552 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count( ),
new ColorRampDelegate( mRATView ) );
561QWidget *ColorDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &,
const QModelIndex & )
const
566void ColorDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
568 const QColor color { index.data( Qt::ItemDataRole::EditRole ).value<QColor>() };
572void ColorDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
574 const QColor color {
static_cast<QgsColorButton *
>( editor )->color( ) };
575 model->setData( index, color );
578QWidget *ColorAlphaDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
580 QWidget *editor { ColorDelegate::createEditor( parent, option, index ) };
585QWidget *ColorRampDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &,
const QModelIndex & )
const
591void ColorRampDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
593 const QgsGradientColorRamp ramp { qvariant_cast<QgsGradientColorRamp>( index.data( Qt::ItemDataRole::EditRole ) ) };
597void ColorRampDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
602void ColorRampDelegate::paint( QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
604 const QgsGradientColorRamp ramp { qvariant_cast<QgsGradientColorRamp>( index.data( Qt::ItemDataRole::EditRole ) ) };
605 QLinearGradient gradient( QPointF( 0, 0 ), QPointF( 1, 0 ) );
606 gradient.setCoordinateMode( QGradient::CoordinateMode::ObjectBoundingMode );
607 gradient.setColorAt( 0, ramp.color1() );
608 gradient.setColorAt( 1, ramp.color2() );
609 const QRect r = option.rect.adjusted( 1, 1, -1, -1 );
611 painter->fillRect( r, QBrush{ gradient } );
614QWidget *ColorRampAlphaDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
616 QWidget *editor { ColorRampDelegate::createEditor( parent, option, index ) };
622QString LocalizedDoubleDelegate::displayText(
const QVariant &value,
const QLocale &locale )
const
625 const QString s( value.toString() );
626 const int dotPosition( s.indexOf(
'.' ) );
628 if ( dotPosition < 0 && s.indexOf(
'e' ) < 0 )
631 return QLocale().toString( value.toDouble(),
'f',
precision );
636 else precision = s.length() - dotPosition - 1;
638 if ( -1 < value.toDouble() && value.toDouble() < 1 )
640 return QLocale().toString( value.toDouble(),
'g',
precision );
644 return QLocale().toString( value.toDouble(),
'f',
precision );
647 return QLocale().toString( value.toDouble( ),
'f' );
MessageLevel
Level for messages This will be used both for message log and message bar in application.
@ Warning
Warning message.
@ Alpha
Field usage Alpha.
@ AlphaMin
Field usage AlphaMin.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
virtual QString dataSourceUri(bool expandAuthConfig=false) const
Gets the data source specification.
static QIcon iconForFieldType(QVariant::Type type, QVariant::Type subType=QVariant::Type::Invalid, const QString &typeString=QString())
Returns an icon corresponding to a field type.
A dialog which allows users to modify the properties of a QgsGradientColorRamp.
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
void triggerRepaint(bool deferredUpdate=false)
Will advise the map canvas (and any other interested party) that this layer requires to be repainted.
A bar for displaying non-blocking messages to the user.
void pushMessage(const QString &text, Qgis::MessageLevel level=Qgis::MessageLevel::Info, int duration=-1)
A convenience method for pushing a message with the specified text to the bar.
The QgsRasterAttributeTableAddColumnDialog class collects options to add a new column to a raster att...
The QgsRasterAttributeTableAddColumnDialog class collects options to add a new row to a raster attrib...
bool insertAfter() const
Returns true if the desired insertion position for the new row is after the currently selected row,...
The QgsRasterAttributeTableModel class manages a QgsRasterAttributeTable.
The Field class represents a Raster Attribute Table field, including its name, usage and type.
The QgsRasterAttributeTable class represents a Raster Attribute Table (RAT).
static QHash< Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation > usageInformation()
Returns information about supported Raster Attribute Table usages.
virtual bool writeNativeAttributeTable(QString *errorMessage=nullptr)
Writes the native attribute table, optionally reporting any error in errorMessage,...
QgsRasterAttributeTable * attributeTable(int bandNumber) const
Returns the (possibly NULL) attribute table for the specified bandNumber.
virtual QgsRasterDataProvider::ProviderCapabilities providerCapabilities() const
Returns flags containing the supported capabilities of the data provider.
Represents a raster layer.
QgsRasterAttributeTable * attributeTable(int bandNumber) const
Returns the (possibly NULL) raster attribute table for the given band bandNumber.
int bandCount() const
Returns the number of bands in this layer.
QString bandName(int bandNoInt) const
Returns the name of a band given its number.
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
void setRenderer(QgsRasterRenderer *renderer)
Sets the raster's renderer.
Raster renderer pipe that applies colors to a raster.
Scoped object for saving and restoring a QPainter object's state.
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
#define QgsDebugError(str)