32#include <QSortFilterProxyModel>
35#include "moc_qgsrasterattributetablewidget.cpp"
37QgsRasterAttributeTableWidget::QgsRasterAttributeTableWidget( QWidget *parent,
QgsRasterLayer *rasterLayer,
const int bandNumber )
39 , mRasterLayer( rasterLayer )
44 QToolBar *editToolBar =
new QToolBar(
this );
47 mActionToggleEditing =
new QAction(
QgsApplication::getThemeIcon(
"/mActionEditTable.svg" ), tr(
"&Edit Attribute Table" ), editToolBar );
48 mActionToggleEditing->setCheckable(
true );
49 connect( mActionToggleEditing, &QAction::triggered,
this, [
this](
bool editable ) {
50 setEditable( editable );
53 editToolBar->addAction( mActionToggleEditing );
56 connect( mActionAddColumn, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::addColumn );
57 editToolBar->addAction( mActionAddColumn );
60 connect( mActionAddRow, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::addRow );
61 editToolBar->addAction( mActionAddRow );
64 connect( mActionRemoveRow, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::removeRow );
65 editToolBar->addAction( mActionRemoveRow );
68 connect( mActionRemoveColumn, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::removeColumn );
69 editToolBar->addAction( mActionRemoveColumn );
72 connect( mActionSaveChanges, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::saveChanges );
73 editToolBar->addAction( mActionSaveChanges );
75 layout()->setMenuBar( editToolBar );
77 connect( mClassifyButton, &QPushButton::clicked,
this, &QgsRasterAttributeTableWidget::classify );
79 mProxyModel =
new QSortFilterProxyModel(
this );
81 mRATView->setModel( mProxyModel );
83 connect( mRATView->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &QgsRasterAttributeTableWidget::updateButtons );
91void QgsRasterAttributeTableWidget::setRasterLayer(
QgsRasterLayer *rasterLayer,
const int bandNumber )
93 mRasterLayer = rasterLayer;
97bool QgsRasterAttributeTableWidget::isDirty()
const
99 return mAttributeTableBuffer && mAttributeTableBuffer->isDirty();
102void QgsRasterAttributeTableWidget::init(
int bandNumber )
104 disconnect( mRasterBandsComboBox, qOverload<int>( &QComboBox::currentIndexChanged ),
this, &QgsRasterAttributeTableWidget::bandChanged );
105 mAttributeTableBuffer =
nullptr;
107 mRasterBandsComboBox->clear();
109 QList<int> availableRats;
113 for (
int checkBandNumber = 1; checkBandNumber <= mRasterLayer->bandCount(); ++checkBandNumber )
116 if ( mRasterLayer->attributeTable( checkBandNumber ) )
118 mRasterBandsComboBox->addItem( mRasterLayer->bandName( checkBandNumber ), checkBandNumber );
119 availableRats.push_back( checkBandNumber );
123 if ( !availableRats.isEmpty() )
125 if ( availableRats.contains( bandNumber ) )
127 mCurrentBand = bandNumber;
129 else if ( !availableRats.isEmpty() )
131 mCurrentBand = availableRats.first();
134 mAttributeTableBuffer = std::make_unique<QgsRasterAttributeTable>( *mRasterLayer->attributeTable( mCurrentBand ) );
135 mRasterBandsComboBox->setCurrentIndex( availableRats.indexOf( mCurrentBand ) );
139 if ( mAttributeTableBuffer )
141 mModel = std::make_unique<QgsRasterAttributeTableModel>( mAttributeTableBuffer.get() );
142 mModel->setEditable( mEditable );
144 connect( mModel.get(), &QgsRasterAttributeTableModel::dataChanged,
this, [
this](
const QModelIndex &,
const QModelIndex &,
const QVector<int> & ) {
148 connect( mModel.get(), &QgsRasterAttributeTableModel::columnsInserted,
this, [
this](
const QModelIndex &,
int,
int ) {
152 connect( mModel.get(), &QgsRasterAttributeTableModel::columnsRemoved,
this, [
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 )
248 QgsRasterAttributeTable *attributeTable { mRasterLayer->dataProvider()->attributeTable( mCurrentBand ) };
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() )
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?" ) ) ) == QMessageBox::Yes )
332 if ( QgsRasterRenderer *renderer = mAttributeTableBuffer->createRenderer( mRasterLayer->dataProvider(), mCurrentBand, mClassifyComboBox->currentData().toInt() ) )
334 mRasterLayer->setRenderer( renderer );
335 mRasterLayer->triggerRepaint();
336 emit rendererChanged();
345void QgsRasterAttributeTableWidget::addColumn()
347 if ( mAttributeTableBuffer )
349 QgsRasterAttributeTableAddColumnDialog dlg { mAttributeTableBuffer.get() };
350 if ( dlg.exec() == QDialog::Accepted )
352 QString errorMessage;
355 if ( !mModel->insertColor( dlg.
position(), &errorMessage ) )
362 if ( !mModel->insertRamp( dlg.
position(), &errorMessage ) )
379void QgsRasterAttributeTableWidget::removeColumn()
381 const QModelIndex currentIndex { mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ) };
382 if ( mAttributeTableBuffer && currentIndex.isValid() && currentIndex.column() < mAttributeTableBuffer->fields().count() )
384 if ( QMessageBox::question(
nullptr, tr(
"Remove Column" ), tr(
"Do you want to remove the selected column? This action cannot be undone." ) ) == QMessageBox::Yes )
386 QString errorMessage;
387 if ( !mModel->removeField( currentIndex.column(), &errorMessage ) )
395void QgsRasterAttributeTableWidget::addRow()
397 if ( mAttributeTableBuffer )
400 int position { mModel->rowCount( QModelIndex() ) };
401 const QModelIndex currentIndex { mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ) };
404 if ( currentIndex.isValid() )
408 QgsRasterAttributeTableAddRowDialog dlg;
409 if ( dlg.exec() != QDialog::DialogCode::Accepted )
415 position = currentIndex.row() + ( dlg.
insertAfter() ? 1 : 0 );
419 bool result {
true };
420 QString errorMessage;
422 QVariantList rowData;
424 QList<QgsRasterAttributeTable::Field> fields { mAttributeTableBuffer->fields() };
425 for (
const QgsRasterAttributeTable::Field &field : std::as_const( fields ) )
430 result = mModel->insertRow( position, rowData, &errorMessage );
438 mRATView->scrollTo( mRATView->model()->index( position, 0 ) );
443void QgsRasterAttributeTableWidget::removeRow()
445 if ( mAttributeTableBuffer && mRATView->selectionModel()->currentIndex().isValid() )
447 if ( QMessageBox::question(
nullptr, tr(
"Remove Row" ), tr(
"Do you want to remove the selected row? This action cannot be undone." ) ) == QMessageBox::Yes )
449 QString errorMessage;
450 if ( !mModel->removeRow( mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ).row(), &errorMessage ) )
458void QgsRasterAttributeTableWidget::bandChanged(
const int index )
460 const QVariant itemData = mRasterBandsComboBox->itemData( index );
462 if ( itemData.isValid() )
466 setEditable(
false );
468 init( itemData.toInt() );
472void QgsRasterAttributeTableWidget::notify(
const QString &title,
const QString &message,
Qgis::MessageLevel level )
476 mMessageBar->pushMessage( message, level );
486 QMessageBox::information(
nullptr, title, message );
491 QMessageBox::warning(
nullptr, title, message );
496 QMessageBox::critical(
nullptr, title, message );
503void QgsRasterAttributeTableWidget::setDelegates()
505 mClassifyComboBox->clear();
506 if ( mAttributeTableBuffer )
508 const QList<QgsRasterAttributeTable::Field> tableFields { mAttributeTableBuffer->fields() };
512 for (
const QgsRasterAttributeTable::Field &f : std::as_const( tableFields ) )
515 mRATView->setItemDelegateForColumn( fieldIdx,
nullptr );
517 if ( usageInfo[f.usage].maybeClass )
523 if ( ( !f.isColor() && !f.isRamp() ) && f.type == QMetaType::Type::Double )
525 mRATView->setItemDelegateForColumn( fieldIdx,
new LocalizedDoubleDelegate( mRATView ) );
531 if ( mAttributeTableBuffer->hasColor() )
535 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count(),
new ColorAlphaDelegate( mRATView ) );
539 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count(),
new ColorDelegate( mRATView ) );
542 else if ( mAttributeTableBuffer->hasRamp() )
546 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count(),
new ColorRampAlphaDelegate( mRATView ) );
550 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count(),
new ColorRampDelegate( mRATView ) );
559QWidget *ColorDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &,
const QModelIndex & )
const
564void ColorDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
566 const QColor color { index.data( Qt::ItemDataRole::EditRole ).value<QColor>() };
570void ColorDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
572 const QColor color {
static_cast<QgsColorButton *
>( editor )->color() };
573 model->setData( index, color );
576QWidget *ColorAlphaDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
578 QWidget *editor { ColorDelegate::createEditor( parent, option, index ) };
583QWidget *ColorRampDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &,
const QModelIndex & )
const
589void ColorRampDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
591 const QgsGradientColorRamp ramp { qvariant_cast<QgsGradientColorRamp>( index.data( Qt::ItemDataRole::EditRole ) ) };
595void ColorRampDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
600void ColorRampDelegate::paint( QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
602 const QgsGradientColorRamp ramp { qvariant_cast<QgsGradientColorRamp>( index.data( Qt::ItemDataRole::EditRole ) ) };
603 QLinearGradient gradient( QPointF( 0, 0 ), QPointF( 1, 0 ) );
604 gradient.setCoordinateMode( QGradient::CoordinateMode::ObjectBoundingMode );
605 gradient.setColorAt( 0, ramp.
color1() );
606 gradient.setColorAt( 1, ramp.
color2() );
607 const QRect r = option.rect.adjusted( 1, 1, -1, -1 );
609 painter->fillRect( r, QBrush { gradient } );
612QWidget *ColorRampAlphaDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
614 QWidget *editor { ColorRampDelegate::createEditor( parent, option, index ) };
620QString LocalizedDoubleDelegate::displayText(
const QVariant &value,
const QLocale &locale )
const
623 const QString s( value.toString() );
624 const int dotPosition( s.indexOf(
'.' ) );
626 if ( dotPosition < 0 && s.indexOf(
'e' ) < 0 )
629 return QLocale().toString( value.toDouble(),
'f', precision );
633 if ( dotPosition < 0 )
636 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 );
@ 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)