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 ();