34#include <QSortFilterProxyModel>
38#include "moc_qgsrasterattributetablewidget.cpp"
40using namespace Qt::StringLiterals;
42QgsRasterAttributeTableWidget::QgsRasterAttributeTableWidget( QWidget *parent,
QgsRasterLayer *rasterLayer,
const int bandNumber )
44 , mRasterLayer( rasterLayer )
49 QToolBar *editToolBar =
new QToolBar(
this );
52 mActionToggleEditing =
new QAction(
QgsApplication::getThemeIcon(
"/mActionEditTable.svg" ), tr(
"&Edit Attribute Table" ), editToolBar );
53 mActionToggleEditing->setCheckable(
true );
54 connect( mActionToggleEditing, &QAction::triggered,
this, [
this](
bool editable ) { setEditable( editable ); } );
56 editToolBar->addAction( mActionToggleEditing );
59 connect( mActionAddColumn, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::addColumn );
60 editToolBar->addAction( mActionAddColumn );
63 connect( mActionAddRow, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::addRow );
64 editToolBar->addAction( mActionAddRow );
67 connect( mActionRemoveRow, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::removeRow );
68 editToolBar->addAction( mActionRemoveRow );
71 connect( mActionRemoveColumn, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::removeColumn );
72 editToolBar->addAction( mActionRemoveColumn );
75 connect( mActionSaveChanges, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::saveChanges );
76 editToolBar->addAction( mActionSaveChanges );
78 layout()->setMenuBar( editToolBar );
80 connect( mClassifyButton, &QPushButton::clicked,
this, &QgsRasterAttributeTableWidget::classify );
82 mProxyModel =
new QSortFilterProxyModel(
this );
84 mRATView->setModel( mProxyModel );
86 connect( mRATView->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &QgsRasterAttributeTableWidget::updateButtons );
94void QgsRasterAttributeTableWidget::setRasterLayer(
QgsRasterLayer *rasterLayer,
const int bandNumber )
96 mRasterLayer = rasterLayer;
100bool QgsRasterAttributeTableWidget::isDirty()
const
102 return mAttributeTableBuffer && mAttributeTableBuffer->isDirty();
105void QgsRasterAttributeTableWidget::init(
int bandNumber )
107 disconnect( mRasterBandsComboBox, qOverload<int>( &QComboBox::currentIndexChanged ),
this, &QgsRasterAttributeTableWidget::bandChanged );
108 mAttributeTableBuffer =
nullptr;
110 mRasterBandsComboBox->clear();
112 QList<int> availableRats;
116 for (
int checkBandNumber = 1; checkBandNumber <= mRasterLayer->bandCount(); ++checkBandNumber )
119 if ( mRasterLayer->attributeTable( checkBandNumber ) )
121 mRasterBandsComboBox->addItem( mRasterLayer->bandName( checkBandNumber ), checkBandNumber );
122 availableRats.push_back( checkBandNumber );
126 if ( !availableRats.isEmpty() )
128 if ( availableRats.contains( bandNumber ) )
130 mCurrentBand = bandNumber;
132 else if ( !availableRats.isEmpty() )
134 mCurrentBand = availableRats.first();
137 mAttributeTableBuffer = std::make_unique<QgsRasterAttributeTable>( *mRasterLayer->attributeTable( mCurrentBand ) );
138 mRasterBandsComboBox->setCurrentIndex( availableRats.indexOf( mCurrentBand ) );
142 if ( mAttributeTableBuffer )
144 mModel = std::make_unique<QgsRasterAttributeTableModel>( mAttributeTableBuffer.get() );
145 mModel->setEditable( mEditable );
147 connect( mModel.get(), &QgsRasterAttributeTableModel::dataChanged,
this, [
this](
const QModelIndex &,
const QModelIndex &,
const QVector<int> & ) { updateButtons(); } );
149 connect( mModel.get(), &QgsRasterAttributeTableModel::columnsInserted,
this, [
this](
const QModelIndex &,
int,
int ) { setDelegates(); } );
151 connect( mModel.get(), &QgsRasterAttributeTableModel::columnsRemoved,
this, [
this](
const QModelIndex &,
int,
int ) { setDelegates(); } );
153 static_cast<QSortFilterProxyModel *
>( mRATView->model() )->setSourceModel( mModel.get() );
158 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 );
161 connect( mRasterBandsComboBox, qOverload<int>( &QComboBox::currentIndexChanged ),
this, &QgsRasterAttributeTableWidget::bandChanged );
166void QgsRasterAttributeTableWidget::updateButtons()
168 const bool enableEditingButtons(
static_cast<bool>( mAttributeTableBuffer ) && mEditable && mRATView->selectionModel()->currentIndex().isValid() );
169 mActionToggleEditing->setChecked( mEditable );
170 mActionToggleEditing->setEnabled( mAttributeTableBuffer && mRasterLayer );
171 mActionAddColumn->setEnabled( mEditable );
172 mActionRemoveColumn->setEnabled( enableEditingButtons );
173 mActionAddRow->setEnabled( enableEditingButtons );
174 mActionRemoveRow->setEnabled( enableEditingButtons );
175 mActionSaveChanges->setEnabled( mAttributeTableBuffer && mAttributeTableBuffer->isDirty() );
176 mClassifyButton->setEnabled( mAttributeTableBuffer && mRasterLayer );
177 mClassifyComboBox->setEnabled( mAttributeTableBuffer && mRasterLayer );
180void QgsRasterAttributeTableWidget::setDockMode(
bool dockMode )
186void QgsRasterAttributeTableWidget::setMessageBar(
QgsMessageBar *bar )
191bool QgsRasterAttributeTableWidget::setEditable(
bool editable,
bool allowCancel )
193 const bool isDirty { mAttributeTableBuffer && mAttributeTableBuffer->isDirty() && mCurrentBand > 0 && mRasterLayer->attributeTable( mCurrentBand ) };
194 bool retVal {
true };
196 if ( !editable && isDirty )
198 QMessageBox::StandardButtons buttons { QMessageBox::Button::Yes | QMessageBox::Button::No };
202 buttons |= QMessageBox::Button::Cancel;
205 switch ( QMessageBox::question(
nullptr, tr(
"Save Attribute Table" ), tr(
"Attribute table contains unsaved changes, do you want to save the changes?" ), buttons ) )
207 case QMessageBox::Button::Cancel:
212 case QMessageBox::Button::Yes:
218 case QMessageBox::Button::No:
222 mAttributeTableBuffer = std::make_unique<QgsRasterAttributeTable>( *mRasterLayer->attributeTable( mCurrentBand ) );
223 init( mCurrentBand );
232 mEditable = editable;
233 mModel->setEditable( editable );
241void QgsRasterAttributeTableWidget::saveChanges()
243 if ( mRasterLayer && mAttributeTableBuffer && mAttributeTableBuffer->isDirty() && mCurrentBand > 0 )
245 QgsRasterAttributeTable *attributeTable { mRasterLayer->dataProvider()->attributeTable( mCurrentBand ) };
246 if ( !attributeTable )
248 QgsDebugError( u
"Error saving RAT: RAT for band %1 is unexpectedly gone!"_s.arg( mCurrentBand ) );
252 *attributeTable = *mAttributeTableBuffer;
253 QString errorMessage;
254 QString newPath { attributeTable->
filePath() };
256 bool saveToNative {
false };
258 if ( newPath.isEmpty() && !nativeRatSupported )
260 newPath = QFileDialog::getOpenFileName(
262 tr(
"Save Raster Attribute Table (band %1) To File" ).arg( mCurrentBand ),
263 QFile::exists( mRasterLayer->dataProvider()->dataSourceUri() ) ? mRasterLayer->dataProvider()->dataSourceUri() +
".vat.dbf" : QString(),
264 u
"VAT DBF Files (*.vat.dbf)"_s
266 if ( newPath.isEmpty() )
272 else if ( newPath.isEmpty() )
277 bool writeSuccess {
false };
280 if ( !saveToNative && !newPath.isEmpty() )
284 else if ( saveToNative )
286 writeSuccess = mRasterLayer->dataProvider()->writeNativeAttributeTable( &errorMessage );
291 mAttributeTableBuffer->setDirty(
false );
292 notify( tr(
"Attribute Table Write Success" ), tr(
"The raster attribute table has been successfully saved." ),
Qgis::MessageLevel::Success );
310void QgsRasterAttributeTableWidget::classify()
312 if ( !mAttributeTableBuffer )
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?" ) ) )
333 == QMessageBox::Yes )
335 if ( std::unique_ptr<QgsRasterRenderer> renderer = mAttributeTableBuffer->createRenderer( mRasterLayer->dataProvider(), mCurrentBand, mClassifyComboBox->currentData().toInt() ) )
337 mRasterLayer->setRenderer( renderer.release() );
338 mRasterLayer->triggerRepaint();
339 emit rendererChanged();
348void QgsRasterAttributeTableWidget::addColumn()
350 if ( mAttributeTableBuffer )
352 QgsRasterAttributeTableAddColumnDialog dlg { mAttributeTableBuffer.get() };
353 if ( dlg.exec() == QDialog::Accepted )
355 QString errorMessage;
358 if ( !mModel->insertColor( dlg.
position(), &errorMessage ) )
365 if ( !mModel->insertRamp( dlg.
position(), &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() )
411 QgsRasterAttributeTableAddRowDialog dlg;
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() };
428 for (
const QgsRasterAttributeTable::Field &field : std::as_const( 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() };
515 for (
const QgsRasterAttributeTable::Field &f : std::as_const( tableFields ) )
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 );
636 if ( dotPosition < 0 )
639 precision = s.length() - dotPosition - 1;
641 if ( -1 < value.toDouble() && value.toDouble() < 1 )
643 return QLocale().toString( value.toDouble(),
'g', precision );
647 return QLocale().toString( value.toDouble(),
'f', precision );
@ MainWindowToolbar
Main window toolbar icons.
@ DockedToolbar
Toolbars for docked windows.
@ 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.
static QSize iconSize(Qgis::UserInterfaceIconType iconType=Qgis::UserInterfaceIconType::MainWindowToolbar)
Returns the desired size for user interface icons.
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.
#define QgsDebugError(str)