summaryrefslogtreecommitdiffstats
path: root/src/gui/ContactList.vala
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/ContactList.vala')
-rw-r--r--src/gui/ContactList.vala729
1 files changed, 729 insertions, 0 deletions
diff --git a/src/gui/ContactList.vala b/src/gui/ContactList.vala
new file mode 100644
index 0000000..8696f93
--- /dev/null
+++ b/src/gui/ContactList.vala
@@ -0,0 +1,729 @@
+public class ContactList {
+ private Gtk.TreeView contactView;
+ private ContactListModel contactList;
+ private ContactGroupModel model;
+ private Gee.Map<string, Gee.Map<string, Contact>> groups = new Gee.HashMap<string, Gee.Map<string, Contact>>();
+
+ 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<string, Contact.Resource>? 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<string, Contact.Resource> 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<string, Gee.Map<string, Contact>> 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<string, Contact> get_group(string group) {
+ return groups[group];
+ }
+
+ private class ContactGroupModel : Object, Gtk.TreeModel {
+ private int stamp = 0;
+ private Gee.Map<String, Gtk.TreeModel> groupModels = new Gee.TreeMap<String, Gtk.TreeModel>();
+ private Gee.ArrayList<Gtk.TreeIter?> childIters = new Gee.ArrayList<Gtk.TreeIter?>();
+ 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<Gtk.TreePath> children = new Gee.LinkedList<Gtk.TreePath>();
+
+ 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<String, Gtk.TreeModel> 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<string, Contact> entries = new Gee.TreeMap<string, Contact>();
+
+
+ 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<string, Contact>();
+ }
+
+ 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<string, Contact> 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<string, Contact> 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();
+ });
+ }
+ }
+}