]> cgit.babelmonkeys.de Git - jubjub.git/blob - src/gui/gtk/JubGtkRosterUI.m
4102e3432e3cf0307ffb90c9ff4908a613b1d4a2
[jubjub.git] / src / gui / gtk / JubGtkRosterUI.m
1 #import <ObjXMPP/namespaces.h>
2 #include <string.h>
3
4 #import "JubGtkRosterUI.h"
5 #import "JubGObjectMap.h"
6 #import "JubGtkChatUI.h"
7 #import "JubGtkHelper.h"
8
9 static void roster_row_activated(GtkTreeView *tree_view, GtkTreePath *path,
10     GtkTreeViewColumn *column, gpointer data)
11 {
12         JubGtkRosterUI *roster = data;
13         GtkTreeIter row_iter;
14         GtkTreeModel *tree_model;
15         gchar *jid_s;
16         XMPPJID *jid;
17         OFAutoreleasePool *pool;
18
19         tree_model = gtk_tree_view_get_model(tree_view);
20         gtk_tree_model_get_iter(tree_model, &row_iter, path);
21         gtk_tree_model_get(tree_model, &row_iter, 1, &jid_s, -1);
22
23         // This was a group row
24         if (!jid_s) return;
25
26         pool = [OFAutoreleasePool new];
27         jid = [XMPPJID JIDWithString: [OFString stringWithUTF8String: jid_s]];
28
29         [roster performSelectorOnMainThread: @selector(chatForJID:)
30                                  withObject: jid
31                               waitUntilDone: NO];
32         [pool release];
33 }
34
35 static void presence_changed(GtkComboBox *combo_box, gpointer data)
36 {
37         XMPPPresence *pres;
38         XMPPConnection *connection = data;
39         OFAutoreleasePool *pool = [OFAutoreleasePool new];
40
41         const char *status = gtk_combo_box_get_active_id(combo_box);
42
43         if (!strcmp(status, "unavailable"))
44                 pres = [XMPPPresence presenceWithType: @"unavailable"];
45         else {
46                 pres = [XMPPPresence presence];
47                 if (strcmp(status, "available"))
48                         [pres setShow: @(status)];
49         }
50
51         [connection sendStanza: pres];
52
53         [pool release];
54 }
55
56 static gboolean filter_roster_by_presence(GtkTreeModel *model,
57     GtkTreeIter *iter, gpointer data)
58 {
59         char *status;
60         gtk_tree_model_get(model, iter, 2, &status, -1);
61
62         // Groups have no status
63         if (!status)
64                 return TRUE;
65
66         if (!strcmp(status, "unavailable"))
67                 return FALSE;
68
69         return TRUE;
70 }
71
72 @implementation JubGtkRosterUI
73 - initWithClient: (JubChatClient*)client_
74 {
75         self = [super init];
76
77         @try {
78                 GtkTreeView *roster_view;
79                 GtkBuilder *builder;
80
81                 groupMap = [[OFMapTable alloc]
82                     initWithKeyFunctions: keyFunctions
83                           valueFunctions: rowRefFunctions];
84                 contactMap = [[OFMutableDictionary alloc] init];
85                 chatMap = [[OFMutableDictionary alloc] init];
86                 client = [client_ retain];
87
88                 [client.contactManager addDelegate: self];
89
90                 builder = gtk_builder_new();
91                 gtk_builder_add_from_file(builder, "data/gtk/roster.ui", NULL);
92                 gtk_builder_connect_signals(builder, NULL);
93
94                 roster_window =
95                     GTK_WIDGET(gtk_builder_get_object(builder, "RosterWindow"));
96
97                 gtk_widget_show(roster_window);
98
99                 roster_model = GTK_TREE_STORE(gtk_builder_get_object(builder,
100                     "RosterTreeStore"));
101
102                 roster_filter = GTK_TREE_MODEL_FILTER(
103                     gtk_builder_get_object(builder, "RosterTreeModelFilter"));
104
105                 gtk_tree_model_filter_set_visible_func(roster_filter,
106                     filter_roster_by_presence, NULL, NULL);
107
108                 roster_view = GTK_TREE_VIEW(gtk_builder_get_object(builder,
109                         "RosterTreeView"));
110
111                 g_signal_connect(roster_view, "row_activated",
112                     G_CALLBACK(roster_row_activated), self);
113
114                 presence_combo = GTK_COMBO_BOX(gtk_builder_get_object(builder,
115                         "PresenceComboBox"));
116
117                 presence_combo_changed_handler_id =
118                     g_signal_connect(presence_combo, "changed",
119                         G_CALLBACK(presence_changed), client.connection);
120
121                 g_object_unref(G_OBJECT(builder));
122         } @catch (id e) {
123                 [self release];
124                 @throw e;
125         }
126
127         return self;
128 }
129
130 - (void)dealloc
131 {
132         [client.contactManager removeDelegate: self];
133         [groupMap release];
134         [contactMap release];
135         [chatMap release];
136         [client release];
137
138         gtk_widget_destroy(roster_window);
139
140         [super dealloc];
141 }
142
143 // FIXME: This needs to move somewhere else
144 - (JubGtkChatUI*)chatForJID: (XMPPJID*)jid
145 {
146         OFAutoreleasePool *pool = [OFAutoreleasePool new];
147         JubGtkChatUI *chat =
148             [chatMap objectForKey: [jid bareJID]];
149         if (chat == nil) {
150                 OFString * title = [@"Chat with " stringByAppendingString:
151                     [jid bareJID]];
152
153                 chat = [[[JubGtkChatUI alloc]
154                     initWithTitle: title
155                        closeBlock: ^{
156                                 [chatMap removeObjectForKey: [jid bareJID]];
157                         }
158                         sendBlock: ^(OFString *text) {
159                                 XMPPMessage *msg =
160                                     [XMPPMessage messageWithType: @"chat"];
161                                 msg.to = jid;
162                                 msg.body = text;
163                                 [client.connection sendStanza: msg];
164                         }
165                 ] autorelease];
166
167                 [chatMap setObject: chat
168                             forKey: [jid bareJID]];
169         }
170
171         [pool release];
172
173         return chat;
174 }
175
176 -  (void)contact: (XMPPContact*)contact
177   didSendMessage: (XMPPMessage*)message
178 {
179         if (message.body == nil || ![message.type isEqual: @"chat"])
180                 return;
181
182         JubGtkChatUI *chat = [self chatForJID: message.from];
183         [chat addMessage: message.body
184                   sender: [message.from bareJID]];
185 }
186
187 /* Roster Delegate methods */
188 - (void)Jub_addRosterItem: (XMPPRosterItem*)item
189                     group: (OFString*)group
190 {
191         g_idle_add_block(^{
192                 GtkTreeIter group_iter, contact_iter;
193                 GtkTreeRowReference *group_ref, *contact_ref;
194                 GtkTreePath *group_path, *contact_path;
195                 OFString *bareJID = [item.JID bareJID];
196                 OFMapTable *contactRows;
197
198                 if (!(contactRows = [contactMap objectForKey: bareJID])) {
199                         contactRows = [OFMapTable
200                             mapTableWithKeyFunctions: keyFunctions
201                                       valueFunctions: rowRefFunctions];
202
203                         [contactMap setObject: contactRows
204                                        forKey: bareJID];
205                 }
206
207                 group_ref = [groupMap valueForKey: group];
208
209                 if (!group_ref) {
210                         // Create new group row
211                         gtk_tree_store_append(roster_model, &group_iter, NULL);
212                         gtk_tree_store_set(roster_model, &group_iter,
213                             0, [group UTF8String], -1);
214
215                         group_path = gtk_tree_model_get_path(GTK_TREE_MODEL(
216                                 roster_model), &group_iter);
217
218                         group_ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(
219                                 roster_model), group_path);
220
221                         [groupMap setValue: group_ref
222                                     forKey: group];
223                 } else {
224                         // Get iter for existing group row
225                         group_path = gtk_tree_row_reference_get_path(group_ref);
226
227                         gtk_tree_model_get_iter(GTK_TREE_MODEL(roster_model),
228                             &group_iter, group_path);
229                 }
230                 gtk_tree_path_free(group_path);
231
232                 // Create new contact row
233                 gtk_tree_store_append(roster_model, &contact_iter, &group_iter);
234                 if (item.name)
235                         gtk_tree_store_set(roster_model, &contact_iter,
236                             0, [item.name UTF8String],
237                             1, [bareJID UTF8String],
238                             2, "unavailable", -1);
239                 else
240                         gtk_tree_store_set(roster_model, &contact_iter,
241                             0, [item.JID.node UTF8String],
242                             1, [bareJID UTF8String],
243                             2, "unavailable", -1);
244
245                 contact_path = gtk_tree_model_get_path(GTK_TREE_MODEL(
246                         roster_model), &contact_iter);
247
248                 contact_ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(
249                         roster_model), contact_path);
250
251                 gtk_tree_path_free(contact_path);
252
253                 [contactRows setValue: contact_ref
254                                forKey: group];
255         });
256 }
257
258 - (void)Jub_removeRosterItem: (XMPPRosterItem*)item
259                        group: (OFString*)group
260 {
261         g_idle_add_block(^{
262                 GtkTreeIter contact_iter, group_iter;
263                 GtkTreePath *contact_path, *group_path;
264                 GtkTreeRowReference *contact_ref, *group_ref;
265                 OFString *bareJID = [item.JID bareJID];
266                 OFMapTable *contactRows = [contactMap objectForKey: bareJID];
267
268                 contact_ref = [contactRows valueForKey: group];
269                 contact_path = gtk_tree_row_reference_get_path(contact_ref);
270                 gtk_tree_model_get_iter(GTK_TREE_MODEL(roster_model),
271                     &contact_iter, contact_path);
272                 gtk_tree_path_free(contact_path);
273
274                 gtk_tree_store_remove(roster_model, &contact_iter);
275
276                 group_ref = [groupMap valueForKey: group];
277                 group_path = gtk_tree_row_reference_get_path(group_ref);
278                 gtk_tree_model_get_iter(GTK_TREE_MODEL(roster_model),
279                     &group_iter, group_path);
280
281                 if (!gtk_tree_model_iter_has_child(GTK_TREE_MODEL(roster_model),
282                     &group_iter)) {
283                         gtk_tree_store_remove(roster_model, &group_iter);
284                         [groupMap removeValueForKey: group];
285                 }
286
287                 gtk_tree_path_free(group_path);
288
289                 [contactRows removeValueForKey: group];
290                 if([contactRows count] == 0)
291                         [contactMap removeObjectForKey: bareJID];
292         });
293 }
294
295 - (void)contactManager: (XMPPContactManager*)manager
296          didAddContact: (XMPPContact*)contact
297 {
298         XMPPRosterItem *rosterItem = contact.rosterItem;
299         OFArray *groups = rosterItem.groups;;
300
301         if (groups == nil)
302                 groups = @[ @"General" ];
303
304         for (OFString *group in groups)
305                 [self Jub_addRosterItem: rosterItem
306                                   group: group];
307 }
308
309 - (void)contactManager: (XMPPContactManager*)manager
310       didRemoveContact: (XMPPContact*)contact
311 {
312         XMPPRosterItem *rosterItem = contact.rosterItem;
313         OFArray *groups = rosterItem.groups;
314
315         if (groups == nil)
316                 groups = @[ @"General" ];
317
318         for (OFString *group in groups)
319                 [self Jub_removeRosterItem: rosterItem
320                                      group: group];
321 }
322
323 -            (void)contact: (XMPPContact*)contact
324   willUpdateWithRosterItem: (XMPPRosterItem*)rosterItem;
325 {
326         // Remove contact from old set of groups
327         XMPPRosterItem *oldItem = contact.rosterItem;
328         OFArray *groups = oldItem.groups;
329
330         if (groups == nil)
331                 groups = @[ @"General" ];
332
333         for (OFString *group in groups)
334                 [self Jub_removeRosterItem: oldItem
335                                      group: group];
336
337         // Add contact to new set of groups
338         groups = rosterItem.groups;
339
340         if (groups == nil)
341                 groups = @[ @"General" ];
342
343         for (OFString *group in groups)
344                 [self Jub_addRosterItem: rosterItem
345                                   group: group];
346 }
347
348 -   (void)contact: (XMPPContact*)contact
349   didSendPresence: (XMPPPresence*)presence
350 {
351         g_idle_add_block(^{
352                 GtkTreeIter iter;
353                 GtkTreePath *path;
354                 GtkTreeRowReference *ref;
355                 OFString *bareJID = [contact.rosterItem.JID bareJID];
356                 OFMapTable *contactRows = [contactMap objectForKey: bareJID];
357
358                 for (OFString *group in contact.rosterItem.groups) {
359                         ref = [contactRows valueForKey: group];
360                         path = gtk_tree_row_reference_get_path(ref);
361                         gtk_tree_model_get_iter(GTK_TREE_MODEL(roster_model),
362                             &iter, path);
363                         gtk_tree_path_free(path);
364
365                         if ([presence.type isEqual: @"available"])
366                                 if (presence.show != nil)
367                                         gtk_tree_store_set(roster_model, &iter,
368                                             2, [presence.show UTF8String], -1);
369                                 else
370                                         gtk_tree_store_set(roster_model, &iter,
371                                             2, "available", -1);
372                         else if ([presence.type isEqual: @"unavailable"])
373                                 gtk_tree_store_set(roster_model, &iter,
374                                     2, "unavailable", -1);
375
376                         gtk_tree_store_set(roster_model, &iter,
377                             3, [presence.status UTF8String], -1);
378                 }
379
380                 gtk_tree_model_filter_refilter(roster_filter);
381         });
382 }
383
384 -      (void)client: (JubChatClient*)client_
385   didChangePresence: (XMPPPresence*)presence
386 {
387         OFString *tooltip = @"";
388         OFString *show =
389             [[presence elementForName: @"show"
390                             namespace: XMPP_NS_CLIENT] stringValue];
391         OFString *status =
392             [[presence elementForName: @"status"
393                             namespace: XMPP_NS_CLIENT] stringValue];
394
395         if (status != nil)
396                 tooltip = [@"<b>Status:</b> " stringByAppendingString: status];
397
398         g_idle_add_block(^{
399                 // Block the PresenceComboBox's changed handler, so it doesn't
400                 // fire and resend presence
401                 g_signal_handler_block(presence_combo,
402                     presence_combo_changed_handler_id);
403
404                 if ([presence.type isEqual: @"unavailable"])
405                         gtk_combo_box_set_active_id(presence_combo,
406                             "unavailable");
407                 else if (show == nil)
408                         gtk_combo_box_set_active_id(presence_combo,
409                             "available");
410                 else
411                         gtk_combo_box_set_active_id(presence_combo,
412                             [show UTF8String]);
413
414                 // Unblock the changed handler
415                 g_signal_handler_unblock(presence_combo,
416                     presence_combo_changed_handler_id);
417
418                 gtk_widget_set_tooltip_markup(GTK_WIDGET(presence_combo),
419                     [tooltip UTF8String]);
420         });
421 }
422 @end