public class ContactList { private Gtk.TreeView contactView; private ContactListModel contactList; private ContactGroupModel model; private Gee.Map> groups = new Gee.HashMap>(); public signal void start_conversation(string jid); public ContactList(Gtk.TreeView contactView0) { contactView = contactView0; contactList = new ContactListModel(this); model = new ContactGroupModel(contactList); contactView.set_model(new ContactListFilter(this, new ContactListSorter(model))); contactView.query_tooltip.connect((x, y, keyboard_tip, tooltip) => { Gtk.TreeModel model; Gtk.TreeIter iter; if(!contactView.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; }); contactView.has_tooltip = true; contactView.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); }); contactView.model.row_has_child_toggled.connect((path, iter) => { contactView.expand_row(path, true); }); Gtk.TreeViewColumn contactColumn = new Gtk.TreeViewColumn.with_attributes(null, new CellRendererContact(), "data", 0, null); contactView.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.is_empty) { model.remove_group(group.key); groups.unset(group.key); } else { model.add_group(group.key); } } } public Gee.Map get_group(string group) { return groups[group]; } private class ContactGroupModel : 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 ContactGroupModel(ContactListModel contactList0) { contactList = contactList0; } public void add_group(string name) { int index = 0; foreach(String group in groupModels.keys) { if(group.data == name) { Gtk.TreePath path = new Gtk.TreePath.from_indices(index, -1); Gtk.TreeIter iter; get_iter(out iter, path); row_changed(path, iter); return; } index++; } 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); Gtk.TreePath parent = path.copy(); parent.up(); if(parent.get_depth() == 1) { Gtk.TreeIter iter; get_iter(out iter, parent); if(!iter_has_child(iter)) { row_has_child_toggled(parent, iter); } } }); model.row_has_child_toggled.connect((subpath, subiter) => { Gtk.TreePath path = child_path_to_path(name, subpath); 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); Gtk.TreePath parent = path.copy(); parent.up(); if(parent.get_depth() == 1) { get_iter(out iter, parent); Value value; get_value(iter, 0, out value); if(iter_n_children(iter) == 0) { row_has_child_toggled(parent, iter); } } }); model.rows_reordered.connect((subpath, subiter, new_order) => { Gtk.TreePath path = child_path_to_path(name, subpath); Gtk.TreeIter iter; get_iter(out iter, path); rows_reordered(path, iter, new_order); }); groupModels[new String(name)] = model; 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 remove_group(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.unset(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 pointer_to_int(void* ptr) { return (int)(long)ptr; } 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(pointer_to_int(iter.user_data3) != -1) return get_nth_child_iter(pointer_to_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 = (-1).to_pointer(); 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 = child_iter_index(subiter).to_pointer(); } 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(pointer_to_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(pointer_to_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 = child_iter_index(childIter).to_pointer(); 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(pointer_to_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 = child_iter_index(subiter).to_pointer(); 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(pointer_to_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 = child_iter_index(childParent).to_pointer(); } else { iter.user_data3 = (-1).to_pointer(); } return true; } public void ref_node(Gtk.TreeIter iter) {} public void unref_node(Gtk.TreeIter iter) {} } private class ContactListModel : Object, Gtk.TreeModel { private unowned ContactList contactList; private int stamp = 0; private Gee.TreeMap entries = new Gee.TreeMap(); public ContactListModel(ContactList contactList0) { contactList = contactList0; } public void update_contact(Contact c) { ++stamp; bool added = true; if(c.jid in entries.keys) { added = false; foreach(string group in entries[c.jid].get_groups()) { contactList.groups[group].unset(c.jid); } } 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 contactList.groups.keys)) { contactList.groups[group] = new Gee.HashMap(); } contactList.groups[group][c.jid] = c; } contactList.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()); }); } } private class ContactListFilter : Gtk.TreeModelFilter { private unowned ContactList contactList; private bool show_contact(Contact c) { return (c.get_resource_with_highest_priority() != null); } public ContactListFilter(ContactList contactList0, Gtk.TreeModel childModel) { Object(child_model: childModel); contactList = contactList0; 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 show_contact(c); } else { assert(value.get_object() is String); string group = (value.get_object() as String).data; foreach(Gee.Map.Entry contact in contactList.get_group(group)) { if(show_contact(contact.value)) return true; } return false; } }); } } private class ContactListSorter : Gtk.TreeModelSort { public ContactListSorter(Gtk.TreeModel childModel) { Object(model: childModel); set_sort_column_id(0, Gtk.SortType.ASCENDING); set_sort_func(0, (model, itera, iterb) => { Value value; model.get_value(itera, 0, out value); Object a = value.get_object(); model.get_value(iterb, 0, out value); Object b = value.get_object(); if(a is String && b is String) { return (a as String).data.collate((b as String).data); } else if(a is Contact && b is Contact) { return (a as Contact).display_string.collate((b as Contact).display_string); } assert_not_reached(); }); } } }