mirror of
https://gitlab.gnome.org/GNOME/gitg
synced 2024-10-04 06:59:25 +00:00
Implemented multiple selection for commit
This commit is contained in:
parent
d57424b0ac
commit
8d78cd6267
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue