Skip to content

Commit

Permalink
Gui enhancements
Browse files Browse the repository at this point in the history
  • Loading branch information
elpaso authored and nyalldawson committed Nov 8, 2022
1 parent 3479a66 commit 70c93d4
Show file tree
Hide file tree
Showing 18 changed files with 484 additions and 199 deletions.
2 changes: 1 addition & 1 deletion python/core/auto_generated/raster/qgsrasterlayer.sip.in
Expand Up @@ -261,7 +261,7 @@ Returns the (possibly NULL) raster attribute table for the given band ``bandNumb
.. versionadded:: 3.30
%End

int attributeTableCount( );
int attributeTableCount( ) const;
%Docstring
Returns the number of attribute tables for the raster by counting the number of bands that have an associated attribute table.

Expand Down
Expand Up @@ -46,6 +46,33 @@ Sets the raster layer and an optional band number.
bool isDirty( ) const;
%Docstring
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

};
Expand Down
136 changes: 128 additions & 8 deletions src/app/qgisapp.cpp
Expand Up @@ -4380,6 +4380,12 @@ void QgisApp::setupConnections()

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( mActionPreviewModeOff, &QAction::triggered, this, &QgisApp::disablePreviewMode );
connect( mActionPreviewModeMono, &QAction::triggered, this, &QgisApp::activateMonoPreview );
Expand Down Expand Up @@ -5600,7 +5606,7 @@ void QgisApp::fileExit()
}

QgsCanvasRefreshBlocker refreshBlocker;
if ( checkUnsavedLayerEdits() && checkMemoryLayers() && saveDirty() && checkExitBlockers() )
if ( checkUnsavedLayerEdits() && checkMemoryLayers() && saveDirty() && checkExitBlockers() && checkUnsavedRasterAttributeTableEdits() )
{
closeProject();
userProfileManager()->setDefaultFromActive();
Expand Down Expand Up @@ -5638,7 +5644,7 @@ bool QgisApp::fileNew( bool promptToSaveFlag, bool forceBlank )

if ( promptToSaveFlag )
{
if ( !checkUnsavedLayerEdits() || !checkMemoryLayers() || !saveDirty() )
if ( !checkUnsavedLayerEdits() || !checkMemoryLayers() || !saveDirty() || !checkUnsavedRasterAttributeTableEdits() )
{
return false; //cancel pressed
}
Expand Down Expand Up @@ -5710,7 +5716,7 @@ bool QgisApp::fileNewFromTemplate( const QString &fileName )
if ( checkTasksDependOnProject() )
return false;

if ( !checkUnsavedLayerEdits() || !checkMemoryLayers() || !saveDirty() )
if ( !checkUnsavedLayerEdits() || !checkMemoryLayers() || !saveDirty() || !checkUnsavedRasterAttributeTableEdits() )
{
return false; //cancel pressed
}
Expand Down Expand Up @@ -6206,7 +6212,7 @@ void QgisApp::fileOpen()
return;

// 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
QgsSettings settings;
Expand Down Expand Up @@ -6262,7 +6268,7 @@ void QgisApp::fileRevert()
QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) == QMessageBox::No )
return;

if ( !checkUnsavedLayerEdits() || !checkMemoryLayers() )
if ( !checkUnsavedLayerEdits() || !checkMemoryLayers() || ! checkUnsavedRasterAttributeTableEdits() )
return;

// re-open the current project
Expand Down Expand Up @@ -6760,7 +6766,7 @@ void QgisApp::openProject( QAction *action )
if ( checkTasksDependOnProject() )
return;

if ( checkUnsavedLayerEdits() && checkMemoryLayers() && saveDirty() )
if ( checkUnsavedLayerEdits() && checkMemoryLayers() && saveDirty() && checkUnsavedRasterAttributeTableEdits() )
addProject( project );
}

Expand Down Expand Up @@ -6805,7 +6811,7 @@ void QgisApp::openProject( const QString &fileName )
return;

// 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
addProject( fileName );
Expand Down Expand Up @@ -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()
{
if ( !mLayerTreeView )
Expand All @@ -12053,7 +12083,7 @@ void QgisApp::createRasterAttributeTable()
if ( ! rat )
{
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 );
return;
}
Expand Down Expand Up @@ -13381,6 +13411,94 @@ bool QgisApp::checkUnsavedLayerEdits()
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()
{
if ( !QgsSettings().value( QStringLiteral( "askToSaveMemoryLayers" ), true, QgsSettings::App ).toBool() )
Expand Down Expand Up @@ -16175,6 +16293,8 @@ void QgisApp::showLayerProperties( QgsMapLayer *mapLayer, const QString &page )
{
rasterLayerPropertiesDialog->deleteLater();
} );


break;
}

Expand Down
26 changes: 24 additions & 2 deletions src/app/qgisapp.h
Expand Up @@ -1106,15 +1106,15 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
void legendLayerStretchUsingCurrentExtent();

/**
* Open the RasterAttributeTable for the raster layer.
* Open the Raster Attribute Table for the raster layer.
* Only works on raster layers.
*
* \since QGIS 3.30
*/
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.
*
* Only works on raster layers.
Expand All @@ -1123,6 +1123,15 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
*/
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.
bool event( QEvent *event ) override;

Expand Down Expand Up @@ -1339,6 +1348,19 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
*/
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
* shows a warning to users that their contents will be lost on
Expand Down
4 changes: 3 additions & 1 deletion src/app/qgsapplayertreeviewmenuprovider.cpp
Expand Up @@ -229,8 +229,10 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
}
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)
Expand Down
31 changes: 3 additions & 28 deletions src/core/providers/gdal/qgsgdalprovider.cpp
Expand Up @@ -3212,25 +3212,10 @@ bool QgsGdalProvider::writeNativeAttributeTable( QString *errorMessage ) //#spel
}

// 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" ) );
GDALClose( mGdalDataset );

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;
}
setEditable( true );
wasReopenedReadWrite = true;
}

Expand Down Expand Up @@ -3327,17 +3312,7 @@ bool QgsGdalProvider::writeNativeAttributeTable( QString *errorMessage ) //#spel
if ( wasReopenedReadWrite )
{
QgsDebugMsg( QStringLiteral( "re-opening the dataset in read-only mode" ) );
GDALClose( mGdalDataset );

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;
}
setEditable( false );
}

return success;
Expand Down

0 comments on commit 70c93d4

Please sign in to comment.