32#include <QSortFilterProxyModel>
36#include "moc_qgsrasterattributetablewidget.cpp"
38using namespace Qt::StringLiterals;
40QgsRasterAttributeTableWidget::QgsRasterAttributeTableWidget( QWidget *parent,
QgsRasterLayer *rasterLayer,
const int bandNumber )
42 , mRasterLayer( rasterLayer )
47 QToolBar *editToolBar =
new QToolBar(
this );
50 mActionToggleEditing =
new QAction(
QgsApplication::getThemeIcon(
"/mActionEditTable.svg" ), tr(
"&Edit Attribute Table" ), editToolBar );
51 mActionToggleEditing->setCheckable(
true );
52 connect( mActionToggleEditing, &QAction::triggered,
this, [
this](
bool editable ) { setEditable( editable ); } );
54 editToolBar->addAction( mActionToggleEditing );
57 connect( mActionAddColumn, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::addColumn );
58 editToolBar->addAction( mActionAddColumn );
61 connect( mActionAddRow, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::addRow );
62 editToolBar->addAction( mActionAddRow );
65 connect( mActionRemoveRow, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::removeRow );
66 editToolBar->addAction( mActionRemoveRow );
69 connect( mActionRemoveColumn, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::removeColumn );
70 editToolBar->addAction( mActionRemoveColumn );
73 connect( mActionSaveChanges, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::saveChanges );
74 editToolBar->addAction( mActionSaveChanges );
76 layout()->setMenuBar( editToolBar );
78 connect( mClassifyButton, &QPushButton::clicked,
this, &QgsRasterAttributeTableWidget::classify );
80 mProxyModel =
new QSortFilterProxyModel(
this );
82 mRATView->setModel( mProxyModel );
84 connect( mRATView->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &QgsRasterAttributeTableWidget::updateButtons );
92void QgsRasterAttributeTableWidget::setRasterLayer(
QgsRasterLayer *rasterLayer,
const int bandNumber )
94 mRasterLayer = rasterLayer;
98bool QgsRasterAttributeTableWidget::isDirty()
const
100 return mAttributeTableBuffer && mAttributeTableBuffer->isDirty();
103void QgsRasterAttributeTableWidget::init(
int bandNumber )
105 disconnect( mRasterBandsComboBox, qOverload<int>( &QComboBox::currentIndexChanged ),
this, &QgsRasterAttributeTableWidget::bandChanged );
106 mAttributeTableBuffer =
nullptr;
108 mRasterBandsComboBox->clear();
110 QList<int> availableRats;
114 for (
int checkBandNumber = 1; checkBandNumber <= mRasterLayer->bandCount(); ++checkBandNumber )
117 if ( mRasterLayer->attributeTable( checkBandNumber ) )
119 mRasterBandsComboBox->addItem( mRasterLayer->bandName( checkBandNumber ), checkBandNumber );
120 availableRats.push_back( checkBandNumber );
124 if ( !availableRats.isEmpty() )
126 if ( availableRats.contains( bandNumber ) )
128 mCurrentBand = bandNumber;
130 else if ( !availableRats.isEmpty() )
132 mCurrentBand = availableRats.first();
135 mAttributeTableBuffer = std::make_unique<QgsRasterAttributeTable>( *mRasterLayer->attributeTable( mCurrentBand ) );
136 mRasterBandsComboBox->setCurrentIndex( availableRats.indexOf( mCurrentBand ) );
140 if ( mAttributeTableBuffer )
142 mModel = std::make_unique<QgsRasterAttributeTableModel>( mAttributeTableBuffer.get() );
143 mModel->setEditable( mEditable );
145 connect( mModel.get(), &QgsRasterAttributeTableModel::dataChanged,
this, [
this](
const QModelIndex &,
const QModelIndex &,
const QVector<int> & ) { updateButtons(); } );
147 connect( mModel.get(), &QgsRasterAttributeTableModel::columnsInserted,
this, [
this](
const QModelIndex &,
int,
int ) { setDelegates(); } );
149 connect( mModel.get(), &QgsRasterAttributeTableModel::columnsRemoved,
this, [
this](
const QModelIndex &,
int,
int ) { setDelegates(); } );
151 static_cast<QSortFilterProxyModel *
>( mRATView->model() )->setSourceModel( mModel.get() );
156 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 );
159 connect( mRasterBandsComboBox, qOverload<int>( &QComboBox::currentIndexChanged ),
this, &QgsRasterAttributeTableWidget::bandChanged );
164void QgsRasterAttributeTableWidget::updateButtons()
166 const bool enableEditingButtons(
static_cast<bool>( mAttributeTableBuffer ) && mEditable && mRATView->selectionModel()->currentIndex().isValid() );
167 mActionToggleEditing->setChecked( mEditable );
168 mActionToggleEditing->setEnabled( mAttributeTableBuffer && mRasterLayer );
169 mActionAddColumn->setEnabled( mEditable );
170 mActionRemoveColumn->setEnabled( enableEditingButtons );
171 mActionAddRow->setEnabled( enableEditingButtons );
172 mActionRemoveRow->setEnabled( enableEditingButtons );
173 mActionSaveChanges->setEnabled( mAttributeTableBuffer && mAttributeTableBuffer->isDirty() );
174 mClassifyButton->setEnabled( mAttributeTableBuffer && mRasterLayer );
175 mClassifyComboBox->setEnabled( mAttributeTableBuffer && mRasterLayer );
178void QgsRasterAttributeTableWidget::setDockMode(
bool dockMode )
184void QgsRasterAttributeTableWidget::setMessageBar(
QgsMessageBar *bar )
189bool QgsRasterAttributeTableWidget::setEditable(
bool editable,
bool allowCancel )
191 const bool isDirty { mAttributeTableBuffer && mAttributeTableBuffer->isDirty() && mCurrentBand > 0 && mRasterLayer->attributeTable( mCurrentBand ) };
192 bool retVal {
true };
194 if ( !editable && isDirty )
196 QMessageBox::StandardButtons buttons { QMessageBox::Button::Yes | QMessageBox::Button::No };
200 buttons |= QMessageBox::Button::Cancel;
203 switch ( QMessageBox::question(
nullptr, tr(
"Save Attribute Table" ), tr(
"Attribute table contains unsaved changes, do you want to save the changes?" ), buttons ) )
205 case QMessageBox::Button::Cancel:
210 case QMessageBox::Button::Yes:
216 case QMessageBox::Button::No:
220 mAttributeTableBuffer = std::make_unique<QgsRasterAttributeTable>( *mRasterLayer->attributeTable( mCurrentBand ) );
221 init( mCurrentBand );
230 mEditable = editable;
231 mModel->setEditable( editable );
239void QgsRasterAttributeTableWidget::saveChanges()
241 if ( mRasterLayer && mAttributeTableBuffer && mAttributeTableBuffer->isDirty() && mCurrentBand > 0 )
243 QgsRasterAttributeTable *attributeTable { mRasterLayer->dataProvider()->attributeTable( mCurrentBand ) };
244 if ( !attributeTable )
246 QgsDebugError( u
"Error saving RAT: RAT for band %1 is unexpectedly gone!"_s.arg( mCurrentBand ) );
250 *attributeTable = *mAttributeTableBuffer;
251 QString errorMessage;
252 QString newPath { attributeTable->
filePath() };
254 bool saveToNative {
false };
256 if ( newPath.isEmpty() && !nativeRatSupported )
258 newPath = QFileDialog::getOpenFileName(
260 tr(
"Save Raster Attribute Table (band %1) To File" ).arg( mCurrentBand ),
261 QFile::exists( mRasterLayer->dataProvider()->dataSourceUri() ) ? mRasterLayer->dataProvider()->dataSourceUri() +
".vat.dbf" : QString(),
262 u
"VAT DBF Files (*.vat.dbf)"_s
264 if ( newPath.isEmpty() )
270 else if ( newPath.isEmpty() )
275 bool writeSuccess {
false };
278 if ( !saveToNative && !newPath.isEmpty() )
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 );
308void QgsRasterAttributeTableWidget::classify()
310 if ( !mAttributeTableBuffer )
322 QString confirmMessage;
323 QString errorMessage;
325 if ( !mAttributeTableBuffer->isValid( &errorMessage ) )
327 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 );
330 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?" ) ) )
331 == QMessageBox::Yes )
333 if ( QgsRasterRenderer *renderer = mAttributeTableBuffer->createRenderer( mRasterLayer->dataProvider(), mCurrentBand, mClassifyComboBox->currentData().toInt() ) )
335 mRasterLayer->setRenderer( renderer );
336 mRasterLayer->triggerRepaint();
337 emit rendererChanged();
346void QgsRasterAttributeTableWidget::addColumn()
348 if ( mAttributeTableBuffer )
350 QgsRasterAttributeTableAddColumnDialog dlg { mAttributeTableBuffer.get() };
351 if ( dlg.exec() == QDialog::Accepted )
353 QString errorMessage;
356 if ( !mModel->insertColor( dlg.
position(), &errorMessage ) )
363 if ( !mModel->insertRamp( dlg.
position(), &errorMessage ) )
380void QgsRasterAttributeTableWidget::removeColumn()
382 const QModelIndex currentIndex { mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ) };
383 if ( mAttributeTableBuffer && currentIndex.isValid() && currentIndex.column() < mAttributeTableBuffer->fields().count() )
385 if ( QMessageBox::question(
nullptr, tr(
"Remove Column" ), tr(
"Do you want to remove the selected column? This action cannot be undone." ) ) == QMessageBox::Yes )
387 QString errorMessage;
388 if ( !mModel->removeField( currentIndex.column(), &errorMessage ) )
396void QgsRasterAttributeTableWidget::addRow()
398 if ( mAttributeTableBuffer )
401 int position { mModel->rowCount( QModelIndex() ) };
402 const QModelIndex currentIndex { mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ) };
405 if ( currentIndex.isValid() )
409 QgsRasterAttributeTableAddRowDialog dlg;
410 if ( dlg.exec() != QDialog::DialogCode::Accepted )
416 position = currentIndex.row() + ( dlg.
insertAfter() ? 1 : 0 );
420 bool result {
true };
421 QString errorMessage;
423 QVariantList rowData;
425 QList<QgsRasterAttributeTable::Field> fields { mAttributeTableBuffer->fields() };
426 for (
const QgsRasterAttributeTable::Field &field : std::as_const( fields ) )
431 result = mModel->insertRow( position, rowData, &errorMessage );
439 mRATView->scrollTo( mRATView->model()->index( position, 0 ) );
444void QgsRasterAttributeTableWidget::removeRow()
446 if ( mAttributeTableBuffer && mRATView->selectionModel()->currentIndex().isValid() )
448 if ( QMessageBox::question(
nullptr, tr(
"Remove Row" ), tr(
"Do you want to remove the selected row? This action cannot be undone." ) ) == QMessageBox::Yes )
450 QString errorMessage;
451 if ( !mModel->removeRow( mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ).row(), &errorMessage ) )
459void QgsRasterAttributeTableWidget::bandChanged(
const int index )
461 const QVariant itemData = mRasterBandsComboBox->itemData( index );
463 if ( itemData.isValid() )
467 setEditable(
false );
469 init( itemData.toInt() );
473void QgsRasterAttributeTableWidget::notify(
const QString &title,
const QString &message,
Qgis::MessageLevel level )
477 mMessageBar->pushMessage( message, level );
487 QMessageBox::information(
nullptr, title, message );
492 QMessageBox::warning(
nullptr, title, message );
497 QMessageBox::critical(
nullptr, title, message );
504void QgsRasterAttributeTableWidget::setDelegates()
506 mClassifyComboBox->clear();
507 if ( mAttributeTableBuffer )
509 const QList<QgsRasterAttributeTable::Field> tableFields { mAttributeTableBuffer->fields() };
513 for (
const QgsRasterAttributeTable::Field &f : std::as_const( tableFields ) )
516 mRATView->setItemDelegateForColumn( fieldIdx,
nullptr );
518 if ( usageInfo[f.usage].maybeClass )
524 if ( ( !f.isColor() && !f.isRamp() ) && f.type == QMetaType::Type::Double )
526 mRATView->setItemDelegateForColumn( fieldIdx,
new LocalizedDoubleDelegate( mRATView ) );
532 if ( mAttributeTableBuffer->hasColor() )
536 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count(),
new ColorAlphaDelegate( mRATView ) );
540 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count(),
new ColorDelegate( mRATView ) );
543 else if ( mAttributeTableBuffer->hasRamp() )
547 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count(),
new ColorRampAlphaDelegate( mRATView ) );
551 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count(),
new ColorRampDelegate( mRATView ) );
560QWidget *ColorDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &,
const QModelIndex & )
const
565void ColorDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
567 const QColor color { index.data( Qt::ItemDataRole::EditRole ).value<QColor>() };
571void ColorDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
573 const QColor color {
static_cast<QgsColorButton *
>( editor )->color() };
574 model->setData( index, color );
577QWidget *ColorAlphaDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
579 QWidget *editor { ColorDelegate::createEditor( parent, option, index ) };
584QWidget *ColorRampDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &,
const QModelIndex & )
const
590void ColorRampDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
592 const QgsGradientColorRamp ramp { qvariant_cast<QgsGradientColorRamp>( index.data( Qt::ItemDataRole::EditRole ) ) };
596void ColorRampDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
601void ColorRampDelegate::paint( QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
603 const QgsGradientColorRamp ramp { qvariant_cast<QgsGradientColorRamp>( index.data( Qt::ItemDataRole::EditRole ) ) };
604 QLinearGradient gradient( QPointF( 0, 0 ), QPointF( 1, 0 ) );
605 gradient.setCoordinateMode( QGradient::CoordinateMode::ObjectBoundingMode );
606 gradient.setColorAt( 0, ramp.
color1() );
607 gradient.setColorAt( 1, ramp.
color2() );
608 const QRect r = option.rect.adjusted( 1, 1, -1, -1 );
610 painter->fillRect( r, QBrush { gradient } );
613QWidget *ColorRampAlphaDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
615 QWidget *editor { ColorRampDelegate::createEditor( parent, option, index ) };
621QString LocalizedDoubleDelegate::displayText(
const QVariant &value,
const QLocale &locale )
const
624 const QString s( value.toString() );
625 const int dotPosition( s.indexOf(
'.' ) );
627 if ( dotPosition < 0 && s.indexOf(
'e' ) < 0 )
630 return QLocale().toString( value.toDouble(),
'f', precision );
634 if ( dotPosition < 0 )
637 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 );
@ 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 ...
QColor color1() const
Returns the gradient start color.
QColor color2() const
Returns the gradient end color.
A bar for displaying non-blocking messages to the user.
QMetaType::Type type() const
Returns the new column type.
bool isRamp() const
Returns true if the add color ramp column option was checked.
bool isColor() const
Returns true if the add color column option was checked.
int position() const
Returns the position where the new column (before) will be inserted.
QString name() const
Returns the new column name.
Qgis::RasterAttributeTableFieldUsage usage() const
Returns the new column name.
bool insertAfter() const
Returns true if the desired insertion position for the new row is after the currently selected row,...
static QHash< Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation > usageInformation()
Returns information about supported Raster Attribute Table usages.
bool writeToFile(const QString &path, QString *errorMessage=nullptr)
Writes the Raster Attribute Table to a DBF file specified by path, optionally reporting any error in ...
QString filePath() const
Returns the (possibly empty) path of the file-based RAT, the path is set when a RAT is read or writte...
Represents a raster layer.
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)