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 mActionToggleEditing->setEnabled( enableEditingButtons );
174 mActionAddColumn->setEnabled( mEditable );
175 mActionRemoveColumn->setEnabled( enableEditingButtons );
176 mActionAddRow->setEnabled( enableEditingButtons );
177 mActionRemoveRow->setEnabled( enableEditingButtons );
178 mActionSaveChanges->setEnabled( mAttributeTableBuffer && mAttributeTableBuffer->isDirty() );
179 mClassifyButton->setEnabled( mAttributeTableBuffer && mRasterLayer );
180 mClassifyComboBox->setEnabled( mAttributeTableBuffer && mRasterLayer );
196 const bool isDirty { mAttributeTableBuffer &&mAttributeTableBuffer->isDirty() &&mCurrentBand > 0 && mRasterLayer->
attributeTable( mCurrentBand ) };
197 bool retVal {
true };
201 QMessageBox::StandardButtons buttons { QMessageBox::Button::Yes | QMessageBox::Button::No };
205 buttons |= QMessageBox::Button::Cancel;
208 switch ( QMessageBox::question(
nullptr, tr(
"Save Attribute Table" ), tr(
"Attribute table contains unsaved changes, do you want to save the changes?" ), buttons ) )
210 case QMessageBox::Button::Cancel:
215 case QMessageBox::Button::Yes:
221 case QMessageBox::Button::No:
225 mAttributeTableBuffer = std::make_unique<QgsRasterAttributeTable>( *mRasterLayer->
attributeTable( mCurrentBand ) );
226 init( mCurrentBand );
235 mEditable = editable;
236 mModel->setEditable( editable );
246 if ( mRasterLayer && mAttributeTableBuffer && mAttributeTableBuffer->isDirty() && mCurrentBand > 0 )
249 if ( ! attributeTable )
251 QgsDebugError( QStringLiteral(
"Error saving RAT: RAT for band %1 is unexpectedly gone!" ).arg( mCurrentBand ) );
255 *attributeTable = *mAttributeTableBuffer;
256 QString errorMessage;
257 QString newPath { attributeTable->filePath() };
258 const bool nativeRatSupported = mRasterLayer->
dataProvider()->
providerCapabilities().testFlag( QgsRasterDataProvider::ProviderCapability::NativeRasterAttributeTable );
259 bool saveToNative {
false };
261 if ( newPath.isEmpty() && ! nativeRatSupported )
263 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)" ) );
264 if ( newPath.isEmpty() )
270 else if ( newPath.isEmpty() )
275 bool writeSuccess {
false };
278 if ( ! saveToNative && ! newPath.isEmpty() )
280 writeSuccess = attributeTable->writeToFile( attributeTable->filePath(), &errorMessage );
282 else if ( saveToNative )
289 mAttributeTableBuffer->setDirty(
false );
290 notify( tr(
"Attribute Table Write Success" ), tr(
"The raster attribute table has been successfully saved." ), Qgis::MessageLevel::Success );
294 notify( tr(
"Attribute Table Write Error" ), errorMessage, Qgis::MessageLevel::Critical );
309void QgsRasterAttributeTableWidget::classify()
312 if ( ! mAttributeTableBuffer )
314 notify( tr(
"Classification Error" ), tr(
"The raster attribute table is not set." ), Qgis::MessageLevel::Critical );
318 if ( ! mRasterLayer )
320 notify( tr(
"Classification Error" ), tr(
"The raster layer is not set." ), Qgis::MessageLevel::Critical );
324 QString confirmMessage;
325 QString errorMessage;
327 if ( ! mAttributeTableBuffer->isValid( &errorMessage ) )
329 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 );
332 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 )
335 if (
QgsRasterRenderer *renderer = mAttributeTableBuffer->createRenderer( mRasterLayer->
dataProvider(), mCurrentBand, mClassifyComboBox->currentData().toInt() ) )
343 notify( tr(
"Classification Error" ), tr(
"The classification returned no classes." ), Qgis::MessageLevel::Critical );
348void QgsRasterAttributeTableWidget::addColumn()
350 if ( mAttributeTableBuffer )
353 if ( dlg.exec() == QDialog::Accepted )
355 QString errorMessage;
358 if ( ! mModel->insertColor( dlg.position(), &errorMessage ) )
360 notify( tr(
"Error adding color column" ), errorMessage, Qgis::MessageLevel::Critical );
363 else if ( dlg.isRamp() )
365 if ( ! mModel->insertRamp( dlg.position(), &errorMessage ) )
367 notify( tr(
"Error adding color ramp column" ), errorMessage, Qgis::MessageLevel::Critical );
372 if ( ! mModel->insertField( dlg.position(), dlg.name(), dlg.usage(), dlg.type(), &errorMessage ) )
374 notify( tr(
"Error adding new column" ), errorMessage, Qgis::MessageLevel::Critical );
382void QgsRasterAttributeTableWidget::removeColumn()
384 const QModelIndex currentIndex { mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ) };
385 if ( mAttributeTableBuffer && currentIndex.isValid() && currentIndex.column() < mAttributeTableBuffer->fields().count() )
387 if ( QMessageBox::question(
nullptr, tr(
"Remove Column" ), tr(
"Do you want to remove the selected column? This action cannot be undone." ) ) == QMessageBox::Yes )
389 QString errorMessage;
390 if ( ! mModel->removeField( currentIndex.column(), &errorMessage ) )
392 notify( tr(
"Error removing column" ), errorMessage, Qgis::MessageLevel::Critical );
398void QgsRasterAttributeTableWidget::addRow()
400 if ( mAttributeTableBuffer )
403 int position { mModel->rowCount( QModelIndex() ) };
404 const QModelIndex currentIndex { mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ) };
407 if ( currentIndex.isValid() )
412 if ( dlg.exec() != QDialog::DialogCode::Accepted )
418 position = currentIndex.row() + ( dlg.
insertAfter() ? 1 : 0 );
422 bool result {
true };
423 QString errorMessage;
425 QVariantList rowData;
427 QList<QgsRasterAttributeTable::Field> fields { mAttributeTableBuffer->fields() };
430 rowData.push_back( QVariant( field.type ) );
433 result = mModel->insertRow( position, rowData, &errorMessage );
437 notify( tr(
"Error adding row" ), errorMessage, Qgis::MessageLevel::Critical );
441 mRATView->scrollTo( mRATView->model()->index( position, 0 ) );
446void QgsRasterAttributeTableWidget::removeRow()
448 if ( mAttributeTableBuffer && mRATView->selectionModel()->currentIndex().isValid() )
450 if ( QMessageBox::question(
nullptr, tr(
"Remove Row" ), tr(
"Do you want to remove the selected row? This action cannot be undone." ) ) == QMessageBox::Yes )
452 QString errorMessage;
453 if ( ! mModel->removeRow( mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ).row(), &errorMessage ) )
455 notify( tr(
"Error removing row" ), errorMessage, Qgis::MessageLevel::Critical );
461void QgsRasterAttributeTableWidget::bandChanged(
const int index )
463 const QVariant itemData = mRasterBandsComboBox->itemData( index );
465 if ( itemData.isValid() )
471 init( itemData.toInt( ) );
475void QgsRasterAttributeTableWidget::notify(
const QString &title,
const QString &message,
Qgis::MessageLevel level )
485 case Qgis::MessageLevel::Info:
486 case Qgis::MessageLevel::Success:
487 case Qgis::MessageLevel::NoLevel:
489 QMessageBox::information(
nullptr, title, message );
492 case Qgis::MessageLevel::Warning:
494 QMessageBox::warning(
nullptr, title, message );
497 case Qgis::MessageLevel::Critical:
499 QMessageBox::critical(
nullptr, title, message );
506void QgsRasterAttributeTableWidget::setDelegates()
508 mClassifyComboBox->clear();
509 if ( mAttributeTableBuffer )
511 const QList<QgsRasterAttributeTable::Field> tableFields { mAttributeTableBuffer->fields() };
518 mRATView->setItemDelegateForColumn( fieldIdx,
nullptr );
520 if ( usageInfo[f.usage].maybeClass )
526 if ( ( ! f.isColor() && ! f.isRamp() ) && f.type == QVariant::Type::Double )
528 mRATView->setItemDelegateForColumn( fieldIdx,
new LocalizedDoubleDelegate( mRATView ) );
534 if ( mAttributeTableBuffer->hasColor() )
538 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count( ),
new ColorAlphaDelegate( mRATView ) );
542 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count( ),
new ColorDelegate( mRATView ) );
545 else if ( mAttributeTableBuffer->hasRamp() )
549 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count( ),
new ColorRampAlphaDelegate( mRATView ) );
553 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count( ),
new ColorRampDelegate( mRATView ) );
562QWidget *ColorDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &,
const QModelIndex & )
const
567void ColorDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
569 const QColor color { index.data( Qt::ItemDataRole::EditRole ).value<QColor>() };
573void ColorDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
575 const QColor color {
static_cast<QgsColorButton *
>( editor )->color( ) };
576 model->setData( index, color );
579QWidget *ColorAlphaDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
581 QWidget *editor { ColorDelegate::createEditor( parent, option, index ) };
586QWidget *ColorRampDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &,
const QModelIndex & )
const
592void ColorRampDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
594 const QgsGradientColorRamp ramp { qvariant_cast<QgsGradientColorRamp>( index.data( Qt::ItemDataRole::EditRole ) ) };
598void ColorRampDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
603void ColorRampDelegate::paint( QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
605 const QgsGradientColorRamp ramp { qvariant_cast<QgsGradientColorRamp>( index.data( Qt::ItemDataRole::EditRole ) ) };
606 QLinearGradient gradient( QPointF( 0, 0 ), QPointF( 1, 0 ) );
607 gradient.setCoordinateMode( QGradient::CoordinateMode::ObjectBoundingMode );
608 gradient.setColorAt( 0, ramp.color1() );
609 gradient.setColorAt( 1, ramp.color2() );
610 const QRect r = option.rect.adjusted( 1, 1, -1, -1 );
612 painter->fillRect( r, QBrush{ gradient } );
615QWidget *ColorRampAlphaDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
617 QWidget *editor { ColorRampDelegate::createEditor( parent, option, index ) };
623QString LocalizedDoubleDelegate::displayText(
const QVariant &value,
const QLocale &locale )
const
626 const QString s( value.toString() );
627 const int dotPosition( s.indexOf(
'.' ) );
629 if ( dotPosition < 0 && s.indexOf(
'e' ) < 0 )
632 return QLocale().toString( value.toDouble(),
'f',
precision );
637 else precision = s.length() - dotPosition - 1;
639 if ( -1 < value.toDouble() && value.toDouble() < 1 )
641 return QLocale().toString( value.toDouble(),
'g',
precision );
645 return QLocale().toString( value.toDouble(),
'f',
precision );
648 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)