@@ -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