Skip to content

Commit 04ff180

Browse files
jeremypwJeremy Wootten
andauthored
Do not allow simultaneous opening of projects with parent-child relationship (#1500)
* Check whether new project is parent or child of existing one * Dialog before close parent/child projects * Use ngettext for dialog text * Update POTFILES * Update copyright date on changed files * Restore FileView completely before restore open documents --------- Co-authored-by: Jeremy Wootten <jeremy@Proteus-EL07R6-9b3c42bb.localdomain>
1 parent f0cdf42 commit 04ff180

5 files changed

Lines changed: 159 additions & 31 deletions

File tree

po/POTFILES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ src/Dialogs/GlobalSearchDialog.vala
55
src/Dialogs/NewBranchDialog.vala
66
src/Dialogs/PreferencesDialog.vala
77
src/Dialogs/RestoreConfirmationDialog.vala
8+
src/Dialogs/CloseProjectsConfirmationDialog.vala
89
src/Dialogs/OverwriteUncommittedConfirmationDialog.vala
910
src/FolderManager/File.vala
1011
src/FolderManager/FileItem.vala
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2024 elementary, Inc. (https://elementary.io)
3+
*
4+
* This program is free software; you can redistribute it and/or
5+
* modify it under the terms of the GNU Lesser General Public
6+
* License version 3 as published by the Free Software Foundation.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+
* General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU Lesser General Public
14+
* License along with this program; if not, write to the
15+
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16+
* Boston, MA 02110-1301 USA
17+
*/
18+
19+
public class Scratch.Dialogs.CloseProjectsConfirmationDialog : Granite.MessageDialog {
20+
21+
public uint n_parents { get; construct; }
22+
public uint n_children { get; construct; }
23+
24+
public CloseProjectsConfirmationDialog (MainWindow parent, uint n_parents, uint n_children) {
25+
Object (
26+
buttons: Gtk.ButtonsType.NONE,
27+
transient_for: parent,
28+
n_parents: n_parents,
29+
n_children: n_children
30+
);
31+
}
32+
33+
construct {
34+
image_icon = new ThemedIcon ("dialog-warning");
35+
var button_label = "";
36+
// We can assume that either n_parents or n_children is zero (but not both).
37+
// We can assume n_parents is either zero or one
38+
if (n_children > 0) {
39+
primary_text = ngettext (
40+
_("This folder is the parent of an open project"),
41+
_("This folder is the parent of %u open projects").printf (n_children),
42+
(ulong) n_children
43+
);
44+
;
45+
secondary_text = ngettext (
46+
_("Opening this folder will close the child project"),
47+
_("Opening this folder will close all child projects"),
48+
(ulong) n_children
49+
);
50+
51+
button_label = ngettext (
52+
_("Close Child Project"),
53+
_("Close Child Projects"),
54+
(ulong) n_children
55+
);
56+
} else {
57+
primary_text = _("This folder is a child of an open project");
58+
secondary_text = _("Opening this folder will close the parent project");
59+
button_label = _("Close Parent Project");
60+
}
61+
62+
add_button (_("Don't Open"), Gtk.ResponseType.REJECT);
63+
64+
var ignore_button = (Gtk.Button) add_button (button_label, Gtk.ResponseType.ACCEPT);
65+
ignore_button.get_style_context ().add_class (Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION);
66+
}
67+
}

src/FolderManager/FileView.vala

Lines changed: 72 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*-
2-
* Copyright (c) 2017 - 2022 elementary LLC. (https://elementary.io),
2+
* Copyright (c) 2017 - 2024 elementary LLC. (https://elementary.io),
33
* 2013 Julien Spautz <spautz.julien@gmail.com>
44
*
55
* This program is free software: you can redistribute it and/or modify
@@ -126,9 +126,9 @@ public class Scratch.FolderManager.FileView : Code.Widgets.SourceList, Code.Pane
126126
write_settings ();
127127
}
128128

129-
public void restore_saved_state () {
129+
public async void restore_saved_state () {
130130
foreach (unowned string path in settings.get_strv ("opened-folders")) {
131-
add_folder (new File (path), false);
131+
yield add_folder (new File (path), false);
132132
}
133133
}
134134

@@ -505,7 +505,7 @@ public class Scratch.FolderManager.FileView : Code.Widgets.SourceList, Code.Pane
505505
}
506506
}
507507

508-
private void add_folder (File folder, bool expand) {
508+
private async void add_folder (File folder, bool expand) {
509509
if (is_open (folder)) {
510510
warning ("Folder '%s' is already open.", folder.path);
511511
return;
@@ -514,25 +514,80 @@ public class Scratch.FolderManager.FileView : Code.Widgets.SourceList, Code.Pane
514514
return;
515515
}
516516

517-
var folder_root = new ProjectFolderItem (folder, this); // Constructor adds project to GitManager
518-
this.root.add (folder_root);
519-
rename_items_with_same_name (folder_root);
517+
var add_file = folder.file;
518+
// Need to deal with case where folder is parent or child of an existing project
519+
var parents = new List<ProjectFolderItem> ();
520+
var children = new List<ProjectFolderItem> ();
520521

521-
folder_root.expanded = expand;
522-
folder_root.closed.connect (() => {
523-
toplevel_action_group.activate_action (MainWindow.ACTION_CLOSE_PROJECT_DOCS, new Variant.string (folder_root.path));
524-
root.remove (folder_root);
525-
foreach (var child in root.children) {
526-
var child_folder = (ProjectFolderItem) child;
527-
if (child_folder.name != child_folder.file.name) {
528-
rename_items_with_same_name (child_folder);
522+
foreach (var child in root.children) {
523+
var item = (ProjectFolderItem) child;
524+
if (add_file.get_relative_path (item.file.file) != null) {
525+
debug ("Trying to add parent of existing project");
526+
children.append (item);
527+
} else if (item.file.file.get_relative_path (add_file) != null) {
528+
debug ("Trying to add child of existing project");
529+
parents.append (item);
530+
}
531+
}
532+
533+
if (parents.length () > 0 || children.length () > 0) {
534+
assert (parents.length () <= 1);
535+
assert (parents.length () == 0 || children.length () == 0);
536+
var dialog = new Scratch.Dialogs.CloseProjectsConfirmationDialog (
537+
(MainWindow) get_toplevel (),
538+
parents.length (),
539+
children.length ()
540+
);
541+
542+
var close_projects = false;
543+
dialog.response.connect ((res) => {
544+
dialog.destroy ();
545+
if (res == Gtk.ResponseType.ACCEPT) {
546+
close_projects = true;
547+
}
548+
});
549+
550+
dialog.run ();
551+
552+
if (close_projects) {
553+
foreach (var item in parents) {
554+
item.closed ();
555+
}
556+
557+
foreach (var item in children) {
558+
item.closed ();
529559
}
560+
} else {
561+
return;
530562
}
531-
Scratch.Services.GitManager.get_instance ().remove_project (folder_root);
563+
}
564+
565+
// Process any closed signals emitted before proceeding
566+
Idle.add (() => {
567+
var folder_root = new ProjectFolderItem (folder, this); // Constructor adds project to GitManager
568+
this.root.add (folder_root);
569+
rename_items_with_same_name (folder_root);
570+
571+
folder_root.expanded = expand;
572+
folder_root.closed.connect (() => {
573+
toplevel_action_group.activate_action (MainWindow.ACTION_CLOSE_PROJECT_DOCS, new Variant.string (folder_root.path));
574+
root.remove (folder_root);
575+
foreach (var child in root.children) {
576+
var child_folder = (ProjectFolderItem) child;
577+
if (child_folder.name != child_folder.file.name) {
578+
rename_items_with_same_name (child_folder);
579+
}
580+
}
581+
Scratch.Services.GitManager.get_instance ().remove_project (folder_root);
582+
write_settings ();
583+
});
584+
532585
write_settings ();
586+
add_folder.callback ();
587+
return Source.REMOVE;
533588
});
534589

535-
write_settings ();
590+
yield;
536591
}
537592

538593
private bool is_open (File folder) {

src/MainWindow.vala

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -311,9 +311,6 @@ namespace Scratch {
311311
toolbar.templates_button.visible = (plugins.plugin_iface.template_manager.template_available);
312312
});
313313

314-
// Restore session
315-
restore_saved_state_extra ();
316-
317314
// Create folder for unsaved documents
318315
create_unsaved_documents_directory ();
319316

@@ -492,9 +489,6 @@ namespace Scratch {
492489
}
493490
});
494491

495-
496-
folder_manager_view.restore_saved_state ();
497-
498492
folder_manager_view.rename_request.connect ((file) => {
499493
var allow = true;
500494
foreach (var window in app.get_windows ()) {
@@ -576,13 +570,11 @@ namespace Scratch {
576570
});
577571

578572
hook_func ();
573+
574+
restore ();
579575
});
580576

581577
document_view.realize.connect (() => {
582-
if (restore_docs) {
583-
restore_opened_documents ();
584-
}
585-
586578
document_view.update_outline_visible ();
587579
update_find_actions ();
588580
});
@@ -697,6 +689,7 @@ namespace Scratch {
697689
}
698690
}
699691

692+
// DocumentView's number of docs updates asychronously so need Idle
700693
Idle.add (() => {
701694
document_view.request_placeholder_if_empty ();
702695
restore_override = null;
@@ -761,8 +754,7 @@ namespace Scratch {
761754
bool focus = true,
762755
int cursor_position = 0) {
763756

764-
FolderManager.ProjectFolderItem? project = folder_manager_view.get_project_for_file (doc.file);
765-
doc.source_view.project = project;
757+
doc.source_view.project = folder_manager_view.get_project_for_file (doc.file);
766758
document_view.open_document (doc, focus, cursor_position);
767759
}
768760

@@ -774,8 +766,7 @@ namespace Scratch {
774766
return;
775767
}
776768

777-
FolderManager.ProjectFolderItem? project = folder_manager_view.get_project_for_file (doc.file);
778-
doc.source_view.project = project;
769+
doc.source_view.project = folder_manager_view.get_project_for_file (doc.file);
779770
document_view.open_document (doc, focus, 0, range);
780771
}
781772

@@ -797,6 +788,19 @@ namespace Scratch {
797788
return true;
798789
}
799790

791+
private void restore () {
792+
// Plugin panes size
793+
hp1.set_position (Scratch.saved_state.get_int ("hp1-size"));
794+
vp.set_position (Scratch.saved_state.get_int ("vp-size"));
795+
// Ensure foldermanager finishes loading projects before start opening documents
796+
folder_manager_view.restore_saved_state.begin ((obj, res) => {
797+
folder_manager_view.restore_saved_state.end (res);
798+
if (restore_docs) {
799+
restore_opened_documents ();
800+
}
801+
});
802+
}
803+
800804
// Save session information different from window state
801805
private void restore_saved_state_extra () {
802806
// Plugin panes size

src/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ code_files = files(
2020
'Utils.vala',
2121
'Dialogs/PreferencesDialog.vala',
2222
'Dialogs/RestoreConfirmationDialog.vala',
23+
'Dialogs/CloseProjectsConfirmationDialog.vala',
2324
'Dialogs/OverwriteUncommittedConfirmationDialog.vala',
2425
'Dialogs/GlobalSearchDialog.vala',
2526
'Dialogs/NewBranchDialog.vala',

0 commit comments

Comments
 (0)