17#include "moc_qgsrasterattributetablewidget.cpp"
29#include <QSortFilterProxyModel>
33QgsRasterAttributeTableWidget::QgsRasterAttributeTableWidget( QWidget *parent,
QgsRasterLayer *rasterLayer,
const int bandNumber )
35 , mRasterLayer( rasterLayer )
40 QToolBar *editToolBar =
new QToolBar(
this );
43 mActionToggleEditing =
new QAction(
QgsApplication::getThemeIcon(
"/mActionEditTable.svg" ), tr(
"&Edit Attribute Table" ), editToolBar );
44 mActionToggleEditing->setCheckable(
true );
45 connect( mActionToggleEditing, &QAction::triggered,
this, [ = ](
bool editable )
47 setEditable( editable );
50 editToolBar->addAction( mActionToggleEditing );
53 connect( mActionAddColumn, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::addColumn );
54 editToolBar->addAction( mActionAddColumn );
57 connect( mActionAddRow, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::addRow );
58 editToolBar->addAction( mActionAddRow );
61 connect( mActionRemoveRow, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::removeRow );
62 editToolBar->addAction( mActionRemoveRow );
65 connect( mActionRemoveColumn, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::removeColumn );
66 editToolBar->addAction( mActionRemoveColumn );
69 connect( mActionSaveChanges, &QAction::triggered,
this, &QgsRasterAttributeTableWidget::saveChanges );
70 editToolBar->addAction( mActionSaveChanges );
72 layout()->setMenuBar( editToolBar );
74 connect( mClassifyButton, &QPushButton::clicked,
this, &QgsRasterAttributeTableWidget::classify );
76 mProxyModel =
new QSortFilterProxyModel(
this );
78 mRATView->setModel( mProxyModel );
80 connect( mRATView->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &QgsRasterAttributeTableWidget::updateButtons );
88void QgsRasterAttributeTableWidget::setRasterLayer(
QgsRasterLayer *rasterLayer,
const int bandNumber )
90 mRasterLayer = rasterLayer;
94bool QgsRasterAttributeTableWidget::isDirty()
const
96 return mAttributeTableBuffer && mAttributeTableBuffer->isDirty();
99void QgsRasterAttributeTableWidget::init(
int bandNumber )
102 disconnect( mRasterBandsComboBox, qOverload<int>( &QComboBox::currentIndexChanged ),
this, &QgsRasterAttributeTableWidget::bandChanged );
103 mAttributeTableBuffer =
nullptr;
105 mRasterBandsComboBox->clear();
107 QList<int> availableRats;
111 for (
int checkBandNumber = 1; checkBandNumber <= mRasterLayer->bandCount(); ++checkBandNumber )
114 if ( mRasterLayer->attributeTable( checkBandNumber ) )
116 mRasterBandsComboBox->addItem( mRasterLayer->bandName( checkBandNumber ), checkBandNumber );
117 availableRats.push_back( checkBandNumber );
121 if ( !availableRats.isEmpty() )
123 if ( availableRats.contains( bandNumber ) )
125 mCurrentBand = bandNumber;
127 else if ( ! availableRats.isEmpty() )
129 mCurrentBand = availableRats.first();
132 mAttributeTableBuffer = std::make_unique<QgsRasterAttributeTable>( *mRasterLayer->attributeTable( mCurrentBand ) );
133 mRasterBandsComboBox->setCurrentIndex( availableRats.indexOf( mCurrentBand ) );
137 if ( mAttributeTableBuffer )
140 mModel->setEditable( mEditable );
142 connect( mModel.get(), &QgsRasterAttributeTableModel::dataChanged,
this, [ = ](
const QModelIndex &,
const QModelIndex &,
const QVector<int> & )
147 connect( mModel.get(), &QgsRasterAttributeTableModel::columnsInserted,
this, [ = ](
const QModelIndex &,
int,
int )
152 connect( mModel.get(), &QgsRasterAttributeTableModel::columnsRemoved,
this, [ = ](
const QModelIndex &,
int,
int )
157 static_cast<QSortFilterProxyModel *
>( mRATView->model() )->setSourceModel( mModel.get() );
162 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 );
165 connect( mRasterBandsComboBox, qOverload<int>( &QComboBox::currentIndexChanged ),
this, &QgsRasterAttributeTableWidget::bandChanged );
170void QgsRasterAttributeTableWidget::updateButtons()
172 const bool enableEditingButtons(
static_cast<bool>( mAttributeTableBuffer ) && mEditable && mRATView->selectionModel()->currentIndex().isValid() );
173 mActionToggleEditing->setChecked( mEditable );
174 mActionToggleEditing->setEnabled( mAttributeTableBuffer && mRasterLayer );
175 mActionAddColumn->setEnabled( mEditable );
176 mActionRemoveColumn->setEnabled( enableEditingButtons );
177 mActionAddRow->setEnabled( enableEditingButtons );
178 mActionRemoveRow->setEnabled( enableEditingButtons );
179 mActionSaveChanges->setEnabled( mAttributeTableBuffer && mAttributeTableBuffer->isDirty() );
180 mClassifyButton->setEnabled( mAttributeTableBuffer && mRasterLayer );
181 mClassifyComboBox->setEnabled( mAttributeTableBuffer && mRasterLayer );
184void QgsRasterAttributeTableWidget::setDockMode(
bool dockMode )
190void QgsRasterAttributeTableWidget::setMessageBar(
QgsMessageBar *bar )
195bool QgsRasterAttributeTableWidget::setEditable(
bool editable,
bool allowCancel )
197 const bool isDirty { mAttributeTableBuffer &&mAttributeTableBuffer->isDirty() &&mCurrentBand > 0 && mRasterLayer->attributeTable( mCurrentBand ) };
198 bool retVal {
true };
200 if ( ! editable && isDirty )
202 QMessageBox::StandardButtons buttons { QMessageBox::Button::Yes | QMessageBox::Button::No };
206 buttons |= QMessageBox::Button::Cancel;
209 switch ( QMessageBox::question(
nullptr, tr(
"Save Attribute Table" ), tr(
"Attribute table contains unsaved changes, do you want to save the changes?" ), buttons ) )
211 case QMessageBox::Button::Cancel:
216 case QMessageBox::Button::Yes:
222 case QMessageBox::Button::No:
226 mAttributeTableBuffer = std::make_unique<QgsRasterAttributeTable>( *mRasterLayer->attributeTable( mCurrentBand ) );
227 init( mCurrentBand );
236 mEditable = editable;
237 mModel->setEditable( editable );
245void QgsRasterAttributeTableWidget::saveChanges()
247 if ( mRasterLayer && mAttributeTableBuffer && mAttributeTableBuffer->isDirty() && mCurrentBand > 0 )
250 if ( ! attributeTable )
252 QgsDebugError( QStringLiteral(
"Error saving RAT: RAT for band %1 is unexpectedly gone!" ).arg( mCurrentBand ) );
256 *attributeTable = *mAttributeTableBuffer;
257 QString errorMessage;
258 QString newPath { attributeTable->filePath() };
260 bool saveToNative {
false };
262 if ( newPath.isEmpty() && ! nativeRatSupported )
264 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)" ) );
265 if ( newPath.isEmpty() )
271 else if ( newPath.isEmpty() )
276 bool writeSuccess {
false };
279 if ( ! saveToNative && ! newPath.isEmpty() )
281 writeSuccess = attributeTable->writeToFile( attributeTable->filePath(), &errorMessage );
283 else if ( saveToNative )
285 writeSuccess = mRasterLayer->dataProvider()->writeNativeAttributeTable( &errorMessage );
290 mAttributeTableBuffer->setDirty(
false );
291 notify( tr(
"Attribute Table Write Success" ), tr(
"The raster attribute table has been successfully saved." ),
Qgis::MessageLevel::Success );
310void QgsRasterAttributeTableWidget::classify()
313 if ( ! mAttributeTableBuffer )
319 if ( ! mRasterLayer )
325 QString confirmMessage;
326 QString errorMessage;
328 if ( ! mAttributeTableBuffer->isValid( &errorMessage ) )
330 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 );
333 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 )
336 if (
QgsRasterRenderer *renderer = mAttributeTableBuffer->createRenderer( mRasterLayer->dataProvider(), mCurrentBand, mClassifyComboBox->currentData().toInt() ) )
338 mRasterLayer->setRenderer( renderer );
339 mRasterLayer->triggerRepaint( );
340 emit rendererChanged();
349void QgsRasterAttributeTableWidget::addColumn()
351 if ( mAttributeTableBuffer )
354 if ( dlg.exec() == QDialog::Accepted )
356 QString errorMessage;
359 if ( ! mModel->insertColor( dlg.position(), &errorMessage ) )
364 else if ( dlg.isRamp() )
366 if ( ! mModel->insertRamp( dlg.position(), &errorMessage ) )
373 if ( ! mModel->insertField( dlg.position(), dlg.name(), dlg.usage(), dlg.type(), &errorMessage ) )
383void QgsRasterAttributeTableWidget::removeColumn()
385 const QModelIndex currentIndex { mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ) };
386 if ( mAttributeTableBuffer && currentIndex.isValid() && currentIndex.column() < mAttributeTableBuffer->fields().count() )
388 if ( QMessageBox::question(
nullptr, tr(
"Remove Column" ), tr(
"Do you want to remove the selected column? This action cannot be undone." ) ) == QMessageBox::Yes )
390 QString errorMessage;
391 if ( ! mModel->removeField( currentIndex.column(), &errorMessage ) )
399void QgsRasterAttributeTableWidget::addRow()
401 if ( mAttributeTableBuffer )
404 int position { mModel->rowCount( QModelIndex() ) };
405 const QModelIndex currentIndex { mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ) };
408 if ( currentIndex.isValid() )
413 if ( dlg.exec() != QDialog::DialogCode::Accepted )
419 position = currentIndex.row() + ( dlg.
insertAfter() ? 1 : 0 );
423 bool result {
true };
424 QString errorMessage;
426 QVariantList rowData;
428 QList<QgsRasterAttributeTable::Field> fields { mAttributeTableBuffer->fields() };
434 result = mModel->insertRow( position, rowData, &errorMessage );
442 mRATView->scrollTo( mRATView->model()->index( position, 0 ) );
447void QgsRasterAttributeTableWidget::removeRow()
449 if ( mAttributeTableBuffer && mRATView->selectionModel()->currentIndex().isValid() )
451 if ( QMessageBox::question(
nullptr, tr(
"Remove Row" ), tr(
"Do you want to remove the selected row? This action cannot be undone." ) ) == QMessageBox::Yes )
453 QString errorMessage;
454 if ( ! mModel->removeRow( mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ).row(), &errorMessage ) )
462void QgsRasterAttributeTableWidget::bandChanged(
const int index )
464 const QVariant itemData = mRasterBandsComboBox->itemData( index );
466 if ( itemData.isValid() )
470 setEditable(
false );
472 init( itemData.toInt( ) );
476void QgsRasterAttributeTableWidget::notify(
const QString &title,
const QString &message,
Qgis::MessageLevel level )
480 mMessageBar->pushMessage( message, level );
490 QMessageBox::information(
nullptr, title, message );
495 QMessageBox::warning(
nullptr, title, message );
500 QMessageBox::critical(
nullptr, title, message );
507void QgsRasterAttributeTableWidget::setDelegates()
509 mClassifyComboBox->clear();
510 if ( mAttributeTableBuffer )
512 const QList<QgsRasterAttributeTable::Field> tableFields { mAttributeTableBuffer->fields() };
519 mRATView->setItemDelegateForColumn( fieldIdx,
nullptr );
521 if ( usageInfo[f.usage].maybeClass )
527 if ( ( ! f.isColor() && ! f.isRamp() ) && f.type == QMetaType::Type::Double )
529 mRATView->setItemDelegateForColumn( fieldIdx,
new LocalizedDoubleDelegate( mRATView ) );
535 if ( mAttributeTableBuffer->hasColor() )
539 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count( ),
new ColorAlphaDelegate( mRATView ) );
543 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count( ),
new ColorDelegate( mRATView ) );
546 else if ( mAttributeTableBuffer->hasRamp() )
550 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count( ),
new ColorRampAlphaDelegate( mRATView ) );
554 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count( ),
new ColorRampDelegate( mRATView ) );
563QWidget *ColorDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &,
const QModelIndex & )
const
568void ColorDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
570 const QColor color { index.data( Qt::ItemDataRole::EditRole ).value<QColor>() };
574void ColorDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
576 const QColor color {
static_cast<QgsColorButton *
>( editor )->color( ) };
577 model->setData( index, color );
580QWidget *ColorAlphaDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
582 QWidget *editor { ColorDelegate::createEditor( parent, option, index ) };
587QWidget *ColorRampDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &,
const QModelIndex & )
const
593void ColorRampDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
595 const QgsGradientColorRamp ramp { qvariant_cast<QgsGradientColorRamp>( index.data( Qt::ItemDataRole::EditRole ) ) };
599void ColorRampDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
604void ColorRampDelegate::paint( QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
606 const QgsGradientColorRamp ramp { qvariant_cast<QgsGradientColorRamp>( index.data( Qt::ItemDataRole::EditRole ) ) };
607 QLinearGradient gradient( QPointF( 0, 0 ), QPointF( 1, 0 ) );
608 gradient.setCoordinateMode( QGradient::CoordinateMode::ObjectBoundingMode );
609 gradient.setColorAt( 0, ramp.color1() );
610 gradient.setColorAt( 1, ramp.color2() );
611 const QRect r = option.rect.adjusted( 1, 1, -1, -1 );
613 painter->fillRect( r, QBrush{ gradient } );
616QWidget *ColorRampAlphaDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
618 QWidget *editor { ColorRampDelegate::createEditor( parent, option, index ) };
624QString LocalizedDoubleDelegate::displayText(
const QVariant &value,
const QLocale &locale )
const
627 const QString s( value.toString() );
628 const int dotPosition( s.indexOf(
'.' ) );
630 if ( dotPosition < 0 && s.indexOf(
'e' ) < 0 )
633 return QLocale().toString( value.toDouble(),
'f',
precision );
638 else precision = s.length() - dotPosition - 1;
640 if ( -1 < value.toDouble() && value.toDouble() < 1 )
642 return QLocale().toString( value.toDouble(),
'g',
precision );
646 return QLocale().toString( value.toDouble(),
'f',
precision );
649 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)