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 ) {
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 )
100 disconnect( mRasterBandsComboBox, qOverload<int>( &QComboBox::currentIndexChanged ),
this, &QgsRasterAttributeTableWidget::bandChanged );
101 mAttributeTableBuffer =
nullptr;
103 mRasterBandsComboBox->clear();
105 QList<int> availableRats;
109 for (
int checkBandNumber = 1; checkBandNumber <= mRasterLayer->bandCount(); ++checkBandNumber )
112 if ( mRasterLayer->attributeTable( checkBandNumber ) )
114 mRasterBandsComboBox->addItem( mRasterLayer->bandName( checkBandNumber ), checkBandNumber );
115 availableRats.push_back( checkBandNumber );
119 if ( !availableRats.isEmpty() )
121 if ( availableRats.contains( bandNumber ) )
123 mCurrentBand = bandNumber;
125 else if ( !availableRats.isEmpty() )
127 mCurrentBand = availableRats.first();
130 mAttributeTableBuffer = std::make_unique<QgsRasterAttributeTable>( *mRasterLayer->attributeTable( mCurrentBand ) );
131 mRasterBandsComboBox->setCurrentIndex( availableRats.indexOf( mCurrentBand ) );
135 if ( mAttributeTableBuffer )
138 mModel->setEditable( mEditable );
140 connect( mModel.get(), &QgsRasterAttributeTableModel::dataChanged,
this, [=](
const QModelIndex &,
const QModelIndex &,
const QVector<int> & ) {
144 connect( mModel.get(), &QgsRasterAttributeTableModel::columnsInserted,
this, [=](
const QModelIndex &,
int,
int ) {
148 connect( mModel.get(), &QgsRasterAttributeTableModel::columnsRemoved,
this, [=](
const QModelIndex &,
int,
int ) {
152 static_cast<QSortFilterProxyModel *
>( mRATView->model() )->setSourceModel( mModel.get() );
157 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 );
160 connect( mRasterBandsComboBox, qOverload<int>( &QComboBox::currentIndexChanged ),
this, &QgsRasterAttributeTableWidget::bandChanged );
165void QgsRasterAttributeTableWidget::updateButtons()
167 const bool enableEditingButtons(
static_cast<bool>( mAttributeTableBuffer ) && mEditable && mRATView->selectionModel()->currentIndex().isValid() );
168 mActionToggleEditing->setChecked( mEditable );
169 mActionToggleEditing->setEnabled( mAttributeTableBuffer && mRasterLayer );
170 mActionAddColumn->setEnabled( mEditable );
171 mActionRemoveColumn->setEnabled( enableEditingButtons );
172 mActionAddRow->setEnabled( enableEditingButtons );
173 mActionRemoveRow->setEnabled( enableEditingButtons );
174 mActionSaveChanges->setEnabled( mAttributeTableBuffer && mAttributeTableBuffer->isDirty() );
175 mClassifyButton->setEnabled( mAttributeTableBuffer && mRasterLayer );
176 mClassifyComboBox->setEnabled( mAttributeTableBuffer && mRasterLayer );
179void QgsRasterAttributeTableWidget::setDockMode(
bool dockMode )
185void QgsRasterAttributeTableWidget::setMessageBar(
QgsMessageBar *bar )
190bool QgsRasterAttributeTableWidget::setEditable(
bool editable,
bool allowCancel )
192 const bool isDirty { mAttributeTableBuffer && mAttributeTableBuffer->isDirty() && mCurrentBand > 0 && mRasterLayer->attributeTable( mCurrentBand ) };
193 bool retVal {
true };
195 if ( !editable && isDirty )
197 QMessageBox::StandardButtons buttons { QMessageBox::Button::Yes | QMessageBox::Button::No };
201 buttons |= QMessageBox::Button::Cancel;
204 switch ( QMessageBox::question(
nullptr, tr(
"Save Attribute Table" ), tr(
"Attribute table contains unsaved changes, do you want to save the changes?" ), buttons ) )
206 case QMessageBox::Button::Cancel:
211 case QMessageBox::Button::Yes:
217 case QMessageBox::Button::No:
221 mAttributeTableBuffer = std::make_unique<QgsRasterAttributeTable>( *mRasterLayer->attributeTable( mCurrentBand ) );
222 init( mCurrentBand );
231 mEditable = editable;
232 mModel->setEditable( editable );
240void QgsRasterAttributeTableWidget::saveChanges()
242 if ( mRasterLayer && mAttributeTableBuffer && mAttributeTableBuffer->isDirty() && mCurrentBand > 0 )
245 if ( !attributeTable )
247 QgsDebugError( QStringLiteral(
"Error saving RAT: RAT for band %1 is unexpectedly gone!" ).arg( mCurrentBand ) );
251 *attributeTable = *mAttributeTableBuffer;
252 QString errorMessage;
253 QString newPath { attributeTable->filePath() };
255 bool saveToNative {
false };
257 if ( newPath.isEmpty() && !nativeRatSupported )
259 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)" ) );
260 if ( newPath.isEmpty() )
266 else if ( newPath.isEmpty() )
271 bool writeSuccess {
false };
274 if ( !saveToNative && !newPath.isEmpty() )
276 writeSuccess = attributeTable->writeToFile( attributeTable->filePath(), &errorMessage );
278 else if ( saveToNative )
280 writeSuccess = mRasterLayer->dataProvider()->writeNativeAttributeTable( &errorMessage );
285 mAttributeTableBuffer->setDirty(
false );
286 notify( tr(
"Attribute Table Write Success" ), tr(
"The raster attribute table has been successfully saved." ),
Qgis::MessageLevel::Success );
304void QgsRasterAttributeTableWidget::classify()
306 if ( !mAttributeTableBuffer )
318 QString confirmMessage;
319 QString errorMessage;
321 if ( !mAttributeTableBuffer->isValid( &errorMessage ) )
323 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 );
326 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 )
328 if (
QgsRasterRenderer *renderer = mAttributeTableBuffer->createRenderer( mRasterLayer->dataProvider(), mCurrentBand, mClassifyComboBox->currentData().toInt() ) )
330 mRasterLayer->setRenderer( renderer );
331 mRasterLayer->triggerRepaint();
332 emit rendererChanged();
341void QgsRasterAttributeTableWidget::addColumn()
343 if ( mAttributeTableBuffer )
346 if ( dlg.exec() == QDialog::Accepted )
348 QString errorMessage;
351 if ( !mModel->insertColor( dlg.position(), &errorMessage ) )
356 else if ( dlg.isRamp() )
358 if ( !mModel->insertRamp( dlg.position(), &errorMessage ) )
365 if ( !mModel->insertField( dlg.position(), dlg.name(), dlg.usage(), dlg.type(), &errorMessage ) )
375void QgsRasterAttributeTableWidget::removeColumn()
377 const QModelIndex currentIndex { mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ) };
378 if ( mAttributeTableBuffer && currentIndex.isValid() && currentIndex.column() < mAttributeTableBuffer->fields().count() )
380 if ( QMessageBox::question(
nullptr, tr(
"Remove Column" ), tr(
"Do you want to remove the selected column? This action cannot be undone." ) ) == QMessageBox::Yes )
382 QString errorMessage;
383 if ( !mModel->removeField( currentIndex.column(), &errorMessage ) )
391void QgsRasterAttributeTableWidget::addRow()
393 if ( mAttributeTableBuffer )
396 int position { mModel->rowCount( QModelIndex() ) };
397 const QModelIndex currentIndex { mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ) };
400 if ( currentIndex.isValid() )
405 if ( dlg.exec() != QDialog::DialogCode::Accepted )
411 position = currentIndex.row() + ( dlg.
insertAfter() ? 1 : 0 );
415 bool result {
true };
416 QString errorMessage;
418 QVariantList rowData;
420 QList<QgsRasterAttributeTable::Field> fields { mAttributeTableBuffer->fields() };
426 result = mModel->insertRow( position, rowData, &errorMessage );
434 mRATView->scrollTo( mRATView->model()->index( position, 0 ) );
439void QgsRasterAttributeTableWidget::removeRow()
441 if ( mAttributeTableBuffer && mRATView->selectionModel()->currentIndex().isValid() )
443 if ( QMessageBox::question(
nullptr, tr(
"Remove Row" ), tr(
"Do you want to remove the selected row? This action cannot be undone." ) ) == QMessageBox::Yes )
445 QString errorMessage;
446 if ( !mModel->removeRow( mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ).row(), &errorMessage ) )
454void QgsRasterAttributeTableWidget::bandChanged(
const int index )
456 const QVariant itemData = mRasterBandsComboBox->itemData( index );
458 if ( itemData.isValid() )
462 setEditable(
false );
464 init( itemData.toInt() );
468void QgsRasterAttributeTableWidget::notify(
const QString &title,
const QString &message,
Qgis::MessageLevel level )
472 mMessageBar->pushMessage( message, level );
482 QMessageBox::information(
nullptr, title, message );
487 QMessageBox::warning(
nullptr, title, message );
492 QMessageBox::critical(
nullptr, title, message );
499void QgsRasterAttributeTableWidget::setDelegates()
501 mClassifyComboBox->clear();
502 if ( mAttributeTableBuffer )
504 const QList<QgsRasterAttributeTable::Field> tableFields { mAttributeTableBuffer->fields() };
511 mRATView->setItemDelegateForColumn( fieldIdx,
nullptr );
513 if ( usageInfo[f.usage].maybeClass )
519 if ( ( !f.isColor() && !f.isRamp() ) && f.type == QMetaType::Type::Double )
521 mRATView->setItemDelegateForColumn( fieldIdx,
new LocalizedDoubleDelegate( mRATView ) );
527 if ( mAttributeTableBuffer->hasColor() )
531 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count(),
new ColorAlphaDelegate( mRATView ) );
535 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count(),
new ColorDelegate( mRATView ) );
538 else if ( mAttributeTableBuffer->hasRamp() )
542 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count(),
new ColorRampAlphaDelegate( mRATView ) );
546 mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count(),
new ColorRampDelegate( mRATView ) );
555QWidget *ColorDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &,
const QModelIndex & )
const
560void ColorDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
562 const QColor color { index.data( Qt::ItemDataRole::EditRole ).value<QColor>() };
566void ColorDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
568 const QColor color {
static_cast<QgsColorButton *
>( editor )->color() };
569 model->setData( index, color );
572QWidget *ColorAlphaDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
574 QWidget *editor { ColorDelegate::createEditor( parent, option, index ) };
579QWidget *ColorRampDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &,
const QModelIndex & )
const
585void ColorRampDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
587 const QgsGradientColorRamp ramp { qvariant_cast<QgsGradientColorRamp>( index.data( Qt::ItemDataRole::EditRole ) ) };
591void ColorRampDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
596void ColorRampDelegate::paint( QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
598 const QgsGradientColorRamp ramp { qvariant_cast<QgsGradientColorRamp>( index.data( Qt::ItemDataRole::EditRole ) ) };
599 QLinearGradient gradient( QPointF( 0, 0 ), QPointF( 1, 0 ) );
600 gradient.setCoordinateMode( QGradient::CoordinateMode::ObjectBoundingMode );
601 gradient.setColorAt( 0, ramp.color1() );
602 gradient.setColorAt( 1, ramp.color2() );
603 const QRect r = option.rect.adjusted( 1, 1, -1, -1 );
605 painter->fillRect( r, QBrush { gradient } );
608QWidget *ColorRampAlphaDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
610 QWidget *editor { ColorRampDelegate::createEditor( parent, option, index ) };
616QString LocalizedDoubleDelegate::displayText(
const QVariant &value,
const QLocale &locale )
const
619 const QString s( value.toString() );
620 const int dotPosition( s.indexOf(
'.' ) );
622 if ( dotPosition < 0 && s.indexOf(
'e' ) < 0 )
625 return QLocale().toString( value.toDouble(),
'f',
precision );
629 if ( dotPosition < 0 )
632 precision = s.length() - dotPosition - 1;
634 if ( -1 < value.toDouble() && value.toDouble() < 1 )
636 return QLocale().toString( value.toDouble(),
'g',
precision );
640 return QLocale().toString( value.toDouble(),
'f',
precision );
643 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)