[gitg/wip/actions] Improve selection and implemented editing



commit 3d0ddf2380026022e7b2bef63e3bbc6f8a2b8e87
Author: Jesse van den Kieboom <jessevdk gmail com>
Date:   Sat Jan 18 13:28:23 2014 +0100

    Improve selection and implemented editing

 gitg/history/gitg-history-refs-list.vala |  338 +++++++++++++++++++++++++++---
 gitg/history/gitg-history.vala           |  219 +++++++++++++++-----
 gitg/resources/ui/style.css              |    9 +
 libgitg/gitg-ref.vala                    |   12 +-
 4 files changed, 497 insertions(+), 81 deletions(-)
---
diff --git a/gitg/history/gitg-history-refs-list.vala b/gitg/history/gitg-history-refs-list.vala
index 95c2e11..4c953b3 100644
--- a/gitg/history/gitg-history-refs-list.vala
+++ b/gitg/history/gitg-history-refs-list.vala
@@ -55,6 +55,10 @@ private class RefRow : RefTyped, Gtk.Box
 
        public Gitg.Ref? reference { get; set; }
 
+       private Gtk.Entry? d_editing_entry;
+       private uint d_idle_finish;
+       public signal void editing_done(string new_text, bool cancelled);
+
        public Gitg.RefType ref_type
        {
                get { return reference != null ? reference.parsed_name.rtype : Gitg.RefType.NONE; }
@@ -178,6 +182,89 @@ private class RefRow : RefTyped, Gtk.Box
 
                return t1.casefold().collate(t2.casefold());
        }
+
+       public void begin_editing()
+       {
+               if (d_editing_entry != null)
+               {
+                       return;
+               }
+
+               d_editing_entry = new Gtk.Entry();
+               d_editing_entry.set_width_chars(1);
+               d_editing_entry.get_style_context().add_class("ref_editing_entry");
+               d_editing_entry.show();
+
+               d_editing_entry.set_text(label_text());
+
+               d_label.hide();
+               pack_start(d_editing_entry);
+
+               d_editing_entry.grab_focus();
+               d_editing_entry.select_region(0, -1);
+
+               d_editing_entry.focus_out_event.connect(on_editing_focus_out);
+               d_editing_entry.key_press_event.connect(on_editing_key_press);
+       }
+
+       public override void dispose()
+       {
+               if (d_idle_finish != 0)
+               {
+                       Source.remove(d_idle_finish);
+                       d_idle_finish = 0;
+               }
+
+               base.dispose();
+       }
+
+       private void finish_editing(bool cancelled)
+       {
+               if (d_idle_finish != 0)
+               {
+                       return;
+               }
+
+               d_editing_entry.focus_out_event.disconnect(on_editing_focus_out);
+               d_editing_entry.key_press_event.disconnect(on_editing_key_press);
+
+               d_idle_finish = Idle.add(() => {
+                       d_idle_finish = 0;
+
+                       var new_text = d_editing_entry.text;
+
+                       d_editing_entry.destroy();
+                       d_editing_entry = null;
+
+                       d_label.show();
+
+                       editing_done(new_text, cancelled);
+                       return false;
+               });
+       }
+
+       private bool on_editing_focus_out(Gtk.Widget widget, Gdk.EventFocus event)
+       {
+               finish_editing(false);
+               return false;
+       }
+
+       private bool on_editing_key_press(Gtk.Widget widget, Gdk.EventKey event)
+       {
+               if (event.keyval == Gdk.Key.Escape)
+               {
+                       finish_editing(true);
+                       return true;
+               }
+               else if (event.keyval == Gdk.Key.KP_Enter ||
+                        event.keyval == Gdk.Key.Return)
+               {
+                       finish_editing(false);
+                       return true;
+               }
+
+               return false;
+       }
 }
 
 private class RefHeader : RefTyped, Gtk.Label
@@ -213,6 +300,11 @@ private class RefHeader : RefTyped, Gtk.Label
                margin_left += 12;
        }
 
+       public bool is_sub_header_remote
+       {
+               get { return d_is_sub_header_remote; }
+       }
+
        public int compare_to(RefHeader other)
        {
                // Both are headers of remote type
@@ -227,11 +319,24 @@ private class RefHeader : RefTyped, Gtk.Label
 
 public class RefsList : Gtk.ListBox
 {
-       public signal void edited(Gitg.Ref reference, Gtk.TreePath path, string text);
-       public signal void ref_activated(Gitg.Ref? r);
-
        private Gitg.Repository? d_repository;
-       private Gee.HashMap<string, RefHeader> d_remote_headers;
+       private Gee.HashMap<Gitg.Ref, RefRow> d_ref_map;
+
+       public signal void editing_done(Gitg.Ref reference, string new_text, bool cancelled);
+
+       private class RemoteHeader
+       {
+               public RefHeader header;
+               public Gee.HashSet<Gitg.Ref> references;
+
+               public RemoteHeader(RefHeader h)
+               {
+                       header = h;
+                       references = new Gee.HashSet<Gitg.Ref>();
+               }
+       }
+
+       private Gee.HashMap<string, RemoteHeader> d_header_map;
 
        public Gitg.Repository? repository
        {
@@ -249,7 +354,12 @@ public class RefsList : Gtk.ListBox
 
        construct
        {
-               d_remote_headers = new Gee.HashMap<string, RefHeader>();
+               d_header_map = new Gee.HashMap<string, RemoteHeader>();
+               d_ref_map = new Gee.HashMap<Gitg.Ref, RefRow>();
+               selection_mode = Gtk.SelectionMode.BROWSE;
+
+               row_selected.connect(on_row_selected);
+
                set_sort_func(sort_rows);
        }
 
@@ -293,7 +403,8 @@ public class RefsList : Gtk.ListBox
 
        private void clear()
        {
-               d_remote_headers = new Gee.HashMap<string, RefHeader>();
+               d_header_map = new Gee.HashMap<string, RemoteHeader>();
+               d_ref_map = new Gee.HashMap<Gitg.Ref, RefRow>();
 
                foreach (var child in get_children())
                {
@@ -309,22 +420,66 @@ public class RefsList : Gtk.ListBox
                add(header);
        }
 
-       private RefHeader add_remote_header(string name)
+       private void add_remote_header(string name)
        {
                var header = new RefHeader.remote(name);
                header.show();
 
+               d_header_map[name] = new RemoteHeader(header);
                add(header);
-
-               return header;
        }
 
-       private void add_ref(Gitg.Ref? reference)
+       private void add_ref_row(Gitg.Ref? reference)
        {
                var row = new RefRow(reference);
                row.show();
 
                add(row);
+
+               if (reference != null)
+               {
+                       d_ref_map[reference] = row;
+               }
+       }
+
+       public void add_ref(Gitg.Ref reference)
+       {
+               if (d_ref_map.has_key(reference))
+               {
+                       return;
+               }
+
+               if (reference.parsed_name.rtype == Gitg.RefType.REMOTE)
+               {
+                       var remote = reference.parsed_name.remote_name;
+
+                       if (!d_header_map.has_key(remote))
+                       {
+                               add_remote_header(remote);
+                       }
+
+                       d_header_map[remote].references.add(reference);
+               }
+
+               add_ref_row(reference);
+       }
+
+       public void replace_ref(Gitg.Ref old_ref, Gitg.Ref new_ref)
+       {
+               bool select = false;
+
+               if (d_ref_map.has_key(old_ref))
+               {
+                       select = (get_selected_row().get_child() == d_ref_map[old_ref]);
+               }
+
+               remove_ref(old_ref);
+               add_ref(new_ref);
+
+               if (select)
+               {
+                       select_row(d_ref_map[new_ref].get_parent() as Gtk.ListBoxRow);
+               }
        }
 
        // Checks if the provided reference is a symbolic ref with the name HEAD.
@@ -349,6 +504,31 @@ public class RefsList : Gtk.ListBox
                return name == "HEAD";
        }
 
+       public void remove_ref(Gitg.Ref reference)
+       {
+               if (!d_ref_map.has_key(reference))
+               {
+                       return;
+               }
+
+               d_ref_map[reference].get_parent().destroy();
+               d_ref_map.unset(reference);
+
+               if (reference.parsed_name.rtype == Gitg.RefType.REMOTE)
+               {
+                       var remote = reference.parsed_name.remote_name;
+                       var remote_header = d_header_map[remote];
+
+                       remote_header.references.remove(reference);
+
+                       if (remote_header.references.is_empty)
+                       {
+                               remote_header.header.get_parent().destroy();
+                               d_header_map.unset(remote);
+                       }
+               }
+       }
+
        public void refresh()
        {
                clear();
@@ -358,7 +538,7 @@ public class RefsList : Gtk.ListBox
                        return;
                }
 
-               add_ref(null);
+               add_ref_row(null);
 
                add_header(Gitg.RefType.BRANCH, _("Branches"));
                add_header(Gitg.RefType.REMOTE, _("Remotes"));
@@ -381,35 +561,143 @@ public class RefsList : Gtk.ListBox
                                        return 0;
                                }
 
-                               if (r.parsed_name.rtype == Gitg.RefType.REMOTE)
+                               add_ref(r);
+                               return 0;
+                       });
+               } catch {}
+       }
+
+       private RefRow? get_ref_row(Gtk.ListBoxRow? row)
+       {
+               if (row == null)
+               {
+                       return null;
+               }
+
+               return row.get_child() as RefRow;
+       }
+
+       private RefHeader? get_ref_header(Gtk.ListBoxRow row)
+       {
+               return row.get_child() as RefHeader;
+       }
+
+       public Gee.List<Gitg.Ref> all
+       {
+               owned get
+               {
+                       var ret = new Gee.LinkedList<Gitg.Ref>();
+
+                       foreach (var child in get_children())
+                       {
+                               var r = get_ref_row(child as Gtk.ListBoxRow);
+
+                               if (r != null && r.reference != null)
                                {
-                                       var remote = r.parsed_name.remote_name;
+                                       ret.add(r.reference);
+                               }
+                       }
+
+                       try
+                       {
+                               if (d_repository.is_head_detached())
+                               {
+                                       ret.add(d_repository.get_head());
+                               }
+                       } catch {}
+
+                       return ret;
+               }
+       }
+
+       [Notify]
+       public Gee.List<Gitg.Ref> selection
+       {
+               owned get
+               {
+                       var row = get_selected_row();
+
+                       if (row == null)
+                       {
+                               return all;
+                       }
+
+                       var ref_row = get_ref_row(row);
+                       var ret = new Gee.LinkedList<Gitg.Ref>();
+
+                       if (ref_row != null)
+                       {
+                               if (ref_row.reference == null)
+                               {
+                                       return all;
+                               }
+                               else
+                               {
+                                       ret.add(ref_row.reference);
+                               }
+                       }
+                       else
+                       {
+                               var ref_header = get_ref_header(row);
+                               bool found = false;
 
-                                       if (!d_remote_headers.has_key(remote))
+                               foreach (var child in get_children())
+                               {
+                                       if (found)
                                        {
-                                               d_remote_headers[remote] = add_remote_header(remote);
+                                               var nrow = child as Gtk.ListBoxRow;
+                                               var nref_row = get_ref_row(nrow);
+
+                                               if (nref_row == null)
+                                               {
+                                                       var nref_header = get_ref_header(nrow);
+
+                                                       if (ref_header.is_sub_header_remote ||
+                                                               nref_header.ref_type != ref_header.ref_type)
+                                                       {
+                                                               break;
+                                                       }
+                                               }
+                                               else
+                                               {
+                                                       ret.add(nref_row.reference);
+                                               }
+                                       }
+                                       else if (child == row)
+                                       {
+                                               found = true;
                                        }
                                }
+                       }
 
-                               add_ref(r);
-                               return 0;
-                       });
-               } catch {}
+                       return ret;
+               }
        }
 
-       private RefRow? get_ref_row(Gtk.ListBoxRow row)
+       private void on_row_selected(Gtk.ListBoxRow? row)
        {
-               return row.get_child() as RefRow;
+               notify_property("selection");
        }
 
-       protected override void row_activated(Gtk.ListBoxRow row)
+       private void on_row_editing_done(RefRow row, string new_text, bool cancelled)
        {
-               var r = get_ref_row(row);
+               editing_done(row.reference, new_text, cancelled);
+               row.editing_done.disconnect(on_row_editing_done);
+       }
 
-               if (r != null)
+       public bool begin_editing(Gitg.Ref reference)
+       {
+               if (!d_ref_map.has_key(reference))
                {
-                       ref_activated(r.reference);
+                       return false;
                }
+
+               var row = d_ref_map[reference];
+
+               row.editing_done.connect(on_row_editing_done);
+               row.begin_editing();
+
+               return true;
        }
 }
 
diff --git a/gitg/history/gitg-history.vala b/gitg/history/gitg-history.vala
index 3d29258..a2aa319 100644
--- a/gitg/history/gitg-history.vala
+++ b/gitg/history/gitg-history.vala
@@ -196,8 +196,13 @@ namespace GitgHistory
                        d_panels = new Gitg.UIElements<GitgExt.HistoryPanel>(extset,
                                                                             d_main.stack_panel);
 
-                       d_main.refs_list.ref_activated.connect((r) => {
-                               update_walker(r);
+                       d_main.refs_list.popup_menu.connect(on_refs_list_popup_menu);
+                       d_main.refs_list.button_press_event.connect(on_refs_list_button_press_event);
+
+                       d_main.refs_list.editing_done.connect(on_ref_edited);
+
+                       d_main.refs_list.notify["selection"].connect(() => {
+                               update_walker();
                        });
 
                        application.bind_property("repository", d_main.refs_list,
@@ -206,79 +211,183 @@ namespace GitgHistory
                                                  BindingFlags.SYNC_CREATE);
                }
 
-               private void update_walker(Navigation n, Gitg.Ref? head)
+               private void on_ref_edited(Gitg.Ref reference, string new_text, bool cancelled)
                {
-                       Ggit.OId? id = null;
+                       if (cancelled)
+                       {
+                               return;
+                       }
+
+                       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 (head != null)
+                       if (orig == new_text)
                        {
-                               id = head.get_target();
+                               return;
+                       }
 
-                               if (head.parsed_name.rtype == Gitg.RefType.TAG)
-                               {
-                                       // See to resolve to the commit
-                                       try
-                                       {
-                                               var t = application.repository.lookup<Ggit.Tag>(id);
+                       if (!Ggit.Ref.is_valid_name(@"$prefix$new_text"))
+                       {
+                               var msg = _("The specified name ā€˜%sā€™ contains invalid 
characters").printf(new_text);
+
+                               application.show_infobar(_("Invalid name"),
+                                                        msg,
+                                                        Gtk.MessageType.ERROR);
+
+                               return;
+                       }
+
+                       var branch = reference as Ggit.Branch;
+                       Gitg.Ref? new_ref = null;
 
-                                               id = t.get_target_id();
-                                       } catch {}
+                       try
+                       {
+                               if (branch != null)
+                               {
+                                       new_ref = branch.move(new_text, Ggit.CreateFlags.NONE) as Gitg.Ref;
                                }
+                               else
+                               {
+                                       new_ref = reference.rename(new_text, false) as Gitg.Ref;
+                               }
+                       }
+                       catch (Error e)
+                       {
+                               application.show_infobar(_("Failed to rename"),
+                                                        e.message,
+                                                        Gtk.MessageType.ERROR);
+
+                               return;
                        }
 
-                       d_selected.clear();
+                       d_main.refs_list.replace_ref(reference, new_ref);
+               }
 
-                       if (id != null)
+               private void add_ref_action(Gee.LinkedList<GitgExt.RefAction> actions, GitgExt.RefAction? 
action)
+               {
+                       if (action.visible)
                        {
-                               d_selected.add(id);
-                               d_commit_list_model.set_include(new Ggit.OId[] { id });
+                               actions.add(action);
                        }
-                       else
+               }
+
+               private bool refs_list_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 references = d_main.refs_list.selection;
+
+                       if (references.is_empty || references.first() != references.last())
                        {
-                               var included = new Ggit.OId[] {};
-                               var repo = application.repository;
+                               return false;
+                       }
 
-                               try
-                               {
-                                       repo.references_foreach_name((nm) => {
-                                               Gitg.Ref? r;
-
-                                               try
-                                               {
-                                                       r = repo.lookup_reference(nm);
-                                               } catch { return 0; }
-
-                                               try
-                                               {
-                                                       var resolved = r.resolve();
-
-                                                       try
-                                                       {
-                                                               var t = 
repo.lookup<Ggit.Tag>(resolved.get_target());
-                                                               included += t.get_target_id();
-                                                       }
-                                                       catch
-                                                       {
-                                                               included += resolved.get_target();
-                                                       }
-                                               } catch {}
-
-                                               return 0;
-                                       });
-                               } catch {}
+                       var reference = references.first();
+
+                       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.refs_list.begin_editing(action.reference as Gitg.Ref);
+               }
+
+               private bool on_refs_list_popup_menu(Gtk.Widget widget)
+               {
+                       return refs_list_popup_menu(widget, null);
+               }
+
+               private bool on_refs_list_button_press_event(Gtk.Widget widget, Gdk.EventButton event)
+               {
+                       Gdk.Event *ev = (Gdk.Event *)(&event);
+
+                       if (!ev->triggers_context_menu())
+                       {
+                               return false;
+                       }
+
+                       var row = d_main.refs_list.get_row_at_y((int)event.y);
+                       d_main.refs_list.select_row(row);
+
+                       return refs_list_popup_menu(widget, event);
+               }
+
+               private void update_walker()
+               {
+                       d_selected.clear();
+
+                       foreach (var r in d_main.refs_list.selection)
+                       {
                                try
                                {
-                                       if (repo.is_head_detached())
+                                       var resolved = r.resolve();
+
+                                       if (resolved.is_tag())
                                        {
-                                               var resolved = repo.get_head().resolve();
-                                               included += resolved.get_target();
+                                               var t = 
application.repository.lookup<Ggit.Tag>(resolved.get_target());
+                                               d_selected.add(t.get_target_id());
                                        }
-                               } catch {}
-
-                               d_commit_list_model.set_include(included);
+                                       else
+                                       {
+                                               d_selected.add(resolved.get_target());
+                                       }
+                               }
+                               catch {}
                        }
 
+                       d_commit_list_model.set_include(d_selected.to_array());
                        d_commit_list_model.reload();
                }
        }
diff --git a/gitg/resources/ui/style.css b/gitg/resources/ui/style.css
index f8cabee..a74aac6 100644
--- a/gitg/resources/ui/style.css
+++ b/gitg/resources/ui/style.css
@@ -76,6 +76,15 @@ GitgHistoryPaned panel-switcher {
        background-color: @theme_bg_color;
 }
 
+GitgHistoryPaned GtkEntry.ref_editing_entry {
+       padding: 0;
+       border: 0;
+}
+
+GitgHistoryPaned GtkEntry.ref_editing_entry:selected {
+       background-color: lighter(@theme_selected_bg_color);
+}
+
 .progress-bin {
        background-color: shade (@theme_bg_color, 0.9);
 }
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]