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 mActionAddColumn->setEnabled( mEditable );
174 mActionRemoveColumn->setEnabled( enableEditingButtons );
175 mActionAddRow->setEnabled( enableEditingButtons );
176 mActionRemoveRow->setEnabled( enableEditingButtons );
177 mActionSaveChanges->setEnabled( mAttributeTableBuffer && mAttributeTableBuffer->isDirty() );
178 mClassifyButton->setEnabled( mAttributeTableBuffer && mRasterLayer );
179 mClassifyComboBox->setEnabled( mAttributeTableBuffer && mRasterLayer );
182void QgsRasterAttributeTableWidget::setDockMode(
bool dockMode )
188void QgsRasterAttributeTableWidget::setMessageBar(
QgsMessageBar *bar )
193bool QgsRasterAttributeTableWidget::setEditable(
bool editable,
bool allowCancel )
195 const bool isDirty { mAttributeTableBuffer &&mAttributeTableBuffer->isDirty() &&mCurrentBand > 0 && mRasterLayer->attributeTable( mCurrentBand ) };
196 bool retVal {
true };
198 if ( ! editable && isDirty )
200 QMessageBox::StandardButtons buttons { QMessageBox::Button::Yes | QMessageBox::Button::No };
204 buttons |= QMessageBox::Button::Cancel;
207 switch ( QMessageBox::question(
nullptr, tr(
"Save Attribute Table" ), tr(
"Attribute table contains unsaved changes, do you want to save the changes?" ), buttons ) )
209 case QMessageBox::Button::Cancel:
214 case QMessageBox::Button::Yes:
220 case QMessageBox::Button::No:
224 mAttributeTableBuffer = std::make_unique<QgsRasterAttributeTable>( *mRasterLayer->attributeTable( mCurrentBand ) );
225 init( mCurrentBand );
234 mEditable = editable;
235 mModel->setEditable( editable );
243void QgsRasterAttributeTableWidget::saveChanges()
245 if ( mRasterLayer && mAttributeTableBuffer && mAttributeTableBuffer->isDirty() && mCurrentBand > 0 )
248 if ( ! attributeTable )
250 QgsDebugError( QStringLiteral(
"Error saving RAT: RAT for band %1 is unexpectedly gone!" ).arg( mCurrentBand ) );
254 *attributeTable = *mAttributeTableBuffer;
255 QString errorMessage;
256 QString newPath { attributeTable->filePath() };
258 bool saveToNative {
false };
260 if ( newPath.isEmpty() && ! nativeRatSupported )
262 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)" ) );
263 if ( newPath.isEmpty() )
269 else if ( newPath.isEmpty() )
274 bool writeSuccess {
false };
277 if ( ! saveToNative && ! newPath.isEmpty() )
279 writeSuccess = attributeTable->writeToFile( attributeTable->filePath(), &errorMessage );
281 else if ( saveToNative )
283 writeSuccess = mRasterLayer->dataProvider()->writeNativeAttributeTable( &errorMessage );
288 mAttributeTableBuffer->setDirty(
false );
289 notify( tr(
"Attribute Table Write Success" ), tr(
"The raster attribute table has been successfully saved." ),
Qgis::MessageLevel::Success );
308void QgsRasterAttributeTableWidget::classify()
311 if ( ! mAttributeTableBuffer )
317 if ( ! mRasterLayer )
323 QString confirmMessage;
324 QString errorMessage;
326 if ( ! mAttributeTableBuffer->isValid( &errorMessage ) )
328 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 );
331 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 )
334 if (
QgsRasterRenderer *renderer = mAttributeTableBuffer->createRenderer( mRasterLayer->dataProvider(), mCurrentBand, mClassifyComboBox->currentData().toInt() ) )
336 mRasterLayer->setRenderer( renderer );
337 mRasterLayer->triggerRepaint( );
338 emit rendererChanged();
347void QgsRasterAttributeTableWidget::addColumn()
349 if ( mAttributeTableBuffer )
352 if ( dlg.exec() == QDialog::Accepted )
354 QString errorMessage;
357 if ( ! mModel->insertColor( dlg.position(), &errorMessage ) )
362 else if ( dlg.isRamp() )
364 if ( ! mModel->insertRamp( dlg.position(), &errorMessage ) )
371 if ( ! mModel->insertField( dlg.position(), dlg.name(), dlg.usage(), dlg.type(), &errorMessage ) )
381void QgsRasterAttributeTableWidget::removeColumn()
383 const QModelIndex currentIndex { mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ) };
384 if ( mAttributeTableBuffer && currentIndex.isValid() && currentIndex.column() < mAttributeTableBuffer->fields().count() )
386 if ( QMessageBox::question(
nullptr, tr(
"Remove Column" ), tr(
"Do you want to remove the selected column? This action cannot be undone." ) ) == QMessageBox::Yes )
388 QString errorMessage;
389 if ( ! mModel->removeField( currentIndex.column(), &errorMessage ) )
397void QgsRasterAttributeTableWidget::addRow()
399 if ( mAttributeTableBuffer )
402 int position { mModel->rowCount( QModelIndex() ) };
403 const QModelIndex currentIndex { mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ) };
406 if ( currentIndex.isValid() )
411 if ( dlg.exec() != QDialog::DialogCode::Accepted )
417 position = currentIndex.row() + ( dlg.
insertAfter() ? 1 : 0 );
421 bool result {
true };
422 QString errorMessage;
424 QVariantList rowData;
426 QList<QgsRasterAttributeTable::Field> fields { mAttributeTableBuffer->fields() };
429 rowData.push_back( QVariant( field.type ) );
432 result = mModel->insertRow( position, rowData, &errorMessage );
440 mRATView->scrollTo( mRATView->model()->index( position, 0 ) );
445void QgsRasterAttributeTableWidget::removeRow()
447 if ( mAttributeTableBuffer && mRATView->selectionModel()->currentIndex().isValid() )
449 if ( QMessageBox::question(
nullptr, tr(
"Remove Row" ), tr(
"Do you want to remove the selected row? This action cannot be undone." ) ) == QMessageBox::Yes )
451 QString errorMessage;
452 if ( ! mModel->removeRow( mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ).row(), &errorMessage ) )
460void QgsRasterAttributeTableWidget::bandChanged(
const int index )
462 const QVariant itemData = mRasterBandsComboBox->itemData( index );
464 if ( itemData.isValid() )
468 setEditable(
false );
470 init( itemData.toInt( ) );
474void QgsRasterAttributeTableWidget::notify(
const QString &title,
const QString &message,
Qgis::MessageLevel level )
478 mMessageBar->pushMessage( message, level );
488 QMessageBox::information(
nullptr, title, message );
493 QMessageBox::warning(
nullptr, title, message );
498 QMessageBox::critical(
nullptr, title, message );
505void QgsRasterAttributeTableWidget::setDelegates()
507 mClassifyComboBox->clear();
508 if ( mAttributeTableBuffer )
510 const QList<QgsRasterAttributeTable::Field> tableFields { mAttributeTableBuffer->fields() };
517 mRATView->setItemDelegateForColumn( fieldIdx,
nullptr );
519 if ( usageInfo[f.usage].maybeClass )
525 if ( ( ! f.isColor() && ! f.isRamp() ) && f.type == QVariant::Type::Double )
527 mRATView->setItemDelegateForColumn( fieldIdx,
new LocalizedDoubleDelegate( mRATView ) );
533 if ( mAttributeTableBuffer->hasColor() )
537 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count( ),
new ColorAlphaDelegate( mRATView ) );
541 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count( ),
new ColorDelegate( mRATView ) );
544 else if ( mAttributeTableBuffer->hasRamp() )
548 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count( ),
new ColorRampAlphaDelegate( mRATView ) );
552 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count( ),
new ColorRampDelegate( mRATView ) );
561QWidget *ColorDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &,
const QModelIndex & )
const
566void ColorDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
568 const QColor color { index.data( Qt::ItemDataRole::EditRole ).value<QColor>() };
572void ColorDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
574 const QColor color {
static_cast<QgsColorButton *
>( editor )->color( ) };
575 model->setData( index, color );
578QWidget *ColorAlphaDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
580 QWidget *editor { ColorDelegate::createEditor( parent, option, index ) };
585QWidget *ColorRampDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &,
const QModelIndex & )
const
591void ColorRampDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
593 const QgsGradientColorRamp ramp { qvariant_cast<QgsGradientColorRamp>( index.data( Qt::ItemDataRole::EditRole ) ) };
597void ColorRampDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
602void ColorRampDelegate::paint( QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
604 const QgsGradientColorRamp ramp { qvariant_cast<QgsGradientColorRamp>( index.data( Qt::ItemDataRole::EditRole ) ) };
605 QLinearGradient gradient( QPointF( 0, 0 ), QPointF( 1, 0 ) );
606 gradient.setCoordinateMode( QGradient::CoordinateMode::ObjectBoundingMode );
607 gradient.setColorAt( 0, ramp.color1() );
608 gradient.setColorAt( 1, ramp.color2() );
609 const QRect r = option.rect.adjusted( 1, 1, -1, -1 );
611 painter->fillRect( r, QBrush{ gradient } );
614QWidget *ColorRampAlphaDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
616 QWidget *editor { ColorRampDelegate::createEditor( parent, option, index ) };
622QString LocalizedDoubleDelegate::displayText(
const QVariant &value,
const QLocale &locale )
const
625 const QString s( value.toString() );
626 const int dotPosition( s.indexOf(
'.' ) );
628 if ( dotPosition < 0 && s.indexOf(
'e' ) < 0 )
631 return QLocale().toString( value.toDouble(),
'f',
precision );
636 else 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 );
647 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.
@ 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(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 ...
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.
@ NativeRasterAttributeTable
Provider's rendering is dependent on requested pixel size of the viewport (since QGIS 3....
Represents a raster layer.
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)