Skip to content

Commit b187769

Browse files
authored
Implement create new from template (#1616)
1 parent f84c72f commit b187769

4 files changed

Lines changed: 249 additions & 42 deletions

File tree

src/FolderManager/FileView.vala

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public class Scratch.FolderManager.FileView : Code.Widgets.SourceList, Code.Pane
3131
public const string ACTION_RENAME_FOLDER = "rename-folder";
3232
public const string ACTION_DELETE = "delete";
3333
public const string ACTION_NEW_FILE = "new-file";
34+
public const string ACTION_NEW_FROM_TEMPLATE = "new-from-template";
3435
public const string ACTION_NEW_FOLDER = "new-folder";
3536
public const string ACTION_CLOSE_FOLDER = "close-folder";
3637
public const string ACTION_CLOSE_OTHER_FOLDERS = "close-other-folders";
@@ -43,6 +44,7 @@ public class Scratch.FolderManager.FileView : Code.Widgets.SourceList, Code.Pane
4344
{ ACTION_RENAME_FOLDER, action_rename_folder, "s" },
4445
{ ACTION_DELETE, action_delete, "s" },
4546
{ ACTION_NEW_FILE, add_new_file, "s" },
47+
{ ACTION_NEW_FROM_TEMPLATE, add_new_from_template, "(ss)" },
4648
{ ACTION_NEW_FOLDER, add_new_folder, "s"},
4749
{ ACTION_CLOSE_FOLDER, action_close_folder, "s"},
4850
{ ACTION_CLOSE_OTHER_FOLDERS, action_close_other_folders, "s"}
@@ -363,6 +365,7 @@ public class Scratch.FolderManager.FileView : Code.Widgets.SourceList, Code.Pane
363365
plugins.hook_folder_item_change (source, dest, event);
364366
}
365367

368+
// This only works when the list is stable (nothing being added, expanded etc)
366369
private void rename_file (string path) {
367370
this.select_path (path);
368371
if (this.start_editing_item (selected)) {
@@ -458,9 +461,10 @@ public class Scratch.FolderManager.FileView : Code.Widgets.SourceList, Code.Pane
458461

459462
private void add_new_file (SimpleAction action, Variant? param) {
460463
// Using "path" of parent folder from params, call `on_add_new (false)` on `FolderItem`
461-
var path = param.get_string ();
464+
var path = param != null ? param.get_string () : null;
462465

463466
if (path == null || path == "") {
467+
critical ("No path");
464468
return;
465469
}
466470

@@ -472,6 +476,25 @@ public class Scratch.FolderManager.FileView : Code.Widgets.SourceList, Code.Pane
472476
folder.on_add_new (false);
473477
}
474478

479+
private void add_new_from_template (SimpleAction action, Variant? param) {
480+
// Using "path" of parent folder from params, call `on_add_new (false)` on `FolderItem`
481+
// var path = param.get_string ();
482+
string? parent_path = null, template_path = null;
483+
param.@get ("(ss)", out parent_path, out template_path);
484+
485+
//Do we need this check?
486+
if (parent_path == null || parent_path == "") {
487+
return;
488+
}
489+
490+
var folder = find_path (root, parent_path) as FolderItem;
491+
if (folder == null) {
492+
return;
493+
}
494+
495+
folder.on_add_template (template_path);
496+
}
497+
475498
private void action_launch_app_with_file_path (SimpleAction action, Variant? param) {
476499
var params = param.get_strv ();
477500
Utils.launch_app_with_file (params[1], params[0]);

src/FolderManager/FolderItem.vala

Lines changed: 194 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ namespace Scratch.FolderManager {
2424
* Monitored for changes inside the directory.
2525
*/
2626
public class FolderItem : Item {
27+
private const uint RENAME_AFTER_NEW_DELAY_MSEC = 500;
2728
private GLib.FileMonitor monitor;
2829
private bool children_loaded = false;
2930
private bool has_dummy;
@@ -35,6 +36,8 @@ namespace Scratch.FolderManager {
3536
}
3637
}
3738

39+
public signal void children_finished_loading ();
40+
3841
public FolderItem (File file, FileView view) {
3942
Object (file: file, view: view);
4043
}
@@ -107,6 +110,8 @@ namespace Scratch.FolderManager {
107110
if (root != null) {
108111
root.child_folder_loaded (this); //Updates child status emblens
109112
}
113+
114+
children_finished_loading ();
110115
}
111116

112117
private void on_toggled () {
@@ -237,12 +242,106 @@ namespace Scratch.FolderManager {
237242
new_menu.append_item (new_folder_item);
238243
new_menu.append_item (new_file_item);
239244

245+
//Append any templates/template folders.
246+
unowned string? template_path = GLib.Environment.get_user_special_dir (GLib.UserDirectory.TEMPLATES);
247+
if (template_path != null) {
248+
var template_submenu = new Menu ();
249+
uint template_count = load_templates_from_folder (GLib.File.new_for_path (template_path), template_submenu);
250+
if (template_count > 0) {
251+
if (template_count > MAX_TEMPLATES) {
252+
template_submenu.append_item (new MenuItem (_("…too many templates"), null));
253+
}
254+
255+
new_menu.append_submenu (_("Templates"), template_submenu);
256+
}
257+
}
258+
240259
var new_item = new GLib.MenuItem.submenu (_("New"), new_menu);
241260
new_item.set_submenu (new_menu);
242261

243262
return new_item;
244263
}
245264

265+
// Recursively load templates from folder and subfolders keeping count of total menuitems
266+
const int MAX_TEMPLATES = 2048;
267+
private uint load_templates_from_folder (GLib.File template_folder, Menu template_submenu) {
268+
GLib.List<GLib.File> template_list = null;
269+
GLib.List<GLib.File> folder_list = null;
270+
271+
GLib.FileEnumerator enumerator;
272+
var flags = GLib.FileQueryInfoFlags.NOFOLLOW_SYMLINKS;
273+
uint count = 0;
274+
try {
275+
enumerator = template_folder.enumerate_children ("standard::*", flags, null);
276+
GLib.File location;
277+
GLib.FileInfo? info = enumerator.next_file (null);
278+
279+
while (count < MAX_TEMPLATES && (info != null)) {
280+
if (!info.get_attribute_boolean (GLib.FileAttribute.STANDARD_IS_BACKUP)) {
281+
location = template_folder.get_child (info.get_name ());
282+
if (info.get_file_type () == GLib.FileType.DIRECTORY) {
283+
folder_list.prepend (location);
284+
} else {
285+
template_list.prepend (location);
286+
}
287+
288+
count ++;
289+
}
290+
291+
info = enumerator.next_file (null);
292+
}
293+
} catch (GLib.Error error) {
294+
return 0;
295+
}
296+
297+
folder_list.sort ((a, b) => {
298+
return strcmp (a.get_basename ().down (), b.get_basename ().down ());
299+
});
300+
301+
unowned List<GLib.File> fl = folder_list;
302+
while (fl != null && count < MAX_TEMPLATES) {
303+
var folder = fl.data;
304+
var folder_submenu = new Menu ();
305+
var folder_submenuitem = new MenuItem.submenu (
306+
folder.get_basename (),
307+
folder_submenu
308+
);
309+
310+
var sub_count = load_templates_from_folder (folder, folder_submenu);
311+
if (sub_count > 0) {
312+
template_submenu.append_item (folder_submenuitem);
313+
count += sub_count;
314+
} else {
315+
count -= 1; // Adjust count for ignored folder
316+
}
317+
318+
fl = fl.next;
319+
}
320+
321+
if (count > MAX_TEMPLATES) {
322+
warning ("too many templates! %u", count);
323+
return count;
324+
}
325+
326+
template_list.sort ((a, b) => {
327+
return strcmp (a.get_basename ().down (), b.get_basename ().down ());
328+
});
329+
330+
template_list.@foreach ((template) => {
331+
var template_menuitem = new MenuItem (
332+
template.get_basename (),
333+
GLib.Action.print_detailed_name (
334+
FileView.ACTION_PREFIX + FileView.ACTION_NEW_FROM_TEMPLATE,
335+
new Variant ("(ss)", this.path, template.get_path ())
336+
)
337+
);
338+
339+
template_submenu.append_item (template_menuitem);
340+
});
341+
342+
return count;
343+
}
344+
246345
public void remove_all_badges () {
247346
foreach (var child in children) {
248347
remove_badge (child);
@@ -263,7 +362,7 @@ namespace Scratch.FolderManager {
263362
has_dummy = false;
264363
}
265364

266-
((Code.Widgets.SourceList.ExpandableItem)this).add (item);
365+
base.add (item);
267366
}
268367

269368
public new void remove (Code.Widgets.SourceList.Item item) {
@@ -337,7 +436,6 @@ namespace Scratch.FolderManager {
337436
if (source.query_exists () == false) {
338437
return;
339438
}
340-
341439
var path_item = find_item_for_path (source.get_path ());
342440
if (path_item == null) {
343441
var file = new File (source.get_path ());
@@ -386,26 +484,111 @@ namespace Scratch.FolderManager {
386484
return null;
387485
}
388486

389-
public void on_add_new (bool is_folder) {
487+
public void on_add_template (string template_path) {
488+
// Expand folder before trying to copy template so that child appears for renaming
489+
if (!expanded) {
490+
expanded = true; // causes async loading of children
491+
ulong once = 0;
492+
once = children_finished_loading.connect (() => {
493+
this.disconnect (once);
494+
copy_template (template_path);
495+
});
496+
} else {
497+
copy_template (template_path);
498+
}
499+
}
500+
501+
private void copy_template (string template_path) {
390502
if (!file.is_executable) {
391503
// This is necessary to avoid infinite loop below
392504
warning ("Unable to open parent folder");
393505
return;
394506
}
395507

396-
unowned string name = is_folder ? _("untitled folder") : _("new file");
508+
var template_file = GLib.File.new_for_path (template_path);
509+
name = template_file.get_basename ();
397510
var new_file = file.file.get_child (name);
398511
var n = 1;
512+
while (new_file.query_exists ()) {
513+
new_file = file.file.get_child (("%s %d").printf (name, n));
514+
n++;
515+
}
516+
517+
name = new_file.get_basename ();
518+
519+
//Assume templates are small and can be copied without problems
520+
try {
521+
template_file.copy (
522+
new_file,
523+
TARGET_DEFAULT_MODIFIED_TIME | TARGET_DEFAULT_PERMS | NOFOLLOW_SYMLINKS,
524+
null, //No cancellable
525+
null //No progress
526+
);
527+
} catch (Error e) {
528+
warning ("Error copying template %s", e.message);
529+
return;
530+
}
399531

532+
// Wait for monitor to pickup file creation and add new item
533+
ulong once = 0;
534+
once = child_added.connect (() => {
535+
this.disconnect (once);
536+
var path = new_file.get_path ();
537+
// Still need to wait for sourcelist to become stable and editable
538+
//TODO Find a better way
539+
Timeout.add (RENAME_AFTER_NEW_DELAY_MSEC, () => {
540+
var rename_action = Utils.action_from_group (FileView.ACTION_RENAME_FILE, view.actions);
541+
if (rename_action != null && rename_action.enabled) {
542+
rename_action.activate (path);
543+
} else {
544+
critical ("Rename action not available");
545+
}
546+
547+
return Source.REMOVE;
548+
});
549+
});
550+
}
551+
552+
public void on_add_new (bool is_folder) {
553+
if (!file.is_executable) {
554+
// This is necessary to avoid infinite loop below
555+
warning ("Unable to open parent folder");
556+
return;
557+
}
558+
559+
560+
var name = is_folder ? _("untitled folder") : _("new file");
561+
var new_file = file.file.get_child (name);
562+
var n = 1;
400563
while (new_file.query_exists ()) {
401564
new_file = file.file.get_child (("%s %d").printf (name, n));
402565
n++;
403566
}
404-
expanded = true;
405-
var rename_item = new RenameItem (new_file.get_basename (), is_folder);
567+
568+
name = new_file.get_basename ();
569+
570+
// Expand folder before trying to rename
571+
if (!expanded) {
572+
ulong once = 0;
573+
once = children_finished_loading.connect (() => {
574+
this.disconnect (once);
575+
rename_new (name, is_folder);
576+
});
577+
578+
expanded = true; // causes async loading of children
579+
} else {
580+
rename_new (name, is_folder);
581+
}
582+
}
583+
584+
private void rename_new (string name, bool is_folder) requires (!view.editing) {
585+
var rename_item = new RenameItem (name, is_folder);
406586
add (rename_item);
407-
/* Start editing after finishing signal handler */
408-
GLib.Idle.add (() => {
587+
588+
// Wait until can start editing
589+
// For some reason using an Idle does not work properly here - the editable gets drawn in the wrong place
590+
//TODO Find a way to detect when the sourcelist is stable and can be edited
591+
Timeout.add (RENAME_AFTER_NEW_DELAY_MSEC, () => {
409592
if (view.start_editing_item (rename_item)) {
410593
ulong once = 0;
411594
once = rename_item.edited.connect (() => {
@@ -414,7 +597,7 @@ namespace Scratch.FolderManager {
414597
var new_name = rename_item.name;
415598
try {
416599
var gfile = file.file.get_child_for_display_name (new_name);
417-
if (is_folder) {
600+
if (rename_item.is_folder) {
418601
gfile.make_directory ();
419602
} else {
420603
gfile.create (FileCreateFlags.NONE);
@@ -431,45 +614,16 @@ namespace Scratch.FolderManager {
431614
return Source.CONTINUE;
432615
} else {
433616
remove (rename_item);
617+
return Source.REMOVE;
434618
}
435-
436-
return Source.REMOVE;
437619
});
438620
} else {
621+
critical ("Failed to rename new item");
439622
remove (rename_item);
440623
}
441624

442-
443625
return Source.REMOVE;
444626
});
445627
}
446628
}
447-
448-
internal class RenameItem : Code.Widgets.SourceList.Item {
449-
public bool is_folder { get; construct; }
450-
451-
public RenameItem (string name, bool is_folder) {
452-
Object (
453-
name: name,
454-
is_folder: is_folder
455-
);
456-
}
457-
458-
construct {
459-
editable = true;
460-
edited.connect (on_edited);
461-
462-
if (is_folder) {
463-
icon = GLib.ContentType.get_icon ("inode/directory");
464-
} else {
465-
icon = GLib.ContentType.get_icon ("text");
466-
}
467-
}
468-
469-
private void on_edited (string new_name) {
470-
if (new_name != "") {
471-
name = new_name;
472-
}
473-
}
474-
}
475629
}

0 commit comments

Comments
 (0)