From 37eb78063c1c4a6a8066bfc4ef6196c25a263e93 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sat, 11 Jul 2026 15:09:22 +0100 Subject: [PATCH 01/21] Adapt symbol pane to TreeList --- src/SymbolPane/C/CtagsSymbol.vala | 4 +- src/SymbolPane/C/CtagsSymbolOutline.vala | 98 ++++++------- src/SymbolPane/SymbolOutline.vala | 35 ++--- src/SymbolPane/Vala/ValaComparisonHelper.vala | 6 +- src/SymbolPane/Vala/ValaSymbolItem.vala | 10 +- src/SymbolPane/Vala/ValaSymbolOutline.vala | 130 +++++++++--------- src/Widgets/GitGutterRenderer.vala | 31 ++--- src/Widgets/Sidebar.vala | 6 +- src/Widgets/TreeList/TreeList.vala | 25 +++- src/Widgets/TreeList/TreeListItem.vala | 8 +- 10 files changed, 193 insertions(+), 160 deletions(-) diff --git a/src/SymbolPane/C/CtagsSymbol.vala b/src/SymbolPane/C/CtagsSymbol.vala index 1f7d82e415..c26add1ff7 100644 --- a/src/SymbolPane/C/CtagsSymbol.vala +++ b/src/SymbolPane/C/CtagsSymbol.vala @@ -16,7 +16,7 @@ * */ -public class Scratch.Services.CtagsSymbol : Code.Widgets.SourceList.ExpandableItem, Scratch.Services.SymbolItem { +public class Scratch.Services.CtagsSymbol : Code.TreeListItem, Scratch.Services.SymbolItem { public Scratch.Services.Document doc { get; construct set; } public SymbolType symbol_type { get; set; default = SymbolType.OTHER; } public int line { get; construct set; } @@ -30,7 +30,7 @@ public class Scratch.Services.CtagsSymbol : Code.Widgets.SourceList.ExpandableIt Object ( doc: doc, - name: name, + text: name, line: line ); diff --git a/src/SymbolPane/C/CtagsSymbolOutline.vala b/src/SymbolPane/C/CtagsSymbolOutline.vala index e3c600ddad..89207b0ad3 100644 --- a/src/SymbolPane/C/CtagsSymbolOutline.vala +++ b/src/SymbolPane/C/CtagsSymbolOutline.vala @@ -40,13 +40,10 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin SymbolType.CONSTANT }; } + construct { - store.item_selected.connect ((selected) => { - if (selected == null) { - return; - } - doc.goto (((CtagsSymbol)selected).line); - store.selected = null; + item_activated.connect ((item) => { + doc.goto (((CtagsSymbol)item).line); }); } @@ -75,8 +72,8 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin } private async void parse_output (GLib.Subprocess subprocess) { - var parent_dependent = new Gee.LinkedList (); - var new_root = new Code.Widgets.SourceList.ExpandableItem (_("Symbols")); + var symboliter_list = new Gee.LinkedList (); + var new_root = new Code.TreeListItem () { text = _("Symbols") }; var datainput = new GLib.DataInputStream (subprocess.get_stdout_pipe ()); try { @@ -174,9 +171,9 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin icon, s_type ); - new_root.add (s); + new_root.add_child (s); } else { - parent_dependent.add (new CtagsSymbolIter ( + symboliter_list.add (new CtagsSymbolIter ( name, parent, line, @@ -191,9 +188,9 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin } var found_something = true; - while (found_something && parent_dependent.size > 0) { + while (found_something && symboliter_list.size > 0) { found_something = false; - var iter = parent_dependent.iterator (); + var iter = symboliter_list.iterator (); while (iter.has_next ()) { iter.next (); var i = iter.get (); @@ -201,7 +198,7 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin var parent = find_existing (i.parent, new_root); if (parent != null) { found_something = true; - parent.add (new CtagsSymbol ( + parent.add_child (new CtagsSymbol ( doc, i.name, i.line, @@ -214,7 +211,7 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin var parent_parts = i.parent.split (":", 2); parent = find_existing (parent_parts[1], new_root); if (parent != null) { - parent.name = i.name; + parent.text = i.name; switch (parent_parts[0]) { case "class": parent.icon = new ThemedIcon ("lang-class"); @@ -239,9 +236,9 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin new ThemedIcon ("lang-enum"), SymbolType.ENUM ); - new_root.add (e); + new_root.add_child (e); - e.add (new CtagsSymbol ( + e.add_child (new CtagsSymbol ( doc, i.name, i.line, @@ -255,8 +252,8 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin } // just add the rest - foreach (var symbol in parent_dependent) { - new_root.add (new CtagsSymbol ( + foreach (var symbol in symboliter_list) { + new_root.add_child (new CtagsSymbol ( doc, symbol.name, symbol.line, @@ -266,29 +263,28 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin } Idle.add (() => { - double adjustment_value = store.vadjustment.value; - store.root.clear (); - store.root.add (new_root); - store.root.expand_all (); - store.vadjustment.set_value (adjustment_value); + double adjustment_value = vadjustment.value; + remove_all (); + add_root_item (new_root); + // store.root.expand_all (); + vadjustment.set_value (adjustment_value); destroy_root (root); root = new_root; - add_tooltips (store.root); + add_tooltips (null); return false; }); } - protected override void add_tooltips (Code.Widgets.SourceList.ExpandableItem root) { - foreach (var parent in root.children) { - if (parent is Code.Widgets.SourceList.ExpandableItem) { - add_tooltip ((Code.Widgets.SourceList.ExpandableItem) parent); - } - } + protected override void add_tooltips (Code.TreeListItem? root) { + iterate_children (root, (child) => { + add_tooltip (child); + return Code.TreeList.ITERATE_CONTINUE; + }); } - private void add_tooltip (Code.Widgets.SourceList.ExpandableItem parent) { + private void add_tooltip (Code.TreeListItem parent) { if (parent is CtagsSymbol) { var item = ((CtagsSymbol)parent); var start = item.line; @@ -311,39 +307,45 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin add_tooltips (parent); } - private void destroy_root (Code.Widgets.SourceList.ExpandableItem to_destroy) { - var children = iterate_children (to_destroy); - to_destroy.clear (); + private void destroy_root (Code.TreeListItem to_destroy) { + var children = collect_children (to_destroy); + to_destroy.remove_all_children (); foreach (var item in children) { - item.clear (); + item.remove_all_children (); var parent = item.parent; if (parent != null) { - parent.remove (item); + parent.remove_child (item); } } } - private Gee.TreeSet iterate_children (Code.Widgets.SourceList.ExpandableItem parent) { + private Gee.TreeSet collect_children (Code.TreeListItem? parent) { var result = new Gee.TreeSet (); - foreach (var child in parent.children) { - result.add_all (iterate_children ((CtagsSymbol)child)); - } + iterate_children (parent, (child) => { + result.add_all (collect_children ((CtagsSymbol)child)); + return Code.TreeList.ITERATE_CONTINUE; + }); return result; } - CtagsSymbol? find_existing (string name, Code.Widgets.SourceList.ExpandableItem parent) { + CtagsSymbol? find_existing (string name, Code.TreeListItem parent) { CtagsSymbol match = null; - foreach (var child in parent.children) { - var child_symbol = child as CtagsSymbol; - if (child_symbol.name == name) { + // foreach (var child in parent.children) { + iterate_children (parent, (child) => { + var child_symbol = (CtagsSymbol) child; + if (child_symbol.text == name) { match = child_symbol; - break; + return Code.TreeList.ITERATE_STOP; } else { var res = find_existing (name, child_symbol); - if (res != null) - return res; + if (res != null) { + match = res; + return Code.TreeList.ITERATE_STOP; + } } - } + + return Code.TreeList.ITERATE_CONTINUE; + }); return match; } diff --git a/src/SymbolPane/SymbolOutline.vala b/src/SymbolPane/SymbolOutline.vala index ae9e7374ef..413ef11228 100644 --- a/src/SymbolPane/SymbolOutline.vala +++ b/src/SymbolPane/SymbolOutline.vala @@ -59,11 +59,11 @@ public enum Scratch.Services.SymbolType { } } -public interface Scratch.Services.SymbolItem : Code.Widgets.SourceList.ExpandableItem { +public interface Scratch.Services.SymbolItem : Code.TreeListItem { public abstract SymbolType symbol_type { get; set; default = SymbolType.OTHER;} } -public class Scratch.Services.SymbolOutline : Gtk.Box { +public class Scratch.Services.SymbolOutline : Code.TreeList { protected static SymbolType[] filters; //Initialized by derived classes const string ACTION_GROUP = "symbol"; const string ACTION_PREFIX = ACTION_GROUP + "."; @@ -76,8 +76,8 @@ public class Scratch.Services.SymbolOutline : Gtk.Box { protected Gee.HashMap checks; protected Gtk.SearchEntry search_entry; - protected Code.Widgets.SourceList store; - protected Code.Widgets.SourceList.ExpandableItem root; + // protected Code.TreeList store; + protected Code.TreeListItem root; protected Gtk.CssProvider source_list_style_provider; public Gtk.Widget get_widget () { return this; } public bool tool_box_sensitive { @@ -112,7 +112,7 @@ public class Scratch.Services.SymbolOutline : Gtk.Box { } public virtual void parse_symbols () {} - public virtual void add_tooltips (Code.Widgets.SourceList.ExpandableItem root) {} + public virtual void add_tooltips (Code.TreeListItem? root) {} private Gtk.MenuButton filter_button; private Gtk.Spinner spinner; @@ -123,9 +123,12 @@ public class Scratch.Services.SymbolOutline : Gtk.Box { insert_action_group (ACTION_GROUP, symbol_action_group); checks = new Gee.HashMap (); - store = new Code.Widgets.SourceList (); - root = new Code.Widgets.SourceList.ExpandableItem (_("Symbols")); - store.root.add (root); + // store = new Code.TreeList (); + root = new Code.TreeListItem () { + text = _("Symbols") + }; + + add_root_item (root); search_entry = new Gtk.SearchEntry () { placeholder_text = _("Find Symbol"), @@ -179,11 +182,11 @@ public class Scratch.Services.SymbolOutline : Gtk.Box { tool_box.append (search_entry); tool_box.append (stack); append (tool_box); - append (store); + // append (store); set_up_css (); realize.connect (() => { - store.set_filter_func (filter_func, false); + // store.set_filter_func (filter_func, false); search_entry.changed.connect (schedule_refilter); }); } @@ -229,13 +232,13 @@ public class Scratch.Services.SymbolOutline : Gtk.Box { // Do not exclude text search misses on Item with children as may // hide hits on its children - if (item is Code.Widgets.SourceList.ExpandableItem) { - var expandable = (Code.Widgets.SourceList.ExpandableItem)item; + if (item is Code.TreeListItem) { + var expandable = (Code.TreeListItem) item; if (expandable.n_children > 0) { return true; } - return ((SymbolItem)item).name.contains (search_entry.text); + return ((SymbolItem)item).text.contains (search_entry.text); } return true; @@ -256,10 +259,10 @@ public class Scratch.Services.SymbolOutline : Gtk.Box { delay_refilter = false; return Source.CONTINUE; } else { - refilter_timeout_id = 0; - store.refilter (); + // refilter_timeout_id = 0; + // store.refilter (); // Ensure new visible items shown when filter removed - store.root.expand_all (true, true); + expand_all (null); return Source.REMOVE; } }); diff --git a/src/SymbolPane/Vala/ValaComparisonHelper.vala b/src/SymbolPane/Vala/ValaComparisonHelper.vala index 4c38d32058..eba4b1f9f2 100644 --- a/src/SymbolPane/Vala/ValaComparisonHelper.vala +++ b/src/SymbolPane/Vala/ValaComparisonHelper.vala @@ -17,9 +17,9 @@ */ namespace Scratch.Services.ValaComparison { - int sort_function (Code.Widgets.SourceList.Item str1, Code.Widgets.SourceList.Item str2) { + int sort_function (Code.TreeListItem str1, Code.TreeListItem str2) { if (!(str1 is ValaSymbolItem && str2 is ValaSymbolItem)) - return str1.name.collate (str2.name); + return str1.text.collate (str2.text); var a = (ValaSymbolItem) str1; var b = (ValaSymbolItem) str2; var sa = a.symbol; @@ -52,7 +52,7 @@ namespace Scratch.Services.ValaComparison { return (compare_signal ((Vala.Signal) sa, sb)); else if (sa is Vala.Struct) return (compare_struct ((Vala.Struct) sa, sb)); - return str1.name.collate (str2.name); + return str1.text.collate (str2.text); } int compare_class (Vala.Class s, Vala.Symbol s2) { diff --git a/src/SymbolPane/Vala/ValaSymbolItem.vala b/src/SymbolPane/Vala/ValaSymbolItem.vala index ac3bb20f70..1bea31a5e5 100644 --- a/src/SymbolPane/Vala/ValaSymbolItem.vala +++ b/src/SymbolPane/Vala/ValaSymbolItem.vala @@ -16,7 +16,7 @@ * */ -public class Scratch.Services.ValaSymbolItem : Code.Widgets.SourceList.ExpandableItem, Code.Widgets.SourceListSortable, Scratch.Services.SymbolItem { +public class Scratch.Services.ValaSymbolItem : Code.TreeListItem, Scratch.Services.SymbolItem { public Vala.Symbol symbol { get; construct; } public SymbolType symbol_type { get; set; default = SymbolType.OTHER; } public ValaSymbolItem (Vala.Symbol symbol, string _tooltip) { @@ -30,12 +30,12 @@ public class Scratch.Services.ValaSymbolItem : Code.Widgets.SourceList.Expandabl if (symbol is Vala.CreationMethod) { var klass = ((Vala.CreationMethod)symbol).class_name; if (symbol.name == ".new") { - name = klass; + text = klass; } else { - name = "%s.%s".printf (klass, symbol.name); + text = "%s.%s".printf (klass, symbol.name); } } else { - name = symbol.name; + text = symbol.name; } if (symbol is Vala.Struct) { @@ -105,7 +105,7 @@ public class Scratch.Services.ValaSymbolItem : Code.Widgets.SourceList.Expandabl debug ("Destroy Vala symbol"); } - public int compare (Code.Widgets.SourceList.Item a, Code.Widgets.SourceList.Item b) { + public int compare (Code.TreeListItem a, Code.TreeListItem b) { return ValaComparison.sort_function (a, b); } diff --git a/src/SymbolPane/Vala/ValaSymbolOutline.vala b/src/SymbolPane/Vala/ValaSymbolOutline.vala index 4399302ccd..eacc2f7b8c 100644 --- a/src/SymbolPane/Vala/ValaSymbolOutline.vala +++ b/src/SymbolPane/Vala/ValaSymbolOutline.vala @@ -48,13 +48,8 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline parser = new Vala.Parser (); resolver = new Code.Plugins.ValaSymbolResolver (); - store.item_selected.connect ((selected) => { - if (selected == null) { - return; - } - - doc.goto (((ValaSymbolItem)selected).symbol.source_reference.begin.line); - store.selected = null; + item_activated.connect ((item) => { + doc.goto (((ValaSymbolItem)item).symbol.source_reference.begin.line); }); doc.doc_closed.connect (doc_closed); @@ -109,33 +104,32 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline if (!cancellable.is_cancelled () || took_too_long) { Idle.add (() => { - double adjustment_value = store.vadjustment.value; - var root_children = store.root.children; // Keep reference to children for later destruction - store.root.clear (); // This does not destroy children but disconnects signals - avoids terminal warnings - foreach (var child in root_children) { // Destroy items after clearing list to avoid memory leak - destroy_all_children ((Code.Widgets.SourceList.ExpandableItem)child); - } - - if (took_too_long) { - var warning_item = new Code.Widgets.SourceList.Item () { - icon = new ThemedIcon ("dialog-warning"), - markup = "%s".printf (_("Too Many Symbols")), - tooltip = _("%s contains too many Vala symbols.\nParsing and showing them took too long.").printf (doc.file.get_basename ()), - selectable = false - }; - - store.root.add (warning_item); - } else { - store.root.add (new_root); - } - - store.root.expand_all (); - add_tooltips (store.root); - store.vadjustment.set_value (adjustment_value); + double adjustment_value = vadjustment.value; + // var root_children = root.children; // Keep reference to children for later destruction + // root.clear (); // This does not destroy children but disconnects signals - avoids terminal warnings + // foreach (var child in root_children) { // Destroy items after clearing list to avoid memory leak + // destroy_all_children ((Code.TreeListItem)child); + // } + + remove_all (); + // if (took_too_long) { + // var warning_item = new Code.Widgets.SourceList.Item () { + // icon = new ThemedIcon ("dialog-warning"), + // markup = "%s".printf (_("Too Many Symbols")), + // tooltip = _("%s contains too many Vala symbols.\nParsing and showing them took too long.").printf (doc.file.get_basename ()), + // selectable = false + // }; + + // root.add (warning_item); + // } else { + add_root_item (new_root); + // } + + // root.expand_all (); + // add_tooltips (root); + // vadjustment.set_value (adjustment_value); return Source.REMOVE; }); - } else { - destroy_all_children (new_root); } after_parse (); @@ -143,15 +137,14 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline }); } - protected override void add_tooltips (Code.Widgets.SourceList.ExpandableItem root) { - foreach (var parent in root.children) { - if (parent is Code.Widgets.SourceList.ExpandableItem) { - add_tooltip ((Code.Widgets.SourceList.ExpandableItem) parent); - } - } + protected override void add_tooltips (Code.TreeListItem? root = null) { + iterate_children (root, (child) => { + add_tooltip ((Code.TreeListItem) parent); + return Code.TreeList.ITERATE_CONTINUE; + }); } - private void add_tooltip (Code.Widgets.SourceList.ExpandableItem parent) { + private void add_tooltip (Code.TreeListItem parent) { if (parent is ValaSymbolItem) { var item = ((ValaSymbolItem)parent); var symbol = item.symbol; @@ -169,28 +162,28 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline add_tooltips (parent); } - private void destroy_all_children (Code.Widgets.SourceList.ExpandableItem parent) { - foreach (var child in parent.children) { - remove (child, parent); - } - } + // private void destroy_all_children (Code.TreeListItem parent) { + // foreach (var child in parent.children) { + // remove (child, parent); + // } + // } - private new void remove (Code.Widgets.SourceList.Item item, Code.Widgets.SourceList.ExpandableItem parent) { - if (item is Code.Widgets.SourceList.ExpandableItem) { - destroy_all_children ((Code.Widgets.SourceList.ExpandableItem)item); - } + // private new void remove (Code.Widgets.SourceList.Item item, Code.TreeListItem parent) { + // if (item is Code.TreeListItem) { + // destroy_all_children ((Code.TreeListItem)item); + // } - parent.remove (item); - } + // parent.remove (item); + // } // Called from separate thread - private Code.Widgets.SourceList.ExpandableItem construct_tree (GLib.Cancellable cancellable) { + private Code.TreeListItem construct_tree (GLib.Cancellable cancellable) { var fields = resolver.get_properties_fields (); var symbols = resolver.get_symbols (); // Remove fake fields created by the vala parser. symbols.remove_all (fields); - var new_root = new Code.Widgets.SourceList.ExpandableItem (_("Symbols")); + var new_root = new Code.TreeListItem () { text = _("Symbols") }; new_root.tooltip = _("Vala symbols found in %s").printf (doc.file.get_basename ()); foreach (var symbol in symbols) { if (cancellable.is_cancelled ()) @@ -211,11 +204,11 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline private ValaSymbolItem construct_child ( Vala.Symbol symbol, - Code.Widgets.SourceList.ExpandableItem given_parent, + Code.TreeListItem given_parent, GLib.Cancellable cancellable ) { - Code.Widgets.SourceList.ExpandableItem parent; + Code.TreeListItem parent; if (symbol.scope.parent_scope.owner.name == null) parent = given_parent; else @@ -230,29 +223,36 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline symbol, "" ); - parent.add (tree_child); + + parent.add_child (tree_child); return tree_child; } - ValaSymbolItem? find_existing (Vala.Symbol symbol, Code.Widgets.SourceList.ExpandableItem parent, GLib.Cancellable cancellable) { + ValaSymbolItem? find_existing (Vala.Symbol symbol, Code.TreeListItem parent, GLib.Cancellable cancellable) { ValaSymbolItem match = null; - foreach (var _child in parent.children) { - if (cancellable.is_cancelled ()) - break; + iterate_children (null, (_child) => { + if (cancellable.is_cancelled ()) { + return Code.TreeList.ITERATE_STOP; + } var child = _child as ValaSymbolItem; - if (child == null) - continue; + if (child == null) { + return Code.TreeList.ITERATE_CONTINUE; + } if (child.symbol == symbol) { match = child; - break; + return Code.TreeList.ITERATE_STOP; } else { var res = find_existing (symbol, child, cancellable); - if (res != null) - return res; + if (res != null) { + match = res; + return Code.TreeList.ITERATE_STOP; + } } - } + + return Code.TreeList.ITERATE_CONTINUE; + }); return match; } diff --git a/src/Widgets/GitGutterRenderer.vala b/src/Widgets/GitGutterRenderer.vala index 3e3f12ea5a..58a9c77372 100644 --- a/src/Widgets/GitGutterRenderer.vala +++ b/src/Widgets/GitGutterRenderer.vala @@ -62,23 +62,22 @@ public class Scratch.Widgets.GitGutterRenderer : GtkSource.GutterRenderer { status_color_map.set (status, color); } - public override void snapshot_line (Gtk.Snapshot snapshot, GtkSource.GutterLines lines, uint line) { - warning ("snapshot_line"); - //Gutter and diff lines numbers start at one, source lines start at 0 - var gutter_line_no = (int) lines.get_first (); - Gdk.RGBA? color; - if (line_status_map.has_key (gutter_line_no)) { - color = status_color_map[line_status_map[gutter_line_no]]; - } else { - color = status_color_map [Services.VCStatus.NONE]; - } + // public override void snapshot_line (Gtk.Snapshot snapshot, GtkSource.GutterLines lines, uint line) { + // //Gutter and diff lines numbers start at one, source lines start at 0 + // var gutter_line_no = (int) lines.get_first (); + // Gdk.RGBA? color; + // if (line_status_map.has_key (gutter_line_no)) { + // color = status_color_map[line_status_map[gutter_line_no]]; + // } else { + // color = status_color_map [Services.VCStatus.NONE]; + // } - var rect = Graphene.Rect () { - origin = Graphene.Point () { x = 0.0f, y = 0.0f }, - size = Graphene.Size () { width = 6.0f , height = 12.0f } - }; - snapshot.append_color (color, rect); - } + // var rect = Graphene.Rect () { + // origin = Graphene.Point () { x = 0.0f, y = 0.0f }, + // size = Graphene.Size () { width = 6.0f , height = 12.0f } + // }; + // snapshot.append_color (color, rect); + // } // public override void draw (Cairo.Context cr, // Gdk.Rectangle bg, diff --git a/src/Widgets/Sidebar.vala b/src/Widgets/Sidebar.vala index 500cec6873..eac7b768a3 100644 --- a/src/Widgets/Sidebar.vala +++ b/src/Widgets/Sidebar.vala @@ -176,8 +176,8 @@ public class Code.Sidebar : Gtk.Box { } public void focus_sidebar () { - if (stack.visible_child is Code.Widgets.SourceList) { - ((Code.Widgets.SourceList) stack.visible_child).grab_focus (); - } + // if (stack.visible_child is Code.TreeList) { + // ((Code.Widgets.SourceList) stack.visible_child).grab_focus (); + // } } } diff --git a/src/Widgets/TreeList/TreeList.vala b/src/Widgets/TreeList/TreeList.vala index b5830733f4..8b6f43ae8d 100644 --- a/src/Widgets/TreeList/TreeList.vala +++ b/src/Widgets/TreeList/TreeList.vala @@ -4,12 +4,19 @@ */ public class Code.TreeList : Gtk.Box { + private Gtk.ScrolledWindow scrolled_window; private Gtk.ListView list_view; protected GLib.ListStore root_model; private Gtk.TreeListModel tree_model; protected Gtk.SelectionModel selection_model; - public bool activate_on_single_click { get; set; } + public Gtk.Adjustment vadjustment { + get { + return scrolled_window.vadjustment; + } + } + + public bool activate_on_single_click { get; set; default = true;} public signal void item_activated (TreeListItem item); // public TreeListItem? selected { get; set; } // Selection handled by SelectionModel @@ -169,6 +176,10 @@ public class Code.TreeList : Gtk.Box { } } + public void remove_all () { + root_model.remove_all (); + } + public void sort_root_children (CompareDataFunc sort_func) { root_model.sort (sort_func); } @@ -194,4 +205,16 @@ public class Code.TreeList : Gtk.Box { item = (TreeListItem?) (model.get_object (pos++)); } while (item != null && cb (item)); } + + public void expand_all (TreeListItem? start) { + iterate_children (start, expand_callback); + } + + private bool expand_callback (TreeListItem item) { + if (item.is_expandable) { + iterate_children (item, expand_callback); + } + + return TreeList.ITERATE_CONTINUE; + } } diff --git a/src/Widgets/TreeList/TreeListItem.vala b/src/Widgets/TreeList/TreeListItem.vala index 296ba47bc5..b86ca7b8bc 100644 --- a/src/Widgets/TreeList/TreeListItem.vala +++ b/src/Widgets/TreeList/TreeListItem.vala @@ -15,7 +15,7 @@ public class Code.TreeListItem : Object { public ListStore? child_model { get; set; default = null; } public TreeListItem? parent { get; set; default = null; } - public bool is_expandable { get; construct; } + public bool is_expandable { get; set construct; } public bool is_expanded { get; set; } // gets bound to the ListItem (temporarily) public Binding expanded_binding { get; set; } // Need to save bind so we can unbind public bool is_activatable { get; set; default = true; } @@ -23,6 +23,12 @@ public class Code.TreeListItem : Object { public bool is_editable { get; set; default = false; } public bool is_dummy { get; construct; } + public uint n_children { + get { + return child_model == null ? 0 : child_model.get_n_items (); + } + } + public signal void child_added (TreeListItem item); // To emulate source list public TreeListItem () { From fd5962f288c3f5d5572530c4ed19a5ea166c4dea Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 12 Jul 2026 15:56:39 +0100 Subject: [PATCH 02/21] Temp disable plugins and gutter renderers --- meson.build | 2 +- src/Widgets/SourceView.vala | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/meson.build b/meson.build index ae6bb0b225..d9b5443f47 100644 --- a/meson.build +++ b/meson.build @@ -110,7 +110,7 @@ endif subdir('data') subdir('src') if get_option('plugins') - subdir('plugins') + # subdir('plugins') endif subdir('po') diff --git a/src/Widgets/SourceView.vala b/src/Widgets/SourceView.vala index df88dd3f15..dcd7d970b2 100644 --- a/src/Widgets/SourceView.vala +++ b/src/Widgets/SourceView.vala @@ -109,12 +109,12 @@ namespace Scratch.Widgets { draw_spaces_tag.draw_spaces = true; source_buffer.tag_table.add (draw_spaces_tag); - // Make the gutter renderer and insert into the left side of the source view. - git_diff_gutter_renderer = new GitGutterRenderer (); - navmark_gutter_renderer = new NavMarkGutterRenderer (source_buffer); + // // Make the gutter renderer and insert into the left side of the source view. + // git_diff_gutter_renderer = new GitGutterRenderer (); + // navmark_gutter_renderer = new NavMarkGutterRenderer (source_buffer); - get_gutter (Gtk.TextWindowType.LEFT).insert (git_diff_gutter_renderer, 10); - get_gutter (Gtk.TextWindowType.LEFT).insert (navmark_gutter_renderer, -48); + // get_gutter (Gtk.TextWindowType.LEFT).insert (git_diff_gutter_renderer, 10); + // get_gutter (Gtk.TextWindowType.LEFT).insert (navmark_gutter_renderer, -48); smart_home_end = GtkSource.SmartHomeEndType.AFTER; @@ -383,7 +383,7 @@ namespace Scratch.Widgets { source_buffer.style_scheme = scheme ?? style_scheme_manager.get_scheme ("classic"); } - git_diff_gutter_renderer.set_style_scheme (source_buffer.style_scheme); + // git_diff_gutter_renderer.set_style_scheme (source_buffer.style_scheme); style_changed (source_buffer.style_scheme); } From 7809476bfb718a6dc930ed6fd3d1499841dd3b7c Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 12 Jul 2026 17:17:38 +0100 Subject: [PATCH 03/21] Make TreeList sealed class --- src/FolderManager/FileView.vala | 64 +++++++----- src/FolderManager/ProjectFolderItem.vala | 2 +- src/MainWindow.vala | 113 +++++++++++---------- src/Services/Document.vala | 2 +- src/SymbolPane/C/CtagsSymbolOutline.vala | 16 +-- src/SymbolPane/SymbolOutline.vala | 44 ++++---- src/SymbolPane/Vala/ValaSymbolOutline.vala | 12 +-- src/Widgets/SearchBar.vala | 24 ++--- src/Widgets/SourceView.vala | 36 +++---- src/Widgets/TreeList/TreeList.vala | 22 ++-- src/meson.build | 6 +- 11 files changed, 177 insertions(+), 164 deletions(-) diff --git a/src/FolderManager/FileView.vala b/src/FolderManager/FileView.vala index fb9a814d84..a537cdc388 100644 --- a/src/FolderManager/FileView.vala +++ b/src/FolderManager/FileView.vala @@ -21,7 +21,11 @@ /** * SourceList that displays folders and their contents. */ -public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { +// public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { +public class Scratch.FolderManager.FileView : Granite.Bin, Code.PaneSwitcher { + public signal void file_activate (File file); + public signal bool rename_request (File file); + public const string ACTION_GROUP = "file-view"; public const string ACTION_PREFIX = ACTION_GROUP + "."; public const string ACTION_LAUNCH_APP_WITH_FILE_PATH = "launch-app-with-file-path"; @@ -50,22 +54,23 @@ public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { { ACTION_CLOSE_OTHER_FOLDERS, action_close_other_folders, "s"} }; - private GLib.Settings settings; - private Scratch.Services.GitManager git_manager; - private Scratch.Services.PluginsManager plugins; - - public signal void file_activate (File file); - public signal bool rename_request (File file); public SimpleActionGroup actions { get; private set; } public string icon_name { get; set; } public string title { get; set; } + public bool is_empty { get { return tree_list.n_root_items () == 0; } } + + private Code.TreeList tree_list; + private GLib.Settings settings; + private Scratch.Services.GitManager git_manager; + private Scratch.Services.PluginsManager plugins; public FileView (Scratch.Services.PluginsManager plugins_manager) { plugins = plugins_manager; } construct { + tree_list = new Code.TreeList (); icon_name = "folder-symbolic"; title = _("Folders"); @@ -82,11 +87,13 @@ public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { }); // Convert ListView signal into file_activate - item_activated.connect ((item) => { + tree_list.item_activated.connect ((item) => { if (item is FileItem) { file_activate (((FileItem) item).file); } }); + + child = tree_list; } private void action_close_folder (SimpleAction action, GLib.Variant? parameter) { @@ -115,7 +122,7 @@ public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { } List to_remove = null; - iterate_children (null, (child) => { + tree_list.iterate_children (null, (child) => { var project_folder_item = (ProjectFolderItem) child; if (project_folder_item != folder_root) { activate_action ( @@ -130,7 +137,7 @@ public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { return Code.TreeList.ITERATE_CONTINUE; }); - remove_root_children (to_remove); + tree_list.remove_root_children (to_remove); //Make remaining project the active one set_project_active (path); } @@ -174,8 +181,8 @@ public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { public void open_folder (File folder) { if (is_open (folder)) { var existing = find_path (null, folder.path); - if (existing is Code.Widgets.SourceList.ExpandableItem) { - ((Code.Widgets.SourceList.ExpandableItem)existing).expanded = true; + if (existing is Code.TreeListItem) { + ((Code.TreeListItem)existing).is_expanded = true; } return; @@ -185,7 +192,7 @@ public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { } public void collapse_all () { - iterate_children (null, (child) => { + tree_list.iterate_children (null, (child) => { child.collapse_all (true, true); return Code.TreeList.ITERATE_CONTINUE; }); @@ -196,7 +203,7 @@ public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { return; } - sort_root_children ((a, b) => { + tree_list.sort_root_children ((a, b) => { return strcmp ( ((ProjectFolderItem)a).name.down (), ((ProjectFolderItem)b).name.down () @@ -213,14 +220,14 @@ public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { } public void unselect_all () { - selection_model.unselect_all (); + tree_list.unselect_all (); } public void collapse_other_projects () { unowned string path; path = git_manager.active_project_path; - iterate_children (null, (child) => { + tree_list.iterate_children (null, (child) => { var project_folder = ((ProjectFolderItem) child); if (project_folder.path != path) { project_folder.is_expanded = false; @@ -295,11 +302,10 @@ public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { GLib.File? target_file = null // Alternatively find this file ) { - var model = list != null ? list.child_model : root_model; var target = target_file ?? GLib.File.new_for_path (path); Code.TreeListItem? matched_item = null; - iterate_children (null, (item) => { + tree_list.iterate_children (null, (item) => { if ((item is Item) && item.path == path) { matched_item = item; return Code.TreeList.ITERATE_STOP; @@ -340,7 +346,7 @@ public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { public ProjectFolderItem? get_project_for_file (GLib.File file) { ProjectFolderItem? matched_project = null; - iterate_children (null, (item) => { + tree_list.iterate_children (null, (item) => { if (item is ProjectFolderItem) { var folder = (ProjectFolderItem) item; if (folder.file.file.equal (file) || folder.contains_file (file)) { @@ -376,7 +382,7 @@ public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { } public void clear_badges () { - iterate_children (null, (child) => { + tree_list.iterate_children (null, (child) => { if (child is ProjectFolderItem) { ((FolderItem)child).remove_all_badges (); } @@ -389,6 +395,10 @@ public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { plugins.hook_folder_item_change (source, dest, event); } + public void iterate_children (Code.TreeListItem? start, Code.TreeList.ListIteratorCallback cb) { + tree_list.iterate_children (start, cb); + } + // This only works when the list is stable (nothing being added, expanded etc) private void rename_file (string path) { // this.select_path (path); @@ -453,7 +463,7 @@ public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { private void rename_items_with_same_name (Item item) { // string item_name = item.file.name; - // iterate_children (null, (child) => { + // tree_list.iterate_children (null, (child) => { // string new_other_item_name, new_item_name; // var other_item = (ProjectFolderItem) child; @@ -635,7 +645,7 @@ public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { var parents = new List (); var children = new List (); - iterate_children (null, (child) => { + tree_list.iterate_children (null, (child) => { var item = (ProjectFolderItem) child; if (add_file.get_relative_path (item.file.file) != null) { debug ("Trying to add parent of existing project"); @@ -686,7 +696,7 @@ public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { // Process any closed signals emitted before proceeding Idle.add (() => { var folder_root = new ProjectFolderItem (folder, this); // Constructor adds project to GitManager - add_root_item (folder_root); + tree_list.add_root_item (folder_root); rename_items_with_same_name (folder_root); folder_root.is_expanded = expand; @@ -697,9 +707,9 @@ public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { folder_root.path ); - remove_root_item (folder_root); + tree_list.remove_root_item (folder_root); - iterate_children (null, (child) => { + tree_list.iterate_children (null, (child) => { var child_folder = (ProjectFolderItem) child; if (child_folder.name != child_folder.file.name) { rename_items_with_same_name (child_folder); @@ -730,7 +740,7 @@ public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { private bool is_open (File folder) { bool open = false; - iterate_children (null, (child) => { + tree_list.iterate_children (null, (child) => { if (folder.path == ((Item) child).path) { open = true; return Code.TreeList.ITERATE_STOP; @@ -744,7 +754,7 @@ public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { private void write_settings () { string[] to_save = {}; - iterate_children (null, (item) => { + tree_list.iterate_children (null, (item) => { var saved = false; var folder_path = ((Item) item).path; diff --git a/src/FolderManager/ProjectFolderItem.vala b/src/FolderManager/ProjectFolderItem.vala index 3f9437c06c..8126e2b2fb 100644 --- a/src/FolderManager/ProjectFolderItem.vala +++ b/src/FolderManager/ProjectFolderItem.vala @@ -196,7 +196,7 @@ namespace Scratch.FolderManager { FileView.ACTION_CLOSE_OTHER_FOLDERS, view.actions ); - close_other_folders_action.set_enabled (view.n_root_items () > 1); + close_other_folders_action.set_enabled (!view.is_empty); var close_actions_section = new GLib.Menu (); close_actions_section.append_item (close_folder_item); diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 36e36bc74c..e0f8e48ea6 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -463,36 +463,37 @@ namespace Scratch { sidebar = new Code.Sidebar (); - folder_manager_view = new FolderManager.FileView (plugins); + // folder_manager_view = new FolderManager.FileView (plugins); - sidebar.add_tab (folder_manager_view); + // sidebar.add_tab (folder_manager_view); + // sidebar.add_tab (new Granite.HeaderLabel ("Dummy Sidebar")); - folder_manager_view.file_activate.connect ((file) => { - // var file = new Scratch.FolderManager.File (a); - var doc = new Scratch.Services.Document (actions, file.file); + // folder_manager_view.file_activate.connect ((file) => { + // // var file = new Scratch.FolderManager.File (a); + // var doc = new Scratch.Services.Document (actions, file.file); - if (file.is_valid_textfile) { - open_document.begin (doc); - } else { - open_binary (file.file); - } - }); + // if (file.is_valid_textfile) { + // open_document.begin (doc); + // } else { + // open_binary (file.file); + // } + // }); - folder_manager_view.rename_request.connect ((file) => { - var allow = true; - foreach (var window in app.get_windows ()) { - var win = (MainWindow)window; - foreach (var doc in win.document_view.docs) { - if (doc.file.equal (file.file)) { - // Only allow sidebar to rename docs that are in sync with their file in - // all windows - allow = allow && !doc.locked && doc.saved; - } - } - } + // folder_manager_view.rename_request.connect ((file) => { + // var allow = true; + // foreach (var window in app.get_windows ()) { + // var win = (MainWindow)window; + // foreach (var doc in win.document_view.docs) { + // if (doc.file.equal (file.file)) { + // // Only allow sidebar to rename docs that are in sync with their file in + // // all windows + // allow = allow && !doc.locked && doc.saved; + // } + // } + // } - return allow; - }); + // return allow; + // }); terminal = new Code.Terminal () { visible = false @@ -594,7 +595,7 @@ namespace Scratch { toolbar.set_document_focus (doc); - folder_manager_view.select_path (doc.file.get_path ()); + // folder_manager_view.select_path (doc.file.get_path ()); // Must follow setting focus document for editorconfig plug plugins.hook_document (doc); @@ -668,7 +669,7 @@ namespace Scratch { document_view.update_outline_visible (); restore_override = null; if (focused_file != null) { - folder_manager_view.expand_to_path (focused_file.get_path ()); + // folder_manager_view.expand_to_path (focused_file.get_path ()); } } @@ -752,8 +753,8 @@ namespace Scratch { bool focus = true, int cursor_position = 0) { - FolderManager.ProjectFolderItem? project = folder_manager_view.get_project_for_file (doc.file); - doc.source_view.project = project; + // FolderManager.ProjectFolderItem? project = folder_manager_view.get_project_for_file (doc.file); + // doc.source_view.project = project; yield document_view.open_document (doc, focus, cursor_position); } @@ -765,7 +766,7 @@ namespace Scratch { return; } - doc.source_view.project = folder_manager_view.get_project_for_file (doc.file); + // doc.source_view.project = folder_manager_view.get_project_for_file (doc.file); yield document_view.open_document (doc, focus, 0, range); } @@ -792,12 +793,12 @@ namespace Scratch { hp1.set_position (Scratch.saved_state.get_int ("hp1-size")); vp.set_position (Scratch.saved_state.get_int ("vp-size")); // Ensure foldermanager finishes loading projects before start opening documents - folder_manager_view.restore_saved_state.begin ((obj, res) => { - folder_manager_view.restore_saved_state.end (res); + // folder_manager_view.restore_saved_state.begin ((obj, res) => { + // folder_manager_view.restore_saved_state.end (res); if (restore_docs) { restore_opened_documents.begin (); } - }); + // }); } private void create_unsaved_documents_directory () { @@ -1021,10 +1022,10 @@ namespace Scratch { var index = 0; var obj = files.get_item (index++); while (obj != null) { - var file = (GLib.File) obj; - var foldermanager_file = new FolderManager.File (file.get_path ()); - folder_manager_view.open_folder (foldermanager_file); - obj = files.get_item (index++); + // var file = (GLib.File) obj; + // var foldermanager_file = new FolderManager.File (file.get_path ()); + // folder_manager_view.open_folder (foldermanager_file); + // obj = files.get_item (index++); } } }); @@ -1037,7 +1038,7 @@ var index = 0; if (path == "") { choose_folder (); } else { - folder_manager_view.open_folder (new FolderManager.File (path)); + // folder_manager_view.open_folder (new FolderManager.File (path)); } } @@ -1117,7 +1118,7 @@ var index = 0; } private void action_collapse_all_folders () { - folder_manager_view.collapse_all (); + // folder_manager_view.collapse_all (); } private void action_save () { @@ -1319,7 +1320,7 @@ var index = 0; } if (search_path != "") { - folder_manager_view.search_global (search_path, search_bar.entry_text); + // folder_manager_view.search_global (search_path, search_bar.entry_text); } else { // Fallback to standard search warning ("Unable to perform global search - search document instead"); @@ -1450,21 +1451,21 @@ var index = 0; } private void action_set_active_project (SimpleAction action, Variant? param) { - var project_path = param.get_string (); - if (folder_manager_view.project_is_open (project_path)) { - git_manager.active_project_path = project_path; - folder_manager_view.collapse_other_projects (); - //The opened folders are not changed so no need to update "opened-folders" setting - } else { - warning ("Attempt to set folder path %s which is not opened as active project ignored", project_path); - //TODO Handle this by opening the folder - } - - var new_build_dir = Services.GitManager.get_instance ().get_default_build_dir (null); - terminal.change_location (new_build_dir); - if (terminal.visible) { - terminal.terminal.grab_focus (); - } + // var project_path = param.get_string (); + // if (folder_manager_view.project_is_open (project_path)) { + // git_manager.active_project_path = project_path; + // folder_manager_view.collapse_other_projects (); + // //The opened folders are not changed so no need to update "opened-folders" setting + // } else { + // warning ("Attempt to set folder path %s which is not opened as active project ignored", project_path); + // //TODO Handle this by opening the folder + // } + + // var new_build_dir = Services.GitManager.get_instance ().get_default_build_dir (null); + // terminal.change_location (new_build_dir); + // if (terminal.visible) { + // terminal.terminal.grab_focus (); + // } } private void action_toggle_outline (SimpleAction action) { @@ -1490,7 +1491,7 @@ var index = 0; } private void action_branch_actions (SimpleAction action, Variant? param) { - folder_manager_view.branch_actions (get_target_path_for_actions (param)); + // folder_manager_view.branch_actions (get_target_path_for_actions (param)); } private void action_move_tab_to_new_window () { diff --git a/src/Services/Document.vala b/src/Services/Document.vala index 67ae2c9a2d..6ad3cf5aa7 100644 --- a/src/Services/Document.vala +++ b/src/Services/Document.vala @@ -283,7 +283,7 @@ namespace Scratch.Services { var fc = new Gtk.EventControllerFocus (); source_view.add_controller (fc); fc.enter.connect (() => { - return_if_fail (!locked); + if (locked) { return; } // Not an error if (fc.is_focus) { if (!is_file_temporary) { check_undoable_actions (); diff --git a/src/SymbolPane/C/CtagsSymbolOutline.vala b/src/SymbolPane/C/CtagsSymbolOutline.vala index 89207b0ad3..3f0e383e01 100644 --- a/src/SymbolPane/C/CtagsSymbolOutline.vala +++ b/src/SymbolPane/C/CtagsSymbolOutline.vala @@ -42,7 +42,7 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin } construct { - item_activated.connect ((item) => { + tree_list.item_activated.connect ((item) => { doc.goto (((CtagsSymbol)item).line); }); } @@ -263,11 +263,11 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin } Idle.add (() => { - double adjustment_value = vadjustment.value; - remove_all (); - add_root_item (new_root); + double adjustment_value = tree_list.vadjustment.value; + tree_list.remove_all (); + tree_list.add_root_item (new_root); // store.root.expand_all (); - vadjustment.set_value (adjustment_value); + tree_list.vadjustment.set_value (adjustment_value); destroy_root (root); root = new_root; @@ -278,7 +278,7 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin } protected override void add_tooltips (Code.TreeListItem? root) { - iterate_children (root, (child) => { + tree_list.iterate_children (root, (child) => { add_tooltip (child); return Code.TreeList.ITERATE_CONTINUE; }); @@ -321,7 +321,7 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin private Gee.TreeSet collect_children (Code.TreeListItem? parent) { var result = new Gee.TreeSet (); - iterate_children (parent, (child) => { + tree_list.iterate_children (parent, (child) => { result.add_all (collect_children ((CtagsSymbol)child)); return Code.TreeList.ITERATE_CONTINUE; }); @@ -331,7 +331,7 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin CtagsSymbol? find_existing (string name, Code.TreeListItem parent) { CtagsSymbol match = null; // foreach (var child in parent.children) { - iterate_children (parent, (child) => { + tree_list.iterate_children (parent, (child) => { var child_symbol = (CtagsSymbol) child; if (child_symbol.text == name) { match = child_symbol; diff --git a/src/SymbolPane/SymbolOutline.vala b/src/SymbolPane/SymbolOutline.vala index 413ef11228..4382538b21 100644 --- a/src/SymbolPane/SymbolOutline.vala +++ b/src/SymbolPane/SymbolOutline.vala @@ -63,22 +63,8 @@ public interface Scratch.Services.SymbolItem : Code.TreeListItem { public abstract SymbolType symbol_type { get; set; default = SymbolType.OTHER;} } -public class Scratch.Services.SymbolOutline : Code.TreeList { - protected static SymbolType[] filters; //Initialized by derived classes - const string ACTION_GROUP = "symbol"; - const string ACTION_PREFIX = ACTION_GROUP + "."; - const string ACTION_SELECT = "action-select"; - const string ACTION_TOGGLE = "toggle-"; - const uint SPINNER_DELAY_MSEC = 300; - SimpleActionGroup symbol_action_group; - +public class Scratch.Services.SymbolOutline : Gtk.Box { public Scratch.Services.Document doc { get; construct; } - - protected Gee.HashMap checks; - protected Gtk.SearchEntry search_entry; - // protected Code.TreeList store; - protected Code.TreeListItem root; - protected Gtk.CssProvider source_list_style_provider; public Gtk.Widget get_widget () { return this; } public bool tool_box_sensitive { set { @@ -87,8 +73,16 @@ public class Scratch.Services.SymbolOutline : Code.TreeList { } } + public virtual void parse_symbols () {} + public virtual void add_tooltips (Code.TreeListItem? root) {} + + protected static SymbolType[] filters; //Initialized by derived classes + protected Gee.HashMap checks; + protected Gtk.SearchEntry search_entry; + protected Code.TreeList tree_list; + protected Code.TreeListItem root; + protected Gtk.CssProvider source_list_style_provider; protected bool took_too_long; - private uint show_spinner_timeout_id = 0; protected void before_parse () { tool_box_sensitive = true; took_too_long = false; @@ -111,14 +105,20 @@ public class Scratch.Services.SymbolOutline : Code.TreeList { tool_box_sensitive = !took_too_long; } - public virtual void parse_symbols () {} - public virtual void add_tooltips (Code.TreeListItem? root) {} - + private const string ACTION_GROUP = "symbol"; + private const string ACTION_PREFIX = ACTION_GROUP + "."; + private const string ACTION_SELECT = "action-select"; + private const string ACTION_TOGGLE = "toggle-"; + private const uint SPINNER_DELAY_MSEC = 300; private Gtk.MenuButton filter_button; private Gtk.Spinner spinner; private Gtk.Stack stack; + private uint show_spinner_timeout_id = 0; + + SimpleActionGroup symbol_action_group; construct { + tree_list = new Code.TreeList (); symbol_action_group = new SimpleActionGroup (); insert_action_group (ACTION_GROUP, symbol_action_group); @@ -128,7 +128,7 @@ public class Scratch.Services.SymbolOutline : Code.TreeList { text = _("Symbols") }; - add_root_item (root); + tree_list.add_root_item (root); search_entry = new Gtk.SearchEntry () { placeholder_text = _("Find Symbol"), @@ -182,7 +182,7 @@ public class Scratch.Services.SymbolOutline : Code.TreeList { tool_box.append (search_entry); tool_box.append (stack); append (tool_box); - // append (store); + append (tree_list); set_up_css (); realize.connect (() => { @@ -262,7 +262,7 @@ public class Scratch.Services.SymbolOutline : Code.TreeList { // refilter_timeout_id = 0; // store.refilter (); // Ensure new visible items shown when filter removed - expand_all (null); + tree_list.expand_all (null); return Source.REMOVE; } }); diff --git a/src/SymbolPane/Vala/ValaSymbolOutline.vala b/src/SymbolPane/Vala/ValaSymbolOutline.vala index eacc2f7b8c..76f680442a 100644 --- a/src/SymbolPane/Vala/ValaSymbolOutline.vala +++ b/src/SymbolPane/Vala/ValaSymbolOutline.vala @@ -48,7 +48,7 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline parser = new Vala.Parser (); resolver = new Code.Plugins.ValaSymbolResolver (); - item_activated.connect ((item) => { + tree_list.item_activated.connect ((item) => { doc.goto (((ValaSymbolItem)item).symbol.source_reference.begin.line); }); @@ -104,14 +104,14 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline if (!cancellable.is_cancelled () || took_too_long) { Idle.add (() => { - double adjustment_value = vadjustment.value; + double adjustment_value = tree_list.vadjustment.value; // var root_children = root.children; // Keep reference to children for later destruction // root.clear (); // This does not destroy children but disconnects signals - avoids terminal warnings // foreach (var child in root_children) { // Destroy items after clearing list to avoid memory leak // destroy_all_children ((Code.TreeListItem)child); // } - remove_all (); + tree_list.remove_all (); // if (took_too_long) { // var warning_item = new Code.Widgets.SourceList.Item () { // icon = new ThemedIcon ("dialog-warning"), @@ -122,7 +122,7 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline // root.add (warning_item); // } else { - add_root_item (new_root); + tree_list.add_root_item (new_root); // } // root.expand_all (); @@ -138,7 +138,7 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline } protected override void add_tooltips (Code.TreeListItem? root = null) { - iterate_children (root, (child) => { + tree_list.iterate_children (root, (child) => { add_tooltip ((Code.TreeListItem) parent); return Code.TreeList.ITERATE_CONTINUE; }); @@ -230,7 +230,7 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline ValaSymbolItem? find_existing (Vala.Symbol symbol, Code.TreeListItem parent, GLib.Cancellable cancellable) { ValaSymbolItem match = null; - iterate_children (null, (_child) => { + tree_list.iterate_children (null, (_child) => { if (cancellable.is_cancelled ()) { return Code.TreeList.ITERATE_STOP; } diff --git a/src/Widgets/SearchBar.vala b/src/Widgets/SearchBar.vala index 8f5ddd06ba..0ec50bf3a8 100644 --- a/src/Widgets/SearchBar.vala +++ b/src/Widgets/SearchBar.vala @@ -26,19 +26,14 @@ public enum Scratch.CaseSensitiveMode { } namespace Scratch.Widgets { - public class SearchBar : Gtk.Box { //TODO In Gtk4 use a BinLayout Widget + public class SearchBar : Granite.Bin { public weak MainWindow window { get; construct; } + private Gtk.Button tool_arrow_up; private Gtk.Button tool_arrow_down; - - /** - * Is the search cyclic? e.g., when you are at the bottom, if you press - * "Down", it will go at the start of the file to search for the content - * of the search entry. - **/ private Granite.SwitchModelButton cycle_search_button ; private Gtk.ComboBoxText case_sensitive_search_button; private Granite.SwitchModelButton regex_search_button; @@ -116,15 +111,13 @@ namespace Scratch.Widgets { } construct { - this.orientation = HORIZONTAL; - search_entry = new Gtk.SearchEntry () { hexpand = true, placeholder_text = _("Find"), }; entry_for_search = new Gtk.Entry (); - entry_for_search.set_parent (search_entry); + // entry_for_search.set_parent (search_entry); search_entry.set_key_capture_widget (entry_for_search); search_occurence_count_label = new Gtk.Label (_("No Results")); @@ -214,7 +207,8 @@ namespace Scratch.Widgets { margin_start = 6 }; search_box.add_css_class (Granite.STYLE_CLASS_LINKED); - search_box.append (search_entry); + // search_box.append (search_entry); + search_box.append (entry_for_search); search_box.append (tool_arrow_down); search_box.append (tool_arrow_up); search_box.append (search_menubutton); @@ -229,7 +223,8 @@ namespace Scratch.Widgets { placeholder_text = _("Replace With") }; entry_for_replace = new Gtk.Entry (); - entry_for_replace.set_parent (replace_search_entry); + replace_search_entry.set_key_capture_widget (entry_for_replace); + // entry_for_replace.set_parent (replace_search_entry); entry_for_replace.set_icon_from_icon_name (Gtk.EntryIconPosition.PRIMARY, "edit-symbolic"); replace_tool_button = new Gtk.Button.with_label (_("Replace")); @@ -245,7 +240,8 @@ namespace Scratch.Widgets { margin_start = 3 }; replace_grid.add_css_class (Granite.STYLE_CLASS_LINKED); - replace_grid.append (replace_search_entry); + replace_grid.append (entry_for_replace); + // replace_grid.append (replace_search_entry); replace_grid.append (replace_tool_button); replace_grid.append (replace_all_tool_button); @@ -290,7 +286,7 @@ namespace Scratch.Widgets { reveal_child = false }; - append (revealer); + this.child = revealer; update_search_widgets (); var key_controller = new Gtk.EventControllerKey () { diff --git a/src/Widgets/SourceView.vala b/src/Widgets/SourceView.vala index dcd7d970b2..c5a600dfe8 100644 --- a/src/Widgets/SourceView.vala +++ b/src/Widgets/SourceView.vala @@ -216,11 +216,11 @@ namespace Scratch.Widgets { buffer.notify_property ("has-selection"); buffer.notify_property ("language"); - navmark_gutter_renderer.notify["has-marks"].connect (() => { - next_mark_action.set_enabled (navmark_gutter_renderer.has_marks); - prev_mark_action.set_enabled (next_mark_action.get_enabled ()); - }); - navmark_gutter_renderer.notify_property ("has-marks"); + // navmark_gutter_renderer.notify["has-marks"].connect (() => { + // next_mark_action.set_enabled (navmark_gutter_renderer.has_marks); + // prev_mark_action.set_enabled (next_mark_action.get_enabled ()); + // }); + // navmark_gutter_renderer.notify_property ("has-marks"); // // For Gtk3 we need to convert extra_menu to additional Gtk.MenuItems. This is omitted in Gtk4 // populate_popup.connect_after ((menu) => { @@ -620,7 +620,7 @@ namespace Scratch.Widgets { Gtk.TextIter? cur_iter = null; buffer.get_iter_at_offset (out cur_iter, cursor_position); var cur_line = cur_iter.get_line (); - navmark_gutter_renderer.add_mark_at_line (cur_line); + // navmark_gutter_renderer.add_mark_at_line (cur_line); } public void goto_previous_mark () { @@ -635,12 +635,12 @@ namespace Scratch.Widgets { Gtk.TextIter? start, end; int line; if (get_current_line (out start, out end)) { - navmark_gutter_renderer.get_nearest_marked_line (start.get_line (), before, out line); - Gtk.TextIter? iter; - buffer.get_iter_at_line (out iter, line); - if (iter != null) { - cursor_position = iter.get_offset (); - } + // navmark_gutter_renderer.get_nearest_marked_line (start.get_line (), before, out line); + // Gtk.TextIter? iter; + // buffer.get_iter_at_line (out iter, line); + // if (iter != null) { + // cursor_position = iter.get_offset (); + // } } } @@ -709,7 +709,7 @@ namespace Scratch.Widgets { var name = mark.get_name (); if (name != null && name.has_prefix ("NavMark")) { warning ("NavMark deleted"); - navmark_gutter_renderer.remove_mark (mark); + // navmark_gutter_renderer.remove_mark (mark); } } @@ -736,14 +736,14 @@ namespace Scratch.Widgets { refresh_timeout_id = Timeout.add (250, () => { refresh_timeout_id = 0; if (project != null && project.is_git_repo) { - git_diff_gutter_renderer.line_status_map.clear (); + // git_diff_gutter_renderer.line_status_map.clear (); project.refresh_diff (ref git_diff_gutter_renderer.line_status_map, location.get_path ()); - git_diff_gutter_renderer.queue_draw (); + // git_diff_gutter_renderer.queue_draw (); } - if (navmark_gutter_renderer.has_marks) { - navmark_gutter_renderer.queue_draw (); - } + // if (navmark_gutter_renderer.has_marks) { + // navmark_gutter_renderer.queue_draw (); + // } return Source.REMOVE; }); diff --git a/src/Widgets/TreeList/TreeList.vala b/src/Widgets/TreeList/TreeList.vala index 8b6f43ae8d..d3ee438da7 100644 --- a/src/Widgets/TreeList/TreeList.vala +++ b/src/Widgets/TreeList/TreeList.vala @@ -3,12 +3,9 @@ * SPDX-FileCopyrightText: 20126 elementary, Inc. */ -public class Code.TreeList : Gtk.Box { - private Gtk.ScrolledWindow scrolled_window; - private Gtk.ListView list_view; - protected GLib.ListStore root_model; - private Gtk.TreeListModel tree_model; - protected Gtk.SelectionModel selection_model; +public sealed class Code.TreeList : Granite.Bin { + + public signal void item_activated (TreeListItem item); public Gtk.Adjustment vadjustment { get { @@ -18,9 +15,14 @@ public class Code.TreeList : Gtk.Box { public bool activate_on_single_click { get; set; default = true;} - public signal void item_activated (TreeListItem item); // public TreeListItem? selected { get; set; } // Selection handled by SelectionModel + private Gtk.ScrolledWindow scrolled_window; + private Gtk.ListView list_view; + private GLib.ListStore root_model; + private Gtk.TreeListModel tree_model; + private Gtk.SelectionModel selection_model; + construct { root_model = new GLib.ListStore (typeof (TreeListItem)); // Passthrough false (create Gtk.TreeListRows), autoexpand false @@ -73,7 +75,7 @@ public class Code.TreeList : Gtk.Box { tree_header_factory.unbind.connect (() => {}); tree_header_factory.teardown.connect (() => {}); - append (list_view); + child = list_view; } protected virtual void create_listitem_child (Gtk.ListItem item) { @@ -210,6 +212,10 @@ public class Code.TreeList : Gtk.Box { iterate_children (start, expand_callback); } + public void unselect_all () { + selection_model.unselect_all (); + } + private bool expand_callback (TreeListItem item) { if (item.is_expandable) { iterate_children (item, expand_callback); diff --git a/src/meson.build b/src/meson.build index 66d547b2ee..71c8838090 100644 --- a/src/meson.build +++ b/src/meson.build @@ -57,9 +57,9 @@ code_files = files( 'Widgets/PaneSwitcher.vala', 'Widgets/PopoverMenuItem.vala', 'Widgets/SearchBar.vala', - 'Widgets/SourceList/CellRendererBadge.vala', - 'Widgets/SourceList/CellRendererExpander.vala', - 'Widgets/SourceList/SourceList.vala', + # 'Widgets/SourceList/CellRendererBadge.vala', + # 'Widgets/SourceList/CellRendererExpander.vala', + # 'Widgets/SourceList/SourceList.vala', 'Widgets/SourceView.vala', 'Widgets/Terminal.vala', 'Widgets/WelcomeView.vala', From 80ce7ece80ca4ddd22a9e7945424f239767bc4f8 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 12 Jul 2026 17:29:40 +0100 Subject: [PATCH 04/21] Correct property name --- src/Widgets/TreeList/TreeList.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Widgets/TreeList/TreeList.vala b/src/Widgets/TreeList/TreeList.vala index d3ee438da7..37c3b44178 100644 --- a/src/Widgets/TreeList/TreeList.vala +++ b/src/Widgets/TreeList/TreeList.vala @@ -34,7 +34,7 @@ public sealed class Code.TreeList : Granite.Bin { header_factory = tree_header_factory }; - bind_property ("activate-on-single-click", list_view, "activate-on-single-click", BIDIRECTIONAL | SYNC_CREATE); + bind_property ("activate-on-single-click", list_view, "single-click-activate", BIDIRECTIONAL | SYNC_CREATE); list_view.activate.connect ((pos) => { item_activated ((TreeListItem) selection_model.get_item (pos)); }); From 2d1218b46eb81e7deadcb521fccaef7cea05b089 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 13 Jul 2026 14:53:37 +0100 Subject: [PATCH 05/21] Temp commit --- plugins/fuzzy-search/fuzzy-search.vala | 2 +- .../BranchActions/BranchActionDialog.vala | 4 +- src/FolderManager/File.vala | 337 +++-- src/FolderManager/FileItem.vala | 168 ++- src/FolderManager/FolderItem.vala | 1067 ++++++++-------- .../{FileView.vala => FolderTree.vala} | 71 +- src/FolderManager/Item.vala | 210 ++- src/FolderManager/ProjectFolderItem.vala | 1127 ++++++++--------- src/FolderManager/ProjectList.vala | 795 ++++++++++++ src/MainWindow.vala | 18 +- src/Services/GitManager.vala | 10 +- src/Services/PluginManager.vala | 11 +- src/SymbolPane/SymbolOutline.vala | 8 +- src/SymbolPane/Vala/ValaSymbolItem.vala | 2 + src/SymbolPane/Vala/ValaSymbolOutline.vala | 19 +- src/Utils.vala | 12 +- src/Widgets/ChooseProjectButton.vala | 10 +- src/Widgets/GitGutterRenderer.vala | 2 +- src/Widgets/SourceView.vala | 2 +- src/Widgets/TreeList/TreeList.vala | 29 +- src/Widgets/TreeList/TreeListItem.vala | 7 +- src/meson.build | 4 +- 22 files changed, 2352 insertions(+), 1563 deletions(-) rename src/FolderManager/{FileView.vala => FolderTree.vala} (92%) create mode 100644 src/FolderManager/ProjectList.vala diff --git a/plugins/fuzzy-search/fuzzy-search.vala b/plugins/fuzzy-search/fuzzy-search.vala index 169fd5a801..e4e5699a78 100644 --- a/plugins/fuzzy-search/fuzzy-search.vala +++ b/plugins/fuzzy-search/fuzzy-search.vala @@ -126,7 +126,7 @@ public class Scratch.Plugins.FuzzySearch: Peas.ExtensionBase, Scratch.Services.A var popover = new Scratch.FuzzySearchPopover (indexer, window); popover.open_file.connect ((filepath) => { - var file = new Scratch.FolderManager.File (filepath); + var file = new Code.File (filepath); var doc = new Scratch.Services.Document (window.actions, file.file); window.open_document.begin (doc); diff --git a/src/Dialogs/BranchActions/BranchActionDialog.vala b/src/Dialogs/BranchActions/BranchActionDialog.vala index 4c1a55e80d..d29d20fda1 100644 --- a/src/Dialogs/BranchActions/BranchActionDialog.vala +++ b/src/Dialogs/BranchActions/BranchActionDialog.vala @@ -43,11 +43,11 @@ public class Scratch.Dialogs.BranchActionDialog : Granite.MessageDialog { } public bool can_apply { get; set; default = false; } - public FolderManager.ProjectFolderItem project { get; construct; } + public Code.ProjectFolderItem project { get; construct; } private Gtk.Stack stack; - public BranchActionDialog (FolderManager.ProjectFolderItem project) { + public BranchActionDialog (Code.ProjectFolderItem project) { Object ( project: project, modal: true diff --git a/src/FolderManager/File.vala b/src/FolderManager/File.vala index 8bef07dee7..2b5e03289a 100644 --- a/src/FolderManager/File.vala +++ b/src/FolderManager/File.vala @@ -1,216 +1,211 @@ /*- - * Copyright (c) 2017-2025 elementary LLC. (https://elementary.io), - * 2013 Julien Spautz - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 - * as published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Authored by: Julien Spautz , Andrei-Costin Zisu - */ - -namespace Scratch.FolderManager { - - /** - * Class for easily dealing with files. - */ - public class File : GLib.Object { - public GLib.File file { get; private set; } - private GLib.FileInfo? info = null; // Non-null after loading - - public File (string path) { - Object (path: path); - } +* Copyright (c) 2017-2025 elementary LLC. (https://elementary.io), +* 2013 Julien Spautz +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License version 3 +* as published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranties of +* MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR +* PURPOSE. See the GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +* Authored by: Julien Spautz , Andrei-Costin Zisu +*/ + +public class Code.File : GLib.Object { + public GLib.File file { get; private set; } + private GLib.FileInfo? info = null; // Non-null after loading + + public File (string path) { + Object (path: path); + } - // returns the path the file - public string path { - owned get { - return file.get_path (); - } - set construct { - load_file_for_path (value); - } + // returns the path the file + public string path { + owned get { + return file.get_path (); } + set construct { + load_file_for_path (value); + } + } - // returns the basename of the file - private string _name; - public string name { - get { - if (_name != null) { - return _name; - } - - if (info == null) { - _name = file.get_basename (); - } else { - _name = info.get_display_name (); - } - + // returns the basename of the file + private string _name; + public string name { + get { + if (_name != null) { return _name; } - } - - // returns the icon of the file's content type - private GLib.Icon? _icon = null; - public GLib.Icon icon { - get { - if (_icon != null) { - return _icon; - } - if (info != null) { - _icon = GLib.ContentType.get_icon (info.get_content_type ()); - } else { - _icon = new ThemedIcon ("missing-image"); - } - - return _icon; + if (info == null) { + _name = file.get_basename (); + } else { + _name = info.get_display_name (); } - } - // checks if file exists - public bool exists { - get { return file.query_exists (); } + return _name; } + } - // Checks if we're dealing with a non-backup directory - // If parent is hidden then inherit validity from parent - private bool? _is_valid_directory = null; - public bool is_valid_directory { - get { - if (_is_valid_directory == null) { - _is_valid_directory = info != null && - !info.get_is_backup () && - info.get_file_type () == FileType.DIRECTORY; - } - - return _is_valid_directory; + // returns the icon of the file's content type + private GLib.Icon? _icon = null; + public GLib.Icon icon { + get { + if (_icon != null) { + return _icon; } - } - // checks if we're dealing with a textfile - private bool? _is_valid_textfile = null; - public bool is_valid_textfile { - get { - if (_is_valid_textfile == null) { - _is_valid_textfile = !path.has_suffix ("~") && Utils.check_if_valid_text_file (path, info); - } - - return _is_valid_textfile; + if (info != null) { + _icon = GLib.ContentType.get_icon (info.get_content_type ()); + } else { + _icon = new ThemedIcon ("missing-image"); } - } - // Files can be executed and folders can be cd'd into - public bool is_executable { - get { - try { - return get_boolean_file_attribute (GLib.FileAttribute.ACCESS_CAN_EXECUTE); - } catch (GLib.Error error) { - return false; - } - } + return _icon; } + } - // returns a list of all children of a directory - private bool children_valid = false; - private Gee.ArrayList _children = new Gee.ArrayList (); - public Gee.Collection children { - owned get { - if (children_valid) { - return _children; - } - - _children.clear (); - - try { - var enumerator = file.enumerate_children ( - GLib.FileAttribute.STANDARD_NAME, - FileQueryInfoFlags.NONE - ); - - var file_info = new FileInfo (); - while ((file_info = enumerator.next_file ()) != null) { - var child = file.get_child (file_info.get_name ()); - var child_file = new File (child.get_path ()); - if (child_file.is_valid_directory || child_file.is_valid_textfile) { - _children.add (child_file); - } - } - - children_valid = true; - } catch (GLib.Error error) { - warning (error.message); - } + // checks if file exists + public bool exists { + get { return file.query_exists (); } + } - return _children; + // Checks if we're dealing with a non-backup directory + // If parent is hidden then inherit validity from parent + private bool? _is_valid_directory = null; + public bool is_valid_directory { + get { + if (_is_valid_directory == null) { + _is_valid_directory = info != null && + !info.get_is_backup () && + info.get_file_type () == FileType.DIRECTORY; } + + return _is_valid_directory; } + } - private bool get_boolean_file_attribute (string attribute) throws GLib.Error { - var info = file.query_info (attribute, GLib.FileQueryInfoFlags.NONE); + // checks if we're dealing with a textfile + private bool? _is_valid_textfile = null; + public bool is_valid_textfile { + get { + if (_is_valid_textfile == null) { + _is_valid_textfile = !path.has_suffix ("~") && Utils.check_if_valid_text_file (path, info); + } - return info.get_attribute_boolean (attribute); + return _is_valid_textfile; } + } - private void load_file_for_path (string path) { - file = GLib.File.new_for_path (path); - - info = new FileInfo (); + // Files can be executed and folders can be cd'd into + public bool is_executable { + get { try { - var query_string = GLib.FileAttribute.STANDARD_CONTENT_TYPE + "," + - GLib.FileAttribute.STANDARD_IS_BACKUP + "," + - GLib.FileAttribute.STANDARD_IS_HIDDEN + "," + - GLib.FileAttribute.STANDARD_DISPLAY_NAME + "," + - GLib.FileAttribute.STANDARD_TYPE; - - info = file.query_info (query_string, FileQueryInfoFlags.NONE); + return get_boolean_file_attribute (GLib.FileAttribute.ACCESS_CAN_EXECUTE); } catch (GLib.Error error) { - info = null; - warning (error.message); + return false; } } + } + + // returns a list of all children of a directory + private bool children_valid = false; + private Gee.ArrayList _children = new Gee.ArrayList (); + public Gee.Collection children { + owned get { + if (children_valid) { + return _children; + } + + _children.clear (); - public void rename (string name) { try { - if (exists) { - file.set_display_name (name); + var enumerator = file.enumerate_children ( + GLib.FileAttribute.STANDARD_NAME, + FileQueryInfoFlags.NONE + ); + + var file_info = new FileInfo (); + while ((file_info = enumerator.next_file ()) != null) { + var child = file.get_child (file_info.get_name ()); + var child_file = new File (child.get_path ()); + if (child_file.is_valid_directory || child_file.is_valid_textfile) { + _children.add (child_file); + } } + + children_valid = true; } catch (GLib.Error error) { warning (error.message); } + + return _children; } + } - public void trash () { - try { - file.trash (); - } catch (GLib.Error error) { - warning (error.message); - } + private bool get_boolean_file_attribute (string attribute) throws GLib.Error { + var info = file.query_info (attribute, GLib.FileQueryInfoFlags.NONE); + + return info.get_attribute_boolean (attribute); + } + + private void load_file_for_path (string path) { + file = GLib.File.new_for_path (path); + + info = new FileInfo (); + try { + var query_string = GLib.FileAttribute.STANDARD_CONTENT_TYPE + "," + + GLib.FileAttribute.STANDARD_IS_BACKUP + "," + + GLib.FileAttribute.STANDARD_IS_HIDDEN + "," + + GLib.FileAttribute.STANDARD_DISPLAY_NAME + "," + + GLib.FileAttribute.STANDARD_TYPE; + + info = file.query_info (query_string, FileQueryInfoFlags.NONE); + } catch (GLib.Error error) { + info = null; + warning (error.message); } + } - public static int compare (File a, File b) { - if (a.is_valid_directory && b.is_valid_textfile) { - return -1; - } - if (a.is_valid_textfile && b.is_valid_directory) { - return 1; + public void rename (string name) { + try { + if (exists) { + file.set_display_name (name); } + } catch (GLib.Error error) { + warning (error.message); + } + } - return strcmp (a.path.collate_key_for_filename (), - b.path.collate_key_for_filename ()); + public void trash () { + try { + file.trash (); + } catch (GLib.Error error) { + warning (error.message); } + } - public void invalidate_cache () { - children_valid = false; + public static int compare (File a, File b) { + if (a.is_valid_directory && b.is_valid_textfile) { + return -1; + } + if (a.is_valid_textfile && b.is_valid_directory) { + return 1; } + + return strcmp (a.path.collate_key_for_filename (), + b.path.collate_key_for_filename ()); + } + + public void invalidate_cache () { + children_valid = false; } } + diff --git a/src/FolderManager/FileItem.vala b/src/FolderManager/FileItem.vala index d75ff5841b..8575e18bc9 100644 --- a/src/FolderManager/FileItem.vala +++ b/src/FolderManager/FileItem.vala @@ -18,101 +18,97 @@ * Authored by: Julien Spautz , Andrei-Costin Zisu */ -namespace Scratch.FolderManager { - /** - * Normal item in the source list, represents a textfile. - */ - public class FileItem : Item { - public FileItem (File file, FileView view) { - Object (file: file, view: view); - } - - // The ListView has an activate signal - // public override void activated () { - // view.activate (file.path); - // } - - public override GLib.Menu? get_context_menu () { - GLib.FileInfo info = null; - - try { - info = file.file.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, 0); - } catch (Error e) { - warning (e.message); - } - - var file_type = info.get_attribute_string (GLib.FileAttribute.STANDARD_CONTENT_TYPE); - var contractor_items = Utils.create_contract_items_for_file (file.file); - var external_actions_section = new GLib.Menu (); - external_actions_section.append_item (create_submenu_for_open_in (file_type)); - if (contractor_items.get_n_items () > 0) { - external_actions_section.append_submenu ( - _("Other Actions"), - contractor_items - ); - } - - var rename_item = new GLib.MenuItem ( - _("Rename"), - GLib.Action.print_detailed_name ( - FileView.ACTION_PREFIX + FileView.ACTION_RENAME_FILE, - new Variant.string (file.path) - ) - ); - var rename_action = Utils.action_from_group (FileView.ACTION_RENAME_FILE, view.actions); - rename_action.set_enabled (view.rename_request (file)); - - var delete_item = new GLib.MenuItem ( - _("Move to Trash"), - GLib.Action.print_detailed_name ( - FileView.ACTION_PREFIX + FileView.ACTION_DELETE, - new Variant.string (file.path) - ) - ); +public class Code.FileItem : FolderManagerItem { + public FileItem (File file, FolderTree view) { + Object (file: file, view: view); + } - var direct_actions_section = new GLib.Menu (); - direct_actions_section.append_item (rename_item); - direct_actions_section.append_item (delete_item); + // The ListView has an activate signal + // public override void activated () { + // view.activate (file.path); + // } - var menu_model = new GLib.Menu (); - menu_model.append_section (null, external_actions_section); - menu_model.append_section (null, direct_actions_section); + public override GLib.Menu? get_context_menu () { + GLib.FileInfo info = null; - return menu_model; + try { + info = file.file.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, 0); + } catch (Error e) { + warning (e.message); } - private GLib.MenuItem create_submenu_for_open_in (string? file_type) { - var new_window_menu_item = new GLib.MenuItem ( - _("New Window"), - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_OPEN_IN_NEW_WINDOW, - file.path - ) + var file_type = info.get_attribute_string (GLib.FileAttribute.STANDARD_CONTENT_TYPE); + var contractor_items = Utils.create_contract_items_for_file (file.file); + var external_actions_section = new GLib.Menu (); + external_actions_section.append_item (create_submenu_for_open_in (file_type)); + if (contractor_items.get_n_items () > 0) { + external_actions_section.append_submenu ( + _("Other Actions"), + contractor_items ); + } - var top_section = new GLib.Menu (); - if (file.is_valid_textfile) { - top_section.append_item (new_window_menu_item); - } - - var other_menu_item = new GLib.MenuItem ( - _("Other Application…"), - GLib.Action.print_detailed_name ( - FileView.ACTION_PREFIX + FileView.ACTION_SHOW_APP_CHOOSER, - file.path - ) - ); + var rename_item = new GLib.MenuItem ( + _("Rename"), + GLib.Action.print_detailed_name ( + FolderTree.ACTION_PREFIX + FolderTree.ACTION_RENAME_FILE, + new Variant.string (file.path) + ) + ); + var rename_action = Utils.action_from_group (FolderTree.ACTION_RENAME_FILE, view.actions); + rename_action.set_enabled (view.rename_request (file)); + + var delete_item = new GLib.MenuItem ( + _("Move to Trash"), + GLib.Action.print_detailed_name ( + FolderTree.ACTION_PREFIX + FolderTree.ACTION_DELETE, + new Variant.string (file.path) + ) + ); + + var direct_actions_section = new GLib.Menu (); + direct_actions_section.append_item (rename_item); + direct_actions_section.append_item (delete_item); + + var menu_model = new GLib.Menu (); + menu_model.append_section (null, external_actions_section); + menu_model.append_section (null, direct_actions_section); + + return menu_model; + } - var extra_section = new GLib.Menu (); - extra_section.append_item (other_menu_item); + private GLib.MenuItem create_submenu_for_open_in (string? file_type) { + var new_window_menu_item = new GLib.MenuItem ( + _("New Window"), + GLib.Action.print_detailed_name ( + MainWindow.ACTION_PREFIX + MainWindow.ACTION_OPEN_IN_NEW_WINDOW, + file.path + ) + ); + + var top_section = new GLib.Menu (); + if (file.is_valid_textfile) { + top_section.append_item (new_window_menu_item); + } - var open_in_menu = new GLib.Menu (); - open_in_menu.append_section (null, top_section); - open_in_menu.append_section (null, Utils.create_executable_app_items_for_file (file.file, file_type)); - open_in_menu.append_section (null, extra_section); + var other_menu_item = new GLib.MenuItem ( + _("Other Application…"), + GLib.Action.print_detailed_name ( + FolderTree.ACTION_PREFIX + FolderTree.ACTION_SHOW_APP_CHOOSER, + file.path + ) + ); - var open_in_menu_item = new GLib.MenuItem.submenu (_("Open In"), open_in_menu); - return open_in_menu_item; - } + var extra_section = new GLib.Menu (); + extra_section.append_item (other_menu_item); + + var open_in_menu = new GLib.Menu (); + open_in_menu.append_section (null, top_section); + open_in_menu.append_section (null, Utils.create_executable_app_items_for_file (file.file, file_type)); + open_in_menu.append_section (null, extra_section); + + var open_in_menu_item = new GLib.MenuItem.submenu (_("Open In"), open_in_menu); + return open_in_menu_item; } } + diff --git a/src/FolderManager/FolderItem.vala b/src/FolderManager/FolderItem.vala index bcd0e81fd2..d83426be63 100644 --- a/src/FolderManager/FolderItem.vala +++ b/src/FolderManager/FolderItem.vala @@ -18,644 +18,639 @@ * Authored by: Julien Spautz , Andrei-Costin Zisu */ -namespace Scratch.FolderManager { - /** - * Expandable item in the source list, represents a folder. - * 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; - private Code.TreeListItem dummy; /* Blank item for expanded empty folders */ - - public bool loading_required { - get { - return !children_loaded && file.children.size > 0; - } +public class Code.FolderItem : FolderManagerItem { + private const uint RENAME_AFTER_NEW_DELAY_MSEC = 500; + private GLib.FileMonitor monitor; + private bool children_loaded = false; + private bool has_dummy; + private Code.TreeListItem dummy; /* Blank item for expanded empty folders */ + + public bool loading_required { + get { + return !children_loaded && file.children.size > 0; } + } - public signal void children_finished_loading (); + public signal void children_finished_loading (); - public FolderItem (File file, FileView view) { - Object ( - file: file, - view: view - ); - } + public FolderItem (File file, FolderTree view) { + Object ( + file: file, + view: view + ); + } - ~FolderItem () { - monitor.cancel (); - } + ~FolderItem () { + monitor.cancel (); + } - construct { - is_selectable = false; - is_expandable = true; + construct { + is_selectable = false; + is_expandable = true; - dummy = new Code.TreeListItem.dummy (); - // Must add dummy on unexpanded folders else expander will not show - add_child (dummy); - has_dummy = true; + dummy = new Code.TreeListItem.dummy (); + // Must add dummy on unexpanded folders else expander will not show + add_child (dummy); + has_dummy = true; - // Signal when expanded - notify["is-expanded"].connect (on_toggled); + // Signal when expanded + notify["is-expanded"].connect (on_toggled); - try { - monitor = file.file.monitor_directory (GLib.FileMonitorFlags.NONE); - monitor.changed.connect (on_changed); - } catch (GLib.Error e) { - warning (e.message); - } + try { + monitor = file.file.monitor_directory (GLib.FileMonitorFlags.NONE); + monitor.changed.connect (on_changed); + } catch (GLib.Error e) { + warning (e.message); } + } - private void create_child_items (File file) { - foreach (var child_file in file.children) { - if (child_file.is_valid_directory) { - add_child (new FolderItem (child_file, view)); - } else if (child_file.is_valid_textfile) { - add_child (new FileItem (child_file, view)); - } + private void create_child_items (File file) { + foreach (var child_file in file.children) { + if (child_file.is_valid_directory) { + add_child (new FolderItem (child_file, view)); + } else if (child_file.is_valid_textfile) { + add_child (new FileItem (child_file, view)); } } + } - public void load_children () { - if (loading_required) { - create_child_items (file); - after_children_loaded (); - } + public void load_children () { + if (loading_required) { + create_child_items (file); + after_children_loaded (); } + } - private async void load_children_async () { - if (loading_required) { - // Do we need to yield after each child with dynamic view?? - Idle.add (() => { - create_child_items (file); - load_children_async.callback (); - return Source.REMOVE; - }); + private async void load_children_async () { + if (loading_required) { + // Do we need to yield after each child with dynamic view?? + Idle.add (() => { + create_child_items (file); + load_children_async.callback (); + return Source.REMOVE; + }); - yield; - } + yield; + } - after_children_loaded (); + after_children_loaded (); + } + + private void add_file (File child) { + Code.TreeListItem item = null; + if (child.is_valid_directory) { + item = new FolderItem (child, view); + } else if (child.is_valid_textfile) { + item = new FileItem (child, view); } - private void add_file (File child) { - Code.TreeListItem item = null; - if (child.is_valid_directory) { - item = new FolderItem (child, view); - } else if (child.is_valid_textfile) { - item = new FileItem (child, view); - } + if (item != null) { + add_child (item); + } + } - if (item != null) { - add_child (item); - } + private void after_children_loaded () { + children_loaded = true; + var root = get_root_folder (); + if (root != null) { + root.after_child_folder_loaded (this); //Updates child status emblens } - private void after_children_loaded () { - children_loaded = true; + children_finished_loading (); + } + + private void on_toggled () { + if (is_expanded) { + load_children_async.begin (); + } else { var root = get_root_folder (); - if (root != null) { - root.after_child_folder_loaded (this); //Updates child status emblens + if (root != null && + root.monitored_repo != null) { + //When toggled closed, update status to reflect hidden contents + root.update_item_status (this); } + } + } - children_finished_loading (); + public override GLib.Menu? get_context_menu () { + GLib.FileInfo info = null; + try { + info = file.file.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, GLib.FileQueryInfoFlags.NONE); + } catch (Error e) { + warning (e.message); } - private void on_toggled () { - if (is_expanded) { - load_children_async.begin (); - } else { - var root = get_root_folder (); - if (root != null && - root.monitored_repo != null) { - //When toggled closed, update status to reflect hidden contents - root.update_item_status (this); - } - } + var file_type = info.get_content_type (); + + var contractor_items = Utils.create_contract_items_for_file (file.file); + + var rename_menu_item = new GLib.MenuItem ( + _("Rename"), + GLib.Action.print_detailed_name ( + FolderTree.ACTION_PREFIX + FolderTree.ACTION_RENAME_FOLDER, + new Variant.string (file.path) + ) + ); + + var delete_item = new GLib.MenuItem ( + _("Move to Trash"), + GLib.Action.print_detailed_name ( + FolderTree.ACTION_PREFIX + FolderTree.ACTION_DELETE, + new Variant.string (file.path) + ) + ); + + var search_item = new GLib.MenuItem ( + _("Find in Folder…"), + GLib.Action.print_detailed_name ( + MainWindow.ACTION_PREFIX + MainWindow.ACTION_FIND_GLOBAL, + new Variant.string (file.file.get_path ()) + ) + ); + + var external_actions_section = new GLib.Menu (); + external_actions_section.append_item (create_submenu_for_open_in (file_type)); + if (contractor_items.get_n_items () > 0) { + external_actions_section.append_submenu (_("Other Actions"), contractor_items); } - public override GLib.Menu? get_context_menu () { - GLib.FileInfo info = null; - try { - info = file.file.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, GLib.FileQueryInfoFlags.NONE); - } catch (Error e) { - warning (e.message); - } + var direct_actions_section = new GLib.Menu (); + direct_actions_section.append_item (create_submenu_for_new ()); + direct_actions_section.append_item (rename_menu_item); + direct_actions_section.append_item (delete_item); - var file_type = info.get_content_type (); + var search_section = new GLib.Menu (); + search_section.append_item (search_item); - var contractor_items = Utils.create_contract_items_for_file (file.file); + var menu_model = new GLib.Menu (); + menu_model.append_section (null, external_actions_section); + menu_model.append_section (null, direct_actions_section); + menu_model.append_section (null, search_section); - var rename_menu_item = new GLib.MenuItem ( - _("Rename"), - GLib.Action.print_detailed_name ( - FileView.ACTION_PREFIX + FileView.ACTION_RENAME_FOLDER, - new Variant.string (file.path) - ) - ); + return menu_model; + } - var delete_item = new GLib.MenuItem ( - _("Move to Trash"), - GLib.Action.print_detailed_name ( - FileView.ACTION_PREFIX + FileView.ACTION_DELETE, - new Variant.string (file.path) - ) - ); + protected GLib.MenuItem create_submenu_for_open_in (string? file_type) { + var open_in_terminal_pane_item = new GLib.MenuItem ( + (_("Terminal Pane")), + GLib.Action.print_detailed_name ( + MainWindow.ACTION_PREFIX + MainWindow.ACTION_OPEN_IN_TERMINAL, + new Variant.string (file.path) + ) + ); + open_in_terminal_pane_item.set_icon (new ThemedIcon ("panel-bottom-symbolic")); + + var other_menu_item = new GLib.MenuItem ( + _("Other Application…"), + GLib.Action.print_detailed_name ( + FolderTree.ACTION_PREFIX + FolderTree.ACTION_SHOW_APP_CHOOSER, + file.path + ) + ); + + var extra_section = new GLib.Menu (); + extra_section.append_item (other_menu_item); + + var terminal_pane_section = new Menu (); + terminal_pane_section.append_item (open_in_terminal_pane_item); + + file_type = file_type ?? "inode/directory"; + + var open_in_menu = new GLib.Menu (); + open_in_menu.append_section (null, terminal_pane_section); + open_in_menu.append_section (null, Utils.create_executable_app_items_for_file (file.file, file_type)); + open_in_menu.append_section (null, extra_section); + + var open_in_menu_item = new GLib.MenuItem.submenu (_("Open In"), open_in_menu); + return open_in_menu_item; + } - var search_item = new GLib.MenuItem ( - _("Find in Folder…"), - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_FIND_GLOBAL, - new Variant.string (file.file.get_path ()) - ) - ); + protected GLib.MenuItem create_submenu_for_new () { + var new_folder_item = new GLib.MenuItem ( + _("Folder"), + GLib.Action.print_detailed_name ( + FolderTree.ACTION_PREFIX + FolderTree.ACTION_NEW_FOLDER, + new Variant.string (file.path) + ) + ); + + var new_file_item = new GLib.MenuItem ( + _("Empty File"), + GLib.Action.print_detailed_name ( + FolderTree.ACTION_PREFIX + FolderTree.ACTION_NEW_FILE, + new Variant.string (file.path) + ) + ); + + var new_menu = new GLib.Menu (); + 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) { + var template_submenu = new Menu (); + uint template_count = load_templates_from_folder (GLib.File.new_for_path (template_path), template_submenu); + if (template_count > 0) { + if (template_count > MAX_TEMPLATES) { + template_submenu.append_item (new MenuItem (_("…too many templates"), null)); + } - var external_actions_section = new GLib.Menu (); - external_actions_section.append_item (create_submenu_for_open_in (file_type)); - if (contractor_items.get_n_items () > 0) { - external_actions_section.append_submenu (_("Other Actions"), contractor_items); + new_menu.append_submenu (_("Templates"), template_submenu); } + } - var direct_actions_section = new GLib.Menu (); - direct_actions_section.append_item (create_submenu_for_new ()); - direct_actions_section.append_item (rename_menu_item); - direct_actions_section.append_item (delete_item); + var new_item = new GLib.MenuItem.submenu (_("New"), new_menu); + new_item.set_submenu (new_menu); - var search_section = new GLib.Menu (); - search_section.append_item (search_item); + return new_item; + } - var menu_model = new GLib.Menu (); - menu_model.append_section (null, external_actions_section); - menu_model.append_section (null, direct_actions_section); - menu_model.append_section (null, search_section); + // Recursively load templates from folder and subfolders keeping count of total menuitems + const int MAX_TEMPLATES = 2048; + private uint load_templates_from_folder ( + GLib.File template_folder, + Menu template_submenu + ) { + GLib.List template_list = null; + GLib.List folder_list = null; + + GLib.FileEnumerator enumerator; + var flags = GLib.FileQueryInfoFlags.NOFOLLOW_SYMLINKS; + uint count = 0; + 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); + } - return menu_model; - } + count ++; + } - protected GLib.MenuItem create_submenu_for_open_in (string? file_type) { - var open_in_terminal_pane_item = new GLib.MenuItem ( - (_("Terminal Pane")), - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_OPEN_IN_TERMINAL, - new Variant.string (file.path) - ) - ); - open_in_terminal_pane_item.set_icon (new ThemedIcon ("panel-bottom-symbolic")); + info = enumerator.next_file (null); + } + } catch (GLib.Error error) { + return 0; + } - var other_menu_item = new GLib.MenuItem ( - _("Other Application…"), - GLib.Action.print_detailed_name ( - FileView.ACTION_PREFIX + FileView.ACTION_SHOW_APP_CHOOSER, - file.path - ) + folder_list.sort ((a, b) => { + return strcmp (a.get_basename ().down (), b.get_basename ().down ()); + }); + + unowned List fl = folder_list; + while (fl != null && count < MAX_TEMPLATES) { + var folder = fl.data; + var folder_submenu = new Menu (); + var folder_submenuitem = new MenuItem.submenu ( + folder.get_basename (), + folder_submenu ); - var extra_section = new GLib.Menu (); - extra_section.append_item (other_menu_item); - - var terminal_pane_section = new Menu (); - terminal_pane_section.append_item (open_in_terminal_pane_item); - - file_type = file_type ?? "inode/directory"; + var sub_count = load_templates_from_folder (folder, folder_submenu); + if (sub_count > 0) { + template_submenu.append_item (folder_submenuitem); + count += sub_count; + } else { + count -= 1; // Adjust count for ignored folder + } - var open_in_menu = new GLib.Menu (); - open_in_menu.append_section (null, terminal_pane_section); - open_in_menu.append_section (null, Utils.create_executable_app_items_for_file (file.file, file_type)); - open_in_menu.append_section (null, extra_section); + fl = fl.next; + } - var open_in_menu_item = new GLib.MenuItem.submenu (_("Open In"), open_in_menu); - return open_in_menu_item; + if (count > MAX_TEMPLATES) { + warning ("too many templates! %u", count); + return count; } - protected GLib.MenuItem create_submenu_for_new () { - var new_folder_item = new GLib.MenuItem ( - _("Folder"), - GLib.Action.print_detailed_name ( - FileView.ACTION_PREFIX + FileView.ACTION_NEW_FOLDER, - new Variant.string (file.path) - ) - ); + template_list.sort ((a, b) => { + return strcmp (a.get_basename ().down (), b.get_basename ().down ()); + }); - var new_file_item = new GLib.MenuItem ( - _("Empty File"), + template_list.@foreach ((template) => { + var template_menuitem = new MenuItem ( + template.get_basename (), GLib.Action.print_detailed_name ( - FileView.ACTION_PREFIX + FileView.ACTION_NEW_FILE, - new Variant.string (file.path) + FolderTree.ACTION_PREFIX + FolderTree.ACTION_NEW_FROM_TEMPLATE, + new Variant ("(ss)", this.path, template.get_path ()) ) ); - var new_menu = new GLib.Menu (); - 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) { - var template_submenu = new Menu (); - uint template_count = load_templates_from_folder (GLib.File.new_for_path (template_path), template_submenu); - if (template_count > 0) { - if (template_count > MAX_TEMPLATES) { - template_submenu.append_item (new MenuItem (_("…too many templates"), null)); - } - - new_menu.append_submenu (_("Templates"), template_submenu); - } - } - - var new_item = new GLib.MenuItem.submenu (_("New"), new_menu); - new_item.set_submenu (new_menu); - - return new_item; - } - - // Recursively load templates from folder and subfolders keeping count of total menuitems - const int MAX_TEMPLATES = 2048; - private uint load_templates_from_folder ( - GLib.File template_folder, - Menu template_submenu - ) { - GLib.List template_list = null; - GLib.List folder_list = null; - - GLib.FileEnumerator enumerator; - var flags = GLib.FileQueryInfoFlags.NOFOLLOW_SYMLINKS; - uint count = 0; - 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); - } + template_submenu.append_item (template_menuitem); + }); - count ++; - } + return count; + } - info = enumerator.next_file (null); - } - } catch (GLib.Error error) { - return 0; - } + public void remove_all_badges () { + view.iterate_children (this, (child) => { + remove_badge (child); + return Code.TreeList.ITERATE_CONTINUE; + }); + } - folder_list.sort ((a, b) => { - return strcmp (a.get_basename ().down (), b.get_basename ().down ()); - }); + private void remove_badge (Code.TreeListItem item) { - unowned List fl = folder_list; - while (fl != null && count < MAX_TEMPLATES) { - var folder = fl.data; - var folder_submenu = new Menu (); - var folder_submenuitem = new MenuItem.submenu ( - folder.get_basename (), - folder_submenu - ); - - var sub_count = load_templates_from_folder (folder, folder_submenu); - if (sub_count > 0) { - template_submenu.append_item (folder_submenuitem); - count += sub_count; - } else { - count -= 1; // Adjust count for ignored folder - } + if (item is FolderItem) { + ((FolderItem) item).remove_all_badges (); + } - fl = fl.next; - } + item.badge = ""; + } - if (count > MAX_TEMPLATES) { - warning ("too many templates! %u", count); - return count; - } + public override void add_child (Code.TreeListItem item) { + if (has_dummy) { + base.remove_child (dummy); + has_dummy = false; + } - template_list.sort ((a, b) => { - return strcmp (a.get_basename ().down (), b.get_basename ().down ()); - }); + base.add_child (item); + } - 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 ()) - ) - ); + // Only for removing "real" items; dummy is added/removed directly + public override void remove_child (Code.TreeListItem item) requires (has_dummy == false) { + if (item is FolderItem) { + var folder = (FolderItem) item; + folder.remove_all_children (); + } - template_submenu.append_item (template_menuitem); - }); + base.remove_child (item); - return count; + // Add back dummy if empty + if (has_no_children ()) { + base.add_child (dummy); + has_dummy = true; } + } - public void remove_all_badges () { - view.iterate_children (this, (child) => { - remove_badge (child); - return Code.TreeList.ITERATE_CONTINUE; - }); + // public new void clear () { + // ((Code.Widgets.SourceList.ExpandableItem)this).clear (); + // has_dummy = false; + // } + + protected virtual void on_changed ( + GLib.File source, + GLib.File? dest, + GLib.FileMonitorEvent event + ) { + if (source.get_basename ().has_prefix (".goutputstream")) { + return; // Ignore changes due to temp files and streams } - private void remove_badge (Code.TreeListItem item) { + view.folder_item_update_hook (source, dest, event); - if (item is FolderItem) { - ((FolderItem) item).remove_all_badges (); + if (!children_loaded) { // No child items except dummy, child never expanded + /* Empty folder with dummy item will come here even if expanded */ + switch (event) { + case GLib.FileMonitorEvent.DELETED: + file.invalidate_cache (); //TODO Throttle if required + if (is_expanded) { + // Force refresh of child model? + notify_property ("is-expanded"); + } + break; + case GLib.FileMonitorEvent.CREATED: + file.invalidate_cache (); //TODO Throttle if required + if (is_expanded) { + notify_property ("is-expanded"); + } + break; + case FileMonitorEvent.RENAMED: + case FileMonitorEvent.PRE_UNMOUNT: + case FileMonitorEvent.UNMOUNTED: + case FileMonitorEvent.CHANGED: + case FileMonitorEvent.CHANGES_DONE_HINT: + case FileMonitorEvent.MOVED: + case FileMonitorEvent.MOVED_IN: + case FileMonitorEvent.MOVED_OUT: + case FileMonitorEvent.ATTRIBUTE_CHANGED: + + break; } + } else { + // Child has been expanded ( but could be closed now) and + // items loaded (or dummy). + // No cache invalidation is needed here because the entire state + // is kept in the tree + //TODO Test for TreeList + switch (event) { + case GLib.FileMonitorEvent.DELETED: + // Find item corresponding to deleted file + // Note may not be found if deleted file is not valid for display + var path_item = find_item_for_path (source.get_path ()); + if (path_item != null) { + remove_child (path_item); + } - item.badge = ""; - } + break; + case GLib.FileMonitorEvent.CREATED: + if (source.query_exists () == false) { + return; + } - public override void add_child (Code.TreeListItem item) { - if (has_dummy) { - base.remove_child (dummy); - has_dummy = false; - } + var path_item = find_item_for_path (source.get_path ()); + if (path_item == null) { + var file = new File (source.get_path ()); + if (file.is_valid_directory) { + path_item = new FolderItem (file, view); + } else if (file.is_valid_textfile) { + path_item = new FileItem (file, view); + } - base.add_child (item); - } + add_child (path_item); // null parameter is silently ignored + } - // Only for removing "real" items; dummy is added/removed directly - public override void remove_child (Code.TreeListItem item) requires (has_dummy == false) { - if (item is FolderItem) { - var folder = (FolderItem) item; - folder.remove_all_children (); + break; + case FileMonitorEvent.RENAMED: + case FileMonitorEvent.PRE_UNMOUNT: + case FileMonitorEvent.UNMOUNTED: + case FileMonitorEvent.CHANGED: + case FileMonitorEvent.CHANGES_DONE_HINT: + case FileMonitorEvent.MOVED: + case FileMonitorEvent.MOVED_IN: + case FileMonitorEvent.MOVED_OUT: + case FileMonitorEvent.ATTRIBUTE_CHANGED: + break; } + } - base.remove_child (item); - - // Add back dummy if empty - if (has_no_children ()) { - base.add_child (dummy); - has_dummy = true; + // Reduce spamming of root (still results in multiple signals per change in file being edited + //TODO Throttle this signal? + if (event == FileMonitorEvent.CHANGES_DONE_HINT) { + //TODO Get root folder once as it will not change for the life of this folder + var root = get_root_folder (this); + if (root != null) { + root.child_folder_changed (this); } } + } - // public new void clear () { - // ((Code.Widgets.SourceList.ExpandableItem)this).clear (); - // has_dummy = false; - // } - - protected virtual void on_changed ( - GLib.File source, - GLib.File? dest, - GLib.FileMonitorEvent event - ) { - if (source.get_basename ().has_prefix (".goutputstream")) { - return; // Ignore changes due to temp files and streams + private Code.FolderManagerItem? find_item_for_path (string path) { + Code.FolderManagerItem? found_item = null; + view.iterate_children (this, (item) => { + // Item could be dummy + if (item is Code.FolderManagerItem) { + var child = (Code.FolderManagerItem) item; + if (child.path == path) { + found_item = child; + return Code.TreeList.ITERATE_STOP; + } } - view.folder_item_update_hook (source, dest, event); + return Code.TreeList.ITERATE_CONTINUE; + }); - if (!children_loaded) { // No child items except dummy, child never expanded - /* Empty folder with dummy item will come here even if expanded */ - switch (event) { - case GLib.FileMonitorEvent.DELETED: - file.invalidate_cache (); //TODO Throttle if required - if (is_expanded) { - // Force refresh of child model? - notify_property ("is-expanded"); - } - break; - case GLib.FileMonitorEvent.CREATED: - file.invalidate_cache (); //TODO Throttle if required - if (is_expanded) { - notify_property ("is-expanded"); - } - break; - case FileMonitorEvent.RENAMED: - case FileMonitorEvent.PRE_UNMOUNT: - case FileMonitorEvent.UNMOUNTED: - case FileMonitorEvent.CHANGED: - case FileMonitorEvent.CHANGES_DONE_HINT: - case FileMonitorEvent.MOVED: - case FileMonitorEvent.MOVED_IN: - case FileMonitorEvent.MOVED_OUT: - case FileMonitorEvent.ATTRIBUTE_CHANGED: - - break; - } - } else { - // Child has been expanded ( but could be closed now) and - // items loaded (or dummy). - // No cache invalidation is needed here because the entire state - // is kept in the tree - //TODO Test for TreeList - switch (event) { - case GLib.FileMonitorEvent.DELETED: - // Find item corresponding to deleted file - // Note may not be found if deleted file is not valid for display - var path_item = find_item_for_path (source.get_path ()); - if (path_item != null) { - remove_child (path_item); - } + return found_item; + } - break; - case GLib.FileMonitorEvent.CREATED: - if (source.query_exists () == false) { - return; - } + public void on_add_template (string template_path) { + // Expand folder before trying to copy template so that child appears for renaming + if (!is_expanded) { + is_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); + } + } - var path_item = find_item_for_path (source.get_path ()); - if (path_item == null) { - var file = new File (source.get_path ()); - if (file.is_valid_directory) { - path_item = new FolderItem (file, view); - } else if (file.is_valid_textfile) { - path_item = new FileItem (file, view); - } + 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; + } - add_child (path_item); // null parameter is silently ignored - } + 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++; + } - break; - case FileMonitorEvent.RENAMED: - case FileMonitorEvent.PRE_UNMOUNT: - case FileMonitorEvent.UNMOUNTED: - case FileMonitorEvent.CHANGED: - case FileMonitorEvent.CHANGES_DONE_HINT: - case FileMonitorEvent.MOVED: - case FileMonitorEvent.MOVED_IN: - case FileMonitorEvent.MOVED_OUT: - case FileMonitorEvent.ATTRIBUTE_CHANGED: - break; - } - } + name = new_file.get_basename (); - // Reduce spamming of root (still results in multiple signals per change in file being edited - //TODO Throttle this signal? - if (event == FileMonitorEvent.CHANGES_DONE_HINT) { - //TODO Get root folder once as it will not change for the life of this folder - var root = get_root_folder (this); - if (root != null) { - root.child_folder_changed (this); - } - } + //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; } - private FolderManager.Item? find_item_for_path (string path) { - FolderManager.Item? found_item = null; - view.iterate_children (this, (item) => { - // Item could be dummy - if (item is FolderManager.Item) { - var child = (FolderManager.Item) item; - if (child.path == path) { - found_item = child; - return Code.TreeList.ITERATE_STOP; - } + // 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 (FolderTree.ACTION_RENAME_FILE, view.actions); + if (rename_action != null && rename_action.enabled) { + rename_action.activate (path); + } else { + critical ("Rename action not available"); } - return Code.TreeList.ITERATE_CONTINUE; + return Source.REMOVE; }); + }); + } - return found_item; - } - - public void on_add_template (string template_path) { - // Expand folder before trying to copy template so that child appears for renaming - if (!is_expanded) { - is_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); - } + 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; } - 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; - } - 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++; - } + 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++; + } - 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; - } + name = new_file.get_basename (); - // Wait for monitor to pickup file creation and add new item + // Expand folder before trying to rename + if (!is_expanded) { ulong once = 0; - once = child_added.connect (() => { + once = children_finished_loading.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; - }); + rename_new (name, is_folder); }); - } - - 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++; - } - - name = new_file.get_basename (); - - // Expand folder before trying to rename - if (!is_expanded) { - ulong once = 0; - once = children_finished_loading.connect (() => { - this.disconnect (once); - rename_new (name, is_folder); - }); - - is_expanded = true; // causes async loading of children - } else { - rename_new (name, is_folder); - } + is_expanded = true; // causes async loading of children + } else { + rename_new (name, is_folder); } + } - private void rename_new (string name, bool is_folder) { - // assert (!view.editing); - // var rename_item = new RenameItem (name, is_folder); - // add_child (rename_item); - - // // 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 (() => { - // rename_item.disconnect (once); - // // A name was accepted so create the corresponding file - // var new_name = rename_item.name; - // try { - // var gfile = file.file.get_child_for_display_name (new_name); - // if (rename_item.is_folder) { - // gfile.make_directory (); - // } else { - // gfile.create (FileCreateFlags.NONE); - // view.activate (gfile.get_path ()); - // } - // } catch (Error e) { - // warning (e.message); - // } - // }); - - // /* Need to remove rename item even when editing cancelled so cannot use "edited" signal */ - // Timeout.add (200, () => { - // if (view.editing) { - // return Source.CONTINUE; - // } else { - // remove_child (rename_item); - // return Source.REMOVE; - // } - // }); - // } else { - // critical ("Failed to rename new item"); - // remove_child (rename_item); - // } - - // return Source.REMOVE; - // }); - } + private void rename_new (string name, bool is_folder) { + // assert (!view.editing); + // var rename_item = new RenameItem (name, is_folder); + // add_child (rename_item); + + // // 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 (() => { + // rename_item.disconnect (once); + // // A name was accepted so create the corresponding file + // var new_name = rename_item.name; + // try { + // var gfile = file.file.get_child_for_display_name (new_name); + // if (rename_item.is_folder) { + // gfile.make_directory (); + // } else { + // gfile.create (FileCreateFlags.NONE); + // view.activate (gfile.get_path ()); + // } + // } catch (Error e) { + // warning (e.message); + // } + // }); + + // /* Need to remove rename item even when editing cancelled so cannot use "edited" signal */ + // Timeout.add (200, () => { + // if (view.editing) { + // return Source.CONTINUE; + // } else { + // remove_child (rename_item); + // return Source.REMOVE; + // } + // }); + // } else { + // critical ("Failed to rename new item"); + // remove_child (rename_item); + // } + + // return Source.REMOVE; + // }); } } + diff --git a/src/FolderManager/FileView.vala b/src/FolderManager/FolderTree.vala similarity index 92% rename from src/FolderManager/FileView.vala rename to src/FolderManager/FolderTree.vala index a537cdc388..65c3e56af6 100644 --- a/src/FolderManager/FileView.vala +++ b/src/FolderManager/FolderTree.vala @@ -1,28 +1,22 @@ -/*- - * Copyright (c) 2017-2026 elementary LLC. (https://elementary.io), - * 2013 Julien Spautz - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 - * as published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Authored by: Julien Spautz , Andrei-Costin Zisu +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2026 elementary, Inc. */ -/** - * SourceList that displays folders and their contents. - */ -// public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { -public class Scratch.FolderManager.FileView : Granite.Bin, Code.PaneSwitcher { +/* FolderTree shows a single folder and its children as a tree */ +public class Code.FolderTreeItem : Code.TreeListItem { + public FolderManagerItem? item { get; construct; } //Either File or Folder Item + + public FolderTreeItem (FolderManagerItem item) { + Object ( item: item ); + } + + construct { + item.file.bind_property ("name", this, text); + } +} + +public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { public signal void file_activate (File file); public signal bool rename_request (File file); @@ -65,7 +59,7 @@ public class Scratch.FolderManager.FileView : Granite.Bin, Code.PaneSwitcher { private Scratch.Services.GitManager git_manager; private Scratch.Services.PluginsManager plugins; - public FileView (Scratch.Services.PluginsManager plugins_manager) { + public FolderTree (Scratch.Services.PluginsManager plugins_manager) { plugins = plugins_manager; } @@ -96,6 +90,11 @@ public class Scratch.FolderManager.FileView : Granite.Bin, Code.PaneSwitcher { child = tree_list; } + public bool contains_file (GLib.File gfile) { + //TODO Fill out + return false; + } + private void action_close_folder (SimpleAction action, GLib.Variant? parameter) { var path = parameter.get_string (); if (path == null || path == "") { @@ -232,7 +231,7 @@ public class Scratch.FolderManager.FileView : Granite.Bin, Code.PaneSwitcher { if (project_folder.path != path) { project_folder.is_expanded = false; activate_action ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_HIDE_PROJECT_DOCS, + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_HIDE_PROJECT_DOCS, "s", project_folder.path ); @@ -306,7 +305,7 @@ public class Scratch.FolderManager.FileView : Granite.Bin, Code.PaneSwitcher { Code.TreeListItem? matched_item = null; tree_list.iterate_children (null, (item) => { - if ((item is Item) && item.path == path) { + if ((item is FolderManagerItem) && item.path == path) { matched_item = item; return Code.TreeList.ITERATE_STOP; } @@ -340,9 +339,9 @@ public class Scratch.FolderManager.FileView : Granite.Bin, Code.PaneSwitcher { return matched_item; } - public bool project_is_open (string project_path) { - return get_project_for_file (GLib.File.new_for_path (project_path)) != null; - } + // public bool project_is_open (string project_path) { + // return get_project_for_file (GLib.File.new_for_path (project_path)) != null; + // } public ProjectFolderItem? get_project_for_file (GLib.File file) { ProjectFolderItem? matched_project = null; @@ -367,7 +366,7 @@ public class Scratch.FolderManager.FileView : Granite.Bin, Code.PaneSwitcher { /* Do global search on project containing the file path supplied in parameter */ public void search_global (string path, string? term = null) { - var item_for_path = (Item?)(expand_to_path (path)); + var item_for_path = (FolderManagerItem?)(expand_to_path (path)); if (item_for_path != null) { var search_root = item_for_path.get_root_folder (); if (search_root is ProjectFolderItem) { @@ -461,7 +460,7 @@ public class Scratch.FolderManager.FileView : Granite.Bin, Code.PaneSwitcher { // } } - private void rename_items_with_same_name (Item item) { + private void rename_items_with_same_name (FolderManagerItem item) { // string item_name = item.file.name; // tree_list.iterate_children (null, (child) => { // string new_other_item_name, new_item_name; @@ -616,10 +615,10 @@ public class Scratch.FolderManager.FileView : Granite.Bin, Code.PaneSwitcher { var item = find_path (null, path); if (item != null) { - var item_to_delete = item as Scratch.FolderManager.Item; + var item_to_delete = item as Code.FolderManagerItem; // Wait for ProjectFolderItem closed signal handle logic to run before moving item to trash - if (item_to_delete is Scratch.FolderManager.ProjectFolderItem) { + if (item_to_delete is Code.ProjectFolderItem) { item_to_delete.closed.connect_after (() => { item_to_delete.trash (); }); @@ -662,7 +661,7 @@ public class Scratch.FolderManager.FileView : Granite.Bin, Code.PaneSwitcher { assert (parents.length () <= 1); assert (parents.length () == 0 || children.length () == 0); var dialog = new Scratch.Dialogs.CloseProjectsConfirmationDialog ( - (MainWindow) get_root (), + (Scratch.MainWindow) get_root (), parents.length (), children.length () ); @@ -741,7 +740,7 @@ public class Scratch.FolderManager.FileView : Granite.Bin, Code.PaneSwitcher { private bool is_open (File folder) { bool open = false; tree_list.iterate_children (null, (child) => { - if (folder.path == ((Item) child).path) { + if (folder.path == ((FolderManagerItem) child).path) { open = true; return Code.TreeList.ITERATE_STOP; } @@ -756,7 +755,7 @@ public class Scratch.FolderManager.FileView : Granite.Bin, Code.PaneSwitcher { string[] to_save = {}; tree_list.iterate_children (null, (item) => { var saved = false; - var folder_path = ((Item) item).path; + var folder_path = ((FolderManagerItem) item).path; //Do we need to de-duplicate? Not possible to open a project twice? foreach (var saved_folder in to_save) { diff --git a/src/FolderManager/Item.vala b/src/FolderManager/Item.vala index f4621a1bf2..3643d2b11d 100644 --- a/src/FolderManager/Item.vala +++ b/src/FolderManager/Item.vala @@ -18,128 +18,124 @@ * Authored by: Julien Spautz , Andrei-Costin Zisu */ -namespace Scratch.FolderManager { - /** - * Common abstract class for file and folder items. - */ - public abstract class Item: Code.TreeListItem { - public File file { get; construct; } - public FileView view { get; construct; } - - public string name { - get { - return text; - } - - set { - text = value; - } - } +public abstract class Code.FolderManagerItem: Object { + public Code.File file { get; construct; } + public Code.FolderTree view { get; construct; } - public string path { - owned get { return file.path; } - set { file.path = value; } + public string name { + get { + return text; } - public signal void edited (string new_name); - - construct { - is_selectable = true; - is_editable = true; - text = file.name; - icon = file.icon; - edited.connect (rename); - tooltip = Scratch.Utils.replace_home_with_tilde (file.path); - - notify["activatable-tooltip"].connect (() => { - tooltip = ("%s\n" + Granite.TOOLTIP_SECONDARY_TEXT_MARKUP).printf ( - Scratch.Utils.replace_home_with_tilde (file.path), - secondary_icon_tooltip - ); - }); + set { + text = value; } + } - protected void rename (string new_name) { - file.rename (new_name); - } + public string path { + owned get { return file.path; } + set { file.path = value; } + } - public bool equal (Item b) { - return path == b.path; - } + public signal void edited (string new_name); + + construct { + is_selectable = true; + is_editable = true; + text = file.name; + icon = file.icon; + edited.connect (rename); + tooltip = Scratch.Utils.replace_home_with_tilde (file.path); + + notify["activatable-tooltip"].connect (() => { + tooltip = ("%s\n" + Granite.TOOLTIP_SECONDARY_TEXT_MARKUP).printf ( + Scratch.Utils.replace_home_with_tilde (file.path), + secondary_icon_tooltip + ); + }); + } - public void trash () { - file.trash (); - } + protected void rename (string new_name) { + file.rename (new_name); + } - public int compare (Code.TreeListItem a, Code.TreeListItem b) { - // if (a is RenameItem) { - // return -1; - // } else - // if (b is RenameItem) { - // return 1; - // } + public bool equal (FolderManagerItem b) { + return path == b.path; + } - if (a is FolderItem && b is FileItem) { - return -1; - } else if (a is FileItem && b is FolderItem) { - return 1; - } + public void trash () { + file.trash (); + } - assert (a is Item && b is Item); //Ensure more informative error message + public int compare (Code.TreeListItem a, Code.TreeListItem b) { + // if (a is RenameItem) { + // return -1; + // } else + // if (b is RenameItem) { + // return 1; + // } - return File.compare (((Item)a).file, ((Item)b).file); + if (a is FolderItem && b is FileItem) { + return -1; + } else if (a is FileItem && b is FolderItem) { + return 1; } - public bool allow_dnd_sorting () { - return false; - } + assert (a is FolderManagerItem && b is FolderManagerItem); //Ensure more informative error message - public ProjectFolderItem? get_root_folder (Code.TreeListItem start = null) { - if (start == null) { - start = this; - } - - if (start is ProjectFolderItem) { - return start as ProjectFolderItem; - } else if (start.parent is ProjectFolderItem) { - return start.parent as ProjectFolderItem; - } else if (start.parent != null) { - return get_root_folder (start.parent); - } else { - return null; - } - } + return File.compare (((FolderManagerItem)a).file, ((FolderManagerItem)b).file); + } - // protected class RenameItem : Code.TreeListItem { - // public bool is_folder { get; construct; } - // public string name { get; set construct; } - - // public RenameItem (string name, bool is_folder) { - // Object ( - // name: name - // is_folder: is_folder - // ); - // } - - // construct { - // is_editable = true; - // is_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; - // } - // } - // } + public bool allow_dnd_sorting () { + return false; + } - public virtual GLib.Menu? get_context_menu () { return null; } + public ProjectFolderItem? get_root_folder (Code.TreeListItem start = null) { + if (start == null) { + start = this; + } + + if (start is ProjectFolderItem) { + return start as ProjectFolderItem; + } else if (start.parent is ProjectFolderItem) { + return start.parent as ProjectFolderItem; + } else if (start.parent != null) { + return get_root_folder (start.parent); + } else { + return null; + } } + + // protected class RenameItem : Code.TreeListItem { + // public bool is_folder { get; construct; } + // public string name { get; set construct; } + + // public RenameItem (string name, bool is_folder) { + // Object ( + // name: name + // is_folder: is_folder + // ); + // } + + // construct { + // is_editable = true; + // is_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; + // } + // } + // } + + public virtual GLib.Menu? get_context_menu () { return null; } } + diff --git a/src/FolderManager/ProjectFolderItem.vala b/src/FolderManager/ProjectFolderItem.vala index 8126e2b2fb..77022b2d8e 100644 --- a/src/FolderManager/ProjectFolderItem.vala +++ b/src/FolderManager/ProjectFolderItem.vala @@ -1,688 +1,687 @@ /*- - * Copyright (c) 2018-2026 elementary LLC. (https://elementary.io), - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 - * as published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Authored by: David Hewitt - */ - -namespace Scratch.FolderManager { - public class ProjectFolderItem : FolderItem { - struct VisibleItem { - public string rel_path; - public Item item; - } - - private static Icon added_icon; - private static Icon modified_icon; - +* Copyright (c) 2018-2026 elementary LLC. (https://elementary.io), +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License version 3 +* as published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranties of +* MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR +* PURPOSE. See the GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +* Authored by: David Hewitt +*/ + + +public class Code.ProjectFolderItem : FolderItem { + struct VisibleItem { + public string rel_path; + public FolderManagerItem item; + } + private static Icon added_icon; + private static Icon modified_icon; - public signal void closed (); + public signal void closed (); - public Scratch.Services.MonitoredRepository? monitored_repo { get; private set; default = null; } - // Cache the visible item in the project. - private List visible_item_list = null; + public Scratch.Services.MonitoredRepository? monitored_repo { get; private set; default = null; } + // Cache the visible item in the project. + private List visible_item_list = null; - public bool is_git_repo { - get { - return monitored_repo != null; - } + public bool is_git_repo { + get { + return monitored_repo != null; } + } - private Ggit.Repository? git_repo { - get { - return (is_git_repo ? monitored_repo.git_repo : null); - } + private Ggit.Repository? git_repo { + get { + return (is_git_repo ? monitored_repo.git_repo : null); } + } - public ProjectFolderItem (File file, FileView view) requires (file.is_valid_directory) { - Object (file: file, view: view); - } + public ProjectFolderItem (File file, FolderTree view) requires (file.is_valid_directory) { + Object (file: file, view: view); + } - static construct { - added_icon = new ThemedIcon ("emblem-git-new-symbolic"); - modified_icon = new ThemedIcon ("emblem-git-modified-symbolic"); - } + static construct { + added_icon = new ThemedIcon ("emblem-git-new-symbolic"); + modified_icon = new ThemedIcon ("emblem-git-modified-symbolic"); + } - private void branch_or_name_changed () { - if (monitored_repo != null) { - //As SourceList items are not widgets we have to use markup to change appearance of text. - if (monitored_repo.head_is_branch) { - text = "%s\n%s".printf ( - name, monitored_repo.branch_name - ); - } else { //Distinguish detached heads visually - text = "%s\n %s".printf ( - name, monitored_repo.branch_name - ); - } + private void branch_or_name_changed () { + if (monitored_repo != null) { + //As SourceList items are not widgets we have to use markup to change appearance of text. + if (monitored_repo.head_is_branch) { + text = "%s\n%s".printf ( + name, monitored_repo.branch_name + ); + } else { //Distinguish detached heads visually + text = "%s\n %s".printf ( + name, monitored_repo.branch_name + ); } } + } - construct { - monitored_repo = Scratch.Services.GitManager.get_instance ().add_project (this); - notify["name"].connect (branch_or_name_changed); - if (monitored_repo != null) { - monitored_repo.branch_changed.connect (branch_or_name_changed); - monitored_repo.ignored_changed.connect ((deprioritize_git_ignored)); - monitored_repo.file_status_change.connect (() => update_item_status (null)); - monitored_repo.update_status_map (); - monitored_repo.branch_changed (); - } + construct { + monitored_repo = Scratch.Services.GitManager.get_instance ().add_project (this); + notify["name"].connect (branch_or_name_changed); + if (monitored_repo != null) { + monitored_repo.branch_changed.connect (branch_or_name_changed); + monitored_repo.ignored_changed.connect ((deprioritize_git_ignored)); + monitored_repo.file_status_change.connect (() => update_item_status (null)); + monitored_repo.update_status_map (); + monitored_repo.branch_changed (); } + } - protected override void on_changed (GLib.File source, GLib.File? dest, GLib.FileMonitorEvent event) { - if (source.equal (file.file) && event == DELETED) { - closed (); - } else { - base.on_changed (source, dest, event); - } + protected override void on_changed (GLib.File source, GLib.File? dest, GLib.FileMonitorEvent event) { + if (source.equal (file.file) && event == DELETED) { + closed (); + } else { + base.on_changed (source, dest, event); } + } - public void child_folder_changed (FolderItem folder) { - if (monitored_repo != null) { - monitored_repo.update_status_map (); - } + public void child_folder_changed (FolderItem folder) { + if (monitored_repo != null) { + monitored_repo.update_status_map (); } + } - public void after_child_folder_loaded (FolderItem folder) { - view.iterate_children (folder, (child) => { - if (child is Item) { - var item = (Item)child; - var rel_path = this.file.file.get_relative_path (item.file.file); - if (rel_path != null && rel_path != "") { - visible_item_list.prepend ({rel_path, item}); - } - } - - return Code.TreeList.ITERATE_CONTINUE; - }); - + //TODO Move to FolderTree + public void after_child_folder_loaded (FolderItem folder) { + // view.iterate_children (folder, (child) => { + // if (child is FolderManagerItem) { + // var item = (FolderManagerItem)child; + // var rel_path = this.file.file.get_relative_path (item.file.file); + // if (rel_path != null && rel_path != "") { + // visible_item_list.prepend ({rel_path, item}); + // } + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + + + // if (monitored_repo != null) { + // monitored_repo.update_status_map (); + // update_item_status (folder); + // deprioritize_git_ignored (); + // } + } - if (monitored_repo != null) { - monitored_repo.update_status_map (); - update_item_status (folder); - deprioritize_git_ignored (); + public override GLib.Menu? get_context_menu () { + string file_type = ""; + try { + var info = file.file.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, GLib.FileQueryInfoFlags.NONE); + if (info.has_attribute (GLib.FileAttribute.STANDARD_CONTENT_TYPE)) { + file_type = info.get_content_type (); } + } catch (Error e) { + warning (e.message); } - public override GLib.Menu? get_context_menu () { - string file_type = ""; - try { - var info = file.file.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, GLib.FileQueryInfoFlags.NONE); - if (info.has_attribute (GLib.FileAttribute.STANDARD_CONTENT_TYPE)) { - file_type = info.get_content_type (); - } - } catch (Error e) { - warning (e.message); - } - - MenuItem set_active_folder_item; - if (is_git_repo) { - set_active_folder_item = new GLib.MenuItem ( - _("Set as Active Project"), - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_SET_ACTIVE_PROJECT, - new Variant.string (file.path) - ) - ); - } else { - set_active_folder_item = new GLib.MenuItem ( - _("Open in Terminal Pane"), - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_OPEN_IN_TERMINAL, - new Variant.string ( - Services.GitManager.get_instance ().get_default_build_dir (path) - ) - ) - ); - } - - set_active_folder_item.set_attribute_value ( - "accel", - Utils.get_accel_for_action ( - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_OPEN_IN_TERMINAL, - "" - ) - ) - ); - - var external_actions_section = new GLib.Menu (); - external_actions_section.append_item (set_active_folder_item); - external_actions_section.append_item (create_submenu_for_open_in (file_type)); - - var folder_actions_section = new GLib.Menu (); - folder_actions_section.append_item (create_submenu_for_new ()); - if (monitored_repo != null) { - var branch_action_item = new MenuItem ( - _("Branch Actions…"), - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_BRANCH_ACTIONS, - new Variant.string (file.path) - ) - ); - folder_actions_section.append_item (branch_action_item); - } - - var close_folder_item = new GLib.MenuItem ( - _("Close Folder"), + MenuItem set_active_folder_item; + if (is_git_repo) { + set_active_folder_item = new GLib.MenuItem ( + _("Set as Active Project"), GLib.Action.print_detailed_name ( - FileView.ACTION_PREFIX + FileView.ACTION_CLOSE_FOLDER, + MainWindow.ACTION_PREFIX + MainWindow.ACTION_SET_ACTIVE_PROJECT, new Variant.string (file.path) ) ); - - var close_all_except_item = new GLib.MenuItem ( - _("Close Other Folders"), + } else { + set_active_folder_item = new GLib.MenuItem ( + _("Open in Terminal Pane"), GLib.Action.print_detailed_name ( - FileView.ACTION_PREFIX + FileView.ACTION_CLOSE_OTHER_FOLDERS, - new Variant.string (file.path) + MainWindow.ACTION_PREFIX + MainWindow.ACTION_OPEN_IN_TERMINAL, + new Variant.string ( + Services.GitManager.get_instance ().get_default_build_dir (path) + ) ) ); - var close_other_folders_action = Utils.action_from_group ( - FileView.ACTION_CLOSE_OTHER_FOLDERS, - view.actions - ); - close_other_folders_action.set_enabled (!view.is_empty); - - var close_actions_section = new GLib.Menu (); - close_actions_section.append_item (close_folder_item); - close_actions_section.append_item (close_all_except_item); - - var n_open = Scratch.Services.DocumentManager.get_instance ().open_for_project (path); - var open_text = ngettext ("Close %u Open Document", - "Close %u Open Documents", - n_open).printf (n_open); + } - var close_item = new GLib.MenuItem ( - open_text, + set_active_folder_item.set_attribute_value ( + "accel", + Utils.get_accel_for_action ( GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_PROJECT_DOCS, - new Variant.string (file.file.get_path ()) + MainWindow.ACTION_PREFIX + MainWindow.ACTION_OPEN_IN_TERMINAL, + "" ) - ); - - var hide_text = ngettext ("Hide %u Open Document", - "Hide %u Open Documents", - n_open).printf (n_open); - - var hide_item = new GLib.MenuItem ( - hide_text, + ) + ); + + var external_actions_section = new GLib.Menu (); + external_actions_section.append_item (set_active_folder_item); + external_actions_section.append_item (create_submenu_for_open_in (file_type)); + + var folder_actions_section = new GLib.Menu (); + folder_actions_section.append_item (create_submenu_for_new ()); + if (monitored_repo != null) { + var branch_action_item = new MenuItem ( + _("Branch Actions…"), GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_HIDE_PROJECT_DOCS, - new Variant.string (file.file.get_path ()) + MainWindow.ACTION_PREFIX + MainWindow.ACTION_BRANCH_ACTIONS, + new Variant.string (file.path) ) ); + folder_actions_section.append_item (branch_action_item); + } - hide_item.set_attribute_value ( - "accel", - Utils.get_accel_for_action ( - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_HIDE_PROJECT_DOCS, - "" - ) + var close_folder_item = new GLib.MenuItem ( + _("Close Folder"), + GLib.Action.print_detailed_name ( + FolderTree.ACTION_PREFIX + FolderTree.ACTION_CLOSE_FOLDER, + new Variant.string (file.path) + ) + ); + + var close_all_except_item = new GLib.MenuItem ( + _("Close Other Folders"), + GLib.Action.print_detailed_name ( + FolderTree.ACTION_PREFIX + FolderTree.ACTION_CLOSE_OTHER_FOLDERS, + new Variant.string (file.path) + ) + ); + var close_other_folders_action = Utils.action_from_group ( + FolderTree.ACTION_CLOSE_OTHER_FOLDERS, + view.actions + ); + close_other_folders_action.set_enabled (!view.is_empty); + + var close_actions_section = new GLib.Menu (); + close_actions_section.append_item (close_folder_item); + close_actions_section.append_item (close_all_except_item); + + var n_open = Scratch.Services.DocumentManager.get_instance ().open_for_project (path); + var open_text = ngettext ("Close %u Open Document", + "Close %u Open Documents", + n_open).printf (n_open); + + var close_item = new GLib.MenuItem ( + open_text, + GLib.Action.print_detailed_name ( + MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_PROJECT_DOCS, + new Variant.string (file.file.get_path ()) + ) + ); + + var hide_text = ngettext ("Hide %u Open Document", + "Hide %u Open Documents", + n_open).printf (n_open); + + var hide_item = new GLib.MenuItem ( + hide_text, + GLib.Action.print_detailed_name ( + MainWindow.ACTION_PREFIX + MainWindow.ACTION_HIDE_PROJECT_DOCS, + new Variant.string (file.file.get_path ()) + ) + ); + + hide_item.set_attribute_value ( + "accel", + Utils.get_accel_for_action ( + GLib.Action.print_detailed_name ( + MainWindow.ACTION_PREFIX + MainWindow.ACTION_HIDE_PROJECT_DOCS, + "" ) - ); - - var n_restorable = Scratch.Services.DocumentManager.get_instance ().restorable_for_project (path); - var restore_text = ngettext ("Restore %u Hidden Document", - "Restore %u Hidden Documents", - n_restorable).printf (n_restorable); - - var restore_item = new GLib.MenuItem ( - restore_text, + ) + ); + + var n_restorable = Scratch.Services.DocumentManager.get_instance ().restorable_for_project (path); + var restore_text = ngettext ("Restore %u Hidden Document", + "Restore %u Hidden Documents", + n_restorable).printf (n_restorable); + + var restore_item = new GLib.MenuItem ( + restore_text, + GLib.Action.print_detailed_name ( + MainWindow.ACTION_PREFIX + MainWindow.ACTION_RESTORE_PROJECT_DOCS, + new Variant.string (file.file.get_path ()) + ) + ); + + restore_item.set_attribute_value ( + "accel", + Utils.get_accel_for_action ( GLib.Action.print_detailed_name ( MainWindow.ACTION_PREFIX + MainWindow.ACTION_RESTORE_PROJECT_DOCS, - new Variant.string (file.file.get_path ()) + "" ) - ); + ) + ); - restore_item.set_attribute_value ( - "accel", - Utils.get_accel_for_action ( - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_RESTORE_PROJECT_DOCS, - "" - ) - ) - ); - - var direct_actions_section = new GLib.Menu (); - if (n_restorable > 0) { - direct_actions_section.append_item (restore_item); - } + var direct_actions_section = new GLib.Menu (); + if (n_restorable > 0) { + direct_actions_section.append_item (restore_item); + } - if (n_open > 0) { - direct_actions_section.append_item (hide_item); - direct_actions_section.append_item (close_item); - } + if (n_open > 0) { + direct_actions_section.append_item (hide_item); + direct_actions_section.append_item (close_item); + } - var search_item = new GLib.MenuItem ( - _("Find in Project…"), + var search_item = new GLib.MenuItem ( + _("Find in Project…"), + GLib.Action.print_detailed_name ( + MainWindow.ACTION_PREFIX + MainWindow.ACTION_FIND_GLOBAL, + new Variant.string (file.file.get_path ()) + ) + ); + + search_item.set_attribute_value ( + "accel", + Utils.get_accel_for_action ( GLib.Action.print_detailed_name ( MainWindow.ACTION_PREFIX + MainWindow.ACTION_FIND_GLOBAL, - new Variant.string (file.file.get_path ()) + "" ) - ); + ) + ); - search_item.set_attribute_value ( - "accel", - Utils.get_accel_for_action ( - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_FIND_GLOBAL, - "" - ) - ) - ); + var search_actions_section = new GLib.Menu (); + search_actions_section.append_item (search_item); - var search_actions_section = new GLib.Menu (); - search_actions_section.append_item (search_item); + var menu_model = new GLib.Menu (); + menu_model.append_section (null, external_actions_section); + menu_model.append_section (null, folder_actions_section); + menu_model.append_section (null, close_actions_section); + menu_model.append_section (null, direct_actions_section); + menu_model.append_section (null, search_actions_section); - var menu_model = new GLib.Menu (); - menu_model.append_section (null, external_actions_section); - menu_model.append_section (null, folder_actions_section); - menu_model.append_section (null, close_actions_section); - menu_model.append_section (null, direct_actions_section); - menu_model.append_section (null, search_actions_section); + return menu_model; + } - return menu_model; + public void update_item_status (FolderItem? start_folder) { + if (monitored_repo == null) { + debug ("Ignore non-git folders"); + return; } - - public void update_item_status (FolderItem? start_folder) { - if (monitored_repo == null) { - debug ("Ignore non-git folders"); - return; + bool is_new = false; + string start_path = start_folder != null ? start_folder.path : ""; + visible_item_list.@foreach ((visible_item) => { + if (start_path.has_prefix (visible_item.rel_path)) { + return; //Only need to update status for start_folder and its children } - bool is_new = false; - string start_path = start_folder != null ? start_folder.path : ""; - visible_item_list.@foreach ((visible_item) => { - if (start_path.has_prefix (visible_item.rel_path)) { - return; //Only need to update status for start_folder and its children - } - - var item = visible_item.item; - item.secondary_icon = null; - monitored_repo.non_current_entries.@foreach ((entry) => { - // Match non_current_path with parent folder as well as itself - var match = entry.key.has_prefix (visible_item.rel_path); - if (match) { - is_new = (entry.@value & (Ggit.StatusFlags.WORKING_TREE_NEW | Ggit.StatusFlags.INDEX_NEW)) > 0; - // Only mark folders new if only contains new items otherwise mark modified - if (item is FolderItem && - is_new && item.secondary_icon == null) { - - item.secondary_icon = added_icon; - item.secondary_icon_tooltip = _("New"); - return true; // scan all children - } - if (!(item is FolderItem) || !item.is_expanded) { //No need to show status when children shown - item.secondary_icon = is_new ? added_icon : modified_icon; - item.secondary_icon_tooltip = is_new ? _("New") : _("Modified"); - } - return false; - } else { - return true; + var item = visible_item.item; + item.secondary_icon = null; + monitored_repo.non_current_entries.@foreach ((entry) => { + // Match non_current_path with parent folder as well as itself + var match = entry.key.has_prefix (visible_item.rel_path); + if (match) { + is_new = (entry.@value & (Ggit.StatusFlags.WORKING_TREE_NEW | Ggit.StatusFlags.INDEX_NEW)) > 0; + // Only mark folders new if only contains new items otherwise mark modified + if (item is FolderItem && + is_new && item.secondary_icon == null) { + + item.secondary_icon = added_icon; + item.secondary_icon_tooltip = _("New"); + return true; // scan all children } - }); - }); - } - - public bool contains_file (GLib.File descendant) { - return file.file.get_relative_path (descendant) != null; - } - private void deprioritize_git_ignored () requires (monitored_repo != null) { - visible_item_list.@foreach ((visible_item) => { - var item = visible_item.item; - try { - if (monitored_repo.path_is_ignored (visible_item.rel_path)) { - item.text = Markup.printf_escaped ("%s", item.name); - } else { - item.text = item.name; + if (!(item is FolderItem) || !item.is_expanded) { //No need to show status when children shown + item.secondary_icon = is_new ? added_icon : modified_icon; + item.secondary_icon_tooltip = is_new ? _("New") : _("Modified"); } - } catch (Error e) { - warning ("An error occurred while checking if item '%s' is git-ignored: %s", item.name, e.message); + return false; + } else { + return true; } }); - } + }); + } - public bool checkout_branch_ref (Ggit.Ref branch_ref) { - if (branch_ref.is_branch ()) { - try { - var branch_name = ((Ggit.Branch)branch_ref).get_name (); - if (branch_name != null) { - return monitored_repo.change_local_branch (branch_name); - } else { - return false; - } - } catch (Error e) { - warning ("Failed to get branch name from ref. %s", e.message); - return false; - } - } else { - var target_shorthand = branch_ref.get_shorthand (); - if (target_shorthand != null) { - return monitored_repo.checkout_remote_branch (target_shorthand); + public bool contains_file (GLib.File descendant) { + return file.file.get_relative_path (descendant) != null; + } + + private void deprioritize_git_ignored () requires (monitored_repo != null) { + visible_item_list.@foreach ((visible_item) => { + var item = visible_item.item; + try { + if (monitored_repo.path_is_ignored (visible_item.rel_path)) { + item.text = Markup.printf_escaped ("%s", item.name); } else { - return false; + item.text = item.name; } + } catch (Error e) { + warning ("An error occurred while checking if item '%s' is git-ignored: %s", item.name, e.message); } - } + }); + } - public void new_branch (string branch_name) { + public bool checkout_branch_ref (Ggit.Ref branch_ref) { + if (branch_ref.is_branch ()) { try { - if (monitored_repo.head_is_branch) { - monitored_repo.create_new_branch (branch_name); + var branch_name = ((Ggit.Branch)branch_ref).get_name (); + if (branch_name != null) { + return monitored_repo.change_local_branch (branch_name); } else { - throw new IOError.NOT_FOUND ("Cannot create a new branch when head is detached"); + return false; } } catch (Error e) { - var dialog = new Granite.MessageDialog ( - _("Error while creating new branch: “%s”").printf (branch_name), - e.message, - new ThemedIcon ("git"), - Gtk.ButtonsType.CLOSE - ) { - badge_icon = new ThemedIcon ("dialog-error"), - modal = true - }; - dialog.transient_for = (Gtk.Window)(view.get_root ()); - dialog.response.connect (() => { - dialog.destroy (); - }); - dialog.show (); + warning ("Failed to get branch name from ref. %s", e.message); + return false; + } + } else { + var target_shorthand = branch_ref.get_shorthand (); + if (target_shorthand != null) { + return monitored_repo.checkout_remote_branch (target_shorthand); + } else { + return false; } } + } - public bool is_recent_ref (Ggit.Ref bref) { - return monitored_repo.is_recent_ref (bref); + public void new_branch (string branch_name) { + try { + if (monitored_repo.head_is_branch) { + monitored_repo.create_new_branch (branch_name); + } else { + throw new IOError.NOT_FOUND ("Cannot create a new branch when head is detached"); + } + } catch (Error e) { + var dialog = new Granite.MessageDialog ( + _("Error while creating new branch: “%s”").printf (branch_name), + e.message, + new ThemedIcon ("git"), + Gtk.ButtonsType.CLOSE + ) { + badge_icon = new ThemedIcon ("dialog-error"), + modal = true + }; + dialog.transient_for = (Gtk.Window)(view.get_root ()); + dialog.response.connect (() => { + dialog.destroy (); + }); + dialog.show (); } + } - public Gee.ArrayList get_all_branch_refs () requires (is_git_repo) { - return monitored_repo.get_all_branch_refs (); - } + public bool is_recent_ref (Ggit.Ref bref) { + return monitored_repo.is_recent_ref (bref); + } - public bool has_local_branch_name (string name) { - return is_git_repo ? monitored_repo.has_local_branch_name (name) : false; - } + public Gee.ArrayList get_all_branch_refs () requires (is_git_repo) { + return monitored_repo.get_all_branch_refs (); + } - public bool has_remote_branch_name (string name) { - return is_git_repo ? monitored_repo.has_remote_branch_name (name) : false; - } + public bool has_local_branch_name (string name) { + return is_git_repo ? monitored_repo.has_local_branch_name (name) : false; + } - public bool has_branch_name (string name, out bool found_is_remote) { - found_is_remote = false; - // var is_remote = false; - var found = false; - if (!has_local_branch_name (name)) { - found = has_remote_branch_name (name); - found_is_remote = found; - } else { - found = true; - } + public bool has_remote_branch_name (string name) { + return is_git_repo ? monitored_repo.has_remote_branch_name (name) : false; + } - return found; + public bool has_branch_name (string name, out bool found_is_remote) { + found_is_remote = false; + // var is_remote = false; + var found = false; + if (!has_local_branch_name (name)) { + found = has_remote_branch_name (name); + found_is_remote = found; + } else { + found = true; } - public string get_current_branch_name () { - return is_git_repo ? monitored_repo.branch_name : ""; + return found; + } + + public string get_current_branch_name () { + return is_git_repo ? monitored_repo.branch_name : ""; + } + + public bool is_valid_new_branch_name (string new_name) { + return is_git_repo ? monitored_repo.is_valid_new_local_branch_name (new_name) : false; + } + + // The parameter "is_explicit" indicates whether a global search was requested + // via a context menu on an explicitly chosen folder, in which case everything in that + // folder will be searched, or whether the hot-key was used in which case the search will + // take place on the active project and will omit certain folders + public async void global_search ( + GLib.File start_folder = this.file.file, + string? term = null, + bool is_explicit = false + ) { + /* For now set all options to the most inclusive (except case). + * The ability to set these in the dialog (or by parameter) may be added later. */ + string? search_term = null; + bool search_tracked_only = false; + bool recurse_subfolders = true; + bool check_is_text = true; + string[] path_spec = {"*.*"}; + bool modified_only = false; + bool case_sensitive = false; + Regex? pattern = null; + + var wholeword_search = Scratch.settings.get_boolean ("wholeword-search"); + var case_mode = (Scratch.CaseSensitiveMode)(Scratch.settings.get_enum ("case-sensitive-search")); + var use_regex = Scratch.settings.get_boolean ("regex-search"); + + switch (case_mode) { + case NEVER: + case_sensitive = false; + break; + case MIXED: + case_sensitive = (term != term.ascii_up () && term != term.ascii_down ()); + break; + case ALWAYS: + case_sensitive = true; + break; + default: + assert_not_reached (); } - public bool is_valid_new_branch_name (string new_name) { - return is_git_repo ? monitored_repo.is_valid_new_local_branch_name (new_name) : false; + var folder_name = start_folder.get_basename (); + if (this.file.file.equal (start_folder)) { + folder_name = name; } - // The parameter "is_explicit" indicates whether a global search was requested - // via a context menu on an explicitly chosen folder, in which case everything in that - // folder will be searched, or whether the hot-key was used in which case the search will - // take place on the active project and will omit certain folders - public async void global_search ( - GLib.File start_folder = this.file.file, - string? term = null, - bool is_explicit = false + var dialog = new Scratch.Dialogs.GlobalSearchDialog ( + folder_name, + monitored_repo != null && monitored_repo.git_repo != null, + case_sensitive, + wholeword_search, + use_regex ) { - /* For now set all options to the most inclusive (except case). - * The ability to set these in the dialog (or by parameter) may be added later. */ - string? search_term = null; - bool search_tracked_only = false; - bool recurse_subfolders = true; - bool check_is_text = true; - string[] path_spec = {"*.*"}; - bool modified_only = false; - bool case_sensitive = false; - Regex? pattern = null; - - var wholeword_search = Scratch.settings.get_boolean ("wholeword-search"); - var case_mode = (CaseSensitiveMode)(Scratch.settings.get_enum ("case-sensitive-search")); - var use_regex = Scratch.settings.get_boolean ("regex-search"); - - switch (case_mode) { - case NEVER: - case_sensitive = false; - break; - case MIXED: - case_sensitive = (term != term.ascii_up () && term != term.ascii_down ()); - break; - case ALWAYS: - case_sensitive = true; + search_term = term + }; + + dialog.response.connect ((response) => { + switch (response) { + case Gtk.ResponseType.ACCEPT: + search_term = dialog.search_term; break; - default: - assert_not_reached (); - } - var folder_name = start_folder.get_basename (); - if (this.file.file.equal (start_folder)) { - folder_name = name; + default: + term = null; + break; } - var dialog = new Scratch.Dialogs.GlobalSearchDialog ( - folder_name, - monitored_repo != null && monitored_repo.git_repo != null, - case_sensitive, - wholeword_search, - use_regex - ) { - search_term = term - }; - - dialog.response.connect ((response) => { - switch (response) { - case Gtk.ResponseType.ACCEPT: - search_term = dialog.search_term; - break; - - default: - term = null; - break; + dialog.destroy (); + global_search.callback (); + }); + + dialog.show (); + yield; + + if (search_term != null) { + // Remove results of previous search before attempting a new one + remove_all_badges (); + // Collapse everything + collapse_all (true, true); + + /* Put search term in search bar to help user locate the position of the matches in each doc */ + var search_variant = new Variant.string (search_term); + var app = (Gtk.Application)GLib.Application.get_default (); + var win = (Scratch.MainWindow)(app.get_active_window ()); + win.actions.lookup_action ("action-find").activate (search_variant); + + if (!use_regex) { + search_term = Regex.escape_string (search_term); + if (wholeword_search) { + search_term = "\\b%s\\b".printf (search_term); } + } // else use search_term as is - TODO do we need to escape it? - dialog.destroy (); - global_search.callback (); - }); - - dialog.show (); - yield; - - if (search_term != null) { - // Remove results of previous search before attempting a new one - remove_all_badges (); - // Collapse everything - collapse_all (true, true); - - /* Put search term in search bar to help user locate the position of the matches in each doc */ - var search_variant = new Variant.string (search_term); - var app = (Gtk.Application)GLib.Application.get_default (); - var win = (Scratch.MainWindow)(app.get_active_window ()); - win.actions.lookup_action ("action-find").activate (search_variant); - - if (!use_regex) { - search_term = Regex.escape_string (search_term); - if (wholeword_search) { - search_term = "\\b%s\\b".printf (search_term); - } - } // else use search_term as is - TODO do we need to escape it? - - try { - var flags = RegexCompileFlags.MULTILINE; - if (!case_sensitive) { - flags |= RegexCompileFlags.CASELESS; - } - - pattern = new Regex (search_term, flags); - } catch (Error e) { - critical ("Error creating regex from '%s': %s", search_term, e.message); - return; + try { + var flags = RegexCompileFlags.MULTILINE; + if (!case_sensitive) { + flags |= RegexCompileFlags.CASELESS; } - } else { + + pattern = new Regex (search_term, flags); + } catch (Error e) { + critical ("Error creating regex from '%s': %s", search_term, e.message); return; } + } else { + return; + } - var status_scope = Ggit.StatusOption.DEFAULT; - if (!modified_only) { - status_scope |= Ggit.StatusOption.INCLUDE_UNMODIFIED; - } - if (!search_tracked_only) { - status_scope |= Ggit.StatusOption.INCLUDE_UNTRACKED; - } - var status_options = new Ggit.StatusOptions ( - status_scope, - Ggit.StatusShow.WORKDIR_ONLY, - path_spec - ); + var status_scope = Ggit.StatusOption.DEFAULT; + if (!modified_only) { + status_scope |= Ggit.StatusOption.INCLUDE_UNMODIFIED; + } + if (!search_tracked_only) { + status_scope |= Ggit.StatusOption.INCLUDE_UNTRACKED; + } + var status_options = new Ggit.StatusOptions ( + status_scope, + Ggit.StatusShow.WORKDIR_ONLY, + path_spec + ); - if (monitored_repo != null && !is_explicit) { - try { - monitored_repo.git_repo.file_status_foreach (status_options, (rel_path, status) => { - var target = file.file.resolve_relative_path (rel_path); - if (check_is_text && rel_path.has_prefix ("po/")) { // Ignore translation files - return 0; - } + if (monitored_repo != null && !is_explicit) { + try { + monitored_repo.git_repo.file_status_foreach (status_options, (rel_path, status) => { + var target = file.file.resolve_relative_path (rel_path); + if (check_is_text && rel_path.has_prefix ("po/")) { // Ignore translation files + return 0; + } - if ((recurse_subfolders && start_folder.get_relative_path (target) != null) || - start_folder.equal (target.get_parent ())) { + if ((recurse_subfolders && start_folder.get_relative_path (target) != null) || + start_folder.equal (target.get_parent ())) { - perform_match (target, pattern, check_is_text); - } + perform_match (target, pattern, check_is_text); + } - return 0; //TODO Allow cancelling? - }); - } catch (Error err) { - warning ("Error getting file status: %s", err.message); - } - } else { - search_folder_children (start_folder, pattern, recurse_subfolders); + return 0; //TODO Allow cancelling? + }); + } catch (Error err) { + warning ("Error getting file status: %s", err.message); } - - return; + } else { + search_folder_children (start_folder, pattern, recurse_subfolders); } - private void search_folder_children (GLib.File start_folder, Regex pattern, bool recurse_subfolders) { - try { - var enumerator = start_folder.enumerate_children ( - FileAttribute.STANDARD_CONTENT_TYPE + "," + FileAttribute.STANDARD_TYPE, - FileQueryInfoFlags.NOFOLLOW_SYMLINKS, - null - ); + return; + } - unowned FileInfo info = null; - unowned GLib.File child = null; - while (enumerator.iterate (out info, out child, null) && info != null) { - if (info != null && info.has_attribute (FileAttribute.STANDARD_TYPE)) { - if (info.get_file_type () == FileType.DIRECTORY) { - if (recurse_subfolders) { - search_folder_children (child, pattern, recurse_subfolders); - } - } else { - perform_match (child, pattern, true, info); + private void search_folder_children (GLib.File start_folder, Regex pattern, bool recurse_subfolders) { + try { + var enumerator = start_folder.enumerate_children ( + FileAttribute.STANDARD_CONTENT_TYPE + "," + FileAttribute.STANDARD_TYPE, + FileQueryInfoFlags.NOFOLLOW_SYMLINKS, + null + ); + + unowned FileInfo info = null; + unowned GLib.File child = null; + while (enumerator.iterate (out info, out child, null) && info != null) { + if (info != null && info.has_attribute (FileAttribute.STANDARD_TYPE)) { + if (info.get_file_type () == FileType.DIRECTORY) { + if (recurse_subfolders) { + search_folder_children (child, pattern, recurse_subfolders); } + } else { + perform_match (child, pattern, true, info); } } - } catch (Error enumerate_error) { - warning ("Error enumerating children of %s: %s", start_folder.get_path (), enumerate_error.message); } + } catch (Error enumerate_error) { + warning ("Error enumerating children of %s: %s", start_folder.get_path (), enumerate_error.message); } + } - private void perform_match (GLib.File target, - Regex search_regex, - bool check_is_text = false, - FileInfo? target_info = null) { - string contents; - string target_path = target.get_path (); - if (check_is_text) { - FileInfo? info = null; - if (target_info == null) { - try { - info = target.query_info ( - FileAttribute.STANDARD_CONTENT_TYPE, - FileQueryInfoFlags.NOFOLLOW_SYMLINKS, - null - ); - } catch (Error query_error) { - warning ( - "Error getting file info for %s: %s. Ignoring.", target.get_path (), query_error.message - ); - } - } else { - info = target_info; - } - - if (info == null) { - return; + private void perform_match (GLib.File target, + Regex search_regex, + bool check_is_text = false, + FileInfo? target_info = null) { + string contents; + string target_path = target.get_path (); + if (check_is_text) { + FileInfo? info = null; + if (target_info == null) { + try { + info = target.query_info ( + FileAttribute.STANDARD_CONTENT_TYPE, + FileQueryInfoFlags.NOFOLLOW_SYMLINKS, + null + ); + } catch (Error query_error) { + warning ( + "Error getting file info for %s: %s. Ignoring.", target.get_path (), query_error.message + ); } + } else { + info = target_info; + } - var type = info.get_content_type (); - if (!ContentType.is_mime_type (type, "text/*") || - ContentType.is_mime_type (type, "image/*")) { //Do not search svg images - return; - } + if (info == null) { + return; } - try { - FileUtils.get_contents (target_path, out contents); - } catch (Error e) { - warning ("error getting contents: %s", e.message); + var type = info.get_content_type (); + if (!ContentType.is_mime_type (type, "text/*") || + ContentType.is_mime_type (type, "image/*")) { //Do not search svg images return; } + } - MatchInfo? match_info = null; - int match_count = 0; - try { - for (search_regex.match (contents, RegexMatchFlags.NOTEMPTY, out match_info); - match_info.matches (); - match_info.next ()) { + try { + FileUtils.get_contents (target_path, out contents); + } catch (Error e) { + warning ("error getting contents: %s", e.message); + return; + } - match_count++; - } - } catch (RegexError next_error) { - critical ("Error getting next match: %s", next_error.message); - } + MatchInfo? match_info = null; + int match_count = 0; + try { + for (search_regex.match (contents, RegexMatchFlags.NOTEMPTY, out match_info); + match_info.matches (); + match_info.next ()) { - if (match_count > 0) { - var item = view.expand_to_path (target_path); - if (item != null) { - item.badge = match_count.to_string (); - } + match_count++; } - - return; + } catch (RegexError next_error) { + critical ("Error getting next match: %s", next_error.message); } - public void refresh_diff (ref Gee.HashMap line_status_map, string doc_path) { - monitored_repo.refresh_diff (doc_path, ref line_status_map); + if (match_count > 0) { + var item = view.expand_to_path (target_path); + if (item != null) { + item.badge = match_count.to_string (); + } } + + return; + } + + public void refresh_diff (ref Gee.HashMap line_status_map, string doc_path) { + monitored_repo.refresh_diff (doc_path, ref line_status_map); } } + diff --git a/src/FolderManager/ProjectList.vala b/src/FolderManager/ProjectList.vala new file mode 100644 index 0000000000..a9659bc591 --- /dev/null +++ b/src/FolderManager/ProjectList.vala @@ -0,0 +1,795 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2026 elementary, Inc. + */ + +//TODO Can we just use ProjectFolderItems direct? +public class Code.ProjectListItem : Object { + public ProjectFolderItem project_folder { get; construct; } + public string path { owned get { return project_folder.path; }} + public GLib.File gfile { get { return project_folder.file.file; }} + public Code.FolderTree folder_tree { get; set construct; } + + private Scratch.Services.GitManager git_manager; + + private bool is_expanded { get; set; } + private bool is_active_project { get { return git_manager.active_project_path == path; } } + + public ProjectListItem (ProjectFolderItem project) { + Object ( + project_folder: project + ); + } + + construct { + folder_tree = new FolderTree (path); + git_manager = Scratch.Services.GitManager.get_instance (); + } + + public bool is_or_contains_file (GLib.File gfile) { + return path == gfile.get_path () || folder_tree.contains_file (gfile); + } + + public void collapse_all () { + // folder_tree.collapse_all (); + is_expanded = false; + } + + public void expand_all () { + // folder_tree.expand_all (); + is_expanded = true; + } + + public void closed () { + // folder_tree.clear (); + project_folder.closed (); + } +} + +/* ProjectList is a flat list of FolderTrees each representing a single project */ +public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { + public const string ACTION_GROUP = "project-list"; + public const string ACTION_PREFIX = ACTION_GROUP + "."; + public const string ACTION_CLOSE_FOLDER = "close-folder"; + public const string ACTION_CLOSE_OTHER_FOLDERS = "close-other-folders"; + public SimpleActionGroup actions { get; private set; } + public bool is_empty { get { return list_store.n_root_items () == 0; } } + + private Gtk.ScrolledWindow scrolled_window; + private Gtk.ListView list_view; + private GLib.ListStore list_store; + private Gtk.NoSelection selection_model; + + private GLib.Settings settings; + private Scratch.Services.PluginsManager plugins; + + private const ActionEntry[] ACTION_ENTRIES = { + { ACTION_CLOSE_FOLDER, action_close_folder, "s"}, + { ACTION_CLOSE_OTHER_FOLDERS, action_close_other_folders, "s"} + }; + + public ProjectList (Scratch.Services.PluginsManager plugins_manager) { + plugins = plugins_manager; + } + + construct { + settings = new GLib.Settings ("io.elementary.code.folder-manager"); + list_store = new ListStore (typeof (ProjectListItem)); + selection_model = new Gtk.NoSelection (); + var list_factory = new Gtk.SignalListItemFactory (); + list_view = new Gtk.ListView (selection_model, list_factory) { + header_factory = tree_header_factory + }; + + actions = new SimpleActionGroup (); + actions.add_action_entries (ACTION_ENTRIES, this); + insert_action_group (ACTION_GROUP, actions); + + Scratch.saved_state.changed["order-folders"].connect (() => { + order_folders (); + }); + + child = list_store; + + list_factory.setup.connect ((obj) => { + var listitem = (Gtk.ListItem) obj; + create_listitem_child (listitem); + // By default just create a use a label (not expandable) + }); + list_factory.teardown.connect ((obj) => { + var listitem = (Gtk.ListItem) obj; + teardown_listitem_child (listitem); + }); + list_factory.bind.connect ((obj) => { + var listitem = (Gtk.ListItem) obj; + var data = (Code.ProjectListItem) (listitem.item); + bind_data_to_row (data, listitem); + }); + list_factory.unbind.connect ((obj) => { + var listitem = (Gtk.ListItem) obj; + var data = (Code.ProjectListItem) (listitem.item); + unbind_data_from_row (data, listitem); + }); + } + + public async void restore_saved_state () { + foreach (unowned string path in settings.get_strv ("opened-folders")) { + yield add_new_project_folder (path, false, true); + } + } + + // If project exists expand it, otherwise create + public void open_project_folder (File folder) { + if (is_open_project (folder.path)) { + return; + } + + add_new_project_folder.begin (folder, true, false); + } + + public void collapse_all () { + //TODO Write iterate children + iterate_children ((listitem) => { + listitem.collapse_all (); + return Code.TreeList.ITERATE_CONTINUE; + }); + } + + public void order_folders () { + if (!Scratch.saved_state.get_boolean ("order-folders")) { + return; + } + + //TODO Sorting + } + + public void select_path (string path) { + //TODO write find_path + // find_path (null, path); + } + + public void unselect_file (GLib.File file) { // Needed? + //TODO Complete this + } + + public void unselect_all () { // Needed?? + //TODO Call unselect all on all children. + // tree_list.unselect_all (); + } + + public void collapse_other_projects (string active_project_path) { + iterate_children ((listitem) => { + if (listitem.path != path) { + listitem.collapse_all (); + activate_action ( + MainWindow.ACTION_PREFIX + MainWindow.ACTION_HIDE_PROJECT_DOCS, + "s", + listitem.path + ); + } else if (listitem.path == path) { + listitem.expand_all (); + activate_action ( + MainWindow.ACTION_PREFIX + MainWindow.ACTION_RESTORE_PROJECT_DOCS, + "s", + listitem.path + ); + } + + return Code.TreeList.ITERATE_CONTINUE; + }); + } + + public void branch_actions (string path) { + // // Must only carry out branch actions on active project so switch if necessary. + // //TODO Warn before switching active project? + // var active_project = set_active_project (path); + // if (active_project == null || !active_project.is_git_repo) { + // Gdk.Display.get_default ().beep (); + // return; + // } + + // var dialog = new Dialogs.BranchActionDialog (active_project); + // dialog.response.connect ((res) => { + // if (res == Gtk.ResponseType.APPLY) { + // perform_branch_action (dialog); + // } + + // dialog.destroy (); + // }); + + // dialog.present (); + } + + private void perform_branch_action ( + Scratch.Dialogs.BranchActionDialog dialog + ) { + // switch (dialog.action) { + // case CHECKOUT: + // dialog.project.checkout_branch_ref (dialog.branch_ref); + // break; + // case COMMIT: + // break; + // case PUSH: + // break; + // case PULL: + // break; + // case MERGE: + // break; + // case DELETE: + // break; + // case CREATE: + // dialog.project.new_branch (dialog.new_branch_name); + // break; + // default: + // assert_not_reached (); + // } + } + + // Call to find top level foldertree and then call find path on that. + private FolderManagerItem? find_path ( + string path, // File path to search fod + bool expand = false // Whether to expsnd to show found item + // GLib.File? target_file = null // Alternatively find this file + ) { + + // var target = target_file ?? GLib.File.new_for_path (path); + FolderManagerItem? matched_item = null; + + iterate_children ((listitem) => { + if (listitem.path == path) { + matched_item = listitem.project_folder; + return Code.TreeList.ITERATE_STOP; + } else if (path.has_prefix (listitem.path)) { //TODO Ensure paths are compatible + matched_item = listitem.tree_root_item.find_path (path); + return Code.TreeList.ITERATE_STOP; + } + + return Code.TreeList.ITERATE_CONTINUE; + }); + + + return matched_item; + // if (listitem is FolderItem) { + // var folder = item as FolderItem; + // var folder_root = folder.file.file; + // if (folder_root.get_relative_path (target) == null) { + // return Code.TreeList.ITERATE_CONTINUE; + // } + + // if (!folder.is_expanded) { + // if (expand) { + // folder.load_children (); //Synchronous + // folder.is_expanded = true; + // } else { + // return Code.TreeList.ITERATE_CONTINUE; + // } + // } + + // var recurse_item = find_path (folder, path, expand, target); + // if (recurse_item != null) { + // matched_item = recurse_item; + // return Code.TreeList.ITERATE_STOP; + // } + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + + } + + + + public ProjectListItem? get_project_for_file (GLib.File file) { + ProjectListItem? matched_project_item = null; + iterate_children ((listitem) => { + if (listitem.is_or_contains_file (file)) { + matched_project = listitem; + return Code.TreeList.ITERATE_STOP; + } + + return Code.TreeList.ITERATE_CONTINUE; + }); + + return matched_project; + } + + public Code.TreeListItem? expand_to_path (string path) { + return find_path (null, path, true); + } + + /* Do global search on project containing the file path supplied in parameter */ + public void search_global (string path, string? term = null) { + var item_for_path = (FolderManagerItem?)(expand_to_path (path)); + if (item_for_path != null) { + var search_root = item_for_path.get_root_folder (); + if (search_root is ProjectFolderItem) { + GLib.File start_folder = (item_for_path is FolderItem) + ? item_for_path.file.file + : search_root.file.file; + + bool is_explicit = !(item_for_path is ProjectFolderItem); + search_root.global_search.begin (start_folder, term, is_explicit); + } + } + } + + public void clear_badges () { + tree_list.iterate_children (null, (child) => { + if (child is ProjectFolderItem) { + ((FolderItem)child).remove_all_badges (); + } + + return Code.TreeList.ITERATE_CONTINUE; + }); + } + + public void folder_item_update_hook (GLib.File source, GLib.File? dest, GLib.FileMonitorEvent event) { + 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)) { + // ulong once = 0; + // once = selected.edited.connect ((new_name) => { + // selected.disconnect (once); + // var new_path = Path.get_dirname (path) + Path.DIR_SEPARATOR_S + new_name; + // activate_action ( + // MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_TAB, + // "s", + // path + // ); + + // // RecentManager requires valid URI + // var new_uri = "file://" + new_path; // Code only edits local files + // Gtk.RecentManager.get_default ().add_item (new_uri); + + // activate (new_path); + // }); + // } + + // // Handle cancelled rename (which does not produce signal) + // Timeout.add (200, () => { + // if (this.editing) { + // return Source.CONTINUE; + // } else { + // // Avoid selected but unopened item if rename cancelled (they would not open if clicked on) + // this.unselect_all (); + // return Source.REMOVE; + // } + // }); + } + + private void rename_folder (string path) { + // var folder_to_rename = find_path (null, path) as FolderItem; + // if (folder_to_rename == null) { + // critical ("Could not find folder from given path to rename: %s", path); + // return; + // } + + // folder_to_rename.selectable = true; + // if (start_editing_item (folder_to_rename)) { + // // Need to poll view as no signal emited when editing cancelled and need to set + // // selectable to false anyway. + // Timeout.add (200, () => { + // if (editing) { + // return Source.CONTINUE; + // } else { + // unselect_all (); + // // Must do this *after* unselecting all else sourcelist breaks + // folder_to_rename.selectable = false; + // } + + // return Source.REMOVE; + // }); + // } else { + // critical ("Could not rename %s", path); + // folder_to_rename.selectable = false; + // } + } + + private void rename_items_with_same_name (FolderManagerItem item) { + // string item_name = item.file.name; + // tree_list.iterate_children (null, (child) => { + // string new_other_item_name, new_item_name; + // var other_item = (ProjectFolderItem) child; + + // if (Utils.find_unique_path ( + // item.file.file, + // other_item.file.file, + // out new_item_name, + // out new_other_item_name + // ) + // ) { + // if (item_name.length < new_item_name.length) { + // item_name = new_item_name; + // } + + // if (other_item.name.length < new_other_item_name.length) { + // other_item.name = new_other_item_name; + // } + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + + // item.name = item_name; + } + + // private void add_new_folder (SimpleAction action, Variant? param) { + // // Using "path" of parent folder from params, call `on_add_new (true)` on `FolderItem` + // var path = param.get_string (); + + // if (path == null || path == "") { + // return; + // } + + // var folder = find_path (null, path); + // if (folder == null) { + // return; + // } + + + // folder.on_add_new (true); + // } + + // 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 != null ? param.get_string () : null; + + // if (path == null || path == "") { + // critical ("No path"); + // return; + // } + + // var folder = find_path (null, path) as FolderItem; + // if (folder == null) { + // return; + // } + + // 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 (null, 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]); + // } + + // private void action_show_app_chooser (SimpleAction action, Variant? param) { + // var path = param.get_string (); + + // if (path == null || path == "") { + // return; + // } + + // var file = GLib.File.new_for_path (path); + // var dialog = new Gtk.AppChooserDialog (new Gtk.Window (), Gtk.DialogFlags.MODAL, file); + // dialog.deletable = false; + + // dialog.response.connect ((res) => { + // if (res == Gtk.ResponseType.OK) { + // var app_info = dialog.get_app_info (); + // if (app_info != null) { + // Utils.launch_app_with_file (app_info.get_id (), path); + // } + // } + + // dialog.destroy (); + // }); + + // dialog.show (); + // } + + // private void action_execute_contract_with_file_path (SimpleAction action, Variant? param) { + // var params = param.get_strv (); + // var path = params[0]; + // if (path == null || path == "") { + // return; + // } + + // var contract_name = params[1]; + // if (contract_name == null || contract_name == "") { + // return; + // } + + // Utils.execute_contract_with_file_path (path, contract_name); + // } + + // private void action_rename_file (SimpleAction action, Variant? param) { + // var path = param.get_string (); + + // if (path == null || path == "") { + // return; + // } + + // rename_file (path); + // } + + // private void action_rename_folder (SimpleAction action, Variant? param) { + // var path = param.get_string (); + + // if (path == null || path == "") { + // return; + // } + + // rename_folder (path); + // } + + + // private void action_delete (SimpleAction action, Variant? param) { + // var path = param.get_string (); + + // if (path == null || path == "") { + // return; + // } + + // var item = find_path (null, path); + // if (item != null) { + // var item_to_delete = item as Code.FolderManagerItem; + + // // Wait for ProjectFolderItem closed signal handle logic to run before moving item to trash + // if (item_to_delete is Code.ProjectFolderItem) { + // item_to_delete.closed.connect_after (() => { + // item_to_delete.trash (); + // }); + // item_to_delete.closed (); + // return; + // } + + // item_to_delete.trash (); + // } + // } + + // Only call when path is known to be a new project + private async void add_new_project_folder (string path, bool expand, bool restoring = false) { + var folder = new File (path); + if (!folder.is_valid_directory) { + warning ("Cannot open invalid directory."); + return; + } + + var add_file = folder.file; + // Need to deal with case where folder is parent or child of an existing project + var parents = new List (); + var children = new List (); + + iterate_children (null, (listitem) => { + // var item = (ProjectFolderItem) child; + if (add_file.get_relative_path (listitem.gfile) != null) { + debug ("Trying to add parent of existing project"); + children.append (listitem); + } else if (listitem.gfile.get_relative_path (add_file) != null) { + debug ("Trying to add child of existing project"); + parents.append (listitem); + } + + return Code.TreeList.ITERATE_CONTINUE; + }); + + if (parents.length () > 0 || children.length () > 0) { + assert (parents.length () <= 1); + assert (parents.length () == 0 || children.length () == 0); + var dialog = new Scratch.Dialogs.CloseProjectsConfirmationDialog ( + (Scratch.MainWindow) get_root (), + parents.length (), + children.length () + ); + + var close_projects = false; + dialog.response.connect ((res) => { + if (res == Gtk.ResponseType.ACCEPT) { + close_projects = true; + } + + dialog.destroy (); + add_new_project_folder.callback (); + }); + + dialog.show (); + yield; + + if (close_projects) { + foreach (var listitem in parents) { + listitem.closed (); + } + + foreach (var item in children) { + listitem.closed (); + } + } else { + return; + } + } + + // Process any closed signals emitted before proceeding + Idle.add (() => { + var new_project = new ProjectFolderItem (folder, this); // Constructor adds project to GitManager + new_project.project.closed.connect (() => { + activate_action ( + MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_PROJECT_DOCS, + "s", + folder_root.path + ); + + tree_list.remove_root_item (folder_root); + + tree_list.iterate_children (null, (child) => { + var child_folder = (ProjectFolderItem) child; + if (child_folder.name != child_folder.file.name) { + rename_items_with_same_name (child_folder); + } + + return Code.TreeList.ITERATE_CONTINUE; + }); + + Scratch.Services.GitManager.get_instance ().remove_project (folder_root); + write_settings (); + }); + + var new_item = new ProjectListItem (folder); + list_store.append_item (new_item); + if (expand) { + new_item.expand_all (); + } + // rename_items_with_same_name (new_project); //TODO do this later + + // 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) { + write_settings (); + } + + add_new_project_folder.callback (); + return Source.REMOVE; + }); + + yield; + + // order_folders (); //TODO do later + } + + // private bool is_open_project (string path) { + // bool open = false; + // // Only iterate this model + // iterate_children ((listitem) => { + // if (path == listitem.path) { + // open = true; + // return Code.TreeList.ITERATE_STOP; + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + + // return open; + // } + + private void write_settings () { + string[] to_save = {}; + tree_list.iterate_children (null, (item) => { + var saved = false; + var folder_path = ((FolderManagerItem) item).path; + + //Do we need to de-duplicate? Not possible to open a project twice? + foreach (var saved_folder in to_save) { + if (folder_path == saved_folder) { + saved = true; + break; + } + } + + if (!saved) { + to_save += folder_path; + } + + return Code.TreeList.ITERATE_CONTINUE; + }); + + settings.set_strv ("opened-folders", to_save); + } + + private void action_close_folder (SimpleAction action, GLib.Variant? parameter) { + var path = parameter.get_string (); + if (path == null || path == "") { + return; + } + + var project_item = find_path (null, path) as ProjectFolderItem; + if (project_item == null) { + return; + } + + project_item.closed (); + } + + private void action_close_other_folders (SimpleAction action, GLib.Variant? parameter) { + var path = parameter.get_string (); + if (path == null || path == "") { + return; + } + + var folder_root = find_path (null, path) as ProjectFolderItem; + if (folder_root == null) { + return; + } + + List to_remove = null; + tree_list.iterate_children (null, (child) => { + var project_folder_item = (ProjectFolderItem) child; + if (project_folder_item != folder_root) { + activate_action ( + MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_PROJECT_DOCS, + "s", + project_folder_item.path + ); + to_remove.prepend (project_folder_item); + git_manager.remove_project (project_folder_item); + } + + return Code.TreeList.ITERATE_CONTINUE; + }); + + tree_list.remove_root_children (to_remove); + //Make remaining project the active one + set_project_active (path); + } + + private void action_set_active_project (SimpleAction action, GLib.Variant? parameter) { + var path = parameter.get_string (); + if (path == null || path == "") { + return; + } + + set_active_project (path); + } + + private ProjectFolderItem? set_active_project (string path) { + var folder_root = find_path (null, path) as ProjectFolderItem; + if (folder_root == null) { + return null; + } + + git_manager.active_project_path = path; + + write_settings (); + + return folder_root; + } + + private void set_project_active (string path) { + activate_action ( + MainWindow.ACTION_PREFIX + MainWindow.ACTION_SET_ACTIVE_PROJECT, + "s", + path + ); + } + + delegate bool ProjectListIteratorCallback (ProjectListItem item); + private void iterate_children (ProjectListIteratorCallback cb) { + ProjectListItem? item = null; + uint pos = 0; + do { + item = (ProjectListItem?) (list_store.get_object (pos++)); + } while (item != null && cb (item)); + } +} diff --git a/src/MainWindow.vala b/src/MainWindow.vala index e0f8e48ea6..9dc10087c8 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -46,10 +46,10 @@ namespace Scratch { public Scratch.Widgets.SearchBar search_bar; private Code.WelcomeView welcome_view; private Code.Terminal terminal; - private FolderManager.FileView folder_manager_view; + private Code.ProjectList folder_manager_view; private Scratch.Services.DocumentManager document_manager; // Plugins - private Scratch.Services.PluginsManager plugins; + public Scratch.Services.PluginsManager plugins { get; private set;} // Widgets for Plugins public Code.Sidebar sidebar; @@ -463,13 +463,13 @@ namespace Scratch { sidebar = new Code.Sidebar (); - // folder_manager_view = new FolderManager.FileView (plugins); + // folder_manager_view = new Code.FileView (plugins); // sidebar.add_tab (folder_manager_view); // sidebar.add_tab (new Granite.HeaderLabel ("Dummy Sidebar")); // folder_manager_view.file_activate.connect ((file) => { - // // var file = new Scratch.FolderManager.File (a); + // // var file = new Code.File (a); // var doc = new Scratch.Services.Document (actions, file.file); // if (file.is_valid_textfile) { @@ -583,7 +583,7 @@ namespace Scratch { document_view.tab_removed.connect ((doc) => { update_find_actions (); - folder_manager_view.unselect_file (doc.file); + // folder_manager_view.unselect_file (doc.file); }); document_view.document_change.connect ((doc) => { @@ -745,7 +745,7 @@ namespace Scratch { } public void open_folder (File folder) { - var foldermanager_file = new FolderManager.File (folder.get_path ()); + var foldermanager_file = new Code.File (folder.get_path ()); folder_manager_view.open_folder (foldermanager_file); } @@ -753,7 +753,7 @@ namespace Scratch { bool focus = true, int cursor_position = 0) { - // FolderManager.ProjectFolderItem? project = folder_manager_view.get_project_for_file (doc.file); + // Code.ProjectFolderItem? project = folder_manager_view.get_project_for_file (doc.file); // doc.source_view.project = project; yield document_view.open_document (doc, focus, cursor_position); } @@ -1023,7 +1023,7 @@ var index = 0; var obj = files.get_item (index++); while (obj != null) { // var file = (GLib.File) obj; - // var foldermanager_file = new FolderManager.File (file.get_path ()); + // var foldermanager_file = new Code.File (file.get_path ()); // folder_manager_view.open_folder (foldermanager_file); // obj = files.get_item (index++); } @@ -1038,7 +1038,7 @@ var index = 0; if (path == "") { choose_folder (); } else { - // folder_manager_view.open_folder (new FolderManager.File (path)); + // folder_manager_view.open_folder (new Code.File (path)); } } diff --git a/src/Services/GitManager.vala b/src/Services/GitManager.vala index 9827d65832..8f2268558f 100644 --- a/src/Services/GitManager.vala +++ b/src/Services/GitManager.vala @@ -46,17 +46,17 @@ namespace Scratch.Services { construct { // Used to populate the ChooseProject popover in sorted order - project_liststore = new ListStore (typeof (FolderManager.ProjectFolderItem)); + project_liststore = new ListStore (typeof (Code.ProjectFolderItem)); settings.bind ("active-project-path", this, "active-project-path", DEFAULT); } - public MonitoredRepository? add_project (FolderManager.ProjectFolderItem root_folder) { + public MonitoredRepository? add_project (Code.ProjectFolderItem root_folder) { var root_path = root_folder.path; MonitoredRepository? monitored_repo = null; uint position; if (project_liststore.find_with_equal_func ( root_folder, - (a, b) => { return ((FolderManager.Item) a).equal ((FolderManager.Item) b); }, + (a, b) => { return ((Code.FolderManagerItem) a).equal ((Code.FolderManagerItem) b); }, out position )) { @@ -89,13 +89,13 @@ namespace Scratch.Services { } [CCode (instance_pos = -1)] - private int project_sort_func (FolderManager.ProjectFolderItem a, FolderManager.ProjectFolderItem b) { + private int project_sort_func (Code.ProjectFolderItem a, Code.ProjectFolderItem b) { GLib.File file_a = a.file.file; GLib.File file_b = b.file.file; return Path.get_basename (file_a.get_path ()).collate (Path.get_basename (file_b.get_path ())); } - public void remove_project (FolderManager.ProjectFolderItem root_folder) { + public void remove_project (Code.ProjectFolderItem root_folder) { var root_path = root_folder.file.file.get_path (); uint position; diff --git a/src/Services/PluginManager.vala b/src/Services/PluginManager.vala index 3324a47c45..fc8860c3f2 100644 --- a/src/Services/PluginManager.vala +++ b/src/Services/PluginManager.vala @@ -30,17 +30,18 @@ public class Scratch.Services.Interface : GLib.Object { ); } - public Scratch.Services.Document open_file (File file) { - var doc = new Scratch.Services.Document (manager.window.actions, file); - manager.window.open_document.begin (doc); - return doc; - } + // public Scratch.Services.Document open_file (File file) { + // var doc = new Scratch.Services.Document (manager.window.actions, file); + // manager.window.open_document.begin (doc); + // return doc; + // } public void close_document (Scratch.Services.Document doc) { manager.window.close_document (doc); } } +//TODO Make singleton public class Scratch.Services.PluginsManager : GLib.Object { public signal void hook_window (Scratch.MainWindow window); public signal void hook_share_menu (GLib.MenuModel menu); diff --git a/src/SymbolPane/SymbolOutline.vala b/src/SymbolPane/SymbolOutline.vala index 4382538b21..afdd8d581c 100644 --- a/src/SymbolPane/SymbolOutline.vala +++ b/src/SymbolPane/SymbolOutline.vala @@ -124,11 +124,11 @@ public class Scratch.Services.SymbolOutline : Gtk.Box { checks = new Gee.HashMap (); // store = new Code.TreeList (); - root = new Code.TreeListItem () { - text = _("Symbols") - }; + // root = new Code.TreeListItem () { + // text = _("Symbols") + // }; - tree_list.add_root_item (root); + // tree_list.add_root_item (root); search_entry = new Gtk.SearchEntry () { placeholder_text = _("Find Symbol"), diff --git a/src/SymbolPane/Vala/ValaSymbolItem.vala b/src/SymbolPane/Vala/ValaSymbolItem.vala index 1bea31a5e5..62b4008222 100644 --- a/src/SymbolPane/Vala/ValaSymbolItem.vala +++ b/src/SymbolPane/Vala/ValaSymbolItem.vala @@ -99,6 +99,8 @@ public class Scratch.Services.ValaSymbolItem : Code.TreeListItem, Scratch.Servic } else { warning (symbol.type_name); } + + warning ("new symbol text %s", text); } ~ValaSymbolItem () { diff --git a/src/SymbolPane/Vala/ValaSymbolOutline.vala b/src/SymbolPane/Vala/ValaSymbolOutline.vala index 76f680442a..f01015880a 100644 --- a/src/SymbolPane/Vala/ValaSymbolOutline.vala +++ b/src/SymbolPane/Vala/ValaSymbolOutline.vala @@ -106,7 +106,7 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline Idle.add (() => { double adjustment_value = tree_list.vadjustment.value; // var root_children = root.children; // Keep reference to children for later destruction - // root.clear (); // This does not destroy children but disconnects signals - avoids terminal warnings + // tree_list.clear (); // This does not destroy children but disconnects signals - avoids terminal warnings // foreach (var child in root_children) { // Destroy items after clearing list to avoid memory leak // destroy_all_children ((Code.TreeListItem)child); // } @@ -125,9 +125,9 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline tree_list.add_root_item (new_root); // } - // root.expand_all (); - // add_tooltips (root); - // vadjustment.set_value (adjustment_value); + tree_list.expand_all (null); + add_tooltips (root); + tree_list.vadjustment.set_value (adjustment_value); return Source.REMOVE; }); } @@ -178,12 +178,13 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline // Called from separate thread private Code.TreeListItem construct_tree (GLib.Cancellable cancellable) { + warning ("construct tree"); var fields = resolver.get_properties_fields (); var symbols = resolver.get_symbols (); // Remove fake fields created by the vala parser. symbols.remove_all (fields); - var new_root = new Code.TreeListItem () { text = _("Symbols") }; + var new_root = new Code.TreeListItem () { text = _("Construct Tree Symbols") }; new_root.tooltip = _("Vala symbols found in %s").printf (doc.file.get_basename ()); foreach (var symbol in symbols) { if (cancellable.is_cancelled ()) @@ -207,18 +208,18 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline Code.TreeListItem given_parent, GLib.Cancellable cancellable ) { - +warning ("construct child"); Code.TreeListItem parent; - if (symbol.scope.parent_scope.owner.name == null) + if (symbol.scope.parent_scope.owner.name == null) { parent = given_parent; - else + } else { parent = find_existing (symbol.scope.parent_scope.owner, given_parent, cancellable); + } if (parent == null) { parent = construct_child (symbol.scope.parent_scope.owner, given_parent, cancellable); } - var tree_child = new ValaSymbolItem ( symbol, "" diff --git a/src/Utils.vala b/src/Utils.vala index 9307da5a23..3a8f5fc2a6 100644 --- a/src/Utils.vala +++ b/src/Utils.vala @@ -211,8 +211,8 @@ namespace Scratch.Utils { ///TRANSLATORS '%s' represents the quoted basename of a uri to be opened with the default app _("Show '%s' with default app").printf (file.get_basename ()), GLib.Action.print_detailed_name ( - Scratch.FolderManager.FileView.ACTION_PREFIX - + Scratch.FolderManager.FileView.ACTION_LAUNCH_APP_WITH_FILE_PATH, + Code.FolderTree.ACTION_PREFIX + + Code.FolderTree.ACTION_LAUNCH_APP_WITH_FILE_PATH, new GLib.Variant.array ( GLib.VariantType.STRING, { file.get_path (), "" } @@ -241,8 +241,8 @@ namespace Scratch.Utils { var menu_item = new MenuItem ( app_info.get_name (), GLib.Action.print_detailed_name ( - Scratch.FolderManager.FileView.ACTION_PREFIX - + Scratch.FolderManager.FileView.ACTION_LAUNCH_APP_WITH_FILE_PATH, + Code.FolderTree.ACTION_PREFIX + + Code.FolderTree.ACTION_LAUNCH_APP_WITH_FILE_PATH, new GLib.Variant.array ( GLib.VariantType.STRING, { file.get_path (), app_id } @@ -291,8 +291,8 @@ namespace Scratch.Utils { var menu_item = new GLib.MenuItem ( contract_name, GLib.Action.print_detailed_name ( - Scratch.FolderManager.FileView.ACTION_PREFIX - + Scratch.FolderManager.FileView.ACTION_EXECUTE_CONTRACT_WITH_FILE_PATH, + Code.FolderTree.ACTION_PREFIX + + Code.FolderTree.ACTION_EXECUTE_CONTRACT_WITH_FILE_PATH, new GLib.Variant.array ( GLib.VariantType.STRING, { file.get_path (), contract_name } diff --git a/src/Widgets/ChooseProjectButton.vala b/src/Widgets/ChooseProjectButton.vala index 70ae55a05e..9a8c85cde3 100644 --- a/src/Widgets/ChooseProjectButton.vala +++ b/src/Widgets/ChooseProjectButton.vala @@ -115,8 +115,8 @@ public class Code.ChooseProjectButton : Gtk.Box { 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); + if (item is Code.ProjectFolderItem) { + var row = create_project_row ((Code.ProjectFolderItem)item); project_listbox.insert (row, index); } } @@ -125,8 +125,8 @@ public class Code.ChooseProjectButton : Gtk.Box { project_listbox.remove_all (); 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); + if (item is Code.ProjectFolderItem) { + var row = create_project_row ((Code.ProjectFolderItem)item); project_listbox.insert (row, index); } } @@ -161,7 +161,7 @@ public class Code.ChooseProjectButton : Gtk.Box { } } - private Gtk.Widget create_project_row (Scratch.FolderManager.ProjectFolderItem project_folder) { + private Gtk.Widget create_project_row (Code.ProjectFolderItem project_folder) { var project_path = project_folder.file.file.get_path (); return new ProjectRow (project_path); } diff --git a/src/Widgets/GitGutterRenderer.vala b/src/Widgets/GitGutterRenderer.vala index 58a9c77372..419af28f76 100644 --- a/src/Widgets/GitGutterRenderer.vala +++ b/src/Widgets/GitGutterRenderer.vala @@ -17,7 +17,7 @@ public class Scratch.Widgets.GitGutterRenderer : GtkSource.GutterRenderer { public Gee.HashMap line_status_map; public Gee.HashMap status_color_map; - public FolderManager.ProjectFolderItem? project { get; set; default = null; } + public Code.ProjectFolderItem? project { get; set; default = null; } static construct { fallback_scheme = GtkSource.StyleSchemeManager.get_default ().get_scheme ("classic"); // We can assume this always exists diff --git a/src/Widgets/SourceView.vala b/src/Widgets/SourceView.vala index c5a600dfe8..7101aa5130 100644 --- a/src/Widgets/SourceView.vala +++ b/src/Widgets/SourceView.vala @@ -28,7 +28,7 @@ namespace Scratch.Widgets { public Gtk.TextTag error_tag; public GLib.File location { get; set; } - public FolderManager.ProjectFolderItem project { get; set; default = null; } + public Code.ProjectFolderItem project { get; set; default = null; } public SimpleActionGroup actions { get; construct; } private string font; diff --git a/src/Widgets/TreeList/TreeList.vala b/src/Widgets/TreeList/TreeList.vala index 37c3b44178..f273aec4e1 100644 --- a/src/Widgets/TreeList/TreeList.vala +++ b/src/Widgets/TreeList/TreeList.vala @@ -21,13 +21,16 @@ public sealed class Code.TreeList : Granite.Bin { private Gtk.ListView list_view; private GLib.ListStore root_model; private Gtk.TreeListModel tree_model; - private Gtk.SelectionModel selection_model; + private Gtk.SingleSelection selection_model; construct { root_model = new GLib.ListStore (typeof (TreeListItem)); // Passthrough false (create Gtk.TreeListRows), autoexpand false tree_model = new Gtk.TreeListModel (root_model, false, false, create_model_func); selection_model = new Gtk.SingleSelection (tree_model); + selection_model.sections_changed.connect ((pos, n_items) => { + warning ("sections changed pos %u, n items %u", pos, n_items); + }); var tree_list_factory = new Gtk.SignalListItemFactory (); var tree_header_factory = new Gtk.SignalListItemFactory (); list_view = new Gtk.ListView (selection_model, tree_list_factory) { @@ -64,13 +67,18 @@ public sealed class Code.TreeList : Granite.Bin { // HEADER FACTORY HANDLERS tree_header_factory.setup.connect ((obj) => { // By default create header and subheader, expandable - var listitem = (Gtk.ListItem) obj; - create_headeritem_child (listitem); + warning ("tree header setup"); + var listheader = (Gtk.ListHeader) obj; + create_headeritem_child (listheader); }); tree_header_factory.bind.connect ((obj) => { - var listitem = (Gtk.ListItem) obj; - var treelistrow = (Gtk.TreeListRow) listitem.item; - var data = (Code.TreeListItem) treelistrow.item; + warning ("tree header bind"); + var listheader = (Gtk.ListHeader) obj; + + warning ("listheader item is a %s", listheader.item.get_type ().name ()); + var row = (Gtk.TreeListRow) listheader.item; + var data = (Code.TreeListItem) row.item; + bind_data_to_header (data, row, listheader); }); tree_header_factory.unbind.connect (() => {}); tree_header_factory.teardown.connect (() => {}); @@ -104,7 +112,8 @@ public sealed class Code.TreeList : Granite.Bin { //Must undo any signal connections etc made in bind_data_to_row } - protected virtual void create_headeritem_child (Gtk.ListItem item) { + protected virtual void create_headeritem_child (Gtk.ListHeader item) { + warning ("create headeritem child pos"); var text_label = new Gtk.Label ("") { halign = START, }; @@ -124,17 +133,19 @@ public sealed class Code.TreeList : Granite.Bin { protected virtual void teardown_headeritem_child () { // Must be paired with create_listitem child } - protected virtual void bind_data_to_headeritem ( + protected virtual void bind_data_to_header ( TreeListItem data, Gtk.TreeListRow row, - Gtk.ListItem item + Gtk.ListHeader item ) { + var expander = (Gtk.TreeExpander) item.child; var box = (Gtk.Box) expander.get_child (); var text_label = (Gtk.Label) box.get_first_child (); var subtext_label = (Gtk.Label) text_label.get_next_sibling (); text_label.label = data.text; subtext_label.label = data.sub_text; + warning ("bind data to header %s", text_label.label); } protected virtual void unbind_data_from_headerrow ( diff --git a/src/Widgets/TreeList/TreeListItem.vala b/src/Widgets/TreeList/TreeListItem.vala index b86ca7b8bc..4ad9af8b02 100644 --- a/src/Widgets/TreeList/TreeListItem.vala +++ b/src/Widgets/TreeList/TreeListItem.vala @@ -22,7 +22,6 @@ public class Code.TreeListItem : Object { public bool is_selectable { get; set; default = true; } public bool is_editable { get; set; default = false; } public bool is_dummy { get; construct; } - public uint n_children { get { return child_model == null ? 0 : child_model.get_n_items (); @@ -33,15 +32,13 @@ public class Code.TreeListItem : Object { public TreeListItem () { Object ( - is_dummy: false, - is_expandable: false + is_dummy: false ); } public TreeListItem.dummy () { Object ( - is_dummy: true, - is_expandable: false + is_dummy: true ); } diff --git a/src/meson.build b/src/meson.build index 71c8838090..912bf5bf07 100644 --- a/src/meson.build +++ b/src/meson.build @@ -31,10 +31,12 @@ code_files = files( 'Dialogs/BranchActions/BranchNameRow.vala', 'FolderManager/File.vala', 'FolderManager/FileItem.vala', - 'FolderManager/FileView.vala', + # 'FolderManager/FileView.vala', 'FolderManager/FolderItem.vala', 'FolderManager/Item.vala', 'FolderManager/ProjectFolderItem.vala', + 'FolderManager/FolderTree.vala', + 'FolderManager/ProjectList.vala', 'Services/CommentToggler.vala', 'Services/Document.vala', 'Services/DocumentManager.vala', From 891aae5528faa1d0c74c967cc2d5d75b06e0e67f Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 13 Jul 2026 17:56:41 +0100 Subject: [PATCH 06/21] Temp commit --- src/FolderManager/File.vala | 50 +- src/FolderManager/FolderTree.vala | 893 +++++++++++++++-------------- src/FolderManager/ProjectList.vala | 164 +++--- 3 files changed, 565 insertions(+), 542 deletions(-) diff --git a/src/FolderManager/File.vala b/src/FolderManager/File.vala index 2b5e03289a..f036b5b305 100644 --- a/src/FolderManager/File.vala +++ b/src/FolderManager/File.vala @@ -19,22 +19,38 @@ */ public class Code.File : GLib.Object { - public GLib.File file { get; private set; } + public GLib.File file { get; set construct; } private GLib.FileInfo? info = null; // Non-null after loading public File (string path) { Object (path: path); } - // returns the path the file - public string path { - owned get { - return file.get_path (); - } - set construct { - load_file_for_path (value); + construct { + file = GLib.File.new_for_path (path); + info = new FileInfo (); + try { + var query_string = GLib.FileAttribute.STANDARD_CONTENT_TYPE + "," + + GLib.FileAttribute.STANDARD_IS_BACKUP + "," + + GLib.FileAttribute.STANDARD_IS_HIDDEN + "," + + GLib.FileAttribute.STANDARD_DISPLAY_NAME + "," + + GLib.FileAttribute.STANDARD_TYPE; + + info = file.query_info (query_string, FileQueryInfoFlags.NONE); + } catch (GLib.Error error) { + info = null; + warning (error.message); } } + // returns the path the file + public string path { get; construct; } + // owned get { + // return file.get_path (); + // } + // set construct { + // load_file_for_path (value); + // } + // } // returns the basename of the file private string _name; @@ -156,24 +172,6 @@ public class Code.File : GLib.Object { return info.get_attribute_boolean (attribute); } - private void load_file_for_path (string path) { - file = GLib.File.new_for_path (path); - - info = new FileInfo (); - try { - var query_string = GLib.FileAttribute.STANDARD_CONTENT_TYPE + "," + - GLib.FileAttribute.STANDARD_IS_BACKUP + "," + - GLib.FileAttribute.STANDARD_IS_HIDDEN + "," + - GLib.FileAttribute.STANDARD_DISPLAY_NAME + "," + - GLib.FileAttribute.STANDARD_TYPE; - - info = file.query_info (query_string, FileQueryInfoFlags.NONE); - } catch (GLib.Error error) { - info = null; - warning (error.message); - } - } - public void rename (string name) { try { if (exists) { diff --git a/src/FolderManager/FolderTree.vala b/src/FolderManager/FolderTree.vala index 65c3e56af6..8ff819f18e 100644 --- a/src/FolderManager/FolderTree.vala +++ b/src/FolderManager/FolderTree.vala @@ -31,8 +31,6 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { 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_CLOSE_FOLDER = "close-folder"; - public const string ACTION_CLOSE_OTHER_FOLDERS = "close-other-folders"; private const ActionEntry[] ACTION_ENTRIES = { { ACTION_LAUNCH_APP_WITH_FILE_PATH, action_launch_app_with_file_path, "as" }, @@ -44,41 +42,45 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { { 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"} }; public SimpleActionGroup actions { get; private set; } public string icon_name { get; set; } public string title { get; set; } + public string root_path { get; construct; } public bool is_empty { get { return tree_list.n_root_items () == 0; } } private Code.TreeList tree_list; - private GLib.Settings settings; - private Scratch.Services.GitManager git_manager; - private Scratch.Services.PluginsManager plugins; + // private GLib.Settings settings; + // private Scratch.Services.GitManager git_manager; + // private Scratch.Services.PluginsManager plugins; - public FolderTree (Scratch.Services.PluginsManager plugins_manager) { - plugins = plugins_manager; + // public FolderTree (Scratch.Services.PluginsManager plugins_manager) { + // plugins = plugins_manager; + // } + public FolderTree (string root_path) { + Object ( + root_path: root_path + ); } construct { tree_list = new Code.TreeList (); - icon_name = "folder-symbolic"; - title = _("Folders"); + // icon_name = "folder-symbolic"; + // title = _("Folders"); - settings = new GLib.Settings ("io.elementary.code.folder-manager"); + // settings = new GLib.Settings ("io.elementary.code.folder-manager"); - git_manager = Scratch.Services.GitManager.get_instance (); + // git_manager = Scratch.Services.GitManager.get_instance (); actions = new SimpleActionGroup (); actions.add_action_entries (ACTION_ENTRIES, this); insert_action_group (ACTION_GROUP, actions); - Scratch.saved_state.changed["order-folders"].connect (() => { - order_folders (); - }); + // Scratch.saved_state.changed["order-folders"].connect (() => { + // order_folders (); + // }); // Convert ListView signal into file_activate tree_list.item_activated.connect ((item) => { @@ -95,100 +97,100 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { return false; } - private void action_close_folder (SimpleAction action, GLib.Variant? parameter) { - var path = parameter.get_string (); - if (path == null || path == "") { - return; - } - - var project_item = find_path (null, path) as ProjectFolderItem; - if (project_item == null) { - return; - } - - project_item.closed (); - } - - private void action_close_other_folders (SimpleAction action, GLib.Variant? parameter) { - var path = parameter.get_string (); - if (path == null || path == "") { - return; - } + // private void action_close_folder (SimpleAction action, GLib.Variant? parameter) { + // var path = parameter.get_string (); + // if (path == null || path == "") { + // return; + // } - var folder_root = find_path (null, path) as ProjectFolderItem; - if (folder_root == null) { - return; - } + // var project_item = find_path (null, path) as ProjectFolderItem; + // if (project_item == null) { + // return; + // } - List to_remove = null; - tree_list.iterate_children (null, (child) => { - var project_folder_item = (ProjectFolderItem) child; - if (project_folder_item != folder_root) { - activate_action ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_PROJECT_DOCS, - "s", - project_folder_item.path - ); - to_remove.prepend (project_folder_item); - git_manager.remove_project (project_folder_item); - } - - return Code.TreeList.ITERATE_CONTINUE; - }); + // project_item.closed (); + // } - tree_list.remove_root_children (to_remove); - //Make remaining project the active one - set_project_active (path); - } + // private void action_close_other_folders (SimpleAction action, GLib.Variant? parameter) { + // var path = parameter.get_string (); + // if (path == null || path == "") { + // return; + // } + + // var folder_root = find_path (null, path) as ProjectFolderItem; + // if (folder_root == null) { + // return; + // } + + // List to_remove = null; + // tree_list.iterate_children (null, (child) => { + // var project_folder_item = (ProjectFolderItem) child; + // if (project_folder_item != folder_root) { + // activate_action ( + // MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_PROJECT_DOCS, + // "s", + // project_folder_item.path + // ); + // to_remove.prepend (project_folder_item); + // git_manager.remove_project (project_folder_item); + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + + // tree_list.remove_root_children (to_remove); + // //Make remaining project the active one + // set_project_active (path); + // } - private void action_set_active_project (SimpleAction action, GLib.Variant? parameter) { - var path = parameter.get_string (); - if (path == null || path == "") { - return; - } + // private void action_set_active_project (SimpleAction action, GLib.Variant? parameter) { + // var path = parameter.get_string (); + // if (path == null || path == "") { + // return; + // } - set_active_project (path); - } + // set_active_project (path); + // } - private ProjectFolderItem? set_active_project (string path) { - var folder_root = find_path (null, path) as ProjectFolderItem; - if (folder_root == null) { - return null; - } + // private ProjectFolderItem? set_active_project (string path) { + // var folder_root = find_path (null, path) as ProjectFolderItem; + // if (folder_root == null) { + // return null; + // } - git_manager.active_project_path = path; + // git_manager.active_project_path = path; - write_settings (); + // write_settings (); - return folder_root; - } + // return folder_root; + // } - private void set_project_active (string path) { - activate_action ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_SET_ACTIVE_PROJECT, - "s", - path - ); - } + // private void set_project_active (string path) { + // activate_action ( + // MainWindow.ACTION_PREFIX + MainWindow.ACTION_SET_ACTIVE_PROJECT, + // "s", + // path + // ); + // } - public async void restore_saved_state () { - foreach (unowned string path in settings.get_strv ("opened-folders")) { - yield add_folder (new File (path), false, true); - } - } + // public async void restore_saved_state () { + // foreach (unowned string path in settings.get_strv ("opened-folders")) { + // yield add_folder (new File (path), false, true); + // } + // } - public void open_folder (File folder) { - if (is_open (folder)) { - var existing = find_path (null, folder.path); - if (existing is Code.TreeListItem) { - ((Code.TreeListItem)existing).is_expanded = true; - } + // public void open_folder (File folder) { + // if (is_open (folder)) { + // var existing = find_path (null, folder.path); + // if (existing is Code.TreeListItem) { + // ((Code.TreeListItem)existing).is_expanded = true; + // } - return; - } + // return; + // } - add_folder.begin (folder, true); - } + // add_folder.begin (folder, true); + // } public void collapse_all () { tree_list.iterate_children (null, (child) => { @@ -197,18 +199,18 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { }); } - public void order_folders () { - if (!Scratch.saved_state.get_boolean ("order-folders")) { - return; - } + // public void order_folders () { + // if (!Scratch.saved_state.get_boolean ("order-folders")) { + // return; + // } - tree_list.sort_root_children ((a, b) => { - return strcmp ( - ((ProjectFolderItem)a).name.down (), - ((ProjectFolderItem)b).name.down () - ); - }); - } + // tree_list.sort_root_children ((a, b) => { + // return strcmp ( + // ((ProjectFolderItem)a).name.down (), + // ((ProjectFolderItem)b).name.down () + // ); + // }); + // } public void select_path (string path) { find_path (null, path); @@ -222,77 +224,77 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { tree_list.unselect_all (); } - public void collapse_other_projects () { - unowned string path; - path = git_manager.active_project_path; - - tree_list.iterate_children (null, (child) => { - var project_folder = ((ProjectFolderItem) child); - if (project_folder.path != path) { - project_folder.is_expanded = false; - activate_action ( - Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_HIDE_PROJECT_DOCS, - "s", - project_folder.path - ); - } else if (project_folder.path == path) { - project_folder.is_expanded = true; - activate_action ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_RESTORE_PROJECT_DOCS, - "s", - project_folder.path - ); - } - - return Code.TreeList.ITERATE_CONTINUE; - }); - } - - public void branch_actions (string path) { - // Must only carry out branch actions on active project so switch if necessary. - //TODO Warn before switching active project? - var active_project = set_active_project (path); - if (active_project == null || !active_project.is_git_repo) { - Gdk.Display.get_default ().beep (); - return; - } - - var dialog = new Dialogs.BranchActionDialog (active_project); - dialog.response.connect ((res) => { - if (res == Gtk.ResponseType.APPLY) { - perform_branch_action (dialog); - } - - dialog.destroy (); - }); + // public void collapse_other_projects () { + // unowned string path; + // path = git_manager.active_project_path; + + // tree_list.iterate_children (null, (child) => { + // var project_folder = ((ProjectFolderItem) child); + // if (project_folder.path != path) { + // project_folder.is_expanded = false; + // activate_action ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_HIDE_PROJECT_DOCS, + // "s", + // project_folder.path + // ); + // } else if (project_folder.path == path) { + // project_folder.is_expanded = true; + // activate_action ( + // MainWindow.ACTION_PREFIX + MainWindow.ACTION_RESTORE_PROJECT_DOCS, + // "s", + // project_folder.path + // ); + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + // } - dialog.present (); - } + // public void branch_actions (string path) { + // // Must only carry out branch actions on active project so switch if necessary. + // //TODO Warn before switching active project? + // var active_project = set_active_project (path); + // if (active_project == null || !active_project.is_git_repo) { + // Gdk.Display.get_default ().beep (); + // return; + // } + + // var dialog = new Dialogs.BranchActionDialog (active_project); + // dialog.response.connect ((res) => { + // if (res == Gtk.ResponseType.APPLY) { + // perform_branch_action (dialog); + // } + + // dialog.destroy (); + // }); + + // dialog.present (); + // } - private void perform_branch_action ( - Scratch.Dialogs.BranchActionDialog dialog - ) { - switch (dialog.action) { - case CHECKOUT: - dialog.project.checkout_branch_ref (dialog.branch_ref); - break; - case COMMIT: - break; - case PUSH: - break; - case PULL: - break; - case MERGE: - break; - case DELETE: - break; - case CREATE: - dialog.project.new_branch (dialog.new_branch_name); - break; - default: - assert_not_reached (); - } - } + // private void perform_branch_action ( + // Scratch.Dialogs.BranchActionDialog dialog + // ) { + // switch (dialog.action) { + // case CHECKOUT: + // dialog.project.checkout_branch_ref (dialog.branch_ref); + // break; + // case COMMIT: + // break; + // case PUSH: + // break; + // case PULL: + // break; + // case MERGE: + // break; + // case DELETE: + // break; + // case CREATE: + // dialog.project.new_branch (dialog.new_branch_name); + // break; + // default: + // assert_not_reached (); + // } + // } private Code.TreeListItem? find_path ( Code.TreeListItem? list, // Starting point for search @@ -343,58 +345,59 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { // return get_project_for_file (GLib.File.new_for_path (project_path)) != null; // } - public ProjectFolderItem? get_project_for_file (GLib.File file) { - ProjectFolderItem? matched_project = null; - tree_list.iterate_children (null, (item) => { - if (item is ProjectFolderItem) { - var folder = (ProjectFolderItem) item; - if (folder.file.file.equal (file) || folder.contains_file (file)) { - matched_project = folder; - return Code.TreeList.ITERATE_STOP; - } - } - - return Code.TreeList.ITERATE_CONTINUE; - }); - - return matched_project; - } + // public ProjectFolderItem? get_project_for_file (GLib.File file) { + // ProjectFolderItem? matched_project = null; + // tree_list.iterate_children (null, (item) => { + // if (item is ProjectFolderItem) { + // var folder = (ProjectFolderItem) item; + // if (folder.file.file.equal (file) || folder.contains_file (file)) { + // matched_project = folder; + // return Code.TreeList.ITERATE_STOP; + // } + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + + // return matched_project; + // } public Code.TreeListItem? expand_to_path (string path) { return find_path (null, path, true); } - /* Do global search on project containing the file path supplied in parameter */ - public void search_global (string path, string? term = null) { - var item_for_path = (FolderManagerItem?)(expand_to_path (path)); - if (item_for_path != null) { - var search_root = item_for_path.get_root_folder (); - if (search_root is ProjectFolderItem) { - GLib.File start_folder = (item_for_path is FolderItem) - ? item_for_path.file.file - : search_root.file.file; - - bool is_explicit = !(item_for_path is ProjectFolderItem); - search_root.global_search.begin (start_folder, term, is_explicit); - } - } - } + // /* Do global search on project containing the file path supplied in parameter */ + // public void search_global (string path, string? term = null) { + // var item_for_path = (FolderManagerItem?)(expand_to_path (path)); + // if (item_for_path != null) { + // var search_root = item_for_path.get_root_folder (); + // if (search_root is ProjectFolderItem) { + // GLib.File start_folder = (item_for_path is FolderItem) + // ? item_for_path.file.file + // : search_root.file.file; + + // bool is_explicit = !(item_for_path is ProjectFolderItem); + // search_root.global_search.begin (start_folder, term, is_explicit); + // } + // } + // } public void clear_badges () { - tree_list.iterate_children (null, (child) => { - if (child is ProjectFolderItem) { - ((FolderItem)child).remove_all_badges (); - } + // tree_list.iterate_children (null, (child) => { + // if (child is ProjectFolderItem) { + // ((FolderItem)child).remove_all_badges (); + // } - return Code.TreeList.ITERATE_CONTINUE; - }); + // return Code.TreeList.ITERATE_CONTINUE; + // }); } public void folder_item_update_hook (GLib.File source, GLib.File? dest, GLib.FileMonitorEvent event) { - plugins.hook_folder_item_change (source, dest, event); + // plugins.hook_folder_item_change (source, dest, event); + //TODO Make plugins a singleton } - public void iterate_children (Code.TreeListItem? start, Code.TreeList.ListIteratorCallback cb) { + private void iterate_children (Code.TreeListItem? start, Code.TreeList.ListIteratorCallback cb) { tree_list.iterate_children (start, cb); } @@ -489,289 +492,289 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { } private void add_new_folder (SimpleAction action, Variant? param) { - // Using "path" of parent folder from params, call `on_add_new (true)` on `FolderItem` - var path = param.get_string (); + // // Using "path" of parent folder from params, call `on_add_new (true)` on `FolderItem` + // var path = param.get_string (); - if (path == null || path == "") { - return; - } + // if (path == null || path == "") { + // return; + // } - var folder = find_path (null, path) as FolderItem; - if (folder == null) { - return; - } + // var folder = find_path (null, path) as FolderItem; + // if (folder == null) { + // return; + // } - folder.on_add_new (true); + // folder.on_add_new (true); } 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 != null ? param.get_string () : null; + // // Using "path" of parent folder from params, call `on_add_new (false)` on `FolderItem` + // var path = param != null ? param.get_string () : null; - if (path == null || path == "") { - critical ("No path"); - return; - } + // if (path == null || path == "") { + // critical ("No path"); + // return; + // } - var folder = find_path (null, path) as FolderItem; - if (folder == null) { - return; - } + // var folder = find_path (null, path) as FolderItem; + // if (folder == null) { + // return; + // } - folder.on_add_new (false); + // 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); + // // 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; - } + // //Do we need this check? + // if (parent_path == null || parent_path == "") { + // return; + // } - var folder = find_path (null, parent_path) as FolderItem; - if (folder == null) { - return; - } + // var folder = find_path (null, parent_path) as FolderItem; + // if (folder == null) { + // return; + // } - folder.on_add_template (template_path); + // 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]); + // var params = param.get_strv (); + // Utils.launch_app_with_file (params[1], params[0]); } private void action_show_app_chooser (SimpleAction action, Variant? param) { - var path = param.get_string (); + // var path = param.get_string (); - if (path == null || path == "") { - return; - } + // if (path == null || path == "") { + // return; + // } - var file = GLib.File.new_for_path (path); - var dialog = new Gtk.AppChooserDialog (new Gtk.Window (), Gtk.DialogFlags.MODAL, file); - dialog.deletable = false; + // var file = GLib.File.new_for_path (path); + // var dialog = new Gtk.AppChooserDialog (new Gtk.Window (), Gtk.DialogFlags.MODAL, file); + // dialog.deletable = false; - dialog.response.connect ((res) => { - if (res == Gtk.ResponseType.OK) { - var app_info = dialog.get_app_info (); - if (app_info != null) { - Utils.launch_app_with_file (app_info.get_id (), path); - } - } + // dialog.response.connect ((res) => { + // if (res == Gtk.ResponseType.OK) { + // var app_info = dialog.get_app_info (); + // if (app_info != null) { + // Utils.launch_app_with_file (app_info.get_id (), path); + // } + // } - dialog.destroy (); - }); + // dialog.destroy (); + // }); - dialog.show (); + // dialog.show (); } private void action_execute_contract_with_file_path (SimpleAction action, Variant? param) { - var params = param.get_strv (); - var path = params[0]; - if (path == null || path == "") { - return; - } - - var contract_name = params[1]; - if (contract_name == null || contract_name == "") { - return; - } - - Utils.execute_contract_with_file_path (path, contract_name); + // var params = param.get_strv (); + // var path = params[0]; + // if (path == null || path == "") { + // return; + // } + + // var contract_name = params[1]; + // if (contract_name == null || contract_name == "") { + // return; + // } + + // Utils.execute_contract_with_file_path (path, contract_name); } private void action_rename_file (SimpleAction action, Variant? param) { - var path = param.get_string (); + // var path = param.get_string (); - if (path == null || path == "") { - return; - } + // if (path == null || path == "") { + // return; + // } - rename_file (path); + // rename_file (path); } private void action_rename_folder (SimpleAction action, Variant? param) { - var path = param.get_string (); + // var path = param.get_string (); - if (path == null || path == "") { - return; - } + // if (path == null || path == "") { + // return; + // } - rename_folder (path); + // rename_folder (path); } private void action_delete (SimpleAction action, Variant? param) { - var path = param.get_string (); - - if (path == null || path == "") { - return; - } - - var item = find_path (null, path); - if (item != null) { - var item_to_delete = item as Code.FolderManagerItem; - - // Wait for ProjectFolderItem closed signal handle logic to run before moving item to trash - if (item_to_delete is Code.ProjectFolderItem) { - item_to_delete.closed.connect_after (() => { - item_to_delete.trash (); - }); - item_to_delete.closed (); - return; - } - - item_to_delete.trash (); - } - } - - private async void add_folder (File folder, bool expand, bool restoring = false) { - if (is_open (folder)) { - warning ("Folder '%s' is already open.", folder.path); - return; - } else if (!folder.is_valid_directory) { - warning ("Cannot open invalid directory."); - return; - } - - var add_file = folder.file; - // Need to deal with case where folder is parent or child of an existing project - var parents = new List (); - var children = new List (); - - tree_list.iterate_children (null, (child) => { - var item = (ProjectFolderItem) child; - if (add_file.get_relative_path (item.file.file) != null) { - debug ("Trying to add parent of existing project"); - children.append (item); - } else if (item.file.file.get_relative_path (add_file) != null) { - debug ("Trying to add child of existing project"); - parents.append (item); - } - - return Code.TreeList.ITERATE_CONTINUE; - }); - - if (parents.length () > 0 || children.length () > 0) { - assert (parents.length () <= 1); - assert (parents.length () == 0 || children.length () == 0); - var dialog = new Scratch.Dialogs.CloseProjectsConfirmationDialog ( - (Scratch.MainWindow) get_root (), - parents.length (), - children.length () - ); - - var close_projects = false; - dialog.response.connect ((res) => { - if (res == Gtk.ResponseType.ACCEPT) { - close_projects = true; - } - - dialog.destroy (); - add_folder.callback (); - }); - - dialog.show (); - yield; - - if (close_projects) { - foreach (var item in parents) { - item.closed (); - } - - foreach (var item in children) { - item.closed (); - } - } else { - return; - } - } - - // Process any closed signals emitted before proceeding - Idle.add (() => { - var folder_root = new ProjectFolderItem (folder, this); // Constructor adds project to GitManager - tree_list.add_root_item (folder_root); - rename_items_with_same_name (folder_root); - - folder_root.is_expanded = expand; - folder_root.closed.connect (() => { - activate_action ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_PROJECT_DOCS, - "s", - folder_root.path - ); - - tree_list.remove_root_item (folder_root); - - tree_list.iterate_children (null, (child) => { - var child_folder = (ProjectFolderItem) child; - if (child_folder.name != child_folder.file.name) { - rename_items_with_same_name (child_folder); - } - - return Code.TreeList.ITERATE_CONTINUE; - }); + // var path = param.get_string (); - Scratch.Services.GitManager.get_instance ().remove_project (folder_root); - write_settings (); - }); + // if (path == null || path == "") { + // return; + // } - // 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) { - write_settings (); - } + // var item = find_path (null, path); + // if (item != null) { + // var item_to_delete = item as Code.FolderManagerItem; + + // // Wait for ProjectFolderItem closed signal handle logic to run before moving item to trash + // if (item_to_delete is Code.ProjectFolderItem) { + // item_to_delete.closed.connect_after (() => { + // item_to_delete.trash (); + // }); + // item_to_delete.closed (); + // return; + // } - add_folder.callback (); - return Source.REMOVE; - }); + // item_to_delete.trash (); + // } + } - yield; + // private async void add_folder (File folder, bool expand, bool restoring = false) { + // if (is_open (folder)) { + // warning ("Folder '%s' is already open.", folder.path); + // return; + // } else if (!folder.is_valid_directory) { + // warning ("Cannot open invalid directory."); + // return; + // } + + // var add_file = folder.file; + // // Need to deal with case where folder is parent or child of an existing project + // var parents = new List (); + // var children = new List (); + + // tree_list.iterate_children (null, (child) => { + // var item = (ProjectFolderItem) child; + // if (add_file.get_relative_path (item.file.file) != null) { + // debug ("Trying to add parent of existing project"); + // children.append (item); + // } else if (item.file.file.get_relative_path (add_file) != null) { + // debug ("Trying to add child of existing project"); + // parents.append (item); + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + + // if (parents.length () > 0 || children.length () > 0) { + // assert (parents.length () <= 1); + // assert (parents.length () == 0 || children.length () == 0); + // var dialog = new Scratch.Dialogs.CloseProjectsConfirmationDialog ( + // (Scratch.MainWindow) get_root (), + // parents.length (), + // children.length () + // ); + + // var close_projects = false; + // dialog.response.connect ((res) => { + // if (res == Gtk.ResponseType.ACCEPT) { + // close_projects = true; + // } + + // dialog.destroy (); + // add_folder.callback (); + // }); + + // dialog.show (); + // yield; + + // if (close_projects) { + // foreach (var item in parents) { + // item.closed (); + // } + + // foreach (var item in children) { + // item.closed (); + // } + // } else { + // return; + // } + // } + + // // Process any closed signals emitted before proceeding + // Idle.add (() => { + // var folder_root = new ProjectFolderItem (folder, this); // Constructor adds project to GitManager + // tree_list.add_root_item (folder_root); + // rename_items_with_same_name (folder_root); + + // folder_root.is_expanded = expand; + // folder_root.closed.connect (() => { + // activate_action ( + // MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_PROJECT_DOCS, + // "s", + // folder_root.path + // ); + + // tree_list.remove_root_item (folder_root); + + // tree_list.iterate_children (null, (child) => { + // var child_folder = (ProjectFolderItem) child; + // if (child_folder.name != child_folder.file.name) { + // rename_items_with_same_name (child_folder); + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + + // Scratch.Services.GitManager.get_instance ().remove_project (folder_root); + // write_settings (); + // }); + + // // 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) { + // write_settings (); + // } + + // add_folder.callback (); + // return Source.REMOVE; + // }); + + // yield; + + // order_folders (); + // } - order_folders (); - } + // private bool is_open (File folder) { + // bool open = false; + // tree_list.iterate_children (null, (child) => { + // if (folder.path == ((FolderManagerItem) child).path) { + // open = true; + // return Code.TreeList.ITERATE_STOP; + // } - private bool is_open (File folder) { - bool open = false; - tree_list.iterate_children (null, (child) => { - if (folder.path == ((FolderManagerItem) child).path) { - open = true; - return Code.TreeList.ITERATE_STOP; - } + // return Code.TreeList.ITERATE_CONTINUE; + // }); - return Code.TreeList.ITERATE_CONTINUE; - }); + // return open; + // } - return open; - } + // private void write_settings () { + // string[] to_save = {}; + // tree_list.iterate_children (null, (item) => { + // var saved = false; + // var folder_path = ((FolderManagerItem) item).path; - private void write_settings () { - string[] to_save = {}; - tree_list.iterate_children (null, (item) => { - var saved = false; - var folder_path = ((FolderManagerItem) item).path; - - //Do we need to de-duplicate? Not possible to open a project twice? - foreach (var saved_folder in to_save) { - if (folder_path == saved_folder) { - saved = true; - break; - } - } + // //Do we need to de-duplicate? Not possible to open a project twice? + // foreach (var saved_folder in to_save) { + // if (folder_path == saved_folder) { + // saved = true; + // break; + // } + // } - if (!saved) { - to_save += folder_path; - } + // if (!saved) { + // to_save += folder_path; + // } - return Code.TreeList.ITERATE_CONTINUE; - }); + // return Code.TreeList.ITERATE_CONTINUE; + // }); - settings.set_strv ("opened-folders", to_save); - } + // settings.set_strv ("opened-folders", to_save); + // } } diff --git a/src/FolderManager/ProjectList.vala b/src/FolderManager/ProjectList.vala index a9659bc591..7cd728e323 100644 --- a/src/FolderManager/ProjectList.vala +++ b/src/FolderManager/ProjectList.vala @@ -5,8 +5,8 @@ //TODO Can we just use ProjectFolderItems direct? public class Code.ProjectListItem : Object { - public ProjectFolderItem project_folder { get; construct; } - public string path { owned get { return project_folder.path; }} + public ProjectFolderItem project_folder { get; set construct; } + public string path { get; construct; } public GLib.File gfile { get { return project_folder.file.file; }} public Code.FolderTree folder_tree { get; set construct; } @@ -15,15 +15,39 @@ public class Code.ProjectListItem : Object { private bool is_expanded { get; set; } private bool is_active_project { get { return git_manager.active_project_path == path; } } - public ProjectListItem (ProjectFolderItem project) { + public ProjectListItem (string path) { Object ( - project_folder: project + path: path ); } construct { folder_tree = new FolderTree (path); git_manager = Scratch.Services.GitManager.get_instance (); + + var new_project = new ProjectFolderItem (new Code.File (path), folder_tree); // Constructor adds project to GitManager + new_project.project.closed.connect (() => { + activate_action ( + MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_PROJECT_DOCS, + "s", + folder_root.path + ); + + tree_list.remove_root_item (folder_root); + + tree_list.iterate_children (null, (child) => { + var child_folder = (ProjectFolderItem) child; + if (child_folder.name != child_folder.file.name) { + rename_items_with_same_name (child_folder); + } + + return Code.TreeList.ITERATE_CONTINUE; + }); + + Scratch.Services.GitManager.get_instance ().remove_project (folder_root); + write_settings (); + }); + } public bool is_or_contains_file (GLib.File gfile) { @@ -40,9 +64,14 @@ public class Code.ProjectListItem : Object { is_expanded = true; } - public void closed () { + public void close () { // folder_tree.clear (); project_folder.closed (); + git_manager.remove_project (project_folder); // Takes care of active_project? + } + + public void set_as_active_project () { + git_manager.active_project_path = path; } } @@ -50,8 +79,9 @@ public class Code.ProjectListItem : Object { public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { public const string ACTION_GROUP = "project-list"; public const string ACTION_PREFIX = ACTION_GROUP + "."; - public const string ACTION_CLOSE_FOLDER = "close-folder"; - public const string ACTION_CLOSE_OTHER_FOLDERS = "close-other-folders"; + public const string ACTION_CLOSE_PROJECT_FOLDER = "close-project-folder"; + public const string ACTION_CLOSE_OTHER_PROJECT_FOLDERS = "close-other-project-folders"; + public const string CLOSE_PROJECT_DOCS_ACTION_NAME = Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_CLOSE_PROJECT_DOCS; public SimpleActionGroup actions { get; private set; } public bool is_empty { get { return list_store.n_root_items () == 0; } } @@ -64,13 +94,13 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { private Scratch.Services.PluginsManager plugins; private const ActionEntry[] ACTION_ENTRIES = { - { ACTION_CLOSE_FOLDER, action_close_folder, "s"}, - { ACTION_CLOSE_OTHER_FOLDERS, action_close_other_folders, "s"} + { ACTION_CLOSE_PROJECT_FOLDER, action_close_project_folder, "s"}, + { ACTION_CLOSE_OTHER_PROJECT_FOLDERS, action_close_other_project_folders, "s"} }; - public ProjectList (Scratch.Services.PluginsManager plugins_manager) { - plugins = plugins_manager; - } + // public ProjectList (Scratch.Services.PluginsManager plugins_manager) { + // plugins = plugins_manager; + // } construct { settings = new GLib.Settings ("io.elementary.code.folder-manager"); @@ -573,7 +603,7 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { var parents = new List (); var children = new List (); - iterate_children (null, (listitem) => { + iterate_children ((listitem) => { // var item = (ProjectFolderItem) child; if (add_file.get_relative_path (listitem.gfile) != null) { debug ("Trying to add parent of existing project"); @@ -610,11 +640,11 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { if (close_projects) { foreach (var listitem in parents) { - listitem.closed (); + listitem.close (); } - foreach (var item in children) { - listitem.closed (); + foreach (var listitem in children) { + listitem.close (); } } else { return; @@ -623,28 +653,7 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { // Process any closed signals emitted before proceeding Idle.add (() => { - var new_project = new ProjectFolderItem (folder, this); // Constructor adds project to GitManager - new_project.project.closed.connect (() => { - activate_action ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_PROJECT_DOCS, - "s", - folder_root.path - ); - - tree_list.remove_root_item (folder_root); - - tree_list.iterate_children (null, (child) => { - var child_folder = (ProjectFolderItem) child; - if (child_folder.name != child_folder.file.name) { - rename_items_with_same_name (child_folder); - } - return Code.TreeList.ITERATE_CONTINUE; - }); - - Scratch.Services.GitManager.get_instance ().remove_project (folder_root); - write_settings (); - }); var new_item = new ProjectListItem (folder); list_store.append_item (new_item); @@ -684,7 +693,7 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { // return open; // } - private void write_settings () { + private void write_open_folders_setting () { string[] to_save = {}; tree_list.iterate_children (null, (item) => { var saved = false; @@ -708,50 +717,64 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { settings.set_strv ("opened-folders", to_save); } - private void action_close_folder (SimpleAction action, GLib.Variant? parameter) { + private void action_close_project_folder (SimpleAction action, GLib.Variant? parameter) { var path = parameter.get_string (); if (path == null || path == "") { return; } - var project_item = find_path (null, path) as ProjectFolderItem; - if (project_item == null) { - return; - } + iterate_children ((listitem) => { + if (listitem.path == path) { + listitem.close (); + return TreeList.ITERATE_STOP; + } - project_item.closed (); + return TreeList.ITERATE_CONTINUE; + }); } - private void action_close_other_folders (SimpleAction action, GLib.Variant? parameter) { + private void action_close_other_project_folders (SimpleAction action, GLib.Variant? parameter) { var path = parameter.get_string (); if (path == null || path == "") { return; } - var folder_root = find_path (null, path) as ProjectFolderItem; - if (folder_root == null) { - return; - } - - List to_remove = null; - tree_list.iterate_children (null, (child) => { - var project_folder_item = (ProjectFolderItem) child; - if (project_folder_item != folder_root) { - activate_action ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_PROJECT_DOCS, - "s", - project_folder_item.path - ); - to_remove.prepend (project_folder_item); - git_manager.remove_project (project_folder_item); + List to_remove = null; + iterate_children ((listitem) => { + if (listitem.path != path) { + listitem.close (); + to_remove.prepend (listitem); } - return Code.TreeList.ITERATE_CONTINUE; + return TreeList.ITERATE_CONTINUE; }); - tree_list.remove_root_children (to_remove); - //Make remaining project the active one - set_project_active (path); + // List to_remove = null; + // tree_list.iterate_children (null, (child) => { + // var project_folder_item = (ProjectFolderItem) child; + // if (project_folder_item != folder_root) { + // activate_action ( + // MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_PROJECT_DOCS, + // "s", + // project_folder_item.path + // ); + // to_remove.prepend (project_folder_item); + // git_manager.remove_project (project_folder_item); + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + + foreach (ProjectListItem listitem in to_remove) { + activate_action ( + CLOSE_PROJECT_DOCS_ACTION_NAME, + "s", + listitem.path + ); + uint pos; + list_store.find (listitem, out pos); + list_store.remove (pos); + } } private void action_set_active_project (SimpleAction action, GLib.Variant? parameter) { @@ -764,16 +787,15 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { } private ProjectFolderItem? set_active_project (string path) { - var folder_root = find_path (null, path) as ProjectFolderItem; - if (folder_root == null) { + var listitem = find_path (path); + if (listitem == null) { return null; } - git_manager.active_project_path = path; - - write_settings (); + listitem.set_as_active_project (); + write_open_folders_setting (); - return folder_root; + return listitem; } private void set_project_active (string path) { From c1725ca4d7a021987f3be3a037d1044575ab348e Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 14 Jul 2026 11:17:01 +0100 Subject: [PATCH 07/21] Temp commit --- src/FolderManager/FolderTree.vala | 18 +- src/FolderManager/ProjectFolderItem.vala | 4 +- src/FolderManager/ProjectList.vala | 230 ++++++++++++----------- src/MainWindow.vala | 14 +- src/Services/GitManager.vala | 1 + 5 files changed, 140 insertions(+), 127 deletions(-) diff --git a/src/FolderManager/FolderTree.vala b/src/FolderManager/FolderTree.vala index 8ff819f18e..546a21c522 100644 --- a/src/FolderManager/FolderTree.vala +++ b/src/FolderManager/FolderTree.vala @@ -213,7 +213,7 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { // } public void select_path (string path) { - find_path (null, path); + // find_path (null, path); } public void unselect_file (GLib.File file) { @@ -224,6 +224,10 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { tree_list.unselect_all (); } + public void remove_all () { + tree_list.remove_all (); + } + // public void collapse_other_projects () { // unowned string path; // path = git_manager.active_project_path; @@ -296,17 +300,15 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { // } // } - private Code.TreeListItem? find_path ( - Code.TreeListItem? list, // Starting point for search + public FolderManagerItem? find_path ( + Code.TreeListItem? start, string path, // File path to search fod - bool expand = false, // Whether to expsnd to show found item - GLib.File? target_file = null // Alternatively find this file + bool expand = false // Whether to expsnd to show found item ) { - var target = target_file ?? GLib.File.new_for_path (path); - Code.TreeListItem? matched_item = null; + FolderManagerItem? matched_item = null; - tree_list.iterate_children (null, (item) => { + tree_list.iterate_children (start, (item) => { if ((item is FolderManagerItem) && item.path == path) { matched_item = item; return Code.TreeList.ITERATE_STOP; diff --git a/src/FolderManager/ProjectFolderItem.vala b/src/FolderManager/ProjectFolderItem.vala index 77022b2d8e..170c7d2484 100644 --- a/src/FolderManager/ProjectFolderItem.vala +++ b/src/FolderManager/ProjectFolderItem.vala @@ -27,7 +27,7 @@ public class Code.ProjectFolderItem : FolderItem { private static Icon added_icon; private static Icon modified_icon; - public signal void closed (); + public signal void deleted (); public Scratch.Services.MonitoredRepository? monitored_repo { get; private set; default = null; } // Cache the visible item in the project. @@ -83,7 +83,7 @@ public class Code.ProjectFolderItem : FolderItem { protected override void on_changed (GLib.File source, GLib.File? dest, GLib.FileMonitorEvent event) { if (source.equal (file.file) && event == DELETED) { - closed (); + deleted (); } else { base.on_changed (source, dest, event); } diff --git a/src/FolderManager/ProjectList.vala b/src/FolderManager/ProjectList.vala index 7cd728e323..4424aa55cb 100644 --- a/src/FolderManager/ProjectList.vala +++ b/src/FolderManager/ProjectList.vala @@ -6,18 +6,19 @@ //TODO Can we just use ProjectFolderItems direct? public class Code.ProjectListItem : Object { public ProjectFolderItem project_folder { get; set construct; } + public ProjectList view { get; set construct; } // Needed to activate actions public string path { get; construct; } public GLib.File gfile { get { return project_folder.file.file; }} - public Code.FolderTree folder_tree { get; set construct; } + private Code.FolderTree folder_tree; private Scratch.Services.GitManager git_manager; - private bool is_expanded { get; set; } private bool is_active_project { get { return git_manager.active_project_path == path; } } - public ProjectListItem (string path) { + public ProjectListItem (string path, ProjectList view) { Object ( - path: path + path: path, + view: view ); } @@ -25,27 +26,13 @@ public class Code.ProjectListItem : Object { folder_tree = new FolderTree (path); git_manager = Scratch.Services.GitManager.get_instance (); - var new_project = new ProjectFolderItem (new Code.File (path), folder_tree); // Constructor adds project to GitManager - new_project.project.closed.connect (() => { - activate_action ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_PROJECT_DOCS, - "s", - folder_root.path - ); - - tree_list.remove_root_item (folder_root); - - tree_list.iterate_children (null, (child) => { - var child_folder = (ProjectFolderItem) child; - if (child_folder.name != child_folder.file.name) { - rename_items_with_same_name (child_folder); - } - - return Code.TreeList.ITERATE_CONTINUE; - }); + // ProjectFolderItem constructor adds project to GitManager + var project_folder = new ProjectFolderItem (new Code.File (path), folder_tree); - Scratch.Services.GitManager.get_instance ().remove_project (folder_root); - write_settings (); + // Closed signal emitted when project folder is externally deleted + project_folder.deleted.connect (() => { + folder_tree.remove_all (); + view.item_deleted (this); }); } @@ -64,15 +51,23 @@ public class Code.ProjectListItem : Object { is_expanded = true; } + // Does not remove from liststore - that is up to ProjectList public void close () { - // folder_tree.clear (); - project_folder.closed (); + folder_tree.remove_all (); git_manager.remove_project (project_folder); // Takes care of active_project? } public void set_as_active_project () { git_manager.active_project_path = path; } + + public FolderManagerItem? find_path ( + string path, // File path to search fod + bool expand = false // Whether to expsnd to show found item + // GLib.File? target_file = null // Alternatively find this file + ) { + return folder_tree.find_path (null, path, expand); + } } /* ProjectList is a flat list of FolderTrees each representing a single project */ @@ -83,7 +78,11 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { public const string ACTION_CLOSE_OTHER_PROJECT_FOLDERS = "close-other-project-folders"; public const string CLOSE_PROJECT_DOCS_ACTION_NAME = Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_CLOSE_PROJECT_DOCS; public SimpleActionGroup actions { get; private set; } - public bool is_empty { get { return list_store.n_root_items () == 0; } } + public bool is_empty { get { return list_store.n_items == 0; } } + + // PaneSwitcher interface + public string icon_name { get; set; } + public string title { get; set; } private Gtk.ScrolledWindow scrolled_window; private Gtk.ListView list_view; @@ -103,13 +102,14 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { // } construct { + //For Code.PaneSwitcher iterface + icon_name = "not-exist"; + title = "Projects"; settings = new GLib.Settings ("io.elementary.code.folder-manager"); list_store = new ListStore (typeof (ProjectListItem)); - selection_model = new Gtk.NoSelection (); + selection_model = new Gtk.NoSelection (list_store); var list_factory = new Gtk.SignalListItemFactory (); - list_view = new Gtk.ListView (selection_model, list_factory) { - header_factory = tree_header_factory - }; + list_view = new Gtk.ListView (selection_model, list_factory); actions = new SimpleActionGroup (); actions.add_action_entries (ACTION_ENTRIES, this); @@ -119,26 +119,26 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { order_folders (); }); - child = list_store; + child = list_view; list_factory.setup.connect ((obj) => { var listitem = (Gtk.ListItem) obj; - create_listitem_child (listitem); + // create_listitem_child (listitem); // By default just create a use a label (not expandable) }); list_factory.teardown.connect ((obj) => { var listitem = (Gtk.ListItem) obj; - teardown_listitem_child (listitem); + // teardown_listitem_child (listitem); }); list_factory.bind.connect ((obj) => { var listitem = (Gtk.ListItem) obj; var data = (Code.ProjectListItem) (listitem.item); - bind_data_to_row (data, listitem); + // bind_data_to_row (data, listitem); }); list_factory.unbind.connect ((obj) => { var listitem = (Gtk.ListItem) obj; var data = (Code.ProjectListItem) (listitem.item); - unbind_data_from_row (data, listitem); + // unbind_data_from_row (data, listitem); }); } @@ -148,13 +148,14 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { } } - // If project exists expand it, otherwise create public void open_project_folder (File folder) { - if (is_open_project (folder.path)) { - return; + ProjectListItem? listitem; + if (is_existing_project_path (folder.path, out listitem)) { + listitem.expand_all (); //TODO Just expand first level? + return; //TODO Should we expand here? } - add_new_project_folder.begin (folder, true, false); + add_new_project_folder.begin (folder.path, true, false); } public void collapse_all () { @@ -189,17 +190,17 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { public void collapse_other_projects (string active_project_path) { iterate_children ((listitem) => { - if (listitem.path != path) { + if (listitem.path != active_project_path) { listitem.collapse_all (); activate_action ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_HIDE_PROJECT_DOCS, + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_HIDE_PROJECT_DOCS, "s", listitem.path ); - } else if (listitem.path == path) { + } else { listitem.expand_all (); activate_action ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_RESTORE_PROJECT_DOCS, + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_RESTORE_PROJECT_DOCS, "s", listitem.path ); @@ -264,13 +265,12 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { // var target = target_file ?? GLib.File.new_for_path (path); FolderManagerItem? matched_item = null; - iterate_children ((listitem) => { if (listitem.path == path) { matched_item = listitem.project_folder; return Code.TreeList.ITERATE_STOP; } else if (path.has_prefix (listitem.path)) { //TODO Ensure paths are compatible - matched_item = listitem.tree_root_item.find_path (path); + matched_item = listitem.find_path (path, expand); return Code.TreeList.ITERATE_STOP; } @@ -308,23 +308,23 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { } - + // Which project any file is in public ProjectListItem? get_project_for_file (GLib.File file) { ProjectListItem? matched_project_item = null; iterate_children ((listitem) => { if (listitem.is_or_contains_file (file)) { - matched_project = listitem; + matched_project_item = listitem; return Code.TreeList.ITERATE_STOP; } return Code.TreeList.ITERATE_CONTINUE; }); - return matched_project; + return matched_project_item; } - public Code.TreeListItem? expand_to_path (string path) { - return find_path (null, path, true); + public FolderManagerItem? expand_to_path (string path) { + return find_path (path, true); } /* Do global search on project containing the file path supplied in parameter */ @@ -343,18 +343,24 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { } } - public void clear_badges () { - tree_list.iterate_children (null, (child) => { - if (child is ProjectFolderItem) { - ((FolderItem)child).remove_all_badges (); - } + // public void clear_badges () { + // tree_list.iterate_children (null, (child) => { + // if (child is ProjectFolderItem) { + // ((FolderItem)child).remove_all_badges (); + // } - return Code.TreeList.ITERATE_CONTINUE; - }); - } + // return Code.TreeList.ITERATE_CONTINUE; + // }); + // } - public void folder_item_update_hook (GLib.File source, GLib.File? dest, GLib.FileMonitorEvent event) { - plugins.hook_folder_item_change (source, dest, event); + // public void folder_item_update_hook (GLib.File source, GLib.File? dest, GLib.FileMonitorEvent event) { + // plugins.hook_folder_item_change (source, dest, event); + // } + + + public void item_deleted (ProjectListItem listitem) { + // Just remove it for now + remove_project_item (listitem); } // This only works when the list is stable (nothing being added, expanded etc) @@ -641,10 +647,12 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { if (close_projects) { foreach (var listitem in parents) { listitem.close (); + remove_project_item (listitem); } foreach (var listitem in children) { listitem.close (); + remove_project_item (listitem); } } else { return; @@ -653,10 +661,8 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { // Process any closed signals emitted before proceeding Idle.add (() => { - - - var new_item = new ProjectListItem (folder); - list_store.append_item (new_item); + var new_item = new ProjectListItem (path, this); + list_store.append (new_item); if (expand) { new_item.expand_all (); } @@ -666,7 +672,7 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { // This interferes with fuzzy-finder plugins_manager // See https://github.com/elementary/code/issues/1533 if (!restoring) { - write_settings (); + write_open_folders_setting (); } add_new_project_folder.callback (); @@ -678,37 +684,39 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { // order_folders (); //TODO do later } - // private bool is_open_project (string path) { - // bool open = false; - // // Only iterate this model - // iterate_children ((listitem) => { - // if (path == listitem.path) { - // open = true; - // return Code.TreeList.ITERATE_STOP; - // } + private bool is_existing_project_path (string path, out ProjectListItem? list_item) { + bool open = false; + list_item = null; + ProjectListItem? matched_item = null; + // Only iterate this model + iterate_children ((listitem) => { + if (path == listitem.path) { + open = true; + matched_item = listitem; + return Code.TreeList.ITERATE_STOP; + } - // return Code.TreeList.ITERATE_CONTINUE; - // }); + return Code.TreeList.ITERATE_CONTINUE; + }); - // return open; - // } + list_item = matched_item; + return open; + } private void write_open_folders_setting () { string[] to_save = {}; - tree_list.iterate_children (null, (item) => { + iterate_children ((listitem) => { var saved = false; - var folder_path = ((FolderManagerItem) item).path; - //Do we need to de-duplicate? Not possible to open a project twice? foreach (var saved_folder in to_save) { - if (folder_path == saved_folder) { + if (listitem.path == saved_folder) { saved = true; break; } } if (!saved) { - to_save += folder_path; + to_save += listitem.path; } return Code.TreeList.ITERATE_CONTINUE; @@ -717,6 +725,18 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { settings.set_strv ("opened-folders", to_save); } + private void remove_project_item (ProjectListItem listitem) { + activate_action ( + CLOSE_PROJECT_DOCS_ACTION_NAME, + "s", + listitem.path + ); + + uint pos; + list_store.find (listitem, out pos); + list_store.remove (pos); + + } private void action_close_project_folder (SimpleAction action, GLib.Variant? parameter) { var path = parameter.get_string (); if (path == null || path == "") { @@ -726,6 +746,7 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { iterate_children ((listitem) => { if (listitem.path == path) { listitem.close (); + remove_project_item (listitem); // OK to remove as we stop iterating return TreeList.ITERATE_STOP; } @@ -743,7 +764,7 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { iterate_children ((listitem) => { if (listitem.path != path) { listitem.close (); - to_remove.prepend (listitem); + to_remove.prepend (listitem); // Delay removal during iteration } return TreeList.ITERATE_CONTINUE; @@ -766,14 +787,7 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { // }); foreach (ProjectListItem listitem in to_remove) { - activate_action ( - CLOSE_PROJECT_DOCS_ACTION_NAME, - "s", - listitem.path - ); - uint pos; - list_store.find (listitem, out pos); - list_store.remove (pos); + remove_project_item (listitem); } } @@ -786,25 +800,21 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { set_active_project (path); } + //TODO Do we need both these functions??? private ProjectFolderItem? set_active_project (string path) { - var listitem = find_path (path); - if (listitem == null) { - return null; - } - - listitem.set_as_active_project (); - write_open_folders_setting (); - - return listitem; - } - - private void set_project_active (string path) { - activate_action ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_SET_ACTIVE_PROJECT, - "s", - path - ); - } + ProjectListItem? project_item; + is_existing_project_path (path, out project_item); + project_item.set_as_active_project (); + return project_item.project_folder; + } + + // private void set_project_active (string path) { + // activate_action ( + // MainWindow.ACTION_PREFIX + MainWindow.ACTION_SET_ACTIVE_PROJECT, + // "s", + // path + // ); + // } delegate bool ProjectListIteratorCallback (ProjectListItem item); private void iterate_children (ProjectListIteratorCallback cb) { diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 9dc10087c8..9a1162f833 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -132,7 +132,7 @@ namespace Scratch { { ACTION_FIND_PREVIOUS, action_find_previous }, { ACTION_FIND_GLOBAL, action_find_global, "s" }, { ACTION_OPEN, action_open }, - { ACTION_OPEN_FOLDER, action_open_folder, "s" }, + { ACTION_OPEN_FOLDER, action_open_folder_as_project, "s" }, { ACTION_OPEN_PROJECT, action_open_project }, { ACTION_COLLAPSE_ALL_FOLDERS, action_collapse_all_folders }, { ACTION_PREFERENCES, action_preferences }, @@ -744,9 +744,9 @@ namespace Scratch { return search_bar.search_occurrences > 0; } - public void open_folder (File folder) { + public void open_folder_as_project (File folder) { var foldermanager_file = new Code.File (folder.get_path ()); - folder_manager_view.open_folder (foldermanager_file); + folder_manager_view.open_project_folder (foldermanager_file); } public async void open_document (Scratch.Services.Document doc, @@ -1024,7 +1024,7 @@ var index = 0; while (obj != null) { // var file = (GLib.File) obj; // var foldermanager_file = new Code.File (file.get_path ()); - // folder_manager_view.open_folder (foldermanager_file); + // folder_manager_view.open_folder_as_project (foldermanager_file); // obj = files.get_item (index++); } } @@ -1033,12 +1033,12 @@ var index = 0; chooser.show (); } - private void action_open_folder (SimpleAction action, Variant? param) { + private void action_open_folder_as_project (SimpleAction action, Variant? param) { var path = param.get_string (); if (path == "") { choose_folder (); } else { - // folder_manager_view.open_folder (new Code.File (path)); + // folder_manager_view.open_folder_as_project (new Code.File (path)); } } @@ -1081,7 +1081,7 @@ var index = 0; sidebar.cloning_in_progress = false; if (success) { - open_folder (workdir); + open_folder_as_project (workdir); if (this.is_active) { sidebar.notify_cloning_success (); } else { diff --git a/src/Services/GitManager.vala b/src/Services/GitManager.vala index 8f2268558f..2ccbf2b6ab 100644 --- a/src/Services/GitManager.vala +++ b/src/Services/GitManager.vala @@ -46,6 +46,7 @@ namespace Scratch.Services { construct { // Used to populate the ChooseProject popover in sorted order + //TODO This seems to duplicate the sidebar store - can we combine? project_liststore = new ListStore (typeof (Code.ProjectFolderItem)); settings.bind ("active-project-path", this, "active-project-path", DEFAULT); } From bfa8d225ea449372edf163e213e4a40b7f66988c Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 14 Jul 2026 17:17:49 +0100 Subject: [PATCH 08/21] Temp commit --- src/Application.vala | 2 +- src/FolderManager/File.vala | 2 +- src/FolderManager/FileItem.vala | 8 +- src/FolderManager/FolderItem.vala | 127 +++-- src/FolderManager/FolderTree.vala | 9 +- src/FolderManager/Item.vala | 21 +- src/FolderManager/ProjectFolderItem.vala | 568 ++++++++++++----------- src/FolderManager/ProjectList.vala | 217 ++++----- src/Widgets/TreeList/TreeListItem.vala | 2 +- 9 files changed, 527 insertions(+), 429 deletions(-) diff --git a/src/Application.vala b/src/Application.vala index ee7b260081..2c626d39b0 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -180,7 +180,7 @@ namespace Scratch { bool is_folder; if (Scratch.Services.FileHandler.can_open_file (file, out is_folder)) { if (is_folder) { - window.open_folder (file); + window.open_folder_as_project (file); // Opens in sidebar not as documents } else { debug ("Files length: %d\n", files.length); var doc = new Scratch.Services.Document (window.actions, file); diff --git a/src/FolderManager/File.vala b/src/FolderManager/File.vala index f036b5b305..58ddffdf77 100644 --- a/src/FolderManager/File.vala +++ b/src/FolderManager/File.vala @@ -113,7 +113,7 @@ public class Code.File : GLib.Object { public bool is_valid_textfile { get { if (_is_valid_textfile == null) { - _is_valid_textfile = !path.has_suffix ("~") && Utils.check_if_valid_text_file (path, info); + _is_valid_textfile = !path.has_suffix ("~") && Scratch.Utils.check_if_valid_text_file (path, info); } return _is_valid_textfile; diff --git a/src/FolderManager/FileItem.vala b/src/FolderManager/FileItem.vala index 8575e18bc9..a8cf627e12 100644 --- a/src/FolderManager/FileItem.vala +++ b/src/FolderManager/FileItem.vala @@ -38,7 +38,7 @@ public class Code.FileItem : FolderManagerItem { } var file_type = info.get_attribute_string (GLib.FileAttribute.STANDARD_CONTENT_TYPE); - var contractor_items = Utils.create_contract_items_for_file (file.file); + var contractor_items = Scratch.Utils.create_contract_items_for_file (file.file); var external_actions_section = new GLib.Menu (); external_actions_section.append_item (create_submenu_for_open_in (file_type)); if (contractor_items.get_n_items () > 0) { @@ -55,7 +55,7 @@ public class Code.FileItem : FolderManagerItem { new Variant.string (file.path) ) ); - var rename_action = Utils.action_from_group (FolderTree.ACTION_RENAME_FILE, view.actions); + var rename_action = Scratch.Utils.action_from_group (FolderTree.ACTION_RENAME_FILE, view.actions); rename_action.set_enabled (view.rename_request (file)); var delete_item = new GLib.MenuItem ( @@ -81,7 +81,7 @@ public class Code.FileItem : FolderManagerItem { var new_window_menu_item = new GLib.MenuItem ( _("New Window"), GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_OPEN_IN_NEW_WINDOW, + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_OPEN_IN_NEW_WINDOW, file.path ) ); @@ -104,7 +104,7 @@ public class Code.FileItem : FolderManagerItem { var open_in_menu = new GLib.Menu (); open_in_menu.append_section (null, top_section); - open_in_menu.append_section (null, Utils.create_executable_app_items_for_file (file.file, file_type)); + open_in_menu.append_section (null, Scratch.Utils.create_executable_app_items_for_file (file.file, file_type)); open_in_menu.append_section (null, extra_section); var open_in_menu_item = new GLib.MenuItem.submenu (_("Open In"), open_in_menu); diff --git a/src/FolderManager/FolderItem.vala b/src/FolderManager/FolderItem.vala index d83426be63..0da8fa6a67 100644 --- a/src/FolderManager/FolderItem.vala +++ b/src/FolderManager/FolderItem.vala @@ -18,12 +18,38 @@ * Authored by: Julien Spautz , Andrei-Costin Zisu */ -public class Code.FolderItem : FolderManagerItem { - private const uint RENAME_AFTER_NEW_DELAY_MSEC = 500; - private GLib.FileMonitor monitor; - private bool children_loaded = false; - private bool has_dummy; - private Code.TreeListItem dummy; /* Blank item for expanded empty folders */ +public interface Code.FolderInterface : Object { + public abstract bool is_expanded { get; set; } + public abstract ListStore? child_model { get; set; } + public abstract FileMonitor monitor { get; set; } + public virtual void on_changed ( + GLib.File source, + GLib.File? dest, + GLib.FileMonitorEvent event + ) {} + + public virtual void init_monitor_directory (GLib.File gfile) { + try { + monitor = gfile.monitor_directory (GLib.FileMonitorFlags.NONE); + monitor.changed.connect (on_changed); + } catch (GLib.Error e) { + warning (e.message); + } + } + + public delegate bool IterateChildrenCallback (Object obj); + public virtual void iterate_children (IterateChildrenCallback cb) {} + // { + // uint pos = 0; + // Object child; + // do { + // child = list_model.get_object (pos++); + // } while (child != null && cb (child)); + // } +} + +public class Code.FolderItem : FolderManagerItem, Code.FolderInterface { // Ultimately a Code.TreeListItem + public signal void children_finished_loading (); public bool loading_required { get { @@ -31,7 +57,14 @@ public class Code.FolderItem : FolderManagerItem { } } - public signal void children_finished_loading (); + // Code.FolderInterface + public bool is_expanded { get; set; } + protected FileMonitor monitor { get; set; } + + private const uint RENAME_AFTER_NEW_DELAY_MSEC = 500; + private bool children_loaded = false; + private bool has_dummy; + private Code.TreeListItem dummy; /* Blank item for expanded empty folders */ public FolderItem (File file, FolderTree view) { Object ( @@ -53,15 +86,17 @@ public class Code.FolderItem : FolderManagerItem { add_child (dummy); has_dummy = true; + init_monitor_directory (file.file); // Signal when expanded notify["is-expanded"].connect (on_toggled); + } - try { - monitor = file.file.monitor_directory (GLib.FileMonitorFlags.NONE); - monitor.changed.connect (on_changed); - } catch (GLib.Error e) { - warning (e.message); - } + public override void iterate_children (IterateChildrenCallback cb) { + uint pos = 0; + Object? child = null; + do { + child = child_model.get_object (pos++); + } while (child != null && cb (child)); } private void create_child_items (File file) { @@ -120,16 +155,15 @@ public class Code.FolderItem : FolderManagerItem { } private void on_toggled () { - if (is_expanded) { - load_children_async.begin (); - } else { - var root = get_root_folder (); - if (root != null && - root.monitored_repo != null) { - //When toggled closed, update status to reflect hidden contents - root.update_item_status (this); - } - } + // if (is_expanded) { + // load_children_async.begin (); + // } else { + // var root = get_root_folder (); + // if (root != null) { + // //When toggled closed, update status to reflect hidden contents + // root.update_item_status (this); + // } + // } } public override GLib.Menu? get_context_menu () { @@ -142,7 +176,7 @@ public class Code.FolderItem : FolderManagerItem { var file_type = info.get_content_type (); - var contractor_items = Utils.create_contract_items_for_file (file.file); + var contractor_items = Scratch.Utils.create_contract_items_for_file (file.file); var rename_menu_item = new GLib.MenuItem ( _("Rename"), @@ -163,7 +197,7 @@ public class Code.FolderItem : FolderManagerItem { var search_item = new GLib.MenuItem ( _("Find in Folder…"), GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_FIND_GLOBAL, + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_FIND_GLOBAL, new Variant.string (file.file.get_path ()) ) ); @@ -194,7 +228,7 @@ public class Code.FolderItem : FolderManagerItem { var open_in_terminal_pane_item = new GLib.MenuItem ( (_("Terminal Pane")), GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_OPEN_IN_TERMINAL, + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_OPEN_IN_TERMINAL, new Variant.string (file.path) ) ); @@ -218,7 +252,7 @@ public class Code.FolderItem : FolderManagerItem { var open_in_menu = new GLib.Menu (); open_in_menu.append_section (null, terminal_pane_section); - open_in_menu.append_section (null, Utils.create_executable_app_items_for_file (file.file, file_type)); + open_in_menu.append_section (null, Scratch.Utils.create_executable_app_items_for_file (file.file, file_type)); open_in_menu.append_section (null, extra_section); var open_in_menu_item = new GLib.MenuItem.submenu (_("Open In"), open_in_menu); @@ -349,21 +383,21 @@ public class Code.FolderItem : FolderManagerItem { return count; } - public void remove_all_badges () { - view.iterate_children (this, (child) => { - remove_badge (child); - return Code.TreeList.ITERATE_CONTINUE; - }); - } + // public void remove_all_badges () { + // view.iterate_children (this, (child) => { + // remove_badge (child); + // return Code.TreeList.ITERATE_CONTINUE; + // }); + // } - private void remove_badge (Code.TreeListItem item) { + // private void remove_badge (Code.TreeListItem item) { - if (item is FolderItem) { - ((FolderItem) item).remove_all_badges (); - } + // if (item is FolderItem) { + // ((FolderItem) item).remove_all_badges (); + // } - item.badge = ""; - } + // item.badge = ""; + // } public override void add_child (Code.TreeListItem item) { if (has_dummy) { @@ -444,7 +478,7 @@ public class Code.FolderItem : FolderManagerItem { case GLib.FileMonitorEvent.DELETED: // Find item corresponding to deleted file // Note may not be found if deleted file is not valid for display - var path_item = find_item_for_path (source.get_path ()); + var path_item = get_child_for_path (source.get_path ()); if (path_item != null) { remove_child (path_item); } @@ -455,7 +489,7 @@ public class Code.FolderItem : FolderManagerItem { return; } - var path_item = find_item_for_path (source.get_path ()); + var path_item = get_child_for_path (source.get_path ()); if (path_item == null) { var file = new File (source.get_path ()); if (file.is_valid_directory) { @@ -492,12 +526,13 @@ public class Code.FolderItem : FolderManagerItem { } } - private Code.FolderManagerItem? find_item_for_path (string path) { + // Called on MonitorEvent so only concerns direct children + private Code.FolderManagerItem? get_child_for_path (string path) { Code.FolderManagerItem? found_item = null; - view.iterate_children (this, (item) => { + iterate_children ((obj) => { // Item could be dummy - if (item is Code.FolderManagerItem) { - var child = (Code.FolderManagerItem) item; + if (obj is Code.FolderManagerItem) { + var child = (Code.FolderManagerItem) obj; if (child.path == path) { found_item = child; return Code.TreeList.ITERATE_STOP; @@ -563,7 +598,7 @@ public class Code.FolderItem : FolderManagerItem { // 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 (FolderTree.ACTION_RENAME_FILE, view.actions); + var rename_action = Scratch.Utils.action_from_group (FolderTree.ACTION_RENAME_FILE, view.actions); if (rename_action != null && rename_action.enabled) { rename_action.activate (path); } else { diff --git a/src/FolderManager/FolderTree.vala b/src/FolderManager/FolderTree.vala index 546a21c522..f6694e0d6e 100644 --- a/src/FolderManager/FolderTree.vala +++ b/src/FolderManager/FolderTree.vala @@ -59,6 +59,8 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { // public FolderTree (Scratch.Services.PluginsManager plugins_manager) { // plugins = plugins_manager; // } + + // Root_path is path of the associated //TODO Needed? public FolderTree (string root_path) { Object ( root_path: root_path @@ -307,10 +309,11 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { ) { FolderManagerItem? matched_item = null; + var target = GLib.File.new_for_path (path); tree_list.iterate_children (start, (item) => { if ((item is FolderManagerItem) && item.path == path) { - matched_item = item; + matched_item = (FolderManagerItem) item; return Code.TreeList.ITERATE_STOP; } @@ -330,7 +333,7 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { } } - var recurse_item = find_path (folder, path, expand, target); + var recurse_item = find_path (folder, path, expand); if (recurse_item != null) { matched_item = recurse_item; return Code.TreeList.ITERATE_STOP; @@ -412,7 +415,7 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { // selected.disconnect (once); // var new_path = Path.get_dirname (path) + Path.DIR_SEPARATOR_S + new_name; // activate_action ( - // MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_TAB, + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_CLOSE_TAB, // "s", // path // ); diff --git a/src/FolderManager/Item.vala b/src/FolderManager/Item.vala index 3643d2b11d..56992ff465 100644 --- a/src/FolderManager/Item.vala +++ b/src/FolderManager/Item.vala @@ -18,10 +18,20 @@ * Authored by: Julien Spautz , Andrei-Costin Zisu */ -public abstract class Code.FolderManagerItem: Object { +public interface Code.FolderManagerItemInterface : Object { + public abstract string path { get; set; } + public abstract string name { get; set; } + public abstract string badge { get; set; } + public virtual Menu? get_context_menu () { return null; } +} + +public abstract class Code.FolderManagerItem: Code.TreeListItem, Code.FolderManagerItemInterface { + public signal void edited (string new_name); + public Code.File file { get; construct; } public Code.FolderTree view { get; construct; } + // interface Code.FolderManagerItemInterface public string name { get { return text; @@ -32,12 +42,9 @@ public abstract class Code.FolderManagerItem: Object { } } - public string path { - owned get { return file.path; } - set { file.path = value; } - } + public string path { get; set; } + public string badge { get; set; } - public signal void edited (string new_name); construct { is_selectable = true; @@ -53,6 +60,8 @@ public abstract class Code.FolderManagerItem: Object { secondary_icon_tooltip ); }); + + file.bind_property ("path", this, "path", SYNC_CREATE); } protected void rename (string new_name) { diff --git a/src/FolderManager/ProjectFolderItem.vala b/src/FolderManager/ProjectFolderItem.vala index 170c7d2484..fe4e771cb0 100644 --- a/src/FolderManager/ProjectFolderItem.vala +++ b/src/FolderManager/ProjectFolderItem.vala @@ -17,78 +17,85 @@ * Authored by: David Hewitt */ - -public class Code.ProjectFolderItem : FolderItem { - struct VisibleItem { - public string rel_path; - public FolderManagerItem item; - } - - private static Icon added_icon; - private static Icon modified_icon; - +// Top level items for ProjectList not derived from TreeListItem but sharing some interfaces +public class Code.ProjectFolderItem : Object, Code.FolderInterface, Code.FolderManagerItemInterface { public signal void deleted (); - public Scratch.Services.MonitoredRepository? monitored_repo { get; private set; default = null; } - // Cache the visible item in the project. - private List visible_item_list = null; - public bool is_git_repo { get { return monitored_repo != null; } } - private Ggit.Repository? git_repo { - get { - return (is_git_repo ? monitored_repo.git_repo : null); - } - } + // Code.FolderManagerItemInterface + public string path { get; set; } + public string name { get; set; } + public string badge { get; set; } - public ProjectFolderItem (File file, FolderTree view) requires (file.is_valid_directory) { - Object (file: file, view: view); - } + // Code.FolderItemInterface + public bool is_expanded { get; set; } + public FileMonitor monitor { get; set; } + public ListStore? child_model { get; set; default = null;} + + public Code.File file { get; construct; } + public Code.ProjectList view { get; construct; } + // For convenience + public GLib.File gfile { get { return file.file; }} + private static Icon added_icon; + private static Icon modified_icon; static construct { added_icon = new ThemedIcon ("emblem-git-new-symbolic"); modified_icon = new ThemedIcon ("emblem-git-modified-symbolic"); } - private void branch_or_name_changed () { - if (monitored_repo != null) { - //As SourceList items are not widgets we have to use markup to change appearance of text. - if (monitored_repo.head_is_branch) { - text = "%s\n%s".printf ( - name, monitored_repo.branch_name - ); - } else { //Distinguish detached heads visually - text = "%s\n %s".printf ( - name, monitored_repo.branch_name - ); - } + private struct VisibleItem { + public string rel_path; + public FolderManagerItem item; + } + + + private Scratch.Services.GitManager git_manager; + + // Cache the visible item in the project. (Excludes items loaded but in collapsed folder) + private List visible_item_list = null; + // Child widget shows tree of project files and folders + private Code.FolderTree folder_tree; + + private Ggit.Repository? git_repo { + get { + return (is_git_repo ? monitored_repo.git_repo : null); } } + public ProjectFolderItem (File file, Code.ProjectList view) requires (file.is_valid_directory) { + Object (file: file, view: view); + } + construct { + path = file.path; monitored_repo = Scratch.Services.GitManager.get_instance ().add_project (this); + git_manager = Scratch.Services.GitManager.get_instance (); + folder_tree = new Code.FolderTree (path); notify["name"].connect (branch_or_name_changed); if (monitored_repo != null) { monitored_repo.branch_changed.connect (branch_or_name_changed); monitored_repo.ignored_changed.connect ((deprioritize_git_ignored)); - monitored_repo.file_status_change.connect (() => update_item_status (null)); + // monitored_repo.file_status_change.connect (() => update_item_status (null)); monitored_repo.update_status_map (); monitored_repo.branch_changed (); } } - protected override void on_changed (GLib.File source, GLib.File? dest, GLib.FileMonitorEvent event) { - if (source.equal (file.file) && event == DELETED) { - deleted (); - } else { - base.on_changed (source, dest, event); - } + public void refresh_diff ( + ref Gee.HashMap line_status_map, + string doc_path + ) { + monitored_repo.refresh_diff (doc_path, ref line_status_map); } + public void child_folder_changed (FolderItem folder) { if (monitored_repo != null) { monitored_repo.update_status_map (); @@ -117,228 +124,234 @@ public class Code.ProjectFolderItem : FolderItem { // } } - public override GLib.Menu? get_context_menu () { - string file_type = ""; - try { - var info = file.file.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, GLib.FileQueryInfoFlags.NONE); - if (info.has_attribute (GLib.FileAttribute.STANDARD_CONTENT_TYPE)) { - file_type = info.get_content_type (); - } - } catch (Error e) { - warning (e.message); - } - - MenuItem set_active_folder_item; - if (is_git_repo) { - set_active_folder_item = new GLib.MenuItem ( - _("Set as Active Project"), - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_SET_ACTIVE_PROJECT, - new Variant.string (file.path) - ) - ); - } else { - set_active_folder_item = new GLib.MenuItem ( - _("Open in Terminal Pane"), - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_OPEN_IN_TERMINAL, - new Variant.string ( - Services.GitManager.get_instance ().get_default_build_dir (path) - ) - ) - ); - } - - set_active_folder_item.set_attribute_value ( - "accel", - Utils.get_accel_for_action ( - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_OPEN_IN_TERMINAL, - "" - ) - ) - ); - - var external_actions_section = new GLib.Menu (); - external_actions_section.append_item (set_active_folder_item); - external_actions_section.append_item (create_submenu_for_open_in (file_type)); - - var folder_actions_section = new GLib.Menu (); - folder_actions_section.append_item (create_submenu_for_new ()); - if (monitored_repo != null) { - var branch_action_item = new MenuItem ( - _("Branch Actions…"), - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_BRANCH_ACTIONS, - new Variant.string (file.path) - ) - ); - folder_actions_section.append_item (branch_action_item); - } - - var close_folder_item = new GLib.MenuItem ( - _("Close Folder"), - GLib.Action.print_detailed_name ( - FolderTree.ACTION_PREFIX + FolderTree.ACTION_CLOSE_FOLDER, - new Variant.string (file.path) - ) - ); - - var close_all_except_item = new GLib.MenuItem ( - _("Close Other Folders"), - GLib.Action.print_detailed_name ( - FolderTree.ACTION_PREFIX + FolderTree.ACTION_CLOSE_OTHER_FOLDERS, - new Variant.string (file.path) - ) - ); - var close_other_folders_action = Utils.action_from_group ( - FolderTree.ACTION_CLOSE_OTHER_FOLDERS, - view.actions - ); - close_other_folders_action.set_enabled (!view.is_empty); - - var close_actions_section = new GLib.Menu (); - close_actions_section.append_item (close_folder_item); - close_actions_section.append_item (close_all_except_item); - - var n_open = Scratch.Services.DocumentManager.get_instance ().open_for_project (path); - var open_text = ngettext ("Close %u Open Document", - "Close %u Open Documents", - n_open).printf (n_open); - - var close_item = new GLib.MenuItem ( - open_text, - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_PROJECT_DOCS, - new Variant.string (file.file.get_path ()) - ) - ); - - var hide_text = ngettext ("Hide %u Open Document", - "Hide %u Open Documents", - n_open).printf (n_open); - - var hide_item = new GLib.MenuItem ( - hide_text, - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_HIDE_PROJECT_DOCS, - new Variant.string (file.file.get_path ()) - ) - ); - - hide_item.set_attribute_value ( - "accel", - Utils.get_accel_for_action ( - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_HIDE_PROJECT_DOCS, - "" - ) - ) - ); - - var n_restorable = Scratch.Services.DocumentManager.get_instance ().restorable_for_project (path); - var restore_text = ngettext ("Restore %u Hidden Document", - "Restore %u Hidden Documents", - n_restorable).printf (n_restorable); - - var restore_item = new GLib.MenuItem ( - restore_text, - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_RESTORE_PROJECT_DOCS, - new Variant.string (file.file.get_path ()) - ) - ); - - restore_item.set_attribute_value ( - "accel", - Utils.get_accel_for_action ( - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_RESTORE_PROJECT_DOCS, - "" - ) - ) - ); - - var direct_actions_section = new GLib.Menu (); - if (n_restorable > 0) { - direct_actions_section.append_item (restore_item); - } - - if (n_open > 0) { - direct_actions_section.append_item (hide_item); - direct_actions_section.append_item (close_item); - } + public override Menu? get_context_menu () { + // string file_type = ""; + // try { + // var info = gfile.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, GLib.FileQueryInfoFlags.NONE); + // if (info.has_attribute (GLib.FileAttribute.STANDARD_CONTENT_TYPE)) { + // file_type = info.get_content_type (); + // } + // } catch (Error e) { + // warning (e.message); + // } - var search_item = new GLib.MenuItem ( - _("Find in Project…"), - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_FIND_GLOBAL, - new Variant.string (file.file.get_path ()) - ) - ); + // MenuItem set_active_folder_item; + // if (is_git_repo) { + // set_active_folder_item = new GLib.MenuItem ( + // _("Set as Active Project"), + // GLib.Action.print_detailed_name ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_SET_ACTIVE_PROJECT, + // new Variant.string (file.path) + // ) + // ); + // } else { + // set_active_folder_item = new GLib.MenuItem ( + // _("Open in Terminal Pane"), + // GLib.Action.print_detailed_name ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_OPEN_IN_TERMINAL, + // new Variant.string ( + // Services.GitManager.get_instance ().get_default_build_dir (path) + // ) + // ) + // ); + // } - search_item.set_attribute_value ( - "accel", - Utils.get_accel_for_action ( - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_FIND_GLOBAL, - "" - ) - ) - ); + // set_active_folder_item.set_attribute_value ( + // "accel", + // Scratch.Utils.get_accel_for_action ( + // GLib.Action.print_detailed_name ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_OPEN_IN_TERMINAL, + // "" + // ) + // ) + // ); + + // var external_actions_section = new GLib.Menu (); + // external_actions_section.append_item (set_active_folder_item); + // // external_actions_section.append_item (create_submenu_for_open_in (file_type)); + + // var folder_actions_section = new GLib.Menu (); + // folder_actions_section.append_item (create_submenu_for_new ()); + // if (monitored_repo != null) { + // var branch_action_item = new MenuItem ( + // _("Branch Actions…"), + // GLib.Action.print_detailed_name ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_BRANCH_ACTIONS, + // new Variant.string (file.path) + // ) + // ); + // folder_actions_section.append_item (branch_action_item); + // } - var search_actions_section = new GLib.Menu (); - search_actions_section.append_item (search_item); + // var close_folder_item = new GLib.MenuItem ( + // _("Close Folder"), + // GLib.Action.print_detailed_name ( + // ProjectList.ACTION_PREFIX + ProjectList.ACTION_CLOSE_PROJECT_FOLDER, + // new Variant.string (file.path) + // ) + // ); + + // var close_all_except_item = new GLib.MenuItem ( + // _("Close Other Folders"), + // GLib.Action.print_detailed_name ( + // ProjectList.ACTION_PREFIX + ProjectList.ACTION_CLOSE_OTHER_PROJECT_FOLDERS, + // new Variant.string (file.path) + // ) + // ); + // // var close_other_folders_action = Scratch.Utils.action_from_group ( + // // ProjectList.ACTION_CLOSE_OTHER_PROJECT_FOLDERS, + // // view.actions + // // ); + // // close_other_folders_action.set_enabled (!view.is_empty); + + // var close_actions_section = new GLib.Menu (); + // close_actions_section.append_item (close_folder_item); + // close_actions_section.append_item (close_all_except_item); + + // var n_open = Scratch.Services.DocumentManager.get_instance ().open_for_project (path); + // var open_text = ngettext ("Close %u Open Document", + // "Close %u Open Documents", + // n_open).printf (n_open); + + // var close_item = new GLib.MenuItem ( + // open_text, + // GLib.Action.print_detailed_name ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_CLOSE_PROJECT_DOCS, + // new Variant.string (file.file.get_path ()) + // ) + // ); + + // var hide_text = ngettext ("Hide %u Open Document", + // "Hide %u Open Documents", + // n_open).printf (n_open); + + // var hide_item = new GLib.MenuItem ( + // hide_text, + // GLib.Action.print_detailed_name ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_HIDE_PROJECT_DOCS, + // new Variant.string (file.file.get_path ()) + // ) + // ); + + // hide_item.set_attribute_value ( + // "accel", + // Scratch.Utils.get_accel_for_action ( + // GLib.Action.print_detailed_name ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_HIDE_PROJECT_DOCS, + // "" + // ) + // ) + // ); + + // var n_restorable = Scratch.Services.DocumentManager.get_instance ().restorable_for_project (path); + // var restore_text = ngettext ("Restore %u Hidden Document", + // "Restore %u Hidden Documents", + // n_restorable).printf (n_restorable); + + // var restore_item = new GLib.MenuItem ( + // restore_text, + // GLib.Action.print_detailed_name ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_RESTORE_PROJECT_DOCS, + // new Variant.string (file.file.get_path ()) + // ) + // ); + + // restore_item.set_attribute_value ( + // "accel", + // Scratch.Utils.get_accel_for_action ( + // GLib.Action.print_detailed_name ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_RESTORE_PROJECT_DOCS, + // "" + // ) + // ) + // ); + + // var direct_actions_section = new GLib.Menu (); + // if (n_restorable > 0) { + // direct_actions_section.append_item (restore_item); + // } - var menu_model = new GLib.Menu (); - menu_model.append_section (null, external_actions_section); - menu_model.append_section (null, folder_actions_section); - menu_model.append_section (null, close_actions_section); - menu_model.append_section (null, direct_actions_section); - menu_model.append_section (null, search_actions_section); + // if (n_open > 0) { + // direct_actions_section.append_item (hide_item); + // direct_actions_section.append_item (close_item); + // } - return menu_model; + // var search_item = new GLib.MenuItem ( + // _("Find in Project…"), + // GLib.Action.print_detailed_name ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_FIND_GLOBAL, + // new Variant.string (file.file.get_path ()) + // ) + // ); + + // search_item.set_attribute_value ( + // "accel", + // Scratch.Utils.get_accel_for_action ( + // GLib.Action.print_detailed_name ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_FIND_GLOBAL, + // "" + // ) + // ) + // ); + + // var search_actions_section = new GLib.Menu (); + // search_actions_section.append_item (search_item); + + // var menu_model = new GLib.Menu (); + // menu_model.append_section (null, external_actions_section); + // menu_model.append_section (null, folder_actions_section); + // menu_model.append_section (null, close_actions_section); + // menu_model.append_section (null, direct_actions_section); + // menu_model.append_section (null, search_actions_section); + + // return menu_model; + + return null; } - public void update_item_status (FolderItem? start_folder) { - if (monitored_repo == null) { - debug ("Ignore non-git folders"); - return; - } - bool is_new = false; - string start_path = start_folder != null ? start_folder.path : ""; - visible_item_list.@foreach ((visible_item) => { - if (start_path.has_prefix (visible_item.rel_path)) { - return; //Only need to update status for start_folder and its children - } - - var item = visible_item.item; - item.secondary_icon = null; - monitored_repo.non_current_entries.@foreach ((entry) => { - // Match non_current_path with parent folder as well as itself - var match = entry.key.has_prefix (visible_item.rel_path); - if (match) { - is_new = (entry.@value & (Ggit.StatusFlags.WORKING_TREE_NEW | Ggit.StatusFlags.INDEX_NEW)) > 0; - // Only mark folders new if only contains new items otherwise mark modified - if (item is FolderItem && - is_new && item.secondary_icon == null) { - - item.secondary_icon = added_icon; - item.secondary_icon_tooltip = _("New"); - return true; // scan all children - } - - if (!(item is FolderItem) || !item.is_expanded) { //No need to show status when children shown - item.secondary_icon = is_new ? added_icon : modified_icon; - item.secondary_icon_tooltip = is_new ? _("New") : _("Modified"); - } - return false; - } else { - return true; - } - }); - }); + public void update_item_status () { + // var status = folder_tree.update_status (); + //TODO update appearance according to global status } + // public void update_item_status (FolderItem? start_folder) { + // if (monitored_repo == null) { + // debug ("Ignore non-git folders"); + // return; + // } + // bool is_new = false; + // string start_path = start_folder != null ? start_folder.path : ""; + // visible_item_list.@foreach ((visible_item) => { + // if (start_path.has_prefix (visible_item.rel_path)) { + // return; //Only need to update status for start_folder and its children + // } + + // var item = visible_item.item; + // // item.secondary_icon = null; + // monitored_repo.non_current_entries.@foreach ((entry) => { + // // Match non_current_path with parent folder as well as itself + // var match = entry.key.has_prefix (visible_item.rel_path); + // if (match) { + // is_new = (entry.@value & (Ggit.StatusFlags.WORKING_TREE_NEW | Ggit.StatusFlags.INDEX_NEW)) > 0; + // // Only mark folders new if only contains new items otherwise mark modified + // if (item is FolderItem && + // is_new && item.secondary_icon == null) { + + // item.secondary_icon = added_icon; + // item.secondary_icon_tooltip = _("New"); + // return true; // scan all children + // } + + // if (!(item is FolderItem) || !item.is_expanded) { //No need to show status when children shown + // item.secondary_icon = is_new ? added_icon : modified_icon; + // item.secondary_icon_tooltip = is_new ? _("New") : _("Modified"); + // } + // return false; + // } else { + // return true; + // } + // }); + // }); + // } public bool contains_file (GLib.File descendant) { return file.file.get_relative_path (descendant) != null; @@ -518,9 +531,9 @@ public class Code.ProjectFolderItem : FolderItem { if (search_term != null) { // Remove results of previous search before attempting a new one - remove_all_badges (); + folder_tree.clear_badges (); // Collapse everything - collapse_all (true, true); + collapse (); /* Put search term in search bar to help user locate the position of the matches in each doc */ var search_variant = new Variant.string (search_term); @@ -589,6 +602,32 @@ public class Code.ProjectFolderItem : FolderItem { return; } + public void set_as_active_project () { + git_manager.active_project_path = path; + } + + public void collapse () { + is_expanded = false; + } + + public void expand () { + is_expanded = true; + } + + public FolderManagerItem? find_path (string path, bool expand) { + return folder_tree.find_path (null, path, expand); + } + // Does not remove from liststore - that is up to ProjectList + public void close () { + folder_tree.remove_all (); + git_manager.remove_project (this); // Takes care of active_project? + } + protected override void on_changed (GLib.File source, GLib.File? dest, GLib.FileMonitorEvent event) { + if (source.equal (file.file) && event == DELETED) { + deleted (); + } + } + private void search_folder_children (GLib.File start_folder, Regex pattern, bool recurse_subfolders) { try { var enumerator = start_folder.enumerate_children ( @@ -680,8 +719,19 @@ public class Code.ProjectFolderItem : FolderItem { return; } - public void refresh_diff (ref Gee.HashMap line_status_map, string doc_path) { - monitored_repo.refresh_diff (doc_path, ref line_status_map); + private void branch_or_name_changed () { + if (monitored_repo != null) { + //As SourceList items are not widgets we have to use markup to change appearance of text. + if (monitored_repo.head_is_branch) { + name = "%s\n%s".printf ( + file.name, monitored_repo.branch_name + ); + } else { //Distinguish detached heads visually + name = "%s\n %s".printf ( + file.name, monitored_repo.branch_name + ); + } + } } } diff --git a/src/FolderManager/ProjectList.vala b/src/FolderManager/ProjectList.vala index 4424aa55cb..98e781ef67 100644 --- a/src/FolderManager/ProjectList.vala +++ b/src/FolderManager/ProjectList.vala @@ -3,72 +3,72 @@ * SPDX-FileCopyrightText: 2026 elementary, Inc. */ -//TODO Can we just use ProjectFolderItems direct? -public class Code.ProjectListItem : Object { - public ProjectFolderItem project_folder { get; set construct; } - public ProjectList view { get; set construct; } // Needed to activate actions - public string path { get; construct; } - public GLib.File gfile { get { return project_folder.file.file; }} - - private Code.FolderTree folder_tree; - private Scratch.Services.GitManager git_manager; - private bool is_expanded { get; set; } - private bool is_active_project { get { return git_manager.active_project_path == path; } } - - public ProjectListItem (string path, ProjectList view) { - Object ( - path: path, - view: view - ); - } - - construct { - folder_tree = new FolderTree (path); - git_manager = Scratch.Services.GitManager.get_instance (); - - // ProjectFolderItem constructor adds project to GitManager - var project_folder = new ProjectFolderItem (new Code.File (path), folder_tree); - - // Closed signal emitted when project folder is externally deleted - project_folder.deleted.connect (() => { - folder_tree.remove_all (); - view.item_deleted (this); - }); - - } - - public bool is_or_contains_file (GLib.File gfile) { - return path == gfile.get_path () || folder_tree.contains_file (gfile); - } - - public void collapse_all () { - // folder_tree.collapse_all (); - is_expanded = false; - } - - public void expand_all () { - // folder_tree.expand_all (); - is_expanded = true; - } - - // Does not remove from liststore - that is up to ProjectList - public void close () { - folder_tree.remove_all (); - git_manager.remove_project (project_folder); // Takes care of active_project? - } - - public void set_as_active_project () { - git_manager.active_project_path = path; - } - - public FolderManagerItem? find_path ( - string path, // File path to search fod - bool expand = false // Whether to expsnd to show found item - // GLib.File? target_file = null // Alternatively find this file - ) { - return folder_tree.find_path (null, path, expand); - } -} +// //TODO Can we just use ProjectFolderItems direct? +// public class Code.ProjectListItem : Object { +// public ProjectFolderItem project_folder { get; set construct; } +// public ProjectList view { get; set construct; } // Needed to activate actions +// public string path { get; construct; } +// public GLib.File gfile { get { return project_folder.file.file; }} + +// private Code.FolderTree folder_tree; +// private Scratch.Services.GitManager git_manager; +// private bool is_expanded { get; set; } +// private bool is_active_project { get { return git_manager.active_project_path == path; } } + +// public ProjectListItem (string path, ProjectList view) { +// Object ( +// path: path, +// view: view +// ); +// } + +// construct { +// folder_tree = new FolderTree (path); +// git_manager = Scratch.Services.GitManager.get_instance (); + +// // ProjectFolderItem constructor adds project to GitManager +// var project_folder = new ProjectFolderItem (new Code.File (path), folder_tree); + +// // Closed signal emitted when project folder is externally deleted +// project_folder.deleted.connect (() => { +// folder_tree.remove_all (); +// view.item_deleted (this); +// }); + +// } + +// public bool is_or_contains_file (GLib.File gfile) { +// return path == gfile.get_path () || folder_tree.contains_file (gfile); +// } + +// public void collapse_all () { +// // folder_tree.collapse_all (); +// is_expanded = false; +// } + +// public void expand_all () { +// // folder_tree.expand_all (); +// is_expanded = true; +// } + +// // Does not remove from liststore - that is up to ProjectList +// public void close () { +// folder_tree.remove_all (); +// git_manager.remove_project (project_folder); // Takes care of active_project? +// } + +// public void set_as_active_project () { +// git_manager.active_project_path = path; +// } + +// public FolderManagerItem? find_path ( +// string path, // File path to search fod +// bool expand = false // Whether to expsnd to show found item +// // GLib.File? target_file = null // Alternatively find this file +// ) { +// return folder_tree.find_path (null, path, expand); +// } +// } /* ProjectList is a flat list of FolderTrees each representing a single project */ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { @@ -106,7 +106,7 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { icon_name = "not-exist"; title = "Projects"; settings = new GLib.Settings ("io.elementary.code.folder-manager"); - list_store = new ListStore (typeof (ProjectListItem)); + list_store = new ListStore (typeof (ProjectFolderItem)); selection_model = new Gtk.NoSelection (list_store); var list_factory = new Gtk.SignalListItemFactory (); list_view = new Gtk.ListView (selection_model, list_factory); @@ -132,12 +132,12 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { }); list_factory.bind.connect ((obj) => { var listitem = (Gtk.ListItem) obj; - var data = (Code.ProjectListItem) (listitem.item); + var data = (Code.ProjectFolderItem) (listitem.item); // bind_data_to_row (data, listitem); }); list_factory.unbind.connect ((obj) => { var listitem = (Gtk.ListItem) obj; - var data = (Code.ProjectListItem) (listitem.item); + var data = (Code.ProjectFolderItem) (listitem.item); // unbind_data_from_row (data, listitem); }); } @@ -149,9 +149,9 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { } public void open_project_folder (File folder) { - ProjectListItem? listitem; + ProjectFolderItem? listitem; if (is_existing_project_path (folder.path, out listitem)) { - listitem.expand_all (); //TODO Just expand first level? + listitem.expand (); return; //TODO Should we expand here? } @@ -159,9 +159,9 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { } public void collapse_all () { - //TODO Write iterate children iterate_children ((listitem) => { - listitem.collapse_all (); + // For now just collapse the top level + listitem.is_expanded = false; return Code.TreeList.ITERATE_CONTINUE; }); } @@ -191,14 +191,14 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { public void collapse_other_projects (string active_project_path) { iterate_children ((listitem) => { if (listitem.path != active_project_path) { - listitem.collapse_all (); + listitem.collapse (); activate_action ( Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_HIDE_PROJECT_DOCS, "s", listitem.path ); } else { - listitem.expand_all (); + listitem.expand (); activate_action ( Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_RESTORE_PROJECT_DOCS, "s", @@ -256,18 +256,19 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { // } } + //TODO do we need to return anything? // Call to find top level foldertree and then call find path on that. - private FolderManagerItem? find_path ( + private FolderManagerItemInterface? find_path ( string path, // File path to search fod bool expand = false // Whether to expsnd to show found item // GLib.File? target_file = null // Alternatively find this file ) { // var target = target_file ?? GLib.File.new_for_path (path); - FolderManagerItem? matched_item = null; + FolderManagerItemInterface? matched_item = null; iterate_children ((listitem) => { if (listitem.path == path) { - matched_item = listitem.project_folder; + matched_item = listitem; return Code.TreeList.ITERATE_STOP; } else if (path.has_prefix (listitem.path)) { //TODO Ensure paths are compatible matched_item = listitem.find_path (path, expand); @@ -307,23 +308,23 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { } + //TODO Is this needed? + // // Which project any file is in + // public ProjectFolderItem? get_project_for_file (GLib.File file) { + // ProjectFolderItem? matched_project_item = null; + // iterate_children ((listitem) => { + // if (listitem.is_or_contains_file (file)) { + // matched_project_item = listitem; + // return Code.TreeList.ITERATE_STOP; + // } - // Which project any file is in - public ProjectListItem? get_project_for_file (GLib.File file) { - ProjectListItem? matched_project_item = null; - iterate_children ((listitem) => { - if (listitem.is_or_contains_file (file)) { - matched_project_item = listitem; - return Code.TreeList.ITERATE_STOP; - } - - return Code.TreeList.ITERATE_CONTINUE; - }); + // return Code.TreeList.ITERATE_CONTINUE; + // }); - return matched_project_item; - } + // return matched_project_item; + // } - public FolderManagerItem? expand_to_path (string path) { + public FolderManagerItemInterface? expand_to_path (string path) { return find_path (path, true); } @@ -358,7 +359,7 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { // } - public void item_deleted (ProjectListItem listitem) { + public void item_deleted (ProjectFolderItem listitem) { // Just remove it for now remove_project_item (listitem); } @@ -372,7 +373,7 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { // selected.disconnect (once); // var new_path = Path.get_dirname (path) + Path.DIR_SEPARATOR_S + new_name; // activate_action ( - // MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_TAB, + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_CLOSE_TAB, // "s", // path // ); @@ -606,8 +607,8 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { var add_file = folder.file; // Need to deal with case where folder is parent or child of an existing project - var parents = new List (); - var children = new List (); + var parents = new List (); + var children = new List (); iterate_children ((listitem) => { // var item = (ProjectFolderItem) child; @@ -661,10 +662,10 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { // Process any closed signals emitted before proceeding Idle.add (() => { - var new_item = new ProjectListItem (path, this); + var new_item = new ProjectFolderItem (folder, this); list_store.append (new_item); if (expand) { - new_item.expand_all (); + new_item.expand (); } // rename_items_with_same_name (new_project); //TODO do this later @@ -684,10 +685,10 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { // order_folders (); //TODO do later } - private bool is_existing_project_path (string path, out ProjectListItem? list_item) { + private bool is_existing_project_path (string path, out ProjectFolderItem? list_item) { bool open = false; list_item = null; - ProjectListItem? matched_item = null; + ProjectFolderItem? matched_item = null; // Only iterate this model iterate_children ((listitem) => { if (path == listitem.path) { @@ -725,7 +726,7 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { settings.set_strv ("opened-folders", to_save); } - private void remove_project_item (ProjectListItem listitem) { + private void remove_project_item (ProjectFolderItem listitem) { activate_action ( CLOSE_PROJECT_DOCS_ACTION_NAME, "s", @@ -760,7 +761,7 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { return; } - List to_remove = null; + List to_remove = null; iterate_children ((listitem) => { if (listitem.path != path) { listitem.close (); @@ -786,7 +787,7 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { // return Code.TreeList.ITERATE_CONTINUE; // }); - foreach (ProjectListItem listitem in to_remove) { + foreach (ProjectFolderItem listitem in to_remove) { remove_project_item (listitem); } } @@ -802,10 +803,10 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { //TODO Do we need both these functions??? private ProjectFolderItem? set_active_project (string path) { - ProjectListItem? project_item; + ProjectFolderItem? project_item; is_existing_project_path (path, out project_item); project_item.set_as_active_project (); - return project_item.project_folder; + return project_item; } // private void set_project_active (string path) { @@ -816,12 +817,12 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { // ); // } - delegate bool ProjectListIteratorCallback (ProjectListItem item); + delegate bool ProjectListIteratorCallback (ProjectFolderItem item); private void iterate_children (ProjectListIteratorCallback cb) { - ProjectListItem? item = null; + ProjectFolderItem? item = null; uint pos = 0; do { - item = (ProjectListItem?) (list_store.get_object (pos++)); + item = (ProjectFolderItem?) (list_store.get_object (pos++)); } while (item != null && cb (item)); } } diff --git a/src/Widgets/TreeList/TreeListItem.vala b/src/Widgets/TreeList/TreeListItem.vala index 4ad9af8b02..7b5836e4cc 100644 --- a/src/Widgets/TreeList/TreeListItem.vala +++ b/src/Widgets/TreeList/TreeListItem.vala @@ -13,7 +13,7 @@ public class Code.TreeListItem : Object { public string secondary_icon_tooltip { get; set; default = ""; } public string badge = ""; // Use label styled with Granite.STYLE_CLASS_BADGE? - public ListStore? child_model { get; set; default = null; } + public ListStore? child_model { get; set; } public TreeListItem? parent { get; set; default = null; } public bool is_expandable { get; set construct; } public bool is_expanded { get; set; } // gets bound to the ListItem (temporarily) From b52689d379c38a16707a74eada5d527ad0ef5d4e Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 14 Jul 2026 19:49:45 +0100 Subject: [PATCH 09/21] Temp commit --- src/FolderManager/FolderTree.vala | 1 + src/FolderManager/Item.vala | 5 ++- src/FolderManager/ProjectFolderItem.vala | 44 ++++++++++----------- src/FolderManager/ProjectList.vala | 49 ++++++++++++------------ src/MainWindow.vala | 30 ++++++++------- src/Services/GitManager.vala | 20 +++++++++- src/Widgets/Sidebar.vala | 14 +++++-- 7 files changed, 97 insertions(+), 66 deletions(-) diff --git a/src/FolderManager/FolderTree.vala b/src/FolderManager/FolderTree.vala index f6694e0d6e..57e710ab7b 100644 --- a/src/FolderManager/FolderTree.vala +++ b/src/FolderManager/FolderTree.vala @@ -308,6 +308,7 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { bool expand = false // Whether to expsnd to show found item ) { +warning ("Folder tree find path"); FolderManagerItem? matched_item = null; var target = GLib.File.new_for_path (path); diff --git a/src/FolderManager/Item.vala b/src/FolderManager/Item.vala index 56992ff465..981e5ac2ba 100644 --- a/src/FolderManager/Item.vala +++ b/src/FolderManager/Item.vala @@ -23,6 +23,9 @@ public interface Code.FolderManagerItemInterface : Object { public abstract string name { get; set; } public abstract string badge { get; set; } public virtual Menu? get_context_menu () { return null; } + public bool equal (FolderManagerItemInterface b) { + return path == b.path; + } } public abstract class Code.FolderManagerItem: Code.TreeListItem, Code.FolderManagerItemInterface { @@ -83,7 +86,7 @@ public abstract class Code.FolderManagerItem: Code.TreeListItem, Code.FolderMana // if (b is RenameItem) { // return 1; // } - + warning ("compare item"); if (a is FolderItem && b is FileItem) { return -1; } else if (a is FileItem && b is FolderItem) { diff --git a/src/FolderManager/ProjectFolderItem.vala b/src/FolderManager/ProjectFolderItem.vala index fe4e771cb0..65d7a379a0 100644 --- a/src/FolderManager/ProjectFolderItem.vala +++ b/src/FolderManager/ProjectFolderItem.vala @@ -41,6 +41,7 @@ public class Code.ProjectFolderItem : Object, Code.FolderInterface, Code.FolderM public Code.ProjectList view { get; construct; } // For convenience public GLib.File gfile { get { return file.file; }} + public string secondary_text { get; private set; } private static Icon added_icon; private static Icon modified_icon; @@ -74,13 +75,14 @@ public class Code.ProjectFolderItem : Object, Code.FolderInterface, Code.FolderM construct { path = file.path; + name = file.name; monitored_repo = Scratch.Services.GitManager.get_instance ().add_project (this); git_manager = Scratch.Services.GitManager.get_instance (); folder_tree = new Code.FolderTree (path); - notify["name"].connect (branch_or_name_changed); + file.notify["name"].connect (branch_or_name_changed); if (monitored_repo != null) { monitored_repo.branch_changed.connect (branch_or_name_changed); - monitored_repo.ignored_changed.connect ((deprioritize_git_ignored)); + // monitored_repo.ignored_changed.connect ((deprioritize_git_ignored)); // monitored_repo.file_status_change.connect (() => update_item_status (null)); monitored_repo.update_status_map (); monitored_repo.branch_changed (); @@ -353,24 +355,25 @@ public class Code.ProjectFolderItem : Object, Code.FolderInterface, Code.FolderM // }); // } + // This assumes the descendant exists public bool contains_file (GLib.File descendant) { return file.file.get_relative_path (descendant) != null; } - private void deprioritize_git_ignored () requires (monitored_repo != null) { - visible_item_list.@foreach ((visible_item) => { - var item = visible_item.item; - try { - if (monitored_repo.path_is_ignored (visible_item.rel_path)) { - item.text = Markup.printf_escaped ("%s", item.name); - } else { - item.text = item.name; - } - } catch (Error e) { - warning ("An error occurred while checking if item '%s' is git-ignored: %s", item.name, e.message); - } - }); - } + // private void deprioritize_git_ignored () requires (monitored_repo != null) { + // visible_item_list.@foreach ((visible_item) => { + // var item = visible_item.item; + // try { + // if (monitored_repo.path_is_ignored (visible_item.rel_path)) { + // deprioritize = true; + // } else { + + // } + // } catch (Error e) { + // warning ("An error occurred while checking if item '%s' is git-ignored: %s", item.name, e.message); + // } + // }); + // } public bool checkout_branch_ref (Ggit.Ref branch_ref) { if (branch_ref.is_branch ()) { @@ -720,16 +723,13 @@ public class Code.ProjectFolderItem : Object, Code.FolderInterface, Code.FolderM } private void branch_or_name_changed () { + name = file.name; if (monitored_repo != null) { //As SourceList items are not widgets we have to use markup to change appearance of text. if (monitored_repo.head_is_branch) { - name = "%s\n%s".printf ( - file.name, monitored_repo.branch_name - ); + secondary_text = monitored_repo.branch_name; } else { //Distinguish detached heads visually - name = "%s\n %s".printf ( - file.name, monitored_repo.branch_name - ); + secondary_text = _("%s (detached)").printf (monitored_repo.branch_name); } } } diff --git a/src/FolderManager/ProjectList.vala b/src/FolderManager/ProjectList.vala index 98e781ef67..322a1f3db9 100644 --- a/src/FolderManager/ProjectList.vala +++ b/src/FolderManager/ProjectList.vala @@ -85,7 +85,7 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { public string title { get; set; } private Gtk.ScrolledWindow scrolled_window; - private Gtk.ListView list_view; + private Gtk.ListBox list_box; private GLib.ListStore list_store; private Gtk.NoSelection selection_model; @@ -108,8 +108,8 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { settings = new GLib.Settings ("io.elementary.code.folder-manager"); list_store = new ListStore (typeof (ProjectFolderItem)); selection_model = new Gtk.NoSelection (list_store); - var list_factory = new Gtk.SignalListItemFactory (); - list_view = new Gtk.ListView (selection_model, list_factory); + list_box = new Gtk.ListBox (); + list_box.bind_model (selection_model, create_widget); actions = new SimpleActionGroup (); actions.add_action_entries (ACTION_ENTRIES, this); @@ -119,27 +119,7 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { order_folders (); }); - child = list_view; - - list_factory.setup.connect ((obj) => { - var listitem = (Gtk.ListItem) obj; - // create_listitem_child (listitem); - // By default just create a use a label (not expandable) - }); - list_factory.teardown.connect ((obj) => { - var listitem = (Gtk.ListItem) obj; - // teardown_listitem_child (listitem); - }); - list_factory.bind.connect ((obj) => { - var listitem = (Gtk.ListItem) obj; - var data = (Code.ProjectFolderItem) (listitem.item); - // bind_data_to_row (data, listitem); - }); - list_factory.unbind.connect ((obj) => { - var listitem = (Gtk.ListItem) obj; - var data = (Code.ProjectFolderItem) (listitem.item); - // unbind_data_from_row (data, listitem); - }); + child = list_box; } public async void restore_saved_state () { @@ -149,8 +129,10 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { } public void open_project_folder (File folder) { + warning ("open project folder"); ProjectFolderItem? listitem; if (is_existing_project_path (folder.path, out listitem)) { + warning ("exists - expanding"); listitem.expand (); return; //TODO Should we expand here? } @@ -263,7 +245,7 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { bool expand = false // Whether to expsnd to show found item // GLib.File? target_file = null // Alternatively find this file ) { - +warning ("Project list find path %s", path); // var target = target_file ?? GLib.File.new_for_path (path); FolderManagerItemInterface? matched_item = null; iterate_children ((listitem) => { @@ -325,6 +307,7 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { // } public FolderManagerItemInterface? expand_to_path (string path) { + warning ("expand to path"); return find_path (path, true); } @@ -599,6 +582,7 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { // Only call when path is known to be a new project private async void add_new_project_folder (string path, bool expand, bool restoring = false) { + warning ("add new project folder async"); var folder = new File (path); if (!folder.is_valid_directory) { warning ("Cannot open invalid directory."); @@ -623,7 +607,9 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { return Code.TreeList.ITERATE_CONTINUE; }); + warning ("done iterate children"); if (parents.length () > 0 || children.length () > 0) { + warning ("oarent or children"); assert (parents.length () <= 1); assert (parents.length () == 0 || children.length () == 0); var dialog = new Scratch.Dialogs.CloseProjectsConfirmationDialog ( @@ -660,9 +646,11 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { } } + warning ("before open folder in idle"); // Process any closed signals emitted before proceeding Idle.add (() => { var new_item = new ProjectFolderItem (folder, this); + warning ("appending item to store"); list_store.append (new_item); if (expand) { new_item.expand (); @@ -825,4 +813,15 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { item = (ProjectFolderItem?) (list_store.get_object (pos++)); } while (item != null && cb (item)); } + + private Gtk.Widget create_widget (Object obj) { + var project_item = (ProjectFolderItem) obj; + var label = new Granite.HeaderLabel ("") { + label = project_item.name, + secondary_text = "Branch name goes here" + // secondary_text = project_item.secondary_text + }; + + return label; + } } diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 9a1162f833..9030f00221 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -463,9 +463,9 @@ namespace Scratch { sidebar = new Code.Sidebar (); - // folder_manager_view = new Code.FileView (plugins); + folder_manager_view = new Code.ProjectList (); - // sidebar.add_tab (folder_manager_view); + sidebar.add_tab (folder_manager_view); // sidebar.add_tab (new Granite.HeaderLabel ("Dummy Sidebar")); // folder_manager_view.file_activate.connect ((file) => { @@ -745,6 +745,7 @@ namespace Scratch { } public void open_folder_as_project (File folder) { + warning ("opening %s as project", folder.get_path ()); var foldermanager_file = new Code.File (folder.get_path ()); folder_manager_view.open_project_folder (foldermanager_file); } @@ -793,12 +794,12 @@ namespace Scratch { hp1.set_position (Scratch.saved_state.get_int ("hp1-size")); vp.set_position (Scratch.saved_state.get_int ("vp-size")); // Ensure foldermanager finishes loading projects before start opening documents - // folder_manager_view.restore_saved_state.begin ((obj, res) => { - // folder_manager_view.restore_saved_state.end (res); + folder_manager_view.restore_saved_state.begin ((obj, res) => { + folder_manager_view.restore_saved_state.end (res); if (restore_docs) { restore_opened_documents.begin (); } - // }); + }); } private void create_unsaved_documents_directory () { @@ -939,6 +940,7 @@ namespace Scratch { } private void action_open () { + warning ("action open"); var all_files_filter = new Gtk.FileFilter (); all_files_filter.set_filter_name (_("All files")); all_files_filter.add_pattern ("*"); @@ -967,10 +969,12 @@ namespace Scratch { } file_chooser.response.connect ((res) => { + warning ("got response %s", res.to_string ()); var files = file_chooser.get_files (); // Returns ListModel of GFile objects // var uris = file_chooser.get_uris (); file_chooser.destroy (); // Close now so it does not stay open during lengthy or failed loading if (res == Gtk.ResponseType.ACCEPT) { + warning ("ACCEPT"); var index = 0; var obj = files.get_item (index++); while (obj != null) { @@ -1007,6 +1011,7 @@ namespace Scratch { } private void choose_folder () { + warning ("choose folder"); var chooser = new Gtk.FileChooserNative ( "Select a folder.", this, Gtk.FileChooserAction.SELECT_FOLDER, _("_Open"), @@ -1014,18 +1019,17 @@ namespace Scratch { ); chooser.select_multiple = true; - chooser.response.connect ((res) => { var files = chooser.get_files (); chooser.destroy (); if (res == Gtk.ResponseType.ACCEPT) { -var index = 0; + var index = 0; var obj = files.get_item (index++); while (obj != null) { - // var file = (GLib.File) obj; - // var foldermanager_file = new Code.File (file.get_path ()); - // folder_manager_view.open_folder_as_project (foldermanager_file); - // obj = files.get_item (index++); + var file = (GLib.File) obj; + var foldermanager_file = new Code.File (file.get_path ()); + folder_manager_view.open_project_folder (foldermanager_file); + obj = files.get_item (index++); } } }); @@ -1038,7 +1042,7 @@ var index = 0; if (path == "") { choose_folder (); } else { - // folder_manager_view.open_folder_as_project (new Code.File (path)); + folder_manager_view.open_project_folder (new Code.File (path)); } } @@ -1118,7 +1122,7 @@ var index = 0; } private void action_collapse_all_folders () { - // folder_manager_view.collapse_all (); + folder_manager_view.collapse_all (); } private void action_save () { diff --git a/src/Services/GitManager.vala b/src/Services/GitManager.vala index 2ccbf2b6ab..65c32a81c7 100644 --- a/src/Services/GitManager.vala +++ b/src/Services/GitManager.vala @@ -45,6 +45,7 @@ namespace Scratch.Services { } construct { + warning ("git manager construct"); // Used to populate the ChooseProject popover in sorted order //TODO This seems to duplicate the sidebar store - can we combine? project_liststore = new ListStore (typeof (Code.ProjectFolderItem)); @@ -52,19 +53,34 @@ namespace Scratch.Services { } public MonitoredRepository? add_project (Code.ProjectFolderItem root_folder) { + warning ("add project to git, root folder path %s", root_folder == null ? "Null" : root_folder.path); var root_path = root_folder.path; MonitoredRepository? monitored_repo = null; uint position; if (project_liststore.find_with_equal_func ( root_folder, - (a, b) => { return ((Code.FolderManagerItem) a).equal ((Code.FolderManagerItem) b); }, + (oa, ob) => { + warning ("compare"); + if (oa == null || ob == null) { + return false; + } + + warning ("neither null"); + var a = (Code.ProjectFolderItem) oa; + var b = (Code.ProjectFolderItem) ob; + + warning ("a.name %s", a.name); + warning ("b.name %s", b.name); + return str_equal (a.name, b.name); + }, out position )) { +warning ("found"); var repo = project_gitrepo_map.@get (root_path); return repo; } - +warning ("not found"); try { var git_repo = Ggit.Repository.open (root_folder.file.file); if (!project_gitrepo_map.has_key (root_path)) { diff --git a/src/Widgets/Sidebar.vala b/src/Widgets/Sidebar.vala index eac7b768a3..3ee1d14c9f 100644 --- a/src/Widgets/Sidebar.vala +++ b/src/Widgets/Sidebar.vala @@ -44,6 +44,9 @@ public class Code.Sidebar : Gtk.Box { construct { orientation = Gtk.Orientation.VERTICAL; + vexpand = true; + hexpand = true; + add_css_class (Granite.STYLE_CLASS_SIDEBAR); choose_project_button = new Code.ChooseProjectButton () { @@ -62,7 +65,9 @@ public class Code.Sidebar : Gtk.Box { }; headerbar.add_css_class (Granite.STYLE_CLASS_FLAT); - stack = new Gtk.Stack (); + stack = new Gtk.Stack () { + vexpand = true + }; stack.transition_type = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT; var overlay = new Gtk.Overlay () { @@ -74,7 +79,9 @@ public class Code.Sidebar : Gtk.Box { stack_switcher.visible = false; stack_switcher.stack = stack; - var actionbar = new Gtk.ActionBar (); + var actionbar = new Gtk.ActionBar () { + valign = END + }; actionbar.add_css_class (Granite.STYLE_CLASS_FLAT); var collapse_all_menu_item = new GLib.MenuItem (_("Collapse All"), Scratch.MainWindow.ACTION_PREFIX @@ -96,7 +103,8 @@ public class Code.Sidebar : Gtk.Box { project_menu_model = project_menu; var label = new Gtk.Label ( _("Manage project folders…")) { - halign = START + halign = START, + valign = CENTER }; var project_menu_button = new Gtk.MenuButton () { hexpand = true, From ce66a0e3643d028cba89f7503c6c1e20398235ba Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 14 Jul 2026 19:55:53 +0100 Subject: [PATCH 10/21] Add expander --- src/FolderManager/ProjectFolderItem.vala | 2 +- src/FolderManager/ProjectList.vala | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/FolderManager/ProjectFolderItem.vala b/src/FolderManager/ProjectFolderItem.vala index 65d7a379a0..54013e3958 100644 --- a/src/FolderManager/ProjectFolderItem.vala +++ b/src/FolderManager/ProjectFolderItem.vala @@ -37,6 +37,7 @@ public class Code.ProjectFolderItem : Object, Code.FolderInterface, Code.FolderM public FileMonitor monitor { get; set; } public ListStore? child_model { get; set; default = null;} + public Code.FolderTree folder_tree; public Code.File file { get; construct; } public Code.ProjectList view { get; construct; } // For convenience @@ -61,7 +62,6 @@ public class Code.ProjectFolderItem : Object, Code.FolderInterface, Code.FolderM // Cache the visible item in the project. (Excludes items loaded but in collapsed folder) private List visible_item_list = null; // Child widget shows tree of project files and folders - private Code.FolderTree folder_tree; private Ggit.Repository? git_repo { get { diff --git a/src/FolderManager/ProjectList.vala b/src/FolderManager/ProjectList.vala index 322a1f3db9..34ecae9f67 100644 --- a/src/FolderManager/ProjectList.vala +++ b/src/FolderManager/ProjectList.vala @@ -822,6 +822,11 @@ warning ("Project list find path %s", path); // secondary_text = project_item.secondary_text }; - return label; + var expander = new Gtk.Expander ("") { + label_widget = label, + child = project_item.folder_tree + }; + + return expander; } } From d64c17e4416f77799ba4788b31726398db44f0d2 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 16 Jul 2026 12:51:33 +0100 Subject: [PATCH 11/21] Fill first level --- src/FolderManager/FolderTree.vala | 9 +++++++ src/FolderManager/ProjectList.vala | 5 +++- src/SymbolPane/C/CtagsSymbolOutline.vala | 4 +-- src/SymbolPane/SymbolOutline.vala | 7 +++++- src/SymbolPane/Vala/ValaSymbolOutline.vala | 4 +-- src/Widgets/ChooseProjectButton.vala | 1 + src/Widgets/TreeList/TreeList.vala | 29 ++++++++++++++-------- 7 files changed, 43 insertions(+), 16 deletions(-) diff --git a/src/FolderManager/FolderTree.vala b/src/FolderManager/FolderTree.vala index 57e710ab7b..3baff759b5 100644 --- a/src/FolderManager/FolderTree.vala +++ b/src/FolderManager/FolderTree.vala @@ -92,6 +92,15 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { }); child = tree_list; + + var cfile = new Code.File (root_path); + foreach (var child_file in cfile.children) { + if (child_file.is_valid_directory) { + tree_list.add_root_item (new FolderItem (child_file, this)); + } else if (child_file.is_valid_textfile) { + tree_list.add_root_item (new FileItem (child_file, this)); + } + } } public bool contains_file (GLib.File gfile) { diff --git a/src/FolderManager/ProjectList.vala b/src/FolderManager/ProjectList.vala index 34ecae9f67..b2e30e0af5 100644 --- a/src/FolderManager/ProjectList.vala +++ b/src/FolderManager/ProjectList.vala @@ -119,7 +119,10 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { order_folders (); }); - child = list_box; + scrolled_window = new Gtk.ScrolledWindow () { + child = list_box, + }; + child = scrolled_window; } public async void restore_saved_state () { diff --git a/src/SymbolPane/C/CtagsSymbolOutline.vala b/src/SymbolPane/C/CtagsSymbolOutline.vala index 3f0e383e01..7d5c6402fe 100644 --- a/src/SymbolPane/C/CtagsSymbolOutline.vala +++ b/src/SymbolPane/C/CtagsSymbolOutline.vala @@ -263,11 +263,11 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin } Idle.add (() => { - double adjustment_value = tree_list.vadjustment.value; + double adjustment_value = vadj.value; tree_list.remove_all (); tree_list.add_root_item (new_root); // store.root.expand_all (); - tree_list.vadjustment.set_value (adjustment_value); + vadj.set_value (adjustment_value); destroy_root (root); root = new_root; diff --git a/src/SymbolPane/SymbolOutline.vala b/src/SymbolPane/SymbolOutline.vala index afdd8d581c..dc1a9926d2 100644 --- a/src/SymbolPane/SymbolOutline.vala +++ b/src/SymbolPane/SymbolOutline.vala @@ -80,6 +80,8 @@ public class Scratch.Services.SymbolOutline : Gtk.Box { protected Gee.HashMap checks; protected Gtk.SearchEntry search_entry; protected Code.TreeList tree_list; + protected Gtk.ScrolledWindow scrolled_window; + protected Gtk.Adjustment vadj { get { return scrolled_window.vadjustment; }} protected Code.TreeListItem root; protected Gtk.CssProvider source_list_style_provider; protected bool took_too_long; @@ -119,6 +121,9 @@ public class Scratch.Services.SymbolOutline : Gtk.Box { construct { tree_list = new Code.TreeList (); + scrolled_window = new Gtk.ScrolledWindow () { + child = tree_list + }; symbol_action_group = new SimpleActionGroup (); insert_action_group (ACTION_GROUP, symbol_action_group); @@ -182,7 +187,7 @@ public class Scratch.Services.SymbolOutline : Gtk.Box { tool_box.append (search_entry); tool_box.append (stack); append (tool_box); - append (tree_list); + append (scrolled_window); set_up_css (); realize.connect (() => { diff --git a/src/SymbolPane/Vala/ValaSymbolOutline.vala b/src/SymbolPane/Vala/ValaSymbolOutline.vala index f01015880a..b2ba2c5c90 100644 --- a/src/SymbolPane/Vala/ValaSymbolOutline.vala +++ b/src/SymbolPane/Vala/ValaSymbolOutline.vala @@ -104,7 +104,7 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline if (!cancellable.is_cancelled () || took_too_long) { Idle.add (() => { - double adjustment_value = tree_list.vadjustment.value; + double adjustment_value = vadj.value; // var root_children = root.children; // Keep reference to children for later destruction // tree_list.clear (); // This does not destroy children but disconnects signals - avoids terminal warnings // foreach (var child in root_children) { // Destroy items after clearing list to avoid memory leak @@ -127,7 +127,7 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline tree_list.expand_all (null); add_tooltips (root); - tree_list.vadjustment.set_value (adjustment_value); + vadj.set_value (adjustment_value); return Source.REMOVE; }); } diff --git a/src/Widgets/ChooseProjectButton.vala b/src/Widgets/ChooseProjectButton.vala index 9a8c85cde3..4cbf30c4a2 100644 --- a/src/Widgets/ChooseProjectButton.vala +++ b/src/Widgets/ChooseProjectButton.vala @@ -51,6 +51,7 @@ public class Code.ChooseProjectButton : Gtk.Box { box.append (label_widget); box.append (cloning_spinner); menu_button = new Gtk.MenuButton () { + hexpand = true, child = box }; menu_button.set_parent (this); diff --git a/src/Widgets/TreeList/TreeList.vala b/src/Widgets/TreeList/TreeList.vala index f273aec4e1..4fb0aa7213 100644 --- a/src/Widgets/TreeList/TreeList.vala +++ b/src/Widgets/TreeList/TreeList.vala @@ -7,17 +7,17 @@ public sealed class Code.TreeList : Granite.Bin { public signal void item_activated (TreeListItem item); - public Gtk.Adjustment vadjustment { - get { - return scrolled_window.vadjustment; - } - } + // public Gtk.Adjustment vadjustment { + // get { + // return scrolled_window.vadjustment; + // } + // } public bool activate_on_single_click { get; set; default = true;} // public TreeListItem? selected { get; set; } // Selection handled by SelectionModel - private Gtk.ScrolledWindow scrolled_window; + // private Gtk.ScrolledWindow scrolled_window; private Gtk.ListView list_view; private GLib.ListStore root_model; private Gtk.TreeListModel tree_model; @@ -33,12 +33,14 @@ public sealed class Code.TreeList : Granite.Bin { }); var tree_list_factory = new Gtk.SignalListItemFactory (); var tree_header_factory = new Gtk.SignalListItemFactory (); - list_view = new Gtk.ListView (selection_model, tree_list_factory) { - header_factory = tree_header_factory - }; + // list_view = new Gtk.ListView (selection_model, tree_list_factory) { + // header_factory = tree_header_factory + // }; + list_view = new Gtk.ListView (selection_model, tree_list_factory); bind_property ("activate-on-single-click", list_view, "single-click-activate", BIDIRECTIONAL | SYNC_CREATE); list_view.activate.connect ((pos) => { + warning ("list view activate %u", pos); item_activated ((TreeListItem) selection_model.get_item (pos)); }); // LIST ITEM FACTORY HANDLERS @@ -83,6 +85,12 @@ public sealed class Code.TreeList : Granite.Bin { tree_header_factory.unbind.connect (() => {}); tree_header_factory.teardown.connect (() => {}); + // scrolled_window = new Gtk.ScrolledWindow () { + // child = list_view, + // min_content_height = 400 + // }; + + // child = scrolled_window; child = list_view; } @@ -90,7 +98,8 @@ public sealed class Code.TreeList : Granite.Bin { var label = new Gtk.Label ("") { halign = START, }; - label.add_css_class (Granite.STYLE_CLASS_H4_LABEL); + // label.add_css_class (Granite.STYLE_CLASS_H4_LABEL); + label.add_css_class ("menu-item"); item.child = label; } protected virtual void teardown_listitem_child (Gtk.ListItem item) { From 04334d45afff9a5e2563dd4e54c4b720c8cd0127 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 16 Jul 2026 19:51:17 +0100 Subject: [PATCH 12/21] Add icon, badge expander, close button --- src/FolderManager/ProjectFolderItem.vala | 12 ++-- src/FolderManager/ProjectList.vala | 60 ++++++++++++------- src/Widgets/TreeList/TreeList.vala | 73 ++++++++++++++++-------- 3 files changed, 95 insertions(+), 50 deletions(-) diff --git a/src/FolderManager/ProjectFolderItem.vala b/src/FolderManager/ProjectFolderItem.vala index 54013e3958..6fa9f5cb55 100644 --- a/src/FolderManager/ProjectFolderItem.vala +++ b/src/FolderManager/ProjectFolderItem.vala @@ -44,12 +44,12 @@ public class Code.ProjectFolderItem : Object, Code.FolderInterface, Code.FolderM public GLib.File gfile { get { return file.file; }} public string secondary_text { get; private set; } - private static Icon added_icon; - private static Icon modified_icon; - static construct { - added_icon = new ThemedIcon ("emblem-git-new-symbolic"); - modified_icon = new ThemedIcon ("emblem-git-modified-symbolic"); - } + // private static Icon added_icon; + // private static Icon modified_icon; + // static construct { + // added_icon = new ThemedIcon ("emblem-git-new-symbolic"); + // modified_icon = new ThemedIcon ("emblem-git-modified-symbolic"); + // } private struct VisibleItem { public string rel_path; diff --git a/src/FolderManager/ProjectList.vala b/src/FolderManager/ProjectList.vala index b2e30e0af5..706c7258fa 100644 --- a/src/FolderManager/ProjectList.vala +++ b/src/FolderManager/ProjectList.vala @@ -102,6 +102,10 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { // } construct { + actions = new SimpleActionGroup (); + actions.add_action_entries (ACTION_ENTRIES, this); + insert_action_group (ACTION_GROUP, actions); + //For Code.PaneSwitcher iterface icon_name = "not-exist"; title = "Projects"; @@ -109,11 +113,41 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { list_store = new ListStore (typeof (ProjectFolderItem)); selection_model = new Gtk.NoSelection (list_store); list_box = new Gtk.ListBox (); - list_box.bind_model (selection_model, create_widget); - - actions = new SimpleActionGroup (); - actions.add_action_entries (ACTION_ENTRIES, this); - insert_action_group (ACTION_GROUP, actions); + list_box.add_css_class (Granite.STYLE_CLASS_SIDEBAR); + list_box.bind_model ( + selection_model, + (obj) => { + var project_item = (ProjectFolderItem) obj; + var label = new Granite.HeaderLabel ("") { + label = project_item.name, + secondary_text = "Branch name goes here", + hexpand = true + }; + + var close_button = new Gtk.Button () { + action_name = ACTION_PREFIX + ACTION_CLOSE_PROJECT_FOLDER, + action_target = project_item.path, + icon_name = "process-stop", + valign = START, + margin_top = 12 + }; + + var box = new Gtk.Box (HORIZONTAL, 6) { + hexpand = true + }; + var expander = new Gtk.Expander ("") { + label_widget = label, + child = project_item.folder_tree, + hexpand = true + }; + + box.prepend (expander); + box.append (close_button); + + + return box; + } + ); Scratch.saved_state.changed["order-folders"].connect (() => { order_folders (); @@ -816,20 +850,4 @@ warning ("Project list find path %s", path); item = (ProjectFolderItem?) (list_store.get_object (pos++)); } while (item != null && cb (item)); } - - private Gtk.Widget create_widget (Object obj) { - var project_item = (ProjectFolderItem) obj; - var label = new Granite.HeaderLabel ("") { - label = project_item.name, - secondary_text = "Branch name goes here" - // secondary_text = project_item.secondary_text - }; - - var expander = new Gtk.Expander ("") { - label_widget = label, - child = project_item.folder_tree - }; - - return expander; - } } diff --git a/src/Widgets/TreeList/TreeList.vala b/src/Widgets/TreeList/TreeList.vala index 4fb0aa7213..5f0283bfb6 100644 --- a/src/Widgets/TreeList/TreeList.vala +++ b/src/Widgets/TreeList/TreeList.vala @@ -32,11 +32,12 @@ public sealed class Code.TreeList : Granite.Bin { warning ("sections changed pos %u, n items %u", pos, n_items); }); var tree_list_factory = new Gtk.SignalListItemFactory (); - var tree_header_factory = new Gtk.SignalListItemFactory (); + // var tree_header_factory = new Gtk.SignalListItemFactory (); // list_view = new Gtk.ListView (selection_model, tree_list_factory) { // header_factory = tree_header_factory // }; list_view = new Gtk.ListView (selection_model, tree_list_factory); + list_view.add_css_class ("compact"); bind_property ("activate-on-single-click", list_view, "single-click-activate", BIDIRECTIONAL | SYNC_CREATE); list_view.activate.connect ((pos) => { @@ -66,24 +67,24 @@ public sealed class Code.TreeList : Granite.Bin { unbind_data_from_row (data, treelistrow, listitem); }); - // HEADER FACTORY HANDLERS - tree_header_factory.setup.connect ((obj) => { - // By default create header and subheader, expandable - warning ("tree header setup"); - var listheader = (Gtk.ListHeader) obj; - create_headeritem_child (listheader); - }); - tree_header_factory.bind.connect ((obj) => { - warning ("tree header bind"); - var listheader = (Gtk.ListHeader) obj; - - warning ("listheader item is a %s", listheader.item.get_type ().name ()); - var row = (Gtk.TreeListRow) listheader.item; - var data = (Code.TreeListItem) row.item; - bind_data_to_header (data, row, listheader); - }); - tree_header_factory.unbind.connect (() => {}); - tree_header_factory.teardown.connect (() => {}); + // // HEADER FACTORY HANDLERS + // tree_header_factory.setup.connect ((obj) => { + // // By default create header and subheader, expandable + // warning ("tree header setup"); + // var listheader = (Gtk.ListHeader) obj; + // create_headeritem_child (listheader); + // }); + // tree_header_factory.bind.connect ((obj) => { + // warning ("tree header bind"); + // var listheader = (Gtk.ListHeader) obj; + + // warning ("listheader item is a %s", listheader.item.get_type ().name ()); + // var row = (Gtk.TreeListRow) listheader.item; + // var data = (Code.TreeListItem) row.item; + // bind_data_to_header (data, row, listheader); + // }); + // tree_header_factory.unbind.connect (() => {}); + // tree_header_factory.teardown.connect (() => {}); // scrolled_window = new Gtk.ScrolledWindow () { // child = list_view, @@ -97,10 +98,26 @@ public sealed class Code.TreeList : Granite.Bin { protected virtual void create_listitem_child (Gtk.ListItem item) { var label = new Gtk.Label ("") { halign = START, + hexpand = true }; // label.add_css_class (Granite.STYLE_CLASS_H4_LABEL); - label.add_css_class ("menu-item"); - item.child = label; + label.add_css_class (Granite.STYLE_CLASS_SMALL_LABEL); + var primary_image = new Gtk.Image.from_icon_name (null); + var secondary_image = new Gtk.Image.from_icon_name (null); + var badge_label = new Gtk.Label (""); + badge_label.add_css_class (Granite.STYLE_CLASS_MENUITEM); + var box = new Gtk.Box (HORIZONTAL, 6) { + hexpand = true + }; + box.append (primary_image); + box.append (label); + box.append (secondary_image); + box.append (badge_label); + badge_label.add_css_class (Granite.STYLE_CLASS_SMALL_LABEL); + var expander = new Gtk.TreeExpander () { + child = box + }; + item.child = expander; } protected virtual void teardown_listitem_child (Gtk.ListItem item) { // Must be paired with create_listitem child @@ -110,8 +127,18 @@ public sealed class Code.TreeList : Granite.Bin { Gtk.TreeListRow row, Gtk.ListItem item) { // Must be matched with create item widget when overriding - var label = (Gtk.Label)(item.child); - label.label = data.text; + var expander = (Gtk.TreeExpander) (item.child); + expander.set_list_row (row); + expander.hide_expander = !data.is_expandable; + var box = (Gtk.Box)(expander.child); //TODO use expander? + var primary_image = (Gtk.Image)(box.get_first_child ()); + var name_label = (Gtk.Label)(primary_image.get_next_sibling ()); + var secondary_image = (Gtk.Image) (name_label.get_next_sibling ()); + var badge_label = (Gtk.Label) secondary_image.get_next_sibling (); + primary_image.icon_name = data.is_expandable ? "folder" : "text-x-vala"; //TODO Icon based on file type + secondary_image.icon_name = "emblem-default"; //TODO Different icon + badge_label.label = "32"; //TODO Different icon + name_label.label = data.text; } protected virtual void unbind_data_from_row ( TreeListItem data, From 1e4ae856d4ac1297fa43832456e3d8d5fc4da1b1 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 16 Jul 2026 20:07:42 +0100 Subject: [PATCH 13/21] Put project expander on right --- src/FolderManager/ProjectList.vala | 38 ++++++++++++++++++------------ 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/FolderManager/ProjectList.vala b/src/FolderManager/ProjectList.vala index 706c7258fa..13aac437a4 100644 --- a/src/FolderManager/ProjectList.vala +++ b/src/FolderManager/ProjectList.vala @@ -124,28 +124,36 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { hexpand = true }; - var close_button = new Gtk.Button () { - action_name = ACTION_PREFIX + ACTION_CLOSE_PROJECT_FOLDER, - action_target = project_item.path, - icon_name = "process-stop", - valign = START, - margin_top = 12 + // var close_button = new Gtk.Button () { + // action_name = ACTION_PREFIX + ACTION_CLOSE_PROJECT_FOLDER, + // action_target = project_item.path, + // icon_name = "process-stop", + // valign = START, + // margin_top = 12 + // }; + + var grid = new Gtk.Grid (); + var expander = new Gtk.Expander ("") { + // label_widget = label, + // child = project_item.folder_tree, + hexpand = false, + valign = CENTER }; - var box = new Gtk.Box (HORIZONTAL, 6) { - hexpand = true - }; - var expander = new Gtk.Expander ("") { - label_widget = label, + var revealer = new Gtk.Revealer () { child = project_item.folder_tree, - hexpand = true + reveal_child = false }; + expander.notify["expanded"].connect (() => { + revealer.reveal_child = expander.expanded; + }); - box.prepend (expander); - box.append (close_button); + grid.attach (label, 0, 0); + grid.attach (expander, 1, 0); + grid.attach (revealer, 0, 1, 2, 1); - return box; + return grid; } ); From 196c637ddbae05d02b95f5a49666cdafbe637a6d Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Fri, 17 Jul 2026 12:56:01 +0100 Subject: [PATCH 14/21] Expand on activate for folders --- src/FolderManager/FolderItem.vala | 26 +++--- src/FolderManager/FolderTree.vala | 5 + src/FolderManager/ProjectList.vala | 16 ---- src/Services/GitManager.vala | 2 - src/Widgets/TreeList/TreeList.vala | 122 +++++++++++-------------- src/Widgets/TreeList/TreeListItem.vala | 9 ++ 6 files changed, 79 insertions(+), 101 deletions(-) diff --git a/src/FolderManager/FolderItem.vala b/src/FolderManager/FolderItem.vala index 0da8fa6a67..72c4395de5 100644 --- a/src/FolderManager/FolderItem.vala +++ b/src/FolderManager/FolderItem.vala @@ -18,6 +18,7 @@ * Authored by: Julien Spautz , Andrei-Costin Zisu */ +// Folder interface shared with Project folder items used in ListBox public interface Code.FolderInterface : Object { public abstract bool is_expanded { get; set; } public abstract ListStore? child_model { get; set; } @@ -39,13 +40,6 @@ public interface Code.FolderInterface : Object { public delegate bool IterateChildrenCallback (Object obj); public virtual void iterate_children (IterateChildrenCallback cb) {} - // { - // uint pos = 0; - // Object child; - // do { - // child = list_model.get_object (pos++); - // } while (child != null && cb (child)); - // } } public class Code.FolderItem : FolderManagerItem, Code.FolderInterface { // Ultimately a Code.TreeListItem @@ -58,7 +52,7 @@ public class Code.FolderItem : FolderManagerItem, Code.FolderInterface { // Ulti } // Code.FolderInterface - public bool is_expanded { get; set; } + // protected bool is_expanded { get; set; } protected FileMonitor monitor { get; set; } private const uint RENAME_AFTER_NEW_DELAY_MSEC = 500; @@ -87,6 +81,7 @@ public class Code.FolderItem : FolderManagerItem, Code.FolderInterface { // Ulti has_dummy = true; init_monitor_directory (file.file); + // Signal when expanded notify["is-expanded"].connect (on_toggled); } @@ -146,18 +141,21 @@ public class Code.FolderItem : FolderManagerItem, Code.FolderInterface { // Ulti private void after_children_loaded () { children_loaded = true; - var root = get_root_folder (); - if (root != null) { - root.after_child_folder_loaded (this); //Updates child status emblens - } + // var root = get_root_folder (); + // if (root != null) { + // root.after_child_folder_loaded (this); //Updates child status emblens + // } children_finished_loading (); } private void on_toggled () { + warning ("%s - expanded %s", this.name, this.is_expanded.to_string ()); // if (is_expanded) { + // warning ("load children"); // load_children_async.begin (); - // } else { + // } + // else { // var root = get_root_folder (); // if (root != null) { // //When toggled closed, update status to reflect hidden contents @@ -546,6 +544,7 @@ public class Code.FolderItem : FolderManagerItem, Code.FolderInterface { // Ulti } public void on_add_template (string template_path) { + warning ("add template"); // Expand folder before trying to copy template so that child appears for renaming if (!is_expanded) { is_expanded = true; // causes async loading of children @@ -611,6 +610,7 @@ public class Code.FolderItem : FolderManagerItem, Code.FolderInterface { // Ulti } public void on_add_new (bool is_folder) { + warning ("add new"); if (!file.is_executable) { // This is necessary to avoid infinite loop below warning ("Unable to open parent folder"); diff --git a/src/FolderManager/FolderTree.vala b/src/FolderManager/FolderTree.vala index 3baff759b5..a18592b865 100644 --- a/src/FolderManager/FolderTree.vala +++ b/src/FolderManager/FolderTree.vala @@ -86,8 +86,13 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { // Convert ListView signal into file_activate tree_list.item_activated.connect ((item) => { + warning ("item activated"); if (item is FileItem) { file_activate (((FileItem) item).file); + } else if (item.is_expandable) { + warning ("toggle"); + // Toggle folder + item.is_expanded = !item.is_expanded; } }); diff --git a/src/FolderManager/ProjectList.vala b/src/FolderManager/ProjectList.vala index 13aac437a4..5e07ecaaf2 100644 --- a/src/FolderManager/ProjectList.vala +++ b/src/FolderManager/ProjectList.vala @@ -124,18 +124,8 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { hexpand = true }; - // var close_button = new Gtk.Button () { - // action_name = ACTION_PREFIX + ACTION_CLOSE_PROJECT_FOLDER, - // action_target = project_item.path, - // icon_name = "process-stop", - // valign = START, - // margin_top = 12 - // }; - var grid = new Gtk.Grid (); var expander = new Gtk.Expander ("") { - // label_widget = label, - // child = project_item.folder_tree, hexpand = false, valign = CENTER }; @@ -152,7 +142,6 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { grid.attach (expander, 1, 0); grid.attach (revealer, 0, 1, 2, 1); - return grid; } ); @@ -627,7 +616,6 @@ warning ("Project list find path %s", path); // Only call when path is known to be a new project private async void add_new_project_folder (string path, bool expand, bool restoring = false) { - warning ("add new project folder async"); var folder = new File (path); if (!folder.is_valid_directory) { warning ("Cannot open invalid directory."); @@ -652,9 +640,7 @@ warning ("Project list find path %s", path); return Code.TreeList.ITERATE_CONTINUE; }); - warning ("done iterate children"); if (parents.length () > 0 || children.length () > 0) { - warning ("oarent or children"); assert (parents.length () <= 1); assert (parents.length () == 0 || children.length () == 0); var dialog = new Scratch.Dialogs.CloseProjectsConfirmationDialog ( @@ -691,11 +677,9 @@ warning ("Project list find path %s", path); } } - warning ("before open folder in idle"); // Process any closed signals emitted before proceeding Idle.add (() => { var new_item = new ProjectFolderItem (folder, this); - warning ("appending item to store"); list_store.append (new_item); if (expand) { new_item.expand (); diff --git a/src/Services/GitManager.vala b/src/Services/GitManager.vala index 65c32a81c7..863a75c68e 100644 --- a/src/Services/GitManager.vala +++ b/src/Services/GitManager.vala @@ -45,7 +45,6 @@ namespace Scratch.Services { } construct { - warning ("git manager construct"); // Used to populate the ChooseProject popover in sorted order //TODO This seems to duplicate the sidebar store - can we combine? project_liststore = new ListStore (typeof (Code.ProjectFolderItem)); @@ -53,7 +52,6 @@ namespace Scratch.Services { } public MonitoredRepository? add_project (Code.ProjectFolderItem root_folder) { - warning ("add project to git, root folder path %s", root_folder == null ? "Null" : root_folder.path); var root_path = root_folder.path; MonitoredRepository? monitored_repo = null; uint position; diff --git a/src/Widgets/TreeList/TreeList.vala b/src/Widgets/TreeList/TreeList.vala index 5f0283bfb6..add6234182 100644 --- a/src/Widgets/TreeList/TreeList.vala +++ b/src/Widgets/TreeList/TreeList.vala @@ -42,7 +42,10 @@ public sealed class Code.TreeList : Granite.Bin { bind_property ("activate-on-single-click", list_view, "single-click-activate", BIDIRECTIONAL | SYNC_CREATE); list_view.activate.connect ((pos) => { warning ("list view activate %u", pos); - item_activated ((TreeListItem) selection_model.get_item (pos)); + var tree_row = ((Gtk.TreeListRow) selection_model.get_item (pos)); + var data = (Code.TreeListItem) (tree_row.item); + //Always activate regardless of whether expandable - symbol pane works differently to foldermanager + item_activated (data); }); // LIST ITEM FACTORY HANDLERS tree_list_factory.setup.connect ((obj) => { @@ -67,31 +70,6 @@ public sealed class Code.TreeList : Granite.Bin { unbind_data_from_row (data, treelistrow, listitem); }); - // // HEADER FACTORY HANDLERS - // tree_header_factory.setup.connect ((obj) => { - // // By default create header and subheader, expandable - // warning ("tree header setup"); - // var listheader = (Gtk.ListHeader) obj; - // create_headeritem_child (listheader); - // }); - // tree_header_factory.bind.connect ((obj) => { - // warning ("tree header bind"); - // var listheader = (Gtk.ListHeader) obj; - - // warning ("listheader item is a %s", listheader.item.get_type ().name ()); - // var row = (Gtk.TreeListRow) listheader.item; - // var data = (Code.TreeListItem) row.item; - // bind_data_to_header (data, row, listheader); - // }); - // tree_header_factory.unbind.connect (() => {}); - // tree_header_factory.teardown.connect (() => {}); - - // scrolled_window = new Gtk.ScrolledWindow () { - // child = list_view, - // min_content_height = 400 - // }; - - // child = scrolled_window; child = list_view; } @@ -117,6 +95,7 @@ public sealed class Code.TreeList : Granite.Bin { var expander = new Gtk.TreeExpander () { child = box }; + item.child = expander; } protected virtual void teardown_listitem_child (Gtk.ListItem item) { @@ -130,6 +109,7 @@ public sealed class Code.TreeList : Granite.Bin { var expander = (Gtk.TreeExpander) (item.child); expander.set_list_row (row); expander.hide_expander = !data.is_expandable; + data.expanded_binding = data.bind_property ("is-expanded", row, "expanded", BIDIRECTIONAL | SYNC_CREATE); var box = (Gtk.Box)(expander.child); //TODO use expander? var primary_image = (Gtk.Image)(box.get_first_child ()); var name_label = (Gtk.Label)(primary_image.get_next_sibling ()); @@ -146,61 +126,63 @@ public sealed class Code.TreeList : Granite.Bin { Gtk.ListItem item ) { //Must undo any signal connections etc made in bind_data_to_row - } - - protected virtual void create_headeritem_child (Gtk.ListHeader item) { - warning ("create headeritem child pos"); - var text_label = new Gtk.Label ("") { - halign = START, - }; - text_label.add_css_class (Granite.STYLE_CLASS_H3_LABEL); - var subtext_label = new Gtk.Label ("") { - halign = START - }; - subtext_label.add_css_class (Granite.STYLE_CLASS_SMALL_LABEL); - var box = new Gtk.Box (VERTICAL, 0); - box.append (text_label); - box.append (subtext_label); - var expander = new Gtk.TreeExpander () { - child = box, - }; - item.child = expander; - } - protected virtual void teardown_headeritem_child () { - // Must be paired with create_listitem child - } - protected virtual void bind_data_to_header ( - TreeListItem data, - Gtk.TreeListRow row, - Gtk.ListHeader item - ) { - - var expander = (Gtk.TreeExpander) item.child; - var box = (Gtk.Box) expander.get_child (); - var text_label = (Gtk.Label) box.get_first_child (); - var subtext_label = (Gtk.Label) text_label.get_next_sibling (); - text_label.label = data.text; - subtext_label.label = data.sub_text; - warning ("bind data to header %s", text_label.label); - } - - protected virtual void unbind_data_from_headerrow ( - TreeListItem data, - Gtk.TreeListRow row, - Gtk.ListItem item - ) { - //Must undo any signal connections etc made in bind_data_to_row + data.expanded_binding.unbind (); } public ListModel? create_model_func (Object item) { var data = (TreeListItem) item; if (data.is_expandable && data.child_model == null) { + warning ("data %s is expandable and no model", data.text); data.child_model = new GLib.ListStore (typeof (TreeListItem)); } return data.child_model; } + // protected virtual void create_headeritem_child (Gtk.ListHeader item) { + // warning ("create headeritem child pos"); + // var text_label = new Gtk.Label ("") { + // halign = START, + // }; + // text_label.add_css_class (Granite.STYLE_CLASS_H3_LABEL); + // var subtext_label = new Gtk.Label ("") { + // halign = START + // }; + // subtext_label.add_css_class (Granite.STYLE_CLASS_SMALL_LABEL); + // var box = new Gtk.Box (VERTICAL, 0); + // box.append (text_label); + // box.append (subtext_label); + // var expander = new Gtk.TreeExpander () { + // child = box, + // }; + // item.child = expander; + // } + // protected virtual void teardown_headeritem_child () { + // // Must be paired with create_listitem child + // } + // protected virtual void bind_data_to_header ( + // TreeListItem data, + // Gtk.TreeListRow row, + // Gtk.ListHeader item + // ) { + + // var expander = (Gtk.TreeExpander) item.child; + // var box = (Gtk.Box) expander.get_child (); + // var text_label = (Gtk.Label) box.get_first_child (); + // var subtext_label = (Gtk.Label) text_label.get_next_sibling (); + // text_label.label = data.text; + // subtext_label.label = data.sub_text; + // warning ("bind data to header %s", text_label.label); + // } + + // protected virtual void unbind_data_from_headerrow ( + // TreeListItem data, + // Gtk.TreeListRow row, + // Gtk.ListItem item + // ) { + // //Must undo any signal connections etc made in bind_data_to_row + // } + // Root items must generally be expandable public TreeListItem add_root_item ( TreeListItem item diff --git a/src/Widgets/TreeList/TreeListItem.vala b/src/Widgets/TreeList/TreeListItem.vala index 7b5836e4cc..0b5282be1b 100644 --- a/src/Widgets/TreeList/TreeListItem.vala +++ b/src/Widgets/TreeList/TreeListItem.vala @@ -118,4 +118,13 @@ public class Code.TreeListItem : Object { level--; } } + + public delegate bool IterateChildrenCallback (Object obj); + public void iterate_children (IterateChildrenCallback cb) { + uint pos = 0; + Object? child = null; + do { + child = child_model.get_object (pos++); + } while (child != null && cb (child)); + } } From 57520afc6b5a9204cb05a2b4200a6f82c6c6700d Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Fri, 17 Jul 2026 14:30:43 +0100 Subject: [PATCH 15/21] Expand on Activate ProjectListRow --- src/FolderManager/FolderTree.vala | 3 - src/FolderManager/ProjectFolderItem.vala | 1 - src/FolderManager/ProjectList.vala | 83 +++++++++++++++++------- 3 files changed, 58 insertions(+), 29 deletions(-) diff --git a/src/FolderManager/FolderTree.vala b/src/FolderManager/FolderTree.vala index a18592b865..fcb04dcfde 100644 --- a/src/FolderManager/FolderTree.vala +++ b/src/FolderManager/FolderTree.vala @@ -86,12 +86,9 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { // Convert ListView signal into file_activate tree_list.item_activated.connect ((item) => { - warning ("item activated"); if (item is FileItem) { file_activate (((FileItem) item).file); } else if (item.is_expandable) { - warning ("toggle"); - // Toggle folder item.is_expanded = !item.is_expanded; } }); diff --git a/src/FolderManager/ProjectFolderItem.vala b/src/FolderManager/ProjectFolderItem.vala index 6fa9f5cb55..439c928c81 100644 --- a/src/FolderManager/ProjectFolderItem.vala +++ b/src/FolderManager/ProjectFolderItem.vala @@ -56,7 +56,6 @@ public class Code.ProjectFolderItem : Object, Code.FolderInterface, Code.FolderM public FolderManagerItem item; } - private Scratch.Services.GitManager git_manager; // Cache the visible item in the project. (Excludes items loaded but in collapsed folder) diff --git a/src/FolderManager/ProjectList.vala b/src/FolderManager/ProjectList.vala index 5e07ecaaf2..14a6a3140e 100644 --- a/src/FolderManager/ProjectList.vala +++ b/src/FolderManager/ProjectList.vala @@ -118,31 +118,7 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { selection_model, (obj) => { var project_item = (ProjectFolderItem) obj; - var label = new Granite.HeaderLabel ("") { - label = project_item.name, - secondary_text = "Branch name goes here", - hexpand = true - }; - - var grid = new Gtk.Grid (); - var expander = new Gtk.Expander ("") { - hexpand = false, - valign = CENTER - }; - - var revealer = new Gtk.Revealer () { - child = project_item.folder_tree, - reveal_child = false - }; - expander.notify["expanded"].connect (() => { - revealer.reveal_child = expander.expanded; - }); - - grid.attach (label, 0, 0); - grid.attach (expander, 1, 0); - grid.attach (revealer, 0, 1, 2, 1); - - return grid; + return new ProjectListRowWidget (project_item); } ); @@ -154,6 +130,13 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { child = list_box, }; child = scrolled_window; + + list_box.activate_on_single_click = true; + list_box.row_activated.connect ((row) => { + warning ("Project row activated"); + var project_widget = (ProjectListRowWidget) (row.child); + project_widget.toggle_expanded (); + }); } public async void restore_saved_state () { @@ -835,6 +818,7 @@ warning ("Project list find path %s", path); // } delegate bool ProjectListIteratorCallback (ProjectFolderItem item); + // This iterates the model objects not the listrows private void iterate_children (ProjectListIteratorCallback cb) { ProjectFolderItem? item = null; uint pos = 0; @@ -842,4 +826,53 @@ warning ("Project list find path %s", path); item = (ProjectFolderItem?) (list_store.get_object (pos++)); } while (item != null && cb (item)); } + + private class ProjectListRowWidget : Gtk.Grid { + public ProjectFolderItem project_item { get; construct; } + + private Gtk.Expander expander; + private bool expander_locked = false; + private Gtk.Revealer revealer; + + public ProjectListRowWidget (ProjectFolderItem project) { + Object ( + project_item: project + ); + + } + + construct { + var label = new Granite.HeaderLabel ("") { + label = project_item.name, + secondary_text = "Branch name goes here", + hexpand = true + }; + + // We want expander arrow on right so separate expander and revealer + var grid = new Gtk.Grid (); + + expander = new Gtk.Expander ("") { + hexpand = false, + expanded = false, + sensitive = false, // We do not want expander interfering with listboxrow activate + valign = CENTER + }; + + revealer = new Gtk.Revealer () { + child = project_item.folder_tree, + reveal_child = false + }; + + attach (label, 0, 0); + attach (expander, 1, 0); + attach (revealer, 0, 1, 2, 1); + } + + public void toggle_expanded () { + var show = !revealer.child_revealed; + revealer.reveal_child = show; + expander.expanded = show; + } + } + } From efc063f5bd2a0ea146b4725c57ad588c87429613 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Fri, 17 Jul 2026 15:36:49 +0100 Subject: [PATCH 16/21] Minimal context menu --- src/FolderManager/FolderItem.vala | 120 +++++++++++++++-------- src/FolderManager/FolderTree.vala | 1 + src/FolderManager/ProjectFolderItem.vala | 102 +++++++++---------- src/FolderManager/ProjectList.vala | 42 ++++++-- src/Services/GitManager.vala | 1 + 5 files changed, 168 insertions(+), 98 deletions(-) diff --git a/src/FolderManager/FolderItem.vala b/src/FolderManager/FolderItem.vala index 72c4395de5..4afb5187d7 100644 --- a/src/FolderManager/FolderItem.vala +++ b/src/FolderManager/FolderItem.vala @@ -38,6 +38,47 @@ public interface Code.FolderInterface : Object { } } + public virtual GLib.MenuItem create_submenu_for_new (string file_path) { + var new_folder_item = new GLib.MenuItem ( + _("Folder"), + GLib.Action.print_detailed_name ( + FolderTree.ACTION_PREFIX + FolderTree.ACTION_NEW_FOLDER, + new Variant.string (file_path) + ) + ); + + var new_file_item = new GLib.MenuItem ( + _("Empty File"), + GLib.Action.print_detailed_name ( + FolderTree.ACTION_PREFIX + FolderTree.ACTION_NEW_FILE, + new Variant.string (file_path) + ) + ); + + var new_menu = new GLib.Menu (); + 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) { + // var template_submenu = new Menu (); + // uint template_count = load_templates_from_folder (GLib.File.new_for_path (template_path), template_submenu); + // if (template_count > 0) { + // if (template_count > MAX_TEMPLATES) { + // template_submenu.append_item (new MenuItem (_("…too many templates"), null)); + // } + + // new_menu.append_submenu (_("Templates"), template_submenu); + // } + // } + + var new_item = new GLib.MenuItem.submenu (_("New"), new_menu); + new_item.set_submenu (new_menu); + + return new_item; + } + public delegate bool IterateChildrenCallback (Object obj); public virtual void iterate_children (IterateChildrenCallback cb) {} } @@ -150,7 +191,6 @@ public class Code.FolderItem : FolderManagerItem, Code.FolderInterface { // Ulti } private void on_toggled () { - warning ("%s - expanded %s", this.name, this.is_expanded.to_string ()); // if (is_expanded) { // warning ("load children"); // load_children_async.begin (); @@ -207,7 +247,7 @@ public class Code.FolderItem : FolderManagerItem, Code.FolderInterface { // Ulti } var direct_actions_section = new GLib.Menu (); - direct_actions_section.append_item (create_submenu_for_new ()); + direct_actions_section.append_item (create_submenu_for_new (file.path)); direct_actions_section.append_item (rename_menu_item); direct_actions_section.append_item (delete_item); @@ -257,46 +297,46 @@ public class Code.FolderItem : FolderManagerItem, Code.FolderInterface { // Ulti return open_in_menu_item; } - protected GLib.MenuItem create_submenu_for_new () { - var new_folder_item = new GLib.MenuItem ( - _("Folder"), - GLib.Action.print_detailed_name ( - FolderTree.ACTION_PREFIX + FolderTree.ACTION_NEW_FOLDER, - new Variant.string (file.path) - ) - ); - - var new_file_item = new GLib.MenuItem ( - _("Empty File"), - GLib.Action.print_detailed_name ( - FolderTree.ACTION_PREFIX + FolderTree.ACTION_NEW_FILE, - new Variant.string (file.path) - ) - ); - - var new_menu = new GLib.Menu (); - 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) { - var template_submenu = new Menu (); - uint template_count = load_templates_from_folder (GLib.File.new_for_path (template_path), template_submenu); - if (template_count > 0) { - if (template_count > MAX_TEMPLATES) { - template_submenu.append_item (new MenuItem (_("…too many templates"), null)); - } - - new_menu.append_submenu (_("Templates"), template_submenu); - } - } + // protected GLib.MenuItem create_submenu_for_new () { + // var new_folder_item = new GLib.MenuItem ( + // _("Folder"), + // GLib.Action.print_detailed_name ( + // FolderTree.ACTION_PREFIX + FolderTree.ACTION_NEW_FOLDER, + // new Variant.string (file.path) + // ) + // ); + + // var new_file_item = new GLib.MenuItem ( + // _("Empty File"), + // GLib.Action.print_detailed_name ( + // FolderTree.ACTION_PREFIX + FolderTree.ACTION_NEW_FILE, + // new Variant.string (file.path) + // ) + // ); + + // var new_menu = new GLib.Menu (); + // 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) { + // var template_submenu = new Menu (); + // uint template_count = load_templates_from_folder (GLib.File.new_for_path (template_path), template_submenu); + // if (template_count > 0) { + // if (template_count > MAX_TEMPLATES) { + // template_submenu.append_item (new MenuItem (_("…too many templates"), null)); + // } + + // new_menu.append_submenu (_("Templates"), template_submenu); + // } + // } - var new_item = new GLib.MenuItem.submenu (_("New"), new_menu); - new_item.set_submenu (new_menu); + // var new_item = new GLib.MenuItem.submenu (_("New"), new_menu); + // new_item.set_submenu (new_menu); - return new_item; - } + // return new_item; + // } // Recursively load templates from folder and subfolders keeping count of total menuitems const int MAX_TEMPLATES = 2048; diff --git a/src/FolderManager/FolderTree.vala b/src/FolderManager/FolderTree.vala index fcb04dcfde..b9a5501de4 100644 --- a/src/FolderManager/FolderTree.vala +++ b/src/FolderManager/FolderTree.vala @@ -110,6 +110,7 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { return false; } + // private void action_close_folder (SimpleAction action, GLib.Variant? parameter) { // var path = parameter.get_string (); // if (path == null || path == "") { diff --git a/src/FolderManager/ProjectFolderItem.vala b/src/FolderManager/ProjectFolderItem.vala index 439c928c81..c33fa1064d 100644 --- a/src/FolderManager/ProjectFolderItem.vala +++ b/src/FolderManager/ProjectFolderItem.vala @@ -125,54 +125,56 @@ public class Code.ProjectFolderItem : Object, Code.FolderInterface, Code.FolderM // } } + // warning ("returns a menu model"); public override Menu? get_context_menu () { - // string file_type = ""; - // try { - // var info = gfile.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, GLib.FileQueryInfoFlags.NONE); - // if (info.has_attribute (GLib.FileAttribute.STANDARD_CONTENT_TYPE)) { - // file_type = info.get_content_type (); - // } - // } catch (Error e) { - // warning (e.message); - // } +warning ("get project menu"); + string file_type = ""; + try { + var info = gfile.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, GLib.FileQueryInfoFlags.NONE); + if (info.has_attribute (GLib.FileAttribute.STANDARD_CONTENT_TYPE)) { + file_type = info.get_content_type (); + } + } catch (Error e) { + warning (e.message); + } - // MenuItem set_active_folder_item; - // if (is_git_repo) { - // set_active_folder_item = new GLib.MenuItem ( - // _("Set as Active Project"), - // GLib.Action.print_detailed_name ( - // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_SET_ACTIVE_PROJECT, - // new Variant.string (file.path) - // ) - // ); - // } else { - // set_active_folder_item = new GLib.MenuItem ( - // _("Open in Terminal Pane"), - // GLib.Action.print_detailed_name ( - // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_OPEN_IN_TERMINAL, - // new Variant.string ( - // Services.GitManager.get_instance ().get_default_build_dir (path) - // ) - // ) - // ); - // } + MenuItem set_active_folder_item; + if (is_git_repo) { + set_active_folder_item = new GLib.MenuItem ( + _("Set as Active Project"), + GLib.Action.print_detailed_name ( + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_SET_ACTIVE_PROJECT, + new Variant.string (file.path) + ) + ); + } else { + set_active_folder_item = new GLib.MenuItem ( + _("Open in Terminal Pane"), + GLib.Action.print_detailed_name ( + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_OPEN_IN_TERMINAL, + new Variant.string ( + Scratch.Services.GitManager.get_instance ().get_default_build_dir (path) + ) + ) + ); + } - // set_active_folder_item.set_attribute_value ( - // "accel", - // Scratch.Utils.get_accel_for_action ( - // GLib.Action.print_detailed_name ( - // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_OPEN_IN_TERMINAL, - // "" - // ) - // ) - // ); + set_active_folder_item.set_attribute_value ( + "accel", + Scratch.Utils.get_accel_for_action ( + GLib.Action.print_detailed_name ( + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_OPEN_IN_TERMINAL, + "" + ) + ) + ); - // var external_actions_section = new GLib.Menu (); - // external_actions_section.append_item (set_active_folder_item); + var external_actions_section = new GLib.Menu (); + external_actions_section.append_item (set_active_folder_item); // // external_actions_section.append_item (create_submenu_for_open_in (file_type)); // var folder_actions_section = new GLib.Menu (); - // folder_actions_section.append_item (create_submenu_for_new ()); + // folder_actions_section.append_item (create_submenu_for_new (path)); // if (monitored_repo != null) { // var branch_action_item = new MenuItem ( // _("Branch Actions…"), @@ -199,11 +201,11 @@ public class Code.ProjectFolderItem : Object, Code.FolderInterface, Code.FolderM // new Variant.string (file.path) // ) // ); - // // var close_other_folders_action = Scratch.Utils.action_from_group ( - // // ProjectList.ACTION_CLOSE_OTHER_PROJECT_FOLDERS, - // // view.actions - // // ); - // // close_other_folders_action.set_enabled (!view.is_empty); + // var close_other_folders_action = Scratch.Utils.action_from_group ( + // ProjectList.ACTION_CLOSE_OTHER_PROJECT_FOLDERS, + // view.actions + // ); + // close_other_folders_action.set_enabled (!view.is_empty); // var close_actions_section = new GLib.Menu (); // close_actions_section.append_item (close_folder_item); @@ -298,16 +300,16 @@ public class Code.ProjectFolderItem : Object, Code.FolderInterface, Code.FolderM // var search_actions_section = new GLib.Menu (); // search_actions_section.append_item (search_item); - // var menu_model = new GLib.Menu (); - // menu_model.append_section (null, external_actions_section); + var menu_model = new GLib.Menu (); + menu_model.append_section (null, external_actions_section); // menu_model.append_section (null, folder_actions_section); // menu_model.append_section (null, close_actions_section); // menu_model.append_section (null, direct_actions_section); // menu_model.append_section (null, search_actions_section); - // return menu_model; + return menu_model; - return null; + // return null; } public void update_item_status () { diff --git a/src/FolderManager/ProjectList.vala b/src/FolderManager/ProjectList.vala index 14a6a3140e..7a59913652 100644 --- a/src/FolderManager/ProjectList.vala +++ b/src/FolderManager/ProjectList.vala @@ -133,7 +133,6 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { list_box.activate_on_single_click = true; list_box.row_activated.connect ((row) => { - warning ("Project row activated"); var project_widget = (ProjectListRowWidget) (row.child); project_widget.toggle_expanded (); }); @@ -827,7 +826,7 @@ warning ("Project list find path %s", path); } while (item != null && cb (item)); } - private class ProjectListRowWidget : Gtk.Grid { + private class ProjectListRowWidget : Gtk.Box { public ProjectFolderItem project_item { get; construct; } private Gtk.Expander expander; @@ -848,9 +847,6 @@ warning ("Project list find path %s", path); hexpand = true }; - // We want expander arrow on right so separate expander and revealer - var grid = new Gtk.Grid (); - expander = new Gtk.Expander ("") { hexpand = false, expanded = false, @@ -858,14 +854,44 @@ warning ("Project list find path %s", path); valign = CENTER }; + var label_box = new Gtk.Box (HORIZONTAL, 6); + label_box.append (label); + label_box.append (expander); + revealer = new Gtk.Revealer () { child = project_item.folder_tree, reveal_child = false }; - attach (label, 0, 0); - attach (expander, 1, 0); - attach (revealer, 0, 1, 2, 1); + // We want expander arrow on right so separate expander and revealer + // We put the label and the expander in one box (horizontal) which also + // receives menu requests. The revealer with folder tree goes below + orientation = VERTICAL; + append (label_box); + append (revealer); + + var button_controller = new Gtk.GestureClick () { + propagation_phase = CAPTURE, + button = 0 + }; + + var menu_model = project_item.get_context_menu (); + var menu = new Gtk.PopoverMenu.from_model (menu_model) { + position = BOTTOM + }; + + this.append (menu); + + label_box.add_controller (button_controller); + button_controller.pressed.connect ((n_press, x, y) => { + var event = button_controller.get_last_event (null); + if (event.triggers_context_menu ()) { // Only true for press events + warning ("triggers_context_menu"); + + menu.pointing_to = Gdk.Rectangle () {x = (int)x, y = (int)y, height = 1, width = 1}; + menu.popup (); + } + }); } public void toggle_expanded () { diff --git a/src/Services/GitManager.vala b/src/Services/GitManager.vala index 863a75c68e..1e6f3c28e1 100644 --- a/src/Services/GitManager.vala +++ b/src/Services/GitManager.vala @@ -134,6 +134,7 @@ warning ("not found"); build_path = build_file.get_path (); } else { warning ("build path not found %s", build_file.get_path ()); + // Just returns the project path } return build_path; From 4db96eddbf4fe89b83bd3d9b1276cc92861ce1c5 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Fri, 17 Jul 2026 15:47:16 +0100 Subject: [PATCH 17/21] Implement set as active project menu item --- src/FolderManager/ProjectList.vala | 5 ++--- src/MainWindow.vala | 32 ++++++++++++++++-------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/FolderManager/ProjectList.vala b/src/FolderManager/ProjectList.vala index 7a59913652..5212da3212 100644 --- a/src/FolderManager/ProjectList.vala +++ b/src/FolderManager/ProjectList.vala @@ -684,7 +684,7 @@ warning ("Project list find path %s", path); // order_folders (); //TODO do later } - private bool is_existing_project_path (string path, out ProjectFolderItem? list_item) { + public bool is_existing_project_path (string path, out ProjectFolderItem? list_item) { bool open = false; list_item = null; ProjectFolderItem? matched_item = null; @@ -875,6 +875,7 @@ warning ("Project list find path %s", path); button = 0 }; + // Assume menu is static for now var menu_model = project_item.get_context_menu (); var menu = new Gtk.PopoverMenu.from_model (menu_model) { position = BOTTOM @@ -886,8 +887,6 @@ warning ("Project list find path %s", path); button_controller.pressed.connect ((n_press, x, y) => { var event = button_controller.get_last_event (null); if (event.triggers_context_menu ()) { // Only true for press events - warning ("triggers_context_menu"); - menu.pointing_to = Gdk.Rectangle () {x = (int)x, y = (int)y, height = 1, width = 1}; menu.popup (); } diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 9030f00221..ad551ed3d5 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -1455,21 +1455,23 @@ namespace Scratch { } private void action_set_active_project (SimpleAction action, Variant? param) { - // var project_path = param.get_string (); - // if (folder_manager_view.project_is_open (project_path)) { - // git_manager.active_project_path = project_path; - // folder_manager_view.collapse_other_projects (); - // //The opened folders are not changed so no need to update "opened-folders" setting - // } else { - // warning ("Attempt to set folder path %s which is not opened as active project ignored", project_path); - // //TODO Handle this by opening the folder - // } - - // var new_build_dir = Services.GitManager.get_instance ().get_default_build_dir (null); - // terminal.change_location (new_build_dir); - // if (terminal.visible) { - // terminal.terminal.grab_focus (); - // } +warning ("set active project"); + var project_path = param.get_string (); + Code.ProjectFolderItem? project = null; + if (folder_manager_view.is_existing_project_path (project_path, out project)) { + git_manager.active_project_path = project_path; + folder_manager_view.collapse_other_projects (project_path); + //The opened folders are not changed so no need to update "opened-folders" setting + } else { + warning ("Attempt to set folder path %s which is not opened as active project ignored", project_path); + //TODO Handle this by opening the folder + } + + var new_build_dir = Services.GitManager.get_instance ().get_default_build_dir (null); + terminal.change_location (new_build_dir); + if (terminal.visible) { + terminal.terminal.grab_focus (); + } } private void action_toggle_outline (SimpleAction action) { From 6928d7c19979e01bf558c13e37a02b36bb26eb50 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Fri, 17 Jul 2026 15:55:12 +0100 Subject: [PATCH 18/21] Implement collapse others --- src/FolderManager/ProjectList.vala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/FolderManager/ProjectList.vala b/src/FolderManager/ProjectList.vala index 5212da3212..844445945c 100644 --- a/src/FolderManager/ProjectList.vala +++ b/src/FolderManager/ProjectList.vala @@ -870,6 +870,9 @@ warning ("Project list find path %s", path); append (label_box); append (revealer); + project_item.bind_property ("is-expanded", revealer, "reveal-child", BIDIRECTIONAL | SYNC_CREATE); + revealer.bind_property ("reveal-child", expander, "expanded", BIDIRECTIONAL | SYNC_CREATE); + var button_controller = new Gtk.GestureClick () { propagation_phase = CAPTURE, button = 0 @@ -896,7 +899,6 @@ warning ("Project list find path %s", path); public void toggle_expanded () { var show = !revealer.child_revealed; revealer.reveal_child = show; - expander.expanded = show; } } From cc2970728d7689db8cea424428f785b4a2349819 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 19 Jul 2026 11:52:01 +0100 Subject: [PATCH 19/21] Start reorganizing menus and actions --- src/Application.vala | 18 ++ src/FolderManager/FileItem.vala | 153 ++++++----- src/FolderManager/FolderItem.vala | 331 ++++++++--------------- src/FolderManager/FolderTree.vala | 42 ++- src/FolderManager/Item.vala | 166 +++++++++++- src/FolderManager/ProjectFolderItem.vala | 63 +++-- src/FolderManager/ProjectList.vala | 184 ++++--------- src/MainWindow.vala | 1 + src/Utils.vala | 146 +++------- src/meson.build | 46 ++-- 10 files changed, 554 insertions(+), 596 deletions(-) diff --git a/src/Application.vala b/src/Application.vala index 2c626d39b0..0c68b356fd 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -231,6 +231,24 @@ namespace Scratch { return true; } + public bool can_rename_file (Code.File file) { + // folder_manager_view.rename_request.connect ((file) => { + var allow = true; + foreach (var window in get_windows ()) { + var win = (MainWindow)window; + foreach (var doc in win.document_view.docs) { + if (doc.file.equal (file.file)) { + // Only allow sidebar to rename docs that are in sync with their file in + // all windows + allow = allow && !doc.locked && doc.saved; + } + } + } + + return allow; + // }); + } + public static int main (string[] args) { // By default, profile whole app when profiling is enabled in meson_options.txt // These conditional statements can be moved to profile sections of code diff --git a/src/FolderManager/FileItem.vala b/src/FolderManager/FileItem.vala index a8cf627e12..dffdf57278 100644 --- a/src/FolderManager/FileItem.vala +++ b/src/FolderManager/FileItem.vala @@ -18,7 +18,7 @@ * Authored by: Julien Spautz , Andrei-Costin Zisu */ -public class Code.FileItem : FolderManagerItem { +public class Code.FileItem : FolderManagerItem, Code.FolderManagerItemInterface { public FileItem (File file, FolderTree view) { Object (file: file, view: view); } @@ -28,87 +28,94 @@ public class Code.FileItem : FolderManagerItem { // view.activate (file.path); // } - public override GLib.Menu? get_context_menu () { - GLib.FileInfo info = null; - - try { - info = file.file.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, 0); - } catch (Error e) { - warning (e.message); - } - - var file_type = info.get_attribute_string (GLib.FileAttribute.STANDARD_CONTENT_TYPE); - var contractor_items = Scratch.Utils.create_contract_items_for_file (file.file); - var external_actions_section = new GLib.Menu (); - external_actions_section.append_item (create_submenu_for_open_in (file_type)); - if (contractor_items.get_n_items () > 0) { - external_actions_section.append_submenu ( - _("Other Actions"), - contractor_items - ); - } - - var rename_item = new GLib.MenuItem ( - _("Rename"), - GLib.Action.print_detailed_name ( - FolderTree.ACTION_PREFIX + FolderTree.ACTION_RENAME_FILE, - new Variant.string (file.path) - ) - ); - var rename_action = Scratch.Utils.action_from_group (FolderTree.ACTION_RENAME_FILE, view.actions); - rename_action.set_enabled (view.rename_request (file)); - - var delete_item = new GLib.MenuItem ( - _("Move to Trash"), - GLib.Action.print_detailed_name ( - FolderTree.ACTION_PREFIX + FolderTree.ACTION_DELETE, - new Variant.string (file.path) - ) - ); - - var direct_actions_section = new GLib.Menu (); - direct_actions_section.append_item (rename_item); - direct_actions_section.append_item (delete_item); - - var menu_model = new GLib.Menu (); - menu_model.append_section (null, external_actions_section); - menu_model.append_section (null, direct_actions_section); - - return menu_model; - } + // public override GLib.Menu? get_context_menu () { + // GLib.FileInfo info = null; + + // try { + // info = file.file.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, 0); + // } catch (Error e) { + // warning (e.message); + // } + + // var file_type = info.get_attribute_string (GLib.FileAttribute.STANDARD_CONTENT_TYPE); + // var contractor_items = create_contract_items_for_file (file.file); // Interface method + // var external_actions_section = new GLib.Menu (); + // external_actions_section.append_item (create_submenu_for_open_item_in (file_type)); + // if (contractor_items.get_n_items () > 0) { + // external_actions_section.append_submenu ( + // _("Other Actions"), + // contractor_items + // ); + // } + + // var rename_item = new GLib.MenuItem ( + // _("Rename"), + // GLib.Action.print_detailed_name ( + // ITEM_ACTION_PREFIX + ACTION_RENAME_FILE, + // new Variant.string (file.path) + // ) + // ); + // var rename_action = Scratch.Utils.action_from_group (ACTION_RENAME_FILE, view.actions); + // var app = (Scratch.Application) (Application.get_default ()); + // rename_action.set_enabled (app.can_rename_file (file)); + // rename_action.set_enabled (view.rename_request (file)); + + // var delete_item = new GLib.MenuItem ( + // _("Move to Trash"), + // GLib.Action.print_detailed_name ( + // ITEM_ACTION_PREFIX + ACTION_DELETE, + // new Variant.string (file.path) + // ) + // ); + + // var direct_actions_section = new GLib.Menu (); + // direct_actions_section.append_item (rename_item); + // direct_actions_section.append_item (delete_item); + + // var menu_model = new GLib.Menu (); + // menu_model.append_section (null, external_actions_section); + // menu_model.append_section (null, direct_actions_section); + + // return menu_model; + // } - private GLib.MenuItem create_submenu_for_open_in (string? file_type) { - var new_window_menu_item = new GLib.MenuItem ( - _("New Window"), - GLib.Action.print_detailed_name ( - Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_OPEN_IN_NEW_WINDOW, - file.path - ) - ); + public override GLib.MenuItem create_submenu_for_open_item_in (GLib.File gfile, string? file_type) { + var menu_item = base.create_submenu_for_open_item_in (gfile, file_type); + var sub_menu = (GLib.Menu) (menu_item.get_link ("submenu")); - var top_section = new GLib.Menu (); if (file.is_valid_textfile) { + var top_section = new GLib.Menu (); + var new_window_menu_item = new GLib.MenuItem ( + _("New Window"), + GLib.Action.print_detailed_name ( + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_OPEN_IN_NEW_WINDOW, + path + ) + ); top_section.append_item (new_window_menu_item); + sub_menu.prepend_section (null, top_section); } - var other_menu_item = new GLib.MenuItem ( - _("Other Application…"), - GLib.Action.print_detailed_name ( - FolderTree.ACTION_PREFIX + FolderTree.ACTION_SHOW_APP_CHOOSER, - file.path - ) - ); + //TODO Add section for regular file only actions + + // var other_menu_item = new GLib.MenuItem ( + // _("Other Application…"), + // GLib.Action.print_detailed_name ( + // ITEM_ACTION_PREFIX + ACTION_SHOW_APP_CHOOSER, + // file.path + // ) + // ); - var extra_section = new GLib.Menu (); - extra_section.append_item (other_menu_item); + // var extra_section = new GLib.Menu (); + // extra_section.append_item (other_menu_item); - var open_in_menu = new GLib.Menu (); - open_in_menu.append_section (null, top_section); - open_in_menu.append_section (null, Scratch.Utils.create_executable_app_items_for_file (file.file, file_type)); - open_in_menu.append_section (null, extra_section); + // var open_in_menu = new GLib.Menu (); + // open_in_menu.append_section (null, top_section); + // open_in_menu.append_section (null, create_executable_app_items_for_file (file.file, file_type)); + // open_in_menu.append_section (null, extra_section); - var open_in_menu_item = new GLib.MenuItem.submenu (_("Open In"), open_in_menu); - return open_in_menu_item; + // var open_in_menu_item = new GLib.MenuItem.submenu (_("Open In"), open_in_menu); + return menu_item; } } diff --git a/src/FolderManager/FolderItem.vala b/src/FolderManager/FolderItem.vala index 4afb5187d7..46037f1213 100644 --- a/src/FolderManager/FolderItem.vala +++ b/src/FolderManager/FolderItem.vala @@ -38,11 +38,12 @@ public interface Code.FolderInterface : Object { } } + // Used by both ProjectListItem and FolderItem public virtual GLib.MenuItem create_submenu_for_new (string file_path) { var new_folder_item = new GLib.MenuItem ( _("Folder"), GLib.Action.print_detailed_name ( - FolderTree.ACTION_PREFIX + FolderTree.ACTION_NEW_FOLDER, + ITEM_ACTION_PREFIX + ACTION_NEW_FOLDER, new Variant.string (file_path) ) ); @@ -50,7 +51,7 @@ public interface Code.FolderInterface : Object { var new_file_item = new GLib.MenuItem ( _("Empty File"), GLib.Action.print_detailed_name ( - FolderTree.ACTION_PREFIX + FolderTree.ACTION_NEW_FILE, + ITEM_ACTION_PREFIX + ACTION_NEW_FILE, new Variant.string (file_path) ) ); @@ -60,30 +61,116 @@ public interface Code.FolderInterface : Object { 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) { - // var template_submenu = new Menu (); - // uint template_count = load_templates_from_folder (GLib.File.new_for_path (template_path), template_submenu); - // if (template_count > 0) { - // if (template_count > MAX_TEMPLATES) { - // template_submenu.append_item (new MenuItem (_("…too many templates"), null)); - // } - - // new_menu.append_submenu (_("Templates"), template_submenu); - // } - // } + unowned string? template_path = GLib.Environment.get_user_special_dir (GLib.UserDirectory.TEMPLATES); + if (template_path != null) { + var template_submenu = new Menu (); + uint template_count = load_templates_from_folder (file_path, GLib.File.new_for_path (template_path), template_submenu); + if (template_count > 0) { + if (template_count > MAX_TEMPLATES) { + template_submenu.append_item (new MenuItem (_("…too many templates"), null)); + } - var new_item = new GLib.MenuItem.submenu (_("New"), new_menu); + new_menu.append_submenu (_("Templates"), template_submenu); + } + } + + var new_item = new GLib.MenuItem.submenu (_("Add New"), new_menu); new_item.set_submenu (new_menu); return new_item; } + public delegate bool IterateChildrenCallback (Object obj); public virtual void iterate_children (IterateChildrenCallback cb) {} + + // Recursively load templates from folder and subfolders keeping count of total menuitems + const int MAX_TEMPLATES = 2048; + private uint load_templates_from_folder ( + string target_path, + GLib.File template_folder, + Menu template_submenu + ) { + GLib.List template_list = null; + GLib.List folder_list = null; + + GLib.FileEnumerator enumerator; + var flags = GLib.FileQueryInfoFlags.NOFOLLOW_SYMLINKS; + uint count = 0; + 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 0; + } + + folder_list.sort ((a, b) => { + return strcmp (a.get_basename ().down (), b.get_basename ().down ()); + }); + + unowned List fl = folder_list; + while (fl != null && count < MAX_TEMPLATES) { + var folder = fl.data; + var folder_submenu = new Menu (); + var folder_submenuitem = new MenuItem.submenu ( + folder.get_basename (), + folder_submenu + ); + + var sub_count = load_templates_from_folder (target_path, folder, folder_submenu); + if (sub_count > 0) { + template_submenu.append_item (folder_submenuitem); + count += sub_count; + } else { + count -= 1; // Adjust count for ignored folder + } + + fl = fl.next; + } + + if (count > MAX_TEMPLATES) { + warning ("too many templates! %u", count); + return count; + } + + 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 ( + ITEM_ACTION_PREFIX + ACTION_NEW_FROM_TEMPLATE, + new Variant ("(ss)", target_path, template.get_path ()) + ) + ); + + template_submenu.append_item (template_menuitem); + }); + + return count; + } + } -public class Code.FolderItem : FolderManagerItem, Code.FolderInterface { // Ultimately a Code.TreeListItem +public class Code.FolderItem : FolderManagerItem, Code.FolderInterface, Code.FolderManagerItemInterface { // Ultimately a Code.TreeListItem public signal void children_finished_loading (); public bool loading_required { @@ -205,33 +292,9 @@ public class Code.FolderItem : FolderManagerItem, Code.FolderInterface { // Ulti } public override GLib.Menu? get_context_menu () { - GLib.FileInfo info = null; - try { - info = file.file.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, GLib.FileQueryInfoFlags.NONE); - } catch (Error e) { - warning (e.message); - } - - var file_type = info.get_content_type (); - - var contractor_items = Scratch.Utils.create_contract_items_for_file (file.file); - - var rename_menu_item = new GLib.MenuItem ( - _("Rename"), - GLib.Action.print_detailed_name ( - FolderTree.ACTION_PREFIX + FolderTree.ACTION_RENAME_FOLDER, - new Variant.string (file.path) - ) - ); - - var delete_item = new GLib.MenuItem ( - _("Move to Trash"), - GLib.Action.print_detailed_name ( - FolderTree.ACTION_PREFIX + FolderTree.ACTION_DELETE, - new Variant.string (file.path) - ) - ); + var menu_model = base.get_context_menu (); + // Add items related only to folders var search_item = new GLib.MenuItem ( _("Find in Folder…"), GLib.Action.print_detailed_name ( @@ -239,187 +302,21 @@ public class Code.FolderItem : FolderManagerItem, Code.FolderInterface { // Ulti new Variant.string (file.file.get_path ()) ) ); - - var external_actions_section = new GLib.Menu (); - external_actions_section.append_item (create_submenu_for_open_in (file_type)); - if (contractor_items.get_n_items () > 0) { - external_actions_section.append_submenu (_("Other Actions"), contractor_items); - } - - var direct_actions_section = new GLib.Menu (); - direct_actions_section.append_item (create_submenu_for_new (file.path)); - direct_actions_section.append_item (rename_menu_item); - direct_actions_section.append_item (delete_item); - var search_section = new GLib.Menu (); search_section.append_item (search_item); - - var menu_model = new GLib.Menu (); - menu_model.append_section (null, external_actions_section); - menu_model.append_section (null, direct_actions_section); menu_model.append_section (null, search_section); + // var external_actions_section = new GLib.Menu (); + // external_actions_section.append_item (create_submenu_for_open_item_in (file.path, file_type)); - return menu_model; - } - - protected GLib.MenuItem create_submenu_for_open_in (string? file_type) { - var open_in_terminal_pane_item = new GLib.MenuItem ( - (_("Terminal Pane")), - GLib.Action.print_detailed_name ( - Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_OPEN_IN_TERMINAL, - new Variant.string (file.path) - ) - ); - open_in_terminal_pane_item.set_icon (new ThemedIcon ("panel-bottom-symbolic")); - - var other_menu_item = new GLib.MenuItem ( - _("Other Application…"), - GLib.Action.print_detailed_name ( - FolderTree.ACTION_PREFIX + FolderTree.ACTION_SHOW_APP_CHOOSER, - file.path - ) - ); - - var extra_section = new GLib.Menu (); - extra_section.append_item (other_menu_item); - - var terminal_pane_section = new Menu (); - terminal_pane_section.append_item (open_in_terminal_pane_item); - - file_type = file_type ?? "inode/directory"; - - var open_in_menu = new GLib.Menu (); - open_in_menu.append_section (null, terminal_pane_section); - open_in_menu.append_section (null, Scratch.Utils.create_executable_app_items_for_file (file.file, file_type)); - open_in_menu.append_section (null, extra_section); + var direct_actions_section = new GLib.Menu (); + direct_actions_section.append_item (create_submenu_for_new (file.path)); + menu_model.prepend_section (null, direct_actions_section); + // direct_actions_section.append_item (rename_menu_item); + // direct_actions_section.append_item (delete_item); - var open_in_menu_item = new GLib.MenuItem.submenu (_("Open In"), open_in_menu); - return open_in_menu_item; + return menu_model; } - // protected GLib.MenuItem create_submenu_for_new () { - // var new_folder_item = new GLib.MenuItem ( - // _("Folder"), - // GLib.Action.print_detailed_name ( - // FolderTree.ACTION_PREFIX + FolderTree.ACTION_NEW_FOLDER, - // new Variant.string (file.path) - // ) - // ); - - // var new_file_item = new GLib.MenuItem ( - // _("Empty File"), - // GLib.Action.print_detailed_name ( - // FolderTree.ACTION_PREFIX + FolderTree.ACTION_NEW_FILE, - // new Variant.string (file.path) - // ) - // ); - - // var new_menu = new GLib.Menu (); - // 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) { - // var template_submenu = new Menu (); - // uint template_count = load_templates_from_folder (GLib.File.new_for_path (template_path), template_submenu); - // if (template_count > 0) { - // if (template_count > MAX_TEMPLATES) { - // template_submenu.append_item (new MenuItem (_("…too many templates"), null)); - // } - - // new_menu.append_submenu (_("Templates"), template_submenu); - // } - // } - - // var new_item = new GLib.MenuItem.submenu (_("New"), new_menu); - // new_item.set_submenu (new_menu); - - // return new_item; - // } - - // Recursively load templates from folder and subfolders keeping count of total menuitems - const int MAX_TEMPLATES = 2048; - private uint load_templates_from_folder ( - GLib.File template_folder, - Menu template_submenu - ) { - GLib.List template_list = null; - GLib.List folder_list = null; - - GLib.FileEnumerator enumerator; - var flags = GLib.FileQueryInfoFlags.NOFOLLOW_SYMLINKS; - uint count = 0; - 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 0; - } - - folder_list.sort ((a, b) => { - return strcmp (a.get_basename ().down (), b.get_basename ().down ()); - }); - - unowned List fl = folder_list; - while (fl != null && count < MAX_TEMPLATES) { - var folder = fl.data; - var folder_submenu = new Menu (); - var folder_submenuitem = new MenuItem.submenu ( - folder.get_basename (), - folder_submenu - ); - - var sub_count = load_templates_from_folder (folder, folder_submenu); - if (sub_count > 0) { - template_submenu.append_item (folder_submenuitem); - count += sub_count; - } else { - count -= 1; // Adjust count for ignored folder - } - - fl = fl.next; - } - - if (count > MAX_TEMPLATES) { - warning ("too many templates! %u", count); - return count; - } - - 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 ( - FolderTree.ACTION_PREFIX + FolderTree.ACTION_NEW_FROM_TEMPLATE, - new Variant ("(ss)", this.path, template.get_path ()) - ) - ); - - template_submenu.append_item (template_menuitem); - }); - - return count; - } // public void remove_all_badges () { // view.iterate_children (this, (child) => { @@ -637,7 +534,7 @@ public class Code.FolderItem : FolderManagerItem, Code.FolderInterface { // Ulti // 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 = Scratch.Utils.action_from_group (FolderTree.ACTION_RENAME_FILE, view.actions); + var rename_action = Scratch.Utils.action_from_group (ACTION_RENAME_FILE, view.actions); if (rename_action != null && rename_action.enabled) { rename_action.activate (path); } else { diff --git a/src/FolderManager/FolderTree.vala b/src/FolderManager/FolderTree.vala index b9a5501de4..ff92ca690b 100644 --- a/src/FolderManager/FolderTree.vala +++ b/src/FolderManager/FolderTree.vala @@ -18,39 +18,26 @@ public class Code.FolderTreeItem : Code.TreeListItem { public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { public signal void file_activate (File file); - public signal bool rename_request (File file); - - public const string ACTION_GROUP = "file-view"; - public const string ACTION_PREFIX = ACTION_GROUP + "."; - public const string ACTION_LAUNCH_APP_WITH_FILE_PATH = "launch-app-with-file-path"; - public const string ACTION_SHOW_APP_CHOOSER = "show-app-chooser"; - public const string ACTION_EXECUTE_CONTRACT_WITH_FILE_PATH = "execute-contract-with-file-path"; - public const string ACTION_RENAME_FILE = "rename-file"; - 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"; + // All file related actions handled here + + public SimpleActionGroup actions { get; private set; } + public string icon_name { get; set; } + public string title { get; set; } + public string root_path { get; construct; } + public bool is_empty { get { return tree_list.n_root_items () == 0; } } private const ActionEntry[] ACTION_ENTRIES = { - { ACTION_LAUNCH_APP_WITH_FILE_PATH, action_launch_app_with_file_path, "as" }, - { ACTION_SHOW_APP_CHOOSER, action_show_app_chooser, "s" }, - { ACTION_EXECUTE_CONTRACT_WITH_FILE_PATH, action_execute_contract_with_file_path, "as" }, { ACTION_RENAME_FILE, action_rename_file, "s" }, { ACTION_RENAME_FOLDER, action_rename_folder, "s" }, + { ACTION_LAUNCH_APP_WITH_FILE_PATH, action_launch_app_with_file_path, "as" }, + { ACTION_SHOW_APP_CHOOSER, action_show_app_chooser, "s" }, { ACTION_DELETE, action_delete, "s" }, + { ACTION_EXECUTE_CONTRACT_WITH_FILE_PATH, action_execute_contract_with_file_path, "as" }, { ACTION_NEW_FILE, add_new_file, "s" }, { ACTION_NEW_FROM_TEMPLATE, add_new_from_template, "(ss)" }, - { ACTION_NEW_FOLDER, add_new_folder, "s"}, + { ACTION_NEW_FOLDER, add_new_folder, "s"} }; - - public SimpleActionGroup actions { get; private set; } - public string icon_name { get; set; } - public string title { get; set; } - public string root_path { get; construct; } - public bool is_empty { get { return tree_list.n_root_items () == 0; } } - private Code.TreeList tree_list; // private GLib.Settings settings; // private Scratch.Services.GitManager git_manager; @@ -68,6 +55,10 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { } construct { + actions = new SimpleActionGroup (); + actions.add_action_entries (ACTION_ENTRIES, this); + insert_action_group (ITEM_ACTION_GROUP, actions); + tree_list = new Code.TreeList (); // icon_name = "folder-symbolic"; // title = _("Folders"); @@ -78,7 +69,7 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { actions = new SimpleActionGroup (); actions.add_action_entries (ACTION_ENTRIES, this); - insert_action_group (ACTION_GROUP, actions); + insert_action_group (ITEM_ACTION_PREFIX, actions); // Scratch.saved_state.changed["order-folders"].connect (() => { // order_folders (); @@ -110,7 +101,6 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { return false; } - // private void action_close_folder (SimpleAction action, GLib.Variant? parameter) { // var path = parameter.get_string (); // if (path == null || path == "") { diff --git a/src/FolderManager/Item.vala b/src/FolderManager/Item.vala index 981e5ac2ba..1220e3c229 100644 --- a/src/FolderManager/Item.vala +++ b/src/FolderManager/Item.vala @@ -22,10 +22,172 @@ public interface Code.FolderManagerItemInterface : Object { public abstract string path { get; set; } public abstract string name { get; set; } public abstract string badge { get; set; } - public virtual Menu? get_context_menu () { return null; } public bool equal (FolderManagerItemInterface b) { return path == b.path; } + + public virtual GLib.Menu? get_context_menu () { + GLib.FileInfo info = null; + var gfile = GLib.File.new_for_path (path); + try { + info = gfile.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, GLib.FileQueryInfoFlags.NONE); + } catch (Error e) { + warning (e.message); + } + + var file_type = info.get_content_type (); + + // external_actions_section holds submenus for "Open In" and "Other Actions" + // which involve other apps + var external_actions_section = new GLib.Menu (); + external_actions_section.append_item (create_submenu_for_open_item_in (gfile, file_type)); + var contractor_items = create_contract_items_for_file (gfile); + if (contractor_items.get_n_items () > 0) { + external_actions_section.append_submenu (_("Other Actions"), contractor_items); + } + + var menu_model = new GLib.Menu (); + menu_model.append_section (null, external_actions_section); + var rename_menu_item = new GLib.MenuItem ( + _("Rename"), + GLib.Action.print_detailed_name ( + ITEM_ACTION_PREFIX + ACTION_RENAME_FOLDER, + new Variant.string (path) + ) + ); + + var delete_item = new GLib.MenuItem ( + _("Move to Trash"), + GLib.Action.print_detailed_name ( + ITEM_ACTION_PREFIX + ACTION_DELETE, + new Variant.string (path) + ) + ); + + return menu_model; + } + + public virtual GLib.MenuItem create_submenu_for_open_item_in (GLib.File gfile, string? file_type) { + //TODO Open in Terminal Pane only for projects + // var open_in_terminal_pane_item = new GLib.MenuItem ( + // (_("Terminal Pane")), + // GLib.Action.print_detailed_name ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_OPEN_IN_TERMINAL, + // new Variant.string (file_path) + // ) + // ); + // open_in_terminal_pane_item.set_icon (new ThemedIcon ("panel-bottom-symbolic")); + + var file_path = gfile.get_path (); + var other_menu_item = new GLib.MenuItem ( + _("Other Application…"), + GLib.Action.print_detailed_name ( + ITEM_ACTION_PREFIX + ACTION_SHOW_APP_CHOOSER, + file_path + ) + ); + + var extra_section = new GLib.Menu (); + extra_section.append_item (other_menu_item); + + // var terminal_pane_section = new Menu (); + // terminal_pane_section.append_item (open_in_terminal_pane_item); + + file_type = file_type ?? "inode/directory"; + + var open_in_menu = new GLib.Menu (); + // open_in_menu.append_section (null, terminal_pane_section); + open_in_menu.append_section (null, create_executable_app_items_for_file ( + gfile, + file_type + )); + open_in_menu.append_section (null, extra_section); + + var open_in_menu_item = new GLib.MenuItem.submenu (_("Open In"), open_in_menu); + open_in_menu_item.set_link ("submenu", open_in_menu); // So overriding methods can modify + return open_in_menu_item; + } + + public GLib.Menu? create_executable_app_items_for_file (GLib.File file, string file_type) { + var scratch_app = (Scratch.Application) (GLib.Application.get_default ()); + var this_id = scratch_app.application_id + ".desktop"; + var menu = new GLib.Menu (); + + if (scratch_app.is_running_in_flatpak) { + var menu_item = new MenuItem ( + ///TRANSLATORS '%s' represents the quoted basename of a uri to be opened with the default app + _("Show '%s' with default app").printf (file.get_basename ()), + GLib.Action.print_detailed_name ( + ITEM_ACTION_PREFIX + ACTION_LAUNCH_APP_WITH_FILE_PATH, + new GLib.Variant.array ( + GLib.VariantType.STRING, + { file.get_path (), "" } + ) + ) + ); + menu.append_item (menu_item); + } else { + List external_apps = null; + if (file_type == "") { + var files_appinfo = AppInfo.get_default_for_type ("inode/directory", true); + external_apps.prepend (files_appinfo); + } else { + external_apps = GLib.AppInfo.get_all_for_type (file_type); + external_apps.sort ((a, b) => { + return a.get_name ().collate (b.get_name ()); + }); + } + + foreach (AppInfo app_info in external_apps) { + string app_id = app_info.get_id (); + if (app_id == this_id) { + continue; + } + + var menu_item = new MenuItem ( + app_info.get_name (), + GLib.Action.print_detailed_name ( + ITEM_ACTION_PREFIX + ACTION_LAUNCH_APP_WITH_FILE_PATH, + new GLib.Variant.array ( + GLib.VariantType.STRING, + { file.get_path (), app_id } + ) + ) + ); + menu_item.set_icon (app_info.get_icon ()); + menu.append_item (menu_item); + } + } + + return menu; + } + + public GLib.Menu create_contract_items_for_file (GLib.File file) { + var menu = new GLib.Menu (); + + try { + var contracts = Granite.Services.ContractorProxy.get_contracts_for_file (file); + foreach (var contract in contracts) { + string contract_name = contract.get_display_name (); + var menu_item = new GLib.MenuItem ( + contract_name, + GLib.Action.print_detailed_name ( + ITEM_ACTION_PREFIX + ACTION_EXECUTE_CONTRACT_WITH_FILE_PATH, + new GLib.Variant.array ( + GLib.VariantType.STRING, + { file.get_path (), contract_name } + ) + ) + ); + + menu.append_item (menu_item); + } + } catch (Error e) { + warning (e.message); + } + + return menu; + } } public abstract class Code.FolderManagerItem: Code.TreeListItem, Code.FolderManagerItemInterface { @@ -147,7 +309,5 @@ public abstract class Code.FolderManagerItem: Code.TreeListItem, Code.FolderMana // } // } // } - - public virtual GLib.Menu? get_context_menu () { return null; } } diff --git a/src/FolderManager/ProjectFolderItem.vala b/src/FolderManager/ProjectFolderItem.vala index c33fa1064d..3dff7664dd 100644 --- a/src/FolderManager/ProjectFolderItem.vala +++ b/src/FolderManager/ProjectFolderItem.vala @@ -169,12 +169,21 @@ warning ("get project menu"); ) ); + var new_window_menu_item = new GLib.MenuItem ( + _("Open in New Window"), + GLib.Action.print_detailed_name ( + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_OPEN_IN_NEW_WINDOW, + path + ) + ); + var external_actions_section = new GLib.Menu (); external_actions_section.append_item (set_active_folder_item); - // // external_actions_section.append_item (create_submenu_for_open_in (file_type)); + external_actions_section.append_item (new_window_menu_item); + + var folder_actions_section = new GLib.Menu (); + folder_actions_section.append_item (create_submenu_for_new (path)); - // var folder_actions_section = new GLib.Menu (); - // folder_actions_section.append_item (create_submenu_for_new (path)); // if (monitored_repo != null) { // var branch_action_item = new MenuItem ( // _("Branch Actions…"), @@ -186,30 +195,30 @@ warning ("get project menu"); // folder_actions_section.append_item (branch_action_item); // } - // var close_folder_item = new GLib.MenuItem ( - // _("Close Folder"), - // GLib.Action.print_detailed_name ( - // ProjectList.ACTION_PREFIX + ProjectList.ACTION_CLOSE_PROJECT_FOLDER, - // new Variant.string (file.path) - // ) - // ); + var close_folder_item = new GLib.MenuItem ( + _("Close Folder"), + GLib.Action.print_detailed_name ( + PROJECT_ACTION_PREFIX + ACTION_CLOSE_PROJECT_FOLDER, + new Variant.string (file.path) + ) + ); - // var close_all_except_item = new GLib.MenuItem ( - // _("Close Other Folders"), - // GLib.Action.print_detailed_name ( - // ProjectList.ACTION_PREFIX + ProjectList.ACTION_CLOSE_OTHER_PROJECT_FOLDERS, - // new Variant.string (file.path) - // ) - // ); - // var close_other_folders_action = Scratch.Utils.action_from_group ( - // ProjectList.ACTION_CLOSE_OTHER_PROJECT_FOLDERS, - // view.actions - // ); - // close_other_folders_action.set_enabled (!view.is_empty); + var close_all_except_item = new GLib.MenuItem ( + _("Close Other Folders"), + GLib.Action.print_detailed_name ( + PROJECT_ACTION_PREFIX + ACTION_CLOSE_OTHER_PROJECT_FOLDERS, + new Variant.string (file.path) + ) + ); + var close_other_folders_action = Scratch.Utils.action_from_group ( + ACTION_CLOSE_OTHER_PROJECT_FOLDERS, + view.actions + ); + close_other_folders_action.set_enabled (!view.is_empty); - // var close_actions_section = new GLib.Menu (); - // close_actions_section.append_item (close_folder_item); - // close_actions_section.append_item (close_all_except_item); + var close_actions_section = new GLib.Menu (); + close_actions_section.append_item (close_folder_item); + close_actions_section.append_item (close_all_except_item); // var n_open = Scratch.Services.DocumentManager.get_instance ().open_for_project (path); // var open_text = ngettext ("Close %u Open Document", @@ -302,8 +311,8 @@ warning ("get project menu"); var menu_model = new GLib.Menu (); menu_model.append_section (null, external_actions_section); - // menu_model.append_section (null, folder_actions_section); - // menu_model.append_section (null, close_actions_section); + menu_model.append_section (null, folder_actions_section); + menu_model.append_section (null, close_actions_section); // menu_model.append_section (null, direct_actions_section); // menu_model.append_section (null, search_actions_section); diff --git a/src/FolderManager/ProjectList.vala b/src/FolderManager/ProjectList.vala index 844445945c..d77c080425 100644 --- a/src/FolderManager/ProjectList.vala +++ b/src/FolderManager/ProjectList.vala @@ -70,13 +70,38 @@ // } // } -/* ProjectList is a flat list of FolderTrees each representing a single project */ -public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { - public const string ACTION_GROUP = "project-list"; - public const string ACTION_PREFIX = ACTION_GROUP + "."; +namespace Code { + // All project related actions handled in ProjectFolderItem + public const string PROJECT_ACTION_GROUP = "project"; + public const string PROJECT_ACTION_PREFIX = "project."; public const string ACTION_CLOSE_PROJECT_FOLDER = "close-project-folder"; public const string ACTION_CLOSE_OTHER_PROJECT_FOLDERS = "close-other-project-folders"; + public const string ACTION_EXECUTE_CONTRACT_WITH_FILE_PATH = "execute-contract-with-file-path"; + 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"; + + // All fileitem related actions handled in FolderTree + public const string ITEM_ACTION_GROUP = "item"; + public const string ITEM_ACTION_PREFIX = "item."; + public const string ACTION_RENAME_FILE = "rename-file"; + public const string ACTION_RENAME_FOLDER = "rename-folder"; + public const string ACTION_LAUNCH_APP_WITH_FILE_PATH = "launch-app-with-file-path"; + public const string ACTION_SHOW_APP_CHOOSER = "show-app-chooser"; + public const string ACTION_DELETE = "delete"; +} +/* ProjectList is a flat list of FolderTrees each representing a single project */ +public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { + // Moved from folder tree to shared top widget + // public signal bool rename_request (Code.File file); + + private const ActionEntry[] ACTION_ENTRIES = { + { ACTION_CLOSE_PROJECT_FOLDER, action_close_project_folder, "s"}, + { ACTION_CLOSE_OTHER_PROJECT_FOLDERS, action_close_other_project_folders, "s"} + }; + public const string CLOSE_PROJECT_DOCS_ACTION_NAME = Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_CLOSE_PROJECT_DOCS; + public SimpleActionGroup actions { get; private set; } public bool is_empty { get { return list_store.n_items == 0; } } @@ -92,10 +117,6 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { private GLib.Settings settings; private Scratch.Services.PluginsManager plugins; - private const ActionEntry[] ACTION_ENTRIES = { - { ACTION_CLOSE_PROJECT_FOLDER, action_close_project_folder, "s"}, - { ACTION_CLOSE_OTHER_PROJECT_FOLDERS, action_close_other_project_folders, "s"} - }; // public ProjectList (Scratch.Services.PluginsManager plugins_manager) { // plugins = plugins_manager; @@ -104,7 +125,7 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { construct { actions = new SimpleActionGroup (); actions.add_action_entries (ACTION_ENTRIES, this); - insert_action_group (ACTION_GROUP, actions); + insert_action_group (PROJECT_ACTION_GROUP, actions); //For Code.PaneSwitcher iterface icon_name = "not-exist"; @@ -363,95 +384,6 @@ warning ("Project list find path %s", path); remove_project_item (listitem); } - // 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)) { - // ulong once = 0; - // once = selected.edited.connect ((new_name) => { - // selected.disconnect (once); - // var new_path = Path.get_dirname (path) + Path.DIR_SEPARATOR_S + new_name; - // activate_action ( - // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_CLOSE_TAB, - // "s", - // path - // ); - - // // RecentManager requires valid URI - // var new_uri = "file://" + new_path; // Code only edits local files - // Gtk.RecentManager.get_default ().add_item (new_uri); - - // activate (new_path); - // }); - // } - - // // Handle cancelled rename (which does not produce signal) - // Timeout.add (200, () => { - // if (this.editing) { - // return Source.CONTINUE; - // } else { - // // Avoid selected but unopened item if rename cancelled (they would not open if clicked on) - // this.unselect_all (); - // return Source.REMOVE; - // } - // }); - } - - private void rename_folder (string path) { - // var folder_to_rename = find_path (null, path) as FolderItem; - // if (folder_to_rename == null) { - // critical ("Could not find folder from given path to rename: %s", path); - // return; - // } - - // folder_to_rename.selectable = true; - // if (start_editing_item (folder_to_rename)) { - // // Need to poll view as no signal emited when editing cancelled and need to set - // // selectable to false anyway. - // Timeout.add (200, () => { - // if (editing) { - // return Source.CONTINUE; - // } else { - // unselect_all (); - // // Must do this *after* unselecting all else sourcelist breaks - // folder_to_rename.selectable = false; - // } - - // return Source.REMOVE; - // }); - // } else { - // critical ("Could not rename %s", path); - // folder_to_rename.selectable = false; - // } - } - - private void rename_items_with_same_name (FolderManagerItem item) { - // string item_name = item.file.name; - // tree_list.iterate_children (null, (child) => { - // string new_other_item_name, new_item_name; - // var other_item = (ProjectFolderItem) child; - - // if (Utils.find_unique_path ( - // item.file.file, - // other_item.file.file, - // out new_item_name, - // out new_other_item_name - // ) - // ) { - // if (item_name.length < new_item_name.length) { - // item_name = new_item_name; - // } - - // if (other_item.name.length < new_other_item_name.length) { - // other_item.name = new_other_item_name; - // } - // } - - // return Code.TreeList.ITERATE_CONTINUE; - // }); - - // item.name = item_name; - } // private void add_new_folder (SimpleAction action, Variant? param) { // // Using "path" of parent folder from params, call `on_add_new (true)` on `FolderItem` @@ -537,39 +469,21 @@ warning ("Project list find path %s", path); // } // private void action_execute_contract_with_file_path (SimpleAction action, Variant? param) { - // var params = param.get_strv (); - // var path = params[0]; - // if (path == null || path == "") { - // return; - // } - - // var contract_name = params[1]; - // if (contract_name == null || contract_name == "") { - // return; - // } - - // Utils.execute_contract_with_file_path (path, contract_name); - // } - - // private void action_rename_file (SimpleAction action, Variant? param) { - // var path = param.get_string (); + // var params = param.get_strv (); + // var path = params[0]; + // if (path == null || path == "") { + // return; + // } - // if (path == null || path == "") { - // return; - // } + // var contract_name = params[1]; + // if (contract_name == null || contract_name == "") { + // return; + // } - // rename_file (path); + // Utils.execute_contract_with_file_path (path, contract_name); // } - // private void action_rename_folder (SimpleAction action, Variant? param) { - // var path = param.get_string (); - // if (path == null || path == "") { - // return; - // } - - // rename_folder (path); - // } // private void action_delete (SimpleAction action, Variant? param) { @@ -826,6 +740,24 @@ warning ("Project list find path %s", path); } while (item != null && cb (item)); } + private void execute_contract_with_file_path (string path, string contract_name) { + var file = GLib.File.new_for_path (path); + + try { + var contracts = Granite.Services.ContractorProxy.get_contracts_for_file (file); + int length = contracts.size; + for (int i = 0; i < length; i++) { + var contract = contracts[i]; + if (contract.get_display_name () == contract_name) { + contract.execute_with_file (file); + break; + } + } + } catch (Error e) { + warning (e.message); + } + } + private class ProjectListRowWidget : Gtk.Box { public ProjectFolderItem project_item { get; construct; } diff --git a/src/MainWindow.vala b/src/MainWindow.vala index ad551ed3d5..ace2501633 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -613,6 +613,7 @@ namespace Scratch { set_widgets_sensitive (false); } + private void open_binary (File file) { if (!file.query_exists ()) { return; diff --git a/src/Utils.vala b/src/Utils.vala index 3a8f5fc2a6..ee4570659e 100644 --- a/src/Utils.vala +++ b/src/Utils.vala @@ -201,62 +201,6 @@ namespace Scratch.Utils { return false; } - public GLib.Menu? create_executable_app_items_for_file (GLib.File file, string file_type) { - var scratch_app = (Scratch.Application) (GLib.Application.get_default ()); - var this_id = scratch_app.application_id + ".desktop"; - var menu = new GLib.Menu (); - - if (scratch_app.is_running_in_flatpak) { - var menu_item = new MenuItem ( - ///TRANSLATORS '%s' represents the quoted basename of a uri to be opened with the default app - _("Show '%s' with default app").printf (file.get_basename ()), - GLib.Action.print_detailed_name ( - Code.FolderTree.ACTION_PREFIX - + Code.FolderTree.ACTION_LAUNCH_APP_WITH_FILE_PATH, - new GLib.Variant.array ( - GLib.VariantType.STRING, - { file.get_path (), "" } - ) - ) - ); - menu.append_item (menu_item); - } else { - List external_apps = null; - if (file_type == "") { - var files_appinfo = AppInfo.get_default_for_type ("inode/directory", true); - external_apps.prepend (files_appinfo); - } else { - external_apps = GLib.AppInfo.get_all_for_type (file_type); - external_apps.sort ((a, b) => { - return a.get_name ().collate (b.get_name ()); - }); - } - - foreach (AppInfo app_info in external_apps) { - string app_id = app_info.get_id (); - if (app_id == this_id) { - continue; - } - - var menu_item = new MenuItem ( - app_info.get_name (), - GLib.Action.print_detailed_name ( - Code.FolderTree.ACTION_PREFIX - + Code.FolderTree.ACTION_LAUNCH_APP_WITH_FILE_PATH, - new GLib.Variant.array ( - GLib.VariantType.STRING, - { file.get_path (), app_id } - ) - ) - ); - menu_item.set_icon (app_info.get_icon ()); - menu.append_item (menu_item); - } - } - - return menu; - } - public void launch_app_with_file (string app_id, string path) { var scratch_app = (Scratch.Application) (GLib.Application.get_default ()); if (scratch_app.is_running_in_flatpak || app_id == "") { @@ -281,51 +225,51 @@ namespace Scratch.Utils { } } - public GLib.Menu create_contract_items_for_file (GLib.File file) { - var menu = new GLib.Menu (); - - try { - var contracts = Granite.Services.ContractorProxy.get_contracts_for_file (file); - foreach (var contract in contracts) { - string contract_name = contract.get_display_name (); - var menu_item = new GLib.MenuItem ( - contract_name, - GLib.Action.print_detailed_name ( - Code.FolderTree.ACTION_PREFIX - + Code.FolderTree.ACTION_EXECUTE_CONTRACT_WITH_FILE_PATH, - new GLib.Variant.array ( - GLib.VariantType.STRING, - { file.get_path (), contract_name } - ) - ) - ); - - menu.append_item (menu_item); - } - } catch (Error e) { - warning (e.message); - } - - return menu; - } - - public void execute_contract_with_file_path (string path, string contract_name) { - var file = GLib.File.new_for_path (path); - - try { - var contracts = Granite.Services.ContractorProxy.get_contracts_for_file (file); - int length = contracts.size; - for (int i = 0; i < length; i++) { - var contract = contracts[i]; - if (contract.get_display_name () == contract_name) { - contract.execute_with_file (file); - break; - } - } - } catch (Error e) { - warning (e.message); - } - } + // public GLib.Menu create_contract_items_for_file (GLib.File file) { + // var menu = new GLib.Menu (); + + // try { + // var contracts = Granite.Services.ContractorProxy.get_contracts_for_file (file); + // foreach (var contract in contracts) { + // string contract_name = contract.get_display_name (); + // var menu_item = new GLib.MenuItem ( + // contract_name, + // GLib.Action.print_detailed_name ( + // Code.FolderTree.ACTION_PREFIX + // + Code.ProjectList.ACTION_EXECUTE_CONTRACT_WITH_FILE_PATH, + // new GLib.Variant.array ( + // GLib.VariantType.STRING, + // { file.get_path (), contract_name } + // ) + // ) + // ); + + // menu.append_item (menu_item); + // } + // } catch (Error e) { + // warning (e.message); + // } + + // return menu; + // } + + // public void execute_contract_with_file_path (string path, string contract_name) { + // var file = GLib.File.new_for_path (path); + + // try { + // var contracts = Granite.Services.ContractorProxy.get_contracts_for_file (file); + // int length = contracts.size; + // for (int i = 0; i < length; i++) { + // var contract = contracts[i]; + // if (contract.get_display_name () == contract_name) { + // contract.execute_with_file (file); + // break; + // } + // } + // } catch (Error e) { + // warning (e.message); + // } + // } public string get_accel_for_action (string detailed_action_name) { var app_instance = (Gtk.Application) GLib.Application.get_default (); diff --git a/src/meson.build b/src/meson.build index 912bf5bf07..c0255ac944 100644 --- a/src/meson.build +++ b/src/meson.build @@ -85,30 +85,30 @@ executable( install: true ) -codecore = library( - 'codecore', - code_files, - config_header, - dependencies: dependencies, - install: true, - install_dir: [true, true, true], - version: '0.0' -) +# codecore = library( +# 'codecore', +# code_files, +# config_header, +# dependencies: dependencies, +# install: true, +# install_dir: [true, true, true], +# version: '0.0' +# ) -pkg = import('pkgconfig') +# pkg = import('pkgconfig') -pkg.generate( - version: '0.1', - libraries: codecore, - description: 'elementary Code headers', - name: 'codecore', - filebase: 'codecore' -) +# pkg.generate( +# version: '0.1', +# libraries: codecore, +# description: 'elementary Code headers', +# name: 'codecore', +# filebase: 'codecore' +# ) -install_data ('codecore.deps', install_dir: get_option('prefix') / get_option('datadir') / 'vala' / 'vapi') +# install_data ('codecore.deps', install_dir: get_option('prefix') / get_option('datadir') / 'vala' / 'vapi') -codecore_dep = declare_dependency( - link_with: codecore, - dependencies: dependencies, - include_directories: [include_directories('.')] -) +# codecore_dep = declare_dependency( +# link_with: codecore, +# dependencies: dependencies, +# include_directories: [include_directories('.')] +# ) From 98c69ec2101e8c6a065f00d251db36bd870d1128 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 19 Jul 2026 16:07:02 +0100 Subject: [PATCH 20/21] Popup context menu on treelist --- src/FolderManager/FolderItem.vala | 9 +++++-- src/FolderManager/FolderTree.vala | 18 ++++++++++++-- src/FolderManager/ProjectFolderItem.vala | 2 +- src/FolderManager/ProjectList.vala | 31 ++++++++++++++++++------ src/Widgets/TreeList/TreeList.vala | 29 +++++++++++++++++++--- 5 files changed, 73 insertions(+), 16 deletions(-) diff --git a/src/FolderManager/FolderItem.vala b/src/FolderManager/FolderItem.vala index 46037f1213..039750ae3e 100644 --- a/src/FolderManager/FolderItem.vala +++ b/src/FolderManager/FolderItem.vala @@ -60,11 +60,15 @@ public interface Code.FolderInterface : Object { new_menu.append_item (new_folder_item); new_menu.append_item (new_file_item); - // //Append any templates/template folders. + // Append any templates/template folders. unowned string? template_path = GLib.Environment.get_user_special_dir (GLib.UserDirectory.TEMPLATES); if (template_path != null) { var template_submenu = new Menu (); - uint template_count = load_templates_from_folder (file_path, GLib.File.new_for_path (template_path), template_submenu); + uint template_count = load_templates_from_folder ( + file_path, + GLib.File.new_for_path (template_path), + template_submenu + ); if (template_count > 0) { if (template_count > MAX_TEMPLATES) { template_submenu.append_item (new MenuItem (_("…too many templates"), null)); @@ -292,6 +296,7 @@ public class Code.FolderItem : FolderManagerItem, Code.FolderInterface, Code.Fol } public override GLib.Menu? get_context_menu () { + warning ("Folder item get context menu"); var menu_model = base.get_context_menu (); // Add items related only to folders diff --git a/src/FolderManager/FolderTree.vala b/src/FolderManager/FolderTree.vala index ff92ca690b..1501fc4246 100644 --- a/src/FolderManager/FolderTree.vala +++ b/src/FolderManager/FolderTree.vala @@ -16,7 +16,7 @@ public class Code.FolderTreeItem : Code.TreeListItem { } } -public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { +public class Code.FolderTree : Gtk.Box, Code.PaneSwitcher { public signal void file_activate (File file); // All file related actions handled here @@ -39,6 +39,7 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { }; private Code.TreeList tree_list; + private Gtk.PopoverMenu context_menu; // private GLib.Settings settings; // private Scratch.Services.GitManager git_manager; // private Scratch.Services.PluginsManager plugins; @@ -84,7 +85,13 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { } }); - child = tree_list; + context_menu = new Gtk.PopoverMenu.from_model (null) { + position = BOTTOM + }; + this.append (context_menu); + tree_list.popup_context_menu.connect (on_popup_context_menu); + + append (tree_list); var cfile = new Code.File (root_path); foreach (var child_file in cfile.children) { @@ -232,6 +239,13 @@ public class Code.FolderTree : Granite.Bin, Code.PaneSwitcher { tree_list.remove_all (); } + private void on_popup_context_menu (Graphene.Point vp, Code.TreeListItem treelistitem) { + var foldermanageritem = (Code.FolderManagerItem) treelistitem; + var model = foldermanageritem.get_context_menu (); + context_menu.menu_model = model; + context_menu.pointing_to = Gdk.Rectangle () {x = (int) vp.x, y = (int) vp.y, height = 1, width = 1}; + context_menu.popup (); + } // public void collapse_other_projects () { // unowned string path; // path = git_manager.active_project_path; diff --git a/src/FolderManager/ProjectFolderItem.vala b/src/FolderManager/ProjectFolderItem.vala index 3dff7664dd..68eb1d13de 100644 --- a/src/FolderManager/ProjectFolderItem.vala +++ b/src/FolderManager/ProjectFolderItem.vala @@ -18,6 +18,7 @@ */ // Top level items for ProjectList not derived from TreeListItem but sharing some interfaces +// public class Code.ProjectFolderItem : Object, Code.FolderInterface, Code.FolderManagerItemInterface { public class Code.ProjectFolderItem : Object, Code.FolderInterface, Code.FolderManagerItemInterface { public signal void deleted (); public Scratch.Services.MonitoredRepository? monitored_repo { get; private set; default = null; } @@ -127,7 +128,6 @@ public class Code.ProjectFolderItem : Object, Code.FolderInterface, Code.FolderM // warning ("returns a menu model"); public override Menu? get_context_menu () { -warning ("get project menu"); string file_type = ""; try { var info = gfile.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, GLib.FileQueryInfoFlags.NONE); diff --git a/src/FolderManager/ProjectList.vala b/src/FolderManager/ProjectList.vala index d77c080425..aaeab360fc 100644 --- a/src/FolderManager/ProjectList.vala +++ b/src/FolderManager/ProjectList.vala @@ -277,19 +277,30 @@ public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { //TODO do we need to return anything? // Call to find top level foldertree and then call find path on that. - private FolderManagerItemInterface? find_path ( + private Object? find_path ( string path, // File path to search fod bool expand = false // Whether to expsnd to show found item // GLib.File? target_file = null // Alternatively find this file ) { warning ("Project list find path %s", path); // var target = target_file ?? GLib.File.new_for_path (path); - FolderManagerItemInterface? matched_item = null; + ProjectFolderItem? matched_project = null; iterate_children ((listitem) => { if (listitem.path == path) { - matched_item = listitem; + matched_project = listitem; return Code.TreeList.ITERATE_STOP; - } else if (path.has_prefix (listitem.path)) { //TODO Ensure paths are compatible + } + + return Code.TreeList.ITERATE_CONTINUE; + }); + + if (matched_project != null) { + return matched_project; + } + + FolderManagerItem? matched_item = null; + iterate_children ((listitem) => { + if (path.has_prefix (listitem.path)) { //TODO Ensure paths are compatible matched_item = listitem.find_path (path, expand); return Code.TreeList.ITERATE_STOP; } @@ -343,14 +354,18 @@ warning ("Project list find path %s", path); // return matched_project_item; // } - public FolderManagerItemInterface? expand_to_path (string path) { - warning ("expand to path"); - return find_path (path, true); + public FolderManagerItem? expand_to_path (string path) { + var object = find_path (path, true); + if (object is FolderManagerItem) { + return (FolderManagerItem) object; + } + + return null; } /* Do global search on project containing the file path supplied in parameter */ public void search_global (string path, string? term = null) { - var item_for_path = (FolderManagerItem?)(expand_to_path (path)); + var item_for_path = expand_to_path (path); if (item_for_path != null) { var search_root = item_for_path.get_root_folder (); if (search_root is ProjectFolderItem) { diff --git a/src/Widgets/TreeList/TreeList.vala b/src/Widgets/TreeList/TreeList.vala index add6234182..5370c7210c 100644 --- a/src/Widgets/TreeList/TreeList.vala +++ b/src/Widgets/TreeList/TreeList.vala @@ -6,6 +6,8 @@ public sealed class Code.TreeList : Granite.Bin { public signal void item_activated (TreeListItem item); + // Signals context menu button event giving coords on list view and the associated treelistitem + public signal void popup_context_menu (Graphene.Point view_point, Code.TreeListItem treelistitem); // public Gtk.Adjustment vadjustment { // get { @@ -41,7 +43,6 @@ public sealed class Code.TreeList : Granite.Bin { bind_property ("activate-on-single-click", list_view, "single-click-activate", BIDIRECTIONAL | SYNC_CREATE); list_view.activate.connect ((pos) => { - warning ("list view activate %u", pos); var tree_row = ((Gtk.TreeListRow) selection_model.get_item (pos)); var data = (Code.TreeListItem) (tree_row.item); //Always activate regardless of whether expandable - symbol pane works differently to foldermanager @@ -73,7 +74,7 @@ public sealed class Code.TreeList : Granite.Bin { child = list_view; } - protected virtual void create_listitem_child (Gtk.ListItem item) { + protected virtual void create_listitem_child (Gtk.ListItem listitem) { var label = new Gtk.Label ("") { halign = START, hexpand = true @@ -96,10 +97,30 @@ public sealed class Code.TreeList : Granite.Bin { child = box }; - item.child = expander; + listitem.child = expander; + + var button_controller = new Gtk.GestureClick () { + propagation_phase = CAPTURE, + button = 0 + }; + + box.add_controller (button_controller); + button_controller.pressed.connect ((n_press, bx, by) => { + var event = button_controller.get_last_event (null); + if (event.triggers_context_menu ()) { // Only true for press events + var treelistrow = (Gtk.TreeListRow) (listitem.get_item ()); + var data = (Code.TreeListItem) (treelistrow.get_item ()); + var button_point = Graphene.Point () {x = (float) bx, y = (float) by}; + var view_point = Graphene.Point (); + listitem.get_child ().compute_point (list_view, button_point, out view_point); + popup_context_menu (view_point, data); + } + }); } + protected virtual void teardown_listitem_child (Gtk.ListItem item) { // Must be paired with create_listitem child + // Assuming controller will be removed automatically on teardown } protected virtual void bind_data_to_row ( TreeListItem data, @@ -119,6 +140,8 @@ public sealed class Code.TreeList : Granite.Bin { secondary_image.icon_name = "emblem-default"; //TODO Different icon badge_label.label = "32"; //TODO Different icon name_label.label = data.text; + + } protected virtual void unbind_data_from_row ( TreeListItem data, From 67a1fd07d18502098fd6418c4e82a4d98540cfa7 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 19 Jul 2026 18:59:12 +0100 Subject: [PATCH 21/21] Simplify open document --- src/Application.vala | 8 ++- src/FolderManager/FolderTree.vala | 8 ++- src/MainWindow.vala | 95 ++++++++++++++++----------- src/Services/FileHandler.vala | 6 +- src/Services/LocationJumpManager.vala | 4 ++ src/Services/RestoreOverride.vala | 1 + src/Widgets/DocumentView.vala | 34 ++++++---- 7 files changed, 98 insertions(+), 58 deletions(-) diff --git a/src/Application.vala b/src/Application.vala index 0c68b356fd..593da2eab0 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -178,16 +178,18 @@ namespace Scratch { var window = get_last_window (); foreach (var file in files) { bool is_folder; + var path = file.get_uri (); if (Scratch.Services.FileHandler.can_open_file (file, out is_folder)) { if (is_folder) { window.open_folder_as_project (file); // Opens in sidebar not as documents } else { debug ("Files length: %d\n", files.length); - var doc = new Scratch.Services.Document (window.actions, file); + // var doc = new Scratch.Services.Document (window.actions, file); if (location_jump_manager.has_selection_range != null && files.length == 1) { - window.open_document_at_selected_range.begin (doc, true, location_jump_manager.range); + // We know file exists so we do not need to check again + window.document_view.open_document.begin (path, true, 0, location_jump_manager.range); } else { - window.open_document.begin (doc); + window.document_view.open_document.begin (path); } } } diff --git a/src/FolderManager/FolderTree.vala b/src/FolderManager/FolderTree.vala index 1501fc4246..68b05286bd 100644 --- a/src/FolderManager/FolderTree.vala +++ b/src/FolderManager/FolderTree.vala @@ -35,7 +35,8 @@ public class Code.FolderTree : Gtk.Box, Code.PaneSwitcher { { ACTION_EXECUTE_CONTRACT_WITH_FILE_PATH, action_execute_contract_with_file_path, "as" }, { ACTION_NEW_FILE, add_new_file, "s" }, { ACTION_NEW_FROM_TEMPLATE, add_new_from_template, "(ss)" }, - { ACTION_NEW_FOLDER, add_new_folder, "s"} + { ACTION_NEW_FOLDER, add_new_folder, "s"}, + }; private Code.TreeList tree_list; @@ -79,6 +80,11 @@ public class Code.FolderTree : Gtk.Box, Code.PaneSwitcher { // Convert ListView signal into file_activate tree_list.item_activated.connect ((item) => { if (item is FileItem) { + tree_list.activate_action ( + "win.show-document", + "s", + item.path + ); file_activate (((FileItem) item).file); } else if (item.is_expandable) { item.is_expanded = !item.is_expanded; diff --git a/src/MainWindow.vala b/src/MainWindow.vala index ace2501633..59023bbf89 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -39,11 +39,12 @@ namespace Scratch { } } - public Scratch.Widgets.DocumentView document_view; + public Scratch.Widgets.DocumentView document_view { get; private set; } - // Widgets + // These Widgets may be accessed by plugins so public public Scratch.HeaderBar toolbar; public Scratch.Widgets.SearchBar search_bar; + // Not available to plugins private Code.WelcomeView welcome_view; private Code.Terminal terminal; private Code.ProjectList folder_manager_view; @@ -73,6 +74,7 @@ namespace Scratch { public const string ACTION_FIND_PREVIOUS = "action-find-previous"; public const string ACTION_FIND_GLOBAL = "action-find-global"; public const string ACTION_OPEN = "action-open"; + public const string ACTION_SHOW_OR_OPEN_DOCUMENT = "action-document"; public const string ACTION_OPEN_FOLDER = "action-open-folder"; public const string ACTION_OPEN_PROJECT = "action-open-project"; public const string ACTION_COLLAPSE_ALL_FOLDERS = "action-collapse-all-folders"; @@ -132,6 +134,7 @@ namespace Scratch { { ACTION_FIND_PREVIOUS, action_find_previous }, { ACTION_FIND_GLOBAL, action_find_global, "s" }, { ACTION_OPEN, action_open }, + { ACTION_SHOW_OR_OPEN_DOCUMENT,action_document, "s" }, { ACTION_OPEN_FOLDER, action_open_folder_as_project, "s" }, { ACTION_OPEN_PROJECT, action_open_project }, { ACTION_COLLAPSE_ALL_FOLDERS, action_collapse_all_folders }, @@ -469,6 +472,7 @@ namespace Scratch { // sidebar.add_tab (new Granite.HeaderLabel ("Dummy Sidebar")); // folder_manager_view.file_activate.connect ((file) => { + // warning ("file activate %s", file.path); // // var file = new Code.File (a); // var doc = new Scratch.Services.Document (actions, file.file); @@ -649,18 +653,26 @@ namespace Scratch { if (file.query_exists ()) { var is_focused = uri == focused_uri; if (is_focused) { - focused_file = file; + focused_file = file; // remember focused file for focusing after all docs loaded } //TODO Check files valid (settings could have been manually altered) - var doc = new Scratch.Services.Document (actions, file); - if (doc.exists () || !doc.is_file_temporary) { - if (restore_override != null && (file.get_path () == restore_override.file.get_path ())) { - yield open_document_at_selected_range (doc, true, restore_override.range, true); + // var doc = new Scratch.Services.Document (actions, file); + // if (doc.exists () || !doc.is_file_temporary) { + if (restore_override != null && + (file.get_path () == restore_override.file.get_path ())) { + + // yield open_document_at_selected_range (doc, true, restore_override.range, true); + yield document_view.open_document (uri, true, 0, restore_override.range); was_restore_overriden = true; } else { - yield open_document (doc, was_restore_overriden ? false : is_focused, pos); + yield document_view.open_document ( + uri, + //Prevent focus being grabbed if there was an override + was_restore_overriden ? false : is_focused, + pos + ); } - } + // } } } } @@ -751,26 +763,30 @@ namespace Scratch { folder_manager_view.open_project_folder (foldermanager_file); } - public async void open_document (Scratch.Services.Document doc, - bool focus = true, - int cursor_position = 0) { - - // Code.ProjectFolderItem? project = folder_manager_view.get_project_for_file (doc.file); - // doc.source_view.project = project; - yield document_view.open_document (doc, focus, cursor_position); - } - - public async void open_document_at_selected_range (Scratch.Services.Document doc, - bool focus = true, - SelectionRange range = SelectionRange.EMPTY, - bool is_override = false) { - if (restore_override != null && is_override == false) { - return; - } - - // doc.source_view.project = folder_manager_view.get_project_for_file (doc.file); - yield document_view.open_document (doc, focus, 0, range); - } + // public async void open_document ( + // string path, + // bool focus = true, + // int cursor_position = 0 + // ) { + // // Code.ProjectFolderItem? project = folder_manager_view.get_project_for_file (doc.file); + // // doc.source_view.project = project; + // yield document_view.open_document (path, focus, cursor_position); + // } + + // public async void open_document_at_selected_range ( + // // Scratch.Services.Document doc, + // string doc_path, + // bool focus = true, + // SelectionRange range = SelectionRange.EMPTY, + // bool is_override = false + // ) { + // if (restore_override != null && is_override == false) { + // return; + // } + + // // doc.source_view.project = folder_manager_view.get_project_for_file (doc.file); + // yield document_view.open_document (doc_path, focus, 0, range); + // } // Close a document public void close_document (Scratch.Services.Document doc) { @@ -979,12 +995,13 @@ namespace Scratch { var index = 0; var obj = files.get_item (index++); while (obj != null) { - var file = (GLib.File) obj; + var gfile = (GLib.File) obj; // Update last visited path - Utils.last_path = Path.get_dirname (file.get_uri ()); + var path = gfile.get_uri (); + Utils.last_path = Path.get_dirname (path); // Open the file - var doc = new Scratch.Services.Document (actions, file); - open_document.begin (doc); + // var doc = new Scratch.Services.Document (actions, gfile); + document_view.open_document.begin (path); obj = files.get_item (index++); } } @@ -993,6 +1010,11 @@ namespace Scratch { file_chooser.show (); } + // Shows document if alreadu open else open it + private void action_document (SimpleAction action, Variant? param) requires (param != null) { + document_view.open_document.begin (param.get_string ()); + } + private void action_open_in_new_window (SimpleAction action, Variant? param) { var path = param.get_string (); if (path == "") { @@ -1001,9 +1023,9 @@ namespace Scratch { var new_window = new MainWindow (false); var file = File.new_for_path (path); - var doc = new Scratch.Services.Document (new_window.actions, file); + // var doc = new Scratch.Services.Document (new_window.actions, file); - new_window.open_document.begin (doc, true); + new_window.document_view.open_document.begin (path, true); } @@ -1258,8 +1280,7 @@ namespace Scratch { private void restore_project_docs (string project_path) { document_manager.take_restorable_paths (project_path).@foreach ((doc_path) => { - var doc = new Scratch.Services.Document (actions, File.new_for_path (doc_path)); - open_document.begin (doc); // Use this to reassociate project and document. + document_view.open_document.begin (doc_path); // Use this to reassociate project and document. return true; }); } diff --git a/src/Services/FileHandler.vala b/src/Services/FileHandler.vala index 6db312455e..e7d4db22c0 100644 --- a/src/Services/FileHandler.vala +++ b/src/Services/FileHandler.vala @@ -29,10 +29,10 @@ namespace Scratch.Services { } public class FileHandler : GLib.Object { - public static bool can_open_file (File file, out bool is_folder) { + public static bool can_open_file (GLib.File file, out bool is_folder) { is_folder = false; - if (file == null || file.get_path () == null) { - warning ("Ignoring file %s. Cannot determine path", + if (file == null || file.get_path () == null || !file.query_exists ()) { + warning ("Ignoring file %s. Cannot determine path or does not exist", file != null ? file.get_uri () ?? "null" : "null" ); diff --git a/src/Services/LocationJumpManager.vala b/src/Services/LocationJumpManager.vala index 4b4814472b..8cb3d8b495 100644 --- a/src/Services/LocationJumpManager.vala +++ b/src/Services/LocationJumpManager.vala @@ -7,9 +7,13 @@ namespace Scratch { public class LocationJumpManager : GLib.Object { + // This is created by the Application and manages parsing of commandline for a range + // and creating a restoreoverride object to be passed to the MainWindow + // for a particular file public GLib.File file { get; set; } public SelectionRange range { get; set; } + // Check the settings to see whether file was previously open and will be restored public bool has_override_target () { if (file == null) { return false; diff --git a/src/Services/RestoreOverride.vala b/src/Services/RestoreOverride.vala index 08887df62d..0aa0bf9c4e 100644 --- a/src/Services/RestoreOverride.vala +++ b/src/Services/RestoreOverride.vala @@ -9,6 +9,7 @@ public class RestoreOverride : GLib.Object { public GLib.File file { get; construct; } public SelectionRange range { get; construct; } + // This is used to override the cursor position that may be restored from settings public RestoreOverride (GLib.File file, SelectionRange range) { Object ( file: file, diff --git a/src/Widgets/DocumentView.vala b/src/Widgets/DocumentView.vala index 828e3981a9..7618e57d42 100644 --- a/src/Widgets/DocumentView.vala +++ b/src/Widgets/DocumentView.vala @@ -314,43 +314,46 @@ public class Scratch.Widgets.DocumentView : Gtk.Box { } public void new_document () { - var file = File.new_for_path (unsaved_file_path_builder ()); + var path = unsaved_file_path_builder (); + var file = File.new_for_path (path); try { file.create (FileCreateFlags.PRIVATE); - - var doc = new Services.Document (window.actions, file); // Must open document in order to unlock it. - open_document.begin (doc); + open_document.begin (path); } catch (Error e) { critical (e.message); } } public void new_document_from_clipboard (string clipboard) { - var file = File.new_for_path (unsaved_file_path_builder ()); + var path = unsaved_file_path_builder (); + var file = File.new_for_path (path); // Set clipboard content try { file.create (FileCreateFlags.PRIVATE); file.replace_contents (clipboard.data, null, false, 0, null); - var doc = new Services.Document (window.actions, file); - - open_document.begin (doc); - - + open_document.begin (path); } catch (Error e) { critical ("Cannot insert clipboard: %s", clipboard); } } - public async void open_document (Services.Document doc, bool focus = true, int cursor_position = 0, SelectionRange range = SelectionRange.EMPTY) { + // public async void open_document (Services.Document doc, bool focus = true, int cursor_position = 0, SelectionRange range = SelectionRange.EMPTY) { + public async void open_document ( + string doc_path, + bool focus = true, + int cursor_position = 0, + SelectionRange range = SelectionRange.EMPTY + ) { for (int n = 0; n <= docs.length (); n++) { var nth_doc = docs.nth_data (n); if (nth_doc == null) { continue; } - if (nth_doc.file != null && nth_doc.file.get_uri () == doc.file.get_uri ()) { + // if (nth_doc.file != null && nth_doc.file.get_uri () == doc.file.get_uri ()) { + if (nth_doc.file != null && nth_doc.file.get_uri () == doc_path) { if (focus) { current_document = nth_doc; } @@ -369,6 +372,10 @@ public class Scratch.Widgets.DocumentView : Gtk.Box { } } + var doc = new Scratch.Services.Document ( + window.actions, + GLib.File.new_for_path (doc_path) + ); insert_document (doc, (int) docs.length ()); if (focus) { current_document = doc; @@ -538,8 +545,7 @@ public class Scratch.Widgets.DocumentView : Gtk.Box { public void restore_closed_tab (string path) { var file = File.new_for_path (path); - var doc = new Services.Document (window.actions, file); - open_document.begin (doc); + open_document.begin (path); var menu = (Menu) tab_history_button.menu_model; for (var i = 0; i < menu.get_n_items (); i++) {