public class Roster { private Gtk.TreeView rosterView; private ContactListModel contactList; private RosterModel model; private Gee.Map groups = new Gee.HashMap(); public signal void start_conversation(string jid); public Roster(Gtk.TreeView rosterView0) { rosterView = rosterView0; contactList = new ContactListModel(this); model = new RosterModel(contactList); rosterView.set_model(model); rosterView.query_tooltip.connect((x, y, keyboard_tip, tooltip) => { Gtk.TreeModel model; Gtk.TreeIter iter; if(!rosterView.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; }); rosterView.has_tooltip = true; rosterView.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); }); model.row_inserted.connect((path, iter) => { rosterView.expand_to_path(path); }); Gtk.TreeViewColumn contactColumn = new Gtk.TreeViewColumn.with_attributes(null, new CellRendererContact(), "data", 0, null); rosterView.append_column(contactColumn); } public void update_contact(Contact c) { contactList.update_contact(c); } public Contact get_contact(string jid) { return contactList.get_contact(jid); } private void update_groups() { foreach(Gee.Map.Entry group in groups) { if(group.value <= 0) { model.removeGroup(group.key); groups.remove(group.key); } else { model.addGroup(group.key); } } } private class RosterModel : Object, Gtk.TreeModel { private int stamp = 0; private Gee.Map groupModels = new Gee.TreeMap(); private Gee.ArrayList childIters = new Gee.ArrayList(); private ContactListModel contactList; private void incrementStamp() { stamp++; childIters.clear(); } public RosterModel(ContactListModel contactList0) { contactList = contactList0; } public void addGroup(string name) { foreach(String group in groupModels.keys) { if(group.data == name) return; } Gtk.TreeModel model = new GroupFilter(contactList, name); model.row_changed.connect((subpath, subiter) => { Gtk.TreePath path = child_path_to_path(name, subpath); Gtk.TreeIter iter; get_iter(out iter, path); row_changed(path, iter); }); model.row_deleted.connect((path) => { path.prepend_index(0); row_deleted(path); incrementStamp(); }); model.row_has_child_toggled.connect((subpath, subiter) => { Gtk.TreePath path = child_path_to_path(name, subpath); incrementStamp(); Gtk.TreeIter iter; get_iter(out iter, path); row_has_child_toggled(path, iter); }); model.row_inserted.connect((subpath, subiter) => { Gtk.TreePath path = child_path_to_path(name, subpath); incrementStamp(); Gtk.TreeIter iter; get_iter(out iter, path); row_inserted(path, iter); }); model.rows_reordered.connect((subpath, subiter, new_order) => { Gtk.TreePath path = child_path_to_path(name, subpath); incrementStamp(); Gtk.TreeIter iter; get_iter(out iter, path); rows_reordered(path, iter, new_order); }); groupModels[new String(name)] = model; int index = 0; foreach(String group in groupModels.keys) { if(group.data == name) break; index++; } Gtk.TreePath path = new Gtk.TreePath.from_indices(index, -1); Gtk.TreeIter iter; get_iter(out iter, path); row_inserted(path, iter); model.foreach((childModel, childPath, childIter) => { Gtk.TreePath subpath = child_path_to_path(name, childPath); Gtk.TreeIter subiter; get_iter(out subiter, subpath); row_inserted(subpath, subiter); return false; }); } public void removeGroup(string name) { int index = 0; foreach(String group in groupModels.keys) { if(group.data == name) { Gee.LinkedList children = new Gee.LinkedList(); groupModels[group].foreach((childModel, childPath, childIter) => { children.offer_head(childPath); return false; }); while(!children.is_empty) { row_deleted(child_path_to_path(name, children.poll_head())); } Gtk.TreePath path = new Gtk.TreePath.from_indices(index, -1); row_deleted(path); groupModels.remove(group); return; } index++; } } private Gtk.TreePath child_path_to_path(string groupName, Gtk.TreePath childPath) { int index = 0; foreach(String group in groupModels.keys) { if(group.data == groupName) { Gtk.TreePath path = childPath.copy(); path.prepend_index(index); return path; } index++; } assert_not_reached(); } private int child_iter_index(Gtk.TreeIter iter) { for(int index = 0; index < childIters.size; ++index) { if(childIters[index].stamp != iter.stamp) continue; if(childIters[index].user_data != iter.user_data) continue; if(childIters[index].user_data2 != iter.user_data2) continue; if(childIters[index].user_data3 != iter.user_data3) continue; return index; } childIters.add(iter); return childIters.size-1; } private Gtk.TreeIter get_nth_child_iter(int n) { return childIters[n]; } private Gtk.TreeIter? get_child_iter(Gtk.TreeIter iter) { assert(iter.stamp == stamp && iter.user_data == this); if((int)iter.user_data3 != -1) return get_nth_child_iter((int)iter.user_data3); else return null; } private Gtk.TreeModel get_child_model(Gtk.TreeIter iter) { assert(iter.stamp == stamp && iter.user_data == this); return groupModels[iter.user_data2 as String]; } public Type get_column_type(int index) { switch(index) { case 0: return typeof(Object); } assert_not_reached(); } public Gtk.TreeModelFlags get_flags() { return 0; } public bool get_iter(out Gtk.TreeIter iter, Gtk.TreePath path) { if(path.get_depth() == 0) return false; unowned int[] indices = path.get_indices(); int index = indices[0]; if(index < 0 || index >= groupModels.size) return false; Gee.MapIterator it = groupModels.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_key(); iter.user_data3 = (void*)(-1); if(path.get_depth() > 1) { Gtk.TreePath subpath = new Gtk.TreePath(); for(int i = 1; i < path.get_depth(); ++i) { subpath.append_index(indices[i]); } Gtk.TreeIter subiter; if(!it.get_value().get_iter(out subiter, subpath)) return false; iter.user_data3 = (void*)child_iter_index(subiter); } return true; } public int get_n_columns() { return 1; } public Gtk.TreePath get_path(Gtk.TreeIter iter) { assert(iter.stamp == stamp && iter.user_data == this); int index = 0; foreach(String group in groupModels.keys) { if(group == iter.user_data2) break; ++index; } Gtk.TreePath subpath = new Gtk.TreePath(); if((int)iter.user_data3 != -1) { Gtk.TreeIter subiter = get_child_iter(iter); subpath = get_child_model(iter).get_path(subiter); } subpath.prepend_index(index); return subpath; } public void get_value(Gtk.TreeIter iter, int column, out Value value) { assert (column == 0 && iter.stamp == stamp && iter.user_data == this); if((int)iter.user_data3 == -1) { value = Value(typeof(String)); value.take_object(iter.user_data2 as String); } else { get_child_model(iter).get_value(get_child_iter(iter), 0, out value); } } public bool iter_children(out Gtk.TreeIter iter, Gtk.TreeIter? parent) { if (parent == null) { return get_iter(out iter, new Gtk.TreePath.from_indices(0, -1)); } assert(parent.stamp == stamp && parent.user_data == this); Gtk.TreeIter childIter; if(!get_child_model(parent).iter_children(out childIter, get_child_iter(parent))) return false; iter.stamp = stamp; iter.user_data = this; iter.user_data2 = parent.user_data2; iter.user_data3 = (void*)child_iter_index(childIter); return true; } public bool iter_has_child(Gtk.TreeIter iter) { assert(iter.stamp == stamp && iter.user_data == this); return (iter_n_children(iter) > 0); } public int iter_n_children(Gtk.TreeIter? iter) { if (iter == null) return groupModels.size; assert(iter.stamp == stamp && iter.user_data == this); return get_child_model(iter).iter_n_children(get_child_iter(iter)); } public bool iter_next(ref Gtk.TreeIter iter) { assert(iter.stamp == stamp && iter.user_data == this); if((int)iter.user_data3 == -1) { bool next = false; foreach(String group in groupModels.keys) { if(next) { iter.user_data2 = group; return true; } if(group == iter.user_data2) next = true; } iter.stamp = -1; return false; } else { Gtk.TreeModel m = get_child_model(iter); Gtk.TreeIter subiter = get_child_iter(iter); if(!m.iter_next(ref subiter)) { iter.stamp = -1; return false; } iter.user_data3 = (void*)child_iter_index(subiter); return true; } } public bool iter_nth_child(out Gtk.TreeIter iter, Gtk.TreeIter? parent, int n) { if (parent == null) { return get_iter(out iter, new Gtk.TreePath.from_indices(n, -1)); } else { Gtk.TreePath path = get_path(parent); path.append_index(n); return get_iter(out iter, path); } } public bool iter_parent(out Gtk.TreeIter iter, Gtk.TreeIter child) { assert(child.stamp == stamp && child.user_data == this); if((int)child.user_data3 == -1) { iter.stamp = -1; return false; } iter.stamp = stamp; iter.user_data = this; iter.user_data2 = child.user_data2; Gtk.TreeIter childParent; if(get_child_model(child).iter_parent(out childParent, get_child_iter(child))) { iter.user_data3 = (void*)child_iter_index(childParent); } else { iter.user_data3 = (void*)(-1); } return true; } public void ref_node(Gtk.TreeIter iter) {} public void unref_node(Gtk.TreeIter iter) {} } private class ContactListModel : Object, Gtk.TreeModel { private unowned Roster roster; private int stamp = 0; private Gee.TreeMap entries = new Gee.TreeMap(); public ContactListModel(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] = roster.groups[group]-1; } else { warn_if_reached(); } } } entries[c.jid] = c; Gtk.TreeIter iter = Gtk.TreeIter(); iter.stamp = stamp; iter.user_data = this; iter.user_data2 = c; iter.user_data3 = null; 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] = 0; roster.groups[group] = roster.groups[group]+1; } 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); } assert_not_reached(); } 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(); iter.user_data3 = null; return true; } public int get_n_columns() { return 1; } public Gtk.TreePath get_path(Gtk.TreeIter iter) { assert(iter.stamp == stamp && iter.user_data == this); 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) { assert (column == 0 && iter.stamp == stamp && iter.user_data == this); 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) { assert(iter.stamp == stamp && iter.user_data == this); 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 string group; public GroupFilter(Gtk.TreeModel childModel, string group0) { Object(child_model: childModel); 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()); }); } } }