gitg/libgitg/gitg-repository-list-box.vala

658 lines
14 KiB
Vala

/*
* This file is part of gitg
*
* Copyright (C) 2012-2016 - Ignacio Casal Quinteiro
*
* gitg is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* gitg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with gitg. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Gitg
{
public enum SelectionMode
{
NORMAL,
SELECTION
}
public class RepositoryListBox : Gtk.ListBox
{
private string? d_filter_text;
public signal void repository_activated(Repository repository);
public signal void show_error(string primary_message, string secondary_message);
[GtkTemplate (ui = "/org/gnome/gitg/ui/gitg-repository-list-box-row.ui")]
public class Row : Gtk.ListBoxRow
{
private Repository? d_repository;
private DateTime d_time = new DateTime.now_local();
private bool d_loading;
[GtkChild]
private unowned ProgressBin d_progress_bin;
[GtkChild]
private unowned Gtk.Label d_repository_label;
[GtkChild]
private unowned Gtk.Label d_description_label;
[GtkChild]
private unowned Gtk.Label d_branch_label;
[GtkChild]
private unowned Gtk.Spinner d_spinner;
[GtkChild]
private unowned Gtk.CheckButton d_remove_check_button;
[GtkChild]
private unowned Gtk.Revealer d_remove_revealer;
[GtkChild]
private unowned Gtk.Box d_languages_box;
public signal void request_remove();
private SelectionMode d_mode;
private string? d_dirname;
private string? d_branch_name;
public SelectionMode mode
{
get { return d_mode; }
set
{
if (d_mode != value)
{
d_mode = value;
d_remove_revealer.reveal_child = (d_mode == SelectionMode.SELECTION);
d_remove_check_button.active = false;
}
}
}
public new bool selected
{
get; set;
}
construct
{
d_remove_check_button.bind_property("active",
this,
"selected",
BindingFlags.BIDIRECTIONAL |
BindingFlags.SYNC_CREATE);
}
public Repository? repository
{
get { return d_repository; }
set
{
d_repository = value;
update_repository_data();
}
}
public bool can_remove
{
get { return d_remove_check_button.sensitive; }
set { d_remove_check_button.sensitive = value; }
}
public DateTime time
{
get { return d_time; }
set { d_time = value; }
}
public double fraction
{
set { d_progress_bin.fraction = value; }
}
public string? repository_name
{
get { return d_repository_label.get_text(); }
set { d_repository_label.label = value; }
}
public string? dirname
{
get { return d_dirname; }
set
{
d_dirname = value;
update_branch_label();
}
}
public string? branch_name
{
get { return d_branch_name; }
set
{
d_branch_name = value;
update_branch_label();
}
}
private void update_branch_label()
{
if (d_branch_name == null || d_branch_name == "")
{
// Translators: this is used to construct: "at <directory>", to indicate where the repository is at.
d_branch_label.label = _("at %s").printf(d_dirname);
}
else if (d_dirname == null || d_dirname == "")
{
d_branch_label.label = d_branch_name;
}
else
{
// Translators: this is used to construct: "<branch-name> at <directory>"
d_branch_label.label = _("%s at %s").printf(d_branch_name, d_dirname);
}
}
private void update_repository_data()
{
string head_name = "";
string head_description = "";
if (d_repository != null)
{
try
{
var head = d_repository.get_head();
head_name = head.parsed_name.shortname;
var commit = (Ggit.Commit)head.lookup();
var tree = commit.get_tree();
Ggit.OId? entry_id = null;
for (var i = 0; i < tree.size(); i++)
{
var entry = tree.get(i);
var name = entry.get_name();
if (name != null && name.has_suffix(".doap"))
{
entry_id = entry.get_id();
break;
}
}
if (entry_id != null)
{
var blob = d_repository.lookup<Ggit.Blob>(entry_id);
unowned uint8[] content = blob.get_raw_content();
var doap = new Ide.Doap();
doap.load_from_data((string)content, -1);
head_description = doap.get_shortdesc();
foreach (var lang in doap.get_languages())
{
var frame = new Gtk.Frame(null);
frame.shadow_type = Gtk.ShadowType.NONE;
frame.get_style_context().add_class("language-frame");
frame.show();
var label = new Gtk.Label(lang);
var attr_list = new Pango.AttrList();
attr_list.insert(Pango.attr_scale_new(Pango.Scale.SMALL));
label.set_attributes(attr_list);
label.show();
frame.add(label);
d_languages_box.add(frame);
}
}
} catch {}
}
repository_name = d_repository != null ? d_repository.name : "";
d_description_label.label = head_description;
d_description_label.visible = head_description != "";
branch_name = head_name;
}
public bool loading
{
get { return d_loading; }
set
{
d_loading = value;
if (!d_loading)
{
d_spinner.stop();
d_spinner.hide();
d_progress_bin.fraction = 0;
}
else
{
d_spinner.show();
d_spinner.start();
}
}
}
public Row(Repository? repository, string dirname)
{
Object(repository: repository, dirname: dirname);
}
}
public SelectionMode mode { get; set; }
public bool bookmarks_from_recent_files { get; set; default = true; }
private File? d_location;
private uint d_save_repository_bookmarks_id;
private BookmarkFile d_bookmark_file;
public File? location
{
get
{
return d_location;
}
set
{
if (d_save_repository_bookmarks_id != 0)
{
Source.remove(d_save_repository_bookmarks_id);
save_repository_bookmarks();
}
d_location = value;
d_bookmark_file = new BookmarkFile();
try
{
d_bookmark_file.load_from_file(value.get_path());
}
catch (FileError e)
{
if (bookmarks_from_recent_files)
{
// First time create, copy over from recent file manager
copy_bookmarks_from_recent_files();
}
}
catch (Error e)
{
stderr.printf(@"Failed to read repository bookmarks: $(e.message)\n");
}
}
}
private void copy_bookmarks_from_recent_files()
{
var manager = Gtk.RecentManager.get_default();
var items = manager.get_items();
foreach (var item in items)
{
if (!item.has_group("gitg"))
{
continue;
}
var uri = item.get_uri();
d_bookmark_file.set_mime_type(uri, item.get_mime_type());
d_bookmark_file.set_groups(uri, item.get_groups());
d_bookmark_file.set_visited(uri, (time_t)item.get_modified());
var app_name = Environment.get_application_name();
var app_exec = string.join(" ", Environment.get_prgname(), "%f");
try { d_bookmark_file.set_app_info(uri, app_name, app_exec, 1, -1); } catch {}
}
save_repository_bookmarks_timeout();
}
protected override bool button_press_event(Gdk.EventButton event)
{
Gdk.Event *ev = (Gdk.Event *)event;
if (ev->triggers_context_menu() && mode == SelectionMode.NORMAL)
{
mode = SelectionMode.SELECTION;
var row = get_row_at_y((int)event.y) as Row;
if (row != null)
{
row.selected = true;
}
return true;
}
return base.button_press_event(event);
}
protected override void row_activated(Gtk.ListBoxRow row)
{
var r = (Row)row;
if (mode == SelectionMode.SELECTION)
{
r.selected = !r.selected;
return;
}
if (r.repository != null)
{
repository_activated(r.repository);
}
}
construct
{
set_header_func(update_header);
set_filter_func(filter);
set_sort_func(compare_widgets);
show();
set_selection_mode(Gtk.SelectionMode.NONE);
d_bookmark_file = new BookmarkFile();
}
~RepositoryListBox()
{
if (d_save_repository_bookmarks_id != 0)
{
Source.remove(d_save_repository_bookmarks_id);
save_repository_bookmarks();
}
}
private void update_header(Gtk.ListBoxRow row, Gtk.ListBoxRow? before)
{
row.set_header(before != null ? new Gtk.Separator(Gtk.Orientation.HORIZONTAL) : null);
}
private string normalize(string s)
{
return s.normalize(-1, NormalizeMode.ALL).casefold();
}
private bool filter(Gtk.ListBoxRow row)
{
return d_filter_text != null ? normalize(((Row)row).repository_name).contains(normalize(d_filter_text)) : true;
}
private int compare_widgets(Gtk.ListBoxRow a, Gtk.ListBoxRow b)
{
return ((Row)b).time.compare(((Row)a).time);
}
public void populate_bookmarks()
{
var uris = d_bookmark_file.get_uris();
foreach (var uri in uris)
{
try {
if (!d_bookmark_file.has_group(uri, "gitg"))
{
continue;
}
} catch { continue; }
File repo_file = File.new_for_uri(uri);
Repository repo;
try
{
repo = new Repository(repo_file, null);
}
catch
{
try
{
d_bookmark_file.remove_item(uri);
} catch {}
continue;
}
DateTime? visited = null;
try
{
visited = new DateTime.from_unix_utc(d_bookmark_file.get_visited(uri));
} catch {};
add_repository(repo, visited);
}
}
private Row get_row_for_repository(Repository repository)
{
Row? row = null;
foreach (var child in get_children())
{
var d = (Row)child;
if (d.repository.get_location().equal(repository.get_location()))
{
row = d;
break;
}
}
return row;
}
private bool save_repository_bookmarks()
{
d_save_repository_bookmarks_id = 0;
if (location == null)
{
return false;
}
try
{
var dir = location.get_parent();
dir.make_directory_with_parents(null);
} catch {}
try
{
d_bookmark_file.to_file(location.get_path());
}
catch (Error e)
{
stderr.printf(@"Failed to save repository bookmarks: $(e.message)\n");
}
return false;
}
private void add_repository_to_bookmarks(string uri, DateTime? visited = null)
{
d_bookmark_file.set_mime_type(uri, "inode/directory");
d_bookmark_file.set_groups(uri, new string[] { "gitg" });
d_bookmark_file.set_visited(uri, visited == null ? -1 : (time_t)visited.to_unix());
var app_name = Environment.get_application_name();
var app_exec = string.join(" ", Environment.get_prgname(), "%f");
try { d_bookmark_file.set_app_info(uri, app_name, app_exec, 1, -1); } catch {}
save_repository_bookmarks_timeout();
}
private void save_repository_bookmarks_timeout()
{
if (d_save_repository_bookmarks_id != 0)
{
return;
}
d_save_repository_bookmarks_id = Timeout.add(300, save_repository_bookmarks);
}
public void end_cloning(Row row, Repository? repository)
{
if (repository != null)
{
File? workdir = repository.get_workdir();
File? repo_file = repository.get_location();
var uri = (workdir != null) ? workdir.get_uri() : repo_file.get_uri();
add_repository_to_bookmarks(uri);
row.repository = repository;
row.loading = false;
connect_repository_row(row);
}
else
{
remove(row);
}
}
public Row? begin_cloning(File location)
{
var row = new Row(null, Utils.replace_home_dir_with_tilde(location.get_parent()));
row.repository_name = location.get_basename();
row.branch_name = _("Cloning…");
row.loading = true;
row.show();
add(row);
return row;
}
private void connect_repository_row(Row row)
{
var repository = row.repository;
var workdir = repository.workdir != null ? repository.workdir : repository.location;
if (workdir != null)
{
bind_property("mode", row, "mode");
row.notify["selected"].connect(() => {
notify_property("has-selection");
});
row.request_remove.connect(() => {
try
{
d_bookmark_file.remove_item(workdir.get_uri());
} catch {}
remove(row);
});
row.can_remove = true;
}
else
{
row.can_remove = false;
}
}
public Row? add_repository(Repository repository, DateTime? visited = null)
{
Row? row = get_row_for_repository(repository);
var f = repository.workdir != null ? repository.workdir : repository.location;
if (row == null)
{
var dirname = Utils.replace_home_dir_with_tilde((repository.workdir != null ? repository.workdir : repository.location).get_parent());
row = new Row(repository, dirname);
row.show();
connect_repository_row(row);
add(row);
}
row.time = visited != null ? visited : new DateTime.now_local();
invalidate_sort();
if (f != null)
{
add_repository_to_bookmarks(f.get_uri(), visited);
}
return row;
}
public Row[] get_selection()
{
var ret = new Row[0];
foreach (var row in get_children())
{
var r = (Row)row;
if (r.selected)
{
ret += r;
}
}
return ret;
}
public bool has_selection
{
get
{
foreach (var row in get_children())
{
var r = (Row)row;
if (r.selected)
{
return true;
}
}
return false;
}
}
public void filter_text(string? text)
{
d_filter_text = text;
invalidate_filter();
}
}
}
// ex:ts=4 noet