gitg/libgitg/gitg-sidebar.vala
2015-12-21 09:30:46 +01:00

526 lines
9.4 KiB
Vala

/*
* This file is part of gitg
*
* Copyright (C) 2013 - Jesse van den Kieboom
*
* 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 SidebarHint
{
NONE,
HEADER,
SEPARATOR,
DUMMY
}
public enum SidebarColumn
{
HINT,
SECTION,
ITEM
}
public interface SidebarItem : Object
{
public abstract string text { owned get; }
public abstract string? icon_name { owned get; }
public signal void activated(int numclick);
public virtual void activate(int numclick)
{
activated(numclick);
}
}
public class SidebarStore : Gtk.TreeStore
{
private uint d_sections;
private SList<Gtk.TreeIter?> d_parents;
private bool d_clearing;
protected class SidebarText : Object, SidebarItem
{
private string d_text;
public SidebarText(string text)
{
d_text = text;
}
public string text
{
owned get { return d_text; }
}
public string? icon_name
{
owned get { return null; }
}
}
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)
{
if (d_parents != null)
{
base.append(out iter, d_parents.data);
}
else
{
base.append(out iter, null);
}
@set(iter,
SidebarColumn.ITEM, item,
SidebarColumn.HINT, hint,
SidebarColumn.SECTION, d_sections);
}
public SidebarStore append_dummy(string text)
{
Gtk.TreeIter iter;
append_real(new SidebarText(text), SidebarHint.DUMMY, out iter);
return this;
}
public new SidebarStore append(SidebarItem item)
{
Gtk.TreeIter iter;
append_real(item, SidebarHint.NONE, out iter);
return this;
}
public SidebarHeader begin_header(string text, uint id = 0)
{
Gtk.TreeIter iter;
var item = new SidebarHeader(text, id);
append_real(item, SidebarHint.HEADER, out iter);
d_parents.prepend(iter);
return item;
}
public SidebarStore end_header()
{
if (d_parents != null)
{
d_parents.delete_link(d_parents);
}
return this;
}
public uint begin_section()
{
d_parents = null;
return d_sections;
}
public void end_section()
{
++d_sections;
}
public bool clearing
{
get { return d_clearing; }
}
public new void clear()
{
d_clearing = true;
base.clear();
d_clearing = false;
d_sections = 0;
}
public SidebarItem item_for_iter(Gtk.TreeIter iter)
{
SidebarItem item;
@get(iter, SidebarColumn.ITEM, out item);
return item;
}
public void activate(Gtk.TreeIter iter, int numclick)
{
SidebarItem? item;
@get(iter, SidebarColumn.ITEM, out item);
if (item != null)
{
item.activate(numclick);
}
}
}
[GtkTemplate ( ui = "/org/gnome/gitg/ui/gitg-sidebar.ui" )]
public class Sidebar : Gtk.TreeView
{
[GtkChild (name = "column")]
private Gtk.TreeViewColumn d_column;
[GtkChild (name = "renderer_icon")]
private Gtk.CellRendererPixbuf d_renderer_icon;
[GtkChild (name = "renderer_header")]
private Gtk.CellRendererText d_renderer_header;
[GtkChild (name = "renderer_text")]
private Gtk.CellRendererText d_renderer_text;
public signal void deselected();
public signal void populate_popup(Gtk.Menu menu);
construct
{
d_column.set_cell_data_func(d_renderer_icon, (layout, cell, model, iter) => {
SidebarItem item;
model.get(iter, SidebarColumn.ITEM, out item);
cell.visible = (item.icon_name != null);
var r = (Gtk.CellRendererPixbuf)cell;
r.icon_name = item.icon_name;
});
d_column.set_cell_data_func(d_renderer_header, (layout, cell, model, iter) => {
SidebarHint hint;
SidebarItem item;
model.get(iter, SidebarColumn.HINT, out hint, SidebarColumn.ITEM, out item);
cell.visible = (hint == SidebarHint.HEADER);
var r = (Gtk.CellRendererText)cell;
r.text = item.text;
});
d_column.set_cell_data_func(d_renderer_text, (layout, cell, model, iter) => {
SidebarHint hint;
SidebarItem item;
model.get(iter, SidebarColumn.HINT, out hint, SidebarColumn.ITEM, out item);
cell.visible = (hint != SidebarHint.HEADER);
var r = (Gtk.CellRendererText)cell;
r.text = item.text;
if (hint == SidebarHint.DUMMY)
{
var context = get_style_context();
context.save();
context.set_state(Gtk.StateFlags.INSENSITIVE);
var col = context.get_color(context.get_state());
context.restore();
r.foreground_rgba = col;
}
else
{
r.foreground_set = false;
}
});
set_row_separator_func((model, iter) => {
SidebarHint hint;
model.get(iter, SidebarColumn.HINT, out hint);
return hint == SidebarHint.SEPARATOR;
});
var sel = get_selection();
sel.set_select_function(select_function);
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);
if (hint != SidebarHint.HEADER && hint != SidebarHint.DUMMY)
{
model.activate(iter, 1);
}
else
{
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>()
{
Gtk.TreeIter iter;
if (get_selected_iter(out iter))
{
return (T)model.item_for_iter(iter);
}
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) => {
if (model.item_for_iter(iter) == item)
{
get_selection().select_iter(iter);
return true;
}
return false;
});
}
public bool is_selected(SidebarItem item)
{
bool retval = false;
model.foreach((m, path, iter) => {
if (model.item_for_iter(iter) == item)
{
retval = get_selection().iter_is_selected(iter);
return true;
}
return false;
});
return retval;
}
protected override void row_activated(Gtk.TreePath path, Gtk.TreeViewColumn column)
{
if (model.clearing)
{
return;
}
Gtk.TreeIter iter;
if (model.get_iter(out iter, path))
{
model.activate(iter, 2);
}
}
protected override bool key_press_event(Gdk.EventKey event)
{
if ((event.state & Gtk.accelerator_get_default_mod_mask()) != 0)
{
return base.key_press_event(event);
}
switch (event.keyval) {
case Gdk.Key.Return:
case Gdk.Key.ISO_Enter:
case Gdk.Key.KP_Enter:
case Gdk.Key.space:
case Gdk.Key.KP_Space:
Gtk.TreePath? path = null;
Gtk.TreeIter iter;
get_cursor(out path, null);
var sel = get_selection();
if (path != null)
{
if (model.get_iter(out iter, path))
{
if (sel.iter_is_selected(iter))
{
model.activate(iter, 2);
}
else
{
sel.unselect_all();
sel.select_iter(iter);
}
}
}
return true;
}
return base.key_press_event(event);
}
public new SidebarStore model
{
get { return base.get_model() as SidebarStore; }
set { base.set_model(value); }
}
private bool do_populate_popup(Gdk.EventButton? event)
{
Gtk.Menu menu = new Gtk.Menu();
populate_popup(menu);
if (menu.get_children() == null)
{
return false;
}
menu.show_all();
menu.attach_to_widget(this, null);
uint button = 0;
uint32 t = Gdk.CURRENT_TIME;
if (event != null)
{
button = event.button;
t = event.time;
}
menu.popup(null, null, null, button, t);
return true;
}
protected override bool button_press_event(Gdk.EventButton 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);
}
else
{
return base.button_press_event(event);
}
}
protected override bool popup_menu()
{
return do_populate_popup(null);
}
}
}
// ex: ts=4 noet