public class Roster { private Gtk.VBox rosterView; private RosterModel model; private Gee.TreeMap groups = new Gee.TreeMap(); public signal void start_conversation(string jid); public Roster(Gtk.VBox rosterView0) { rosterView = rosterView0; model = new RosterModel(this); } public Gtk.TreeModel get_model() { return model; } public void update_contact(Contact c) { model.update_contact(c); } public Contact get_contact(string jid) { return model.get_contact(jid); } private Gtk.TreeView new_tree_view() { Gtk.TreeView view = new Gtk.TreeView(); view.headers_visible = false; view.query_tooltip.connect((x, y, keyboard_tip, tooltip) => { Gtk.TreeModel model; Gtk.TreeIter iter; if(!view.get_tooltip_context(out x, out y, keyboard_tip, out model, null, out iter)) return false; Value value; model.get_value(iter, 0, out value); Contact? c = value.get_object() as Contact; if(c == null) return false; Gee.Map.Entry? r = c.get_resource_with_highest_priority(); if(r == null) return false; tooltip.set_text("Resource: " + r.key + " (" + r.value.priority.to_string() + ")"); return true; }); view.has_tooltip = true; view.row_activated.connect((view, path, column) => { Gtk.TreeIter iter; view.model.get_iter(out iter, path); Value value; view.model.get_value(iter, 0, out value); Contact contact = value.get_object() as Contact; if(contact == null) return; Gee.Map.Entry res = contact.get_resource_with_highest_priority(); if(res == null) return; //FIXME start_conversation(contact.jid + "/" + res.key); }); Gtk.TreeViewColumn presenceColumn = new Gtk.TreeViewColumn.with_attributes(null, new CellRendererPresence(), "contact", 0, null); presenceColumn.min_width = 32; view.append_column(presenceColumn); Gtk.TreeViewColumn contactColumn = new Gtk.TreeViewColumn(); Gtk.CellRenderer cellRendererContact = new CellRendererContact(); contactColumn.pack_start(cellRendererContact, true); contactColumn.set_attributes(cellRendererContact, "contact", 0, null); Gtk.CellRenderer cellRendererAvatar = new CellRendererAvatar(); contactColumn.pack_end(cellRendererAvatar, false); contactColumn.set_attributes(cellRendererAvatar, "contact", 0, null); view.append_column(contactColumn); return view; } private void update_groups() { int i = 0; foreach(Gee.Map.Entry group in groups) { if(group.value.count <= 0) { rosterView.remove(group.value.expander); group.value.expander.remove(group.value.view); groups.remove(group.key); continue; } if(group.value.count > 0 && group.value.view == null) { group.value.view = new_tree_view(); group.value.view.model = new GroupFilter(this, group.key); group.value.expander = new Gtk.Expander("" + Markup.escape_text(group.key) + ""); group.value.expander.set_use_markup(true); group.value.expander.expanded = true; group.value.expander.add(group.value.view); group.value.expander.show_all(); rosterView.pack_start(group.value.expander, false, false, 0); } rosterView.reorder_child(group.value.expander, i++); } } private class RosterModel : Object, Gtk.TreeModel { private Roster roster; private int stamp = 0; private Gee.TreeMap entries = new Gee.TreeMap(); public RosterModel(Roster roster0) { roster = roster0; } public void update_contact(Contact c) { ++stamp; bool added = true; if(c.jid in entries) { added = false; foreach(string group in entries[c.jid].get_groups()) { if(group in roster.groups) { roster.groups[group].count--; } else { warn_if_reached(); } } } entries[c.jid] = c; Gtk.TreeIter iter = Gtk.TreeIter(); iter.stamp = stamp; iter.user_data = this; iter.user_data2 = c; Gtk.TreePath path = get_path(iter); if(added) row_inserted(path, iter); else row_changed(path, iter); foreach(string group in c.get_groups()) { if(!(group in roster.groups)) roster.groups[group] = new GroupInfo(); roster.groups[group].count++; } roster.update_groups(); } public Contact get_contact(string jid) { string bareJID = jid.split("/", 2)[0]; return entries[bareJID]; } public Type get_column_type(int index) { switch(index) { case 0: return typeof(Contact); } return Type.INVALID; } public Gtk.TreeModelFlags get_flags() { return Gtk.TreeModelFlags.LIST_ONLY; } public bool get_iter(out Gtk.TreeIter iter, Gtk.TreePath path) { if(path.get_depth() != 1) return false; int index = path.get_indices()[0]; if(index < 0 || index >= entries.size) return false; Gee.MapIterator it = entries.map_iterator(); it.first(); for(int i = 0; i < index; ++i) it.next(); iter.stamp = stamp; iter.user_data = this; iter.user_data2 = it.get_value(); return true; } public int get_n_columns() { return 1; } public Gtk.TreePath get_path(Gtk.TreeIter iter) { if(iter.stamp != stamp || iter.user_data != this) return (Gtk.TreePath)null; int index = 0; foreach(Contact c in entries.values) { if(c == iter.user_data2) break; ++index; } return new Gtk.TreePath.from_indices(index, -1); } public void get_value(Gtk.TreeIter iter, int column, out Value value) { if (column != 0 || iter.stamp != stamp || iter.user_data != this) { value = Value(Type.INVALID); return; } Contact c = iter.user_data2 as Contact; value = Value(typeof(Contact)); value.take_object(c); } public bool iter_children(out Gtk.TreeIter iter, Gtk.TreeIter? parent) { if (parent != null) { iter.stamp = -1; return false; } return get_iter(out iter, new Gtk.TreePath.from_indices(0, -1)); } public bool iter_has_child(Gtk.TreeIter iter) { return false; } public int iter_n_children(Gtk.TreeIter? iter) { if (iter == null) return entries.size; else return 0; } public bool iter_next(ref Gtk.TreeIter iter) { if(iter.stamp != stamp || iter.user_data != this) return false; bool next = false; foreach(Contact c in entries.values) { if(next) { iter.user_data2 = c; return true; } if(c == iter.user_data2) next = true; } iter.stamp = -1; return false; } public bool iter_nth_child(out Gtk.TreeIter iter, Gtk.TreeIter? parent, int n) { if (parent != null) { iter.stamp = -1; return false; } return get_iter(out iter, new Gtk.TreePath.from_indices(n, -1)); } public bool iter_parent(out Gtk.TreeIter iter, Gtk.TreeIter child) { iter.stamp = -1; return false; } public void ref_node(Gtk.TreeIter iter) {} public void unref_node(Gtk.TreeIter iter) {} } private class GroupFilter : Gtk.TreeModelFilter { private Roster roster; private string group; public GroupFilter(Roster roster0, string group0) { Object(child_model: roster0.model); roster = roster0; group = group0; set_visible_func((model, iter) => { Value value; model.get_value(iter, 0, out value); Contact c = value.get_object() as Contact; if(c == null) return true; return (group in c.get_groups()); }); } } private class GroupInfo { public int count = 0; public Gtk.Expander expander = null; public Gtk.TreeView view = null; } }