28#include <QSortFilterProxyModel>
32QgsRasterAttributeTableWidget::QgsRasterAttributeTableWidget( QWidget *parent,
QgsRasterLayer *rasterLayer,
const int bandNumber )
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 )
46 setEditable( 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 );
68 connect( mActionSaveChanges, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::saveChanges );
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 );
87void QgsRasterAttributeTableWidget::setRasterLayer(
QgsRasterLayer *rasterLayer,
const int bandNumber )
89 mRasterLayer = rasterLayer;
93bool QgsRasterAttributeTableWidget::isDirty()
const
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 )
113 if ( mRasterLayer->attributeTable( 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( mAttributeTableBuffer && mRasterLayer );
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 );
183void QgsRasterAttributeTableWidget::setDockMode(
bool dockMode )
189void QgsRasterAttributeTableWidget::setMessageBar(
QgsMessageBar *bar )
194bool QgsRasterAttributeTableWidget::setEditable(
bool editable,
bool allowCancel )
196 const bool isDirty { mAttributeTableBuffer &&mAttributeTableBuffer->isDirty() &&mCurrentBand > 0 && mRasterLayer->attributeTable( mCurrentBand ) };
197 bool retVal {
true };
199 if ( ! editable && isDirty )
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 );
244void QgsRasterAttributeTableWidget::saveChanges()
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() };
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 )
284 writeSuccess = mRasterLayer->dataProvider()->writeNativeAttributeTable( &errorMessage );
289 mAttributeTableBuffer->setDirty(
false );
290 notify( tr(
"Attribute Table Write Success" ), tr(
"The raster attribute table has been successfully saved." ),
Qgis::MessageLevel::Success );
309void QgsRasterAttributeTableWidget::classify()
312 if ( ! mAttributeTableBuffer )
318 if ( ! mRasterLayer )
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() ) )
337 mRasterLayer->setRenderer( renderer );
338 mRasterLayer->triggerRepaint( );
339 emit rendererChanged();
348void QgsRasterAttributeTableWidget::addColumn()
350 if ( mAttributeTableBuffer )
353 if ( dlg.exec() == QDialog::Accepted )
355 QString errorMessage;
358 if ( ! mModel->insertColor( dlg.position(), &errorMessage ) )
363 else if ( dlg.isRamp() )
365 if ( ! mModel->insertRamp( dlg.position(), &errorMessage ) )
372 if ( ! mModel->insertField( dlg.position(), dlg.name(), dlg.usage(), dlg.type(), &errorMessage ) )
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 ) )
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() };
433 result = mModel->insertRow( position, rowData, &errorMessage );
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 ) )
461void QgsRasterAttributeTableWidget::bandChanged(
const int index )
463 const QVariant itemData = mRasterBandsComboBox->itemData( index );
465 if ( itemData.isValid() )
469 setEditable(
false );
471 init( itemData.toInt( ) );
475void QgsRasterAttributeTableWidget::notify(
const QString &title,
const QString &message,
Qgis::MessageLevel level )
479 mMessageBar->pushMessage( message, level );
489 QMessageBox::information(
nullptr, title, message );
494 QMessageBox::warning(
nullptr, title, message );
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 == QMetaType::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' );
@ NativeRasterAttributeTable
Indicates that the provider supports native raster attribute table.
MessageLevel
Level for messages This will be used both for message log and message bar in application.
@ Warning
Warning message.
@ Critical
Critical/error message.
@ Info
Information message.
@ Success
Used for reporting a successful operation.
@ 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.
static QIcon iconForFieldType(QMetaType::Type type, QMetaType::Type subType=QMetaType::Type::UnknownType, 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 ...
A bar for displaying non-blocking messages to the user.
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.
Represents a raster layer.
Raster renderer pipe that applies colors to a raster.
Scoped object for saving and restoring a QPainter object's state.
static QVariant createNullVariant(QMetaType::Type metaType)
Helper method to properly create a null QVariant from a metaType Returns the created QVariant.
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
#define QgsDebugError(str)