]> cgit.babelmonkeys.de Git - jubjub.git/blob - src/gui/gtk/JubGtkRosterUI.m
Add some simple chat UI
[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 = [chatMap objectForKey: [message.from bareJID]];
104         if (chat == nil) {
105                 OFString * title = [@"Chat with " stringByAppendingString:
106                     [message.from bareJID]];
107                 chat = [JubGtkChatUI alloc];
108                 [[chat initWithTitle: title
109                            sendBlock: ^(OFString *text) {
110                         XMPPMessage *msg =
111                             [XMPPMessage messageWithType: @"chat"];
112                         msg.to = message.from;
113                         msg.body = text;
114                         [connection sendStanza: msg];
115
116                         [chat addMessage: msg.body
117                                   sender: [message.to bareJID]];
118                 }] autorelease];
119
120                 [chatMap setObject: chat
121                             forKey: [message.from bareJID]];
122         }
123         [chat addMessage: message.body
124                   sender: [message.from bareJID]];
125 }
126
127 /* Roster Delegate methods */
128 struct add_roster_item_param {
129         OFString *group;
130         OFString *name;
131         OFString *jid;
132         OFMapTable *groupMap;
133         OFMapTable *contactRows;
134         GtkTreeStore *roster_model;
135 };
136
137 static gboolean add_roster_item(gpointer user_data)
138 {
139         struct add_roster_item_param *params = user_data;
140         GtkTreeIter group_iter, contact_iter;
141         GtkTreeRowReference *group_ref, *contact_ref;
142         GtkTreePath *group_path, *contact_path;
143
144         group_ref = [params->groupMap valueForKey: params->group];
145
146         if (!group_ref) {
147                 // Create new group row
148                 gtk_tree_store_append(params->roster_model, &group_iter, NULL);
149                 gtk_tree_store_set(params->roster_model, &group_iter,
150                     0, [params->group UTF8String], -1);
151
152                 group_path = gtk_tree_model_get_path(GTK_TREE_MODEL(
153                         params->roster_model), &group_iter);
154
155                 group_ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(
156                         params->roster_model), group_path);
157
158                 [params->groupMap setValue: group_ref
159                                     forKey: params->group];
160         } else {
161                 // Get iter for existing group row
162                 group_path = gtk_tree_row_reference_get_path(group_ref);
163
164                 gtk_tree_model_get_iter(GTK_TREE_MODEL(params->roster_model),
165                     &group_iter, group_path);
166         }
167         gtk_tree_path_free(group_path);
168
169         // Create new contact row
170         gtk_tree_store_append(params->roster_model, &contact_iter, &group_iter);
171         gtk_tree_store_set(params->roster_model, &contact_iter,
172             0, [params->name UTF8String], 1, [params->jid UTF8String], -1);
173
174         contact_path = gtk_tree_model_get_path(GTK_TREE_MODEL(
175                 params->roster_model), &contact_iter);
176
177         contact_ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(
178                 params->roster_model), contact_path);
179
180         gtk_tree_path_free(contact_path);
181
182         [params->contactRows setValue: contact_ref
183                                forKey: params->group];
184
185         [params->group release];
186         [params->name release];
187         [params->jid release];
188         [params->groupMap release];
189         [params->contactRows release];
190         g_object_unref(params->roster_model);
191         free(params);
192
193         return FALSE;
194 }
195
196 struct remove_roster_item_param {
197         OFString *group;
198         OFMapTable *groupMap;
199         OFMapTable *contactRows;
200         GtkTreeStore *roster_model;
201 };
202
203 static gboolean remove_roster_item(gpointer user_data)
204 {
205         struct remove_roster_item_param *params = user_data;
206         GtkTreeIter contact_iter, group_iter;
207         GtkTreePath *contact_path, *group_path;
208         GtkTreeRowReference *contact_ref, *group_ref;
209
210         contact_ref = [params->contactRows valueForKey: params->group];
211         contact_path = gtk_tree_row_reference_get_path(contact_ref);
212         gtk_tree_model_get_iter(GTK_TREE_MODEL(params->roster_model),
213             &contact_iter, contact_path);
214
215         gtk_tree_store_remove(params->roster_model, &contact_iter);
216
217         group_ref = [params->groupMap valueForKey: params->group];
218         group_path = gtk_tree_row_reference_get_path(group_ref);
219         gtk_tree_model_get_iter(GTK_TREE_MODEL(params->roster_model),
220             &group_iter, group_path);
221
222         if (!gtk_tree_model_iter_has_child(GTK_TREE_MODEL(params->roster_model),
223             &group_iter)) {
224                 gtk_tree_store_remove(params->roster_model, &group_iter);
225                 [params->groupMap removeValueForKey: params->group];
226         }
227
228         gtk_tree_path_free(group_path);
229
230         [params->group release];
231         [params->groupMap release];
232         [params->contactRows release];
233         g_object_unref(params->roster_model);
234         free(params);
235
236         return FALSE;
237 }
238
239 - (void)rosterWasReceived: (XMPPRoster*)roster_
240 {
241         of_log(@"Handling roster");
242         [[roster_ rosterItems] enumerateKeysAndObjectsUsingBlock:
243             ^(OFString *bareJID, XMPPRosterItem *item, BOOL *stop) {
244                 OFArray *groups;
245                 OFMapTable *contactRows = [OFMapTable
246                     mapTableWithKeyFunctions: keyFunctions
247                               valueFunctions: rowRefFunctions];
248
249                 [contactMap setObject: contactRows
250                                forKey: bareJID];
251
252                 if (item.groups != nil)
253                         groups = item.groups;
254                 else
255                         groups = @[@"General"];
256
257                 for (OFString *group in groups) {
258                         struct add_roster_item_param *params =
259                             malloc(sizeof(*params));
260                         params->group = [group retain];
261                         params->name = [item.name retain];
262                         params->jid = [bareJID retain];
263                         params->groupMap = [groupMap retain];
264                         params->contactRows = [contactRows retain];
265                         params->roster_model = g_object_ref(roster_model);
266                         g_idle_add(add_roster_item, params);
267                 }
268         }];
269 }
270
271 -         (void)roster: (XMPPRoster*)roster_
272   didReceiveRosterItem: (XMPPRosterItem*)item
273 {
274         OFArray *groups;
275         XMPPRosterItem *oldItem =
276             [roster_.rosterItems objectForKey: [item.JID bareJID]];
277
278         if (oldItem) {
279                 if (oldItem.groups != nil)
280                         groups = oldItem.groups;
281                 else
282                         groups = @[@"General"];
283
284                 for (OFString *group in groups) {
285                         struct remove_roster_item_param *params =
286                             malloc(sizeof(*params));
287                         params->group = [group retain];
288                         params->contactRows = [[contactMap objectForKey:
289                             [oldItem.JID bareJID]] retain];
290                         params->groupMap = [groupMap retain];
291                         params->roster_model = g_object_ref(roster_model);
292                         g_idle_add(remove_roster_item, params);
293                 }
294
295                 [contactMap removeObjectForKey: [item.JID bareJID]];
296         }
297
298         if (![item.subscription isEqual: @"remove"]) {
299                 OFMapTable *contactRows = [OFMapTable
300                     mapTableWithKeyFunctions: keyFunctions
301                               valueFunctions: rowRefFunctions];
302
303                 [contactMap setObject: contactRows
304                                forKey: [item.JID bareJID]];
305
306                 if (item.groups != nil)
307                         groups = item.groups;
308                 else
309                         groups = @[@"General"];
310
311                 for (OFString *group in groups) {
312                         struct add_roster_item_param *params =
313                             malloc(sizeof(*params));
314                         params->group = [group retain];
315                         params->name = [item.name retain];
316                         params->jid = [[item.JID bareJID] retain];
317                         params->groupMap = [groupMap retain];
318                         params->contactRows = [contactRows retain];
319                         params->roster_model = g_object_ref(roster_model);
320                         g_idle_add(add_roster_item, params);
321                 }
322         }
323 }
324
325 @end