Implemented multiple selection for commit

This commit is contained in:
Jesse van den Kieboom 2014-07-11 19:03:48 +02:00
parent d57424b0ac
commit 8d78cd6267
4 changed files with 631 additions and 271 deletions

View file

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

View file

@ -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)
{
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;
stage.delete_path.begin(f.path, (obj, res) => {
try
{
stage.delete_path.end(res);
}
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);
}
reload();
});
}
private void on_unstaged_activated(Gitg.StageStatusFile f, int numclick)
{
d_current_file = f;
d_current_staged = false;
if (numclick == 1)
{
show_unstaged_diff(f);
}
else
foreach (var f in files)
{
if ((f.flags & Ggit.StatusFlags.WORKING_TREE_DELETED) != 0)
{
delete_file(f);
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);
break;
}
}
else
{
stage_file(f);
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();
}
private void show_staged_diff(Gitg.StageStatusFile f)
private void on_unstaged_activated(Gitg.StageStatusFile[] files)
{
stage_files.begin(files, (obj, res) => {
stage_files.end(res);
});
}
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)
{
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;
stage.unstage_path.begin(f.path, (obj, res) => {
try
{
stage.unstage_path.end(res);
}
catch (Error e)
{
var msg = _("Failed to unstage the file `%s'").printf(f.path);
application.show_infobar(msg, e.message, Gtk.MessageType.ERROR);
}
reload();
});
}
private void on_staged_activated(Gitg.StageStatusFile f, int numclick)
{
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);
}
}
}
private SidebarFile? append_files(Gitg.SidebarStore model,
Gitg.StageStatusFile[] files,
SidebarFile.Type type,
Gitg.StageStatusFile? current,
StageUnstageCallback? callback)
{
SidebarFile? citem = null;
foreach (var f in files)
{
var item = new SidebarFile(f, type);
if (current != null && f.path == current.path)
if ((f.flags & Ggit.StatusFlags.INDEX_NEW) != 0)
{
citem = item;
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);
break;
}
}
else
{
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();
}
private void on_staged_activated(Gitg.StageStatusFile[] files)
{
unstage_files.begin(files, (obj, res) => {
unstage_files.end(res);
});
}
private Sidebar.File[] append_files(Gitg.SidebarStore model,
Gitg.StageStatusFile[] files,
Sidebar.File.Type type,
Gee.HashSet<string>? selected_paths,
StageUnstageCallback? callback)
{
var ret = new Sidebar.File[0];
foreach (var f in files)
{
var item = new Sidebar.File(f, type);
if (selected_paths != null && selected_paths.contains(f.path))
{
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,23 +430,39 @@ 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)
{
d_main.sidebar.select(sel);
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(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;
if (sel != null && (sel.stage_type == SidebarFile.Type.UNSTAGED ||
sel.stage_type == SidebarFile.Type.UNTRACKED))
var files = files_for_items(sel, out type);
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();
});

View file

@ -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);
}
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);
uint hint;
model.get(iter, SidebarColumn.HINT, out hint);
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);
return hint != SidebarHint.HEADER && hint != SidebarHint.DUMMY;
});
sel.changed.connect((sel) => {
Gtk.TreeIter iter;
if (model.clearing)
{
return;
}
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();
}
}
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>()
{
var sel = get_selection();
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()

View file

@ -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);
}
}
}