]> cgit.babelmonkeys.de Git - jubjub.git/blob - src/gui/gtk/JubGtkRosterUI.m
Fix return types for signal handlers
[jubjub.git] / src / gui / gtk / JubGtkRosterUI.m
1 #import "JubGtkRosterUI.h"
2 #import "JubGObjectMap.h"
3 #import "JubGtkChatUI.h"
4 #import "JubGtkHelper.h"
5
6 #include <string.h>
7
8 static void roster_row_activated(GtkTreeView *tree_view, GtkTreePath *path,
9     GtkTreeViewColumn *column, gpointer data)
10 {
11         JubGtkRosterUI *roster = data;
12         GtkTreeIter row_iter;
13         GtkTreeModel *tree_model;
14         gchar *jid_s;
15         XMPPJID *jid;
16         OFAutoreleasePool *pool;
17
18         tree_model = gtk_tree_view_get_model(tree_view);
19         gtk_tree_model_get_iter(tree_model, &row_iter, path);
20         gtk_tree_model_get(tree_model, &row_iter, 1, &jid_s, -1);
21
22         // This was a group row
23         if (!jid_s) return;
24
25         pool = [OFAutoreleasePool new];
26         jid = [XMPPJID JIDWithString: [OFString stringWithUTF8String: jid_s]];
27
28         [roster performSelectorOnMainThread: @selector(chatForJID:)
29                                  withObject: jid
30                               waitUntilDone: NO];
31         [pool release];
32 }
33
34 static void presence_changed(GtkComboBox *combo_box, gpointer data)
35 {
36         XMPPPresence *pres;
37         XMPPConnection *connection = data;
38         OFAutoreleasePool *pool = [OFAutoreleasePool new];
39
40         const char *status = gtk_combo_box_get_active_id(combo_box);
41
42         if (!strcmp(status, "unavailable"))
43                 pres = [XMPPPresence presenceWithType: @"unavailable"];
44         else {
45                 pres = [XMPPPresence presence];
46                 if (strcmp(status, "available"))
47                         [pres addShow: @(status)];
48         }
49
50         [connection sendStanza: pres];
51
52         [pool release];
53 }
54
55 static gboolean filter_roster_by_presence(GtkTreeModel *model,
56     GtkTreeIter *iter, gpointer data)
57 {
58         char *jid_s;
59         OFString *jid;
60         OFCountedSet *presences = data;
61
62         gtk_tree_model_get(model, iter, 1, &jid_s, -1);
63
64         // Groups have no JID
65         if (!jid_s)
66                 return TRUE;
67
68         jid = [[OFString alloc] initWithUTF8String: jid_s];
69
70         g_free(jid_s);
71
72         int num = [presences countForObject: jid];
73         if (num) {
74                 [jid release];
75                 return TRUE;
76         } else {
77                 [jid release];
78                 return FALSE;
79         }
80 }
81
82 @implementation JubGtkRosterUI
83 - initWithBuilder: (GtkBuilder*)builder_
84        connection: (XMPPConnection*)connection_
85 {
86         self = [super init];
87
88         @try {
89                 GtkTreeView *roster_view;
90                 GtkComboBox *presence_combo;
91
92                 groupMap = [[OFMapTable alloc]
93                     initWithKeyFunctions: keyFunctions
94                           valueFunctions: rowRefFunctions];
95                 contactMap = [[OFMutableDictionary alloc] init];
96                 chatMap = [[OFMutableDictionary alloc] init];
97                 presences = [[OFCountedSet alloc] init];
98                 connection = [connection_ retain];
99
100                 builder = g_object_ref(builder_);
101
102                 roster_model = GTK_TREE_STORE(gtk_builder_get_object(builder,
103                     "RosterTreeStore"));
104
105                 roster_filter = GTK_TREE_MODEL_FILTER(
106                     gtk_builder_get_object(builder, "RosterTreeModelFilter"));
107
108                 gtk_tree_model_filter_set_visible_func(roster_filter,
109                     filter_roster_by_presence, presences, NULL);
110
111                 roster_view = GTK_TREE_VIEW(gtk_builder_get_object(builder,
112                         "RosterTreeView"));
113
114                 g_signal_connect(roster_view, "row_activated",
115                     G_CALLBACK(roster_row_activated), self);
116
117                 presence_combo = GTK_COMBO_BOX(gtk_builder_get_object(builder,
118                         "PresenceComboBox"));
119
120                 g_signal_connect(presence_combo, "changed",
121                     G_CALLBACK(presence_changed), connection);
122         } @catch (id e) {
123                 [self release];
124                 @throw e;
125         }
126
127         return self;
128 }
129
130 - (void)dealloc
131 {
132         [groupMap release];
133         [contactMap release];
134         [presences release];
135         [connection release];
136
137         if (roster_model)
138                 g_object_unref(roster_model);
139
140         if (roster_filter)
141                 g_object_unref(roster_filter);
142
143         g_object_unref(builder);
144
145         [super dealloc];
146 }
147
148 /* Presence handling */
149 -   (void)connection: (XMPPConnection*)connection
150   didReceivePresence: (XMPPPresence*)presence
151 {
152         if ([presence.type isEqual: @"available"])
153                 [presences addObject: [presence.from bareJID]];
154         else if ([presence.type isEqual: @"unavailable"])
155                 [presences removeObject: [presence.from bareJID]];
156
157         g_idle_add_block(^{
158                 gtk_tree_model_filter_refilter(roster_filter);
159         });
160 }
161
162 // FIXME: This needs to move somewhere else
163 - (JubGtkChatUI*)chatForJID: (XMPPJID*)jid
164 {
165         OFAutoreleasePool *pool = [OFAutoreleasePool new];
166         JubGtkChatUI *chat =
167             [chatMap objectForKey: [jid bareJID]];
168         if (chat == nil) {
169                 OFString * title = [@"Chat with " stringByAppendingString:
170                     [jid bareJID]];
171
172                 chat = [[[JubGtkChatUI alloc]
173                     initWithTitle: title
174                        closeBlock: ^{
175                                 [chatMap removeObjectForKey: [jid bareJID]];
176                         }
177                         sendBlock: ^(OFString *text) {
178                                 XMPPMessage *msg =
179                                     [XMPPMessage messageWithType: @"chat"];
180                                 msg.to = jid;
181                                 msg.body = text;
182                                 [connection sendStanza: msg];
183                         }
184                 ] autorelease];
185
186                 [chatMap setObject: chat
187                             forKey: [jid bareJID]];
188         }
189
190         [pool release];
191
192         return chat;
193 }
194
195 -  (void)connection: (XMPPConnection*)connection
196   didReceiveMessage: (XMPPMessage*)message
197 {
198         JubGtkChatUI *chat = [self chatForJID: message.from];
199         [chat addMessage: message.body
200                   sender: [message.from bareJID]];
201 }
202
203 /* Roster Delegate methods */
204 - (void)Jub_addRosterItem: (XMPPRosterItem*)item
205                     group: (OFString*)group
206 {
207         g_idle_add_block(^{
208                 GtkTreeIter group_iter, contact_iter;
209                 GtkTreeRowReference *group_ref, *contact_ref;
210                 GtkTreePath *group_path, *contact_path;
211                 OFMapTable *contactRows =
212                     [contactMap objectForKey: [item.JID bareJID]];
213
214                 group_ref = [groupMap valueForKey: group];
215
216                 if (!group_ref) {
217                         // Create new group row
218                         gtk_tree_store_append(roster_model, &group_iter, NULL);
219                         gtk_tree_store_set(roster_model, &group_iter,
220                             0, [group UTF8String], -1);
221
222                         group_path = gtk_tree_model_get_path(GTK_TREE_MODEL(
223                                 roster_model), &group_iter);
224
225                         group_ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(
226                                 roster_model), group_path);
227
228                         [groupMap setValue: group_ref
229                                     forKey: group];
230                 } else {
231                         // Get iter for existing group row
232                         group_path = gtk_tree_row_reference_get_path(group_ref);
233
234                         gtk_tree_model_get_iter(GTK_TREE_MODEL(roster_model),
235                             &group_iter, group_path);
236                 }
237                 gtk_tree_path_free(group_path);
238
239                 // Create new contact row
240                 gtk_tree_store_append(roster_model, &contact_iter, &group_iter);
241                 if (item.name)
242                         gtk_tree_store_set(roster_model, &contact_iter,
243                             0, [item.name UTF8String],
244                             1, [[item.JID bareJID] UTF8String], -1);
245                 else
246                         gtk_tree_store_set(roster_model, &contact_iter,
247                             0, [item.JID.node UTF8String],
248                             1, [[item.JID bareJID] UTF8String], -1);
249
250                 contact_path = gtk_tree_model_get_path(GTK_TREE_MODEL(
251                         roster_model), &contact_iter);
252
253                 contact_ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(
254                         roster_model), contact_path);
255
256                 gtk_tree_path_free(contact_path);
257
258                 [contactRows setValue: contact_ref
259                                forKey: group];
260         });
261 }
262
263 - (void)Jub_removeRosterItem: (XMPPRosterItem*)item
264                        group: (OFString*)group
265 {
266         g_idle_add_block(^{
267                 GtkTreeIter contact_iter, group_iter;
268                 GtkTreePath *contact_path, *group_path;
269                 GtkTreeRowReference *contact_ref, *group_ref;
270                 OFMapTable *contactRows =
271                     [contactMap objectForKey: [item.JID bareJID]];
272
273                 contact_ref = [contactRows valueForKey: group];
274                 contact_path = gtk_tree_row_reference_get_path(contact_ref);
275                 gtk_tree_model_get_iter(GTK_TREE_MODEL(roster_model),
276                     &contact_iter, contact_path);
277
278                 gtk_tree_store_remove(roster_model, &contact_iter);
279
280                 group_ref = [groupMap valueForKey: group];
281                 group_path = gtk_tree_row_reference_get_path(group_ref);
282                 gtk_tree_model_get_iter(GTK_TREE_MODEL(roster_model),
283                     &group_iter, group_path);
284
285                 if (!gtk_tree_model_iter_has_child(GTK_TREE_MODEL(roster_model),
286                     &group_iter)) {
287                         gtk_tree_store_remove(roster_model, &group_iter);
288                         [groupMap removeValueForKey: group];
289                 }
290
291                 gtk_tree_path_free(group_path);
292         });
293 }
294
295 - (void)rosterWasReceived: (XMPPRoster*)roster_
296 {
297         of_log(@"Handling roster");
298         [[roster_ rosterItems] enumerateKeysAndObjectsUsingBlock:
299             ^(OFString *bareJID, XMPPRosterItem *item, BOOL *stop) {
300                 OFArray *groups;
301                 OFMapTable *contactRows = [OFMapTable
302                     mapTableWithKeyFunctions: keyFunctions
303                               valueFunctions: rowRefFunctions];
304
305                 [contactMap setObject: contactRows
306                                forKey: bareJID];
307
308                 if (item.groups != nil)
309                         groups = item.groups;
310                 else
311                         groups = @[@"General"];
312
313                 for (OFString *group in groups)
314                         [self Jub_addRosterItem: item
315                                           group: group];
316         }];
317 }
318
319 -         (void)roster: (XMPPRoster*)roster_
320   didReceiveRosterItem: (XMPPRosterItem*)item
321 {
322         OFArray *groups;
323         XMPPRosterItem *oldItem =
324             [roster_.rosterItems objectForKey: [item.JID bareJID]];
325
326         if (oldItem) {
327                 if (oldItem.groups != nil)
328                         groups = oldItem.groups;
329                 else
330                         groups = @[@"General"];
331
332                 for (OFString *group in groups)
333                         [self Jub_removeRosterItem: oldItem
334                                              group: group];
335
336                 [contactMap removeObjectForKey: [item.JID bareJID]];
337         }
338
339         if (![item.subscription isEqual: @"remove"]) {
340                 OFMapTable *contactRows = [OFMapTable
341                     mapTableWithKeyFunctions: keyFunctions
342                               valueFunctions: rowRefFunctions];
343
344                 [contactMap setObject: contactRows
345                                forKey: [item.JID bareJID]];
346
347                 if (item.groups != nil)
348                         groups = item.groups;
349                 else
350                         groups = @[@"General"];
351
352                 for (OFString *group in groups)
353                         [self Jub_addRosterItem: item
354                                           group: group];
355         }
356 }
357
358 @end