[gitg/wip/actions] wip



commit f0f4eab96c88876024478c47da39f2c745785508
Author: Jesse van den Kieboom <jessevdk gmail com>
Date:   Wed Jan 8 14:16:47 2014 +0100

    wip

 gitg/Makefile.am                                  |    3 +-
 gitg/gitg-ref-action-delete.vala                  |   29 +-
 gitg/gitg-ref-action-rename.vala                  |   25 +-
 gitg/gitg-window.vala                             |    7 +-
 gitg/history/gitg-history-navigation-store.vala   |  484 +++++++++++++++++
 gitg/history/gitg-history-navigation-view.vala    |  234 +++++++++
 gitg/history/gitg-history-navigation.vala         |  581 ---------------------
 gitg/history/gitg-history.vala                    |  193 +++++++-
 gitg/resources/gitg-resources.xml                 |    1 +
 gitg/resources/ui/gitg-history-navigation-view.ui |   54 ++
 libgitg-ext/gitg-ext-action.vala                  |   24 +-
 libgitg-ext/gitg-ext-application.vala             |    5 +
 libgitg/gitg-ref.vala                             |   12 +-
 13 files changed, 1043 insertions(+), 609 deletions(-)
---
diff --git a/gitg/Makefile.am b/gitg/Makefile.am
index 7311510..3839356 100644
--- a/gitg/Makefile.am
+++ b/gitg/Makefile.am
@@ -64,7 +64,8 @@ gitg_gitg_VALASOURCES =                                               \
        gitg/preferences/gitg-preferences-interface.vala        \
        gitg/preferences/gitg-preferences-history.vala          \
        gitg/history/gitg-history.vala                          \
-       gitg/history/gitg-history-navigation.vala               \
+       gitg/history/gitg-history-navigation-view.vala          \
+       gitg/history/gitg-history-navigation-store.vala         \
        gitg/history/gitg-history-paned.vala                    \
        gitg/commit/gitg-commit.vala                            \
        gitg/commit/gitg-commit-paned.vala                      \
diff --git a/gitg/gitg-ref-action-delete.vala b/gitg/gitg-ref-action-delete.vala
index e70ae87..cbc961e 100644
--- a/gitg/gitg-ref-action-delete.vala
+++ b/gitg/gitg-ref-action-delete.vala
@@ -20,22 +20,43 @@
 namespace Gitg
 {
 
-class RefActionDelete : GitgExt.RefAction, GitgExt.Action, Object
+class RefActionDelete : GitgExt.Action, GitgExt.RefAction, Object
 {
-       public Ggit.Ref reference { get; construct set; }
+       // Do this to pull in config.h before glib.h (for gettext...)
+       private const string version = Gitg.Config.VERSION;
+
        public GitgExt.ActionInterface action_interface { get; construct set; }
+       public Ggit.Ref reference { get; construct set; }
+
+       public RefActionDelete(GitgExt.ActionInterface action_interface, Ggit.Ref reference)
+       {
+               Object(action_interface: action_interface, reference: reference);
+       }
 
        public string label
        {
-               get { return "Delete"; }
+               get { return _("Delete"); }
        }
 
        public bool enabled
        {
+               get
+               {
+                       var r = reference as Gitg.Ref;
+                       var rtype = r.parsed_name.rtype;
+
+                       return    rtype == RefType.BRANCH
+                              || rtype == RefType.TAG
+                              || rtype == RefType.REMOTE;
+               }
+       }
+
+       public bool visible
+       {
                get { return true; }
        }
 
-       public void activate()
+       public void activated()
        {
        }
 }
diff --git a/gitg/gitg-ref-action-rename.vala b/gitg/gitg-ref-action-rename.vala
index 4362b35..a83e19c 100644
--- a/gitg/gitg-ref-action-rename.vala
+++ b/gitg/gitg-ref-action-rename.vala
@@ -20,23 +20,38 @@
 namespace Gitg
 {
 
-class RefActionRename : GitgExt.RefAction, GitgExt.Action, Object
+class RefActionRename : GitgExt.Action, GitgExt.RefAction, Object
 {
-       public Ggit.Ref reference { get; construct set; }
+       // Do this to pull in config.h before glib.h (for gettext...)
+       private const string version = Gitg.Config.VERSION;
+
        public GitgExt.ActionInterface action_interface { get; construct set; }
+       public Ggit.Ref reference { get; construct set; }
+
+       public RefActionRename(GitgExt.ActionInterface action_interface, Ggit.Ref reference)
+       {
+               Object(action_interface: action_interface, reference: reference);
+       }
 
        public string label
        {
-               get { return "Rename"; }
+               get { return _("Rename"); }
        }
 
-       public bool enabled
+       public bool visible
        {
                get { return true; }
        }
 
-       public void activate()
+       public bool enabled
        {
+               get
+               {
+                       var r = reference as Gitg.Ref;
+                       var rtype = r.parsed_name.rtype;
+
+                       return rtype == RefType.BRANCH || rtype == RefType.TAG;
+               }
        }
 }
 
diff --git a/gitg/gitg-window.vala b/gitg/gitg-window.vala
index eedb457..0a19121 100644
--- a/gitg/gitg-window.vala
+++ b/gitg/gitg-window.vala
@@ -21,7 +21,7 @@ namespace Gitg
 {
 
 [GtkTemplate (ui = "/org/gnome/gitg/ui/gitg-window.ui")]
-public class Window : Gtk.ApplicationWindow, GitgExt.Application, Initable
+public class Window : Gtk.ApplicationWindow, GitgExt.Application, GitgExt.ActionInterface, Initable
 {
        private Settings d_state_settings;
        private Settings d_interface_settings;
@@ -566,6 +566,11 @@ public class Window : Gtk.ApplicationWindow, GitgExt.Application, Initable
        {
                owned get { return d_environment; }
        }
+
+       public GitgExt.ActionInterface action_interface
+       {
+               owned get { return this; }
+       }
 }
 
 }
diff --git a/gitg/history/gitg-history-navigation-store.vala b/gitg/history/gitg-history-navigation-store.vala
new file mode 100644
index 0000000..0fa2e28
--- /dev/null
+++ b/gitg/history/gitg-history-navigation-store.vala
@@ -0,0 +1,484 @@
+/*
+ * This file is part of gitg
+ *
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gitg is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gitg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY 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 gitg. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GitgHistory
+{
+       [Flags]
+       private enum Hint
+       {
+               NONE            = 0,
+               DEFAULT         = 1 << 0,
+               SEPARATOR       = 1 << 1,
+               HEADER_BRANCHES = 1 << 2,
+               HEADER_REMOTES  = 1 << 3,
+               HEADER_REMOTE   = 1 << 4,
+               HEADER_TAGS     = 1 << 5,
+               HEADER          =   HEADER_BRANCHES
+                                 | HEADER_REMOTES
+                                 | HEADER_REMOTE
+                                 | HEADER_TAGS
+       }
+
+       private enum Column
+       {
+               ICON_NAME,
+               NAME,
+               TEXT,
+               HINT,
+               REF,
+               BUSY
+       }
+
+       public class NavigationStore : Gtk.TreeStore
+       {
+               // Do this to pull in config.h before glib.h (for gettext...)
+               private const string version = Gitg.Config.VERSION;
+
+               private List<Gitg.Ref> d_all;
+               private Gitg.Repository? d_repository;
+
+               public NavigationStore(Gitg.Repository? repo)
+               {
+                       Object(repository: repo);
+               }
+
+               construct
+               {
+                       set_column_types({typeof(string),
+                                         typeof(string),
+                                         typeof(string),
+                                         typeof(uint),
+                                         typeof(uint),
+                                         typeof(Gitg.Ref),
+                                         typeof(uint)
+                       });
+               }
+
+               public List<Gitg.Ref> all
+               {
+                       get { return d_all; }
+               }
+
+               [Notify]
+               public Gitg.Repository repository
+               {
+                       get { return d_repository; }
+                       set
+                       {
+                               d_repository = value;
+                               reload();
+                       }
+               }
+
+               public bool show_expanders
+               {
+                       get { return false; }
+               }
+
+               private static int sort_refs(Gitg.Ref a, Gitg.Ref b)
+               {
+                       return a.parsed_name.shortname.ascii_casecmp(b.parsed_name.shortname);
+               }
+
+               private static int sort_remote_refs(Gitg.Ref a, Gitg.Ref b)
+               {
+                       return a.parsed_name.remote_branch.ascii_casecmp(b.parsed_name.remote_branch);
+               }
+
+               private bool find_header(out Gtk.TreeIter iter, Hint header_type)
+               {
+                       header_type &= Hint.HEADER;
+
+                       if (!get_iter_first(out iter))
+                       {
+                               return false;
+                       }
+
+                       while (true)
+                       {
+                               Hint hint = Hint.NONE;
+
+                               @get(iter, Column.HINT, out hint);
+
+                               if ((hint & header_type) != 0)
+                               {
+                                       return true;
+                               }
+
+                               if (!iter_next(ref iter))
+                               {
+                                       break;
+                               }
+                       }
+
+                       return false;
+               }
+
+               private bool find_parent_iter(out Gtk.TreeIter iter, Gitg.Ref reference)
+               {
+                       Hint header_type = Hint.NONE;
+
+                       switch (reference.parsed_name.rtype)
+                       {
+                               case Gitg.RefType.BRANCH:
+                                       header_type = Hint.HEADER_BRANCHES;
+                                       break;
+                               case Gitg.RefType.TAG:
+                                       header_type = Hint.HEADER_TAGS;
+                                       break;
+                               case Gitg.RefType.REMOTE:
+                                       header_type = Hint.HEADER_REMOTES;
+                                       break;
+                               default:
+                                       iter = Gtk.TreeIter();
+                                       return false;
+                       }
+
+                       return find_header(out iter, header_type);
+               }
+
+               private bool find_insert_before(out Gtk.TreeIter iter,
+                                               Gtk.TreeIter     parent,
+                                               string           text)
+               {
+                       if (!iter_children(out iter, parent))
+                       {
+                               return false;
+                       }
+
+                       while (true)
+                       {
+                               string cmptext;
+                               @get(iter, Column.TEXT, out cmptext);
+
+                               if (text < cmptext)
+                               {
+                                       return true;
+                               }
+
+                               if (!iter_next(ref iter))
+                               {
+                                       break;
+                               }
+                       }
+
+                       return false;
+               }
+
+               private void add_ref_internal(Gtk.TreeIter? parent,
+                                             Gitg.Ref      reference,
+                                             bool          append)
+               {
+                       var pn = reference.parsed_name;
+                       string text;
+                       string name = pn.name;
+                       string? icon_name = null;
+                       Hint hint = Hint.NONE;
+
+                       if (pn.rtype == Gitg.RefType.REMOTE)
+                       {
+                               text = pn.remote_branch;
+                       }
+                       else
+                       {
+                               text = pn.shortname;
+                       }
+
+                       var branch = reference as Ggit.Branch;
+
+                       if (branch != null)
+                       {
+                               try
+                               {
+                                       if (branch.is_head())
+                                       {
+                                               icon_name = "object-select-symbolic";
+                                               hint |= Hint.DEFAULT;
+                                       }
+                               } catch {}
+                       }
+
+                       Gtk.TreeIter? par = null;
+
+                       if (parent == null)
+                       {
+                               find_parent_iter(out par, reference);
+                       }
+                       else
+                       {
+                               par = parent;
+                       }
+
+                       Gtk.TreeIter iter;
+                       Gtk.TreeIter before = Gtk.TreeIter();
+
+                       if (append || !find_insert_before(out before, par, text))
+                       {
+                               base.append(out iter, par);
+                       }
+                       else
+                       {
+                               base.insert_before(out iter, par, before);
+                       }
+
+                       @set(iter,
+                            Column.ICON_NAME, icon_name,
+                            Column.NAME, name,
+                            Column.TEXT, text,
+                            Column.HINT, hint,
+                            Column.REF, reference);
+               }
+
+               private void append_ref(Gtk.TreeIter? parent, Gitg.Ref reference)
+               {
+                       add_ref_internal(parent, reference, true);
+               }
+
+               private void insert_ref(Gtk.TreeIter? parent, Gitg.Ref reference)
+               {
+                       add_ref_internal(parent, reference, false);
+               }
+
+               private void make_header(out Gtk.TreeIter iter,
+                                        Gtk.TreeIter?    parent,
+                                        string           text,
+                                        Hint             hint)
+               {
+                       base.append(out iter, parent);
+
+                       @set(iter,
+                            Column.TEXT, text,
+                            Column.HINT, hint);
+               }
+
+               private void populate(Gitg.Repository repo)
+               {
+                       List<Gitg.Ref> branches = new List<Gitg.Ref>();
+                       List<Gitg.Ref> tags = new List<Gitg.Ref>();
+
+                       HashTable<string, Gee.LinkedList<Gitg.Ref>> remotes;
+                       List<string> remotenames = new List<string>();
+
+                       remotes = new HashTable<string, Gee.LinkedList<Gitg.Ref>>(str_hash, str_equal);
+                       d_all = new List<Gitg.Ref>();
+
+                       try
+                       {
+                               repo.references_foreach_name((nm) => {
+                                       Gitg.Ref? r;
+
+                                       try
+                                       {
+                                               r = repo.lookup_reference(nm);
+                                       } catch { return 0; }
+
+                                       d_all.prepend(r);
+
+                                       if (r.parsed_name.rtype == Gitg.RefType.BRANCH)
+                                       {
+                                               branches.insert_sorted(r, sort_refs);
+                                       }
+                                       else if (r.parsed_name.rtype == Gitg.RefType.TAG)
+                                       {
+                                               tags.insert_sorted(r, sort_refs);
+                                       }
+                                       else if (r.parsed_name.rtype == Gitg.RefType.REMOTE)
+                                       {
+                                               Gee.LinkedList<Gitg.Ref> lst;
+
+                                               string rname = r.parsed_name.remote_name;
+
+                                               if (!remotes.lookup_extended(rname, null, out lst))
+                                               {
+                                                       Gee.LinkedList<Gitg.Ref> nlst = new 
Gee.LinkedList<Gitg.Ref>();
+                                                       nlst.insert(0, r);
+
+                                                       remotes.insert(rname, nlst);
+                                                       remotenames.insert_sorted(rname, (a, b) => 
a.ascii_casecmp(b));
+                                               }
+                                               else
+                                               {
+                                                       lst.insert(0, r);
+                                               }
+                                       }
+
+                                       return 0;
+                               });
+
+                               if (repo.is_head_detached())
+                               {
+                                       d_all.prepend(repo.get_head());
+                               }
+                       } catch {}
+
+                       d_all.reverse();
+
+                       //append_normal(_("All commits"), null, null, null, (nc) => activate_ref(null));
+                       Gtk.TreeIter ibranches;
+                       make_header(out ibranches, null, _("Branches"), Hint.HEADER_BRANCHES);
+
+                       foreach (var item in branches)
+                       {
+                               append_ref(ibranches, item);
+                       }
+
+                       // Remotes
+                       Gtk.TreeIter iremotes;
+                       make_header(out iremotes, null, _("Remotes"), Hint.HEADER_REMOTES);
+
+                       foreach (var rname in remotenames)
+                       {
+                               Gtk.TreeIter iremote;
+                               make_header(out iremote, iremotes, rname, Hint.HEADER_REMOTE);
+
+                               var rrefs = remotes.lookup(rname);
+
+                               rrefs.sort((CompareDataFunc)sort_remote_refs);
+
+                               foreach (var rref in remotes.lookup(rname))
+                               {
+                                       append_ref(iremote, rref);
+                               }
+                       }
+
+                       // Tags
+                       Gtk.TreeIter itags;
+                       make_header(out itags, null, _("Tags"), Hint.HEADER_TAGS);
+
+                       foreach (var item in tags)
+                       {
+                               append_ref(itags, item);
+                       }
+               }
+
+               public void reload()
+               {
+                       if (d_repository != null)
+                       {
+                               clear();
+                               populate(d_repository);
+                       }
+               }
+
+               public bool remove_ref(Gitg.Ref reference)
+               {
+                       Gtk.TreeIter iter;
+
+                       if (get_iter_for_ref(out iter, reference))
+                       {
+                               if (remove(ref iter))
+                               {
+                                       d_all.remove(reference);
+                                       return true;
+                               }
+                       }
+
+                       return false;
+               }
+
+               public bool add_ref(Gitg.Ref reference)
+               {
+                       insert_ref(null, reference);
+                       d_all.append(reference);
+
+                       return true;
+               }
+
+               public Gtk.TreePath? get_path_for_ref(Gitg.Ref reference)
+               {
+                       return get_for_ref(null, reference);
+               }
+
+               public bool get_iter_for_ref(out Gtk.TreeIter iter, Gitg.Ref? reference)
+               {
+                       return get_for_ref(out iter, reference) != null;
+               }
+
+               private Gtk.TreePath? get_for_ref(out Gtk.TreeIter iter, Gitg.Ref? reference)
+               {
+                       Gtk.TreePath? ret = null;
+                       Gtk.TreeIter liter = Gtk.TreeIter();
+
+                       @foreach((model, path, i) => {
+                               Gitg.Ref? obj = null;
+
+                               @get(i, Column.REF, out obj);
+
+                               if (obj == reference)
+                               {
+                                       liter = i;
+                                       ret = path;
+
+                                       return true;
+                               }
+
+                               return false;
+                       });
+
+                       iter = liter;
+                       return ret;
+               }
+
+               public void set_busy(Gitg.Ref reference, bool isbusy)
+               {
+                       Gtk.TreeIter iter;
+
+                       if (!get_iter_for_ref(out iter, reference))
+                       {
+                               return;
+                       }
+
+                       if (!isbusy)
+                       {
+                               @set(iter, Column.BUSY, 0);
+                               return;
+                       }
+
+                       uint busy_now;
+                       @get(iter, Column.BUSY, out busy_now);
+
+                       if (busy_now != 0)
+                       {
+                               return;
+                       }
+
+                       @set(iter, Column.BUSY, 1);
+
+                       Timeout.add(50, () => {
+                               uint busy = 0;
+                               @get(iter, Column.BUSY, out busy);
+
+                               if (busy == 0)
+                               {
+                                       return false;
+                               }
+
+                               if (busy == uint.MAX)
+                               {
+                                       busy = 0;
+                               }
+
+                               @set(iter, Column.BUSY, busy + 1);
+                               return true;
+                       });
+               }
+       }
+}
+
+// ex: ts=4 noet
diff --git a/gitg/history/gitg-history-navigation-view.vala b/gitg/history/gitg-history-navigation-view.vala
new file mode 100644
index 0000000..0722175
--- /dev/null
+++ b/gitg/history/gitg-history-navigation-view.vala
@@ -0,0 +1,234 @@
+/*
+ * This file is part of gitg
+ *
+ * Copyright (C) 2012 - Jesse van den Kieboom
+ *
+ * gitg is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gitg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY 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 gitg. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GitgHistory
+{
+
+[GtkTemplate (ui = "/org/gnome/gitg/ui/gitg-history-navigation-view.ui")]
+public class NavigationView : Gtk.TreeView
+{
+       [GtkChild]
+       private Gtk.TreeViewColumn d_column;
+
+       [GtkChild]
+       private Gtk.CellRendererPixbuf d_renderer_icon;
+
+       [GtkChild]
+       private Gtk.CellRendererSpinner d_renderer_spinner;
+
+       [GtkChild]
+       private Gtk.CellRendererText d_renderer_text;
+
+       public signal void edited(Gitg.Ref reference, Gtk.TreePath path, string text);
+       public signal void ref_activated(Gitg.Ref? r);
+
+       construct
+       {
+               finish_ui();
+       }
+
+       private void finish_ui()
+       {
+               d_column.set_attributes(d_renderer_icon, "icon-name", Column.ICON_NAME);
+               d_column.set_attributes(d_renderer_text, "text", Column.TEXT);
+
+               d_column.set_cell_data_func(d_renderer_icon, (layout, cell, model, iter) => {
+                       string? icon_name;
+                       uint busy;
+
+                       model.get(iter,
+                                 Column.ICON_NAME, out icon_name,
+                                 Column.BUSY, out busy);
+
+                       cell.visible = (icon_name != null) && busy == 0;
+               });
+
+               d_column.set_cell_data_func(d_renderer_spinner, (layout, cell, model, iter) => {
+                       uint busy;
+
+                       model.get(iter, Column.BUSY, out busy);
+
+                       var isbusy = busy > 0;
+
+                       d_renderer_spinner.visible = isbusy;
+                       d_renderer_spinner.active = isbusy;
+                       d_renderer_spinner.pulse = busy;
+               });
+
+               d_column.set_cell_data_func(d_renderer_text, (layout, cell, model, iter) => {
+                       Hint hint;
+                       model.get(iter, Column.HINT, out hint);
+
+                       if ((hint & Hint.HEADER) != 0)
+                       {
+                               d_renderer_text.ypad = 6;
+                               d_renderer_text.weight = Pango.Weight.BOLD;
+                       }
+                       else
+                       {
+                               d_renderer_text.ypad = 0;
+                               d_renderer_text.weight = Pango.Weight.NORMAL;
+                       }
+               });
+
+               set_row_separator_func((model, iter) => {
+                       Hint hint;
+                       model.get(iter, Column.HINT, out hint);
+
+                       return hint == Hint.SEPARATOR;
+               });
+
+               var selection = get_selection();
+
+               selection.set_select_function((sel, model, path, cursel) => {
+                       Gtk.TreeIter iter;
+                       model.get_iter(out iter, path);
+
+                       uint hint;
+
+                       model.get(iter, Column.HINT, out hint);
+
+                       return (hint & Hint.HEADER) == 0;
+               });
+       }
+
+       public new NavigationStore model
+       {
+               get { return base.get_model() as NavigationStore; }
+               set { base.set_model(value); }
+       }
+
+       [GtkCallback]
+       private void on_renderer_text_edited(string path, string new_text)
+       {
+               Gtk.TreePath p = new Gtk.TreePath.from_string(path);
+               Gtk.TreeIter iter;
+
+               model.get_iter(out iter, p);
+
+               Gitg.Ref? ret = null;
+               model.get(iter, Column.REF, ref ret);
+
+               edited(ret, p, new_text);
+       }
+
+       private bool select_first_in(Gtk.TreeIter? parent, bool seldef)
+       {
+               Gtk.TreeIter iter;
+
+               if (!model.iter_children(out iter, parent))
+               {
+                       return false;
+               }
+
+               while (true)
+               {
+                       uint hint;
+                       model.get(iter, Column.HINT, out hint);
+
+                       if ((hint & Hint.HEADER) != 0)
+                       {
+                               if (select_first_in(iter, seldef))
+                               {
+                                       return true;
+                               }
+                       }
+
+                       if (!seldef || hint == Hint.DEFAULT)
+                       {
+                               get_selection().select_iter(iter);
+                               return true;
+                       }
+
+                       if (!model.iter_next(ref iter))
+                       {
+                               return false;
+                       }
+               }
+       }
+
+       public void select_first()
+       {
+               select_first_in(null, true) || select_first_in(null, false);
+       }
+
+       public void select()
+       {
+//             if (model.selected_iter != null)
+//             {
+//                     get_selection().select_iter(model.selected_iter);
+//                     model.selected_iter = null;
+//             }
+//             else
+//             {
+                       select_first();
+//             }
+       }
+
+       public Gitg.Ref? selected_ref
+       {
+               owned get
+               {
+                       Gtk.TreeModel model;
+                       Gtk.TreeIter iter;
+
+                       if (!get_selection().get_selected(out model, out iter))
+                       {
+                               return null;
+                       }
+
+                       Gitg.Ref? ret = null;
+                       model.get(iter, Column.REF, out ret);
+
+                       return ret;
+               }
+       }
+
+       public void edit(Gitg.Ref reference)
+       {
+               var path = model.get_path_for_ref(reference);
+
+               if (path != null)
+               {
+                       set_cursor_on_cell(path, d_column, d_renderer_text, true);
+               }
+       }
+
+       protected override void row_activated(Gtk.TreePath path, Gtk.TreeViewColumn col)
+       {
+               Gtk.TreeIter iter;
+
+               if (!model.get_iter(out iter, path))
+               {
+                       return;
+               }
+
+               Gitg.Ref? reference = null;
+               model.get(iter, Column.REF, out reference);
+
+               if (reference != null)
+               {
+                       ref_activated(reference);
+               }
+       }
+}
+
+}
+
+// ex: ts=4 noet
diff --git a/gitg/history/gitg-history.vala b/gitg/history/gitg-history.vala
index 57bf6b8..59efb69 100644
--- a/gitg/history/gitg-history.vala
+++ b/gitg/history/gitg-history.vala
@@ -29,7 +29,7 @@ namespace GitgHistory
 
                public GitgExt.Application? application { owned get; construct set; }
 
-               private Navigation? d_navigation_model;
+               private NavigationStore? d_navigation_store;
                private Gitg.CommitModel? d_commit_list_model;
                private Gee.HashSet<Ggit.OId> d_selected;
                private ulong d_insertsig;
@@ -88,10 +88,7 @@ namespace GitgHistory
                        d_selected = new Gee.HashSet<Ggit.OId>((Gee.HashDataFunc<Ggit.OId>)Ggit.OId.hash,
                                                               (Gee.EqualDataFunc<Ggit.OId>)Ggit.OId.equal);
 
-                       d_navigation_model = new Navigation(application.repository);
-                       d_navigation_model.ref_activated.connect((r) => {
-                               on_ref_activated(d_navigation_model, r);
-                       });
+                       d_navigation_store = new NavigationStore(application.repository);
 
                        d_commit_list_model = new Gitg.CommitModel(application.repository);
                        d_commit_list_model.started.connect(on_commit_model_started);
@@ -99,7 +96,7 @@ namespace GitgHistory
 
                        update_sort_mode();
 
-                       application.bind_property("repository", d_navigation_model,
+                       application.bind_property("repository", d_navigation_store,
                                                  "repository", BindingFlags.DEFAULT);
 
                        application.bind_property("repository", d_commit_list_model,
@@ -197,11 +194,6 @@ namespace GitgHistory
                        return -1;
                }
 
-               private void on_ref_activated(Navigation n, Gitg.Ref? r)
-               {
-                       update_walker(n, r);
-               }
-
                public void activate()
                {
                        d_main.navigation_view.expand_all();
@@ -212,7 +204,7 @@ namespace GitgHistory
                {
                        double vadj = d_main.navigation_view.get_vadjustment().get_value();
 
-                       d_navigation_model.reload();
+                       d_navigation_store.reload();
                        d_main.navigation_view.expand_all();
                        d_main.navigation_view.select();
 
@@ -225,7 +217,7 @@ namespace GitgHistory
                {
                        d_main = new Paned();
 
-                       d_main.navigation_view.model = d_navigation_model;
+                       d_main.navigation_view.model = d_navigation_store;
                        d_main.commit_list_view.model = d_commit_list_model;
 
                        d_main.commit_list_view.get_selection().changed.connect((sel) => {
@@ -243,9 +235,180 @@ namespace GitgHistory
 
                        d_panels = new Gitg.UIElements<GitgExt.HistoryPanel>(extset,
                                                                             d_main.stack_panel);
+
+                       d_main.navigation_view.popup_menu.connect(on_navigation_popup_menu);
+                       d_main.navigation_view.button_press_event.connect(on_navigation_button_press_event);
+
+                       d_main.navigation_view.edited.connect(on_ref_edited);
+
+                       d_main.navigation_view.ref_activated.connect((r) => {
+                               update_walker(r);
+                       });
+               }
+
+               private void on_ref_edited(Gitg.Ref reference, Gtk.TreePath path, string newname)
+               {
+                       string orig;
+                       string? prefix;
+
+                       var pn = reference.parsed_name;
+
+                       if (pn.rtype == Gitg.RefType.REMOTE)
+                       {
+                               orig = pn.remote_branch;
+                               prefix = pn.prefix + "/" + pn.remote_name;
+                       }
+                       else
+                       {
+                               orig = pn.shortname;
+                               prefix = pn.prefix;
+                       }
+
+                       if (orig == newname)
+                       {
+                               return;
+                       }
+
+                       if (!Ggit.Ref.is_valid_name(@"$prefix/$newname"))
+                       {
+                               var msg = _("The specified name ā€˜%sā€™ contains invalid 
characters").printf(newname);
+
+                               application.show_infobar(_("Invalid name"),
+                                                        msg,
+                                                        Gtk.MessageType.ERROR);
+
+                               return;
+                       }
+
+                       var branch = reference as Ggit.Branch;
+                       Gitg.Ref? new_ref = null;
+
+                       try
+                       {
+                               if (branch != null)
+                               {
+                                       new_ref = branch.move(newname, Ggit.CreateFlags.NONE) as Gitg.Ref;
+                               }
+                               else
+                               {
+                                       new_ref = reference.rename(newname, false) as Gitg.Ref;
+                               }
+                       }
+                       catch (Error e)
+                       {
+                               application.show_infobar(_("Failed to rename"),
+                                                        e.message,
+                                                        Gtk.MessageType.ERROR);
+
+                               return;
+                       }
+
+                       d_navigation_store.remove_ref(reference);
+                       d_navigation_store.add_ref(new_ref);
+               }
+
+               private void add_ref_action(Gee.LinkedList<GitgExt.RefAction> actions, GitgExt.RefAction? 
action)
+               {
+                       if (action.visible)
+                       {
+                               actions.add(action);
+                       }
+               }
+
+               private bool navigation_popup_menu(Gtk.Widget widget, Gdk.EventButton? event)
+               {
+                       var button = (event == null ? 0 : event.button);
+                       var time = (event == null ? Gtk.get_current_event_time() : event.time);
+
+                       var actions = new Gee.LinkedList<GitgExt.RefAction>();
+                       var reference = d_main.navigation_view.selected_ref;
+
+                       if (reference == null)
+                       {
+                               return false;
+                       }
+
+                       var rename = new Gitg.RefActionRename(application.action_interface,
+                                                             reference);
+
+                       rename.activated.connect(() => { on_rename_activated(rename); });
+
+                       add_ref_action(actions, rename);
+
+                       add_ref_action(actions,
+                                      new Gitg.RefActionDelete(application.action_interface,
+                                                               reference));
+
+                       var exts = new Peas.ExtensionSet(Gitg.PluginsEngine.get_default(),
+                                                        typeof(GitgExt.RefAction),
+                                                        "action_interface",
+                                                        application.action_interface,
+                                                        "reference",
+                                                        reference);
+
+                       exts.foreach((extset, info, extension) => {
+                               add_ref_action(actions, extension as GitgExt.RefAction);
+                       });
+
+                       if (actions.is_empty)
+                       {
+                               return false;
+                       }
+
+                       Gtk.Menu menu = new Gtk.Menu();
+
+                       foreach (var ac in actions)
+                       {
+                               ac.populate_menu(menu);
+                       }
+
+                       menu.attach_to_widget(widget, null);
+                       menu.popup(null, null, null, button, time);
+
+                       return true;
+               }
+
+               private void on_rename_activated(Gitg.RefActionRename action)
+               {
+                       d_main.navigation_view.edit(action.reference as Gitg.Ref);
+               }
+
+               private bool on_navigation_popup_menu(Gtk.Widget widget)
+               {
+                       return navigation_popup_menu(widget, null);
+               }
+
+               private bool on_navigation_button_press_event(Gtk.Widget widget, Gdk.EventButton event)
+               {
+                       Gdk.Event *ev = (Gdk.Event *)(&event);
+
+                       if (!ev->triggers_context_menu())
+                       {
+                               return false;
+                       }
+
+                       Gtk.TreePath path;
+
+                       if (d_main.navigation_view.get_path_at_pos((int)event.x,
+                                                                  (int)event.y,
+                                                                  out path,
+                                                                  null,
+                                                                  null,
+                                                                  null))
+                       {
+                               var sel = d_main.navigation_view.get_selection();
+
+                               if (!sel.path_is_selected(path))
+                               {
+                                       sel.unselect_all();
+                                       sel.select_path(path);
+                               }
+                       }
+
+                       return navigation_popup_menu(widget, event);
                }
 
-               private void update_walker(Navigation n, Gitg.Ref? head)
+               private void update_walker(Gitg.Ref? head)
                {
                        Ggit.OId? id = null;
 
@@ -277,7 +440,7 @@ namespace GitgHistory
                                var included = new Ggit.OId[] {};
 
                                // Simply push all the refs
-                               foreach (Gitg.Ref r in n.all)
+                               foreach (Gitg.Ref r in d_navigation_store.all)
                                {
                                        try
                                        {
diff --git a/gitg/resources/gitg-resources.xml b/gitg/resources/gitg-resources.xml
index 6c4b21c..87d2112 100644
--- a/gitg/resources/gitg-resources.xml
+++ b/gitg/resources/gitg-resources.xml
@@ -11,6 +11,7 @@
     <file compressed="true" preprocess="xml-stripblanks">ui/gitg-clone-dialog.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/gitg-author-details-dialog.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/gitg-history-paned.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks">ui/gitg-history-navigation-view.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/gitg-commit-paned.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/gitg-commit-dialog.ui</file>
     <file compressed="true">ui/style.css</file>
diff --git a/gitg/resources/ui/gitg-history-navigation-view.ui 
b/gitg/resources/ui/gitg-history-navigation-view.ui
new file mode 100644
index 0000000..3f9cc59
--- /dev/null
+++ b/gitg/resources/ui/gitg-history-navigation-view.ui
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.3 -->
+  <!-- interface-requires gitg 0.0 -->
+  <!-- interface-requires gd 1.0 -->
+  <template class="GitgHistoryNavigationView" parent="Gtk.TreeView">
+    <property name="visible">True</property>
+    <property name="hexpand">True</property>
+    <property name="vexpand">True</property>
+    <property name="can_focus">True</property>
+    <property name="activate_on_single_click">True</property>
+    <property name="show_expanders">False</property>
+    <property name="level_indentation">12</property>
+    <child>
+      <object class="GtkTreeViewColumn" id="d_column">
+        <child>
+          <object class="GtkCellRendererText" id="d_renderer_pad">
+            <property name="xpad">6</property>
+          </object>
+          <cell-packing>
+            <property name="expand">False</property>
+          </cell-packing>
+        </child>
+        <child>
+          <object class="GtkCellRendererPixbuf" id="d_renderer_icon">
+            <property name="follow_state">True</property>
+          </object>
+          <cell-packing>
+            <property name="expand">False</property>
+          </cell-packing>
+        </child>
+        <child>
+          <object class="GtkCellRendererSpinner" id="d_renderer_spinner">
+          </object>
+          <cell-packing>
+            <property name="expand">False</property>
+          </cell-packing>
+        </child>
+        <child>
+          <object class="GtkCellRendererText" id="d_renderer_text">
+            <property name="editable">True</property>
+            <property name="ellipsize">middle</property>
+            <signal name="edited" handler="on_renderer_text_edited"/>
+          </object>
+          <cell-packing>
+            <property name="expand">True</property>
+          </cell-packing>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
+
+<!-- ex:set ts=2 et: -->
diff --git a/libgitg-ext/gitg-ext-action.vala b/libgitg-ext/gitg-ext-action.vala
index a1dcc27..d89a1a9 100644
--- a/libgitg-ext/gitg-ext-action.vala
+++ b/libgitg-ext/gitg-ext-action.vala
@@ -26,8 +26,30 @@ public interface Action : Object
 
        public abstract string label { get; }
        public abstract bool enabled { get; }
+       public abstract bool visible { get; }
 
-       public abstract void activate();
+       public virtual signal void activated()
+       {
+       }
+
+       public virtual void populate_menu(Gtk.Menu menu)
+       {
+               var item = new Gtk.MenuItem.with_label(label);
+
+               if (enabled)
+               {
+                       item.activate.connect(() => {
+                               activated();
+                       });
+               }
+               else
+               {
+                       item.sensitive = false;
+               }
+
+               item.show();
+               menu.append(item);
+       }
 }
 
 }
diff --git a/libgitg-ext/gitg-ext-application.vala b/libgitg-ext/gitg-ext-application.vala
index cffeb12..e22f5d6 100644
--- a/libgitg-ext/gitg-ext-application.vala
+++ b/libgitg-ext/gitg-ext-application.vala
@@ -46,6 +46,11 @@ public interface Application : Object
        public abstract GitgExt.Activity? current_activity { owned get; }
 
        /**
+        * The application action interface.
+        */
+       public abstract GitgExt.ActionInterface action_interface { owned get; }
+
+       /**
         * Set the current application main activity.
         *
         * @param id the id of the activity { link UIElement.id}.
diff --git a/libgitg/gitg-ref.vala b/libgitg/gitg-ref.vala
index 387348e..1cd8ede 100644
--- a/libgitg/gitg-ref.vala
+++ b/libgitg/gitg-ref.vala
@@ -48,6 +48,7 @@ public class ParsedRefName : Object
        private string d_name;
        private string d_remote_name;
        private string d_remote_branch;
+       private string? d_prefix;
 
        /**
         * The type of ref.
@@ -92,6 +93,11 @@ public class ParsedRefName : Object
                parse_name(name);
        }
 
+       public string? prefix
+       {
+               get { return d_prefix; }
+       }
+
        private void parse_name(string name)
        {
                d_name = name;
@@ -104,6 +110,7 @@ public class ParsedRefName : Object
                };
 
                d_shortname = name;
+               d_prefix = null;
 
                for (var i = 0; i < prefixes.length; ++i)
                {
@@ -112,15 +119,18 @@ public class ParsedRefName : Object
                                continue;
                        }
 
+                       d_prefix = prefixes[i];
+
                        rtype = (RefType)(i + 1);
 
                        if (rtype == RefType.STASH)
                        {
+                               d_prefix = "refs/";
                                d_shortname = "stash";
                        }
                        else
                        {
-                               d_shortname = d_name[prefixes[i].length:d_name.length];
+                               d_shortname = d_name[d_prefix.length:d_name.length];
                        }
 
                        if (rtype == RefType.REMOTE)


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]