[gitg] Implemented multiple selection for commit



commit 8d78cd62679e3328e7d71cda367958e4924f66cb
Author: Jesse van den Kieboom <jessevdk gnome org>
Date:   Fri Jul 11 19:03:48 2014 +0200

    Implemented multiple selection for commit

 gitg/commit/gitg-commit-sidebar.vala |  191 +++++++++++++
 gitg/commit/gitg-commit.vala         |  509 +++++++++++++++++++---------------
 libgitg/gitg-sidebar.vala            |  132 +++++++--
 libgitg/gitg-stage.vala              |   36 +++-
 4 files changed, 614 insertions(+), 254 deletions(-)
---
diff --git a/gitg/commit/gitg-commit-sidebar.vala b/gitg/commit/gitg-commit-sidebar.vala
index d07b13e..517dbda 100644
--- a/gitg/commit/gitg-commit-sidebar.vala
+++ b/gitg/commit/gitg-commit-sidebar.vala
@@ -31,6 +31,72 @@ class Sidebar : Gitg.Sidebar
        [Signal(action = true)]
        public signal void discard_selection();
 
+       public signal void selected_items_changed(Gitg.SidebarItem[] items);
+
+       public class File : Object, Gitg.SidebarItem
+       {
+               public enum Type
+               {
+                       NONE,
+                       STAGED,
+                       UNSTAGED,
+                       UNTRACKED
+               }
+
+               Gitg.StageStatusFile d_file;
+               Type d_type;
+
+               public File(Gitg.StageStatusFile f, Type type)
+               {
+                       d_file = f;
+                       d_type = type;
+               }
+
+               public Gitg.StageStatusFile file
+               {
+                       get { return d_file; }
+               }
+
+               public string text
+               {
+                       owned get { return d_file.path; }
+               }
+
+               public Type stage_type
+               {
+                       get { return d_type; }
+               }
+
+               private string? icon_for_status(Ggit.StatusFlags status)
+               {
+                       if ((status & (Ggit.StatusFlags.INDEX_NEW |
+                                          Ggit.StatusFlags.WORKING_TREE_NEW)) != 0)
+                       {
+                               return "list-add-symbolic";
+                       }
+                       else if ((status & (Ggit.StatusFlags.INDEX_MODIFIED |
+                                               Ggit.StatusFlags.INDEX_RENAMED |
+                                               Ggit.StatusFlags.INDEX_TYPECHANGE |
+                                               Ggit.StatusFlags.WORKING_TREE_MODIFIED |
+                                               Ggit.StatusFlags.WORKING_TREE_TYPECHANGE)) != 0)
+                       {
+                               return "text-editor-symbolic";
+                       }
+                       else if ((status & (Ggit.StatusFlags.INDEX_DELETED |
+                                               Ggit.StatusFlags.WORKING_TREE_DELETED)) != 0)
+                       {
+                               return "edit-delete-symbolic";
+                       }
+
+                       return null;
+               }
+
+               public string? icon_name
+               {
+                       owned get { return icon_for_status(d_file.flags); }
+               }
+       }
+
        construct
        {
                unowned Gtk.BindingSet binding_set = Gtk.BindingSet.by_class(get_class());
@@ -52,6 +118,131 @@ class Sidebar : Gitg.Sidebar
                                            Gdk.ModifierType.CONTROL_MASK,
                                            "discard-selection",
                                            0);
+
+               var sel = get_selection();
+               sel.mode = Gtk.SelectionMode.MULTIPLE;
+       }
+
+       private File.Type get_item_type(Gitg.SidebarItem item)
+       {
+               var header = item as Gitg.SidebarStore.SidebarHeader;
+
+               if (header != null)
+               {
+                       return (File.Type)header.id;
+               }
+
+               var file = item as File;
+
+               if (file != null)
+               {
+                       return file.stage_type;
+               }
+
+               return File.Type.NONE;
+       }
+
+       private File.Type selected_type()
+       {
+               foreach (var item in get_selected_items<Gitg.SidebarItem>())
+               {
+                       var tp = get_item_type(item);
+
+                       if (tp != File.Type.NONE)
+                       {
+                               return tp;
+                       }
+               }
+
+               return File.Type.NONE;
+       }
+
+       protected override bool select_function(Gtk.TreeSelection sel,
+                                               Gtk.TreeModel     model,
+                                               Gtk.TreePath      path,
+                                               bool              cursel)
+       {
+               if (cursel)
+               {
+                       return true;
+               }
+
+               Gtk.TreeIter iter;
+               model.get_iter(out iter, path);
+
+               Gitg.SidebarHint hint;
+
+               var m = model as Gitg.SidebarStore;
+               m.get(iter, Gitg.SidebarColumn.HINT, out hint);
+
+               if (hint == Gitg.SidebarHint.DUMMY)
+               {
+                       return false;
+               }
+
+               var item = m.item_for_iter(iter);
+
+               // Prevent selection of the untracked header
+               var header = item as Gitg.SidebarStore.SidebarHeader;
+
+               if (header != null && (File.Type)header.id == File.Type.UNTRACKED)
+               {
+                       return false;
+               }
+
+               var seltp = selected_type();
+
+               if (seltp == File.Type.NONE)
+               {
+                       return true;
+               }
+
+               var tp = get_item_type(item);
+               return tp == seltp;
+       }
+
+       protected override void selection_changed(Gtk.TreeSelection sel)
+       {
+               if (model.clearing)
+               {
+                       return;
+               }
+
+               var items = get_selected_items<Gitg.SidebarItem>();
+
+               if (items.length == 0)
+               {
+                       deselected();
+               }
+               else
+               {
+                       selected_items_changed(items);
+               }
+       }
+
+       public File[] items_of_type(File.Type type)
+       {
+               var ret = new File[0];
+
+               model.foreach((m, path, iter) => {
+                       var item = model.item_for_iter(iter);
+
+                       if (item == null)
+                       {
+                               return false;
+                       }
+
+                       var file = item as File;
+
+                       if (file != null && file.stage_type == type)
+                       {
+                               ret += file;
+                       }
+
+                       return false;
+               });
+
+               return ret;
        }
 }
 
diff --git a/gitg/commit/gitg-commit.vala b/gitg/commit/gitg-commit.vala
index 9809fb8..324b92e 100644
--- a/gitg/commit/gitg-commit.vala
+++ b/gitg/commit/gitg-commit.vala
@@ -26,74 +26,9 @@ namespace GitgCommit
                private Paned? d_main;
                private bool d_reloading;
                private bool d_has_staged;
-               private Gitg.StageStatusFile? d_current_file;
-               private bool d_current_staged;
 
                public GitgExt.Application? application { owned get; construct set; }
 
-               private class SidebarFile : Object, Gitg.SidebarItem
-               {
-                       public enum Type
-                       {
-                               STAGED,
-                               UNSTAGED,
-                               UNTRACKED
-                       }
-
-                       Gitg.StageStatusFile d_file;
-                       Type d_type;
-
-                       public SidebarFile(Gitg.StageStatusFile f, Type type)
-                       {
-                               d_file = f;
-                               d_type = type;
-                       }
-
-                       public Gitg.StageStatusFile file
-                       {
-                               get { return d_file; }
-                       }
-
-                       public string text
-                       {
-                               owned get { return d_file.path; }
-                       }
-
-                       public Type stage_type
-                       {
-                               get { return d_type; }
-                       }
-
-                       private string? icon_for_status(Ggit.StatusFlags status)
-                       {
-                               if ((status & (Ggit.StatusFlags.INDEX_NEW |
-                                                  Ggit.StatusFlags.WORKING_TREE_NEW)) != 0)
-                               {
-                                       return "list-add-symbolic";
-                               }
-                               else if ((status & (Ggit.StatusFlags.INDEX_MODIFIED |
-                                                       Ggit.StatusFlags.INDEX_RENAMED |
-                                                       Ggit.StatusFlags.INDEX_TYPECHANGE |
-                                                       Ggit.StatusFlags.WORKING_TREE_MODIFIED |
-                                                       Ggit.StatusFlags.WORKING_TREE_TYPECHANGE)) != 0)
-                               {
-                                       return "text-editor-symbolic";
-                               }
-                               else if ((status & (Ggit.StatusFlags.INDEX_DELETED |
-                                                       Ggit.StatusFlags.WORKING_TREE_DELETED)) != 0)
-                               {
-                                       return "edit-delete-symbolic";
-                               }
-
-                               return null;
-                       }
-
-                       public string? icon_name
-                       {
-                               owned get { return icon_for_status(d_file.flags); }
-                       }
-               }
-
                public Activity(GitgExt.Application application)
                {
                        Object(application: application);
@@ -152,19 +87,19 @@ namespace GitgCommit
                        return action == "commit";
                }
 
-               private delegate void StageUnstageCallback(Gitg.StageStatusFile f, int numclick);
+               private delegate void StageUnstageCallback(Sidebar.File f);
 
                private delegate void UpdateDiffCallback();
                private UpdateDiffCallback? d_update_diff_callback;
 
-               private void show_unstaged_diff(Gitg.StageStatusFile f)
+               private void show_unstaged_diff(Gitg.StageStatusFile[] files)
                {
                        var stage = application.repository.stage;
 
-                       stage.diff_workdir.begin(f, d_main.diff_view.options, (obj, res) => {
+                       stage.diff_workdir_all.begin(files, d_main.diff_view.options, (obj, res) => {
                                try
                                {
-                                       var d = stage.diff_workdir.end(res);
+                                       var d = stage.diff_workdir_all.end(res);
 
                                        d_main.diff_view.unstaged = true;
                                        d_main.diff_view.staged = false;
@@ -182,78 +117,64 @@ namespace GitgCommit
                        });
 
                        d_update_diff_callback = () => {
-                               show_unstaged_diff(f);
+                               show_unstaged_diff(files);
                        };
                }
 
-               private void stage_file(Gitg.StageStatusFile f)
+               private async void stage_files(owned Gitg.StageStatusFile[] files)
                {
                        var stage = application.repository.stage;
 
-                       stage.stage_path.begin(f.path, (obj, res) => {
-                               try
-                               {
-                                       stage.stage_path.end(res);
-                               }
-                               catch (Error e)
+                       foreach (var f in files)
+                       {
+                               if ((f.flags & Ggit.StatusFlags.WORKING_TREE_DELETED) != 0)
                                {
-                                       var msg = _("Failed to stage the file `%s'").printf(f.path);
-                                       application.show_infobar(msg, e.message, Gtk.MessageType.ERROR);
-                               }
-
-                               reload();
-                       });
-               }
-
-               private void delete_file(Gitg.StageStatusFile f)
-               {
-                       var stage = application.repository.stage;
+                                       try
+                                       {
+                                               yield stage.delete_path(f.path);
+                                       }
+                                       catch (Error e)
+                                       {
+                                               var msg = _("Failed to stage the removal of file 
`%s'").printf(f.path);
+                                               application.show_infobar(msg, e.message, 
Gtk.MessageType.ERROR);
 
-                       stage.delete_path.begin(f.path, (obj, res) => {
-                               try
-                               {
-                                       stage.delete_path.end(res);
+                                               break;
+                                       }
                                }
-                               catch (Error e)
+                               else
                                {
-                                       var msg = _("Failed to stage the removal of file 
`%s'").printf(f.path);
-                                       application.show_infobar(msg, e.message, Gtk.MessageType.ERROR);
+                                       try
+                                       {
+                                               yield stage.stage_path(f.path);
+                                       }
+                                       catch (Error e)
+                                       {
+                                               var msg = _("Failed to stage the file `%s'").printf(f.path);
+                                               application.show_infobar(msg, e.message, 
Gtk.MessageType.ERROR);
+
+                                               break;
+                                       }
                                }
+                       }
 
-                               reload();
-                       });
+                       reload();
                }
 
-               private void on_unstaged_activated(Gitg.StageStatusFile f, int numclick)
+               private void on_unstaged_activated(Gitg.StageStatusFile[] files)
                {
-                       d_current_file = f;
-                       d_current_staged = false;
-
-                       if (numclick == 1)
-                       {
-                               show_unstaged_diff(f);
-                       }
-                       else
-                       {
-                               if ((f.flags & Ggit.StatusFlags.WORKING_TREE_DELETED) != 0)
-                               {
-                                       delete_file(f);
-                               }
-                               else
-                               {
-                                       stage_file(f);
-                               }
-                       }
+                       stage_files.begin(files, (obj, res) => {
+                               stage_files.end(res);
+                       });
                }
 
-               private void show_staged_diff(Gitg.StageStatusFile f)
+               private void show_staged_diff(Gitg.StageStatusFile[] files)
                {
                        var stage = application.repository.stage;
 
-                       stage.diff_index.begin(f, d_main.diff_view.options, (obj, res) => {
+                       stage.diff_index_all.begin(files, d_main.diff_view.options, (obj, res) => {
                                try
                                {
-                                       var d = stage.diff_index.end(res);
+                                       var d = stage.diff_index_all.end(res);
 
                                        d_main.diff_view.unstaged = false;
                                        d_main.diff_view.staged = true;
@@ -271,95 +192,81 @@ namespace GitgCommit
                        });
 
                        d_update_diff_callback = () => {
-                               show_staged_diff(f);
+                               show_staged_diff(files);
                        };
                }
 
-               private void delete_index_file(Gitg.StageStatusFile f)
+               private async void unstage_files(owned Gitg.StageStatusFile[] files)
                {
                        var stage = application.repository.stage;
 
-                       stage.delete_path.begin(f.path, (obj, res) => {
-                               try
-                               {
-                                       stage.delete_path.end(res);
-                               }
-                               catch (Error e)
+                       foreach (var f in files)
+                       {
+                               if ((f.flags & Ggit.StatusFlags.INDEX_NEW) != 0)
                                {
-                                       var msg = _("Failed to unstage the removal of file 
`%s'").printf(f.path);
-                                       application.show_infobar(msg, e.message, Gtk.MessageType.ERROR);
-                               }
-
-                               reload();
-                       });
-               }
-
-               private void unstage_file(Gitg.StageStatusFile f)
-               {
-                       var stage = application.repository.stage;
+                                       try
+                                       {
+                                               yield stage.delete_path(f.path);
+                                       }
+                                       catch (Error e)
+                                       {
+                                               var msg = _("Failed to unstage the removal of file 
`%s'").printf(f.path);
+                                               application.show_infobar(msg, e.message, 
Gtk.MessageType.ERROR);
 
-                       stage.unstage_path.begin(f.path, (obj, res) => {
-                               try
-                               {
-                                       stage.unstage_path.end(res);
+                                               break;
+                                       }
                                }
-                               catch (Error e)
+                               else
                                {
-                                       var msg = _("Failed to unstage the file `%s'").printf(f.path);
-                                       application.show_infobar(msg, e.message, Gtk.MessageType.ERROR);
+                                       try
+                                       {
+                                               yield stage.unstage_path(f.path);
+                                       }
+                                       catch (Error e)
+                                       {
+                                               var msg = _("Failed to unstage the file `%s'").printf(f.path);
+                                               application.show_infobar(msg, e.message, 
Gtk.MessageType.ERROR);
+
+                                               break;
+                                       }
                                }
+                       }
 
-                               reload();
-                       });
+                       reload();
                }
 
-               private void on_staged_activated(Gitg.StageStatusFile f, int numclick)
+               private void on_staged_activated(Gitg.StageStatusFile[] files)
                {
-                       d_current_file = f;
-                       d_current_staged = true;
-
-                       if (numclick == 1)
-                       {
-                               show_staged_diff(f);
-                       }
-                       else
-                       {
-                               if ((f.flags & Ggit.StatusFlags.INDEX_NEW) != 0)
-                               {
-                                       delete_index_file(f);
-                               }
-                               else
-                               {
-                                       unstage_file(f);
-                               }
-                       }
+                       unstage_files.begin(files, (obj, res) => {
+                               unstage_files.end(res);
+                       });
                }
 
-               private SidebarFile? append_files(Gitg.SidebarStore      model,
-                                                 Gitg.StageStatusFile[] files,
-                                                 SidebarFile.Type       type,
-                                                 Gitg.StageStatusFile?  current,
-                                                 StageUnstageCallback?  callback)
+               private Sidebar.File[] append_files(Gitg.SidebarStore      model,
+                                                   Gitg.StageStatusFile[] files,
+                                                   Sidebar.File.Type      type,
+                                                   Gee.HashSet<string>?   selected_paths,
+                                                   StageUnstageCallback?  callback)
                {
-                       SidebarFile? citem = null;
+                       var ret = new Sidebar.File[0];
 
                        foreach (var f in files)
                        {
-                               var item = new SidebarFile(f, type);
+                               var item = new Sidebar.File(f, type);
 
-                               if (current != null && f.path == current.path)
+                               if (selected_paths != null && selected_paths.contains(f.path))
                                {
-                                       citem = item;
+                                       ret += item;
                                }
 
                                item.activated.connect((numclick) => {
-                                       callback(f, numclick);
+                                       callback(item);
                                });
 
                                model.append(item);
                        }
 
-                       return citem;
+                       return ret;
                }
 
                private void reload()
@@ -373,8 +280,21 @@ namespace GitgCommit
 
                        d_reloading = true;
 
-                       var currentfile = d_current_file;
-                       d_current_file = null;
+                       var sb = d_main.sidebar;
+                       var model = sb.model;
+
+                       Sidebar.File.Type selected_type;
+                       Gitg.StageStatusFile[] selected_files;
+
+                       selected_files = files_for_items(sb.get_selected_items<Gitg.SidebarItem>(),
+                                                        out selected_type);
+
+                       var selected_paths = new Gee.HashSet<string>();
+
+                       foreach (var f in selected_files)
+                       {
+                               selected_paths.add(f.path);
+                       }
 
                        // Preload author avatar
                        try
@@ -387,8 +307,6 @@ namespace GitgCommit
                                });
                        } catch {}
 
-                       var model = d_main.sidebar.model;
-
                        var stage = repository.stage;
 
                        var opts = Ggit.StatusOption.INCLUDE_UNTRACKED |
@@ -447,10 +365,10 @@ namespace GitgCommit
                                model.clear();
                                d_main.diff_view.diff = null;
 
-                               model.begin_header(_("Staged"));
+                               var staged_header = model.begin_header(_("Staged"), 
(uint)Sidebar.File.Type.STAGED);
 
-                               SidebarFile? current_staged = null;
-                               SidebarFile? current_unstaged = null;
+                               var current_staged = new Sidebar.File[0];
+                               var current_unstaged = new Sidebar.File[0];
 
                                if (staged.length == 0)
                                {
@@ -460,14 +378,16 @@ namespace GitgCommit
                                {
                                        current_staged = append_files(model,
                                                                      staged,
-                                                                     SidebarFile.Type.STAGED,
-                                                                     currentfile,
-                                                                     on_staged_activated);
+                                                                     Sidebar.File.Type.STAGED,
+                                                                     selected_paths,
+                                                                     (f) => {
+                                                                               on_staged_activated(new 
Gitg.StageStatusFile[] {f.file});
+                                                                     });
                                }
 
                                model.end_header();
 
-                               model.begin_header(_("Unstaged"));
+                               var unstaged_header = model.begin_header(_("Unstaged"), 
(uint)Sidebar.File.Type.UNSTAGED);
 
                                if (unstaged.length == 0)
                                {
@@ -477,14 +397,16 @@ namespace GitgCommit
                                {
                                        current_unstaged = append_files(model,
                                                                        unstaged,
-                                                                       SidebarFile.Type.UNSTAGED,
-                                                                       currentfile,
-                                                                       on_unstaged_activated);
+                                                                       Sidebar.File.Type.UNSTAGED,
+                                                                       selected_paths,
+                                                                       (f) => {
+                                                                               on_unstaged_activated(new 
Gitg.StageStatusFile[] {f.file});
+                                                                       });
                                }
 
                                model.end_header();
 
-                               model.begin_header(_("Untracked"));
+                               model.begin_header(_("Untracked"), (uint)Sidebar.File.Type.UNTRACKED);
 
                                if (untracked.length == 0)
                                {
@@ -494,9 +416,11 @@ namespace GitgCommit
                                {
                                        append_files(model,
                                                     untracked,
-                                                    SidebarFile.Type.UNTRACKED,
+                                                    Sidebar.File.Type.UNTRACKED,
                                                     null,
-                                                    on_unstaged_activated);
+                                                    (f) => {
+                                                        on_unstaged_activated(new Gitg.StageStatusFile[] 
{f.file});
+                                                    });
                                }
 
                                model.end_header();
@@ -506,24 +430,40 @@ namespace GitgCommit
 
                                d_reloading = false;
 
-                               if (currentfile != null)
+                               if (selected_paths.size != 0)
                                {
-                                       SidebarFile? sel = null;
+                                       Sidebar.File[] sel;
 
-                                       if (d_current_staged)
+                                       if (selected_type == Sidebar.File.Type.STAGED)
                                        {
-                                               sel = (current_staged != null) ? current_staged : 
current_unstaged;
+                                               sel = (current_staged.length != 0) ? current_staged : 
current_unstaged;
                                        }
                                        else
                                        {
-                                               sel = (current_unstaged != null) ? current_unstaged : 
current_staged;
+                                               sel = (current_unstaged.length != 0) ? current_unstaged : 
current_staged;
                                        }
 
-                                       if (sel != null)
+                                       if (sel.length != 0)
+                                       {
+                                               foreach (var item in sel)
+                                               {
+                                                       d_main.sidebar.select(item);
+                                               }
+                                       }
+                                       else if (selected_type == Sidebar.File.Type.STAGED)
+                                       {
+                                               d_main.sidebar.select(staged_header);
+                                       }
+                                       else
                                        {
-                                               d_main.sidebar.select(sel);
+                                               d_main.sidebar.select(unstaged_header);
                                        }
                                }
+                               else
+                               {
+                                       // Select unstaged header
+                                       d_main.sidebar.select(unstaged_header);
+                               }
                        });
                }
 
@@ -879,7 +819,7 @@ namespace GitgCommit
                private void on_discard_clicked()
                {
                        var primary = _("Discard changes");
-                       var secondary = _("Are you sure you want to permanently discard the selected changes 
in the file `%s'?").printf(d_current_file.path);
+                       var secondary = _("Are you sure you want to permanently discard the selected 
changes?").printf();
 
                        var q = new GitgExt.UserQuery();
 
@@ -961,16 +901,31 @@ namespace GitgCommit
                        });
                }
 
-               private bool do_discard_file(GitgExt.UserQuery q, Gitg.StageStatusFile f)
+               private async void revert_paths(string[] paths) throws Error
                {
                        var stage = application.repository.stage;
 
+                       foreach (var path in paths)
+                       {
+                               yield stage.revert_path(path);
+                       }
+               }
+
+               private bool do_discard_files(GitgExt.UserQuery q, Gitg.StageStatusFile[] files)
+               {
                        application.busy = true;
 
-                       stage.revert_path.begin(f.path, (o, ret) => {
+                       var paths = new string[files.length];
+
+                       for (var i = 0; i < files.length; i++)
+                       {
+                               paths[i] = files[i].path;
+                       }
+
+                       revert_paths.begin(paths, (o, ret) => {
                                try
                                {
-                                       stage.revert_path.end(ret);
+                                       revert_paths.end(ret);
                                }
                                catch (Error e)
                                {
@@ -988,10 +943,26 @@ namespace GitgCommit
                        return false;
                }
 
-               private void on_discard_menu_activated(SidebarFile f)
+               private void on_discard_menu_activated(Gitg.StageStatusFile[] files)
                {
                        var primary = _("Discard changes");
-                       var secondary = _("Are you sure you want to permanently discard all changes made to 
the file `%s'?").printf(f.file.path);
+                       string secondary;
+
+                       if (files.length == 1)
+                       {
+                               secondary = _("Are you sure you want to permanently discard all changes made 
to the file `%s'?").printf(files[0].path);
+                       }
+                       else
+                       {
+                               var paths = new string[files.length - 1];
+
+                               for (var i = 0; i < files.length - 1; i++)
+                               {
+                                       paths[i] = @"`$(files[i].path)'";
+                               }
+
+                               secondary = _("Are you sure you want to permanently discard all changes made 
to the files %s and `%s'?").printf(string.joinv(", ", paths), files[files.length - 1].path);
+                       }
 
                        var q = new GitgExt.UserQuery();
 
@@ -1009,7 +980,7 @@ namespace GitgCommit
                        q.response.connect((w, r) => {
                                if (r == Gtk.ResponseType.OK)
                                {
-                                       return do_discard_file(q, f.file);
+                                       return do_discard_files(q, files);
                                }
 
                                return true;
@@ -1020,45 +991,117 @@ namespace GitgCommit
 
                private void do_populate_menu(Gtk.Menu menu)
                {
-                       var f = d_main.sidebar.get_selected_item<SidebarFile>();
+                       var items = d_main.sidebar.get_selected_items<Gitg.SidebarItem>();
 
-                       if (f == null)
+                       if (items.length == 0)
                        {
                                return;
                        }
 
-                       if (f.stage_type == SidebarFile.Type.UNSTAGED ||
-                           f.stage_type == SidebarFile.Type.UNTRACKED)
+                       Sidebar.File.Type type;
+
+                       var files = files_for_items(items, out type);
+
+                       if (type == Sidebar.File.Type.UNSTAGED ||
+                           type == Sidebar.File.Type.UNTRACKED)
                        {
                                var stage = new Gtk.MenuItem.with_mnemonic(_("_Stage changes"));
                                menu.append(stage);
 
                                stage.activate.connect(() => {
-                                       on_unstaged_activated(f.file, 2);
+                                       on_unstaged_activated(files);
                                });
                        }
 
-                       if (f.stage_type == SidebarFile.Type.STAGED)
+                       if (type == Sidebar.File.Type.STAGED)
                        {
                                var stage = new Gtk.MenuItem.with_mnemonic(_("_Unstage changes"));
                                menu.append(stage);
 
                                stage.activate.connect(() => {
-                                       on_staged_activated(f.file, 2);
+                                       on_staged_activated(files);
                                });
                        }
 
-                       if (f.stage_type == SidebarFile.Type.UNSTAGED)
+                       if (type == Sidebar.File.Type.UNSTAGED)
                        {
                                var discard = new Gtk.MenuItem.with_mnemonic(_("_Discard changes"));
                                menu.append(discard);
 
                                discard.activate.connect(() => {
-                                       on_discard_menu_activated(f);
+                                       on_discard_menu_activated(files);
                                });
                        }
                }
 
+               private Gitg.StageStatusFile[] files_to_stage_files(Sidebar.File[] files)
+               {
+                       var ret = new Gitg.StageStatusFile[files.length];
+
+                       for (var i = 0; i < ret.length; i++)
+                       {
+                               ret[i] = files[i].file;
+                       }
+
+                       return ret;
+               }
+
+               private Gitg.StageStatusFile[] stage_status_files_of_type(Sidebar.File.Type type)
+               {
+                       return files_to_stage_files(d_main.sidebar.items_of_type(type));
+               }
+
+               private Gitg.StageStatusFile[] files_for_items(Gitg.SidebarItem[] items, out 
Sidebar.File.Type type)
+               {
+                       var files = new Gitg.StageStatusFile[items.length];
+                       files.length = 0;
+
+                       type = Sidebar.File.Type.NONE;
+
+                       foreach (var item in items)
+                       {
+                               var header = item as Gitg.SidebarStore.SidebarHeader;
+
+                               if (header != null)
+                               {
+                                       type = (Sidebar.File.Type)header.id;
+                                       return stage_status_files_of_type(type);
+                               }
+
+                               var file = item as Sidebar.File;
+
+                               if (file != null)
+                               {
+                                       files += file.file;
+                                       type = file.stage_type;
+                               }
+                       }
+
+                       return files;
+               }
+
+               private void sidebar_selection_changed(Gitg.SidebarItem[] items)
+               {
+                       Sidebar.File.Type type;
+
+                       var files = files_for_items(items, out type);
+
+                       if (files.length == 0)
+                       {
+                               d_main.diff_view.diff = null;
+                               return;
+                       }
+
+                       if (type == Sidebar.File.Type.STAGED)
+                       {
+                               show_staged_diff(files);
+                       }
+                       else
+                       {
+                               show_unstaged_diff(files);
+                       }
+               }
+
                private void build_ui()
                {
                        d_main = new Paned();
@@ -1075,28 +1118,44 @@ namespace GitgCommit
                        });
 
                        d_main.sidebar.stage_selection.connect(() => {
-                               var sel = d_main.sidebar.get_selected_item<SidebarFile>();
+                               var sel = d_main.sidebar.get_selected_items<Gitg.SidebarItem>();
+                               Sidebar.File.Type type;
+
+                               var files = files_for_items(sel, out type);
 
-                               if (sel != null && (sel.stage_type == SidebarFile.Type.UNSTAGED ||
-                                                   sel.stage_type == SidebarFile.Type.UNTRACKED))
+                               if (files.length != 0 && (type == Sidebar.File.Type.UNSTAGED ||
+                                                         type == Sidebar.File.Type.UNTRACKED))
                                {
-                                       on_unstaged_activated(sel.file, 2);
+                                       on_unstaged_activated(files);
                                }
                        });
 
                        d_main.sidebar.unstage_selection.connect(() => {
-                               var sel = d_main.sidebar.get_selected_item<SidebarFile>();
+                               var sel = d_main.sidebar.get_selected_items<Gitg.SidebarItem>();
+                               Sidebar.File.Type type;
 
-                               if (sel != null && sel.stage_type == SidebarFile.Type.STAGED)
+                               var files = files_for_items(sel, out type);
+
+                               if (files.length != 0 && type == Sidebar.File.Type.STAGED)
                                {
-                                       on_staged_activated(sel.file, 2);
+                                       on_staged_activated(files);
                                }
                        });
 
                        d_main.sidebar.discard_selection.connect(() => {
-                               
+                               var sel = d_main.sidebar.get_selected_items<Gitg.SidebarItem>();
+                               Sidebar.File.Type type;
+
+                               var files = files_for_items(sel, out type);
+
+                               if (files.length != 0 && type == Sidebar.File.Type.UNSTAGED)
+                               {
+                                       on_discard_menu_activated(files);
+                               }
                        });
 
+                       d_main.sidebar.selected_items_changed.connect(sidebar_selection_changed);
+
                        d_main.button_commit.clicked.connect(() => {
                                on_commit_clicked();
                        });
diff --git a/libgitg/gitg-sidebar.vala b/libgitg/gitg-sidebar.vala
index 5bee494..11df833 100644
--- a/libgitg/gitg-sidebar.vala
+++ b/libgitg/gitg-sidebar.vala
@@ -54,7 +54,7 @@ public class SidebarStore : Gtk.TreeStore
        private SList<Gtk.TreeIter?> d_parents;
        private bool d_clearing;
 
-       private class SidebarText : Object, SidebarItem
+       protected class SidebarText : Object, SidebarItem
        {
                private string d_text;
 
@@ -74,6 +74,23 @@ public class SidebarStore : Gtk.TreeStore
                }
        }
 
+       public class SidebarHeader : SidebarText
+       {
+               private uint d_id;
+
+               public uint id
+               {
+                       get { return d_id; }
+               }
+
+               public SidebarHeader(string text, uint id)
+               {
+                       base(text);
+
+                       d_id = id;
+               }
+       }
+
        private void append_real(SidebarItem      item,
                                 uint             hint,
                                 out Gtk.TreeIter iter)
@@ -109,14 +126,16 @@ public class SidebarStore : Gtk.TreeStore
                return this;
        }
 
-       public SidebarStore begin_header(string text)
+       public SidebarHeader begin_header(string text, uint id = 0)
        {
                Gtk.TreeIter iter;
 
-               append_real(new SidebarText(text), SidebarHint.HEADER, out iter);
+               var item = new SidebarHeader(text, id);
+
+               append_real(item, SidebarHint.HEADER, out iter);
                d_parents.prepend(iter);
 
-               return this;
+               return item;
        }
 
        public SidebarStore end_header()
@@ -250,26 +269,41 @@ public class Sidebar : Gtk.TreeView
 
                var sel = get_selection();
 
-               sel.set_select_function((sel, model, path, cursel) => {
-                       Gtk.TreeIter iter;
-                       model.get_iter(out iter, path);
+               sel.set_select_function(select_function);
 
-                       uint hint;
+               sel.changed.connect(selection_changed);
+       }
 
-                       model.get(iter, SidebarColumn.HINT, out hint);
+       protected virtual bool select_function(Gtk.TreeSelection sel,
+                                              Gtk.TreeModel     model,
+                                              Gtk.TreePath      path,
+                                              bool              cursel)
+       {
+               Gtk.TreeIter iter;
+               model.get_iter(out iter, path);
 
-                       return hint != SidebarHint.HEADER && hint != SidebarHint.DUMMY;
-               });
+               uint hint;
 
-               sel.changed.connect((sel) => {
-                       Gtk.TreeIter iter;
+               model.get(iter, SidebarColumn.HINT, out hint);
 
-                       if (model.clearing)
-                       {
-                               return;
-                       }
+               return hint != SidebarHint.HEADER && hint != SidebarHint.DUMMY;
+       }
+
+       protected virtual void selection_changed(Gtk.TreeSelection sel)
+       {
+               Gtk.TreeIter iter;
+
+               if (model.clearing)
+               {
+                       return;
+               }
+
+               if (get_selected_iter(out iter))
+               {
+                       SidebarHint hint;
+                       model.get(iter, SidebarColumn.HINT, out hint);
 
-                       if (sel.get_selected(null, out iter))
+                       if (hint != SidebarHint.HEADER && hint != SidebarHint.DUMMY)
                        {
                                model.activate(iter, 1);
                        }
@@ -277,15 +311,39 @@ public class Sidebar : Gtk.TreeView
                        {
                                deselected();
                        }
-               });
+               }
+               else
+               {
+                       deselected();
+               }
        }
 
-       public T? get_selected_item<T>()
+       protected bool get_selected_iter(out Gtk.TreeIter iter)
        {
                var sel = get_selection();
+
+               if (sel.count_selected_rows() == 1)
+               {
+                       Gtk.TreeModel m;
+
+                       var rows = sel.get_selected_rows(out m);
+                       m.get_iter(out iter, rows.data);
+
+                       return true;
+               }
+               else
+               {
+                       iter = Gtk.TreeIter();
+               }
+
+               return false;
+       }
+
+       public T? get_selected_item<T>()
+       {
                Gtk.TreeIter iter;
 
-               if (sel.get_selected(null, out iter))
+               if (get_selected_iter(out iter))
                {
                        return (T)model.item_for_iter(iter);
                }
@@ -293,6 +351,25 @@ public class Sidebar : Gtk.TreeView
                return null;
        }
 
+       public T[] get_selected_items<T>()
+       {
+               var sel = get_selection();
+
+               Gtk.TreeModel m;
+               Gtk.TreeIter iter;
+
+               var rows = sel.get_selected_rows(out m);
+               var ret = new T[0];
+
+               foreach (var row in rows)
+               {
+                       m.get_iter(out iter, row);
+                       ret += (T)model.item_for_iter(iter);
+               }
+
+               return ret;
+       }
+
        public void select(SidebarItem item)
        {
                model.foreach((m, path, iter) => {
@@ -356,16 +433,21 @@ public class Sidebar : Gtk.TreeView
 
        protected override bool button_press_event(Gdk.EventButton event)
        {
-               var ret = base.button_press_event(event);
-
                Gdk.Event *ev = (Gdk.Event *)event;
 
                if (ev->triggers_context_menu())
                {
+                       if (get_selection().count_selected_rows() <= 1)
+                       {
+                               base.button_press_event(event);
+                       }
+
                        return do_populate_popup(event);
                }
-
-               return ret;
+               else
+               {
+                       return base.button_press_event(event);
+               }
        }
 
        protected override bool popup_menu()
diff --git a/libgitg/gitg-stage.vala b/libgitg/gitg-stage.vala
index 75b84e8..fa4128d 100644
--- a/libgitg/gitg-stage.vala
+++ b/libgitg/gitg-stage.vala
@@ -904,7 +904,8 @@ public class Stage : Object
                });
        }
 
-       public async Ggit.Diff? diff_index(StageStatusFile f, Ggit.DiffOptions? defopts = null) throws Error
+       public async Ggit.Diff? diff_index_all(StageStatusFile[] files,
+                                              Ggit.DiffOptions? defopts = null) throws Error
        {
                var opts = new Ggit.DiffOptions();
 
@@ -912,7 +913,14 @@ public class Stage : Object
                             Ggit.DiffOption.DISABLE_PATHSPEC_MATCH |
                             Ggit.DiffOption.RECURSE_UNTRACKED_DIRS;
 
-               opts.pathspec = new string[] {f.path};
+               var pspec = new string[files.length];
+
+               for (var i = 0; i < files.length; i++)
+               {
+                       pspec[i] = files[i].path;
+               }
+
+               opts.pathspec = pspec;
 
                if (defopts != null)
                {
@@ -933,7 +941,14 @@ public class Stage : Object
                                                   opts);
        }
 
-       public async Ggit.Diff? diff_workdir(StageStatusFile f, Ggit.DiffOptions? defopts = null) throws Error
+       public async Ggit.Diff? diff_index(StageStatusFile   f,
+                                          Ggit.DiffOptions? defopts = null) throws Error
+       {
+               return yield diff_index_all(new StageStatusFile[] {f}, defopts);
+       }
+
+       public async Ggit.Diff? diff_workdir_all(StageStatusFile[] files,
+                                                Ggit.DiffOptions? defopts = null) throws Error
        {
                var opts = new Ggit.DiffOptions();
 
@@ -941,7 +956,14 @@ public class Stage : Object
                             Ggit.DiffOption.DISABLE_PATHSPEC_MATCH |
                             Ggit.DiffOption.RECURSE_UNTRACKED_DIRS;
 
-               opts.pathspec = new string[] {f.path};
+               var pspec = new string[files.length];
+
+               for (var i = 0; i < files.length; i++)
+               {
+                       pspec[i] = files[i].path;
+               }
+
+               opts.pathspec = pspec;
 
                if (defopts != null)
                {
@@ -958,6 +980,12 @@ public class Stage : Object
                                                      d_repository.get_index(),
                                                      opts);
        }
+
+       public async Ggit.Diff? diff_workdir(StageStatusFile   f,
+                                            Ggit.DiffOptions? defopts = null) throws Error
+       {
+               return yield diff_workdir_all(new StageStatusFile[] {f}, defopts);
+       }
 }
 
 }


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