Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions src/FolderManager/FileView.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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"},
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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;
}

Expand All @@ -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]);
Expand Down
212 changes: 172 additions & 40 deletions src/FolderManager/FolderItem.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -35,6 +36,8 @@ namespace Scratch.FolderManager {
}
}

public signal void children_finished_loading ();

public FolderItem (File file, FileView view) {
Object (file: file, view: view);
}
Expand Down Expand Up @@ -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 () {
Expand Down Expand Up @@ -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<GLib.File> template_list = null;
GLib.List<GLib.File> 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);
Expand All @@ -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) {
Expand Down Expand Up @@ -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 ());
Expand Down Expand Up @@ -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 (() => {
Expand All @@ -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);
Expand All @@ -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;
}
}
}
}
Loading