diff options
-rw-r--r-- | data/ephraim.glade | 22 | ||||
-rw-r--r-- | src/gui/CellRendererContact.vala | 8 | ||||
-rw-r--r-- | src/gui/Contact.vala | 62 | ||||
-rw-r--r-- | src/gui/Conversation.vala | 24 | ||||
-rw-r--r-- | src/gui/CoreConnector.vala | 19 | ||||
-rw-r--r-- | src/gui/Ephraim.vala | 75 | ||||
-rw-r--r-- | src/gui/Roster.vala | 392 |
7 files changed, 388 insertions, 214 deletions
diff --git a/data/ephraim.glade b/data/ephraim.glade index cde04f3..cde3c10 100644 --- a/data/ephraim.glade +++ b/data/ephraim.glade @@ -80,10 +80,24 @@ <property name="hscrollbar_policy">never</property> <property name="vscrollbar_policy">automatic</property> <child> - <object class="GtkTreeView" id="Roster"> + <object class="GtkViewport" id="viewport2"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="headers_visible">False</property> + <property name="resize_mode">queue</property> + <child> + <object class="GtkVBox" id="Roster"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + </object> + </child> </object> </child> </object> @@ -153,6 +167,8 @@ <property name="visible">True</property> <property name="can_focus">True</property> <property name="editable">False</property> + <property name="left_margin">3</property> + <property name="right_margin">3</property> <property name="cursor_visible">False</property> </object> </child> diff --git a/src/gui/CellRendererContact.vala b/src/gui/CellRendererContact.vala index 290ea09..5c7d2e9 100644 --- a/src/gui/CellRendererContact.vala +++ b/src/gui/CellRendererContact.vala @@ -8,11 +8,11 @@ public class CellRendererContact : Gtk.CellRendererText { set { _contact = value; - string str = escape_markup(contact.display_string); + string str = Markup.escape_text(contact.display_string); Gee.Map.Entry<string, Contact.Resource> res = contact.get_resource_with_highest_priority(); if(res != null && res.value.status != null) { - str += "\n<span size=\"small\" fgcolor=\"grey40\" style=\"italic\">" + escape_markup(res.value.status) + "</span>"; + str += "\n<span size=\"small\" fgcolor=\"grey40\" style=\"italic\">" + Markup.escape_text(res.value.status) + "</span>"; } markup = str; @@ -22,8 +22,4 @@ public class CellRendererContact : Gtk.CellRendererText { public CellRendererContact() { ellipsize = Pango.EllipsizeMode.END; } - - private static string escape_markup(string str) { - return str.replace("&", "&").replace("\"", """).replace("<", "<").replace(">", ">"); - } } diff --git a/src/gui/Contact.vala b/src/gui/Contact.vala index 0e27b07..4ced825 100644 --- a/src/gui/Contact.vala +++ b/src/gui/Contact.vala @@ -1,10 +1,20 @@ public class Contact : Object { - public enum Show { - ONLINE, AWAY, CHAT, DND, XA, UNDEFINED + private static Gee.TreeSet<string> defaultGroups = new Gee.TreeSet<string>(); + private Gee.TreeSet<string> groups = new Gee.TreeSet<string>(); + private Gee.HashMap<string, Resource> resources = new Gee.HashMap<string, Resource>(); + + public string jid {get; construct;} + public string? name {get; construct;} + public Subscription subscription {get; set;} + public Gdk.Pixbuf? avatar {get; set; default = null;} + public string display_string {get; private set;} + + static construct { + defaultGroups.add(get_default_group()); } - public enum Subscription { - BOTH + private static string get_default_group() { + return "Allgemein"; } public Contact(string jid0, string? name0) { @@ -13,25 +23,18 @@ public class Contact : Object { update_display_string(); } - public class Resource : Object { - public Resource(int prio0, Show show0, string? status0) { - Object(priority: prio0, show: show0, status: status0); + public Gee.Set<string> get_groups() { + if(groups.is_empty) { + return defaultGroups.read_only_view; + } + else { + return groups.read_only_view; } - - public int priority {get; construct;} - public Show show {get; construct;} - public string? status {get; construct;} } - public string jid {get; construct;} - public string? name {get; construct;} - public Subscription subscription {get; set;} - public Gdk.Pixbuf? avatar {get; set; default = null;} - public Gee.TreeSet<string> groups = new Gee.TreeSet<string>(); - - public string display_string {get; private set;} - - private Gee.HashMap<string, Resource> resources = new Gee.HashMap<string, Resource>(); + public void add_group(string group) { + groups.add(group); + } private void update_display_string() { if (name != null) @@ -58,4 +61,23 @@ public class Contact : Object { resources[resource_name] = resource; update_display_string(); } + + + public enum Show { + ONLINE, AWAY, CHAT, DND, XA, UNDEFINED + } + + public enum Subscription { + BOTH + } + + public class Resource : Object { + public Resource(int prio0, Show show0, string? status0) { + Object(priority: prio0, show: show0, status: status0); + } + + public int priority {get; construct;} + public Show show {get; construct;} + public string? status {get; construct;} + } } diff --git a/src/gui/Conversation.vala b/src/gui/Conversation.vala index 629461e..811fa5c 100644 --- a/src/gui/Conversation.vala +++ b/src/gui/Conversation.vala @@ -16,17 +16,16 @@ public class Conversation { Gtk.Button closeButton = new Gtk.Button(); Gdk.Pixbuf closeImage = render_icon(Gtk.STOCK_CLOSE, (Gtk.IconSize)(-1), null); - //Gtk.Image closeImage = new Gtk.Image.from_stock(Gtk.STOCK_CLOSE, (Gtk.IconSize)4); closeButton.image = new Gtk.Image.from_pixbuf(closeImage.scale_simple(16, 16, Gdk.InterpType.BILINEAR)); - //closeButton.width_request = 20; closeButton.height_request = 20; + closeButton.relief = Gtk.ReliefStyle.NONE; pack_end(closeButton, false, false, 0); show_all(); } } - public Conversation(Gtk.Notebook conversations0, string me0, string jid0, string? display_name0) { + public Conversation(Gtk.Notebook conversations0, string me0, string jid0, string? display_name0, bool explicit = true) { conversations = conversations0; me = me0; jid = jid0; @@ -55,7 +54,10 @@ public class Conversation { unowned Gtk.Label title = builder.get_object("ConversationTitle") as Gtk.Label; title.set_text("Conversation with " + display_name); - conversations.append_page(widget, new TabLabel(display_name)); + int page = conversations.append_page(widget, new TabLabel(display_name)); + conversations.set_tab_reorderable(widget, true); + if(explicit) + conversations.page = page; } ~Conversation() { @@ -66,13 +68,23 @@ public class Conversation { if(type != "chat") return; - content.buffer.text += me + ": " + message + "\n"; + string str = me + ": " + message; + + if(content.buffer.text.length != 0) + str = "\n" + str; + + content.buffer.text += str; } public void receive_message(string type, string message) { if(type != "chat") return; - content.buffer.text += display_name + ": " + message + "\n"; + string str = display_name + ": " + message; + + if(content.buffer.text.length != 0) + str = "\n" + str; + + content.buffer.text += str; } } diff --git a/src/gui/CoreConnector.vala b/src/gui/CoreConnector.vala index a152e58..d43ab2f 100644 --- a/src/gui/CoreConnector.vala +++ b/src/gui/CoreConnector.vala @@ -107,9 +107,20 @@ public class CoreConnector { name = null; Contact contact = new Contact(jid, name); + + Erl.Term groups = match_term.var_content("Groups"); + while(groups.is_cons()) { + Erl.Term group_term = groups.hd(); + + if(group_term.is_binary()) { + string group = ((string)group_term.bin_ptr()).ndup(group_term.bin_size()); + contact.add_group(group); + } + + groups = groups.tl(); + } Erl.Term resources = match_term.var_content("Resources"); - while(resources.is_cons()) { Erl.Term r; @@ -214,10 +225,10 @@ public class CoreConnector { string type = ((string)type_term.atom_ptr()).ndup(type_term.atom_size()); Erl.Term body_term = match_term.var_content("Body"); - if(!body_term.is_list()) + if(!body_term.is_binary()) // TODO Debug output return; - string body = body_term.iolist_to_string(); + string body = ((string)body_term.bin_ptr()).ndup(body_term.bin_size()); sent_message(jid, type, body); } @@ -232,6 +243,6 @@ public class CoreConnector { } public void send_message(string jid, string type, string message) { - con.reg_send("ephraim", Erl.format("{send_message,~s,~a,~s}", jid, type, message)); + con.reg_send("ephraim", Erl.format("{send_message,~w,~a,~w}", Erl.mk_binary(jid.to_utf8()), type, Erl.mk_binary(message.to_utf8()))); } } diff --git a/src/gui/Ephraim.vala b/src/gui/Ephraim.vala index fe9f431..99a7a48 100644 --- a/src/gui/Ephraim.vala +++ b/src/gui/Ephraim.vala @@ -9,26 +9,28 @@ public class Ephraim { return 1; } - Roster roster = new Roster(); - CoreConnector coreconn = new CoreConnector(); - - coreconn.update_contact.connect(roster.update_contact); - unowned Gtk.Window window = builder.get_object("MainWindow") as Gtk.Window; window.hide.connect(Gtk.main_quit); unowned Gtk.MenuItem quitItem = builder.get_object("MenuItemQuit") as Gtk.MenuItem; quitItem.activate.connect(() => window.visible = false); - unowned Gtk.TreeView rosterView = builder.get_object("Roster") as Gtk.TreeView; - rosterView.set_model(roster); + unowned Gtk.VBox rosterView = builder.get_object("Roster") as Gtk.VBox; Gee.TreeMap<string, Conversation> conversations = new Gee.TreeMap<string, Conversation>(); 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)); + + coreconn.update_contact.connect(roster.update_contact); + coreconn.new_conversation.connect((jid) => { Contact contact = roster.get_contact(jid); @@ -58,65 +60,6 @@ public class Ephraim { if(!coreconn.start()) return 1; - 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<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; - }); - rosterView.has_tooltip = true; - - rosterView.row_activated.connect((view, path, column) => { - Gtk.TreeIter iter; - roster.get_iter(out iter, path); - - Value value; - roster.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 - - coreconn.start_conversation(contact.jid + "/" + res.key); - }); - - Gtk.TreeViewColumn presenceColumn = new Gtk.TreeViewColumn.with_attributes(null, new CellRendererPresence(), "contact", 0, null); - presenceColumn.min_width = 32; - rosterView.append_column(presenceColumn); - - Gtk.TreeViewColumn contactColumn = new Gtk.TreeViewColumn(); - - Gtk.CellRenderer cellRendererContact = new CellRendererContact(); - contactColumn.pack_start(cellRendererContact, true); - contactColumn.set_attributes(cellRendererContact, "contact", 0, null); - - Gtk.CellRenderer cellRendererAvatar = new CellRendererAvatar(); - contactColumn.pack_end(cellRendererAvatar, false); - contactColumn.set_attributes(cellRendererAvatar, "contact", 0, null); - - rosterView.append_column(contactColumn); - window.visible = true; Gtk.main(); diff --git a/src/gui/Roster.vala b/src/gui/Roster.vala index be0a5ed..65119e2 100644 --- a/src/gui/Roster.vala +++ b/src/gui/Roster.vala @@ -1,154 +1,328 @@ -public class Roster : Object, Gtk.TreeModel { - private int stamp; - private Gee.TreeMap<string, Contact> entries = new Gee.TreeMap<string, Contact>(); +public class Roster { + private Gtk.VBox rosterView; + private RosterModel model; + private Gee.TreeMap<string, GroupInfo> groups = new Gee.TreeMap<string, GroupInfo>(); + public signal void start_conversation(string jid); - public Roster() { - stamp = 0; + + public Roster(Gtk.VBox rosterView0) { + rosterView = rosterView0; + model = new RosterModel(this); + } + + public Gtk.TreeModel get_model() { + return model; } public void update_contact(Contact c) { - ++stamp; - bool added = !entries.has_key(c.jid); - - entries[c.jid] = c; - - Gtk.TreeIter iter = Gtk.TreeIter(); - iter.stamp = stamp; - iter.user_data = this; - iter.user_data2 = c; - - Gtk.TreePath path = get_path(iter); - - if (added) - row_inserted(path, iter); - else - row_changed(path, iter); + model.update_contact(c); } public Contact get_contact(string jid) { - string bareJID = jid.split("/", 2)[0]; - - return entries[bareJID]; + return model.get_contact(jid); } - public Type get_column_type (int index) { - switch(index) { - case 0: - return typeof(Contact); - } + 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) => { + Gtk.TreeModel model; + Gtk.TreeIter iter; + + if(!view.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; + }); + view.has_tooltip = true; + + view.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); + }); + + 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(); - return Type.INVALID; + Gtk.CellRenderer cellRendererContact = new CellRendererContact(); + contactColumn.pack_start(cellRendererContact, true); + contactColumn.set_attributes(cellRendererContact, "contact", 0, null); + + Gtk.CellRenderer cellRendererAvatar = new CellRendererAvatar(); + contactColumn.pack_end(cellRendererAvatar, false); + contactColumn.set_attributes(cellRendererAvatar, "contact", 0, null); + + view.append_column(contactColumn); + + return view; } + + private void update_groups() { + int i = 0; - public Gtk.TreeModelFlags get_flags () { - return Gtk.TreeModelFlags.LIST_ONLY; + foreach(Gee.Map.Entry<string, GroupInfo> 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; + } + + 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("<i>" + Markup.escape_text(group.key) + "</i>"); + 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); + } + + rosterView.reorder_child(group.value.expander, i++); + } } + + private class RosterModel : Object, Gtk.TreeModel { + private Roster roster; + private int stamp = 0; + private Gee.TreeMap<string, Contact> entries = new Gee.TreeMap<string, Contact>(); + - public bool get_iter (out Gtk.TreeIter iter, Gtk.TreePath path) { - if(path.get_depth() != 1) - return false; + public RosterModel(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].count--; + } + else { + warn_if_reached(); + } + } + } - 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(); + entries[c.jid] = c; - iter.stamp = stamp; - iter.user_data = this; - iter.user_data2 = it.get_value(); + Gtk.TreeIter iter = Gtk.TreeIter(); + iter.stamp = stamp; + iter.user_data = this; + iter.user_data2 = c; - return true; - } + 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] = new GroupInfo(); + + roster.groups[group].count++; + } + + roster.update_groups(); + } - public int get_n_columns () { - return 1; - } + 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); + } + + return Type.INVALID; + } + + 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(); + + return true; + } - public Gtk.TreePath get_path (Gtk.TreeIter iter) { - if(iter.stamp != stamp || iter.user_data != this) - return (Gtk.TreePath)null; + 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; - int index = 0; + int index = 0; - foreach(Contact c in entries.values) { - if(c == iter.user_data2) - break; + foreach(Contact c in entries.values) { + if(c == iter.user_data2) + break; - ++index; - } + ++index; + } - return new Gtk.TreePath.from_indices(index, -1); - } + 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; + 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; + } + + 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; + } - Contact c = iter.user_data2 as Contact; - value = Value(typeof(Contact)); - value.take_object(c); - } + return get_iter(out iter, new Gtk.TreePath.from_indices(0, -1)); + } - public bool iter_children (out Gtk.TreeIter iter, Gtk.TreeIter? parent) { - if (parent != null) { - iter.stamp = -1; + public bool iter_has_child(Gtk.TreeIter iter) { 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 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; + public bool iter_next(ref Gtk.TreeIter iter) { + if(iter.stamp != stamp || iter.user_data != this) + return false; - bool next = false; - foreach(Contact c in entries.values) { - if(next) { - iter.user_data2 = c; - return true; - } + bool next = false; + foreach(Contact c in entries.values) { + if(next) { + iter.user_data2 = c; + return true; + } - if(c == iter.user_data2) - next = 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; + } - iter.stamp = -1; - return false; - } + return get_iter(out iter, new Gtk.TreePath.from_indices(n, -1)); + } - public bool iter_nth_child (out Gtk.TreeIter iter, Gtk.TreeIter? parent, int n) { - if (parent != null) { + 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; - return get_iter(out iter, new Gtk.TreePath.from_indices(n, -1)); + 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()); + }); + } } - - public bool iter_parent (out Gtk.TreeIter iter, Gtk.TreeIter child) { - iter.stamp = -1; - return false; + + private class GroupInfo { + public int count = 0; + public Gtk.Expander expander = null; + public Gtk.TreeView view = null; } - - public void ref_node (Gtk.TreeIter iter) {} - public void unref_node (Gtk.TreeIter iter) {} } |