Skip to content

Commit

Permalink
[feature] Add Rename action under the manage submenu for files in bro…
Browse files Browse the repository at this point in the history
…wser

Allows renaming of files directly in the browser. If the file
corresponds to a spatial dataset with multiple sidecar files then
these will all be renamed accordingly too.

Additionally, users are warned if the file is a layer which is exists
in the current project and are asked whether they want to automatically
update all the layer paths accordingly.

Refs #14611
  • Loading branch information
nyalldawson committed Aug 10, 2021
1 parent 1608547 commit 7a4fc6e
Showing 1 changed file with 97 additions and 9 deletions.
106 changes: 97 additions & 9 deletions src/app/browser/qgsinbuiltdataitemproviders.cpp
Expand Up @@ -503,31 +503,119 @@ void QgsAppFileItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *m

QMenu *manageFileMenu = new QMenu( tr( "Manage" ), menu );

QStringList selectedDeletableFiles;
QStringList selectedManagableFiles;
QList< QPointer< QgsDataItem > > selectedParents;
for ( QgsDataItem *selectedItem : selectedItems )
{
if ( selectedItem->capabilities2() & Qgis::BrowserItemCapability::ItemRepresentsFile )
{
selectedDeletableFiles.append( selectedItem->path() );
selectedManagableFiles.append( selectedItem->path() );
selectedParents << selectedItem->parent();
}
}
const QString deleteText = selectedDeletableFiles.count() == 1 ? tr( "Delete “%1”…" ).arg( fi.fileName() )

if ( selectedManagableFiles.size() == 1 )
{
const QString renameText = tr( "Rename “%1”…" ).arg( fi.fileName() );
QAction *renameAction = new QAction( renameText, menu );
connect( renameAction, &QAction::triggered, this, [ = ]
{
const QString oldPath = selectedManagableFiles.value( 0 );
// Check if the file corresponds to paths in the project
const QList<QgsMapLayer *> layersList = QgsProjectUtils::layersMatchingPath( QgsProject::instance(), oldPath );

const QStringList existingNames = QFileInfo( oldPath ).dir().entryList();

QgsNewNameDialog dlg( tr( "file" ), QFileInfo( oldPath ).fileName(), QStringList(), existingNames, Qt::CaseInsensitive, menu );
dlg.setWindowTitle( tr( "Rename %1" ).arg( QFileInfo( oldPath ).fileName() ) );
dlg.setHintString( tr( "Rename “%1” to" ).arg( QFileInfo( oldPath ).fileName() ) );
dlg.setOverwriteEnabled( false );
dlg.setConflictingNameWarning( tr( "A file with this name already exists." ) );
if ( dlg.exec() != QDialog::Accepted || dlg.name().isEmpty() )
return;

QString newName = dlg.name();
if ( QFileInfo( newName ).suffix().isEmpty() )
newName = newName + '.' + QFileInfo( oldPath ).suffix();

const QString newPath = QFileInfo( oldPath ).dir().filePath( newName );

bool updateLayers = false;
if ( !layersList.empty() )
{
QMessageBox message( QMessageBox::Warning, tr( "Rename %1" ).arg( QFileInfo( oldPath ).fileName() ),
tr( "The file %1 exists in the current project. Are you sure you want to rename it?" ).arg( QFileInfo( oldPath ).fileName() ),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel );
message.setDefaultButton( QMessageBox::Cancel );
message.setButtonText( QMessageBox::Yes, tr( "Rename and Update Layer Paths" ) );
message.setButtonText( QMessageBox::No, tr( "Rename but Leave Layer Paths" ) );

QStringList layerNames;
layerNames.reserve( layersList.size() );
for ( const QgsMapLayer *layer : std::as_const( layersList ) )
{
layerNames << layer->name();
}
const QString detailedText = tr( "The following layers will be affected:" ) + QStringLiteral( "\n\n• %1" ).arg( layerNames.join( QStringLiteral( "\n" ) ) );
message.setDetailedText( detailedText );

int res = message.exec();
if ( res == QMessageBox::Cancel )
return;

if ( res == QMessageBox::Yes )
updateLayers = true;
}

QString error;
const bool result = QgsFileUtils::renameDataset( oldPath, newPath, error );

for ( const QPointer< QgsDataItem > &parent : selectedParents )
{
if ( parent )
parent->refresh();
}

if ( !layersList.empty() )
{
if ( updateLayers )
{
QgsProjectUtils::updateLayerPath( QgsProject::instance(), oldPath, newPath );
QgsProject::instance()->setDirty( true );
}
else
{
// we just update the layer source to get it to recognize that it's now broken in the UI
for ( QgsMapLayer *layer : std::as_const( layersList ) )
{
layer->setDataSource( layer->source(), layer->name(), layer->providerType() );
}
}
}

if ( !result )
{
notify( QString(), error, context, Qgis::MessageLevel::Critical );
}
} );
manageFileMenu->addAction( renameAction );
}

const QString deleteText = selectedManagableFiles.count() == 1 ? tr( "Delete “%1”…" ).arg( fi.fileName() )
: tr( "Delete Selected Files…" );
QAction *deleteAction = new QAction( deleteText, menu );
connect( deleteAction, &QAction::triggered, this, [ = ]
{
// Check if the files correspond to paths in the project
QList<QgsMapLayer *> layersList;
for ( const QString &path : std::as_const( selectedDeletableFiles ) )
for ( const QString &path : std::as_const( selectedManagableFiles ) )
{
layersList << QgsProjectUtils::layersMatchingPath( QgsProject::instance(), path );
}

// now expand out the list of files to include all sidecar files (e.g. .aux.xml files)
QSet< QString > allFilesWithSidecars;
for ( const QString &file : std::as_const( selectedDeletableFiles ) )
for ( const QString &file : std::as_const( selectedManagableFiles ) )
{
allFilesWithSidecars.insert( file );
allFilesWithSidecars.unite( QgsFileUtils::sidecarFilesForPath( file ) );
Expand All @@ -542,9 +630,9 @@ void QgsAppFileItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *m
if ( layersList.empty() )
{
// generic warning
QMessageBox message( QMessageBox::Warning, sortedAllFilesWithSidecars.size() > 1 ? tr( "Delete Files" ) : tr( "Delete %1" ).arg( QFileInfo( selectedDeletableFiles.at( 0 ) ).fileName() ),
QMessageBox message( QMessageBox::Warning, sortedAllFilesWithSidecars.size() > 1 ? tr( "Delete Files" ) : tr( "Delete %1" ).arg( QFileInfo( selectedManagableFiles.at( 0 ) ).fileName() ),
sortedAllFilesWithSidecars.size() > 1 ? tr( "Permanently delete %1 files?" ).arg( sortedAllFilesWithSidecars.size() )
: tr( "Permanently delete “%1”?" ).arg( QFileInfo( selectedDeletableFiles.at( 0 ) ).fileName() ),
: tr( "Permanently delete “%1”?" ).arg( QFileInfo( selectedManagableFiles.at( 0 ) ).fileName() ),
QMessageBox::Yes | QMessageBox::No );
message.setDefaultButton( QMessageBox::No );

Expand All @@ -565,9 +653,9 @@ void QgsAppFileItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *m
}
else
{
QMessageBox message( QMessageBox::Warning, sortedAllFilesWithSidecars.size() > 1 ? tr( "Delete Files" ) : tr( "Delete %1" ).arg( QFileInfo( selectedDeletableFiles.at( 0 ) ).fileName() ),
QMessageBox message( QMessageBox::Warning, sortedAllFilesWithSidecars.size() > 1 ? tr( "Delete Files" ) : tr( "Delete %1" ).arg( QFileInfo( selectedManagableFiles.at( 0 ) ).fileName() ),
sortedAllFilesWithSidecars.size() > 1 ? tr( "One or more selected files exist in the current project. Are you sure you want to delete these files?" )
: tr( "The file %1 exists in the current project. Are you sure you want to delete it?" ).arg( QFileInfo( selectedDeletableFiles.at( 0 ) ).fileName() ),
: tr( "The file %1 exists in the current project. Are you sure you want to delete it?" ).arg( QFileInfo( selectedManagableFiles.at( 0 ) ).fileName() ),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel );
message.setDefaultButton( QMessageBox::Cancel );
message.setButtonText( QMessageBox::Yes, tr( "Delete and Remove Layers" ) );
Expand Down

0 comments on commit 7a4fc6e

Please sign in to comment.