diff --git a/src/FolderManager/FileView.vala b/src/FolderManager/FileView.vala index 2bb5035b3..6537647fd 100644 --- a/src/FolderManager/FileView.vala +++ b/src/FolderManager/FileView.vala @@ -31,6 +31,7 @@ public class Scratch.FolderManager.FileView : Code.Widgets.SourceList, Code.Pane public const string ACTION_RENAME_FOLDER = "rename-folder"; public const string ACTION_DELETE = "delete"; public const string ACTION_NEW_FILE = "new-file"; + public const string ACTION_NEW_FROM_TEMPLATE = "new-from-template"; public const string ACTION_NEW_FOLDER = "new-folder"; public const string ACTION_CHECKOUT_LOCAL_BRANCH = "checkout-local-branch"; public const string ACTION_CHECKOUT_REMOTE_BRANCH = "checkout-remote-branch"; @@ -46,6 +47,7 @@ public class Scratch.FolderManager.FileView : Code.Widgets.SourceList, Code.Pane { ACTION_RENAME_FOLDER, action_rename_folder, "s" }, { ACTION_DELETE, action_delete, "s" }, { ACTION_NEW_FILE, add_new_file, "s" }, + { ACTION_NEW_FROM_TEMPLATE, add_new_from_template, "(ss)" }, { ACTION_NEW_FOLDER, add_new_folder, "s"}, { ACTION_CLOSE_FOLDER, action_close_folder, "s"}, { ACTION_CLOSE_OTHER_FOLDERS, action_close_other_folders, "s"}, @@ -213,7 +215,6 @@ public class Scratch.FolderManager.FileView : Code.Widgets.SourceList, Code.Pane Code.Widgets.SourceList.ExpandableItem list, string path, bool expand = false) { - foreach (var item in list.children) { if (item is Item) { var code_item = (Item)item; @@ -312,6 +313,7 @@ public class Scratch.FolderManager.FileView : Code.Widgets.SourceList, Code.Pane plugins.hook_folder_item_change (source, dest, event); } + // This only works when the list is stable (nothing being added, expanded etc) private void rename_file (string path) { this.select_path (path); if (this.start_editing_item (selected)) { @@ -407,9 +409,10 @@ public class Scratch.FolderManager.FileView : Code.Widgets.SourceList, Code.Pane private void add_new_file (SimpleAction action, Variant? param) { // Using "path" of parent folder from params, call `on_add_new (false)` on `FolderItem` - var path = param.get_string (); + var path = param != null ? param.get_string () : null; if (path == null || path == "") { + critical ("No path"); return; } @@ -421,6 +424,25 @@ public class Scratch.FolderManager.FileView : Code.Widgets.SourceList, Code.Pane folder.on_add_new (false); } + private void add_new_from_template (SimpleAction action, Variant? param) { + // Using "path" of parent folder from params, call `on_add_new (false)` on `FolderItem` + // var path = param.get_string (); + string? parent_path = null, template_path = null; + param.@get ("(ss)", out parent_path, out template_path); + + //Do we need this check? + if (parent_path == null || parent_path == "") { + return; + } + + var folder = find_path (root, parent_path) as FolderItem; + if (folder == null) { + return; + } + + folder.on_add_template (template_path); + } + private void action_launch_app_with_file_path (SimpleAction action, Variant? param) { var params = param.get_strv (); Utils.launch_app_with_file (params[1], params[0]); diff --git a/src/FolderManager/FolderItem.vala b/src/FolderManager/FolderItem.vala index 1fb9c2069..63d6e4e52 100644 --- a/src/FolderManager/FolderItem.vala +++ b/src/FolderManager/FolderItem.vala @@ -24,6 +24,7 @@ namespace Scratch.FolderManager { * Monitored for changes inside the directory. */ public class FolderItem : Item { + private const uint RENAME_AFTER_NEW_DELAY_MSEC = 500; private GLib.FileMonitor monitor; private bool children_loaded = false; private bool has_dummy; @@ -35,6 +36,8 @@ namespace Scratch.FolderManager { } } + public signal void children_finished_loading (); + public FolderItem (File file, FileView view) { Object (file: file, view: view); } @@ -108,6 +111,8 @@ namespace Scratch.FolderManager { if (root != null) { root.child_folder_loaded (this); //Updates child status emblens } + + children_finished_loading (); } private void on_toggled () { @@ -240,12 +245,84 @@ namespace Scratch.FolderManager { new_menu.append_item (new_folder_item); new_menu.append_item (new_file_item); + //Append any templates/template folders. + unowned string? template_path = GLib.Environment.get_user_special_dir (GLib.UserDirectory.TEMPLATES); + if (template_path != null) { + load_templates_from_folder (GLib.File.new_for_path (template_path), new_menu); + } + var new_item = new GLib.MenuItem.submenu (_("New"), new_menu); new_item.set_submenu (new_menu); return new_item; } + //Adapted from Files app code + const int MAX_TEMPLATES = 2048; + private void load_templates_from_folder (GLib.File template_folder, Menu new_submenu, uint count = 0) { + GLib.List template_list = null; + GLib.List folder_list = null; + + GLib.FileEnumerator enumerator; + var flags = GLib.FileQueryInfoFlags.NOFOLLOW_SYMLINKS; + try { + enumerator = template_folder.enumerate_children ("standard::*", flags, null); + GLib.File location; + GLib.FileInfo? info = enumerator.next_file (null); + + while (count < MAX_TEMPLATES && (info != null)) { + if (!info.get_attribute_boolean (GLib.FileAttribute.STANDARD_IS_BACKUP)) { + location = template_folder.get_child (info.get_name ()); + if (info.get_file_type () == GLib.FileType.DIRECTORY) { + folder_list.prepend (location); + } else { + template_list.prepend (location); + count ++; + } + } + + info = enumerator.next_file (null); + } + } catch (GLib.Error error) { + return; + } + + if (folder_list.length () > 0) { + folder_list.sort ((a, b) => { + return strcmp (a.get_basename ().down (), b.get_basename ().down ()); + }); + + folder_list.@foreach ((folder) => { + var folder_submenu = new Menu (); + var folder_submenuitem = new MenuItem.submenu ( + folder.get_basename (), + folder_submenu + ); + + new_submenu.append_item (folder_submenuitem); + load_templates_from_folder (folder, folder_submenu, count); + }); + } + + if (template_list.length () > 0) { + template_list.sort ((a, b) => { + return strcmp (a.get_basename ().down (), b.get_basename ().down ()); + }); + + template_list.@foreach ((template) => { + var template_menuitem = new MenuItem ( + template.get_basename (), + GLib.Action.print_detailed_name ( + FileView.ACTION_PREFIX + FileView.ACTION_NEW_FROM_TEMPLATE, + new Variant ("(ss)", this.path, template.get_path ()) + ) + ); + + new_submenu.append_item (template_menuitem); + }); + } + } + public void remove_all_badges () { foreach (var child in children) { remove_badge (child); @@ -266,7 +343,7 @@ namespace Scratch.FolderManager { has_dummy = false; } - ((Code.Widgets.SourceList.ExpandableItem)this).add (item); + base.add (item); } public new void remove (Code.Widgets.SourceList.Item item) { @@ -340,7 +417,6 @@ namespace Scratch.FolderManager { if (source.query_exists () == false) { return; } - var path_item = find_item_for_path (source.get_path ()); if (path_item == null) { var file = new File (source.get_path ()); @@ -389,26 +465,111 @@ namespace Scratch.FolderManager { return null; } - public void on_add_new (bool is_folder) { + public void on_add_template (string template_path) { + // Expand folder before trying to copy template so that child appears for renaming + if (!expanded) { + expanded = true; // causes async loading of children + ulong once = 0; + once = children_finished_loading.connect (() => { + this.disconnect (once); + copy_template (template_path); + }); + } else { + copy_template (template_path); + } + } + + private void copy_template (string template_path) { if (!file.is_executable) { // This is necessary to avoid infinite loop below warning ("Unable to open parent folder"); return; } - unowned string name = is_folder ? _("untitled folder") : _("new file"); + var template_file = GLib.File.new_for_path (template_path); + name = template_file.get_basename (); var new_file = file.file.get_child (name); var n = 1; + while (new_file.query_exists ()) { + new_file = file.file.get_child (("%s %d").printf (name, n)); + n++; + } + + name = new_file.get_basename (); + + //Assume templates are small and can be copied without problems + try { + template_file.copy ( + new_file, + TARGET_DEFAULT_MODIFIED_TIME | TARGET_DEFAULT_PERMS | NOFOLLOW_SYMLINKS, + null, //No cancellable + null //No progress + ); + } catch (Error e) { + warning ("Error copying template %s", e.message); + return; + } + + // Wait for monitor to pickup file creation and add new item + ulong once = 0; + once = child_added.connect (() => { + this.disconnect (once); + var path = new_file.get_path (); + // Still need to wait for sourcelist to become stable and editable + //TODO Find a better way + Timeout.add (RENAME_AFTER_NEW_DELAY_MSEC, () => { + var rename_action = Utils.action_from_group (FileView.ACTION_RENAME_FILE, view.actions); + if (rename_action != null && rename_action.enabled) { + rename_action.activate (path); + } else { + critical ("Rename action not available"); + } + + return Source.REMOVE; + }); + }); + } + + public void on_add_new (bool is_folder) { + if (!file.is_executable) { + // This is necessary to avoid infinite loop below + warning ("Unable to open parent folder"); + return; + } + + var name = is_folder ? _("untitled folder") : _("new file"); + var new_file = file.file.get_child (name); + var n = 1; while (new_file.query_exists ()) { new_file = file.file.get_child (("%s %d").printf (name, n)); n++; } - expanded = true; - var rename_item = new RenameItem (new_file.get_basename (), is_folder); + + name = new_file.get_basename (); + + // Expand folder before trying to rename + if (!expanded) { + ulong once = 0; + once = children_finished_loading.connect (() => { + this.disconnect (once); + rename_new (name, is_folder); + }); + + expanded = true; // causes async loading of children + } else { + rename_new (name, is_folder); + } + } + + private void rename_new (string name, bool is_folder) requires (!view.editing) { + var rename_item = new RenameItem (name, is_folder); add (rename_item); - /* Start editing after finishing signal handler */ - GLib.Idle.add (() => { + + // Wait until can start editing + // For some reason using an Idle does not work properly here - the editable gets drawn in the wrong place + //TODO Find a way to detect when the sourcelist is stable and can be edited + Timeout.add (RENAME_AFTER_NEW_DELAY_MSEC, () => { if (view.start_editing_item (rename_item)) { ulong once = 0; once = rename_item.edited.connect (() => { @@ -417,7 +578,7 @@ namespace Scratch.FolderManager { var new_name = rename_item.name; try { var gfile = file.file.get_child_for_display_name (new_name); - if (is_folder) { + if (rename_item.is_folder) { gfile.make_directory (); } else { gfile.create (FileCreateFlags.NONE); @@ -434,45 +595,16 @@ namespace Scratch.FolderManager { return Source.CONTINUE; } else { remove (rename_item); + return Source.REMOVE; } - - return Source.REMOVE; }); } else { + critical ("Failed to rename new item"); remove (rename_item); } - return Source.REMOVE; }); } } - - internal class RenameItem : Code.Widgets.SourceList.Item { - public bool is_folder { get; construct; } - - public RenameItem (string name, bool is_folder) { - Object ( - name: name, - is_folder: is_folder - ); - } - - construct { - editable = true; - edited.connect (on_edited); - - if (is_folder) { - icon = GLib.ContentType.get_icon ("inode/directory"); - } else { - icon = GLib.ContentType.get_icon ("text"); - } - } - - private void on_edited (string new_name) { - if (new_name != "") { - name = new_name; - } - } - } } diff --git a/src/FolderManager/Item.vala b/src/FolderManager/Item.vala index 5a1ad924c..6ac39a7bf 100644 --- a/src/FolderManager/Item.vala +++ b/src/FolderManager/Item.vala @@ -96,5 +96,34 @@ namespace Scratch.FolderManager { return null; } } + + protected class RenameItem : Code.Widgets.SourceList.Item { + public bool is_folder { get; construct; } + + public RenameItem (string name, bool is_folder) { + Object ( + name: name, + is_folder: is_folder + ); + } + + construct { + editable = true; + selectable = true; + edited.connect (on_edited); + + if (is_folder) { + icon = GLib.ContentType.get_icon ("inode/directory"); + } else { + icon = GLib.ContentType.get_icon ("text"); + } + } + + private void on_edited (string new_name) { + if (new_name != "") { + name = new_name; + } + } + } } } diff --git a/src/Widgets/SourceList/SourceList.vala b/src/Widgets/SourceList/SourceList.vala index 431182e23..3dd33bd2d 100644 --- a/src/Widgets/SourceList/SourceList.vala +++ b/src/Widgets/SourceList/SourceList.vala @@ -2104,8 +2104,9 @@ public class SourceList : Gtk.ScrolledWindow { } public bool start_editing_item (Item item) requires (item.editable) requires (item.selectable) { - if (editing && item == edited) // If same item again, simply return. + if (editing && item == edited) {// If same item again, simply return. return false; + } var path = data_model.get_item_path (item); if (path != null) {