Gui enhancements

This commit is contained in:
Alessandro Pasotti 2022-10-21 08:55:29 +02:00 committed by Nyall Dawson
parent 3479a66e4e
commit 70c93d4aea
18 changed files with 487 additions and 202 deletions

View File

@ -261,7 +261,7 @@ Returns the (possibly NULL) raster attribute table for the given band ``bandNumb
.. versionadded:: 3.30 .. versionadded:: 3.30
%End %End
int attributeTableCount( ); int attributeTableCount( ) const;
%Docstring %Docstring
Returns the number of attribute tables for the raster by counting the number of bands that have an associated attribute table. Returns the number of attribute tables for the raster by counting the number of bands that have an associated attribute table.

View File

@ -46,6 +46,33 @@ Sets the raster layer and an optional band number.
bool isDirty( ) const; bool isDirty( ) const;
%Docstring %Docstring
Returns ``True`` if the associated raster attribute table is dirty Returns ``True`` if the associated raster attribute table is dirty
%End
void setMessageBar( QgsMessageBar *bar );
%Docstring
Sets the message ``bar`` associated with the widget. This allows the widget to push feedback messages
to the appropriate message bar.
.. seealso:: :py:func:`messageBar`
%End
signals:
void rendererChanged( );
%Docstring
This signal is emitted after a successful classify operation which changed the raster renderer.
%End
public slots:
void saveChanges();
%Docstring
Save the changes in the raster attribute table.
%End
bool setEditable( bool editable );
%Docstring
Set the editable state, it may trigger save changes if the attribute table has unsave changes.
%End %End
}; };

View File

@ -4380,6 +4380,12 @@ void QgisApp::setupConnections()
connect( QgsProject::instance(), &QgsProject::transactionGroupsChanged, this, &QgisApp::onTransactionGroupsChanged ); connect( QgsProject::instance(), &QgsProject::transactionGroupsChanged, this, &QgisApp::onTransactionGroupsChanged );
// Handle dirty raster attribute tables
connect( QgsProject::instance(), qOverload<const QList< QgsMapLayer * > & >( &QgsProject::layersWillBeRemoved ), this, [ = ]( const QList< QgsMapLayer * > &layers )
{
checkUnsavedRasterAttributeTableEdits( layers, false );
} );
// connect preview modes actions // connect preview modes actions
connect( mActionPreviewModeOff, &QAction::triggered, this, &QgisApp::disablePreviewMode ); connect( mActionPreviewModeOff, &QAction::triggered, this, &QgisApp::disablePreviewMode );
connect( mActionPreviewModeMono, &QAction::triggered, this, &QgisApp::activateMonoPreview ); connect( mActionPreviewModeMono, &QAction::triggered, this, &QgisApp::activateMonoPreview );
@ -5600,7 +5606,7 @@ void QgisApp::fileExit()
} }
QgsCanvasRefreshBlocker refreshBlocker; QgsCanvasRefreshBlocker refreshBlocker;
if ( checkUnsavedLayerEdits() && checkMemoryLayers() && saveDirty() && checkExitBlockers() ) if ( checkUnsavedLayerEdits() && checkMemoryLayers() && saveDirty() && checkExitBlockers() && checkUnsavedRasterAttributeTableEdits() )
{ {
closeProject(); closeProject();
userProfileManager()->setDefaultFromActive(); userProfileManager()->setDefaultFromActive();
@ -5638,7 +5644,7 @@ bool QgisApp::fileNew( bool promptToSaveFlag, bool forceBlank )
if ( promptToSaveFlag ) if ( promptToSaveFlag )
{ {
if ( !checkUnsavedLayerEdits() || !checkMemoryLayers() || !saveDirty() ) if ( !checkUnsavedLayerEdits() || !checkMemoryLayers() || !saveDirty() || !checkUnsavedRasterAttributeTableEdits() )
{ {
return false; //cancel pressed return false; //cancel pressed
} }
@ -5710,7 +5716,7 @@ bool QgisApp::fileNewFromTemplate( const QString &fileName )
if ( checkTasksDependOnProject() ) if ( checkTasksDependOnProject() )
return false; return false;
if ( !checkUnsavedLayerEdits() || !checkMemoryLayers() || !saveDirty() ) if ( !checkUnsavedLayerEdits() || !checkMemoryLayers() || !saveDirty() || !checkUnsavedRasterAttributeTableEdits() )
{ {
return false; //cancel pressed return false; //cancel pressed
} }
@ -6206,7 +6212,7 @@ void QgisApp::fileOpen()
return; return;
// possibly save any pending work before opening a new project // possibly save any pending work before opening a new project
if ( checkUnsavedLayerEdits() && checkMemoryLayers() && saveDirty() ) if ( checkUnsavedLayerEdits() && checkMemoryLayers() && saveDirty() && checkUnsavedRasterAttributeTableEdits() )
{ {
// Retrieve last used project dir from persistent settings // Retrieve last used project dir from persistent settings
QgsSettings settings; QgsSettings settings;
@ -6262,7 +6268,7 @@ void QgisApp::fileRevert()
QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) == QMessageBox::No ) QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) == QMessageBox::No )
return; return;
if ( !checkUnsavedLayerEdits() || !checkMemoryLayers() ) if ( !checkUnsavedLayerEdits() || !checkMemoryLayers() || ! checkUnsavedRasterAttributeTableEdits() )
return; return;
// re-open the current project // re-open the current project
@ -6760,7 +6766,7 @@ void QgisApp::openProject( QAction *action )
if ( checkTasksDependOnProject() ) if ( checkTasksDependOnProject() )
return; return;
if ( checkUnsavedLayerEdits() && checkMemoryLayers() && saveDirty() ) if ( checkUnsavedLayerEdits() && checkMemoryLayers() && saveDirty() && checkUnsavedRasterAttributeTableEdits() )
addProject( project ); addProject( project );
} }
@ -6805,7 +6811,7 @@ void QgisApp::openProject( const QString &fileName )
return; return;
// possibly save any pending work before opening a different project // possibly save any pending work before opening a different project
if ( checkUnsavedLayerEdits() && checkMemoryLayers() && saveDirty() ) if ( checkUnsavedLayerEdits() && checkMemoryLayers() && saveDirty() && checkUnsavedRasterAttributeTableEdits() )
{ {
// error handling and reporting is in addProject() function // error handling and reporting is in addProject() function
addProject( fileName ); addProject( fileName );
@ -12027,6 +12033,30 @@ void QgisApp::openRasterAttributeTable()
} }
} }
void QgisApp::loadRasterAttributeTableFromFile( )
{
if ( !mLayerTreeView )
return;
//find current Layer
QgsMapLayer *currentLayer = mLayerTreeView->currentLayer();
if ( !currentLayer )
return;
QgsRasterLayer *layer = qobject_cast<QgsRasterLayer *>( currentLayer );
int bandNumber { 0 };
if ( layer )
{
QgsCreateRasterAttributeTableDialog dlg { layer };
if ( dlg.exec() == QDialog::Accepted )
{
// TODO!
}
}
}
void QgisApp::createRasterAttributeTable() void QgisApp::createRasterAttributeTable()
{ {
if ( !mLayerTreeView ) if ( !mLayerTreeView )
@ -12053,7 +12083,7 @@ void QgisApp::createRasterAttributeTable()
if ( ! rat ) if ( ! rat )
{ {
visibleMessageBar()->pushMessage( tr( "Error Creating Raster Attribute Table" ), visibleMessageBar()->pushMessage( tr( "Error Creating Raster Attribute Table" ),
tr( "The Raster Attribute Table could not be created." ), tr( "The raster attribute table could not be created." ),
Qgis::MessageLevel::Critical ); Qgis::MessageLevel::Critical );
return; return;
} }
@ -13381,6 +13411,94 @@ bool QgisApp::checkUnsavedLayerEdits()
return true; return true;
} }
bool QgisApp::checkUnsavedRasterAttributeTableEdits( const QList<QgsMapLayer *> &mapLayers, bool allowCancel )
{
bool retVal { true };
QVector<QgsRasterLayer *> rasterLayers;
if ( ! mapLayers.isEmpty() )
{
for ( QgsMapLayer *mapLayer : std::as_const( mapLayers ) )
{
if ( QgsRasterLayer *rasterLayer = qobject_cast< QgsRasterLayer *>( mapLayer ) )
{
rasterLayers.push_back( rasterLayer );
}
}
}
else
{
rasterLayers = QgsProject::instance()->layers<QgsRasterLayer *>();
}
for ( QgsRasterLayer *rasterLayer : std::as_const( rasterLayers ) )
{
QStringList dirtyBands;
QList<QgsRasterAttributeTable *> dirtyRats;
for ( int bandNo = 1; bandNo < rasterLayer->bandCount(); ++bandNo )
{
if ( QgsRasterAttributeTable *rat = rasterLayer->attributeTable( bandNo ); rat && rat->isDirty() )
{
dirtyBands.push_back( QString::number( bandNo ) );
dirtyRats.push_back( rat );
}
}
if ( ! dirtyBands.isEmpty( ) )
{
QMessageBox::StandardButtons buttons = QMessageBox::Save | QMessageBox::Discard;
if ( allowCancel )
{
buttons |= QMessageBox::Cancel;
}
switch ( QMessageBox::question( nullptr,
tr( "Save Raster Attribute Table" ),
tr( "Do you want to save the changes to the attribute tables (bands: %1) associated with layer '%2'?" ).arg( dirtyBands.join( QStringLiteral( ", " ) ), rasterLayer->name() ),
buttons ) )
{
case QMessageBox::Save:
{
for ( QgsRasterAttributeTable *rat : std::as_const( dirtyRats ) )
{
QString errorMessage;
if ( rat->filePath().isEmpty( ) )
{
if ( ! rasterLayer->dataProvider()->writeNativeAttributeTable( &errorMessage ) )
{
visibleMessageBar()->pushMessage( tr( "Error Saving Raster Attribute Table" ),
tr( "An error occourred while saving raster attribute table for layer '%1': %2" ).arg( rasterLayer->name(), errorMessage ),
Qgis::MessageLevel::Critical );
retVal = false;
}
}
else
{
if ( ! rat->writeToFile( rat->filePath(), &errorMessage ) )
{
visibleMessageBar()->pushMessage( tr( "Error Saving Raster Attribute Table" ),
tr( "An error occourred while saving raster attribute table for layer '%1' to VAT.DBF file '%2': %3" ).arg( rasterLayer->name(), rat->filePath(), errorMessage ),
Qgis::MessageLevel::Critical );
retVal = false;
}
}
}
break;
}
case QMessageBox::Cancel:
retVal = false;
break;
case QMessageBox::Discard:
default:
break;
}
}
}
return retVal;
}
bool QgisApp::checkMemoryLayers() bool QgisApp::checkMemoryLayers()
{ {
if ( !QgsSettings().value( QStringLiteral( "askToSaveMemoryLayers" ), true, QgsSettings::App ).toBool() ) if ( !QgsSettings().value( QStringLiteral( "askToSaveMemoryLayers" ), true, QgsSettings::App ).toBool() )
@ -16175,6 +16293,8 @@ void QgisApp::showLayerProperties( QgsMapLayer *mapLayer, const QString &page )
{ {
rasterLayerPropertiesDialog->deleteLater(); rasterLayerPropertiesDialog->deleteLater();
} ); } );
break; break;
} }

View File

@ -1106,7 +1106,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
void legendLayerStretchUsingCurrentExtent(); void legendLayerStretchUsingCurrentExtent();
/** /**
* Open the RasterAttributeTable for the raster layer. * Open the Raster Attribute Table for the raster layer.
* Only works on raster layers. * Only works on raster layers.
* *
* \since QGIS 3.30 * \since QGIS 3.30
@ -1114,7 +1114,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
void openRasterAttributeTable(); void openRasterAttributeTable();
/** /**
* Creates a new RasterAttributeTable from the raster layer renderer if the * Creates a new Raster Attribute Table from the raster layer renderer if the
* renderer supports it. * renderer supports it.
* *
* Only works on raster layers. * Only works on raster layers.
@ -1123,6 +1123,15 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
*/ */
void createRasterAttributeTable(); void createRasterAttributeTable();
/**
* Loads a Raster Attribute Table from a VAT.DBF file.
*
* Only works on raster layers.
*
* \since QGIS 3.30
*/
void loadRasterAttributeTableFromFile();
//! Watch for QFileOpenEvent. //! Watch for QFileOpenEvent.
bool event( QEvent *event ) override; bool event( QEvent *event ) override;
@ -1339,6 +1348,19 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
*/ */
bool checkUnsavedLayerEdits(); bool checkUnsavedLayerEdits();
/**
* Checks for unsaved changes in raster attribute tables and prompts the user to save
* or discard these changes for each raster attribute table.
*
* Returns TRUE if there are no unsaved raster attribute table remaining, or the user
* opted to discard them all. Returns FALSE if the user opted to cancel
* on any raster attribute table.
*
* \param mapLayers optional list of layers to check, if empty all project raster layers will be checked.
* \param allowCancel optional flag (default TRUE) that switches on the "Cancel" button.
*/
bool checkUnsavedRasterAttributeTableEdits( const QList<QgsMapLayer *> &mapLayers = QList<QgsMapLayer *>(), bool allowCancel = true );
/** /**
* Checks whether memory layers (with features) exist in the project, and if so * Checks whether memory layers (with features) exist in the project, and if so
* shows a warning to users that their contents will be lost on * shows a warning to users that their contents will be lost on

View File

@ -229,8 +229,10 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
} }
else if ( rlayer->canCreateRasterAttributeTable() ) else if ( rlayer->canCreateRasterAttributeTable() )
{ {
menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddTable.svg" ) ), tr( "Create Raster Attribute Table" ), QgisApp::instance(), &QgisApp::createRasterAttributeTable ); menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCreateTable.svg" ) ), tr( "Create Raster Attribute Table" ), QgisApp::instance(), &QgisApp::createRasterAttributeTable );
} }
menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddTable.svg" ) ), tr( "Load Raster Attribute Table from VAT.DBF" ), QgisApp::instance(), &QgisApp::loadRasterAttributeTableFromFile );
} }
// No raster support in createSqlVectorLayer (yet) // No raster support in createSqlVectorLayer (yet)

View File

@ -3212,25 +3212,10 @@ bool QgsGdalProvider::writeNativeAttributeTable( QString *errorMessage ) //#spel
} }
// Needs to be in write mode for HFA and perhaps other formats! // Needs to be in write mode for HFA and perhaps other formats!
if ( GDALGetAccess( mGdalDataset ) == GA_ReadOnly ) if ( ! isEditable() )
{ {
QgsDebugMsg( QStringLiteral( "re-opening the dataset in read/write mode" ) ); QgsDebugMsg( QStringLiteral( "re-opening the dataset in read/write mode" ) );
GDALClose( mGdalDataset ); setEditable( true );
mGdalBaseDataset = gdalOpen( dataSourceUri( true ), GDAL_OF_UPDATE );
// if the dataset couldn't be opened in read / write mode, tell the user
if ( !mGdalBaseDataset )
{
mGdalBaseDataset = gdalOpen( dataSourceUri( true ), GDAL_OF_READONLY );
//Since we are not a virtual warped dataset, mGdalDataSet and mGdalBaseDataset are supposed to be the same
mGdalDataset = mGdalBaseDataset;
if ( errorMessage )
{
*errorMessage = tr( "GDAL Error reopening dataset in write mode, raster attribute table could not be saved." );
}
return false;
}
wasReopenedReadWrite = true; wasReopenedReadWrite = true;
} }
@ -3327,17 +3312,7 @@ bool QgsGdalProvider::writeNativeAttributeTable( QString *errorMessage ) //#spel
if ( wasReopenedReadWrite ) if ( wasReopenedReadWrite )
{ {
QgsDebugMsg( QStringLiteral( "re-opening the dataset in read-only mode" ) ); QgsDebugMsg( QStringLiteral( "re-opening the dataset in read-only mode" ) );
GDALClose( mGdalDataset ); setEditable( false );
mGdalBaseDataset = gdalOpen( dataSourceUri( true ), GDAL_OF_READONLY );
// if the dataset couldn't be opened in read / write mode, tell the user
if ( !mGdalBaseDataset )
{
mGdalBaseDataset = gdalOpen( dataSourceUri( true ), GDAL_OF_UPDATE );
//Since we are not a virtual warped dataset, mGdalDataSet and mGdalBaseDataset are supposed to be the same
mGdalDataset = mGdalBaseDataset;
}
} }
return success; return success;

View File

@ -479,6 +479,7 @@ bool QgsRasterAttributeTable::removeRow( int position, QString *errorMessage )
return false; return false;
} }
mData.removeAt( position ); mData.removeAt( position );
setDirty( true );
return true; return true;
} }
@ -723,8 +724,12 @@ bool QgsRasterAttributeTable::setValue( const int row, const int column, const Q
} }
const QVariant oldVal = mData[ row ][ column ]; const QVariant oldVal = mData[ row ][ column ];
mData[ row ][ column ] = newVal;
setDirty( newVal != oldVal ); if ( newVal != oldVal )
{
mData[ row ][ column ] = newVal;
setDirty( true );
}
return true; return true;
} }
@ -1508,7 +1513,7 @@ QgsRasterRenderer *QgsRasterAttributeTable::createRenderer( QgsRasterDataProvide
pseudoColorRenderer->setClassificationMin( minValue() ); pseudoColorRenderer->setClassificationMin( minValue() );
pseudoColorRenderer->setClassificationMax( maxValue() ); pseudoColorRenderer->setClassificationMax( maxValue() );
// Use discrete for single colors, interpolated for ramps // Use discrete for single colors, interpolated for ramps
pseudoColorRenderer->createShader( ramp, hasColor() ? QgsColorRampShader::Type::Discrete : QgsColorRampShader::Type::Interpolated, QgsColorRampShader::ClassificationMode::Continuous, ramp->stops().count() + 2, true ); pseudoColorRenderer->createShader( ramp, hasRamp() ? QgsColorRampShader::Type::Interpolated : QgsColorRampShader::Type::Discrete, QgsColorRampShader::ClassificationMode::Continuous, ramp->stops().count() + 2, true );
pseudoColorRenderer->shader()->setMaximumValue( maxValue() ); pseudoColorRenderer->shader()->setMaximumValue( maxValue() );
pseudoColorRenderer->shader()->setMinimumValue( minValue() ); pseudoColorRenderer->shader()->setMinimumValue( minValue() );
// Set labels // Set labels
@ -1516,10 +1521,30 @@ QgsRasterRenderer *QgsRasterAttributeTable::createRenderer( QgsRasterDataProvide
{ {
shaderFunction->setMinimumValue( minValue() ); shaderFunction->setMinimumValue( minValue() );
shaderFunction->setMaximumValue( maxValue() ); shaderFunction->setMaximumValue( maxValue() );
const QList<QgsColorRampShader::ColorRampItem> itemList { shaderFunction->colorRampItemList() }; const bool labelsAreUsable { ramp->count() > 2 && labels.count() == ramp->count() - 1 };
const bool labelsAreUsable { labels.count() == itemList.count() };
if ( ! itemList.isEmpty() ) if ( labelsAreUsable )
{ {
QList<QgsColorRampShader::ColorRampItem> deduplicatedDitemList;
const double range { maxValue() - minValue() };
int stopIdx { 0 };
for ( const QString &label : std::as_const( labels ) )
{
if ( stopIdx >= ramp->count() - 2 )
{
break;
}
double value { minValue() + ramp->stops().at( stopIdx ).offset * range };
QgsColorRampShader::ColorRampItem item { value, ramp->stops().at( stopIdx ).color, label };
deduplicatedDitemList.push_back( item );
stopIdx++;
}
QgsColorRampShader::ColorRampItem item { maxValue(), ramp->color2(), labels.last() };
deduplicatedDitemList.push_back( item );
#if 0
QList<QgsColorRampShader::ColorRampItem> deduplicatedDitemList; QList<QgsColorRampShader::ColorRampItem> deduplicatedDitemList;
// Deduplicate entries, this is necessary when the ramp for athematic // Deduplicate entries, this is necessary when the ramp for athematic
// ramp rasters creates a stop for each class limit (min and max) which // ramp rasters creates a stop for each class limit (min and max) which
@ -1548,6 +1573,8 @@ QgsRasterRenderer *QgsRasterAttributeTable::createRenderer( QgsRasterDataProvide
deduplicatedDitemList.push_back( item ); deduplicatedDitemList.push_back( item );
} }
} }
#endif
shaderFunction->setColorRampItemList( deduplicatedDitemList ); shaderFunction->setColorRampItemList( deduplicatedDitemList );
} }
} }

View File

@ -257,7 +257,7 @@ QgsRasterAttributeTable *QgsRasterLayer::attributeTable( int bandNoInt ) const
return mDataProvider->attributeTable( bandNoInt ); return mDataProvider->attributeTable( bandNoInt );
} }
int QgsRasterLayer::attributeTableCount() int QgsRasterLayer::attributeTableCount() const
{ {
if ( !mDataProvider ) if ( !mDataProvider )
return 0; return 0;

View File

@ -326,7 +326,7 @@ class CORE_EXPORT QgsRasterLayer : public QgsMapLayer, public QgsAbstractProfile
* Returns the number of attribute tables for the raster by counting the number of bands that have an associated attribute table. * Returns the number of attribute tables for the raster by counting the number of bands that have an associated attribute table.
* \since QGIS 3.30 * \since QGIS 3.30
*/ */
int attributeTableCount( ); int attributeTableCount( ) const;
/** /**
* Returns TRUE if the raster renderer is suitable for creation of a raster attribute table. The supported renderers are QgsPalettedRasterRenderer and QgsSingleBandPseudoColorRenderer. * Returns TRUE if the raster renderer is suitable for creation of a raster attribute table. The supported renderers are QgsPalettedRasterRenderer and QgsSingleBandPseudoColorRenderer.

View File

@ -16,11 +16,9 @@
#include "qgsrasterattributetabledialog.h" #include "qgsrasterattributetabledialog.h"
#include "qgsrasterlayer.h" #include "qgsrasterlayer.h"
#include <QMessageBox>
QgsRasterAttributeTableDialog::QgsRasterAttributeTableDialog( QgsRasterLayer *rasterLayer, int bandNumber, QWidget *parent ) QgsRasterAttributeTableDialog::QgsRasterAttributeTableDialog( QgsRasterLayer *rasterLayer, int bandNumber, QWidget *parent )
: QDialog( parent ) : QDialog( parent )
, mRasterLayer( rasterLayer )
{ {
Q_ASSERT( rasterLayer ); Q_ASSERT( rasterLayer );
setupUi( this ); setupUi( this );
@ -28,40 +26,15 @@ QgsRasterAttributeTableDialog::QgsRasterAttributeTableDialog( QgsRasterLayer *ra
setWindowTitle( tr( "Raster Attribute Table for %1" ).arg( rasterLayer->name() ) ); setWindowTitle( tr( "Raster Attribute Table for %1" ).arg( rasterLayer->name() ) );
connect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsRasterAttributeTableDialog::reject ); connect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsRasterAttributeTableDialog::reject );
connect( rasterLayer, &QgsRasterLayer::dataSourceChanged, this, &QgsRasterAttributeTableDialog::reject );
connect( rasterLayer, &QgsRasterLayer::willBeDeleted, this, &QgsRasterAttributeTableDialog::reject );
} }
void QgsRasterAttributeTableDialog::reject() void QgsRasterAttributeTableDialog::reject()
{ {
QList<int> dirtyBands; if ( mRatWidget->setEditable( false ) )
for ( int bandNo = 1; bandNo <= mRasterLayer->bandCount(); ++bandNo )
{ {
if ( QgsRasterAttributeTable *rat = mRasterLayer->attributeTable( bandNo ) ) QDialog::reject();
{
if ( rat->isDirty() )
{
dirtyBands.push_back( bandNo );
}
}
} }
if ( ! dirtyBands.isEmpty() )
{
QString bandsStr;
for ( int i = 0; i < dirtyBands.size(); i++ )
{
bandsStr.append( QString::number( dirtyBands[i] ) );
if ( i < dirtyBands.size() - 1 )
bandsStr.append( QStringLiteral( ", " ) );
}
QString msg { dirtyBands.count( ) > 1 ? tr( "Attribute table bands (%1) contain unsaved changes, close without saving?" ).arg( bandsStr ) : tr( "Attribute table band %1 contains unsaved changes, close without saving?" ).arg( bandsStr ) };
if ( QMessageBox::question( nullptr, tr( "Save Attribute Table" ), msg ) != QMessageBox::Yes )
{
return;
}
}
QDialog::reject();
} }

View File

@ -26,13 +26,13 @@ class QgsRasterLayer;
/** /**
* \ingroup gui * \ingroup gui
* \brief The QgsRasterAttributeTableDialog class embeds an attribute table widget * \brief The QgsRasterAttributeTableDialog class embeds an attribute table widget.
* and contains logic to handle confirmation when closing with unsaved changes.
* \since QGIS 3.30 * \since QGIS 3.30
*/ */
class GUI_EXPORT QgsRasterAttributeTableDialog: public QDialog, private Ui::QRasterAttributeTableDialogBase class GUI_EXPORT QgsRasterAttributeTableDialog: public QDialog, private Ui::QRasterAttributeTableDialogBase
{ {
Q_OBJECT Q_OBJECT
public: public:
QgsRasterAttributeTableDialog( QgsRasterLayer *rasterLayer, int bandNumber = 0, QWidget *parent SIP_TRANSFERTHIS = nullptr ); QgsRasterAttributeTableDialog( QgsRasterLayer *rasterLayer, int bandNumber = 0, QWidget *parent SIP_TRANSFERTHIS = nullptr );
@ -42,9 +42,6 @@ class GUI_EXPORT QgsRasterAttributeTableDialog: public QDialog, private Ui::QRas
void reject() override; void reject() override;
private:
QgsRasterLayer *mRasterLayer = nullptr;
}; };
#endif // QGSRASTERATTRIBUTETABLEDIALOG_H #endif // QGSRASTERATTRIBUTETABLEDIALOG_H

View File

@ -90,14 +90,14 @@ void QgsRasterAttributeTableWidget::setRasterLayer( QgsRasterLayer *rasterLayer,
bool QgsRasterAttributeTableWidget::isDirty() const bool QgsRasterAttributeTableWidget::isDirty() const
{ {
return mAttributeTable && mAttributeTable->isDirty(); return mAttributeTableBuffer && mAttributeTableBuffer->isDirty();
} }
void QgsRasterAttributeTableWidget::init( int bandNumber ) void QgsRasterAttributeTableWidget::init( int bandNumber )
{ {
disconnect( mRasterBandsComboBox, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsRasterAttributeTableWidget::bandChanged ); disconnect( mRasterBandsComboBox, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsRasterAttributeTableWidget::bandChanged );
mAttributeTable = nullptr; mAttributeTableBuffer = nullptr;
mCurrentBand = 0; mCurrentBand = 0;
mRasterBandsComboBox->clear(); mRasterBandsComboBox->clear();
@ -120,22 +120,22 @@ void QgsRasterAttributeTableWidget::init( int bandNumber )
if ( availableRats.contains( bandNumber ) ) if ( availableRats.contains( bandNumber ) )
{ {
mCurrentBand = bandNumber; mCurrentBand = bandNumber;
mAttributeTable = mRasterLayer->attributeTable( mCurrentBand );
mRasterBandsComboBox->setCurrentIndex( availableRats.indexOf( mCurrentBand ) );
} }
else if ( ! availableRats.isEmpty() ) else if ( ! availableRats.isEmpty() )
{ {
mCurrentBand = availableRats.first(); mCurrentBand = availableRats.first();
mAttributeTable = mRasterLayer->attributeTable( mCurrentBand );
mRasterBandsComboBox->setCurrentIndex( availableRats.indexOf( mCurrentBand ) );
} }
mAttributeTableBuffer = std::make_unique<QgsRasterAttributeTable>( *mRasterLayer->attributeTable( mCurrentBand ) );
mRasterBandsComboBox->setCurrentIndex( availableRats.indexOf( mCurrentBand ) );
} }
} }
if ( mAttributeTable ) if ( mAttributeTableBuffer )
{ {
mModel.reset( new QgsRasterAttributeTableModel( mAttributeTable ) ); mModel.reset( new QgsRasterAttributeTableModel( mAttributeTableBuffer.get() ) );
mModel->setEditable( mEditable ); mModel->setEditable( mEditable );
connect( mModel.get(), &QgsRasterAttributeTableModel::dataChanged, this, [ = ]( const QModelIndex &, const QModelIndex &, const QVector<int> & ) connect( mModel.get(), &QgsRasterAttributeTableModel::dataChanged, this, [ = ]( const QModelIndex &, const QModelIndex &, const QVector<int> & )
{ {
updateButtons(); updateButtons();
@ -166,14 +166,15 @@ void QgsRasterAttributeTableWidget::init( int bandNumber )
void QgsRasterAttributeTableWidget::updateButtons() void QgsRasterAttributeTableWidget::updateButtons()
{ {
const bool enableEditingButtons { static_cast<bool>( mAttributeTable ) &&mEditable &&mRATView->selectionModel()->currentIndex().isValid() }; const bool enableEditingButtons( static_cast<bool>( mAttributeTableBuffer ) && mEditable && mRATView->selectionModel()->currentIndex().isValid() );
mActionToggleEditing->setChecked( mEditable );
mActionAddColumn->setEnabled( mEditable ); mActionAddColumn->setEnabled( mEditable );
mActionRemoveColumn->setEnabled( enableEditingButtons ); mActionRemoveColumn->setEnabled( enableEditingButtons );
mActionAddRow->setEnabled( enableEditingButtons ); mActionAddRow->setEnabled( enableEditingButtons );
mActionRemoveRow->setEnabled( enableEditingButtons ); mActionRemoveRow->setEnabled( enableEditingButtons );
mActionSaveChanges->setEnabled( mAttributeTable && mAttributeTable->isDirty() ); mActionSaveChanges->setEnabled( mAttributeTableBuffer && mAttributeTableBuffer->isDirty() );
mClassifyButton->setEnabled( mAttributeTable && mRasterLayer ); mClassifyButton->setEnabled( mAttributeTableBuffer && mRasterLayer );
mClassifyComboBox->setEnabled( mAttributeTable && mRasterLayer ); mClassifyComboBox->setEnabled( mAttributeTableBuffer && mRasterLayer );
} }
void QgsRasterAttributeTableWidget::setMessageBar( QgsMessageBar *bar ) void QgsRasterAttributeTableWidget::setMessageBar( QgsMessageBar *bar )
@ -181,83 +182,109 @@ void QgsRasterAttributeTableWidget::setMessageBar( QgsMessageBar *bar )
mMessageBar = bar; mMessageBar = bar;
} }
void QgsRasterAttributeTableWidget::setEditable( bool editable ) bool QgsRasterAttributeTableWidget::setEditable( bool editable )
{ {
bool isDirty { false }; const bool isDirty { mAttributeTableBuffer &&mAttributeTableBuffer->isDirty() &&mCurrentBand > 0 && mRasterLayer->attributeTable( mCurrentBand ) };
bool retVal { true };
for ( int bandNo = 1; bandNo <= mRasterLayer->bandCount(); ++bandNo ) // Switch to read-only
if ( ! editable && isDirty )
{ {
if ( mRasterLayer->dataProvider()->attributeTable( bandNo ) && mRasterLayer->dataProvider()->attributeTable( bandNo )->isDirty() ) const QMessageBox::StandardButtons buttons { QMessageBox::Button::Cancel | QMessageBox::Button::Yes | QMessageBox::Button::No };
switch ( QMessageBox::question( nullptr, tr( "Save Attribute Table" ), tr( "Attribute table contains unsaved changes, do you want to save the changes?" ), buttons ) )
{ {
isDirty = true; case QMessageBox::Button::Cancel:
break; {
retVal = false;
break;
}
case QMessageBox::Button::Yes:
{
saveChanges();
retVal = true;
break;
}
case QMessageBox::Button::No:
default:
{
// Reset to its original state
mAttributeTableBuffer = std::make_unique<QgsRasterAttributeTable>( *mRasterLayer->attributeTable( mCurrentBand ) );
init( mCurrentBand );
retVal = true;
break;
}
} }
} }
if ( isDirty && QMessageBox::question( nullptr, tr( "Save Attribute Table" ), tr( "Attribute table contains unsaved changes, do you want to save the changes?" ) ) == QMessageBox::Yes )
if ( retVal )
{ {
saveChanges(); mEditable = editable;
mModel->setEditable( editable );
} }
mEditable = editable;
mModel->setEditable( editable );
updateButtons(); updateButtons();
return retVal;
} }
void QgsRasterAttributeTableWidget::saveChanges() void QgsRasterAttributeTableWidget::saveChanges()
{ {
if ( mRasterLayer ) if ( mRasterLayer && mAttributeTableBuffer && mAttributeTableBuffer->isDirty() && mCurrentBand > 0 )
{ {
for ( int bandNo = 1; bandNo <= mRasterLayer->bandCount(); ++bandNo ) QgsRasterAttributeTable *attributeTable { mRasterLayer->dataProvider()->attributeTable( mCurrentBand ) };
if ( ! attributeTable )
{ {
QgsRasterAttributeTable *attributeTable { mRasterLayer->dataProvider()->attributeTable( bandNo ) }; QgsDebugMsg( QStringLiteral( "Error saving RAT: RAT for band %1 is unexpectedly gone!" ).arg( mCurrentBand ) );
if ( attributeTable && attributeTable->isDirty() ) }
else
{
*attributeTable = *mAttributeTableBuffer;
QString errorMessage;
QString newPath { attributeTable->filePath() };
const bool nativeRatSupported = mRasterLayer->dataProvider()->providerCapabilities().testFlag( QgsRasterDataProvider::ProviderCapability::NativeRasterAttributeTable );
bool saveToNative { false };
if ( newPath.isEmpty() && ! nativeRatSupported )
{ {
QString errorMessage; 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)" ) );
QString newPath { attributeTable->filePath() }; if ( newPath.isEmpty() )
const bool nativeRatSupported = mRasterLayer->dataProvider()->providerCapabilities().testFlag( QgsRasterDataProvider::ProviderCapability::NativeRasterAttributeTable );
bool saveToNative { false };
if ( newPath.isEmpty() && ! nativeRatSupported )
{
newPath = QFileDialog::getOpenFileName( nullptr, tr( "Save Raster Attribute Table (band %1) To File" ).arg( bandNo ), QFile::exists( mRasterLayer->dataProvider()->dataSourceUri( ) ) ? mRasterLayer->dataProvider()->dataSourceUri( ) + ".vat.dbf" : QString(), QStringLiteral( "VAT DBF Files (*.vat.dbf)" ) );
if ( newPath.isEmpty() )
{
// Aborted by user
return;
}
}
else if ( newPath.isEmpty() )
{
saveToNative = true;
}
bool writeSuccess { false };
// Save to file
if ( ! saveToNative && ! newPath.isEmpty() )
{
writeSuccess = attributeTable->writeToFile( attributeTable->filePath(), &errorMessage );
}
else if ( saveToNative )
{
writeSuccess = mRasterLayer->dataProvider()->writeNativeAttributeTable( &errorMessage );
}
if ( writeSuccess )
{
notify( tr( "Attribute Table Write Success" ), tr( "The raster attibute table has been successfully saved." ), Qgis::MessageLevel::Success );
}
else
{
notify( tr( "Attribute Table Write Error" ), errorMessage, Qgis::MessageLevel::Critical );
}
// Save to native saves RATs for all bands, no need to loop further.
if ( saveToNative )
{ {
// Aborted by user
return; return;
} }
} }
else if ( newPath.isEmpty() )
{
saveToNative = true;
}
bool writeSuccess { false };
// Save to file
if ( ! saveToNative && ! newPath.isEmpty() )
{
writeSuccess = attributeTable->writeToFile( attributeTable->filePath(), &errorMessage );
}
else if ( saveToNative )
{
writeSuccess = mRasterLayer->dataProvider()->writeNativeAttributeTable( &errorMessage );
}
if ( writeSuccess )
{
mAttributeTableBuffer->setDirty( false );
notify( tr( "Attribute Table Write Success" ), tr( "The raster attibute table has been successfully saved." ), Qgis::MessageLevel::Success );
}
else
{
notify( tr( "Attribute Table Write Error" ), errorMessage, Qgis::MessageLevel::Critical );
}
// Save to native saves RATs for all bands, no need to loop further.
if ( saveToNative )
{
return;
}
} }
} }
@ -267,7 +294,7 @@ void QgsRasterAttributeTableWidget::saveChanges()
void QgsRasterAttributeTableWidget::classify() void QgsRasterAttributeTableWidget::classify()
{ {
if ( ! mAttributeTable ) if ( ! mAttributeTableBuffer )
{ {
notify( tr( "Classification Error" ), tr( "The raster attribute table is not set." ), Qgis::MessageLevel::Critical ); notify( tr( "Classification Error" ), tr( "The raster attribute table is not set." ), Qgis::MessageLevel::Critical );
return; return;
@ -279,13 +306,22 @@ void QgsRasterAttributeTableWidget::classify()
return; return;
} }
if ( QMessageBox::question( nullptr, tr( "Apply Style From Attribute Table" ), tr( "The existing style for the raster will be replaced by a new style from the attribute table and any unsaved changes to the current style will be lost, do you want to proceed?" ) ) == QMessageBox::Yes ) QString confirmMessage;
QString errorMessage;
if ( ! mAttributeTableBuffer->isValid( &errorMessage ) )
{
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 );
}
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 )
{ {
if ( QgsRasterRenderer *renderer = mAttributeTable->createRenderer( mRasterLayer->dataProvider(), mCurrentBand, mClassifyComboBox->currentData().toInt() ) ) if ( QgsRasterRenderer *renderer = mAttributeTableBuffer->createRenderer( mRasterLayer->dataProvider(), mCurrentBand, mClassifyComboBox->currentData().toInt() ) )
{ {
mRasterLayer->setRenderer( renderer ); mRasterLayer->setRenderer( renderer );
mRasterLayer->triggerRepaint( ); mRasterLayer->triggerRepaint( );
emit rendererChanged();
} }
else else
{ {
@ -296,9 +332,9 @@ void QgsRasterAttributeTableWidget::classify()
void QgsRasterAttributeTableWidget::addColumn() void QgsRasterAttributeTableWidget::addColumn()
{ {
if ( mAttributeTable ) if ( mAttributeTableBuffer )
{ {
QgsRasterAttributeTableAddColumnDialog dlg { mAttributeTable }; QgsRasterAttributeTableAddColumnDialog dlg { mAttributeTableBuffer.get() };
if ( dlg.exec() == QDialog::Accepted ) if ( dlg.exec() == QDialog::Accepted )
{ {
QString errorMessage; QString errorMessage;
@ -330,7 +366,7 @@ void QgsRasterAttributeTableWidget::addColumn()
void QgsRasterAttributeTableWidget::removeColumn() void QgsRasterAttributeTableWidget::removeColumn()
{ {
const QModelIndex currentIndex { mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ) }; const QModelIndex currentIndex { mProxyModel->mapToSource( mRATView->selectionModel()->currentIndex() ) };
if ( mAttributeTable && currentIndex.isValid() && currentIndex.column() < mAttributeTable->fields().count() ) if ( mAttributeTableBuffer && currentIndex.isValid() && currentIndex.column() < mAttributeTableBuffer->fields().count() )
{ {
if ( QMessageBox::question( nullptr, tr( "Remove Column" ), tr( "Do you want to remove the selected column? This action cannot be undone." ) ) == QMessageBox::Yes ) if ( QMessageBox::question( nullptr, tr( "Remove Column" ), tr( "Do you want to remove the selected column? This action cannot be undone." ) ) == QMessageBox::Yes )
{ {
@ -345,7 +381,7 @@ void QgsRasterAttributeTableWidget::removeColumn()
void QgsRasterAttributeTableWidget::addRow() void QgsRasterAttributeTableWidget::addRow()
{ {
if ( mAttributeTable ) if ( mAttributeTableBuffer )
{ {
// Default to append // Default to append
int position { mModel->rowCount( QModelIndex() ) }; int position { mModel->rowCount( QModelIndex() ) };
@ -372,7 +408,7 @@ void QgsRasterAttributeTableWidget::addRow()
QVariantList rowData; QVariantList rowData;
QList<QgsRasterAttributeTable::Field> fields { mAttributeTable->fields() }; QList<QgsRasterAttributeTable::Field> fields { mAttributeTableBuffer->fields() };
for ( const QgsRasterAttributeTable::Field &field : std::as_const( fields ) ) for ( const QgsRasterAttributeTable::Field &field : std::as_const( fields ) )
{ {
rowData.push_back( QVariant( field.type ) ); rowData.push_back( QVariant( field.type ) );
@ -393,7 +429,7 @@ void QgsRasterAttributeTableWidget::addRow()
void QgsRasterAttributeTableWidget::removeRow() void QgsRasterAttributeTableWidget::removeRow()
{ {
if ( mAttributeTable && mRATView->selectionModel()->currentIndex().isValid() ) if ( mAttributeTableBuffer && mRATView->selectionModel()->currentIndex().isValid() )
{ {
if ( QMessageBox::question( nullptr, tr( "Remove Row" ), tr( "Do you want to remove the selected row? This action cannot be undone." ) ) == QMessageBox::Yes ) if ( QMessageBox::question( nullptr, tr( "Remove Row" ), tr( "Do you want to remove the selected row? This action cannot be undone." ) ) == QMessageBox::Yes )
{ {
@ -409,8 +445,13 @@ void QgsRasterAttributeTableWidget::removeRow()
void QgsRasterAttributeTableWidget::bandChanged( const int index ) void QgsRasterAttributeTableWidget::bandChanged( const int index )
{ {
const QVariant itemData = mRasterBandsComboBox->itemData( index ); const QVariant itemData = mRasterBandsComboBox->itemData( index );
if ( itemData.isValid() ) if ( itemData.isValid() )
{ {
if ( mEditable )
{
setEditable( false );
}
init( itemData.toInt( ) ); init( itemData.toInt( ) );
} }
} }
@ -449,9 +490,9 @@ void QgsRasterAttributeTableWidget::notify( const QString &title, const QString
void QgsRasterAttributeTableWidget::setDelegates() void QgsRasterAttributeTableWidget::setDelegates()
{ {
mClassifyComboBox->clear(); mClassifyComboBox->clear();
if ( mAttributeTable ) if ( mAttributeTableBuffer )
{ {
const QList<QgsRasterAttributeTable::Field> tableFields { mAttributeTable->fields() }; const QList<QgsRasterAttributeTable::Field> tableFields { mAttributeTableBuffer->fields() };
int fieldIdx { 0 }; int fieldIdx { 0 };
const QHash<Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation> usageInfo { QgsRasterAttributeTable::usageInformation() }; const QHash<Qgis::RasterAttributeTableFieldUsage, QgsRasterAttributeTable::UsageInformation> usageInfo { QgsRasterAttributeTable::usageInformation() };
for ( const QgsRasterAttributeTable::Field &f : std::as_const( tableFields ) ) for ( const QgsRasterAttributeTable::Field &f : std::as_const( tableFields ) )
@ -465,26 +506,26 @@ void QgsRasterAttributeTableWidget::setDelegates()
fieldIdx++; fieldIdx++;
} }
if ( mAttributeTable->hasColor() ) if ( mAttributeTableBuffer->hasColor() )
{ {
if ( mAttributeTable->usages().contains( Qgis::RasterAttributeTableFieldUsage::Alpha ) ) if ( mAttributeTableBuffer->usages().contains( Qgis::RasterAttributeTableFieldUsage::Alpha ) )
{ {
mRATView->setItemDelegateForColumn( mAttributeTable->fields().count( ), new ColorAlphaDelegate( mRATView ) ); mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count( ), new ColorAlphaDelegate( mRATView ) );
} }
else else
{ {
mRATView->setItemDelegateForColumn( mAttributeTable->fields().count( ), new ColorDelegate( mRATView ) ); mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count( ), new ColorDelegate( mRATView ) );
} }
} }
else if ( mAttributeTable->hasRamp() ) else if ( mAttributeTableBuffer->hasRamp() )
{ {
if ( mAttributeTable->usages().contains( Qgis::RasterAttributeTableFieldUsage::AlphaMin ) ) if ( mAttributeTableBuffer->usages().contains( Qgis::RasterAttributeTableFieldUsage::AlphaMin ) )
{ {
mRATView->setItemDelegateForColumn( mAttributeTable->fields().count( ), new ColorRampAlphaDelegate( mRATView ) ); mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count( ), new ColorRampAlphaDelegate( mRATView ) );
} }
else else
{ {
mRATView->setItemDelegateForColumn( mAttributeTable->fields().count( ), new ColorRampDelegate( mRATView ) ); mRATView->setItemDelegateForColumn( mAttributeTableBuffer->fields().count( ), new ColorRampDelegate( mRATView ) );
} }
} }
} }

View File

@ -122,10 +122,48 @@ class GUI_EXPORT QgsRasterAttributeTableWidget : public QWidget, private Ui::Qgs
*/ */
bool isDirty( ) const; bool isDirty( ) const;
/**
* Sets the message \a bar associated with the widget. This allows the widget to push feedback messages
* to the appropriate message bar.
* \see messageBar()
*/
void setMessageBar( QgsMessageBar *bar );
signals:
/**
* This signal is emitted after a successful classify operation which changed the raster renderer.
*/
void rendererChanged( );
public slots:
/**
* Save the changes in the raster attribute table.
*/
void saveChanges();
/**
* Set the editable state, it may trigger save changes if the attribute table has unsave changes.
*/
bool setEditable( bool editable );
private slots:
void classify();
void addColumn();
void removeColumn();
void addRow();
void removeRow();
void bandChanged( const int index );
void notify( const QString &title, const QString &message, Qgis::MessageLevel level = Qgis::MessageLevel::Info );
void setDelegates( );
private: private:
QgsRasterLayer *mRasterLayer = nullptr; QgsRasterLayer *mRasterLayer = nullptr;
QgsRasterAttributeTable *mAttributeTable = nullptr; std::unique_ptr<QgsRasterAttributeTable> mAttributeTableBuffer;
// Default to invalid (bands are 1-indexed) // Default to invalid (bands are 1-indexed)
int mCurrentBand = 0; int mCurrentBand = 0;
bool mEditable = false; bool mEditable = false;
@ -144,28 +182,6 @@ class GUI_EXPORT QgsRasterAttributeTableWidget : public QWidget, private Ui::Qgs
void init( int bandNumber = 0 ); void init( int bandNumber = 0 );
void updateButtons(); void updateButtons();
/**
* Sets the message \a bar associated with the widget. This allows the widget to push feedback messages
* to the appropriate message bar.
* \see messageBar()
*/
void setMessageBar( QgsMessageBar *bar );
private slots:
void setEditable( bool editable );
void saveChanges();
void classify();
void addColumn();
void removeColumn();
void addRow();
void removeRow();
void bandChanged( const int index );
void notify( const QString &title, const QString &message, Qgis::MessageLevel level = Qgis::MessageLevel::Info );
void setDelegates( );
}; };
#endif // QGSRASTERATTRIBUTETABLEWIDGET_H #endif // QGSRASTERATTRIBUTETABLEWIDGET_H

View File

@ -68,6 +68,7 @@
#include "qgsdoublevalidator.h" #include "qgsdoublevalidator.h"
#include "qgsmaplayerconfigwidgetfactory.h" #include "qgsmaplayerconfigwidgetfactory.h"
#include "qgsprojectutils.h" #include "qgsprojectutils.h"
#include "qgsrasterattributetablewidget.h"
#include "qgsrasterlayertemporalpropertieswidget.h" #include "qgsrasterlayertemporalpropertieswidget.h"
#include "qgsprojecttimesettings.h" #include "qgsprojecttimesettings.h"
@ -234,6 +235,21 @@ QgsRasterLayerProperties::QgsRasterLayerProperties( QgsMapLayer *lyr, QgsMapCanv
return; return;
} }
// Setup raster attribute table
if ( mRasterLayer->attributeTableCount() > 0 )
{
mRasterAttributeTableWidget = new QgsRasterAttributeTableWidget( this, mRasterLayer );
mOptsPage_RasterAttributeTable->layout()->addWidget( mRasterAttributeTableWidget );
// When the renderer changes we need to sync the style options page
connect( mRasterAttributeTableWidget, &QgsRasterAttributeTableWidget::rendererChanged, this, &QgsRasterLayerProperties::syncToLayer );
mNoRasterAttributeTableWidget->hide();
}
else
{
mNoRasterAttributeTableWidget->show();
}
mBackupCrs = mRasterLayer->crs(); mBackupCrs = mRasterLayer->crs();
// Handles window modality raising canvas // Handles window modality raising canvas

View File

@ -46,6 +46,7 @@ class QgsMapLayerConfigWidgetFactory;
class QgsMapLayerConfigWidget; class QgsMapLayerConfigWidget;
class QgsPropertyOverrideButton; class QgsPropertyOverrideButton;
class QgsRasterTransparencyWidget; class QgsRasterTransparencyWidget;
class QgsRasterAttributeTableWidget;
/** /**
@ -296,5 +297,7 @@ class GUI_EXPORT QgsRasterLayerProperties : public QgsOptionsDialogBase, private
friend class QgsAppScreenShots; friend class QgsAppScreenShots;
QgsCoordinateReferenceSystem mBackupCrs; QgsCoordinateReferenceSystem mBackupCrs;
QgsRasterAttributeTableWidget *mRasterAttributeTableWidget = nullptr;
}; };
#endif #endif

View File

@ -93,7 +93,7 @@
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="currentRow"> <property name="currentRow">
<number>0</number> <number>-1</number>
</property> </property>
<item> <item>
<property name="text"> <property name="text">
@ -203,6 +203,18 @@
<normaloff>:/images/themes/default/legend.svg</normaloff>:/images/themes/default/legend.svg</iconset> <normaloff>:/images/themes/default/legend.svg</normaloff>:/images/themes/default/legend.svg</iconset>
</property> </property>
</item> </item>
<item>
<property name="text">
<string>Attribute Tables</string>
</property>
<property name="toolTip">
<string>Manage Raster Attribute Tables</string>
</property>
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/propertyicons/attributes.svg</normaloff>:/images/themes/default/propertyicons/attributes.svg</iconset>
</property>
</item>
<item> <item>
<property name="text"> <property name="text">
<string>QGIS Server</string> <string>QGIS Server</string>
@ -254,7 +266,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>10</number>
</property> </property>
<widget class="QWidget" name="mOptsPage_Information"> <widget class="QWidget" name="mOptsPage_Information">
<layout class="QVBoxLayout" name="verticalLayout_20"> <layout class="QVBoxLayout" name="verticalLayout_20">
@ -299,8 +311,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>253</width> <width>648</width>
<height>392</height> <height>690</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_7"> <layout class="QVBoxLayout" name="verticalLayout_7">
@ -1438,6 +1450,55 @@ p, li { white-space: pre-wrap; }
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="mOptsPage_RasterAttributeTable">
<layout class="QVBoxLayout" name="verticalLayout_rat">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="mNoRasterAttributeTableWidget" native="true">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QLabel" name="mRasterAttributeTableLabel">
<property name="text">
<string>There are no raster attribute tables associated with this data source.
If the current symbology can be converted to an attribute table you can create a new attribute table using the context menu available in the layer tree.
From the layer tree context menu it you can also import an existing raster attribute table from a VAT.DBF file and associate it with a raster band.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="mOptsPage_Server"> <widget class="QWidget" name="mOptsPage_Server">
<layout class="QVBoxLayout" name="verticalLayout_8"> <layout class="QVBoxLayout" name="verticalLayout_8">
<property name="leftMargin"> <property name="leftMargin">
@ -1466,7 +1527,7 @@ p, li { white-space: pre-wrap; }
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>263</width> <width>263</width>
<height>566</height> <height>600</height>
</rect> </rect>
</property> </property>
<layout class="QGridLayout" name="gridLayout_12"> <layout class="QGridLayout" name="gridLayout_12">
@ -1994,6 +2055,8 @@ p, li { white-space: pre-wrap; }
</tabstops> </tabstops>
<resources> <resources>
<include location="../../images/images.qrc"/> <include location="../../images/images.qrc"/>
<include location="../../images/images.qrc"/>
<include location="../../images/images.qrc"/>
</resources> </resources>
<connections> <connections>
<connection> <connection>

View File

@ -17,7 +17,7 @@
<item> <item>
<widget class="QLabel" name="mCreateInfoLabel"> <widget class="QLabel" name="mCreateInfoLabel">
<property name="text"> <property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Create a new Raster Attribute Table (RAT) from the current symbology.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Create a new Raster Attribute Table from the current symbology.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>

View File

@ -111,6 +111,9 @@
<property name="text"> <property name="text">
<string>After</string> <string>After</string>
</property> </property>
<property name="checked">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item row="3" column="0"> <item row="3" column="0">
@ -119,7 +122,7 @@
<string>Before</string> <string>Before</string>
</property> </property>
<property name="checked"> <property name="checked">
<bool>true</bool> <bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>