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