28#include <QSortFilterProxyModel> 
   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 )
 
   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 );
 
   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 );
 
   89  mRasterLayer = rasterLayer;
 
   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 )
 
  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 );
 
  195  const bool isDirty { mAttributeTableBuffer &&mAttributeTableBuffer->isDirty() &&mCurrentBand > 0 && mRasterLayer->
attributeTable( mCurrentBand ) };
 
  196  bool retVal { 
true };
 
  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 );
 
  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() };
 
  257      const bool nativeRatSupported = mRasterLayer->
dataProvider()->
providerCapabilities().testFlag( QgsRasterDataProvider::ProviderCapability::NativeRasterAttributeTable );
 
  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 )
 
  288        mAttributeTableBuffer->setDirty( 
false );
 
  289        notify( tr( 
"Attribute Table Write Success" ), tr( 
"The raster attribute table has been successfully saved." ), Qgis::MessageLevel::Success );
 
  293        notify( tr( 
"Attribute Table Write Error" ), errorMessage, Qgis::MessageLevel::Critical );
 
  308void QgsRasterAttributeTableWidget::classify()
 
  311  if ( ! mAttributeTableBuffer )
 
  313    notify( tr( 
"Classification Error" ), tr( 
"The raster attribute table is not set." ), Qgis::MessageLevel::Critical );
 
  317  if ( ! mRasterLayer )
 
  319    notify( tr( 
"Classification Error" ), tr( 
"The raster layer is not set." ), Qgis::MessageLevel::Critical );
 
  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() ) )
 
  342      notify( tr( 
"Classification Error" ), tr( 
"The classification returned no classes." ), Qgis::MessageLevel::Critical );
 
  347void QgsRasterAttributeTableWidget::addColumn()
 
  349  if ( mAttributeTableBuffer )
 
  352    if ( dlg.exec() == QDialog::Accepted )
 
  354      QString errorMessage;
 
  357        if ( ! mModel->insertColor( dlg.position(), &errorMessage ) )
 
  359          notify( tr( 
"Error adding color column" ), errorMessage,  Qgis::MessageLevel::Critical );
 
  362      else if ( dlg.isRamp() )
 
  364        if ( ! mModel->insertRamp( dlg.position(), &errorMessage ) )
 
  366          notify( tr( 
"Error adding color ramp column" ), errorMessage,  Qgis::MessageLevel::Critical );
 
  371        if ( ! mModel->insertField( dlg.position(), dlg.name(), dlg.usage(), dlg.type(), &errorMessage ) )
 
  373          notify( tr( 
"Error adding new column" ), errorMessage,  Qgis::MessageLevel::Critical );
 
  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 ) )
 
  391        notify( tr( 
"Error removing column" ), errorMessage,  Qgis::MessageLevel::Critical );
 
  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 );
 
  436      notify( tr( 
"Error adding row" ), errorMessage,  Qgis::MessageLevel::Critical );
 
  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 ) )
 
  454        notify( tr( 
"Error removing row" ), errorMessage,  Qgis::MessageLevel::Critical );
 
  460void QgsRasterAttributeTableWidget::bandChanged( 
const int index )
 
  462  const QVariant itemData = mRasterBandsComboBox->itemData( index );
 
  464  if ( itemData.isValid() )
 
  470    init( itemData.toInt( ) );
 
  474void QgsRasterAttributeTableWidget::notify( 
const QString &title, 
const QString &message, 
Qgis::MessageLevel level )
 
  484      case Qgis::MessageLevel::Info:
 
  485      case Qgis::MessageLevel::Success:
 
  486      case Qgis::MessageLevel::NoLevel:
 
  488        QMessageBox::information( 
nullptr, title, message );
 
  491      case Qgis::MessageLevel::Warning:
 
  493        QMessageBox::warning( 
nullptr, title, message );
 
  496      case Qgis::MessageLevel::Critical:
 
  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.
 
@ 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.
 
virtual QString dataSourceUri(bool expandAuthConfig=false) const
Gets the data source specification.
 
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 ...
 
void triggerRepaint(bool deferredUpdate=false)
Will advise the map canvas (and any other interested party) that this layer requires to be repainted.
 
A bar for displaying non-blocking messages to the user.
 
void pushMessage(const QString &text, Qgis::MessageLevel level=Qgis::MessageLevel::Info, int duration=-1)
A convenience method for pushing a message with the specified text to the bar.
 
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.
 
virtual bool writeNativeAttributeTable(QString *errorMessage=nullptr)
Writes the native attribute table, optionally reporting any error in errorMessage,...
 
QgsRasterAttributeTable * attributeTable(int bandNumber) const
Returns the (possibly NULL) attribute table for the specified bandNumber.
 
virtual QgsRasterDataProvider::ProviderCapabilities providerCapabilities() const
Returns flags containing the supported capabilities of the data provider.
 
Represents a raster layer.
 
QgsRasterAttributeTable * attributeTable(int bandNumber) const
Returns the (possibly NULL) raster attribute table for the given band bandNumber.
 
int bandCount() const
Returns the number of bands in this layer.
 
QString bandName(int bandNoInt) const
Returns the name of a band given its number.
 
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
 
void setRenderer(QgsRasterRenderer *renderer)
Sets the raster's renderer.
 
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)