From 00e8a7d9d07e87a31e3086c2e2e3f71107cbafdb Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Wed, 28 Jul 2010 00:00:37 +0200 Subject: Revised roster groups (still unfinished) --- data/ephraim.glade | 15 +- src/core/ephraim_roster.erl | 2 +- src/gui/CMakeLists.txt | 1 + src/gui/CellRendererContact.vala | 27 +- src/gui/Conversation.vala | 4 +- src/gui/Ephraim.vala | 9 +- src/gui/Roster.vala | 535 ++++++++++++++++++++++++++++----------- src/gui/String.vala | 7 + 8 files changed, 423 insertions(+), 177 deletions(-) create mode 100644 src/gui/String.vala diff --git a/data/ephraim.glade b/data/ephraim.glade index cde3c10..5535b26 100644 --- a/data/ephraim.glade +++ b/data/ephraim.glade @@ -84,18 +84,11 @@ True queue - + True - vertical - - - - - - - - - + True + False + False diff --git a/src/core/ephraim_roster.erl b/src/core/ephraim_roster.erl index b0fa7cf..e9eb9f8 100644 --- a/src/core/ephraim_roster.erl +++ b/src/core/ephraim_roster.erl @@ -168,7 +168,7 @@ get_alias(Roster, JID) -> {ok, Entry} -> case Entry#roster_entry.name of undefined -> - case dict:find('NICKNAME',Entry#roster_entry.vcard) of + case dict:find('NICKNAME', Entry#roster_entry.vcard) of {ok, Value} -> Value; error -> diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index ac62f9a..e446c77 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -9,6 +9,7 @@ vala_precompile(VALA_C "Conversation.vala" "CoreConnector.vala" "Roster.vala" + "String.vala" PACKAGES gtk+-2.0 gee-1.0 diff --git a/src/gui/CellRendererContact.vala b/src/gui/CellRendererContact.vala index 5c7d2e9..d7d9309 100644 --- a/src/gui/CellRendererContact.vala +++ b/src/gui/CellRendererContact.vala @@ -1,21 +1,28 @@ public class CellRendererContact : Gtk.CellRendererText { - private Contact _contact; + private Object _data; - public Contact contact { + public Object data { get { - return _contact; + return _data; } set { - _contact = value; + _data = value; - string str = Markup.escape_text(contact.display_string); + if(_data is Contact) { + Contact contact = _data as Contact; + + string str = Markup.escape_text(contact.display_string); - Gee.Map.Entry res = contact.get_resource_with_highest_priority(); - if(res != null && res.value.status != null) { - str += "\n" + Markup.escape_text(res.value.status) + ""; - } + Gee.Map.Entry res = contact.get_resource_with_highest_priority(); + if(res != null && res.value.status != null) { + str += "\n" + Markup.escape_text(res.value.status) + ""; + } - markup = str; + markup = str; + } + else if(_data is String) { + markup = Markup.escape_text((_data as String).data); + } } } diff --git a/src/gui/Conversation.vala b/src/gui/Conversation.vala index 246aba0..7c8dc03 100644 --- a/src/gui/Conversation.vala +++ b/src/gui/Conversation.vala @@ -3,7 +3,6 @@ public class Conversation { private Gtk.Widget widget; private Gtk.TextView content; private Gtk.Entry entry; - private string me; private string jid; private string display_name; @@ -25,9 +24,8 @@ public class Conversation { } } - public Conversation(Gtk.Notebook conversations0, string me0, string jid0, string? display_name0, bool explicit = true) { + public Conversation(Gtk.Notebook conversations0, string jid0, string? display_name0, bool explicit = true) { conversations = conversations0; - me = me0; jid = jid0; if(display_name0 != null) diff --git a/src/gui/Ephraim.vala b/src/gui/Ephraim.vala index c75d926..f13516e 100644 --- a/src/gui/Ephraim.vala +++ b/src/gui/Ephraim.vala @@ -15,16 +15,13 @@ public class Ephraim { unowned Gtk.MenuItem quitItem = builder.get_object("MenuItemQuit") as Gtk.MenuItem; quitItem.activate.connect(() => window.visible = false); - unowned Gtk.VBox rosterView = builder.get_object("Roster") as Gtk.VBox; + unowned Gtk.TreeView rosterView = builder.get_object("Roster") as Gtk.TreeView; Gee.TreeMap conversations = new Gee.TreeMap(); unowned Gtk.Notebook conversationNotebook = builder.get_object("Conversations") as Gtk.Notebook; Roster roster = new Roster(rosterView); - // FIXME - string me = "/me"; - CoreConnector coreconn = new CoreConnector(); roster.start_conversation.connect((jid) => coreconn.start_conversation(jid)); @@ -35,10 +32,10 @@ public class Ephraim { Contact contact = roster.get_contact(jid); if(contact != null) { - conversations[jid] = new Conversation(conversationNotebook, me, jid, contact.display_string); + conversations[jid] = new Conversation(conversationNotebook, jid, contact.display_string); } else { - conversations[jid] = new Conversation(conversationNotebook, me, jid, null); + conversations[jid] = new Conversation(conversationNotebook, jid, null); } conversations[jid].send_message.connect((type, message) => coreconn.send_message(jid, type, message)); diff --git a/src/gui/Roster.vala b/src/gui/Roster.vala index 65119e2..feb4fd3 100644 --- a/src/gui/Roster.vala +++ b/src/gui/Roster.vala @@ -1,130 +1,381 @@ public class Roster { - private Gtk.VBox rosterView; + private Gtk.TreeView rosterView; + private ContactListModel contactList; private RosterModel model; - private Gee.TreeMap groups = new Gee.TreeMap(); - + private Gee.Map groups = new Gee.HashMap(); + public signal void start_conversation(string jid); - - - public Roster(Gtk.VBox rosterView0) { + + + public Roster(Gtk.TreeView 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) => { + 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(!view.get_tooltip_context(out x, out y, keyboard_tip, out model, null, out 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; }); - view.has_tooltip = true; - - view.row_activated.connect((view, path, column) => { + 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); }); + + Gtk.TreeViewColumn contactColumn = new Gtk.TreeViewColumn.with_attributes(null, new CellRendererContact(), "data", 0, null); + rosterView.append_column(contactColumn); + } + + public Gtk.TreeModel get_model() { + return model; + } + + 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) { + //rosterView.remove(group.value.expander); + continue; + } + + + } + } + + 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; + + + public RosterModel(ContactListModel contactList0) { + contactList = contactList0; + + groupModels[new String("Test")] = contactList; + + contactList.row_changed.connect((subpath, subiter) => { + Gtk.TreePath path = child_path_to_path("Test", subpath); + stamp++; + Gtk.TreeIter iter; + get_iter(out iter, path); + row_changed(path, iter); + }); + + contactList.row_deleted.connect((path) => { + stamp++; + path.prepend_index(0); + row_deleted(path); + }); + + contactList.row_has_child_toggled.connect((subpath, subiter) => { + Gtk.TreePath path = child_path_to_path("Test", subpath); + stamp++; + Gtk.TreeIter iter; + get_iter(out iter, path); + row_has_child_toggled(path, iter); + }); + + contactList.row_inserted.connect((subpath, subiter) => { + Gtk.TreePath path = child_path_to_path("Test", subpath); + stamp++; + Gtk.TreeIter iter; + get_iter(out iter, path); + row_inserted(path, iter); + }); + + contactList.rows_reordered.connect((subpath, subiter, new_order) => { + Gtk.TreePath path = child_path_to_path("Test", subpath); + stamp++; + Gtk.TreeIter iter; + get_iter(out iter, path); + rows_reordered(path, iter, new_order); + }); + } - 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(); + private Gtk.TreePath child_path_to_path(string group, Gtk.TreePath childPath) { + Gtk.TreePath path = childPath.copy(); + path.prepend_index(0); + return path; + } - Gtk.CellRenderer cellRendererContact = new CellRendererContact(); - contactColumn.pack_start(cellRendererContact, true); - contactColumn.set_attributes(cellRendererContact, "contact", 0, null); + private int child_iter_index(Gtk.TreeIter iter) { + if(iter in childIters) { + stderr.printf("Reusing child iter %i\r\n", childIters.index_of(iter)); + return childIters.index_of(iter); + } + else { + childIters.add(iter); + stderr.printf("Added child iter %i\r\n", childIters.size-1); + return childIters.size-1; + } + } - Gtk.CellRenderer cellRendererAvatar = new CellRendererAvatar(); - contactColumn.pack_end(cellRendererAvatar, false); - contactColumn.set_attributes(cellRendererAvatar, "contact", 0, null); + private Gtk.TreeIter get_nth_child_iter(int n) { + return childIters[n]; + } - view.append_column(contactColumn); + 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; + } - return view; - } - - private void update_groups() { - int i = 0; + private Gtk.TreeModel get_child_model(Gtk.TreeIter iter) { + assert(iter.stamp == stamp && iter.user_data == this); + return groupModels[iter.user_data2 as String]; + } - 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; + 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; + + stderr.printf("Called get_iter for path %s\r\n", path.to_string()); + + 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) { + stderr.printf("get_path: %i %p %p %i\r\n", iter.stamp, iter.user_data, iter.user_data2, (int)iter.user_data3); + 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); + stderr.printf("get_path: subiter: %i %p %p %i\r\n", subiter.stamp, subiter.user_data, subiter.user_data2, (int)subiter.user_data3); + subpath = get_child_model(iter).get_path(subiter); + } + subpath.prepend_index(index); + stderr.printf("Returning path %s\r\n", subpath.to_string()); + return subpath; + } + + public void get_value(Gtk.TreeIter iter, int column, out Value value) { + stderr.printf("get_value: iter=%i %p %p %i, column=%i\r\n", iter.stamp, iter.user_data, iter.user_data2, (int)iter.user_data3, column); + assert (column == 0 && iter.stamp == stamp && iter.user_data == this); + + stderr.printf("Getting value %s\r\n", get_path(iter).to_string()); + + 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(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); + 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); } - rosterView.reorder_child(group.value.expander, i++); + return true; } + + public void ref_node(Gtk.TreeIter iter) {} + public void unref_node(Gtk.TreeIter iter) {} } - - private class RosterModel : Object, Gtk.TreeModel { - private Roster roster; + + private class ContactListModel : Object, Gtk.TreeModel { + private unowned Roster roster; private int stamp = 0; private Gee.TreeMap entries = new Gee.TreeMap(); - - - public RosterModel(Roster roster0) { + + + public ContactListModel(Roster roster0) { roster = roster0; } @@ -133,27 +384,28 @@ public class Roster { 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--; + 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 @@ -161,17 +413,17 @@ public class Roster { foreach(string group in c.get_groups()) { if(!(group in roster.groups)) - roster.groups[group] = new GroupInfo(); - - roster.groups[group].count++; + 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]; } @@ -180,149 +432,140 @@ public class Roster { case 0: return typeof(Contact); } - - return Type.INVALID; + + 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) { - if(iter.stamp != stamp || iter.user_data != this) - return (Gtk.TreePath)null; - + 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) { - if (column != 0 || iter.stamp != stamp || iter.user_data != this) { - value = Value(Type.INVALID); - return; - } - + stderr.printf("get_value2: iter=%i %p %p %i, column=%i\r\n", iter.stamp, iter.user_data, iter.user_data2, (int)iter.user_data3, column); + 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) { - if(iter.stamp != stamp || iter.user_data != this) - return false; - + 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 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; - } } diff --git a/src/gui/String.vala b/src/gui/String.vala new file mode 100644 index 0000000..5d1520f --- /dev/null +++ b/src/gui/String.vala @@ -0,0 +1,7 @@ +public class String : Object { + public string data {get; construct;} + + public String(string data0) { + Object(data: data0); + } +} -- cgit v1.2.3