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