summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/gui/CellRendererContact.vala8
-rw-r--r--src/gui/Contact.vala62
-rw-r--r--src/gui/Conversation.vala24
-rw-r--r--src/gui/CoreConnector.vala19
-rw-r--r--src/gui/Ephraim.vala75
-rw-r--r--src/gui/Roster.vala392
6 files changed, 369 insertions, 211 deletions
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("&", "&amp;").replace("\"", "&quot;").replace("<", "&lt;").replace(">", "&gt;");
- }
}
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) {}
}