@@ -503,31 +503,119 @@ void QgsAppFileItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *m
503
503
504
504
QMenu *manageFileMenu = new QMenu ( tr ( " Manage" ), menu );
505
505
506
- QStringList selectedDeletableFiles ;
506
+ QStringList selectedManagableFiles ;
507
507
QList< QPointer< QgsDataItem > > selectedParents;
508
508
for ( QgsDataItem *selectedItem : selectedItems )
509
509
{
510
510
if ( selectedItem->capabilities2 () & Qgis::BrowserItemCapability::ItemRepresentsFile )
511
511
{
512
- selectedDeletableFiles .append ( selectedItem->path () );
512
+ selectedManagableFiles .append ( selectedItem->path () );
513
513
selectedParents << selectedItem->parent ();
514
514
}
515
515
}
516
- const QString deleteText = selectedDeletableFiles.count () == 1 ? tr ( " Delete “%1”…" ).arg ( fi.fileName () )
516
+
517
+ if ( selectedManagableFiles.size () == 1 )
518
+ {
519
+ const QString renameText = tr ( " Rename “%1”…" ).arg ( fi.fileName () );
520
+ QAction *renameAction = new QAction ( renameText, menu );
521
+ connect ( renameAction, &QAction::triggered, this , [ = ]
522
+ {
523
+ const QString oldPath = selectedManagableFiles.value ( 0 );
524
+ // Check if the file corresponds to paths in the project
525
+ const QList<QgsMapLayer *> layersList = QgsProjectUtils::layersMatchingPath ( QgsProject::instance (), oldPath );
526
+
527
+ const QStringList existingNames = QFileInfo ( oldPath ).dir ().entryList ();
528
+
529
+ QgsNewNameDialog dlg ( tr ( " file" ), QFileInfo ( oldPath ).fileName (), QStringList (), existingNames, Qt::CaseInsensitive, menu );
530
+ dlg.setWindowTitle ( tr ( " Rename %1" ).arg ( QFileInfo ( oldPath ).fileName () ) );
531
+ dlg.setHintString ( tr ( " Rename “%1” to" ).arg ( QFileInfo ( oldPath ).fileName () ) );
532
+ dlg.setOverwriteEnabled ( false );
533
+ dlg.setConflictingNameWarning ( tr ( " A file with this name already exists." ) );
534
+ if ( dlg.exec () != QDialog::Accepted || dlg.name ().isEmpty () )
535
+ return ;
536
+
537
+ QString newName = dlg.name ();
538
+ if ( QFileInfo ( newName ).suffix ().isEmpty () )
539
+ newName = newName + ' .' + QFileInfo ( oldPath ).suffix ();
540
+
541
+ const QString newPath = QFileInfo ( oldPath ).dir ().filePath ( newName );
542
+
543
+ bool updateLayers = false ;
544
+ if ( !layersList.empty () )
545
+ {
546
+ QMessageBox message ( QMessageBox::Warning, tr ( " Rename %1" ).arg ( QFileInfo ( oldPath ).fileName () ),
547
+ tr ( " The file %1 exists in the current project. Are you sure you want to rename it?" ).arg ( QFileInfo ( oldPath ).fileName () ),
548
+ QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel );
549
+ message.setDefaultButton ( QMessageBox::Cancel );
550
+ message.setButtonText ( QMessageBox::Yes, tr ( " Rename and Update Layer Paths" ) );
551
+ message.setButtonText ( QMessageBox::No, tr ( " Rename but Leave Layer Paths" ) );
552
+
553
+ QStringList layerNames;
554
+ layerNames.reserve ( layersList.size () );
555
+ for ( const QgsMapLayer *layer : std::as_const ( layersList ) )
556
+ {
557
+ layerNames << layer->name ();
558
+ }
559
+ const QString detailedText = tr ( " The following layers will be affected:" ) + QStringLiteral ( " \n\n • %1" ).arg ( layerNames.join ( QStringLiteral ( " \n • " ) ) );
560
+ message.setDetailedText ( detailedText );
561
+
562
+ int res = message.exec ();
563
+ if ( res == QMessageBox::Cancel )
564
+ return ;
565
+
566
+ if ( res == QMessageBox::Yes )
567
+ updateLayers = true ;
568
+ }
569
+
570
+ QString error;
571
+ const bool result = QgsFileUtils::renameDataset ( oldPath, newPath, error );
572
+
573
+ for ( const QPointer< QgsDataItem > &parent : selectedParents )
574
+ {
575
+ if ( parent )
576
+ parent->refresh ();
577
+ }
578
+
579
+ if ( !layersList.empty () )
580
+ {
581
+ if ( updateLayers )
582
+ {
583
+ QgsProjectUtils::updateLayerPath ( QgsProject::instance (), oldPath, newPath );
584
+ QgsProject::instance ()->setDirty ( true );
585
+ }
586
+ else
587
+ {
588
+ // we just update the layer source to get it to recognize that it's now broken in the UI
589
+ for ( QgsMapLayer *layer : std::as_const ( layersList ) )
590
+ {
591
+ layer->setDataSource ( layer->source (), layer->name (), layer->providerType () );
592
+ }
593
+ }
594
+ }
595
+
596
+ if ( !result )
597
+ {
598
+ notify ( QString (), error, context, Qgis::MessageLevel::Critical );
599
+ }
600
+ } );
601
+ manageFileMenu->addAction ( renameAction );
602
+ }
603
+
604
+ const QString deleteText = selectedManagableFiles.count () == 1 ? tr ( " Delete “%1”…" ).arg ( fi.fileName () )
517
605
: tr ( " Delete Selected Files…" );
518
606
QAction *deleteAction = new QAction ( deleteText, menu );
519
607
connect ( deleteAction, &QAction::triggered, this , [ = ]
520
608
{
521
609
// Check if the files correspond to paths in the project
522
610
QList<QgsMapLayer *> layersList;
523
- for ( const QString &path : std::as_const ( selectedDeletableFiles ) )
611
+ for ( const QString &path : std::as_const ( selectedManagableFiles ) )
524
612
{
525
613
layersList << QgsProjectUtils::layersMatchingPath ( QgsProject::instance (), path );
526
614
}
527
615
528
616
// now expand out the list of files to include all sidecar files (e.g. .aux.xml files)
529
617
QSet< QString > allFilesWithSidecars;
530
- for ( const QString &file : std::as_const ( selectedDeletableFiles ) )
618
+ for ( const QString &file : std::as_const ( selectedManagableFiles ) )
531
619
{
532
620
allFilesWithSidecars.insert ( file );
533
621
allFilesWithSidecars.unite ( QgsFileUtils::sidecarFilesForPath ( file ) );
@@ -542,9 +630,9 @@ void QgsAppFileItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *m
542
630
if ( layersList.empty () )
543
631
{
544
632
// generic warning
545
- QMessageBox message ( QMessageBox::Warning, sortedAllFilesWithSidecars.size () > 1 ? tr ( " Delete Files" ) : tr ( " Delete %1" ).arg ( QFileInfo ( selectedDeletableFiles .at ( 0 ) ).fileName () ),
633
+ QMessageBox message ( QMessageBox::Warning, sortedAllFilesWithSidecars.size () > 1 ? tr ( " Delete Files" ) : tr ( " Delete %1" ).arg ( QFileInfo ( selectedManagableFiles .at ( 0 ) ).fileName () ),
546
634
sortedAllFilesWithSidecars.size () > 1 ? tr ( " Permanently delete %1 files?" ).arg ( sortedAllFilesWithSidecars.size () )
547
- : tr ( " Permanently delete “%1”?" ).arg ( QFileInfo ( selectedDeletableFiles .at ( 0 ) ).fileName () ),
635
+ : tr ( " Permanently delete “%1”?" ).arg ( QFileInfo ( selectedManagableFiles .at ( 0 ) ).fileName () ),
548
636
QMessageBox::Yes | QMessageBox::No );
549
637
message.setDefaultButton ( QMessageBox::No );
550
638
@@ -565,9 +653,9 @@ void QgsAppFileItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *m
565
653
}
566
654
else
567
655
{
568
- QMessageBox message ( QMessageBox::Warning, sortedAllFilesWithSidecars.size () > 1 ? tr ( " Delete Files" ) : tr ( " Delete %1" ).arg ( QFileInfo ( selectedDeletableFiles .at ( 0 ) ).fileName () ),
656
+ QMessageBox message ( QMessageBox::Warning, sortedAllFilesWithSidecars.size () > 1 ? tr ( " Delete Files" ) : tr ( " Delete %1" ).arg ( QFileInfo ( selectedManagableFiles .at ( 0 ) ).fileName () ),
569
657
sortedAllFilesWithSidecars.size () > 1 ? tr ( " One or more selected files exist in the current project. Are you sure you want to delete these files?" )
570
- : tr ( " The file %1 exists in the current project. Are you sure you want to delete it?" ).arg ( QFileInfo ( selectedDeletableFiles .at ( 0 ) ).fileName () ),
658
+ : tr ( " The file %1 exists in the current project. Are you sure you want to delete it?" ).arg ( QFileInfo ( selectedManagableFiles .at ( 0 ) ).fileName () ),
571
659
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel );
572
660
message.setDefaultButton ( QMessageBox::Cancel );
573
661
message.setButtonText ( QMessageBox::Yes, tr ( " Delete and Remove Layers" ) );
0 commit comments