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