/* * 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 . */ 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 ", 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: " at " 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(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