From 6ec7efc36f44dfddff4c9d843cd4c70cffb95837 Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Tue, 17 Aug 2010 00:22:21 +0200 Subject: Many improvements to roster view --- src/core/ephraim.erl | 2 +- src/core/ephraim_roster.erl | 3 +- src/gui/CellRendererContact.vala | 22 +++++-- src/gui/Contact.vala | 14 ++++- src/gui/CoreConnector.vala | 33 ++++++++++- src/gui/Ephraim.vala | 2 +- src/gui/Roster.vala | 125 +++++++++++++++++++++++++++++---------- 7 files changed, 155 insertions(+), 46 deletions(-) diff --git a/src/core/ephraim.erl b/src/core/ephraim.erl index c8783f0..c59be72 100644 --- a/src/core/ephraim.erl +++ b/src/core/ephraim.erl @@ -143,7 +143,7 @@ loop(State) -> {iq, response, result, _, 'vcard-temp', Payload, _, _, 'jabber:client'} -> State#state.roster ! {vcard_iq, From, Payload}; _ -> - io:format("ephraim: IQ from ~p: ~p~n", [From, IQ]) + ok %io:format("ephraim: IQ from ~p: ~p~n", [From, IQ]) end, loop(State); diff --git a/src/core/ephraim_roster.erl b/src/core/ephraim_roster.erl index aa31fd1..362b788 100644 --- a/src/core/ephraim_roster.erl +++ b/src/core/ephraim_roster.erl @@ -125,7 +125,6 @@ handleRosterIQ(Roster, Item) -> Subscription = binary_to_atom(exmpp_xml:get_attribute(Item, subscription, undefined), utf8), ephraim ! {send_packet, exmpp_stanza:set_recipient(exmpp_iq:get('jabber:client', #xmlel{ns='vcard-temp',name='vCard'}), JID)}, - io:format("ephraim_roster: ~p~n", [exmpp_stanza:set_recipient(exmpp_iq:get('jabber:client', #xmlel{ns='vcard-temp',name='vCard'}), JID)]), updateRosterEntry(Roster, JID, Name, Subscription, Groups). @@ -215,6 +214,6 @@ loop(Roster) -> loop(Roster); Msg -> - io:format("ephraim_roster: ~p~n", [Msg]), + %io:format("ephraim_roster: ~p~n", [Msg]), loop(Roster) end. diff --git a/src/gui/CellRendererContact.vala b/src/gui/CellRendererContact.vala index 924b601..26497ec 100644 --- a/src/gui/CellRendererContact.vala +++ b/src/gui/CellRendererContact.vala @@ -38,14 +38,23 @@ public class CellRendererContact : Gtk.CellRenderer { 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) + ""; + string status = res.value.status; + if(status.chr(-1, '\n') != null) { + status = status.split("\n", 2)[0] + "..."; + } + str += "\n" + Markup.escape_text(status) + ""; } textRenderer.markup = str; Gee.Map.Entry? r = contact.get_resource_with_highest_priority(); if(r == null) { - presenceRenderer.pixbuf = offline; + if(contact.subscription.is_to()) { + presenceRenderer.pixbuf = offline; + } + else { + presenceRenderer.pixbuf = undefined; + } } else { switch(r.value.show) { @@ -68,9 +77,13 @@ public class CellRendererContact : Gtk.CellRenderer { } avatarRenderer.pixbuf = contact.avatar; + + cell_background = "#fff"; } else if(_data is String) { - textRenderer.text = (_data as String).data; + textRenderer.markup = "" + Markup.escape_text((_data as String).data) + ""; + + cell_background = "#ddd"; } } } @@ -96,8 +109,6 @@ public class CellRendererContact : Gtk.CellRenderer { height = calc_height; if(cell_area != null) { - if(&width != null) - width = cell_area.width; if(&x_offset != null) x_offset = 0; if(&y_offset != null) @@ -162,5 +173,6 @@ public class CellRendererContact : Gtk.CellRenderer { Object(); textRenderer.ellipsize = Pango.EllipsizeMode.END; + cell_background_set = true; } } diff --git a/src/gui/Contact.vala b/src/gui/Contact.vala index 4ced825..512c33f 100644 --- a/src/gui/Contact.vala +++ b/src/gui/Contact.vala @@ -20,6 +20,8 @@ public class Contact : Object { public Contact(string jid0, string? name0) { Object(jid: jid0, name: name0); + subscription = Subscription.NONE; + update_display_string(); } @@ -40,7 +42,7 @@ public class Contact : Object { if (name != null) display_string = name; else - display_string = jid; + display_string = jid.split("@", 2)[0]; } public Gee.Map.Entry? get_resource_with_highest_priority() { @@ -68,7 +70,15 @@ public class Contact : Object { } public enum Subscription { - BOTH + NONE, PENDING_OUT, PENDING_IN, PENDING_BOTH, TO, TO_PENDING_IN, FROM, FROM_PENDING_OUT, BOTH; + + public bool is_to() { + return (this == TO || this == TO_PENDING_IN || this == BOTH); + } + + public bool is_from() { + return (this == FROM || this == FROM_PENDING_OUT || this == BOTH); + } } public class Resource : Object { diff --git a/src/gui/CoreConnector.vala b/src/gui/CoreConnector.vala index 5566686..d5f0ecc 100644 --- a/src/gui/CoreConnector.vala +++ b/src/gui/CoreConnector.vala @@ -32,6 +32,9 @@ public class CoreConnector { } private static string from_utf8(Eva.Binary bin) { + if(bin.len == 0) + return ""; + string ret = ((string)bin.data).ndup(bin.len); warn_if_fail(ret.validate()); return ret; @@ -69,13 +72,34 @@ public class CoreConnector { Eva.Binary name_term = match["Name"] as Eva.Binary; string? name; - if (name_term is Eva.Binary) + if (name_term != null) name = from_utf8(name_term); else name = null; Contact contact = new Contact(jid, name); - + + Eva.Atom subscription = match["Subscription"] as Eva.Atom; + if(subscription != null) { + switch(subscription.value) { + case "none": + contact.subscription = Contact.Subscription.NONE; + break; + case "to": + contact.subscription = Contact.Subscription.TO; + break; + case "from": + contact.subscription = Contact.Subscription.FROM; + break; + case "both": + contact.subscription = Contact.Subscription.BOTH; + break; + default: + stderr.printf("Unknown subscription: %s\r\n", subscription.value); + break; + } + } + Eva.Cons groups = match["Groups"] as Eva.Cons; while(groups != null) { Eva.Binary group_term = groups.head as Eva.Binary; @@ -122,8 +146,11 @@ public class CoreConnector { } string? status = null; - if(status_term != null) + if(status_term != null) { status = from_utf8(status_term); + if(status == "") + status = null; + } contact.update_resource(rname, new Contact.Resource(prio, show, status)); } diff --git a/src/gui/Ephraim.vala b/src/gui/Ephraim.vala index f13516e..b0e2571 100644 --- a/src/gui/Ephraim.vala +++ b/src/gui/Ephraim.vala @@ -42,7 +42,7 @@ public class Ephraim { }); coreconn.chat_message.connect((jid, type, from, message) => { - if(!(jid in conversations)) + if(!(jid in conversations.keys)) return; conversations[jid].chat_message(type, from, message); diff --git a/src/gui/Roster.vala b/src/gui/Roster.vala index ca1ef16..32bdb6d 100644 --- a/src/gui/Roster.vala +++ b/src/gui/Roster.vala @@ -2,17 +2,16 @@ public class Roster { private Gtk.TreeView rosterView; private ContactListModel contactList; private RosterModel model; - private Gee.Map groups = new Gee.HashMap(); + 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.set_model(new RosterFilter(this, model)); rosterView.query_tooltip.connect((x, y, keyboard_tip, tooltip) => { Gtk.TreeModel model; @@ -57,7 +56,7 @@ public class Roster { start_conversation(contact.jid + "/" + res.key); }); - model.row_inserted.connect((path, iter) => { + rosterView.model.row_inserted.connect((path, iter) => { rosterView.expand_to_path(path); }); @@ -74,17 +73,21 @@ public class Roster { } private void update_groups() { - foreach(Gee.Map.Entry group in groups) { - if(group.value <= 0) { - model.removeGroup(group.key); - groups.remove(group.key); + foreach(Gee.Map.Entry> group in groups) { + if(group.value.is_empty) { + model.remove_group(group.key); + groups.unset(group.key); } else { - model.addGroup(group.key); + model.add_group(group.key); } } } - + + public Gee.Map get_group(string group) { + return groups[group]; + } + private class RosterModel : Object, Gtk.TreeModel { private int stamp = 0; private Gee.Map groupModels = new Gee.TreeMap(); @@ -101,10 +104,19 @@ public class Roster { contactList = contactList0; } - public void addGroup(string name) { + public void add_group(string name) { + int index = 0; foreach(String group in groupModels.keys) { - if(group.data == name) + 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); @@ -119,12 +131,20 @@ public class Roster { model.row_deleted.connect((path) => { path.prepend_index(0); row_deleted(path); - incrementStamp(); + + 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); - incrementStamp(); Gtk.TreeIter iter; get_iter(out iter, path); row_has_child_toggled(path, iter); @@ -132,15 +152,26 @@ public class Roster { 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); - incrementStamp(); Gtk.TreeIter iter; get_iter(out iter, path); rows_reordered(path, iter, new_order); @@ -148,7 +179,7 @@ public class Roster { groupModels[new String(name)] = model; - int index = 0; + index = 0; foreach(String group in groupModels.keys) { if(group.data == name) break; @@ -171,7 +202,7 @@ public class Roster { }); } - public void removeGroup(string name) { + public void remove_group(string name) { int index = 0; foreach(String group in groupModels.keys) { if(group.data == name) { @@ -190,7 +221,7 @@ public class Roster { Gtk.TreePath path = new Gtk.TreePath.from_indices(index, -1); row_deleted(path); - groupModels.remove(group); + groupModels.unset(group); return; } @@ -454,20 +485,14 @@ public class Roster { public void update_contact(Contact c) { ++stamp; bool added = true; - if(c.jid in entries) { + if(c.jid in entries.keys) { 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(); - } + roster.groups[group].unset(c.jid); } } - entries[c.jid] = c; Gtk.TreeIter iter = Gtk.TreeIter(); @@ -484,10 +509,11 @@ public class Roster { 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; + if(!(group in roster.groups.keys)) { + roster.groups[group] = new Gee.HashMap(); + } + + roster.groups[group][c.jid] = c; } roster.update_groups(); @@ -632,9 +658,44 @@ public class Roster { if(c == null) return true; - + return (group in c.get_groups()); }); } } + + private class RosterFilter : Gtk.TreeModelFilter { + private unowned Roster roster; + + private bool show_contact(Contact c) { + return (c.get_resource_with_highest_priority() != null); + } + + public RosterFilter(Roster roster0, Gtk.TreeModel childModel) { + Object(child_model: childModel); + roster = roster0; + + 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 roster.get_group(group)) { + if(show_contact(contact.value)) + return true; + } + + return false; + } + }); + } + } } -- cgit v1.2.3