diff --git a/data/io.elementary.code.gschema.xml b/data/io.elementary.code.gschema.xml
index 0cfce65a00..480e8c0fea 100644
--- a/data/io.elementary.code.gschema.xml
+++ b/data/io.elementary.code.gschema.xml
@@ -243,5 +243,10 @@
Opened folders.
Opened folders that should be restored in startup.
+
+ []
+ Recently closed folders.
+ Recently closed folders to be shown in project chooser button dropdown.
+
diff --git a/src/FolderManager/FileView.vala b/src/FolderManager/FileView.vala
index 2841e2715e..916bdfdd71 100644
--- a/src/FolderManager/FileView.vala
+++ b/src/FolderManager/FileView.vala
@@ -117,12 +117,7 @@ public class Scratch.FolderManager.FileView : Code.Widgets.SourceList, Code.Pane
foreach (var child in root.children) {
var project_folder_item = (ProjectFolderItem) child;
if (project_folder_item != folder_root) {
- toplevel_action_group.activate_action (
- MainWindow.ACTION_CLOSE_PROJECT_DOCS,
- new Variant.string (project_folder_item.path)
- );
- root.remove (project_folder_item);
- git_manager.remove_project (project_folder_item);
+ project_folder_item.closed (); // Signal not processed 'til loop complete
}
}
//Make remaining project the active one
@@ -663,13 +658,18 @@ public class Scratch.FolderManager.FileView : Code.Widgets.SourceList, Code.Pane
}
Scratch.Services.GitManager.get_instance ().remove_project (folder_root);
write_settings ();
+ update_recently_closed_projects (folder_root, true);
});
// We do not want to rewrite settings while restoring from settings
// This interferes with fuzzy-finder plugins_manager
// See https://github.com/elementary/code/issues/1533
if (!restoring) {
+ // Add opened folder to settings
write_settings ();
+ // Remove folder from recently closed if present
+ update_recently_closed_projects (folder_root, false);
+
}
add_folder.callback ();
@@ -709,4 +709,47 @@ public class Scratch.FolderManager.FileView : Code.Widgets.SourceList, Code.Pane
settings.set_strv ("opened-folders", to_save);
}
+
+ private void update_recently_closed_projects (
+ ProjectFolderItem closed_folder,
+ bool to_be_added
+ ) {
+ string[] recently_closed = settings.get_strv ("closed-folders");
+ var closed_path = closed_folder.path;
+ bool found = false;
+ int index = 0;
+ foreach (string path in recently_closed) {
+ if (path == closed_path) {
+ found = true;
+ break;
+ }
+
+ index++;
+ }
+
+ if ((to_be_added && found) || (!to_be_added && !found)) {
+ return;
+ }
+
+ if (to_be_added) {
+ if (recently_closed.length < 10) {
+ recently_closed += closed_path;
+ } else {
+ recently_closed.move (1, 0, 9);
+ recently_closed[9] = closed_path;
+ }
+ } else {
+ var size = recently_closed.length;
+ if (index < size - 1) {
+ recently_closed.move (index + 1, index, size - index);
+ } else {
+ recently_closed[index] = null; // Need to use null for resize to work
+ }
+
+
+ recently_closed.resize (size - 1);
+ }
+
+ settings.set_strv ("closed-folders", recently_closed);
+ }
}
diff --git a/src/Widgets/ChooseProjectButton.vala b/src/Widgets/ChooseProjectButton.vala
index 132e2c74d4..be3c0e6bbb 100644
--- a/src/Widgets/ChooseProjectButton.vala
+++ b/src/Widgets/ChooseProjectButton.vala
@@ -52,6 +52,14 @@ public class Code.ChooseProjectButton : Gtk.Bin {
return (((ProjectRow) row).project_name.down ().contains (project_filter.text.down ().strip ()));
});
+ project_listbox.set_header_func ((row, before) => {
+ if (before == null && ((ProjectRow) row).is_open) {
+ row.set_header (new Granite.HeaderLabel (_("Open Projects")) { halign = START });
+ } else if (!((ProjectRow) row).is_open && (before == null || ((ProjectRow) before).is_open)) {
+ row.set_header (new Granite.HeaderLabel (_("Recently Closed Projects")) { halign = START });
+ }
+ });
+
project_filter.changed.connect (() => {
project_listbox.invalidate_filter ();
});
@@ -98,30 +106,13 @@ public class Code.ChooseProjectButton : Gtk.Bin {
child = menu_button;
// Initialise with any pre-existing projects (needed for second and subsequent window)
- var git_manager = Scratch.Services.GitManager.get_instance ();
- var src = git_manager.project_liststore;
- for (int index = 0; index < src.n_items; index++) {
- var item = src.get_object (index);
- if (item is Scratch.FolderManager.ProjectFolderItem) {
- var row = create_project_row ((Scratch.FolderManager.ProjectFolderItem)item);
- project_listbox.insert (row, index);
- }
- }
+ insert_project_rows ();
- git_manager.project_liststore.items_changed.connect ((src, pos, n_removed, n_added) => {
- var rows = project_listbox.get_children ();
- for (int index = (int)pos; index < pos + n_removed; index++) {
- var row = rows.nth_data (index);
- row.destroy ();
- }
-
- for (int index = (int)pos; index < pos + n_added; index++) {
- var item = src.get_object (index);
- if (item is Scratch.FolderManager.ProjectFolderItem) {
- var row = create_project_row ((Scratch.FolderManager.ProjectFolderItem)item);
- project_listbox.insert (row, index);
- }
- }
+ // Update the list when projects added to or deleted from git manager
+ var git_manager = Scratch.Services.GitManager.get_instance ();
+ git_manager.project_liststore.items_changed.connect (() => {
+ // Inserting is delayed so the settings should have been updated when runs
+ insert_project_rows ();
});
menu_button.activate.connect (() => {
@@ -139,6 +130,51 @@ public class Code.ChooseProjectButton : Gtk.Bin {
update_button ();
}
+ // Throttle rebuilding the list as gitmanager emits multiple changed signals on startup
+ // and we need to wait for settings to update
+ uint insert_timeout_id = 0;
+ bool insert_wait = false;
+ private void insert_project_rows () {
+ if (insert_timeout_id == 0) {
+ insert_wait = false;
+ Timeout.add (20, () => {
+ if (insert_wait) {
+ insert_wait = false;
+ return Source.CONTINUE;
+ }
+
+ var rows = project_listbox.get_children ();
+ foreach (var row in rows) {
+ row.destroy ();
+ }
+
+ var git_manager = Scratch.Services.GitManager.get_instance ();
+ var src = git_manager.project_liststore;
+ int index = 0;
+ for (index = 0; index < src.n_items; index++) {
+ var item = src.get_object (index);
+ if (item is Scratch.FolderManager.ProjectFolderItem) {
+ var row = create_project_row ((Scratch.FolderManager.ProjectFolderItem)item, true);
+ project_listbox.insert (row, index);
+ }
+ }
+
+ var settings = new GLib.Settings ("io.elementary.code.folder-manager");
+ var recently_closed = settings.get_strv ("closed-folders");
+ var size = recently_closed.length;
+ // List in reverse order so most recently closed appears first
+ for (int idx = size - 1; idx >= 0; idx--) {
+ var row = new ProjectRow (recently_closed[idx], false);
+ project_listbox.insert (row, index++);
+ }
+
+ return Source.REMOVE;
+ });
+ } else {
+ insert_wait = true;
+ }
+ }
+
// Set appearance (only) of project chooser button and list according to active path
private void update_button () {
unowned var active_path = Scratch.Services.GitManager.get_instance ().active_project_path;
@@ -151,9 +187,12 @@ public class Code.ChooseProjectButton : Gtk.Bin {
}
}
- private Gtk.Widget create_project_row (Scratch.FolderManager.ProjectFolderItem project_folder) {
+ private Gtk.Widget create_project_row (
+ Scratch.FolderManager.ProjectFolderItem project_folder,
+ bool is_open = true
+ ) {
var project_path = project_folder.file.file.get_path ();
- var project_row = new ProjectRow (project_path);
+ var project_row = new ProjectRow (project_path, is_open);
// Project folder items cannot be renamed in UI, no need to handle
return project_row;
@@ -172,15 +211,18 @@ public class Code.ChooseProjectButton : Gtk.Bin {
}
public string project_path { get; construct; }
+ public bool is_open { get; construct; }
+
public string project_name {
get {
return check_button.label;
}
}
- public ProjectRow (string project_path) {
+ public ProjectRow (string project_path, bool is_open) {
Object (
- project_path: project_path
+ project_path: project_path,
+ is_open: is_open
);
}
@@ -192,8 +234,13 @@ public class Code.ChooseProjectButton : Gtk.Bin {
construct {
can_focus = true;
- action_name = Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_SET_ACTIVE_PROJECT;
+ action_name = Scratch.MainWindow.ACTION_PREFIX;
action_target = new Variant.string (project_path);
+ if (is_open) {
+ action_name += Scratch.MainWindow.ACTION_SET_ACTIVE_PROJECT;
+ } else {
+ action_name += Scratch.MainWindow.ACTION_OPEN_FOLDER;
+ }
check_button = new Gtk.CheckButton.with_label (Path.get_basename (project_path)) {
can_focus = false
@@ -206,7 +253,7 @@ public class Code.ChooseProjectButton : Gtk.Bin {
button = 0
};
button_controller.released.connect (() => {
- activate ();
+ activate (); // Trigger the linked action
});
show_all ();