X-Git-Url: http://cgit.babelmonkeys.de/?p=jubjub.git;a=blobdiff_plain;f=src%2Fgui%2Fgtk%2FJubGtkRosterUI.m;h=99e51446e75486d6a6f97bafd732c0d9c83c5c8f;hp=9a2da292a1f68075d0f02e176e2bf0c9efd9b4bb;hb=62fb875be3885fcecbed4f614f01975cd200c9c8;hpb=d5df5e6b07d60d4ac8f43213e719d3331a8b5eb6 diff --git a/src/gui/gtk/JubGtkRosterUI.m b/src/gui/gtk/JubGtkRosterUI.m index 9a2da29..99e5144 100644 --- a/src/gui/gtk/JubGtkRosterUI.m +++ b/src/gui/gtk/JubGtkRosterUI.m @@ -1,57 +1,126 @@ +#import +#include + #import "JubGtkRosterUI.h" #import "JubGObjectMap.h" #import "JubGtkChatUI.h" +#import "JubGtkHelper.h" -static gboolean filter_roster_by_presence(GtkTreeModel *model, - GtkTreeIter *iter, gpointer data) +static void roster_row_activated(GtkTreeView *tree_view, GtkTreePath *path, + GtkTreeViewColumn *column, gpointer data) { - char *jid_s; + OFAutoreleasePool *pool; + XMPPContact *contact; + JubChatClient *client = data; + GtkTreeIter row_iter; + GtkTreeModel *tree_model; + gchar *jid_s; OFString *jid; - OFCountedSet *presences = data; - gtk_tree_model_get(model, iter, 1, &jid_s, -1); + tree_model = gtk_tree_view_get_model(tree_view); + gtk_tree_model_get_iter(tree_model, &row_iter, path); + gtk_tree_model_get(tree_model, &row_iter, 1, &jid_s, -1); - // Groups have no JID - if (!jid_s) - return TRUE; + // This was a group row + if (!jid_s) return; + + pool = [OFAutoreleasePool new]; - jid = [[OFString alloc] initWithUTF8String: jid_s]; + jid = [OFString stringWithUTF8String: jid_s]; + contact = [client.contactManager.contacts objectForKey: jid]; + + [client performSelectorOnMainThread: @selector(chatForContact:) + withObject: contact + waitUntilDone: NO]; + [pool release]; +} + +static void presence_changed(GtkComboBox *combo_box, gpointer data) +{ + XMPPPresence *pres; + XMPPConnection *connection = data; + OFAutoreleasePool *pool = [OFAutoreleasePool new]; + + const char *status = gtk_combo_box_get_active_id(combo_box); + + if (!strcmp(status, "unavailable")) + pres = [XMPPPresence presenceWithType: @"unavailable"]; + else { + pres = [XMPPPresence presence]; + if (strcmp(status, "available")) + [pres setShow: @(status)]; + } - g_free(jid_s); + [connection sendStanza: pres]; - int num = [presences countForObject: jid]; - if (num) { - [jid release]; + [pool release]; +} + +static gboolean filter_roster_by_presence(GtkTreeModel *model, + GtkTreeIter *iter, gpointer data) +{ + char *status; + gtk_tree_model_get(model, iter, 2, &status, -1); + + // Groups have no status + if (!status) return TRUE; - } else { - [jid release]; + + if (!strcmp(status, "unavailable")) return FALSE; - } + + return TRUE; } @implementation JubGtkRosterUI -- initWithBuilder: (GtkBuilder*)builder_ +- initWithClient: (JubChatClient*)client { self = [super init]; @try { - groupMap = [[OFMapTable alloc] + GtkTreeView *roster_view; + GtkBuilder *builder; + + _groupMap = [[OFMapTable alloc] initWithKeyFunctions: keyFunctions valueFunctions: rowRefFunctions]; - contactMap = [[OFMutableDictionary alloc] init]; - chatMap = [[OFMutableDictionary alloc] init]; - presences = [[OFCountedSet alloc] init]; + _contactMap = [[OFMutableDictionary alloc] init]; + _client = [client retain]; + + [_client.contactManager addDelegate: self]; + + builder = gtk_builder_new(); + gtk_builder_add_from_file(builder, "data/gtk/roster.ui", NULL); + gtk_builder_connect_signals(builder, NULL); - builder = g_object_ref(builder_); + _roster_window = + GTK_WIDGET(gtk_builder_get_object(builder, "RosterWindow")); - roster_model = GTK_TREE_STORE(gtk_builder_get_object(builder, + gtk_widget_show(_roster_window); + + _roster_model = GTK_TREE_STORE(gtk_builder_get_object(builder, "RosterTreeStore")); - roster_filter = GTK_TREE_MODEL_FILTER( + _roster_filter = GTK_TREE_MODEL_FILTER( gtk_builder_get_object(builder, "RosterTreeModelFilter")); - gtk_tree_model_filter_set_visible_func(roster_filter, - filter_roster_by_presence, presences, NULL); + gtk_tree_model_filter_set_visible_func(_roster_filter, + filter_roster_by_presence, NULL, NULL); + + roster_view = GTK_TREE_VIEW(gtk_builder_get_object(builder, + "RosterTreeView")); + + g_signal_connect(roster_view, "row_activated", + G_CALLBACK(roster_row_activated), client); + + _presence_combo = GTK_COMBO_BOX(gtk_builder_get_object(builder, + "PresenceComboBox")); + + _presence_combo_changed_handler_id = + g_signal_connect(_presence_combo, "changed", + G_CALLBACK(presence_changed), client.connection); + + g_object_unref(G_OBJECT(builder)); } @catch (id e) { [self release]; @throw e; @@ -62,264 +131,289 @@ static gboolean filter_roster_by_presence(GtkTreeModel *model, - (void)dealloc { - [groupMap release]; - [contactMap release]; - [presences release]; - - if (roster_model) - g_object_unref(roster_model); + [_client.contactManager removeDelegate: self]; + [_groupMap release]; + [_contactMap release]; + [_client release]; - if (roster_filter) - g_object_unref(roster_filter); - - g_object_unref(builder); + gtk_widget_destroy(_roster_window); [super dealloc]; } -/* Presence handling */ -static gboolean refilter_roster(gpointer data) -{ - gtk_tree_model_filter_refilter(data); - - return FALSE; -} - -- (void)connection: (XMPPConnection*)connection - didReceivePresence: (XMPPPresence*)presence +- (void)contact: (XMPPContact*)contact + didSendMessage: (XMPPMessage*)message { - if ([presence.type isEqual: @"available"]) - [presences addObject: [presence.from bareJID]]; - else if ([presence.type isEqual: @"unavailable"]) - [presences removeObject: [presence.from bareJID]]; + if (message.body == nil || ![message.type isEqual: @"chat"]) + return; - g_idle_add(refilter_roster, roster_filter); -} - -// FIXME: This needs to move somewhere else -- (void)connection: (XMPPConnection*)connection - didReceiveMessage: (XMPPMessage*)message -{ - JubGtkChatUI *chat = [chatMap objectForKey: [message.from bareJID]]; - if (chat == nil) { - OFString * title = [@"Chat with " stringByAppendingString: - [message.from bareJID]]; - chat = [JubGtkChatUI alloc]; - [[chat initWithTitle: title - sendBlock: ^(OFString *text) { - XMPPMessage *msg = - [XMPPMessage messageWithType: @"chat"]; - msg.to = message.from; - msg.body = text; - [connection sendStanza: msg]; - - [chat addMessage: msg.body - sender: [message.to bareJID]]; - }] autorelease]; - - [chatMap setObject: chat - forKey: [message.from bareJID]]; - } + id chat = [_client chatForContact: contact]; [chat addMessage: message.body sender: [message.from bareJID]]; } /* Roster Delegate methods */ -struct add_roster_item_param { - OFString *group; - OFString *name; - OFString *jid; - OFMapTable *groupMap; - OFMapTable *contactRows; - GtkTreeStore *roster_model; -}; - -static gboolean add_roster_item(gpointer user_data) +- (void)Jub_addRosterItem: (XMPPRosterItem*)item + group: (OFString*)group { - struct add_roster_item_param *params = user_data; - GtkTreeIter group_iter, contact_iter; - GtkTreeRowReference *group_ref, *contact_ref; - GtkTreePath *group_path, *contact_path; + g_idle_add_block(^{ + GtkTreeIter group_iter, contact_iter; + GtkTreeRowReference *group_ref, *contact_ref; + GtkTreePath *group_path, *contact_path; + OFString *bareJID = [item.JID bareJID]; + OFMapTable *contactRows; + + if (!(contactRows = [_contactMap objectForKey: bareJID])) { + contactRows = [OFMapTable + mapTableWithKeyFunctions: keyFunctions + valueFunctions: rowRefFunctions]; + + [_contactMap setObject: contactRows + forKey: bareJID]; + } - group_ref = [params->groupMap valueForKey: params->group]; + group_ref = [_groupMap valueForKey: group]; - if (!group_ref) { - // Create new group row - gtk_tree_store_append(params->roster_model, &group_iter, NULL); - gtk_tree_store_set(params->roster_model, &group_iter, - 0, [params->group UTF8String], -1); + if (!group_ref) { + // Create new group row + gtk_tree_store_append(_roster_model, &group_iter, NULL); + gtk_tree_store_set(_roster_model, &group_iter, + 0, [group UTF8String], -1); - group_path = gtk_tree_model_get_path(GTK_TREE_MODEL( - params->roster_model), &group_iter); + group_path = gtk_tree_model_get_path(GTK_TREE_MODEL( + _roster_model), &group_iter); - group_ref = gtk_tree_row_reference_new(GTK_TREE_MODEL( - params->roster_model), group_path); + group_ref = gtk_tree_row_reference_new(GTK_TREE_MODEL( + _roster_model), group_path); - [params->groupMap setValue: group_ref - forKey: params->group]; - } else { - // Get iter for existing group row - group_path = gtk_tree_row_reference_get_path(group_ref); + [_groupMap setValue: group_ref + forKey: group]; + } else { + // Get iter for existing group row + group_path = gtk_tree_row_reference_get_path(group_ref); - gtk_tree_model_get_iter(GTK_TREE_MODEL(params->roster_model), - &group_iter, group_path); - } - gtk_tree_path_free(group_path); + gtk_tree_model_get_iter(GTK_TREE_MODEL(_roster_model), + &group_iter, group_path); + } + gtk_tree_path_free(group_path); + + // Create new contact row + gtk_tree_store_append(_roster_model, &contact_iter, + &group_iter); + if (item.name) + gtk_tree_store_set(_roster_model, &contact_iter, + 0, [item.name UTF8String], + 1, [bareJID UTF8String], + 2, "unavailable", -1); + else if (item.JID.node) + gtk_tree_store_set(_roster_model, &contact_iter, + 0, [item.JID.node UTF8String], + 1, [bareJID UTF8String], + 2, "unavailable", -1); + else + gtk_tree_store_set(_roster_model, &contact_iter, + 0, [item.JID.domain UTF8String], + 1, [bareJID UTF8String], + 2, "unavailable", -1); - // Create new contact row - gtk_tree_store_append(params->roster_model, &contact_iter, &group_iter); - gtk_tree_store_set(params->roster_model, &contact_iter, - 0, [params->name UTF8String], 1, [params->jid UTF8String], -1); + contact_path = gtk_tree_model_get_path(GTK_TREE_MODEL( + _roster_model), &contact_iter); - contact_path = gtk_tree_model_get_path(GTK_TREE_MODEL( - params->roster_model), &contact_iter); + contact_ref = gtk_tree_row_reference_new(GTK_TREE_MODEL( + _roster_model), contact_path); - contact_ref = gtk_tree_row_reference_new(GTK_TREE_MODEL( - params->roster_model), contact_path); + gtk_tree_path_free(contact_path); - gtk_tree_path_free(contact_path); + [contactRows setValue: contact_ref + forKey: group]; + }); +} - [params->contactRows setValue: contact_ref - forKey: params->group]; +- (void)Jub_removeRosterItem: (XMPPRosterItem*)item + group: (OFString*)group +{ + g_idle_add_block(^{ + GtkTreeIter contact_iter, group_iter; + GtkTreePath *contact_path, *group_path; + GtkTreeRowReference *contact_ref, *group_ref; + OFString *bareJID = [item.JID bareJID]; + OFMapTable *contactRows = [_contactMap objectForKey: bareJID]; + + contact_ref = [contactRows valueForKey: group]; + contact_path = gtk_tree_row_reference_get_path(contact_ref); + gtk_tree_model_get_iter(GTK_TREE_MODEL(_roster_model), + &contact_iter, contact_path); + gtk_tree_path_free(contact_path); + + gtk_tree_store_remove(_roster_model, &contact_iter); + + group_ref = [_groupMap valueForKey: group]; + group_path = gtk_tree_row_reference_get_path(group_ref); + gtk_tree_model_get_iter(GTK_TREE_MODEL(_roster_model), + &group_iter, group_path); - [params->group release]; - [params->name release]; - [params->jid release]; - [params->groupMap release]; - [params->contactRows release]; - g_object_unref(params->roster_model); - free(params); + if (!gtk_tree_model_iter_has_child(GTK_TREE_MODEL( + _roster_model), &group_iter)) { + gtk_tree_store_remove(_roster_model, &group_iter); + [_groupMap removeValueForKey: group]; + } - return FALSE; -} + gtk_tree_path_free(group_path); -struct remove_roster_item_param { - OFString *group; - OFMapTable *groupMap; - OFMapTable *contactRows; - GtkTreeStore *roster_model; -}; + [contactRows removeValueForKey: group]; + if([contactRows count] == 0) + [_contactMap removeObjectForKey: bareJID]; + }); +} -static gboolean remove_roster_item(gpointer user_data) +- (void)contactManager: (XMPPContactManager*)manager + didAddContact: (XMPPContact*)contact { - struct remove_roster_item_param *params = user_data; - GtkTreeIter contact_iter, group_iter; - GtkTreePath *contact_path, *group_path; - GtkTreeRowReference *contact_ref, *group_ref; - - contact_ref = [params->contactRows valueForKey: params->group]; - contact_path = gtk_tree_row_reference_get_path(contact_ref); - gtk_tree_model_get_iter(GTK_TREE_MODEL(params->roster_model), - &contact_iter, contact_path); - - gtk_tree_store_remove(params->roster_model, &contact_iter); - - group_ref = [params->groupMap valueForKey: params->group]; - group_path = gtk_tree_row_reference_get_path(group_ref); - gtk_tree_model_get_iter(GTK_TREE_MODEL(params->roster_model), - &group_iter, group_path); - - if (!gtk_tree_model_iter_has_child(GTK_TREE_MODEL(params->roster_model), - &group_iter)) { - gtk_tree_store_remove(params->roster_model, &group_iter); - [params->groupMap removeValueForKey: params->group]; - } - - gtk_tree_path_free(group_path); + XMPPRosterItem *rosterItem = contact.rosterItem; + OFArray *groups = rosterItem.groups;; - [params->group release]; - [params->groupMap release]; - [params->contactRows release]; - g_object_unref(params->roster_model); - free(params); + if (groups == nil) + groups = @[ @"General" ]; - return FALSE; + for (OFString *group in groups) + [self Jub_addRosterItem: rosterItem + group: group]; } -- (void)rosterWasReceived: (XMPPRoster*)roster_ +- (void)contactManager: (XMPPContactManager*)manager + didRemoveContact: (XMPPContact*)contact { - of_log(@"Handling roster"); - [[roster_ rosterItems] enumerateKeysAndObjectsUsingBlock: - ^(OFString *bareJID, XMPPRosterItem *item, BOOL *stop) { - OFArray *groups; - OFMapTable *contactRows = [OFMapTable - mapTableWithKeyFunctions: keyFunctions - valueFunctions: rowRefFunctions]; - - [contactMap setObject: contactRows - forKey: bareJID]; - - if (item.groups != nil) - groups = item.groups; - else - groups = @[@"General"]; - - for (OFString *group in groups) { - struct add_roster_item_param *params = - malloc(sizeof(*params)); - params->group = [group retain]; - params->name = [item.name retain]; - params->jid = [bareJID retain]; - params->groupMap = [groupMap retain]; - params->contactRows = [contactRows retain]; - params->roster_model = g_object_ref(roster_model); - g_idle_add(add_roster_item, params); - } - }]; + XMPPRosterItem *rosterItem = contact.rosterItem; + OFArray *groups = rosterItem.groups; + + if (groups == nil) + groups = @[ @"General" ]; + + for (OFString *group in groups) + [self Jub_removeRosterItem: rosterItem + group: group]; } -- (void)roster: (XMPPRoster*)roster_ - didReceiveRosterItem: (XMPPRosterItem*)item +- (void)contact: (XMPPContact*)contact + willUpdateWithRosterItem: (XMPPRosterItem*)rosterItem; { - OFArray *groups; - XMPPRosterItem *oldItem = - [roster_.rosterItems objectForKey: [item.JID bareJID]]; + // Remove contact from old set of groups + XMPPRosterItem *oldItem = contact.rosterItem; + OFArray *groups = oldItem.groups; - if (oldItem) { - if (oldItem.groups != nil) - groups = oldItem.groups; - else - groups = @[@"General"]; - - for (OFString *group in groups) { - struct remove_roster_item_param *params = - malloc(sizeof(*params)); - params->group = [group retain]; - params->contactRows = [[contactMap objectForKey: - [oldItem.JID bareJID]] retain]; - params->groupMap = [groupMap retain]; - params->roster_model = g_object_ref(roster_model); - g_idle_add(remove_roster_item, params); - } + if (groups == nil) + groups = @[ @"General" ]; - [contactMap removeObjectForKey: [item.JID bareJID]]; - } + for (OFString *group in groups) + [self Jub_removeRosterItem: oldItem + group: group]; - if (![item.subscription isEqual: @"remove"]) { - OFMapTable *contactRows = [OFMapTable - mapTableWithKeyFunctions: keyFunctions - valueFunctions: rowRefFunctions]; + // Add contact to new set of groups + groups = rosterItem.groups; - [contactMap setObject: contactRows - forKey: [item.JID bareJID]]; + if (groups == nil) + groups = @[ @"General" ]; - if (item.groups != nil) - groups = item.groups; - else - groups = @[@"General"]; - - for (OFString *group in groups) { - struct add_roster_item_param *params = - malloc(sizeof(*params)); - params->group = [group retain]; - params->name = [item.name retain]; - params->jid = [[item.JID bareJID] retain]; - params->groupMap = [groupMap retain]; - params->contactRows = [contactRows retain]; - params->roster_model = g_object_ref(roster_model); - g_idle_add(add_roster_item, params); + for (OFString *group in groups) + [self Jub_addRosterItem: rosterItem + group: group]; +} + +- (void)contact: (XMPPContact*)contact + didSendPresence: (XMPPPresence*)presence +{ + OFDictionary *allPresences = [contact presences]; + XMPPPresence *highPresence = [[[allPresences allObjects] sortedArray] + lastObject]; + OFMutableString *tooltip = + [OFMutableString stringWithString: @"Resources:"]; + + [allPresences enumerateKeysAndObjectsUsingBlock: + ^(OFString *resource, XMPPPresence *pres, BOOL *stop) { + [tooltip appendString: @"\n"]; + [tooltip appendString: resource]; + if ([pres.type isEqual: @"available"]) { + if (pres.show != nil) + [tooltip appendFormat: @" (%@)", pres.show]; + else + [tooltip appendString: @" (available)"]; + } else + [tooltip appendString: @" (unavailable)"]; + + if (pres.status) + [tooltip appendFormat: @": %@", pres.status]; + }]; + + g_idle_add_block(^{ + GtkTreeIter iter; + GtkTreePath *path; + GtkTreeRowReference *ref; + OFString *bareJID = [contact.rosterItem.JID bareJID]; + OFMapTable *contactRows = [_contactMap objectForKey: bareJID]; + + for (OFString *group in contact.rosterItem.groups) { + ref = [contactRows valueForKey: group]; + path = gtk_tree_row_reference_get_path(ref); + gtk_tree_model_get_iter(GTK_TREE_MODEL(_roster_model), + &iter, path); + gtk_tree_path_free(path); + + if ([highPresence.type isEqual: @"available"]) { + if (highPresence.show != nil) + gtk_tree_store_set(_roster_model, &iter, + 2, [highPresence.show UTF8String], + -1); + else + gtk_tree_store_set(_roster_model, &iter, + 2, "available", -1); + } else + gtk_tree_store_set(_roster_model, &iter, + 2, "unavailable", -1); + + gtk_tree_store_set(_roster_model, &iter, + 3, [tooltip UTF8String], -1); } - } + + gtk_tree_model_filter_refilter(_roster_filter); + }); } +- (void)client: (JubChatClient*)client_ + didChangePresence: (XMPPPresence*)presence +{ + OFString *tooltip = @""; + OFString *show = + [[presence elementForName: @"show" + namespace: XMPP_NS_CLIENT] stringValue]; + OFString *status = + [[presence elementForName: @"status" + namespace: XMPP_NS_CLIENT] stringValue]; + + if (status != nil) + tooltip = [@"Status: " stringByAppendingString: status]; + + g_idle_add_block(^{ + // Block the PresenceComboBox's changed handler, so it doesn't + // fire and resend presence + g_signal_handler_block(_presence_combo, + _presence_combo_changed_handler_id); + + if ([presence.type isEqual: @"unavailable"]) + gtk_combo_box_set_active_id(_presence_combo, + "unavailable"); + else if (show == nil) + gtk_combo_box_set_active_id(_presence_combo, + "available"); + else + gtk_combo_box_set_active_id(_presence_combo, + [show UTF8String]); + + // Unblock the changed handler + g_signal_handler_unblock(_presence_combo, + _presence_combo_changed_handler_id); + + gtk_widget_set_tooltip_markup(GTK_WIDGET(_presence_combo), + [tooltip UTF8String]); + }); +} @end