comparison gtk/gtkblist.c @ 14191:009db0b357b5

This is a hand-crafted commit to migrate across subversion revisions 16854:16861, due to some vagaries of the way the original renames were done. Witness that monotone can do in one revision what svn had to spread across several.
author Ethan Blanton <elb@pidgin.im>
date Sat, 16 Dec 2006 04:59:55 +0000
parents
children 3248928fbb00
comparison
equal deleted inserted replaced
14190:366be2ce35a7 14191:009db0b357b5
1 /*
2 * @file gtkblist.c GTK+ BuddyList API
3 * @ingroup gtkui
4 *
5 * gaim
6 *
7 * Gaim is the legal property of its developers, whose names are too numerous
8 * to list here. Please refer to the COPYRIGHT file distributed with this
9 * source distribution.
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 *
25 */
26 #include "internal.h"
27 #include "gtkgaim.h"
28
29 #include "account.h"
30 #include "connection.h"
31 #include "core.h"
32 #include "debug.h"
33 #include "notify.h"
34 #include "prpl.h"
35 #include "prefs.h"
36 #include "plugin.h"
37 #include "request.h"
38 #include "signals.h"
39 #include "gaimstock.h"
40 #include "util.h"
41
42 #include "gtkaccount.h"
43 #include "gtkblist.h"
44 #include "gtkconv.h"
45 #include "gtkdebug.h"
46 #include "gtkdialogs.h"
47 #include "gtkft.h"
48 #include "gtklog.h"
49 #include "gtkmenutray.h"
50 #include "gtkpounce.h"
51 #include "gtkplugin.h"
52 #include "gtkprefs.h"
53 #include "gtkprivacy.h"
54 #include "gtkroomlist.h"
55 #include "gtkstatusbox.h"
56 #include "gtkutils.h"
57
58 #include <gdk/gdkkeysyms.h>
59 #include <gtk/gtk.h>
60 #include <gdk/gdk.h>
61
62 typedef struct
63 {
64 GaimAccount *account;
65
66 GtkWidget *window;
67 GtkWidget *combo;
68 GtkWidget *entry;
69 GtkWidget *entry_for_alias;
70 GtkWidget *account_box;
71
72 } GaimGtkAddBuddyData;
73
74 typedef struct
75 {
76 GaimAccount *account;
77 gchar *default_chat_name;
78
79 GtkWidget *window;
80 GtkWidget *account_menu;
81 GtkWidget *alias_entry;
82 GtkWidget *group_combo;
83 GtkWidget *entries_box;
84 GtkSizeGroup *sg;
85
86 GList *entries;
87
88 } GaimGtkAddChatData;
89
90 typedef struct
91 {
92 GaimAccount *account;
93
94 GtkWidget *window;
95 GtkWidget *account_menu;
96 GtkWidget *entries_box;
97 GtkSizeGroup *sg;
98
99 GList *entries;
100 } GaimGtkJoinChatData;
101
102
103 static GtkWidget *accountmenu = NULL;
104
105 static guint visibility_manager_count = 0;
106 static gboolean gtk_blist_obscured = FALSE;
107 GHashTable* status_icon_hash_table = NULL;
108
109 static GList *gaim_gtk_blist_sort_methods = NULL;
110 static struct gaim_gtk_blist_sort_method *current_sort_method = NULL;
111 static void sort_method_none(GaimBlistNode *node, GaimBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
112
113 /* The functions we use for sorting aren't available in gtk 2.0.x, and
114 * segfault in 2.2.0. 2.2.1 is known to work, so I'll require that */
115 #if GTK_CHECK_VERSION(2,2,1)
116 static void sort_method_alphabetical(GaimBlistNode *node, GaimBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
117 static void sort_method_status(GaimBlistNode *node, GaimBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
118 static void sort_method_log(GaimBlistNode *node, GaimBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
119 #endif
120 static GaimGtkBuddyList *gtkblist = NULL;
121
122 static gboolean gaim_gtk_blist_refresh_timer(GaimBuddyList *list);
123 static void gaim_gtk_blist_update_buddy(GaimBuddyList *list, GaimBlistNode *node, gboolean statusChange);
124 static void gaim_gtk_blist_selection_changed(GtkTreeSelection *selection, gpointer data);
125 static void gaim_gtk_blist_update(GaimBuddyList *list, GaimBlistNode *node);
126 static void gaim_gtk_blist_update_contact(GaimBuddyList *list, GaimBlistNode *node);
127 static char *gaim_get_tooltip_text(GaimBlistNode *node, gboolean full);
128 static const char *item_factory_translate_func (const char *path, gpointer func_data);
129 static gboolean get_iter_from_node(GaimBlistNode *node, GtkTreeIter *iter);
130 static void redo_buddy_list(GaimBuddyList *list, gboolean remove, gboolean rerender);
131 static void gaim_gtk_blist_collapse_contact_cb(GtkWidget *w, GaimBlistNode *node);
132
133 static void gaim_gtk_blist_tooltip_destroy(void);
134
135 struct _gaim_gtk_blist_node {
136 GtkTreeRowReference *row;
137 gboolean contact_expanded;
138 gboolean recent_signonoff;
139 gint recent_signonoff_timer;
140 GString *status_icon_key;
141 };
142
143 static void gaim_gtk_blist_update_buddy_status_icon_key(struct _gaim_gtk_blist_node *gtkbuddynode,
144 GaimBuddy *buddy, GaimStatusIconSize size);
145
146
147 static char dim_grey_string[8] = "";
148 static char *dim_grey()
149 {
150 if (!gtkblist)
151 return "dim grey";
152 if (!dim_grey_string[0]) {
153 GtkStyle *style = gtk_widget_get_style(gtkblist->treeview);
154 snprintf(dim_grey_string, sizeof(dim_grey_string), "#%02x%02x%02x",
155 style->text_aa[GTK_STATE_NORMAL].red >> 8,
156 style->text_aa[GTK_STATE_NORMAL].green >> 8,
157 style->text_aa[GTK_STATE_NORMAL].blue >> 8);
158 }
159 return dim_grey_string;
160 }
161
162 /***************************************************
163 * Callbacks *
164 ***************************************************/
165 static gboolean gtk_blist_visibility_cb(GtkWidget *w, GdkEventVisibility *event, gpointer data)
166 {
167 if (event->state == GDK_VISIBILITY_FULLY_OBSCURED)
168 gtk_blist_obscured = TRUE;
169 else if (gtk_blist_obscured) {
170 gtk_blist_obscured = FALSE;
171 gaim_gtk_blist_refresh_timer(gaim_get_blist());
172 }
173
174 /* continue to handle event normally */
175 return FALSE;
176 }
177
178 static gboolean gtk_blist_window_state_cb(GtkWidget *w, GdkEventWindowState *event, gpointer data)
179 {
180 if(event->changed_mask & GDK_WINDOW_STATE_WITHDRAWN) {
181 if(event->new_window_state & GDK_WINDOW_STATE_WITHDRAWN)
182 gaim_prefs_set_bool("/gaim/gtk/blist/list_visible", FALSE);
183 else {
184 gaim_prefs_set_bool("/gaim/gtk/blist/list_visible", TRUE);
185 gaim_gtk_blist_refresh_timer(gaim_get_blist());
186 }
187 }
188
189 if(event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) {
190 if(event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED)
191 gaim_prefs_set_bool("/gaim/gtk/blist/list_maximized", TRUE);
192 else
193 gaim_prefs_set_bool("/gaim/gtk/blist/list_maximized", FALSE);
194 }
195
196 /* Refresh gtkblist if un-iconifying */
197 if (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED){
198 if (!(event->new_window_state & GDK_WINDOW_STATE_ICONIFIED))
199 gaim_gtk_blist_refresh_timer(gaim_get_blist());
200 }
201
202 return FALSE;
203 }
204
205 static gboolean gtk_blist_delete_cb(GtkWidget *w, GdkEventAny *event, gpointer data)
206 {
207 if(visibility_manager_count)
208 gaim_blist_set_visible(FALSE);
209 else
210 gaim_core_quit();
211
212 /* we handle everything, event should not propogate further */
213 return TRUE;
214 }
215
216 static gboolean gtk_blist_configure_cb(GtkWidget *w, GdkEventConfigure *event, gpointer data)
217 {
218 /* unfortunately GdkEventConfigure ignores the window gravity, but *
219 * the only way we have of setting the position doesn't. we have to *
220 * call get_position because it does pay attention to the gravity. *
221 * this is inefficient and I agree it sucks, but it's more likely *
222 * to work correctly. - Robot101 */
223 gint x, y;
224
225 /* check for visibility because when we aren't visible, this will *
226 * give us bogus (0,0) coordinates. - xOr */
227 if (GTK_WIDGET_VISIBLE(w))
228 gtk_window_get_position(GTK_WINDOW(w), &x, &y);
229 else
230 return FALSE; /* carry on normally */
231
232 #ifdef _WIN32
233 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
234 * when the window is being maximized */
235 if (gdk_window_get_state(w->window)
236 & GDK_WINDOW_STATE_MAXIMIZED) {
237 return FALSE;
238 }
239 #endif
240
241 /* don't save if nothing changed */
242 if (x == gaim_prefs_get_int("/gaim/gtk/blist/x") &&
243 y == gaim_prefs_get_int("/gaim/gtk/blist/y") &&
244 event->width == gaim_prefs_get_int("/gaim/gtk/blist/width") &&
245 event->height == gaim_prefs_get_int("/gaim/gtk/blist/height")) {
246
247 return FALSE; /* carry on normally */
248 }
249
250 /* don't save off-screen positioning */
251 if (x + event->width < 0 ||
252 y + event->height < 0 ||
253 x > gdk_screen_width() ||
254 y > gdk_screen_height()) {
255
256 return FALSE; /* carry on normally */
257 }
258
259 /* ignore changes when maximized */
260 if(gaim_prefs_get_bool("/gaim/gtk/blist/list_maximized"))
261 return FALSE;
262
263 /* store the position */
264 gaim_prefs_set_int("/gaim/gtk/blist/x", x);
265 gaim_prefs_set_int("/gaim/gtk/blist/y", y);
266 gaim_prefs_set_int("/gaim/gtk/blist/width", event->width);
267 gaim_prefs_set_int("/gaim/gtk/blist/height", event->height);
268
269 /* continue to handle event normally */
270 return FALSE;
271 }
272
273 static void gtk_blist_menu_info_cb(GtkWidget *w, GaimBuddy *b)
274 {
275 serv_get_info(b->account->gc, b->name);
276 }
277
278 static void gtk_blist_menu_im_cb(GtkWidget *w, GaimBuddy *b)
279 {
280 gaim_gtkdialogs_im_with_user(b->account, b->name);
281 }
282
283 static void gtk_blist_menu_send_file_cb(GtkWidget *w, GaimBuddy *b)
284 {
285 serv_send_file(b->account->gc, b->name, NULL);
286 }
287
288 static void gtk_blist_menu_autojoin_cb(GtkWidget *w, GaimChat *chat)
289 {
290 gaim_blist_node_set_bool((GaimBlistNode*)chat, "gtk-autojoin",
291 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)));
292 }
293
294 static void gtk_blist_join_chat(GaimChat *chat)
295 {
296 GaimConversation *conv;
297
298 conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT,
299 gaim_chat_get_name(chat),
300 chat->account);
301
302 if (conv != NULL)
303 gaim_gtkconv_present_conversation(conv);
304
305 serv_join_chat(chat->account->gc, chat->components);
306 }
307
308 static void gtk_blist_menu_join_cb(GtkWidget *w, GaimChat *chat)
309 {
310 gtk_blist_join_chat(chat);
311 }
312
313 static void gtk_blist_renderer_edited_cb(GtkCellRendererText *text_rend, char *arg1,
314 char *arg2, gpointer nada)
315 {
316 GtkTreeIter iter;
317 GtkTreePath *path;
318 GValue val;
319 GaimBlistNode *node;
320
321 path = gtk_tree_path_new_from_string (arg1);
322 gtk_tree_model_get_iter (GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
323 gtk_tree_path_free (path);
324 val.g_type = 0;
325 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val);
326 node = g_value_get_pointer(&val);
327 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(gtkblist->treeview), TRUE);
328 g_object_set(G_OBJECT(gtkblist->text_rend), "editable", FALSE, NULL);
329
330 switch (node->type)
331 {
332 case GAIM_BLIST_CONTACT_NODE:
333 {
334 GaimContact *contact = (GaimContact *)node;
335 struct _gaim_gtk_blist_node *gtknode = (struct _gaim_gtk_blist_node *)node->ui_data;
336
337 if (contact->alias || gtknode->contact_expanded)
338 gaim_blist_alias_contact(contact, arg2);
339 else
340 {
341 GaimBuddy *buddy = gaim_contact_get_priority_buddy(contact);
342 gaim_blist_alias_buddy(buddy, arg2);
343 serv_alias_buddy(buddy);
344 }
345 }
346 break;
347
348 case GAIM_BLIST_BUDDY_NODE:
349 gaim_blist_alias_buddy((GaimBuddy*)node, arg2);
350 serv_alias_buddy((GaimBuddy *)node);
351 break;
352 case GAIM_BLIST_GROUP_NODE:
353 gaim_blist_rename_group((GaimGroup*)node, arg2);
354 break;
355 case GAIM_BLIST_CHAT_NODE:
356 gaim_blist_alias_chat((GaimChat*)node, arg2);
357 break;
358 default:
359 break;
360 }
361 }
362
363 static void gtk_blist_menu_alias_cb(GtkWidget *w, GaimBlistNode *node)
364 {
365 GtkTreeIter iter;
366 GtkTreePath *path;
367 const char *text = NULL;
368 char *esc;
369
370 if (!(get_iter_from_node(node, &iter))) {
371 /* This is either a bug, or the buddy is in a collapsed contact */
372 node = node->parent;
373 if (!get_iter_from_node(node, &iter))
374 /* Now it's definitely a bug */
375 return;
376 }
377
378 switch (node->type) {
379 case GAIM_BLIST_BUDDY_NODE:
380 text = gaim_buddy_get_alias((GaimBuddy *)node);
381 break;
382 case GAIM_BLIST_CONTACT_NODE:
383 text = gaim_contact_get_alias((GaimContact *)node);
384 break;
385 case GAIM_BLIST_GROUP_NODE:
386 text = ((GaimGroup *)node)->name;
387 break;
388 case GAIM_BLIST_CHAT_NODE:
389 text = gaim_chat_get_name((GaimChat *)node);
390 break;
391 default:
392 g_return_if_reached();
393 }
394
395 esc = g_markup_escape_text(text, -1);
396 gtk_tree_store_set(gtkblist->treemodel, &iter, NAME_COLUMN, esc, -1);
397 g_free(esc);
398
399 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
400 g_object_set(G_OBJECT(gtkblist->text_rend), "editable", TRUE, NULL);
401 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(gtkblist->treeview), FALSE);
402 gtk_widget_grab_focus(gtkblist->treeview);
403 gtk_tree_view_set_cursor(GTK_TREE_VIEW(gtkblist->treeview), path, gtkblist->text_column, TRUE);
404 gtk_tree_path_free(path);
405 }
406
407 static void gtk_blist_menu_bp_cb(GtkWidget *w, GaimBuddy *b)
408 {
409 gaim_gtk_pounce_editor_show(b->account, b->name, NULL);
410 }
411
412 static void gtk_blist_menu_showlog_cb(GtkWidget *w, GaimBlistNode *node)
413 {
414 GaimLogType type;
415 GaimAccount *account;
416 char *name = NULL;
417
418 gaim_gtk_set_cursor(gtkblist->window, GDK_WATCH);
419
420 if (GAIM_BLIST_NODE_IS_BUDDY(node)) {
421 GaimBuddy *b = (GaimBuddy*) node;
422 type = GAIM_LOG_IM;
423 name = g_strdup(b->name);
424 account = b->account;
425 } else if (GAIM_BLIST_NODE_IS_CHAT(node)) {
426 GaimChat *c = (GaimChat*) node;
427 GaimPluginProtocolInfo *prpl_info = NULL;
428 type = GAIM_LOG_CHAT;
429 account = c->account;
430 prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gaim_find_prpl(gaim_account_get_protocol_id(account)));
431 if (prpl_info && prpl_info->get_chat_name) {
432 name = prpl_info->get_chat_name(c->components);
433 }
434 } else if (GAIM_BLIST_NODE_IS_CONTACT(node)) {
435 gaim_gtk_log_show_contact((GaimContact *)node);
436 gaim_gtk_clear_cursor(gtkblist->window);
437 return;
438 } else {
439 gaim_gtk_clear_cursor(gtkblist->window);
440
441 /* This callback should not have been registered for a node
442 * that doesn't match the type of one of the blocks above. */
443 g_return_if_reached();
444 }
445
446 if (name && account) {
447 gaim_gtk_log_show(type, name, account);
448 g_free(name);
449
450 gaim_gtk_clear_cursor(gtkblist->window);
451 }
452 }
453
454 static void gtk_blist_show_systemlog_cb()
455 {
456 gaim_gtk_syslog_show();
457 }
458
459 static void gtk_blist_show_onlinehelp_cb()
460 {
461 gaim_notify_uri(NULL, GAIM_WEBSITE "documentation.php");
462 }
463
464 static void
465 do_join_chat(GaimGtkJoinChatData *data)
466 {
467 if (data)
468 {
469 GHashTable *components =
470 g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
471 GList *tmp;
472 GaimChat *chat;
473
474 for (tmp = data->entries; tmp != NULL; tmp = tmp->next)
475 {
476 if (g_object_get_data(tmp->data, "is_spin"))
477 {
478 g_hash_table_replace(components,
479 g_strdup(g_object_get_data(tmp->data, "identifier")),
480 g_strdup_printf("%d",
481 gtk_spin_button_get_value_as_int(tmp->data)));
482 }
483 else
484 {
485 g_hash_table_replace(components,
486 g_strdup(g_object_get_data(tmp->data, "identifier")),
487 g_strdup(gtk_entry_get_text(tmp->data)));
488 }
489 }
490
491 chat = gaim_chat_new(data->account, NULL, components);
492 gtk_blist_join_chat(chat);
493 gaim_blist_remove_chat(chat);
494 }
495 }
496
497 static void
498 do_joinchat(GtkWidget *dialog, int id, GaimGtkJoinChatData *info)
499 {
500 switch(id)
501 {
502 case GTK_RESPONSE_OK:
503 do_join_chat(info);
504
505 break;
506 }
507
508 gtk_widget_destroy(GTK_WIDGET(dialog));
509 g_list_free(info->entries);
510 g_free(info);
511 }
512
513 /*
514 * Check the values of all the text entry boxes. If any required input
515 * strings are empty then don't allow the user to click on "OK."
516 */
517 static void
518 joinchat_set_sensitive_if_input_cb(GtkWidget *entry, gpointer user_data)
519 {
520 GaimGtkJoinChatData *data;
521 GList *tmp;
522 const char *text;
523 gboolean required;
524 gboolean sensitive = TRUE;
525
526 data = user_data;
527
528 for (tmp = data->entries; tmp != NULL; tmp = tmp->next)
529 {
530 if (!g_object_get_data(tmp->data, "is_spin"))
531 {
532 required = GPOINTER_TO_INT(g_object_get_data(tmp->data, "required"));
533 text = gtk_entry_get_text(tmp->data);
534 if (required && (*text == '\0'))
535 sensitive = FALSE;
536 }
537 }
538
539 gtk_dialog_set_response_sensitive(GTK_DIALOG(data->window), GTK_RESPONSE_OK, sensitive);
540 }
541
542 static void
543 gaim_gtk_blist_update_privacy_cb(GaimBuddy *buddy)
544 {
545 gaim_gtk_blist_update_buddy(gaim_get_blist(), (GaimBlistNode*)(buddy), TRUE);
546 }
547
548 static void
549 rebuild_joinchat_entries(GaimGtkJoinChatData *data)
550 {
551 GaimConnection *gc;
552 GList *list = NULL, *tmp = NULL;
553 GHashTable *defaults = NULL;
554 struct proto_chat_entry *pce;
555 gboolean focus = TRUE;
556
557 g_return_if_fail(data->account != NULL);
558
559 gc = gaim_account_get_connection(data->account);
560
561 while (GTK_BOX(data->entries_box)->children)
562 {
563 gtk_container_remove(GTK_CONTAINER(data->entries_box),
564 ((GtkBoxChild *)GTK_BOX(data->entries_box)->children->data)->widget);
565 }
566
567 g_list_free(data->entries);
568 data->entries = NULL;
569
570 if (GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL)
571 list = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info(gc);
572
573 if (GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults != NULL)
574 defaults = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults(gc, NULL);
575
576 for (tmp = list; tmp; tmp = tmp->next)
577 {
578 GtkWidget *label;
579 GtkWidget *rowbox;
580 GtkWidget *input;
581
582 pce = tmp->data;
583
584 rowbox = gtk_hbox_new(FALSE, GAIM_HIG_BORDER);
585 gtk_box_pack_start(GTK_BOX(data->entries_box), rowbox, FALSE, FALSE, 0);
586
587 label = gtk_label_new_with_mnemonic(pce->label);
588 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
589 gtk_size_group_add_widget(data->sg, label);
590 gtk_box_pack_start(GTK_BOX(rowbox), label, FALSE, FALSE, 0);
591
592 if (pce->is_int)
593 {
594 GtkObject *adjust;
595 adjust = gtk_adjustment_new(pce->min, pce->min, pce->max,
596 1, 10, 10);
597 input = gtk_spin_button_new(GTK_ADJUSTMENT(adjust), 1, 0);
598 gtk_widget_set_size_request(input, 50, -1);
599 gtk_box_pack_end(GTK_BOX(rowbox), input, FALSE, FALSE, 0);
600 }
601 else
602 {
603 char *value;
604 input = gtk_entry_new();
605 gtk_entry_set_activates_default(GTK_ENTRY(input), TRUE);
606 value = g_hash_table_lookup(defaults, pce->identifier);
607 if (value != NULL)
608 gtk_entry_set_text(GTK_ENTRY(input), value);
609 if (pce->secret)
610 {
611 gtk_entry_set_visibility(GTK_ENTRY(input), FALSE);
612 gtk_entry_set_invisible_char(GTK_ENTRY(input), GAIM_INVISIBLE_CHAR);
613 }
614 gtk_box_pack_end(GTK_BOX(rowbox), input, TRUE, TRUE, 0);
615 g_signal_connect(G_OBJECT(input), "changed",
616 G_CALLBACK(joinchat_set_sensitive_if_input_cb), data);
617 }
618
619 /* Do the following for any type of input widget */
620 if (focus)
621 {
622 gtk_widget_grab_focus(input);
623 focus = FALSE;
624 }
625 gtk_label_set_mnemonic_widget(GTK_LABEL(label), input);
626 gaim_set_accessible_label(input, label);
627 g_object_set_data(G_OBJECT(input), "identifier", (gpointer)pce->identifier);
628 g_object_set_data(G_OBJECT(input), "is_spin", GINT_TO_POINTER(pce->is_int));
629 g_object_set_data(G_OBJECT(input), "required", GINT_TO_POINTER(pce->required));
630 data->entries = g_list_append(data->entries, input);
631
632 g_free(pce);
633 }
634
635 g_list_free(list);
636 g_hash_table_destroy(defaults);
637
638 /* Set whether the "OK" button should be clickable initially */
639 joinchat_set_sensitive_if_input_cb(NULL, data);
640
641 gtk_widget_show_all(data->entries_box);
642 }
643
644 static void
645 joinchat_select_account_cb(GObject *w, GaimAccount *account,
646 GaimGtkJoinChatData *data)
647 {
648 data->account = account;
649 rebuild_joinchat_entries(data);
650 }
651
652 static gboolean
653 chat_account_filter_func(GaimAccount *account)
654 {
655 GaimConnection *gc = gaim_account_get_connection(account);
656 GaimPluginProtocolInfo *prpl_info = NULL;
657
658 prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl);
659
660 return (prpl_info->chat_info != NULL);
661 }
662
663 gboolean
664 gaim_gtk_blist_joinchat_is_showable()
665 {
666 GList *c;
667 GaimConnection *gc;
668
669 for (c = gaim_connections_get_all(); c != NULL; c = c->next) {
670 gc = c->data;
671
672 if (chat_account_filter_func(gaim_connection_get_account(gc)))
673 return TRUE;
674 }
675
676 return FALSE;
677 }
678
679 void
680 gaim_gtk_blist_joinchat_show(void)
681 {
682 GtkWidget *hbox, *vbox;
683 GtkWidget *rowbox;
684 GtkWidget *label;
685 GaimGtkBuddyList *gtkblist;
686 GtkWidget *img = NULL;
687 GaimGtkJoinChatData *data = NULL;
688
689 gtkblist = GAIM_GTK_BLIST(gaim_get_blist());
690 img = gtk_image_new_from_stock(GAIM_STOCK_DIALOG_QUESTION,
691 GTK_ICON_SIZE_DIALOG);
692 data = g_new0(GaimGtkJoinChatData, 1);
693
694 data->window = gtk_dialog_new_with_buttons(_("Join a Chat"),
695 NULL, GTK_DIALOG_NO_SEPARATOR,
696 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
697 GAIM_STOCK_CHAT, GTK_RESPONSE_OK, NULL);
698 gtk_dialog_set_default_response(GTK_DIALOG(data->window), GTK_RESPONSE_OK);
699 gtk_container_set_border_width(GTK_CONTAINER(data->window), GAIM_HIG_BOX_SPACE);
700 gtk_window_set_resizable(GTK_WINDOW(data->window), FALSE);
701 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(data->window)->vbox), GAIM_HIG_BORDER);
702 gtk_container_set_border_width(
703 GTK_CONTAINER(GTK_DIALOG(data->window)->vbox), GAIM_HIG_BOX_SPACE);
704 gtk_window_set_role(GTK_WINDOW(data->window), "join_chat");
705
706 hbox = gtk_hbox_new(FALSE, GAIM_HIG_BORDER);
707 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(data->window)->vbox), hbox);
708 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
709 gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
710
711 vbox = gtk_vbox_new(FALSE, 5);
712 gtk_container_set_border_width(GTK_CONTAINER(vbox), 0);
713 gtk_container_add(GTK_CONTAINER(hbox), vbox);
714
715 label = gtk_label_new(_("Please enter the appropriate information "
716 "about the chat you would like to join.\n"));
717 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
718 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
719 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
720
721 rowbox = gtk_hbox_new(FALSE, GAIM_HIG_BORDER);
722 gtk_box_pack_start(GTK_BOX(vbox), rowbox, TRUE, TRUE, 0);
723
724 data->sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
725
726 label = gtk_label_new_with_mnemonic(_("_Account:"));
727 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
728 gtk_box_pack_start(GTK_BOX(rowbox), label, FALSE, FALSE, 0);
729 gtk_size_group_add_widget(data->sg, label);
730
731 data->account_menu = gaim_gtk_account_option_menu_new(NULL, FALSE,
732 G_CALLBACK(joinchat_select_account_cb),
733 chat_account_filter_func, data);
734 gtk_box_pack_start(GTK_BOX(rowbox), data->account_menu, TRUE, TRUE, 0);
735 gtk_label_set_mnemonic_widget(GTK_LABEL(label),
736 GTK_WIDGET(data->account_menu));
737 gaim_set_accessible_label (data->account_menu, label);
738
739 data->entries_box = gtk_vbox_new(FALSE, 5);
740 gtk_container_add(GTK_CONTAINER(vbox), data->entries_box);
741 gtk_container_set_border_width(GTK_CONTAINER(data->entries_box), 0);
742
743 data->account = gaim_gtk_account_option_menu_get_selected(data->account_menu);
744
745 rebuild_joinchat_entries(data);
746
747 g_signal_connect(G_OBJECT(data->window), "response",
748 G_CALLBACK(do_joinchat), data);
749
750 g_object_unref(data->sg);
751
752 gtk_widget_show_all(data->window);
753 }
754
755 static void gtk_blist_row_expanded_cb(GtkTreeView *tv, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data) {
756 GaimBlistNode *node;
757 GValue val;
758
759 val.g_type = 0;
760 gtk_tree_model_get_value(GTK_TREE_MODEL(gtkblist->treemodel), iter, NODE_COLUMN, &val);
761
762 node = g_value_get_pointer(&val);
763
764 if (GAIM_BLIST_NODE_IS_GROUP(node)) {
765 gaim_blist_node_set_bool(node, "collapsed", FALSE);
766 }
767 }
768
769 static void gtk_blist_row_collapsed_cb(GtkTreeView *tv, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data) {
770 GaimBlistNode *node;
771 GValue val;
772
773 val.g_type = 0;
774 gtk_tree_model_get_value(GTK_TREE_MODEL(gtkblist->treemodel), iter, NODE_COLUMN, &val);
775
776 node = g_value_get_pointer(&val);
777
778 if (GAIM_BLIST_NODE_IS_GROUP(node)) {
779 gaim_blist_node_set_bool(node, "collapsed", TRUE);
780 } else if(GAIM_BLIST_NODE_IS_CONTACT(node)) {
781 gaim_gtk_blist_collapse_contact_cb(NULL, node);
782 }
783 }
784
785 static void gtk_blist_row_activated_cb(GtkTreeView *tv, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data) {
786 GaimBlistNode *node;
787 GtkTreeIter iter;
788 GValue val;
789
790 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
791
792 val.g_type = 0;
793 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val);
794 node = g_value_get_pointer(&val);
795
796 if(GAIM_BLIST_NODE_IS_CONTACT(node) || GAIM_BLIST_NODE_IS_BUDDY(node)) {
797 GaimBuddy *buddy;
798
799 if(GAIM_BLIST_NODE_IS_CONTACT(node))
800 buddy = gaim_contact_get_priority_buddy((GaimContact*)node);
801 else
802 buddy = (GaimBuddy*)node;
803
804 gaim_gtkdialogs_im_with_user(buddy->account, buddy->name);
805 } else if (GAIM_BLIST_NODE_IS_CHAT(node)) {
806 gtk_blist_join_chat((GaimChat *)node);
807 } else if (GAIM_BLIST_NODE_IS_GROUP(node)) {
808 if (gtk_tree_view_row_expanded(tv, path))
809 gtk_tree_view_collapse_row(tv, path);
810 else
811 gtk_tree_view_expand_row(tv,path,FALSE);
812 }
813 }
814
815 static void gaim_gtk_blist_add_chat_cb()
816 {
817 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
818 GtkTreeIter iter;
819 GaimBlistNode *node;
820
821 if(gtk_tree_selection_get_selected(sel, NULL, &iter)){
822 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
823 if (GAIM_BLIST_NODE_IS_BUDDY(node))
824 gaim_blist_request_add_chat(NULL, (GaimGroup*)node->parent->parent, NULL, NULL);
825 if (GAIM_BLIST_NODE_IS_CONTACT(node) || GAIM_BLIST_NODE_IS_CHAT(node))
826 gaim_blist_request_add_chat(NULL, (GaimGroup*)node->parent, NULL, NULL);
827 else if (GAIM_BLIST_NODE_IS_GROUP(node))
828 gaim_blist_request_add_chat(NULL, (GaimGroup*)node, NULL, NULL);
829 }
830 else {
831 gaim_blist_request_add_chat(NULL, NULL, NULL, NULL);
832 }
833 }
834
835 static void gaim_gtk_blist_add_buddy_cb()
836 {
837 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
838 GtkTreeIter iter;
839 GaimBlistNode *node;
840
841 if(gtk_tree_selection_get_selected(sel, NULL, &iter)){
842 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
843 if (GAIM_BLIST_NODE_IS_BUDDY(node)) {
844 gaim_blist_request_add_buddy(NULL, NULL, ((GaimGroup*)node->parent->parent)->name,
845 NULL);
846 } else if (GAIM_BLIST_NODE_IS_CONTACT(node)
847 || GAIM_BLIST_NODE_IS_CHAT(node)) {
848 gaim_blist_request_add_buddy(NULL, NULL, ((GaimGroup*)node->parent)->name, NULL);
849 } else if (GAIM_BLIST_NODE_IS_GROUP(node)) {
850 gaim_blist_request_add_buddy(NULL, NULL, ((GaimGroup*)node)->name, NULL);
851 }
852 }
853 else {
854 gaim_blist_request_add_buddy(NULL, NULL, NULL, NULL);
855 }
856 }
857
858 static void
859 gaim_gtk_blist_remove_cb (GtkWidget *w, GaimBlistNode *node)
860 {
861 if (GAIM_BLIST_NODE_IS_BUDDY(node)) {
862 gaim_gtkdialogs_remove_buddy((GaimBuddy*)node);
863 } else if (GAIM_BLIST_NODE_IS_CHAT(node)) {
864 gaim_gtkdialogs_remove_chat((GaimChat*)node);
865 } else if (GAIM_BLIST_NODE_IS_GROUP(node)) {
866 gaim_gtkdialogs_remove_group((GaimGroup*)node);
867 } else if (GAIM_BLIST_NODE_IS_CONTACT(node)) {
868 gaim_gtkdialogs_remove_contact((GaimContact*)node);
869 }
870 }
871
872 struct _expand {
873 GtkTreeView *treeview;
874 GtkTreePath *path;
875 GaimBlistNode *node;
876 };
877
878 static gboolean
879 scroll_to_expanded_cell(gpointer data)
880 {
881 struct _expand *ex = data;
882 gtk_tree_view_scroll_to_cell(ex->treeview, ex->path, NULL, FALSE, 0, 0);
883 gaim_gtk_blist_update_contact(NULL, ex->node);
884
885 gtk_tree_path_free(ex->path);
886 g_free(ex);
887
888 return FALSE;
889 }
890
891 static void
892 gaim_gtk_blist_expand_contact_cb(GtkWidget *w, GaimBlistNode *node)
893 {
894 struct _gaim_gtk_blist_node *gtknode;
895 GtkTreeIter iter, parent;
896 GaimBlistNode *bnode;
897 GtkTreePath *path;
898
899 struct _expand *ex = g_new0(struct _expand, 1);
900
901 if(!GAIM_BLIST_NODE_IS_CONTACT(node))
902 return;
903
904 gtknode = (struct _gaim_gtk_blist_node *)node->ui_data;
905
906 gtknode->contact_expanded = TRUE;
907
908 for(bnode = node->child; bnode; bnode = bnode->next) {
909 gaim_gtk_blist_update(NULL, bnode);
910 }
911
912 /* This ensures that the bottom buddy is visible, i.e. not scrolled off the alignment */
913 get_iter_from_node(node, &parent);
914 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtkblist->treemodel), &iter, &parent,
915 gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtkblist->treemodel), &parent) -1);
916 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
917
918 /* Let the treeview draw so it knows where to scroll */
919 ex->treeview = GTK_TREE_VIEW(gtkblist->treeview);
920 ex->path = path;
921 ex->node = node->child;
922 g_idle_add(scroll_to_expanded_cell, ex);
923 }
924
925 static void
926 gaim_gtk_blist_collapse_contact_cb(GtkWidget *w, GaimBlistNode *node)
927 {
928 GaimBlistNode *bnode;
929 struct _gaim_gtk_blist_node *gtknode;
930
931 if(!GAIM_BLIST_NODE_IS_CONTACT(node))
932 return;
933
934 gtknode = (struct _gaim_gtk_blist_node *)node->ui_data;
935
936 gtknode->contact_expanded = FALSE;
937
938 for(bnode = node->child; bnode; bnode = bnode->next) {
939 gaim_gtk_blist_update(NULL, bnode);
940 }
941 }
942
943 void
944 gaim_gtk_append_blist_node_proto_menu(GtkWidget *menu, GaimConnection *gc,
945 GaimBlistNode *node)
946 {
947 GList *l, *ll;
948 GaimPluginProtocolInfo *prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl);
949
950 if(!prpl_info || !prpl_info->blist_node_menu)
951 return;
952
953 for(l = ll = prpl_info->blist_node_menu(node); l; l = l->next) {
954 GaimMenuAction *act = (GaimMenuAction *) l->data;
955 gaim_gtk_append_menu_action(menu, act, node);
956 }
957 g_list_free(ll);
958 }
959
960 void
961 gaim_gtk_append_blist_node_extended_menu(GtkWidget *menu, GaimBlistNode *node)
962 {
963 GList *l, *ll;
964
965 for(l = ll = gaim_blist_node_get_extended_menu(node); l; l = l->next) {
966 GaimMenuAction *act = (GaimMenuAction *) l->data;
967 gaim_gtk_append_menu_action(menu, act, node);
968 }
969 g_list_free(ll);
970 }
971
972 void
973 gaim_gtk_blist_make_buddy_menu(GtkWidget *menu, GaimBuddy *buddy, gboolean sub) {
974 GaimPluginProtocolInfo *prpl_info;
975 GaimContact *contact;
976 gboolean contact_expanded = FALSE;
977
978 g_return_if_fail(menu);
979 g_return_if_fail(buddy);
980
981 prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(buddy->account->gc->prpl);
982
983 contact = gaim_buddy_get_contact(buddy);
984 if (contact) {
985 contact_expanded = ((struct _gaim_gtk_blist_node *)(((GaimBlistNode*)contact)->ui_data))->contact_expanded;
986 }
987
988 if (prpl_info && prpl_info->get_info) {
989 gaim_new_item_from_stock(menu, _("Get _Info"), GAIM_STOCK_INFO,
990 G_CALLBACK(gtk_blist_menu_info_cb), buddy, 0, 0, NULL);
991 }
992 gaim_new_item_from_stock(menu, _("I_M"), GAIM_STOCK_IM,
993 G_CALLBACK(gtk_blist_menu_im_cb), buddy, 0, 0, NULL);
994 if (prpl_info && prpl_info->send_file) {
995 if (!prpl_info->can_receive_file ||
996 prpl_info->can_receive_file(buddy->account->gc, buddy->name))
997 {
998 gaim_new_item_from_stock(menu, _("_Send File"),
999 GAIM_STOCK_FILE_TRANSFER,
1000 G_CALLBACK(gtk_blist_menu_send_file_cb),
1001 buddy, 0, 0, NULL);
1002 }
1003 }
1004
1005 gaim_new_item_from_stock(menu, _("Add Buddy _Pounce"), GAIM_STOCK_POUNCE,
1006 G_CALLBACK(gtk_blist_menu_bp_cb), buddy, 0, 0, NULL);
1007
1008 if(((GaimBlistNode*)buddy)->parent->child->next && !sub && !contact_expanded) {
1009 gaim_new_item_from_stock(menu, _("View _Log"), GAIM_STOCK_LOG,
1010 G_CALLBACK(gtk_blist_menu_showlog_cb),
1011 contact, 0, 0, NULL);
1012 } else if (!sub) {
1013 gaim_new_item_from_stock(menu, _("View _Log"), GAIM_STOCK_LOG,
1014 G_CALLBACK(gtk_blist_menu_showlog_cb), buddy, 0, 0, NULL);
1015 }
1016
1017 gaim_gtk_append_blist_node_proto_menu(menu, buddy->account->gc,
1018 (GaimBlistNode *)buddy);
1019 gaim_gtk_append_blist_node_extended_menu(menu, (GaimBlistNode *)buddy);
1020
1021 if (((GaimBlistNode*)buddy)->parent->child->next && !sub && !contact_expanded) {
1022 gaim_separator(menu);
1023
1024 gaim_new_item_from_stock(menu, _("Alias..."), GAIM_STOCK_ALIAS,
1025 G_CALLBACK(gtk_blist_menu_alias_cb),
1026 contact, 0, 0, NULL);
1027 gaim_new_item_from_stock(menu, _("Remove"), GTK_STOCK_REMOVE,
1028 G_CALLBACK(gaim_gtk_blist_remove_cb),
1029 contact, 0, 0, NULL);
1030 } else if (!sub || contact_expanded) {
1031 gaim_separator(menu);
1032
1033 gaim_new_item_from_stock(menu, _("_Alias..."), GAIM_STOCK_ALIAS,
1034 G_CALLBACK(gtk_blist_menu_alias_cb), buddy, 0, 0, NULL);
1035 gaim_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1036 G_CALLBACK(gaim_gtk_blist_remove_cb), buddy,
1037 0, 0, NULL);
1038 }
1039 }
1040
1041 static gboolean
1042 gtk_blist_key_press_cb(GtkWidget *tv, GdkEventKey *event, gpointer data) {
1043 GaimBlistNode *node;
1044 GValue val;
1045 GtkTreeIter iter;
1046 GtkTreeSelection *sel;
1047
1048 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
1049 if(!gtk_tree_selection_get_selected(sel, NULL, &iter))
1050 return FALSE;
1051
1052 val.g_type = 0;
1053 gtk_tree_model_get_value(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
1054 NODE_COLUMN, &val);
1055 node = g_value_get_pointer(&val);
1056
1057 if(event->state & GDK_CONTROL_MASK &&
1058 (event->keyval == 'o' || event->keyval == 'O')) {
1059 GaimBuddy *buddy;
1060
1061 if(GAIM_BLIST_NODE_IS_CONTACT(node)) {
1062 buddy = gaim_contact_get_priority_buddy((GaimContact*)node);
1063 } else if(GAIM_BLIST_NODE_IS_BUDDY(node)) {
1064 buddy = (GaimBuddy*)node;
1065 } else {
1066 return FALSE;
1067 }
1068 if(buddy)
1069 serv_get_info(buddy->account->gc, buddy->name);
1070 }
1071
1072 return FALSE;
1073 }
1074
1075
1076 static GtkWidget *
1077 create_group_menu (GaimBlistNode *node, GaimGroup *g)
1078 {
1079 GtkWidget *menu;
1080 GtkWidget *item;
1081
1082 menu = gtk_menu_new();
1083 gaim_new_item_from_stock(menu, _("Add a _Buddy"), GTK_STOCK_ADD,
1084 G_CALLBACK(gaim_gtk_blist_add_buddy_cb), node, 0, 0, NULL);
1085 item = gaim_new_item_from_stock(menu, _("Add a C_hat"), GTK_STOCK_ADD,
1086 G_CALLBACK(gaim_gtk_blist_add_chat_cb), node, 0, 0, NULL);
1087 gtk_widget_set_sensitive(item, gaim_gtk_blist_joinchat_is_showable());
1088 gaim_new_item_from_stock(menu, _("_Delete Group"), GTK_STOCK_REMOVE,
1089 G_CALLBACK(gaim_gtk_blist_remove_cb), node, 0, 0, NULL);
1090 gaim_new_item_from_stock(menu, _("_Rename"), NULL,
1091 G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
1092
1093 gaim_gtk_append_blist_node_extended_menu(menu, node);
1094
1095 return menu;
1096 }
1097
1098
1099 static GtkWidget *
1100 create_chat_menu(GaimBlistNode *node, GaimChat *c) {
1101 GtkWidget *menu;
1102 gboolean autojoin;
1103
1104 menu = gtk_menu_new();
1105 autojoin = (gaim_blist_node_get_bool(node, "gtk-autojoin") ||
1106 (gaim_blist_node_get_string(node, "gtk-autojoin") != NULL));
1107
1108 gaim_new_item_from_stock(menu, _("_Join"), GAIM_STOCK_CHAT,
1109 G_CALLBACK(gtk_blist_menu_join_cb), node, 0, 0, NULL);
1110 gaim_new_check_item(menu, _("Auto-Join"),
1111 G_CALLBACK(gtk_blist_menu_autojoin_cb), node, autojoin);
1112 gaim_new_item_from_stock(menu, _("View _Log"), GAIM_STOCK_LOG,
1113 G_CALLBACK(gtk_blist_menu_showlog_cb), node, 0, 0, NULL);
1114
1115 gaim_gtk_append_blist_node_proto_menu(menu, c->account->gc, node);
1116 gaim_gtk_append_blist_node_extended_menu(menu, node);
1117
1118 gaim_separator(menu);
1119
1120 gaim_new_item_from_stock(menu, _("_Alias..."), GAIM_STOCK_ALIAS,
1121 G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
1122 gaim_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1123 G_CALLBACK(gaim_gtk_blist_remove_cb), node, 0, 0, NULL);
1124
1125 return menu;
1126 }
1127
1128 static GtkWidget *
1129 create_contact_menu (GaimBlistNode *node)
1130 {
1131 GtkWidget *menu;
1132
1133 menu = gtk_menu_new();
1134
1135 gaim_new_item_from_stock(menu, _("View _Log"), GAIM_STOCK_LOG,
1136 G_CALLBACK(gtk_blist_menu_showlog_cb),
1137 node, 0, 0, NULL);
1138
1139 gaim_separator(menu);
1140
1141 gaim_new_item_from_stock(menu, _("_Alias..."), GAIM_STOCK_ALIAS,
1142 G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
1143 gaim_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1144 G_CALLBACK(gaim_gtk_blist_remove_cb), node, 0, 0, NULL);
1145
1146 gaim_separator(menu);
1147
1148 gaim_new_item_from_stock(menu, _("_Collapse"), GTK_STOCK_ZOOM_OUT,
1149 G_CALLBACK(gaim_gtk_blist_collapse_contact_cb),
1150 node, 0, 0, NULL);
1151
1152 gaim_gtk_append_blist_node_extended_menu(menu, node);
1153
1154 return menu;
1155 }
1156
1157 static GtkWidget *
1158 create_buddy_menu(GaimBlistNode *node, GaimBuddy *b) {
1159 struct _gaim_gtk_blist_node *gtknode = (struct _gaim_gtk_blist_node *)node->ui_data;
1160 GtkWidget *menu;
1161 GtkWidget *menuitem;
1162 gboolean show_offline = gaim_prefs_get_bool("/gaim/gtk/blist/show_offline_buddies");
1163
1164 menu = gtk_menu_new();
1165 gaim_gtk_blist_make_buddy_menu(menu, b, FALSE);
1166
1167 if(GAIM_BLIST_NODE_IS_CONTACT(node)) {
1168 gaim_separator(menu);
1169
1170 if(gtknode->contact_expanded) {
1171 gaim_new_item_from_stock(menu, _("_Collapse"),
1172 GTK_STOCK_ZOOM_OUT,
1173 G_CALLBACK(gaim_gtk_blist_collapse_contact_cb),
1174 node, 0, 0, NULL);
1175 } else {
1176 gaim_new_item_from_stock(menu, _("_Expand"),
1177 GTK_STOCK_ZOOM_IN,
1178 G_CALLBACK(gaim_gtk_blist_expand_contact_cb), node,
1179 0, 0, NULL);
1180 }
1181 if(node->child->next) {
1182 GaimBlistNode *bnode;
1183
1184 for(bnode = node->child; bnode; bnode = bnode->next) {
1185 GaimBuddy *buddy = (GaimBuddy*)bnode;
1186 GdkPixbuf *buf;
1187 GtkWidget *submenu;
1188 GtkWidget *image;
1189
1190 if(buddy == b)
1191 continue;
1192 if(!buddy->account->gc)
1193 continue;
1194 if(!show_offline && !GAIM_BUDDY_IS_ONLINE(buddy))
1195 continue;
1196
1197 menuitem = gtk_image_menu_item_new_with_label(buddy->name);
1198 buf = gaim_gtk_blist_get_status_icon(bnode,
1199 GAIM_STATUS_ICON_SMALL);
1200 image = gtk_image_new_from_pixbuf(buf);
1201 g_object_unref(G_OBJECT(buf));
1202 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem),
1203 image);
1204 gtk_widget_show(image);
1205 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1206 gtk_widget_show(menuitem);
1207
1208 submenu = gtk_menu_new();
1209 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
1210 gtk_widget_show(submenu);
1211
1212 gaim_gtk_blist_make_buddy_menu(submenu, buddy, TRUE);
1213 }
1214 }
1215 }
1216 return menu;
1217 }
1218
1219 static gboolean
1220 gaim_gtk_blist_show_context_menu(GaimBlistNode *node,
1221 GtkMenuPositionFunc func,
1222 GtkWidget *tv,
1223 guint button,
1224 guint32 time)
1225 {
1226 struct _gaim_gtk_blist_node *gtknode;
1227 GtkWidget *menu = NULL;
1228 gboolean handled = FALSE;
1229
1230 gtknode = (struct _gaim_gtk_blist_node *)node->ui_data;
1231
1232 /* Create a menu based on the thing we right-clicked on */
1233 if (GAIM_BLIST_NODE_IS_GROUP(node)) {
1234 GaimGroup *g = (GaimGroup *)node;
1235
1236 menu = create_group_menu(node, g);
1237 } else if (GAIM_BLIST_NODE_IS_CHAT(node)) {
1238 GaimChat *c = (GaimChat *)node;
1239
1240 menu = create_chat_menu(node, c);
1241 } else if ((GAIM_BLIST_NODE_IS_CONTACT(node)) && (gtknode->contact_expanded)) {
1242 menu = create_contact_menu(node);
1243 } else if (GAIM_BLIST_NODE_IS_CONTACT(node) || GAIM_BLIST_NODE_IS_BUDDY(node)) {
1244 GaimBuddy *b;
1245
1246 if (GAIM_BLIST_NODE_IS_CONTACT(node))
1247 b = gaim_contact_get_priority_buddy((GaimContact*)node);
1248 else
1249 b = (GaimBuddy *)node;
1250
1251 menu = create_buddy_menu(node, b);
1252 }
1253
1254 #ifdef _WIN32
1255 /* Unhook the tooltip-timeout since we don't want a tooltip
1256 * to appear and obscure the context menu we are about to show
1257 This is a workaround for GTK+ bug 107320. */
1258 if (gtkblist->timeout) {
1259 g_source_remove(gtkblist->timeout);
1260 gtkblist->timeout = 0;
1261 }
1262 #endif
1263
1264 /* Now display the menu */
1265 if (menu != NULL) {
1266 gtk_widget_show_all(menu);
1267 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, func, tv, button, time);
1268 handled = TRUE;
1269 }
1270
1271 return handled;
1272 }
1273
1274 static gboolean gtk_blist_button_press_cb(GtkWidget *tv, GdkEventButton *event, gpointer user_data)
1275 {
1276 GtkTreePath *path;
1277 GaimBlistNode *node;
1278 GValue val;
1279 GtkTreeIter iter;
1280 GtkTreeSelection *sel;
1281 GaimPlugin *prpl = NULL;
1282 GaimPluginProtocolInfo *prpl_info = NULL;
1283 struct _gaim_gtk_blist_node *gtknode;
1284 gboolean handled = FALSE;
1285
1286 /* Here we figure out which node was clicked */
1287 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL))
1288 return FALSE;
1289 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
1290 val.g_type = 0;
1291 gtk_tree_model_get_value(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val);
1292 node = g_value_get_pointer(&val);
1293 gtknode = (struct _gaim_gtk_blist_node *)node->ui_data;
1294
1295 /* Right click draws a context menu */
1296 if ((event->button == 3) && (event->type == GDK_BUTTON_PRESS)) {
1297 handled = gaim_gtk_blist_show_context_menu(node, NULL, tv, 3, event->time);
1298
1299 /* CTRL+middle click expands or collapse a contact */
1300 } else if ((event->button == 2) && (event->type == GDK_BUTTON_PRESS) &&
1301 (event->state & GDK_CONTROL_MASK) && (GAIM_BLIST_NODE_IS_CONTACT(node))) {
1302 if (gtknode->contact_expanded)
1303 gaim_gtk_blist_collapse_contact_cb(NULL, node);
1304 else
1305 gaim_gtk_blist_expand_contact_cb(NULL, node);
1306 handled = TRUE;
1307
1308 /* Double middle click gets info */
1309 } else if ((event->button == 2) && (event->type == GDK_2BUTTON_PRESS) &&
1310 ((GAIM_BLIST_NODE_IS_CONTACT(node)) || (GAIM_BLIST_NODE_IS_BUDDY(node)))) {
1311 GaimBuddy *b;
1312 if(GAIM_BLIST_NODE_IS_CONTACT(node))
1313 b = gaim_contact_get_priority_buddy((GaimContact*)node);
1314 else
1315 b = (GaimBuddy *)node;
1316
1317 prpl = gaim_find_prpl(gaim_account_get_protocol_id(b->account));
1318 if (prpl != NULL)
1319 prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl);
1320
1321 if (prpl && prpl_info->get_info)
1322 serv_get_info(b->account->gc, b->name);
1323 handled = TRUE;
1324 }
1325
1326 #if (1)
1327 /*
1328 * This code only exists because GTK+ doesn't work. If we return
1329 * FALSE here, as would be normal the event propoagates down and
1330 * somehow gets interpreted as the start of a drag event.
1331 *
1332 * Um, isn't it _normal_ to return TRUE here? Since the event
1333 * was handled? --Mark
1334 */
1335 if(handled) {
1336 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
1337 gtk_tree_selection_select_path(sel, path);
1338 gtk_tree_path_free(path);
1339 return TRUE;
1340 }
1341 #endif
1342 gtk_tree_path_free(path);
1343
1344 return FALSE;
1345 }
1346
1347 static gboolean
1348 gaim_gtk_blist_popup_menu_cb(GtkWidget *tv, void *user_data)
1349 {
1350 GaimBlistNode *node;
1351 GValue val;
1352 GtkTreeIter iter;
1353 GtkTreeSelection *sel;
1354 gboolean handled = FALSE;
1355
1356 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
1357 if (!gtk_tree_selection_get_selected(sel, NULL, &iter))
1358 return FALSE;
1359
1360 val.g_type = 0;
1361 gtk_tree_model_get_value(GTK_TREE_MODEL(gtkblist->treemodel),
1362 &iter, NODE_COLUMN, &val);
1363 node = g_value_get_pointer(&val);
1364
1365 /* Shift+F10 draws a context menu */
1366 handled = gaim_gtk_blist_show_context_menu(node, gaim_gtk_treeview_popup_menu_position_func, tv, 0, GDK_CURRENT_TIME);
1367
1368 return handled;
1369 }
1370
1371 static void gaim_gtk_blist_buddy_details_cb(gpointer data, guint action, GtkWidget *item)
1372 {
1373 gaim_gtk_set_cursor(gtkblist->window, GDK_WATCH);
1374
1375 gaim_prefs_set_bool("/gaim/gtk/blist/show_buddy_icons",
1376 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
1377
1378 gaim_gtk_clear_cursor(gtkblist->window);
1379 }
1380
1381 static void gaim_gtk_blist_show_idle_time_cb(gpointer data, guint action, GtkWidget *item)
1382 {
1383 gaim_gtk_set_cursor(gtkblist->window, GDK_WATCH);
1384
1385 gaim_prefs_set_bool("/gaim/gtk/blist/show_idle_time",
1386 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
1387
1388 gaim_gtk_clear_cursor(gtkblist->window);
1389 }
1390
1391 static void gaim_gtk_blist_show_empty_groups_cb(gpointer data, guint action, GtkWidget *item)
1392 {
1393 gaim_gtk_set_cursor(gtkblist->window, GDK_WATCH);
1394
1395 gaim_prefs_set_bool("/gaim/gtk/blist/show_empty_groups",
1396 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
1397
1398 gaim_gtk_clear_cursor(gtkblist->window);
1399 }
1400
1401 static void gaim_gtk_blist_edit_mode_cb(gpointer callback_data, guint callback_action,
1402 GtkWidget *checkitem)
1403 {
1404 gaim_gtk_set_cursor(gtkblist->window, GDK_WATCH);
1405
1406 gaim_prefs_set_bool("/gaim/gtk/blist/show_offline_buddies",
1407 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(checkitem)));
1408
1409 gaim_gtk_clear_cursor(gtkblist->window);
1410 }
1411
1412 static void gaim_gtk_blist_mute_sounds_cb(gpointer data, guint action, GtkWidget *item)
1413 {
1414 gaim_prefs_set_bool("/gaim/gtk/sound/mute", GTK_CHECK_MENU_ITEM(item)->active);
1415 }
1416
1417 static void
1418 gaim_gtk_blist_mute_pref_cb(const char *name, GaimPrefType type,
1419 gconstpointer value, gpointer data)
1420 {
1421 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(gtkblist->ift,
1422 N_("/Tools/Mute Sounds"))), (gboolean)GPOINTER_TO_INT(value));
1423 }
1424
1425 static void
1426 gaim_gtk_blist_sound_method_pref_cb(const char *name, GaimPrefType type,
1427 gconstpointer value, gpointer data)
1428 {
1429 gboolean sensitive = TRUE;
1430
1431 if(!strcmp(value, "none"))
1432 sensitive = FALSE;
1433
1434 gtk_widget_set_sensitive(gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Mute Sounds")), sensitive);
1435 }
1436
1437 static void
1438 add_buddies_from_vcard(const char *prpl_id, GaimGroup *group, GList *list,
1439 const char *alias)
1440 {
1441 GList *l;
1442 GaimAccount *account = NULL;
1443 GaimConnection *gc;
1444
1445 if (list == NULL)
1446 return;
1447
1448 for (l = gaim_connections_get_all(); l != NULL; l = l->next)
1449 {
1450 gc = (GaimConnection *)l->data;
1451 account = gaim_connection_get_account(gc);
1452
1453 if (!strcmp(gaim_account_get_protocol_id(account), prpl_id))
1454 break;
1455
1456 account = NULL;
1457 }
1458
1459 if (account != NULL)
1460 {
1461 for (l = list; l != NULL; l = l->next)
1462 {
1463 gaim_blist_request_add_buddy(account, l->data,
1464 (group ? group->name : NULL),
1465 alias);
1466 }
1467 }
1468
1469 g_list_foreach(list, (GFunc)g_free, NULL);
1470 g_list_free(list);
1471 }
1472
1473 static gboolean
1474 parse_vcard(const char *vcard, GaimGroup *group)
1475 {
1476 char *temp_vcard;
1477 char *s, *c;
1478 char *alias = NULL;
1479 GList *aims = NULL;
1480 GList *icqs = NULL;
1481 GList *yahoos = NULL;
1482 GList *msns = NULL;
1483 GList *jabbers = NULL;
1484
1485 s = temp_vcard = g_strdup(vcard);
1486
1487 while (*s != '\0' && strncmp(s, "END:vCard", strlen("END:vCard")))
1488 {
1489 char *field, *value;
1490
1491 field = s;
1492
1493 /* Grab the field */
1494 while (*s != '\r' && *s != '\n' && *s != '\0' && *s != ':')
1495 s++;
1496
1497 if (*s == '\r') s++;
1498 if (*s == '\n')
1499 {
1500 s++;
1501 continue;
1502 }
1503
1504 if (*s != '\0') *s++ = '\0';
1505
1506 if ((c = strchr(field, ';')) != NULL)
1507 *c = '\0';
1508
1509 /* Proceed to the end of the line */
1510 value = s;
1511
1512 while (*s != '\r' && *s != '\n' && *s != '\0')
1513 s++;
1514
1515 if (*s == '\r') *s++ = '\0';
1516 if (*s == '\n') *s++ = '\0';
1517
1518 /* We only want to worry about a few fields here. */
1519 if (!strcmp(field, "FN"))
1520 alias = g_strdup(value);
1521 else if (!strcmp(field, "X-AIM") || !strcmp(field, "X-ICQ") ||
1522 !strcmp(field, "X-YAHOO") || !strcmp(field, "X-MSN") ||
1523 !strcmp(field, "X-JABBER"))
1524 {
1525 char **values = g_strsplit(value, ":", 0);
1526 char **im;
1527
1528 for (im = values; *im != NULL; im++)
1529 {
1530 if (!strcmp(field, "X-AIM"))
1531 aims = g_list_append(aims, g_strdup(*im));
1532 else if (!strcmp(field, "X-ICQ"))
1533 icqs = g_list_append(icqs, g_strdup(*im));
1534 else if (!strcmp(field, "X-YAHOO"))
1535 yahoos = g_list_append(yahoos, g_strdup(*im));
1536 else if (!strcmp(field, "X-MSN"))
1537 msns = g_list_append(msns, g_strdup(*im));
1538 else if (!strcmp(field, "X-JABBER"))
1539 jabbers = g_list_append(jabbers, g_strdup(*im));
1540 }
1541
1542 g_strfreev(values);
1543 }
1544 }
1545
1546 g_free(temp_vcard);
1547
1548 if (aims == NULL && icqs == NULL && yahoos == NULL &&
1549 msns == NULL && jabbers == NULL)
1550 {
1551 g_free(alias);
1552
1553 return FALSE;
1554 }
1555
1556 add_buddies_from_vcard("prpl-oscar", group, aims, alias);
1557 add_buddies_from_vcard("prpl-oscar", group, icqs, alias);
1558 add_buddies_from_vcard("prpl-yahoo", group, yahoos, alias);
1559 add_buddies_from_vcard("prpl-msn", group, msns, alias);
1560 add_buddies_from_vcard("prpl-jabber", group, jabbers, alias);
1561
1562 g_free(alias);
1563
1564 return TRUE;
1565 }
1566
1567 #ifdef _WIN32
1568 static void gaim_gtk_blist_drag_begin(GtkWidget *widget,
1569 GdkDragContext *drag_context, gpointer user_data)
1570 {
1571 gaim_gtk_blist_tooltip_destroy();
1572
1573
1574 /* Unhook the tooltip-timeout since we don't want a tooltip
1575 * to appear and obscure the dragging operation.
1576 * This is a workaround for GTK+ bug 107320. */
1577 if (gtkblist->timeout) {
1578 g_source_remove(gtkblist->timeout);
1579 gtkblist->timeout = 0;
1580 }
1581 }
1582 #endif
1583
1584 static void gaim_gtk_blist_drag_data_get_cb(GtkWidget *widget,
1585 GdkDragContext *dc,
1586 GtkSelectionData *data,
1587 guint info,
1588 guint time,
1589 gpointer null)
1590 {
1591
1592 if (data->target == gdk_atom_intern("GAIM_BLIST_NODE", FALSE))
1593 {
1594 GtkTreeRowReference *ref = g_object_get_data(G_OBJECT(dc), "gtk-tree-view-source-row");
1595 GtkTreePath *sourcerow = gtk_tree_row_reference_get_path(ref);
1596 GtkTreeIter iter;
1597 GaimBlistNode *node = NULL;
1598 GValue val;
1599 if(!sourcerow)
1600 return;
1601 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, sourcerow);
1602 val.g_type = 0;
1603 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val);
1604 node = g_value_get_pointer(&val);
1605 gtk_selection_data_set (data,
1606 gdk_atom_intern ("GAIM_BLIST_NODE", FALSE),
1607 8, /* bits */
1608 (void*)&node,
1609 sizeof (node));
1610
1611 gtk_tree_path_free(sourcerow);
1612 }
1613 else if (data->target == gdk_atom_intern("application/x-im-contact", FALSE))
1614 {
1615 GtkTreeRowReference *ref;
1616 GtkTreePath *sourcerow;
1617 GtkTreeIter iter;
1618 GaimBlistNode *node = NULL;
1619 GaimBuddy *buddy;
1620 GaimConnection *gc;
1621 GValue val;
1622 GString *str;
1623 const char *protocol;
1624
1625 ref = g_object_get_data(G_OBJECT(dc), "gtk-tree-view-source-row");
1626 sourcerow = gtk_tree_row_reference_get_path(ref);
1627
1628 if (!sourcerow)
1629 return;
1630
1631 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
1632 sourcerow);
1633 val.g_type = 0;
1634 gtk_tree_model_get_value(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
1635 NODE_COLUMN, &val);
1636
1637 node = g_value_get_pointer(&val);
1638
1639 if (GAIM_BLIST_NODE_IS_CONTACT(node))
1640 {
1641 buddy = gaim_contact_get_priority_buddy((GaimContact *)node);
1642 }
1643 else if (!GAIM_BLIST_NODE_IS_BUDDY(node))
1644 {
1645 gtk_tree_path_free(sourcerow);
1646 return;
1647 }
1648 else
1649 {
1650 buddy = (GaimBuddy *)node;
1651 }
1652
1653 gc = gaim_account_get_connection(buddy->account);
1654
1655 if (gc == NULL)
1656 {
1657 gtk_tree_path_free(sourcerow);
1658 return;
1659 }
1660
1661 protocol =
1662 GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->list_icon(buddy->account,
1663 buddy);
1664
1665 str = g_string_new(NULL);
1666 g_string_printf(str,
1667 "MIME-Version: 1.0\r\n"
1668 "Content-Type: application/x-im-contact\r\n"
1669 "X-IM-Protocol: %s\r\n"
1670 "X-IM-Username: %s\r\n",
1671 protocol,
1672 buddy->name);
1673
1674 if (buddy->alias != NULL)
1675 {
1676 g_string_append_printf(str,
1677 "X-IM-Alias: %s\r\n",
1678 buddy->alias);
1679 }
1680
1681 g_string_append(str, "\r\n");
1682
1683 gtk_selection_data_set(data,
1684 gdk_atom_intern("application/x-im-contact", FALSE),
1685 8, /* bits */
1686 (const guchar *)str->str,
1687 strlen(str->str) + 1);
1688
1689 g_string_free(str, TRUE);
1690 gtk_tree_path_free(sourcerow);
1691 }
1692 }
1693
1694 static void gaim_gtk_blist_drag_data_rcv_cb(GtkWidget *widget, GdkDragContext *dc, guint x, guint y,
1695 GtkSelectionData *sd, guint info, guint t)
1696 {
1697 if (gtkblist->drag_timeout) {
1698 g_source_remove(gtkblist->drag_timeout);
1699 gtkblist->drag_timeout = 0;
1700 }
1701
1702 if (sd->target == gdk_atom_intern("GAIM_BLIST_NODE", FALSE) && sd->data) {
1703 GaimBlistNode *n = NULL;
1704 GtkTreePath *path = NULL;
1705 GtkTreeViewDropPosition position;
1706 memcpy(&n, sd->data, sizeof(n));
1707 if(gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget), x, y, &path, &position)) {
1708 /* if we're here, I think it means the drop is ok */
1709 GtkTreeIter iter;
1710 GaimBlistNode *node;
1711 GValue val;
1712 struct _gaim_gtk_blist_node *gtknode;
1713
1714 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
1715 &iter, path);
1716 val.g_type = 0;
1717 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel),
1718 &iter, NODE_COLUMN, &val);
1719 node = g_value_get_pointer(&val);
1720 gtknode = node->ui_data;
1721
1722 if (GAIM_BLIST_NODE_IS_CONTACT(n)) {
1723 GaimContact *c = (GaimContact*)n;
1724 if (GAIM_BLIST_NODE_IS_CONTACT(node) && gtknode->contact_expanded) {
1725 gaim_blist_merge_contact(c, node);
1726 } else if (GAIM_BLIST_NODE_IS_CONTACT(node) ||
1727 GAIM_BLIST_NODE_IS_CHAT(node)) {
1728 switch(position) {
1729 case GTK_TREE_VIEW_DROP_AFTER:
1730 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
1731 gaim_blist_add_contact(c, (GaimGroup*)node->parent,
1732 node);
1733 break;
1734 case GTK_TREE_VIEW_DROP_BEFORE:
1735 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
1736 gaim_blist_add_contact(c, (GaimGroup*)node->parent,
1737 node->prev);
1738 break;
1739 }
1740 } else if(GAIM_BLIST_NODE_IS_GROUP(node)) {
1741 gaim_blist_add_contact(c, (GaimGroup*)node, NULL);
1742 } else if(GAIM_BLIST_NODE_IS_BUDDY(node)) {
1743 gaim_blist_merge_contact(c, node);
1744 }
1745 } else if (GAIM_BLIST_NODE_IS_BUDDY(n)) {
1746 GaimBuddy *b = (GaimBuddy*)n;
1747 if (GAIM_BLIST_NODE_IS_BUDDY(node)) {
1748 switch(position) {
1749 case GTK_TREE_VIEW_DROP_AFTER:
1750 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
1751 gaim_blist_add_buddy(b, (GaimContact*)node->parent,
1752 (GaimGroup*)node->parent->parent, node);
1753 break;
1754 case GTK_TREE_VIEW_DROP_BEFORE:
1755 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
1756 gaim_blist_add_buddy(b, (GaimContact*)node->parent,
1757 (GaimGroup*)node->parent->parent,
1758 node->prev);
1759 break;
1760 }
1761 } else if(GAIM_BLIST_NODE_IS_CHAT(node)) {
1762 gaim_blist_add_buddy(b, NULL, (GaimGroup*)node->parent,
1763 NULL);
1764 } else if (GAIM_BLIST_NODE_IS_GROUP(node)) {
1765 gaim_blist_add_buddy(b, NULL, (GaimGroup*)node, NULL);
1766 } else if (GAIM_BLIST_NODE_IS_CONTACT(node)) {
1767 if(gtknode->contact_expanded) {
1768 switch(position) {
1769 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
1770 case GTK_TREE_VIEW_DROP_AFTER:
1771 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
1772 gaim_blist_add_buddy(b, (GaimContact*)node,
1773 (GaimGroup*)node->parent, NULL);
1774 break;
1775 case GTK_TREE_VIEW_DROP_BEFORE:
1776 gaim_blist_add_buddy(b, NULL,
1777 (GaimGroup*)node->parent, node->prev);
1778 break;
1779 }
1780 } else {
1781 switch(position) {
1782 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
1783 case GTK_TREE_VIEW_DROP_AFTER:
1784 gaim_blist_add_buddy(b, NULL,
1785 (GaimGroup*)node->parent, NULL);
1786 break;
1787 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
1788 case GTK_TREE_VIEW_DROP_BEFORE:
1789 gaim_blist_add_buddy(b, NULL,
1790 (GaimGroup*)node->parent, node->prev);
1791 break;
1792 }
1793 }
1794 }
1795 } else if (GAIM_BLIST_NODE_IS_CHAT(n)) {
1796 GaimChat *chat = (GaimChat *)n;
1797 if (GAIM_BLIST_NODE_IS_BUDDY(node)) {
1798 switch(position) {
1799 case GTK_TREE_VIEW_DROP_AFTER:
1800 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
1801 case GTK_TREE_VIEW_DROP_BEFORE:
1802 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
1803 gaim_blist_add_chat(chat,
1804 (GaimGroup*)node->parent->parent,
1805 node->parent);
1806 break;
1807 }
1808 } else if(GAIM_BLIST_NODE_IS_CONTACT(node) ||
1809 GAIM_BLIST_NODE_IS_CHAT(node)) {
1810 switch(position) {
1811 case GTK_TREE_VIEW_DROP_AFTER:
1812 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
1813 gaim_blist_add_chat(chat, (GaimGroup*)node->parent, node);
1814 break;
1815 case GTK_TREE_VIEW_DROP_BEFORE:
1816 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
1817 gaim_blist_add_chat(chat, (GaimGroup*)node->parent, node->prev);
1818 break;
1819 }
1820 } else if (GAIM_BLIST_NODE_IS_GROUP(node)) {
1821 gaim_blist_add_chat(chat, (GaimGroup*)node, NULL);
1822 }
1823 } else if (GAIM_BLIST_NODE_IS_GROUP(n)) {
1824 GaimGroup *g = (GaimGroup*)n;
1825 if (GAIM_BLIST_NODE_IS_GROUP(node)) {
1826 switch (position) {
1827 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
1828 case GTK_TREE_VIEW_DROP_AFTER:
1829 gaim_blist_add_group(g, node);
1830 break;
1831 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
1832 case GTK_TREE_VIEW_DROP_BEFORE:
1833 gaim_blist_add_group(g, node->prev);
1834 break;
1835 }
1836 } else if(GAIM_BLIST_NODE_IS_BUDDY(node)) {
1837 gaim_blist_add_group(g, node->parent->parent);
1838 } else if(GAIM_BLIST_NODE_IS_CONTACT(node) ||
1839 GAIM_BLIST_NODE_IS_CHAT(node)) {
1840 gaim_blist_add_group(g, node->parent);
1841 }
1842 }
1843
1844 gtk_tree_path_free(path);
1845 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
1846 }
1847 }
1848 else if (sd->target == gdk_atom_intern("application/x-im-contact",
1849 FALSE) && sd->data)
1850 {
1851 GaimGroup *group = NULL;
1852 GtkTreePath *path = NULL;
1853 GtkTreeViewDropPosition position;
1854 GaimAccount *account;
1855 char *protocol = NULL;
1856 char *username = NULL;
1857 char *alias = NULL;
1858
1859 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
1860 x, y, &path, &position))
1861 {
1862 GtkTreeIter iter;
1863 GaimBlistNode *node;
1864 GValue val;
1865
1866 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
1867 &iter, path);
1868 val.g_type = 0;
1869 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel),
1870 &iter, NODE_COLUMN, &val);
1871 node = g_value_get_pointer(&val);
1872
1873 if (GAIM_BLIST_NODE_IS_BUDDY(node))
1874 {
1875 group = (GaimGroup *)node->parent->parent;
1876 }
1877 else if (GAIM_BLIST_NODE_IS_CHAT(node) ||
1878 GAIM_BLIST_NODE_IS_CONTACT(node))
1879 {
1880 group = (GaimGroup *)node->parent;
1881 }
1882 else if (GAIM_BLIST_NODE_IS_GROUP(node))
1883 {
1884 group = (GaimGroup *)node;
1885 }
1886 }
1887
1888 if (gaim_gtk_parse_x_im_contact((const char *)sd->data, FALSE, &account,
1889 &protocol, &username, &alias))
1890 {
1891 if (account == NULL)
1892 {
1893 gaim_notify_error(NULL, NULL,
1894 _("You are not currently signed on with an account that "
1895 "can add that buddy."), NULL);
1896 }
1897 else
1898 {
1899 gaim_blist_request_add_buddy(account, username,
1900 (group ? group->name : NULL),
1901 alias);
1902 }
1903 }
1904
1905 g_free(username);
1906 g_free(protocol);
1907 g_free(alias);
1908
1909 if (path != NULL)
1910 gtk_tree_path_free(path);
1911
1912 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
1913 }
1914 else if (sd->target == gdk_atom_intern("text/x-vcard", FALSE) && sd->data)
1915 {
1916 gboolean result;
1917 GaimGroup *group = NULL;
1918 GtkTreePath *path = NULL;
1919 GtkTreeViewDropPosition position;
1920
1921 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
1922 x, y, &path, &position))
1923 {
1924 GtkTreeIter iter;
1925 GaimBlistNode *node;
1926 GValue val;
1927
1928 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
1929 &iter, path);
1930 val.g_type = 0;
1931 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel),
1932 &iter, NODE_COLUMN, &val);
1933 node = g_value_get_pointer(&val);
1934
1935 if (GAIM_BLIST_NODE_IS_BUDDY(node))
1936 {
1937 group = (GaimGroup *)node->parent->parent;
1938 }
1939 else if (GAIM_BLIST_NODE_IS_CHAT(node) ||
1940 GAIM_BLIST_NODE_IS_CONTACT(node))
1941 {
1942 group = (GaimGroup *)node->parent;
1943 }
1944 else if (GAIM_BLIST_NODE_IS_GROUP(node))
1945 {
1946 group = (GaimGroup *)node;
1947 }
1948 }
1949
1950 result = parse_vcard((const gchar *)sd->data, group);
1951
1952 gtk_drag_finish(dc, result, (dc->action == GDK_ACTION_MOVE), t);
1953 } else if (sd->target == gdk_atom_intern("text/uri-list", FALSE) && sd->data) {
1954 GtkTreePath *path = NULL;
1955 GtkTreeViewDropPosition position;
1956
1957 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
1958 x, y, &path, &position))
1959 {
1960 GtkTreeIter iter;
1961 GaimBlistNode *node;
1962 GValue val;
1963
1964 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
1965 &iter, path);
1966 val.g_type = 0;
1967 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel),
1968 &iter, NODE_COLUMN, &val);
1969 node = g_value_get_pointer(&val);
1970
1971 if (GAIM_BLIST_NODE_IS_BUDDY(node) || GAIM_BLIST_NODE_IS_CONTACT(node)) {
1972 GaimBuddy *b = GAIM_BLIST_NODE_IS_BUDDY(node) ? (GaimBuddy*)node : gaim_contact_get_priority_buddy((GaimContact*)node);
1973 gaim_dnd_file_manage(sd, b->account, b->name);
1974 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
1975 } else {
1976 gtk_drag_finish(dc, FALSE, FALSE, t);
1977 }
1978 }
1979 }
1980 }
1981
1982 static GdkPixbuf *gaim_gtk_blist_get_buddy_icon(GaimBlistNode *node,
1983 gboolean scaled, gboolean greyed)
1984 {
1985 GdkPixbuf *buf, *ret = NULL;
1986 GdkPixbufLoader *loader;
1987 GaimBuddyIcon *icon;
1988 const guchar *data;
1989 gsize len;
1990 GaimBuddy *buddy = (GaimBuddy *)node;
1991
1992 if(GAIM_BLIST_NODE_IS_CONTACT(node)) {
1993 buddy = gaim_contact_get_priority_buddy((GaimContact*)node);
1994 } else if(GAIM_BLIST_NODE_IS_BUDDY(node)) {
1995 buddy = (GaimBuddy*)node;
1996 } else {
1997 return NULL;
1998 }
1999
2000 #if 0
2001 if (!gaim_prefs_get_bool("/gaim/gtk/blist/show_buddy_icons"))
2002 return NULL;
2003 #endif
2004
2005 if (!(icon = gaim_buddy_get_icon(buddy)))
2006 if (!(icon = gaim_buddy_icons_find(buddy->account, buddy->name))) /* Not sure I like this...*/
2007 return NULL;
2008
2009 loader = gdk_pixbuf_loader_new();
2010 data = gaim_buddy_icon_get_data(icon, &len);
2011 gdk_pixbuf_loader_write(loader, data, len, NULL);
2012 gdk_pixbuf_loader_close(loader, NULL);
2013 buf = gdk_pixbuf_loader_get_pixbuf(loader);
2014 if (buf)
2015 g_object_ref(G_OBJECT(buf));
2016 g_object_unref(G_OBJECT(loader));
2017
2018 if (buf) {
2019 GaimAccount *account = gaim_buddy_get_account(buddy);
2020 GaimPluginProtocolInfo *prpl_info = NULL;
2021 int orig_width, orig_height;
2022 int scale_width, scale_height;
2023
2024 if(account && account->gc)
2025 prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(account->gc->prpl);
2026
2027 if (greyed) {
2028 GaimPresence *presence = gaim_buddy_get_presence(buddy);
2029 if (!GAIM_BUDDY_IS_ONLINE(buddy))
2030 gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.0, FALSE);
2031 if (gaim_presence_is_idle(presence))
2032 gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.25, FALSE);
2033 }
2034
2035 /* i'd use the gaim_gtk_buddy_icon_get_scale_size() thing,
2036 * but it won't tell me the original size, which I need for scaling
2037 * purposes */
2038 scale_width = orig_width = gdk_pixbuf_get_width(buf);
2039 scale_height = orig_height = gdk_pixbuf_get_height(buf);
2040
2041 gaim_buddy_icon_get_scale_size(prpl_info ? &prpl_info->icon_spec : NULL, &scale_width, &scale_height);
2042
2043 if (scaled) {
2044 if(scale_height > scale_width) {
2045 scale_width = 30.0 * (double)scale_width / (double)scale_height;
2046 scale_height = 30;
2047 } else {
2048 scale_height = 30.0 * (double)scale_height / (double)scale_width;
2049 scale_width = 30;
2050 }
2051
2052 ret = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 30, 30);
2053 gdk_pixbuf_fill(ret, 0x00000000);
2054 gdk_pixbuf_scale(buf, ret, (30-scale_width)/2, (30-scale_height)/2, scale_width, scale_height, (30-scale_width)/2, (30-scale_height)/2, (double)scale_width/(double)orig_width, (double)scale_height/(double)orig_height, GDK_INTERP_BILINEAR);
2055 } else {
2056 ret = gdk_pixbuf_scale_simple(buf,scale_width,scale_height, GDK_INTERP_BILINEAR);
2057 }
2058 g_object_unref(G_OBJECT(buf));
2059 }
2060
2061 return ret;
2062 }
2063
2064 struct tooltip_data {
2065 PangoLayout *layout;
2066 GdkPixbuf *status_icon;
2067 GdkPixbuf *avatar;
2068 int avatar_width;
2069 int width;
2070 int height;
2071 };
2072
2073 static struct tooltip_data * create_tip_for_node(GaimBlistNode *node, gboolean full)
2074 {
2075 char *tooltip_text = NULL;
2076 struct tooltip_data *td = g_new0(struct tooltip_data, 1);
2077
2078 td->status_icon = gaim_gtk_blist_get_status_icon(node, GAIM_STATUS_ICON_LARGE);
2079 td->avatar = gaim_gtk_blist_get_buddy_icon(node, !full, FALSE);
2080 tooltip_text = gaim_get_tooltip_text(node, full);
2081 td->layout = gtk_widget_create_pango_layout(gtkblist->tipwindow, NULL);
2082 pango_layout_set_markup(td->layout, tooltip_text, -1);
2083 pango_layout_set_wrap(td->layout, PANGO_WRAP_WORD);
2084 pango_layout_set_width(td->layout, 300000);
2085
2086 pango_layout_get_size (td->layout, &td->width, &td->height);
2087 td->width = PANGO_PIXELS(td->width) + 38 + 8;
2088 td->height = MAX(PANGO_PIXELS(td->height + 4) + 8, 38);
2089
2090 if(td->avatar) {
2091 td->avatar_width = gdk_pixbuf_get_width(td->avatar);
2092 td->width += td->avatar_width + 8;
2093 td->height = MAX(td->height, gdk_pixbuf_get_height(td->avatar) + 8);
2094 }
2095
2096 g_free(tooltip_text);
2097 return td;
2098 }
2099
2100 static void gaim_gtk_blist_paint_tip(GtkWidget *widget, GdkEventExpose *event, GaimBlistNode *node)
2101 {
2102 GtkStyle *style;
2103 int current_height, max_width;
2104 GList *l;
2105
2106 if(gtkblist->tooltipdata == NULL)
2107 return;
2108
2109 style = gtkblist->tipwindow->style;
2110 gtk_paint_flat_box(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
2111 NULL, gtkblist->tipwindow, "tooltip", 0, 0, -1, -1);
2112
2113 max_width = 0;
2114 for(l = gtkblist->tooltipdata; l; l = l->next)
2115 {
2116 struct tooltip_data *td = l->data;
2117 max_width = MAX(max_width, td->width);
2118 }
2119
2120 current_height = 4;
2121 for(l = gtkblist->tooltipdata; l; l = l->next)
2122 {
2123 struct tooltip_data *td = l->data;
2124
2125 #if GTK_CHECK_VERSION(2,2,0)
2126 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, td->status_icon,
2127 0, 0, 4, current_height, -1 , -1, GDK_RGB_DITHER_NONE, 0, 0);
2128 if(td->avatar)
2129 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL,
2130 td->avatar, 0, 0, max_width - (td->avatar_width + 4), current_height, -1 , -1, GDK_RGB_DITHER_NONE, 0, 0);
2131 #else
2132 gdk_pixbuf_render_to_drawable(td->status_icon, GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, 0, 0, 4, current_height, -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
2133 if(td->avatar)
2134 gdk_pixbuf_render_to_drawable(td->avatar,
2135 GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, 0, 0,
2136 max_width - (td->avatar_width + 4),
2137 current_height, -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
2138 #endif
2139
2140 gtk_paint_layout (style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, FALSE,
2141 NULL, gtkblist->tipwindow, "tooltip", 38 + 4, current_height, td->layout);
2142
2143 current_height += td->height;
2144
2145 if(l->next)
2146 gtk_paint_hline(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, NULL, NULL, NULL, 4, max_width - 4, current_height-6);
2147
2148 }
2149 }
2150
2151 static void gaim_gtk_blist_tooltip_destroy()
2152 {
2153 while(gtkblist->tooltipdata) {
2154 struct tooltip_data *td = gtkblist->tooltipdata->data;
2155
2156 if(td->avatar)
2157 g_object_unref(td->avatar);
2158 if(td->status_icon)
2159 g_object_unref(td->status_icon);
2160 g_object_unref(td->layout);
2161 g_free(td);
2162 gtkblist->tooltipdata = g_list_delete_link(gtkblist->tooltipdata, gtkblist->tooltipdata);
2163 }
2164
2165 if (gtkblist->tipwindow == NULL)
2166 return;
2167
2168 gtk_widget_destroy(gtkblist->tipwindow);
2169 gtkblist->tipwindow = NULL;
2170 }
2171
2172 static gboolean gaim_gtk_blist_expand_timeout(GtkWidget *tv)
2173 {
2174 GtkTreePath *path;
2175 GtkTreeIter iter;
2176 GaimBlistNode *node;
2177 GValue val;
2178 struct _gaim_gtk_blist_node *gtknode;
2179
2180 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), gtkblist->tip_rect.x, gtkblist->tip_rect.y, &path, NULL, NULL, NULL))
2181 return FALSE;
2182 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
2183 val.g_type = 0;
2184 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val);
2185 node = g_value_get_pointer(&val);
2186
2187 if(!GAIM_BLIST_NODE_IS_CONTACT(node)) {
2188 gtk_tree_path_free(path);
2189 return FALSE;
2190 }
2191
2192 gtknode = node->ui_data;
2193
2194 if (!gtknode->contact_expanded) {
2195 GtkTreeIter i;
2196
2197 gaim_gtk_blist_expand_contact_cb(NULL, node);
2198
2199 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &gtkblist->contact_rect);
2200 gdk_drawable_get_size(GDK_DRAWABLE(tv->window), &(gtkblist->contact_rect.width), NULL);
2201 gtkblist->mouseover_contact = node;
2202 gtk_tree_path_down (path);
2203 while (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &i, path)) {
2204 GdkRectangle rect;
2205 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &rect);
2206 gtkblist->contact_rect.height += rect.height;
2207 gtk_tree_path_next(path);
2208 }
2209 }
2210 gtk_tree_path_free(path);
2211 return FALSE;
2212 }
2213
2214 static gboolean buddy_is_displayable(GaimBuddy *buddy)
2215 {
2216 struct _gaim_gtk_blist_node *gtknode;
2217
2218 if(!buddy)
2219 return FALSE;
2220
2221 gtknode = ((GaimBlistNode*)buddy)->ui_data;
2222
2223 return (gaim_account_is_connected(buddy->account) &&
2224 (gaim_presence_is_online(buddy->presence) ||
2225 (gtknode && gtknode->recent_signonoff) ||
2226 gaim_prefs_get_bool("/gaim/gtk/blist/show_offline_buddies") ||
2227 gaim_blist_node_get_bool((GaimBlistNode*)buddy, "show_offline")));
2228 }
2229
2230 static gboolean gaim_gtk_blist_tooltip_timeout(GtkWidget *tv)
2231 {
2232 GtkTreePath *path;
2233 GtkTreeIter iter;
2234 GaimBlistNode *node;
2235 GValue val;
2236 int scr_w, scr_h, w, h, x, y;
2237 #if GTK_CHECK_VERSION(2,2,0)
2238 int mon_num;
2239 GdkScreen *screen = NULL;
2240 #endif
2241 gboolean tooltip_top = FALSE;
2242 struct _gaim_gtk_blist_node *gtknode;
2243 GdkRectangle mon_size;
2244
2245 /*
2246 * Attempt to free the previous tooltip. I have a feeling
2247 * this is never needed... but just in case.
2248 */
2249 gaim_gtk_blist_tooltip_destroy();
2250
2251 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), gtkblist->tip_rect.x, gtkblist->tip_rect.y, &path, NULL, NULL, NULL))
2252 return FALSE;
2253 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
2254 val.g_type = 0;
2255 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val);
2256 node = g_value_get_pointer(&val);
2257
2258 gtk_tree_path_free(path);
2259
2260 gtkblist->tipwindow = gtk_window_new(GTK_WINDOW_POPUP);
2261
2262 if(GAIM_BLIST_NODE_IS_CHAT(node) || GAIM_BLIST_NODE_IS_BUDDY(node)) {
2263 struct tooltip_data *td = create_tip_for_node(node, TRUE);
2264 gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
2265 w = td->width;
2266 h = td->height;
2267 } else if(GAIM_BLIST_NODE_IS_CONTACT(node)) {
2268 GaimBlistNode *child;
2269 GaimBuddy *b = gaim_contact_get_priority_buddy((GaimContact *)node);
2270 w = h = 0;
2271 for(child = node->child; child; child = child->next)
2272 {
2273 if(GAIM_BLIST_NODE_IS_BUDDY(child) && buddy_is_displayable((GaimBuddy*)child)) {
2274 struct tooltip_data *td = create_tip_for_node(child, (b == (GaimBuddy*)child));
2275 if (b == (GaimBuddy *)child) {
2276 gtkblist->tooltipdata = g_list_prepend(gtkblist->tooltipdata, td);
2277 } else {
2278 gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
2279 }
2280 w = MAX(w, td->width);
2281 h += td->height;
2282 }
2283 }
2284 } else {
2285 gtk_widget_destroy(gtkblist->tipwindow);
2286 gtkblist->tipwindow = NULL;
2287 return FALSE;
2288 }
2289
2290 if (gtkblist->tooltipdata == NULL) {
2291 gtk_widget_destroy(gtkblist->tipwindow);
2292 gtkblist->tipwindow = NULL;
2293 return FALSE;
2294 }
2295
2296 gtknode = node->ui_data;
2297
2298 gtk_widget_set_app_paintable(gtkblist->tipwindow, TRUE);
2299 gtk_window_set_resizable(GTK_WINDOW(gtkblist->tipwindow), FALSE);
2300 gtk_widget_set_name(gtkblist->tipwindow, "gtk-tooltips");
2301 g_signal_connect(G_OBJECT(gtkblist->tipwindow), "expose_event",
2302 G_CALLBACK(gaim_gtk_blist_paint_tip), NULL);
2303 gtk_widget_ensure_style (gtkblist->tipwindow);
2304
2305
2306 #if GTK_CHECK_VERSION(2,2,0)
2307 gdk_display_get_pointer(gdk_display_get_default(), &screen, &x, &y, NULL);
2308 mon_num = gdk_screen_get_monitor_at_point(screen, x, y);
2309 gdk_screen_get_monitor_geometry(screen, mon_num, &mon_size);
2310
2311 scr_w = mon_size.width + mon_size.x;
2312 scr_h = mon_size.height + mon_size.y;
2313 #else
2314 scr_w = gdk_screen_width();
2315 scr_h = gdk_screen_height();
2316 gdk_window_get_pointer(NULL, &x, &y, NULL);
2317 mon_size.x = 0;
2318 mon_size.y = 0;
2319 #endif
2320
2321 #if GTK_CHECK_VERSION(2,2,0)
2322 if (w > mon_size.width)
2323 w = mon_size.width - 10;
2324
2325 if (h > mon_size.height)
2326 h = mon_size.height - 10;
2327 #endif
2328
2329 if (GTK_WIDGET_NO_WINDOW(gtkblist->window))
2330 y+=gtkblist->window->allocation.y;
2331
2332 x -= ((w >> 1) + 4);
2333
2334 if ((y + h + 4) > scr_h || tooltip_top)
2335 y = y - h - 5;
2336 else
2337 y = y + 6;
2338
2339 if (y < mon_size.y)
2340 y = mon_size.y;
2341
2342 if (y != mon_size.y) {
2343 if ((x + w) > scr_w)
2344 x -= (x + w + 5) - scr_w;
2345 else if (x < mon_size.x)
2346 x = mon_size.x;
2347 } else {
2348 x -= (w / 2 + 10);
2349 if (x < mon_size.x)
2350 x = mon_size.x;
2351 }
2352
2353 gtk_widget_set_size_request(gtkblist->tipwindow, w, h);
2354 gtk_window_move(GTK_WINDOW(gtkblist->tipwindow), x, y);
2355 gtk_widget_show(gtkblist->tipwindow);
2356
2357 return FALSE;
2358 }
2359
2360 static gboolean gaim_gtk_blist_drag_motion_cb(GtkWidget *tv, GdkDragContext *drag_context,
2361 gint x, gint y, guint time, gpointer user_data)
2362 {
2363 GtkTreePath *path;
2364 int delay;
2365
2366 /*
2367 * When dragging a buddy into a contact, this is the delay before
2368 * the contact auto-expands.
2369 */
2370 delay = 900;
2371
2372 if (gtkblist->drag_timeout) {
2373 if ((y > gtkblist->tip_rect.y) && ((y - gtkblist->tip_rect.height) < gtkblist->tip_rect.y))
2374 return FALSE;
2375 /* We've left the cell. Remove the timeout and create a new one below */
2376 g_source_remove(gtkblist->drag_timeout);
2377 }
2378
2379 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), x, y, &path, NULL, NULL, NULL);
2380 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &gtkblist->tip_rect);
2381
2382 if (path)
2383 gtk_tree_path_free(path);
2384 gtkblist->drag_timeout = g_timeout_add(delay, (GSourceFunc)gaim_gtk_blist_expand_timeout, tv);
2385
2386 if (gtkblist->mouseover_contact) {
2387 if ((y < gtkblist->contact_rect.y) || ((y - gtkblist->contact_rect.height) > gtkblist->contact_rect.y)) {
2388 gaim_gtk_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
2389 gtkblist->mouseover_contact = NULL;
2390 }
2391 }
2392
2393 return FALSE;
2394 }
2395
2396 static gboolean gaim_gtk_blist_motion_cb (GtkWidget *tv, GdkEventMotion *event, gpointer null)
2397 {
2398 GtkTreePath *path;
2399 int delay;
2400
2401 delay = gaim_prefs_get_int("/gaim/gtk/blist/tooltip_delay");
2402
2403 if (delay == 0)
2404 return FALSE;
2405
2406 if (gtkblist->timeout) {
2407 if ((event->y > gtkblist->tip_rect.y) && ((event->y - gtkblist->tip_rect.height) < gtkblist->tip_rect.y))
2408 return FALSE;
2409 /* We've left the cell. Remove the timeout and create a new one below */
2410 gaim_gtk_blist_tooltip_destroy();
2411 g_source_remove(gtkblist->timeout);
2412 }
2413
2414 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL);
2415 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &gtkblist->tip_rect);
2416
2417 if (path)
2418 gtk_tree_path_free(path);
2419 gtkblist->timeout = g_timeout_add(delay, (GSourceFunc)gaim_gtk_blist_tooltip_timeout, tv);
2420
2421 if (gtkblist->mouseover_contact) {
2422 if ((event->y < gtkblist->contact_rect.y) || ((event->y - gtkblist->contact_rect.height) > gtkblist->contact_rect.y)) {
2423 gaim_gtk_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
2424 gtkblist->mouseover_contact = NULL;
2425 }
2426 }
2427
2428 return FALSE;
2429 }
2430
2431 static void gaim_gtk_blist_leave_cb (GtkWidget *w, GdkEventCrossing *e, gpointer n)
2432 {
2433
2434 if (gtkblist->timeout) {
2435 g_source_remove(gtkblist->timeout);
2436 gtkblist->timeout = 0;
2437 }
2438
2439 if (gtkblist->drag_timeout) {
2440 g_source_remove(gtkblist->drag_timeout);
2441 gtkblist->drag_timeout = 0;
2442 }
2443
2444 gaim_gtk_blist_tooltip_destroy();
2445
2446 if (gtkblist->mouseover_contact &&
2447 !((e->x > gtkblist->contact_rect.x) && (e->x < (gtkblist->contact_rect.x + gtkblist->contact_rect.width)) &&
2448 (e->y > gtkblist->contact_rect.y) && (e->y < (gtkblist->contact_rect.y + gtkblist->contact_rect.height)))) {
2449 gaim_gtk_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
2450 gtkblist->mouseover_contact = NULL;
2451 }
2452 }
2453
2454 static void
2455 toggle_debug(void)
2456 {
2457 gaim_prefs_set_bool("/gaim/gtk/debug/enabled",
2458 !gaim_prefs_get_bool("/gaim/gtk/debug/enabled"));
2459 }
2460
2461
2462 /***************************************************
2463 * Crap *
2464 ***************************************************/
2465 static GtkItemFactoryEntry blist_menu[] =
2466 {
2467 /* Buddies menu */
2468 { N_("/_Buddies"), NULL, NULL, 0, "<Branch>", NULL },
2469 { N_("/Buddies/New Instant _Message..."), "<CTL>M", gaim_gtkdialogs_im, 0, "<StockItem>", GAIM_STOCK_IM },
2470 { N_("/Buddies/Join a _Chat..."), "<CTL>C", gaim_gtk_blist_joinchat_show, 0, "<StockItem>", GAIM_STOCK_CHAT },
2471 { N_("/Buddies/Get User _Info..."), "<CTL>I", gaim_gtkdialogs_info, 0, "<StockItem>", GAIM_STOCK_INFO },
2472 { N_("/Buddies/View User _Log..."), "<CTL>L", gaim_gtkdialogs_log, 0, "<StockItem>", GAIM_STOCK_LOG },
2473 { "/Buddies/sep1", NULL, NULL, 0, "<Separator>", NULL },
2474 { N_("/Buddies/Show _Offline Buddies"), NULL, gaim_gtk_blist_edit_mode_cb, 1, "<CheckItem>", NULL },
2475 { N_("/Buddies/Show _Empty Groups"), NULL, gaim_gtk_blist_show_empty_groups_cb, 1, "<CheckItem>", NULL },
2476 { N_("/Buddies/Show Buddy _Details"), NULL, gaim_gtk_blist_buddy_details_cb, 1, "<CheckItem>", NULL },
2477 { N_("/Buddies/Show Idle _Times"), NULL, gaim_gtk_blist_show_idle_time_cb, 1, "<CheckItem>", NULL },
2478 { N_("/Buddies/_Sort Buddies"), NULL, NULL, 0, "<Branch>", NULL },
2479 { "/Buddies/sep2", NULL, NULL, 0, "<Separator>", NULL },
2480 { N_("/Buddies/_Add Buddy..."), "<CTL>B", gaim_gtk_blist_add_buddy_cb, 0, "<StockItem>", GTK_STOCK_ADD },
2481 { N_("/Buddies/Add C_hat..."), NULL, gaim_gtk_blist_add_chat_cb, 0, "<StockItem>", GTK_STOCK_ADD },
2482 { N_("/Buddies/Add _Group..."), NULL, gaim_blist_request_add_group, 0, "<StockItem>", GTK_STOCK_ADD },
2483 { "/Buddies/sep3", NULL, NULL, 0, "<Separator>", NULL },
2484 { N_("/Buddies/_Quit"), "<CTL>Q", gaim_core_quit, 0, "<StockItem>", GTK_STOCK_QUIT },
2485
2486 /* Accounts menu */
2487 { N_("/_Accounts"), NULL, NULL, 0, "<Branch>", NULL },
2488 { N_("/Accounts/Add\\/Edit"), "<CTL>A", gaim_gtk_accounts_window_show, 0, "<StockItem>", GAIM_STOCK_ACCOUNTS },
2489
2490 /* Tools */
2491 { N_("/_Tools"), NULL, NULL, 0, "<Branch>", NULL },
2492 { N_("/Tools/Buddy _Pounces"), NULL, gaim_gtk_pounces_manager_show, 0, "<StockItem>", GAIM_STOCK_POUNCE },
2493 { N_("/Tools/Plu_gins"), "<CTL>U", gaim_gtk_plugin_dialog_show, 0, "<StockItem>", GAIM_STOCK_PLUGIN },
2494 { N_("/Tools/Pr_eferences"), "<CTL>P", gaim_gtk_prefs_show, 0, "<StockItem>", GTK_STOCK_PREFERENCES },
2495 { N_("/Tools/Pr_ivacy"), NULL, gaim_gtk_privacy_dialog_show, 0, "<StockItem>", GTK_STOCK_DIALOG_ERROR },
2496 { "/Tools/sep2", NULL, NULL, 0, "<Separator>", NULL },
2497 { N_("/Tools/_File Transfers"), "<CTL>T", gaim_show_xfer_dialog, 0, "<StockItem>", GAIM_STOCK_FILE_TRANSFER },
2498 { N_("/Tools/R_oom List"), NULL, gaim_gtk_roomlist_dialog_show, 0, "<StockItem>", GTK_STOCK_INDEX },
2499 { N_("/Tools/System _Log"), NULL, gtk_blist_show_systemlog_cb, 0, "<StockItem>", GAIM_STOCK_LOG },
2500 { "/Tools/sep3", NULL, NULL, 0, "<Separator>", NULL },
2501 { N_("/Tools/Mute _Sounds"), "<CTL>S", gaim_gtk_blist_mute_sounds_cb, 0, "<CheckItem>", NULL },
2502
2503 /* Help */
2504 { N_("/_Help"), NULL, NULL, 0, "<Branch>", NULL },
2505 { N_("/Help/Online _Help"), "F1", gtk_blist_show_onlinehelp_cb, 0, "<StockItem>", GTK_STOCK_HELP },
2506 { N_("/Help/_Debug Window"), NULL, toggle_debug, 0, "<StockItem>", GAIM_STOCK_DEBUG },
2507 { N_("/Help/_About"), NULL, gaim_gtkdialogs_about, 0, "<StockItem>", GAIM_STOCK_ABOUT },
2508 };
2509
2510 /*********************************************************
2511 * Private Utility functions *
2512 *********************************************************/
2513
2514 static char *gaim_get_tooltip_text(GaimBlistNode *node, gboolean full)
2515 {
2516 GString *str = g_string_new("");
2517 GaimPlugin *prpl;
2518 GaimPluginProtocolInfo *prpl_info = NULL;
2519 char *tmp;
2520
2521 if (GAIM_BLIST_NODE_IS_CHAT(node))
2522 {
2523 GaimChat *chat;
2524 GList *cur;
2525 struct proto_chat_entry *pce;
2526 char *name, *value;
2527
2528 chat = (GaimChat *)node;
2529 prpl = gaim_find_prpl(gaim_account_get_protocol_id(chat->account));
2530 prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl);
2531
2532 tmp = g_markup_escape_text(gaim_chat_get_name(chat), -1);
2533 g_string_append_printf(str, "<span size='larger' weight='bold'>%s</span>", tmp);
2534 g_free(tmp);
2535
2536 if (g_list_length(gaim_connections_get_all()) > 1)
2537 {
2538 tmp = g_markup_escape_text(chat->account->username, -1);
2539 g_string_append_printf(str, _("\n<b>Account:</b> %s"), tmp);
2540 g_free(tmp);
2541 }
2542
2543 if (prpl_info->chat_info != NULL)
2544 cur = prpl_info->chat_info(chat->account->gc);
2545 else
2546 cur = NULL;
2547
2548 while (cur != NULL)
2549 {
2550 pce = cur->data;
2551
2552 if (!pce->secret && (!pce->required &&
2553 g_hash_table_lookup(chat->components, pce->identifier) == NULL))
2554 {
2555 tmp = gaim_text_strip_mnemonic(pce->label);
2556 name = g_markup_escape_text(tmp, -1);
2557 g_free(tmp);
2558 value = g_markup_escape_text(g_hash_table_lookup(
2559 chat->components, pce->identifier), -1);
2560 g_string_append_printf(str, "\n<b>%s</b> %s",
2561 name ? name : "",
2562 value ? value : "");
2563 g_free(name);
2564 g_free(value);
2565 }
2566
2567 g_free(pce);
2568 cur = g_list_remove(cur, pce);
2569 }
2570 }
2571 else if (GAIM_BLIST_NODE_IS_CONTACT(node) || GAIM_BLIST_NODE_IS_BUDDY(node))
2572 {
2573 /* NOTE: THIS FUNCTION IS NO LONGER CALLED FOR CONTACTS
2574 * See create_tip_for_node(). */
2575
2576 GaimContact *c;
2577 GaimBuddy *b;
2578 GaimPresence *presence;
2579 char *tmp;
2580 time_t idle_secs, signon;
2581
2582 if (GAIM_BLIST_NODE_IS_CONTACT(node))
2583 {
2584 c = (GaimContact *)node;
2585 b = gaim_contact_get_priority_buddy(c);
2586 }
2587 else
2588 {
2589 b = (GaimBuddy *)node;
2590 c = gaim_buddy_get_contact(b);
2591 }
2592
2593 prpl = gaim_find_prpl(gaim_account_get_protocol_id(b->account));
2594 prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl);
2595
2596 presence = gaim_buddy_get_presence(b);
2597
2598 /* Buddy Name */
2599 tmp = g_markup_escape_text(gaim_buddy_get_name(b), -1);
2600 g_string_append_printf(str, "<span size='larger' weight='bold'>%s</span>", tmp);
2601 g_free(tmp);
2602
2603 /* Account */
2604 if (full && g_list_length(gaim_connections_get_all()) > 1)
2605 {
2606 tmp = g_markup_escape_text(gaim_account_get_username(
2607 gaim_buddy_get_account(b)), -1);
2608 g_string_append_printf(str, _("\n<b>Account:</b> %s"), tmp);
2609 g_free(tmp);
2610 }
2611
2612 /* Alias */
2613 /* If there's not a contact alias, the node is being displayed with
2614 * this alias, so there's no point in showing it in the tooltip. */
2615 if (full && b->alias != NULL && b->alias[0] != '\0' &&
2616 (c->alias != NULL && c->alias[0] != '\0') &&
2617 strcmp(c->alias, b->alias) != 0)
2618 {
2619 tmp = g_markup_escape_text(b->alias, -1);
2620 g_string_append_printf(str, _("\n<b>Buddy Alias:</b> %s"), tmp);
2621 g_free(tmp);
2622 }
2623
2624 /* Nickname/Server Alias */
2625 /* I'd like to only show this if there's a contact or buddy
2626 * alias, but many people on MSN set long nicknames, which
2627 * get ellipsized, so the only way to see the whole thing is
2628 * to look at the tooltip. */
2629 if (full && b->server_alias != NULL && b->server_alias[0] != '\0')
2630 {
2631 tmp = g_markup_escape_text(b->server_alias, -1);
2632 g_string_append_printf(str, _("\n<b>Nickname:</b> %s"), tmp);
2633 g_free(tmp);
2634 }
2635
2636 /* Logged In */
2637 signon = gaim_presence_get_login_time(presence);
2638 if (full && GAIM_BUDDY_IS_ONLINE(b) && signon > 0)
2639 {
2640 tmp = gaim_str_seconds_to_string(time(NULL) - signon);
2641 g_string_append_printf(str, _("\n<b>Logged In:</b> %s"), tmp);
2642 g_free(tmp);
2643 }
2644
2645 /* Idle */
2646 if (gaim_presence_is_idle(presence))
2647 {
2648 idle_secs = gaim_presence_get_idle_time(presence);
2649 if (idle_secs > 0)
2650 {
2651 tmp = gaim_str_seconds_to_string(time(NULL) - idle_secs);
2652 g_string_append_printf(str, _("\n<b>Idle:</b> %s"), tmp);
2653 g_free(tmp);
2654 }
2655 }
2656
2657 /* Last Seen */
2658 if (full && !GAIM_BUDDY_IS_ONLINE(b))
2659 {
2660 struct _gaim_gtk_blist_node *gtknode = ((GaimBlistNode *)c)->ui_data;
2661 GaimBlistNode *bnode;
2662 int lastseen = 0;
2663
2664 if (!gtknode->contact_expanded || GAIM_BLIST_NODE_IS_CONTACT(node))
2665 {
2666 /* We're either looking at a buddy for a collapsed contact or
2667 * an expanded contact itself so we show the most recent
2668 * (largest) last_seen time for any of the buddies under
2669 * the contact. */
2670 for (bnode = ((GaimBlistNode *)c)->child ; bnode != NULL ; bnode = bnode->next)
2671 {
2672 int value = gaim_blist_node_get_int(bnode, "last_seen");
2673 if (value > lastseen)
2674 lastseen = value;
2675 }
2676 }
2677 else
2678 {
2679 /* We're dealing with a buddy under an expanded contact,
2680 * so we show the last_seen time for the buddy. */
2681 lastseen = gaim_blist_node_get_int(&b->node, "last_seen");
2682 }
2683
2684 if (lastseen > 0)
2685 {
2686 tmp = gaim_str_seconds_to_string(time(NULL) - lastseen);
2687 g_string_append_printf(str, _("\n<b>Last Seen:</b> %s ago"), tmp);
2688 g_free(tmp);
2689 }
2690 }
2691
2692
2693 /* Offline? */
2694 /* FIXME: Why is this status special-cased by the core? -- rlaager */
2695 if (!GAIM_BUDDY_IS_ONLINE(b)) {
2696 g_string_append_printf(str, _("\n<b>Status:</b> Offline"));
2697 }
2698
2699 if (prpl_info && prpl_info->tooltip_text)
2700 {
2701 /* Additional text from the PRPL */
2702 prpl_info->tooltip_text(b, str, full);
2703 }
2704
2705 /* These are Easter Eggs. Patches to remove them will be rejected. */
2706 if (!g_ascii_strcasecmp(b->name, "robflynn"))
2707 g_string_append(str, _("\n<b>Description:</b> Spooky"));
2708 if (!g_ascii_strcasecmp(b->name, "seanegn"))
2709 g_string_append(str, _("\n<b>Status:</b> Awesome"));
2710 if (!g_ascii_strcasecmp(b->name, "chipx86"))
2711 g_string_append(str, _("\n<b>Status:</b> Rockin'"));
2712 }
2713
2714 gaim_signal_emit(gaim_gtk_blist_get_handle(),
2715 "drawing-tooltip", node, str, full);
2716
2717 return g_string_free(str, FALSE);
2718 }
2719
2720 struct _emblem_data {
2721 const char *filename;
2722 int x;
2723 int y;
2724 };
2725
2726 static void g_string_destroy(GString *destroyable)
2727 {
2728 g_string_free(destroyable, TRUE);
2729 }
2730
2731 static void
2732 gaim_gtk_blist_update_buddy_status_icon_key(struct _gaim_gtk_blist_node *gtkbuddynode, GaimBuddy *buddy, GaimStatusIconSize size)
2733 {
2734 GString *key = g_string_sized_new(16);
2735
2736 if(gtkbuddynode && gtkbuddynode->recent_signonoff) {
2737 if(GAIM_BUDDY_IS_ONLINE(buddy))
2738 g_string_printf(key, "login");
2739 else
2740 g_string_printf(key, "logout");
2741 } else {
2742 int i;
2743 const char *protoname = NULL;
2744 GaimAccount *account = buddy->account;
2745 GaimPlugin *prpl = gaim_find_prpl(gaim_account_get_protocol_id(account));
2746 GaimPluginProtocolInfo *prpl_info;
2747 GaimConversation *conv;
2748 struct _emblem_data emblems[4] = {{NULL, 15, 15}, {NULL, 0, 15},
2749 {NULL, 0, 0}, {NULL, 15, 0}};
2750
2751 if(!prpl)
2752 return;
2753
2754 prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl);
2755
2756 if(prpl_info && prpl_info->list_icon) {
2757 protoname = prpl_info->list_icon(account, buddy);
2758 }
2759 if(prpl_info && prpl_info->list_emblems) {
2760 prpl_info->list_emblems(buddy, &emblems[0].filename,
2761 &emblems[1].filename, &emblems[2].filename,
2762 &emblems[3].filename);
2763 }
2764
2765 g_string_assign(key, protoname);
2766
2767 conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM,
2768 gaim_buddy_get_name(buddy), account);
2769
2770 if(conv != NULL) {
2771 GaimGtkConversation *gtkconv = GAIM_GTK_CONVERSATION(conv);
2772 if(gaim_gtkconv_is_hidden(gtkconv)) {
2773 /* add pending emblem */
2774 if(size == GAIM_STATUS_ICON_SMALL) {
2775 emblems[0].filename = "pending";
2776 }
2777 else {
2778 emblems[3].filename = emblems[2].filename;
2779 emblems[2].filename = "pending";
2780 }
2781 }
2782 }
2783
2784 if(size == GAIM_STATUS_ICON_SMALL) {
2785 /* So that only the se icon will composite */
2786 emblems[1].filename = emblems[2].filename = emblems[3].filename = NULL;
2787 }
2788
2789 for(i = 0; i < 4; i++) {
2790 if(emblems[i].filename)
2791 g_string_append_printf(key, "/%s", emblems[i].filename);
2792 }
2793 }
2794
2795 if (!GAIM_BUDDY_IS_ONLINE(buddy))
2796 g_string_append(key, "/off");
2797 else if (gaim_presence_is_idle(gaim_buddy_get_presence(buddy)))
2798 g_string_append(key, "/idle");
2799
2800 if (!gaim_privacy_check(buddy->account, gaim_buddy_get_name(buddy)))
2801 g_string_append(key, "/priv");
2802
2803 if (gtkbuddynode->status_icon_key)
2804 g_string_free(gtkbuddynode->status_icon_key, TRUE);
2805 gtkbuddynode->status_icon_key = key;
2806
2807 }
2808
2809 GdkPixbuf *
2810 gaim_gtk_blist_get_status_icon(GaimBlistNode *node, GaimStatusIconSize size)
2811 {
2812 GdkPixbuf *scale, *status = NULL;
2813 int i, scalesize = 30;
2814 char *filename;
2815 GString *key = g_string_sized_new(16);
2816 const char *protoname = NULL;
2817 struct _gaim_gtk_blist_node *gtknode = node->ui_data;
2818 struct _gaim_gtk_blist_node *gtkbuddynode = NULL;
2819 struct _emblem_data emblems[4] = {{NULL, 15, 15}, {NULL, 0, 15},
2820 {NULL, 0, 0}, {NULL, 15, 0}};
2821 GaimPresence *presence = NULL;
2822 GaimBuddy *buddy = NULL;
2823 GaimChat *chat = NULL;
2824
2825 if(GAIM_BLIST_NODE_IS_CONTACT(node)) {
2826 if(!gtknode->contact_expanded) {
2827 buddy = gaim_contact_get_priority_buddy((GaimContact*)node);
2828 gtkbuddynode = ((GaimBlistNode*)buddy)->ui_data;
2829 }
2830 } else if(GAIM_BLIST_NODE_IS_BUDDY(node)) {
2831 buddy = (GaimBuddy*)node;
2832 gtkbuddynode = node->ui_data;
2833 } else if(GAIM_BLIST_NODE_IS_CHAT(node)) {
2834 chat = (GaimChat*)node;
2835 } else {
2836 return NULL;
2837 }
2838
2839 if (!status_icon_hash_table) {
2840 status_icon_hash_table = g_hash_table_new_full((GHashFunc)g_string_hash,
2841 (GEqualFunc)g_string_equal,
2842 (GDestroyNotify)g_string_destroy,
2843 (GDestroyNotify)gdk_pixbuf_unref);
2844
2845 } else if (buddy && gtkbuddynode->status_icon_key && gtkbuddynode->status_icon_key->str) {
2846 key = g_string_new(gtkbuddynode->status_icon_key->str);
2847
2848 /* Respect the size request given */
2849 if (size == GAIM_STATUS_ICON_SMALL) {
2850 g_string_append(key, "/tiny");
2851 }
2852
2853 scale = g_hash_table_lookup(status_icon_hash_table, key);
2854 if (scale) {
2855 gdk_pixbuf_ref(scale);
2856 g_string_free(key, TRUE);
2857 return scale;
2858 }
2859 }
2860
2861 if(buddy || chat) {
2862 GaimAccount *account;
2863 GaimPlugin *prpl;
2864 GaimPluginProtocolInfo *prpl_info;
2865
2866 if(buddy)
2867 account = buddy->account;
2868 else
2869 account = chat->account;
2870
2871 prpl = gaim_find_prpl(gaim_account_get_protocol_id(account));
2872 if(!prpl)
2873 return NULL;
2874
2875 prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl);
2876
2877 if(prpl_info && prpl_info->list_icon) {
2878 protoname = prpl_info->list_icon(account, buddy);
2879 }
2880 if(prpl_info && prpl_info->list_emblems && buddy) {
2881 if(gtknode && !gtknode->recent_signonoff)
2882 prpl_info->list_emblems(buddy, &emblems[0].filename,
2883 &emblems[1].filename, &emblems[2].filename,
2884 &emblems[3].filename);
2885 }
2886 }
2887
2888 /* Begin Generating Lookup Key */
2889 if (buddy) {
2890 gaim_gtk_blist_update_buddy_status_icon_key(gtkbuddynode, buddy, size);
2891 g_string_assign(key, gtkbuddynode->status_icon_key->str);
2892 }
2893 /* There are only two options for chat or gaimdude - big or small */
2894 else if (chat) {
2895 GaimAccount *account;
2896 GaimPlugin *prpl;
2897 GaimPluginProtocolInfo *prpl_info;
2898
2899 account = chat->account;
2900
2901 prpl = gaim_find_prpl(gaim_account_get_protocol_id(account));
2902 if(!prpl)
2903 return NULL;
2904
2905 prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl);
2906
2907 if(prpl_info && prpl_info->list_icon) {
2908 protoname = prpl_info->list_icon(account, NULL);
2909 }
2910 g_string_append_printf(key, "%s-chat", protoname);
2911 }
2912 else
2913 g_string_append(key, "gaimdude");
2914
2915 /* If the icon is small, we do not store this into the status_icon_key
2916 * in the gtkbuddynode. This way we can respect the size value on cache
2917 * lookup. Otherwise, different sized icons could not be stored easily.
2918 */
2919 if (size == GAIM_STATUS_ICON_SMALL) {
2920 g_string_append(key, "/tiny");
2921 }
2922
2923 /* End Generating Lookup Key */
2924
2925 /* If we already know this icon, just return it */
2926 scale = g_hash_table_lookup(status_icon_hash_table, key);
2927 if (scale) {
2928 gdk_pixbuf_ref(scale);
2929 g_string_free(key, TRUE);
2930 return scale;
2931 }
2932
2933 /* Create a new composite icon */
2934
2935 if(buddy) {
2936 GaimAccount *account;
2937 GaimPlugin *prpl;
2938 GaimPluginProtocolInfo *prpl_info;
2939 GaimConversation *conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM,
2940 gaim_buddy_get_name(buddy),
2941 gaim_buddy_get_account(buddy));
2942
2943 account = buddy->account;
2944
2945 prpl = gaim_find_prpl(gaim_account_get_protocol_id(account));
2946 if(!prpl)
2947 return NULL;
2948
2949 prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl);
2950
2951 if(prpl_info && prpl_info->list_icon) {
2952 protoname = prpl_info->list_icon(account, buddy);
2953 }
2954 if(prpl_info && prpl_info->list_emblems) {
2955 if(gtknode && !gtknode->recent_signonoff)
2956 prpl_info->list_emblems(buddy, &emblems[0].filename,
2957 &emblems[1].filename, &emblems[2].filename,
2958 &emblems[3].filename);
2959 }
2960
2961 if(conv != NULL) {
2962 GaimGtkConversation *gtkconv = GAIM_GTK_CONVERSATION(conv);
2963 if(gtkconv != NULL && gaim_gtkconv_is_hidden(gtkconv)) {
2964 /* add pending emblem */
2965 if(size == GAIM_STATUS_ICON_SMALL) {
2966 emblems[0].filename="pending";
2967 }
2968 else {
2969 emblems[3].filename=emblems[2].filename;
2970 emblems[2].filename="pending";
2971 }
2972 }
2973 }
2974 }
2975
2976 if(size == GAIM_STATUS_ICON_SMALL) {
2977 scalesize = 15;
2978 /* So that only the se icon will composite */
2979 emblems[1].filename = emblems[2].filename = emblems[3].filename = NULL;
2980 }
2981
2982 if(buddy && GAIM_BUDDY_IS_ONLINE(buddy) && gtkbuddynode && gtkbuddynode->recent_signonoff) {
2983 filename = g_build_filename(DATADIR, "pixmaps", "gaim", "status", "default", "login.png", NULL);
2984 } else if(buddy && !GAIM_BUDDY_IS_ONLINE(buddy) && gtkbuddynode && gtkbuddynode->recent_signonoff) {
2985 filename = g_build_filename(DATADIR, "pixmaps", "gaim", "status", "default", "logout.png", NULL);
2986 } else if(buddy || chat) {
2987 char *image = g_strdup_printf("%s.png", protoname);
2988 filename = g_build_filename(DATADIR, "pixmaps", "gaim", "status", "default", image, NULL);
2989 g_free(image);
2990 } else {
2991 /* gaim dude */
2992 filename = g_build_filename(DATADIR, "pixmaps", "gaim.png", NULL);
2993 }
2994
2995 status = gdk_pixbuf_new_from_file(filename, NULL);
2996 g_free(filename);
2997
2998 if(!status) {
2999 g_string_free(key, TRUE);
3000 return NULL;
3001 }
3002
3003 scale = gdk_pixbuf_scale_simple(status, scalesize, scalesize,
3004 GDK_INTERP_BILINEAR);
3005 g_object_unref(status);
3006
3007 for(i=0; i<4; i++) {
3008 if(emblems[i].filename) {
3009 GdkPixbuf *emblem;
3010 char *image = g_strdup_printf("%s.png", emblems[i].filename);
3011 filename = g_build_filename(DATADIR, "pixmaps", "gaim", "status", "default", image, NULL);
3012 g_free(image);
3013 emblem = gdk_pixbuf_new_from_file(filename, NULL);
3014 g_free(filename);
3015 if(emblem) {
3016 if(i == 0 && size == GAIM_STATUS_ICON_SMALL) {
3017 gdk_pixbuf_composite(emblem,
3018 scale, 5, 5,
3019 10, 10,
3020 5, 5,
3021 .6, .6,
3022 GDK_INTERP_BILINEAR,
3023 255);
3024 } else {
3025 gdk_pixbuf_composite(emblem,
3026 scale, emblems[i].x, emblems[i].y,
3027 15, 15,
3028 emblems[i].x, emblems[i].y,
3029 1, 1,
3030 GDK_INTERP_BILINEAR,
3031 255);
3032 }
3033 g_object_unref(emblem);
3034 }
3035 }
3036 }
3037
3038 if(buddy) {
3039 presence = gaim_buddy_get_presence(buddy);
3040 if (!GAIM_BUDDY_IS_ONLINE(buddy))
3041 gdk_pixbuf_saturate_and_pixelate(scale, scale, 0.0, FALSE);
3042 else if (gaim_presence_is_idle(presence))
3043 {
3044 gdk_pixbuf_saturate_and_pixelate(scale, scale, 0.25, FALSE);
3045 }
3046
3047 if (!gaim_privacy_check(buddy->account, gaim_buddy_get_name(buddy)))
3048 {
3049 GdkPixbuf *emblem;
3050 char *filename = g_build_filename(DATADIR, "pixmaps", "gaim", "status", "default", "blocked.png", NULL);
3051
3052 emblem = gdk_pixbuf_new_from_file(filename, NULL);
3053 g_free(filename);
3054
3055 if (emblem)
3056 {
3057 gdk_pixbuf_composite(emblem, scale,
3058 0, 0, scalesize, scalesize,
3059 0, 0,
3060 (double)scalesize / gdk_pixbuf_get_width(emblem),
3061 (double)scalesize / gdk_pixbuf_get_height(emblem),
3062 GDK_INTERP_BILINEAR,
3063 224);
3064 g_object_unref(emblem);
3065 }
3066 }
3067 }
3068
3069 /* Insert the new icon into the status icon hash table */
3070 g_hash_table_insert (status_icon_hash_table, key, scale);
3071 gdk_pixbuf_ref(scale);
3072
3073 return scale;
3074 }
3075
3076 static gchar *gaim_gtk_blist_get_name_markup(GaimBuddy *b, gboolean selected)
3077 {
3078 const char *name;
3079 char *esc, *text = NULL;
3080 GaimPlugin *prpl;
3081 GaimPluginProtocolInfo *prpl_info = NULL;
3082 GaimContact *contact;
3083 GaimPresence *presence;
3084 struct _gaim_gtk_blist_node *gtkcontactnode = NULL;
3085 char *idletime = NULL, *statustext = NULL;
3086 time_t t;
3087 /* XXX Good luck cleaning up this crap */
3088
3089 contact = (GaimContact*)((GaimBlistNode*)b)->parent;
3090 if(contact)
3091 gtkcontactnode = ((GaimBlistNode*)contact)->ui_data;
3092
3093 if(gtkcontactnode && !gtkcontactnode->contact_expanded && contact->alias)
3094 name = contact->alias;
3095 else
3096 name = gaim_buddy_get_alias(b);
3097 esc = g_markup_escape_text(name, strlen(name));
3098
3099 presence = gaim_buddy_get_presence(b);
3100
3101 if (!gaim_prefs_get_bool("/gaim/gtk/blist/show_buddy_icons"))
3102 {
3103 if (!selected && gaim_presence_is_idle(presence))
3104 {
3105 text = g_strdup_printf("<span color='%s'>%s</span>",
3106 dim_grey(), esc);
3107 g_free(esc);
3108 return text;
3109 }
3110 else
3111 return esc;
3112 }
3113
3114 prpl = gaim_find_prpl(gaim_account_get_protocol_id(b->account));
3115
3116 if (prpl != NULL)
3117 prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl);
3118
3119 if (prpl_info && prpl_info->status_text && b->account->gc) {
3120 char *tmp = prpl_info->status_text(b);
3121 const char *end;
3122
3123 if(tmp && !g_utf8_validate(tmp, -1, &end)) {
3124 char *new = g_strndup(tmp,
3125 g_utf8_pointer_to_offset(tmp, end));
3126 g_free(tmp);
3127 tmp = new;
3128 }
3129
3130 #if !GTK_CHECK_VERSION(2,6,0)
3131 if(tmp) {
3132 char buf[32];
3133 char *c = tmp;
3134 int length = 0, vis=0;
3135 gboolean inside = FALSE;
3136 g_strdelimit(tmp, "\n", ' ');
3137 gaim_str_strip_char(tmp, '\r');
3138
3139 while(*c && vis < 20) {
3140 if(*c == '&')
3141 inside = TRUE;
3142 else if(*c == ';')
3143 inside = FALSE;
3144 if(!inside)
3145 vis++;
3146 c = g_utf8_next_char(c); /* this is fun */
3147 }
3148
3149 length = c - tmp;
3150
3151 if(vis == 20)
3152 g_snprintf(buf, sizeof(buf), "%%.%ds...", length);
3153 else
3154 g_snprintf(buf, sizeof(buf), "%%s ");
3155
3156 statustext = g_strdup_printf(buf, tmp);
3157
3158 g_free(tmp);
3159 }
3160 #else
3161 if(tmp) {
3162 g_strdelimit(tmp, "\n", ' ');
3163 gaim_str_strip_char(tmp, '\r');
3164 }
3165 statustext = tmp;
3166 #endif
3167 }
3168
3169 if(!gaim_presence_is_online(presence) && !statustext)
3170 statustext = g_strdup(_("Offline"));
3171 else if (!statustext)
3172 text = g_strdup(esc);
3173
3174 if (gaim_presence_is_idle(presence)) {
3175 if (gaim_prefs_get_bool("/gaim/gtk/blist/show_idle_time")) {
3176 time_t idle_secs = gaim_presence_get_idle_time(presence);
3177
3178 if (idle_secs > 0) {
3179 int ihrs, imin;
3180
3181 time(&t);
3182 ihrs = (t - idle_secs) / 3600;
3183 imin = ((t - idle_secs) / 60) % 60;
3184
3185 if (ihrs)
3186 idletime = g_strdup_printf(_("Idle %dh %02dm"), ihrs, imin);
3187 else
3188 idletime = g_strdup_printf(_("Idle %dm"), imin);
3189 }
3190 else
3191 idletime = g_strdup(_("Idle"));
3192
3193 if (!selected)
3194 text = g_strdup_printf("<span color='%s'>%s</span>\n"
3195 "<span color='%s' size='smaller'>%s%s%s</span>",
3196 dim_grey(), esc, dim_grey(),
3197 idletime != NULL ? idletime : "",
3198 (idletime != NULL && statustext != NULL) ? " - " : "",
3199 statustext != NULL ? statustext : "");
3200 }
3201 else if (!selected && !statustext) /* We handle selected text later */
3202 text = g_strdup_printf("<span color='%s'>%s</span>", dim_grey(), esc);
3203 else if (!selected && !text)
3204 text = g_strdup_printf("<span color='%s'>%s</span>\n"
3205 "<span color='%s' size='smaller'>%s</span>",
3206 dim_grey(), esc, dim_grey(),
3207 statustext != NULL ? statustext : "");
3208 }
3209
3210 /* Not idle and not selected */
3211 else if (!selected && !text)
3212 {
3213 text = g_strdup_printf("%s\n"
3214 "<span color='%s' size='smaller'>%s</span>",
3215 esc, dim_grey(),
3216 statustext != NULL ? statustext : "");
3217 }
3218
3219 /* It is selected. */
3220 if ((selected && !text) || (selected && idletime))
3221 text = g_strdup_printf("%s\n"
3222 "<span size='smaller'>%s%s%s</span>",
3223 esc,
3224 idletime != NULL ? idletime : "",
3225 (idletime != NULL && statustext != NULL) ? " - " : "",
3226 statustext != NULL ? statustext : "");
3227
3228 g_free(idletime);
3229 g_free(statustext);
3230 g_free(esc);
3231
3232 return text;
3233 }
3234
3235 static void gaim_gtk_blist_restore_position()
3236 {
3237 int blist_x, blist_y, blist_width, blist_height;
3238
3239 blist_width = gaim_prefs_get_int("/gaim/gtk/blist/width");
3240
3241 /* if the window exists, is hidden, we're saving positions, and the
3242 * position is sane... */
3243 if (gtkblist && gtkblist->window &&
3244 !GTK_WIDGET_VISIBLE(gtkblist->window) && blist_width != 0) {
3245
3246 blist_x = gaim_prefs_get_int("/gaim/gtk/blist/x");
3247 blist_y = gaim_prefs_get_int("/gaim/gtk/blist/y");
3248 blist_height = gaim_prefs_get_int("/gaim/gtk/blist/height");
3249
3250 /* ...check position is on screen... */
3251 if (blist_x >= gdk_screen_width())
3252 blist_x = gdk_screen_width() - 100;
3253 else if (blist_x + blist_width < 0)
3254 blist_x = 100;
3255
3256 if (blist_y >= gdk_screen_height())
3257 blist_y = gdk_screen_height() - 100;
3258 else if (blist_y + blist_height < 0)
3259 blist_y = 100;
3260
3261 /* ...and move it back. */
3262 gtk_window_move(GTK_WINDOW(gtkblist->window), blist_x, blist_y);
3263 gtk_window_resize(GTK_WINDOW(gtkblist->window), blist_width, blist_height);
3264 if (gaim_prefs_get_bool("/gaim/gtk/blist/list_maximized"))
3265 gtk_window_maximize(GTK_WINDOW(gtkblist->window));
3266 }
3267 }
3268
3269 static gboolean gaim_gtk_blist_refresh_timer(GaimBuddyList *list)
3270 {
3271 GaimBlistNode *gnode, *cnode;
3272
3273 if (gtk_blist_obscured || !GTK_WIDGET_VISIBLE(gtkblist->window))
3274 return TRUE;
3275
3276 for(gnode = list->root; gnode; gnode = gnode->next) {
3277 if(!GAIM_BLIST_NODE_IS_GROUP(gnode))
3278 continue;
3279 for(cnode = gnode->child; cnode; cnode = cnode->next) {
3280 if(GAIM_BLIST_NODE_IS_CONTACT(cnode)) {
3281 GaimBuddy *buddy;
3282
3283 buddy = gaim_contact_get_priority_buddy((GaimContact*)cnode);
3284
3285 if (buddy &&
3286 gaim_presence_is_idle(gaim_buddy_get_presence(buddy)))
3287 gaim_gtk_blist_update_contact(list, (GaimBlistNode*)buddy);
3288 }
3289 }
3290 }
3291
3292 /* keep on going */
3293 return TRUE;
3294 }
3295
3296 static void gaim_gtk_blist_hide_node(GaimBuddyList *list, GaimBlistNode *node, gboolean update)
3297 {
3298 struct _gaim_gtk_blist_node *gtknode = (struct _gaim_gtk_blist_node *)node->ui_data;
3299 GtkTreeIter iter;
3300
3301 if (!gtknode || !gtknode->row || !gtkblist)
3302 return;
3303
3304 if(gtkblist->selected_node == node)
3305 gtkblist->selected_node = NULL;
3306 if (get_iter_from_node(node, &iter)) {
3307 gtk_tree_store_remove(gtkblist->treemodel, &iter);
3308 if(update && (GAIM_BLIST_NODE_IS_CONTACT(node) ||
3309 GAIM_BLIST_NODE_IS_BUDDY(node) || GAIM_BLIST_NODE_IS_CHAT(node))) {
3310 gaim_gtk_blist_update(list, node->parent);
3311 }
3312 }
3313 gtk_tree_row_reference_free(gtknode->row);
3314 gtknode->row = NULL;
3315 }
3316
3317 static const char *require_connection[] =
3318 {
3319 N_("/Buddies/New Instant Message..."),
3320 N_("/Buddies/Join a Chat..."),
3321 N_("/Buddies/Get User Info..."),
3322 N_("/Buddies/Add Buddy..."),
3323 N_("/Buddies/Add Chat..."),
3324 N_("/Buddies/Add Group..."),
3325 };
3326
3327 static const int require_connection_size = sizeof(require_connection)
3328 / sizeof(*require_connection);
3329
3330 /**
3331 * Rebuild dynamic menus and make menu items sensitive/insensitive
3332 * where appropriate.
3333 */
3334 static void
3335 update_menu_bar(GaimGtkBuddyList *gtkblist)
3336 {
3337 GtkWidget *widget;
3338 gboolean sensitive;
3339 int i;
3340
3341 g_return_if_fail(gtkblist != NULL);
3342
3343 gaim_gtk_blist_update_accounts_menu();
3344
3345 sensitive = (gaim_connections_get_all() != NULL);
3346
3347 for (i = 0; i < require_connection_size; i++)
3348 {
3349 widget = gtk_item_factory_get_widget(gtkblist->ift, require_connection[i]);
3350 gtk_widget_set_sensitive(widget, sensitive);
3351 }
3352
3353 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Buddies/Join a Chat..."));
3354 gtk_widget_set_sensitive(widget, gaim_gtk_blist_joinchat_is_showable());
3355
3356 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Buddies/Add Chat..."));
3357 gtk_widget_set_sensitive(widget, gaim_gtk_blist_joinchat_is_showable());
3358
3359 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Buddy Pounces"));
3360 gtk_widget_set_sensitive(widget, (gaim_connections_get_all() != NULL));
3361
3362 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Privacy"));
3363 gtk_widget_set_sensitive(widget, (gaim_connections_get_all() != NULL));
3364
3365 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Room List"));
3366 gtk_widget_set_sensitive(widget, gaim_gtk_roomlist_is_showable());
3367 }
3368
3369 static void
3370 sign_on_off_cb(GaimConnection *gc, GaimBuddyList *blist)
3371 {
3372 GaimGtkBuddyList *gtkblist = GAIM_GTK_BLIST(blist);
3373
3374 update_menu_bar(gtkblist);
3375 }
3376
3377 static void
3378 plugin_changed_cb(GaimPlugin *p, gpointer *data)
3379 {
3380 gaim_gtk_blist_update_plugin_actions();
3381 }
3382
3383 static void
3384 unseen_conv_menu()
3385 {
3386 static GtkWidget *menu = NULL;
3387 GList *convs = NULL;
3388
3389 if (menu)
3390 gtk_widget_destroy(menu);
3391
3392 menu = gtk_menu_new();
3393
3394 convs = gaim_gtk_conversations_find_unseen_list(GAIM_CONV_TYPE_IM, GAIM_UNSEEN_TEXT, TRUE, 0);
3395 if (!convs) {
3396 /* no conversations added, don't show the menu */
3397 gtk_widget_destroy(menu);
3398 return;
3399 }
3400 gaim_gtk_conversations_fill_menu(menu, convs);
3401 g_list_free(convs);
3402 gtk_widget_show_all(menu);
3403 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3,
3404 gtk_get_current_event_time());
3405 }
3406
3407 static gboolean
3408 menutray_press_cb(GtkWidget *widget, GdkEventButton *event)
3409 {
3410 GList *convs;
3411
3412 switch (event->button) {
3413 case 1:
3414 convs = gaim_gtk_conversations_find_unseen_list(GAIM_CONV_TYPE_IM,
3415 GAIM_UNSEEN_TEXT, TRUE, 1);
3416 if (convs) {
3417 gaim_gtkconv_present_conversation((GaimConversation*)convs->data);
3418 g_list_free(convs);
3419 }
3420 break;
3421 case 3:
3422 unseen_conv_menu();
3423 break;
3424 }
3425 return TRUE;
3426 }
3427
3428 static void
3429 conversation_updated_cb(GaimConversation *conv, GaimConvUpdateType type,
3430 GaimGtkBuddyList *gtkblist)
3431 {
3432 GList *convs = NULL;
3433 GList *l = NULL;
3434
3435 if (type != GAIM_CONV_UPDATE_UNSEEN)
3436 return;
3437
3438 if(conv->account != NULL && conv->name != NULL) {
3439 GaimBuddy *buddy = gaim_find_buddy(conv->account, conv->name);
3440 if(buddy != NULL)
3441 gaim_gtk_blist_update_buddy(NULL, (GaimBlistNode *)buddy, TRUE);
3442 }
3443
3444 if (gtkblist->menutrayicon) {
3445 gtk_widget_destroy(gtkblist->menutrayicon);
3446 gtkblist->menutrayicon = NULL;
3447 }
3448
3449 convs = gaim_gtk_conversations_find_unseen_list(GAIM_CONV_TYPE_IM, GAIM_UNSEEN_TEXT, TRUE, 0);
3450 if (convs) {
3451 GtkWidget *img = NULL;
3452 GString *tooltip_text = NULL;
3453
3454 tooltip_text = g_string_new("");
3455 l = convs;
3456 while (l != NULL) {
3457 if (GAIM_IS_GTK_CONVERSATION(l->data)) {
3458 GaimGtkConversation *gtkconv = GAIM_GTK_CONVERSATION((GaimConversation *)l->data);
3459
3460 g_string_append_printf(tooltip_text,
3461 ngettext("%d unread message from %s\n", "%d unread messages from %s\n", gtkconv->unseen_count),
3462 gtkconv->unseen_count,
3463 gtk_label_get_text(GTK_LABEL(gtkconv->tab_label)));
3464 }
3465 l = l->next;
3466 }
3467 if(tooltip_text->len > 0) {
3468 /* get rid of the last newline */
3469 g_string_truncate(tooltip_text, tooltip_text->len -1);
3470 img = gtk_image_new_from_stock(GAIM_STOCK_PENDING, GTK_ICON_SIZE_MENU);
3471
3472 gtkblist->menutrayicon = gtk_event_box_new();
3473 gtk_container_add(GTK_CONTAINER(gtkblist->menutrayicon), img);
3474 gtk_widget_show(img);
3475 gtk_widget_show(gtkblist->menutrayicon);
3476 g_signal_connect(G_OBJECT(gtkblist->menutrayicon), "button-press-event", G_CALLBACK(menutray_press_cb), NULL);
3477
3478 gaim_gtk_menu_tray_append(GAIM_GTK_MENU_TRAY(gtkblist->menutray), gtkblist->menutrayicon, tooltip_text->str);
3479 }
3480 g_string_free(tooltip_text, TRUE);
3481 g_list_free(convs);
3482 }
3483 }
3484
3485 static void
3486 conversation_deleting_cb(GaimConversation *conv, GaimGtkBuddyList *gtkblist)
3487 {
3488 conversation_updated_cb(conv, GAIM_CONV_UPDATE_UNSEEN, gtkblist);
3489 }
3490
3491 /**********************************************************************************
3492 * Public API Functions *
3493 **********************************************************************************/
3494
3495 static void gaim_gtk_blist_new_list(GaimBuddyList *blist)
3496 {
3497 GaimGtkBuddyList *gtkblist;
3498
3499 gtkblist = g_new0(GaimGtkBuddyList, 1);
3500 gtkblist->connection_errors = g_hash_table_new_full(g_direct_hash,
3501 g_direct_equal, NULL, g_free);
3502 blist->ui_data = gtkblist;
3503 }
3504
3505 static void gaim_gtk_blist_new_node(GaimBlistNode *node)
3506 {
3507 node->ui_data = g_new0(struct _gaim_gtk_blist_node, 1);
3508 }
3509
3510 gboolean gaim_gtk_blist_node_is_contact_expanded(GaimBlistNode *node)
3511 {
3512 if GAIM_BLIST_NODE_IS_BUDDY(node)
3513 node = node->parent;
3514
3515 g_return_val_if_fail(GAIM_BLIST_NODE_IS_CONTACT(node), FALSE);
3516
3517 return ((struct _gaim_gtk_blist_node *)node->ui_data)->contact_expanded;
3518 }
3519
3520 void gaim_gtk_blist_update_columns()
3521 {
3522 if(!gtkblist)
3523 return;
3524
3525 if (gaim_prefs_get_bool("/gaim/gtk/blist/show_buddy_icons")) {
3526 gtk_tree_view_column_set_visible(gtkblist->buddy_icon_column, TRUE);
3527 gtk_tree_view_column_set_visible(gtkblist->idle_column, FALSE);
3528 } else {
3529 gtk_tree_view_column_set_visible(gtkblist->idle_column,
3530 gaim_prefs_get_bool("/gaim/gtk/blist/show_idle_time"));
3531 gtk_tree_view_column_set_visible(gtkblist->buddy_icon_column, FALSE);
3532 }
3533 }
3534
3535 static void
3536 show_buddy_icons_pref_cb(const char *name, GaimPrefType type,
3537 gconstpointer val, gpointer data)
3538 {
3539 gaim_gtk_blist_update_columns();
3540 }
3541
3542 enum {
3543 DRAG_BUDDY,
3544 DRAG_ROW,
3545 DRAG_VCARD,
3546 DRAG_TEXT,
3547 DRAG_URI,
3548 NUM_TARGETS
3549 };
3550
3551 static const char *
3552 item_factory_translate_func (const char *path, gpointer func_data)
3553 {
3554 return _((char *)path);
3555 }
3556
3557 void gaim_gtk_blist_setup_sort_methods()
3558 {
3559 gaim_gtk_blist_sort_method_reg("none", _("Manually"), sort_method_none);
3560 #if GTK_CHECK_VERSION(2,2,1)
3561 gaim_gtk_blist_sort_method_reg("alphabetical", _("Alphabetically"), sort_method_alphabetical);
3562 gaim_gtk_blist_sort_method_reg("status", _("By status"), sort_method_status);
3563 gaim_gtk_blist_sort_method_reg("log_size", _("By log size"), sort_method_log);
3564 #endif
3565 gaim_gtk_blist_sort_method_set(gaim_prefs_get_string("/gaim/gtk/blist/sort_type"));
3566 }
3567
3568 static void _prefs_change_redo_list()
3569 {
3570 GtkTreeSelection *sel;
3571 GtkTreeIter iter;
3572 GaimBlistNode *node = NULL;
3573
3574 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
3575 if (gtk_tree_selection_get_selected(sel, NULL, &iter))
3576 {
3577 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
3578 }
3579
3580 redo_buddy_list(gaim_get_blist(), FALSE, FALSE);
3581 #if GTK_CHECK_VERSION(2,6,0)
3582 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(gtkblist->treeview));
3583 #endif
3584
3585 if (node)
3586 {
3587 struct _gaim_gtk_blist_node *gtknode;
3588 GtkTreePath *path;
3589
3590 gtknode = node->ui_data;
3591 if (gtknode && gtknode->row)
3592 {
3593 path = gtk_tree_row_reference_get_path(gtknode->row);
3594 gtk_tree_selection_select_path(sel, path);
3595 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(gtkblist->treeview), path, NULL, FALSE, 0, 0);
3596 gtk_tree_path_free(path);
3597 }
3598 }
3599 }
3600
3601 static void _prefs_change_sort_method(const char *pref_name, GaimPrefType type,
3602 gconstpointer val, gpointer data)
3603 {
3604 if(!strcmp(pref_name, "/gaim/gtk/blist/sort_type"))
3605 gaim_gtk_blist_sort_method_set(val);
3606 }
3607
3608 /*
3609 * "This is so dead sexy."
3610 * "Two thumbs up."
3611 * "Best movie of the year."
3612 *
3613 * This is the function that handles CTRL+F searching in the buddy list.
3614 * It finds the top-most buddy/group/chat/whatever containing the
3615 * entered string.
3616 *
3617 * It's somewhat ineffecient, because we strip all the HTML from the
3618 * "name" column of the buddy list (because the GtkTreeModel does not
3619 * contain the screen name in a non-markedup format). But the alternative
3620 * is to add an extra column to the GtkTreeModel. And this function is
3621 * used rarely, so it shouldn't matter TOO much.
3622 */
3623 static gboolean
3624 _search_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer search_data)
3625 {
3626 gchar *enteredstring;
3627 gchar *tmp;
3628 gchar *withmarkup;
3629 gchar *nomarkup;
3630 gchar *normalized;
3631 gboolean result;
3632 size_t i;
3633 size_t len;
3634 PangoLogAttr *log_attrs;
3635 gchar *word;
3636
3637 if (strcasecmp(key, "Global Thermonuclear War") == 0)
3638 {
3639 gaim_notify_info(NULL, "WOPR",
3640 "Wouldn't you prefer a nice game of chess?", NULL);
3641 return FALSE;
3642 }
3643
3644 gtk_tree_model_get(model, iter, column, &withmarkup, -1);
3645
3646 tmp = g_utf8_normalize(key, -1, G_NORMALIZE_DEFAULT);
3647 enteredstring = g_utf8_casefold(tmp, -1);
3648 g_free(tmp);
3649
3650 nomarkup = gaim_markup_strip_html(withmarkup);
3651 tmp = g_utf8_normalize(nomarkup, -1, G_NORMALIZE_DEFAULT);
3652 g_free(nomarkup);
3653 normalized = g_utf8_casefold(tmp, -1);
3654 g_free(tmp);
3655
3656 if (gaim_str_has_prefix(normalized, enteredstring))
3657 {
3658 g_free(withmarkup);
3659 g_free(enteredstring);
3660 g_free(normalized);
3661 return FALSE;
3662 }
3663
3664
3665 /* Use Pango to separate by words. */
3666 len = g_utf8_strlen(normalized, -1);
3667 log_attrs = g_new(PangoLogAttr, len + 1);
3668
3669 pango_get_log_attrs(normalized, strlen(normalized), -1, NULL, log_attrs, len + 1);
3670
3671 word = normalized;
3672 result = TRUE;
3673 for (i = 0; i < (len - 1) ; i++)
3674 {
3675 if (log_attrs[i].is_word_start &&
3676 gaim_str_has_prefix(word, enteredstring))
3677 {
3678 result = FALSE;
3679 break;
3680 }
3681 word = g_utf8_next_char(word);
3682 }
3683 g_free(log_attrs);
3684
3685 /* The non-Pango version. */
3686 #if 0
3687 word = normalized;
3688 result = TRUE;
3689 while (word[0] != '\0')
3690 {
3691 gunichar c = g_utf8_get_char(word);
3692 if (!g_unichar_isalnum(c))
3693 {
3694 word = g_utf8_find_next_char(word, NULL);
3695 if (gaim_str_has_prefix(word, enteredstring))
3696 {
3697 result = FALSE;
3698 break;
3699 }
3700 }
3701 else
3702 word = g_utf8_find_next_char(word, NULL);
3703 }
3704 #endif
3705
3706 g_free(withmarkup);
3707 g_free(enteredstring);
3708 g_free(normalized);
3709
3710 return result;
3711 }
3712
3713 static void account_modified(GaimAccount *account, GaimGtkBuddyList *gtkblist)
3714 {
3715 if (!gtkblist)
3716 return;
3717
3718 update_menu_bar(gtkblist);
3719 }
3720
3721 static void
3722 account_status_changed(GaimAccount *account, GaimStatus *old,
3723 GaimStatus *new, GaimGtkBuddyList *gtkblist)
3724 {
3725 if (!gtkblist)
3726 return;
3727
3728 update_menu_bar(gtkblist);
3729 }
3730
3731 static gboolean
3732 gtk_blist_window_key_press_cb(GtkWidget *w, GdkEventKey *event, GaimGtkBuddyList *gtkblist)
3733 {
3734 GtkWidget *imhtml;
3735
3736 if (!gtkblist)
3737 return FALSE;
3738
3739 imhtml = gtk_window_get_focus(GTK_WINDOW(gtkblist->window));
3740
3741 if (GTK_IS_IMHTML(imhtml) && gtk_bindings_activate(GTK_OBJECT(imhtml), event->keyval, event->state))
3742 return TRUE;
3743 return FALSE;
3744 }
3745
3746 /***********************************/
3747 /* Connection error handling stuff */
3748 /***********************************/
3749
3750 static void
3751 ce_modify_account_cb(GaimAccount *account)
3752 {
3753 gaim_gtk_account_dialog_show(GAIM_GTK_MODIFY_ACCOUNT_DIALOG, account);
3754 }
3755
3756 static void
3757 ce_enable_account_cb(GaimAccount *account)
3758 {
3759 gaim_account_set_enabled(account, gaim_core_get_ui(), TRUE);
3760 }
3761
3762 static void
3763 connection_error_button_clicked_cb(GtkButton *widget, gpointer user_data)
3764 {
3765 GaimAccount *account;
3766 char *primary;
3767 const char *text;
3768 gboolean enabled;
3769
3770 account = user_data;
3771 primary = g_strdup_printf(_("%s disconnected"),
3772 gaim_account_get_username(account));
3773 text = g_hash_table_lookup(gtkblist->connection_errors, account);
3774
3775 enabled = gaim_account_get_enabled(account, gaim_core_get_ui());
3776 gaim_request_action(account, _("Connection Error"), primary, text, 2,
3777 account, 3,
3778 _("OK"), NULL,
3779 _("Modify Account"), GAIM_CALLBACK(ce_modify_account_cb),
3780 enabled ? _("Connect") : _("Re-enable Account"),
3781 enabled ? GAIM_CALLBACK(gaim_account_connect) :
3782 GAIM_CALLBACK(ce_enable_account_cb));
3783 g_free(primary);
3784 gtk_widget_destroy(GTK_WIDGET(widget));
3785 g_hash_table_remove(gtkblist->connection_errors, account);
3786 }
3787
3788 /* Add some buttons that show connection errors */
3789 static void
3790 create_connection_error_buttons(gpointer key, gpointer value,
3791 gpointer user_data)
3792 {
3793 GaimAccount *account;
3794 GaimStatusType *status_type;
3795 gchar *escaped, *text;
3796 GtkWidget *button, *label, *image, *hbox;
3797 GdkPixbuf *pixbuf;
3798
3799 account = key;
3800 escaped = g_markup_escape_text((const gchar *)value, -1);
3801 text = g_strdup_printf(_("<span color=\"red\">%s disconnected: %s</span>"),
3802 gaim_account_get_username(account),
3803 escaped);
3804 g_free(escaped);
3805
3806 hbox = gtk_hbox_new(FALSE, 0);
3807
3808 /* Create the icon */
3809 if ((status_type = gaim_account_get_status_type_with_primitive(account,
3810 GAIM_STATUS_OFFLINE))) {
3811 pixbuf = gaim_gtk_create_prpl_icon_with_status(account, status_type, 0.5);
3812 if (pixbuf != NULL) {
3813 image = gtk_image_new_from_pixbuf(pixbuf);
3814 g_object_unref(pixbuf);
3815
3816 gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE,
3817 GAIM_HIG_BOX_SPACE);
3818 }
3819 }
3820
3821 /* Create the text */
3822 label = gtk_label_new("");
3823 gtk_label_set_markup(GTK_LABEL(label), text);
3824 g_free(text);
3825 #if GTK_CHECK_VERSION(2,6,0)
3826 g_object_set(label, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
3827 #endif
3828 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE,
3829 GAIM_HIG_BOX_SPACE);
3830
3831 /* Create the actual button and put the icon and text on it */
3832 button = gtk_button_new();
3833 gtk_container_add(GTK_CONTAINER(button), hbox);
3834 g_signal_connect(G_OBJECT(button), "clicked",
3835 G_CALLBACK(connection_error_button_clicked_cb),
3836 account);
3837 gtk_widget_show_all(button);
3838 gtk_box_pack_end(GTK_BOX(gtkblist->error_buttons), button,
3839 FALSE, FALSE, 0);
3840 }
3841
3842 void
3843 gaim_gtk_blist_update_account_error_state(GaimAccount *account, const char *text)
3844 {
3845 GList *l;
3846
3847 if (text == NULL)
3848 g_hash_table_remove(gtkblist->connection_errors, account);
3849 else
3850 g_hash_table_insert(gtkblist->connection_errors, account, g_strdup(text));
3851
3852 /* Remove the old error buttons */
3853 for (l = gtk_container_get_children(GTK_CONTAINER(gtkblist->error_buttons));
3854 l != NULL;
3855 l = l->next)
3856 {
3857 gtk_widget_destroy(GTK_WIDGET(l->data));
3858 }
3859
3860 /* Add new error buttons */
3861 g_hash_table_foreach(gtkblist->connection_errors,
3862 create_connection_error_buttons, NULL);
3863 }
3864
3865 /******************************************/
3866 /* End of connection error handling stuff */
3867 /******************************************/
3868
3869 static void gaim_gtk_blist_show(GaimBuddyList *list)
3870 {
3871 void *handle;
3872 GtkCellRenderer *rend;
3873 GtkTreeViewColumn *column;
3874 GtkWidget *menu;
3875 GtkWidget *sw;
3876 GtkAccelGroup *accel_group;
3877 GtkTreeSelection *selection;
3878 GtkTargetEntry dte[] = {{"GAIM_BLIST_NODE", GTK_TARGET_SAME_APP, DRAG_ROW},
3879 {"application/x-im-contact", 0, DRAG_BUDDY},
3880 {"text/x-vcard", 0, DRAG_VCARD },
3881 {"text/uri-list", 0, DRAG_URI},
3882 {"text/plain", 0, DRAG_TEXT}};
3883 GtkTargetEntry ste[] = {{"GAIM_BLIST_NODE", GTK_TARGET_SAME_APP, DRAG_ROW},
3884 {"application/x-im-contact", 0, DRAG_BUDDY},
3885 {"text/x-vcard", 0, DRAG_VCARD }};
3886 if (gtkblist && gtkblist->window) {
3887 gaim_blist_set_visible(gaim_prefs_get_bool("/gaim/gtk/blist/list_visible"));
3888 return;
3889 }
3890
3891 gtkblist = GAIM_GTK_BLIST(list);
3892
3893 gtkblist->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
3894 gtk_window_set_role(GTK_WINDOW(gtkblist->window), "buddy_list");
3895 gtk_window_set_title(GTK_WINDOW(gtkblist->window), _("Buddy List"));
3896 GTK_WINDOW(gtkblist->window)->allow_shrink = TRUE;
3897
3898 gtkblist->vbox = gtk_vbox_new(FALSE, 0);
3899 gtk_widget_show(gtkblist->vbox);
3900 gtk_container_add(GTK_CONTAINER(gtkblist->window), gtkblist->vbox);
3901
3902 g_signal_connect(G_OBJECT(gtkblist->window), "delete_event", G_CALLBACK(gtk_blist_delete_cb), NULL);
3903 g_signal_connect(G_OBJECT(gtkblist->window), "configure_event", G_CALLBACK(gtk_blist_configure_cb), NULL);
3904 g_signal_connect(G_OBJECT(gtkblist->window), "visibility_notify_event", G_CALLBACK(gtk_blist_visibility_cb), NULL);
3905 g_signal_connect(G_OBJECT(gtkblist->window), "window_state_event", G_CALLBACK(gtk_blist_window_state_cb), NULL);
3906 g_signal_connect(G_OBJECT(gtkblist->window), "key_press_event", G_CALLBACK(gtk_blist_window_key_press_cb), gtkblist);
3907 gtk_widget_add_events(gtkblist->window, GDK_VISIBILITY_NOTIFY_MASK);
3908
3909 /******************************* Menu bar *************************************/
3910 accel_group = gtk_accel_group_new();
3911 gtk_window_add_accel_group(GTK_WINDOW (gtkblist->window), accel_group);
3912 g_object_unref(accel_group);
3913 gtkblist->ift = gtk_item_factory_new(GTK_TYPE_MENU_BAR, "<GaimMain>", accel_group);
3914 gtk_item_factory_set_translate_func(gtkblist->ift,
3915 (GtkTranslateFunc)item_factory_translate_func,
3916 NULL, NULL);
3917 gtk_item_factory_create_items(gtkblist->ift, sizeof(blist_menu) / sizeof(*blist_menu),
3918 blist_menu, NULL);
3919 gaim_gtk_load_accels();
3920 g_signal_connect(G_OBJECT(accel_group), "accel-changed",
3921 G_CALLBACK(gaim_gtk_save_accels_cb), NULL);
3922 menu = gtk_item_factory_get_widget(gtkblist->ift, "<GaimMain>");
3923 gtkblist->menutray = gaim_gtk_menu_tray_new();
3924 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtkblist->menutray);
3925 gtk_widget_show(gtkblist->menutray);
3926 gtk_widget_show(menu);
3927 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), menu, FALSE, FALSE, 0);
3928
3929 accountmenu = gtk_item_factory_get_widget(gtkblist->ift, N_("/Accounts"));
3930
3931 /****************************** GtkTreeView **********************************/
3932 sw = gtk_scrolled_window_new(NULL,NULL);
3933 gtk_widget_show(sw);
3934 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_IN);
3935 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
3936
3937 gtkblist->treemodel = gtk_tree_store_new(BLIST_COLUMNS,
3938 GDK_TYPE_PIXBUF, G_TYPE_BOOLEAN,
3939 G_TYPE_STRING, G_TYPE_STRING, GDK_TYPE_PIXBUF, G_TYPE_POINTER);
3940
3941 gtkblist->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(gtkblist->treemodel));
3942 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(gtkblist->treeview), TRUE);
3943
3944 gtk_widget_show(gtkblist->treeview);
3945 gtk_widget_set_name(gtkblist->treeview, "gaim_gtkblist_treeview");
3946
3947 /* Set up selection stuff */
3948 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
3949 g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(gaim_gtk_blist_selection_changed), NULL);
3950
3951 /* Set up dnd */
3952 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(gtkblist->treeview),
3953 GDK_BUTTON1_MASK, ste, 3,
3954 GDK_ACTION_COPY);
3955 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(gtkblist->treeview),
3956 dte, 5,
3957 GDK_ACTION_COPY | GDK_ACTION_MOVE);
3958
3959 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-data-received", G_CALLBACK(gaim_gtk_blist_drag_data_rcv_cb), NULL);
3960 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-data-get", G_CALLBACK(gaim_gtk_blist_drag_data_get_cb), NULL);
3961 #ifdef _WIN32
3962 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-begin", G_CALLBACK(gaim_gtk_blist_drag_begin), NULL);
3963 #endif
3964
3965 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-motion", G_CALLBACK(gaim_gtk_blist_drag_motion_cb), NULL);
3966
3967 /* Tooltips */
3968 g_signal_connect(G_OBJECT(gtkblist->treeview), "motion-notify-event", G_CALLBACK(gaim_gtk_blist_motion_cb), NULL);
3969 g_signal_connect(G_OBJECT(gtkblist->treeview), "leave-notify-event", G_CALLBACK(gaim_gtk_blist_leave_cb), NULL);
3970
3971 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(gtkblist->treeview), FALSE);
3972
3973 gtkblist->text_column = column = gtk_tree_view_column_new ();
3974
3975 rend = gtk_cell_renderer_pixbuf_new();
3976 gtk_tree_view_column_pack_start(column, rend, FALSE);
3977 gtk_tree_view_column_set_attributes(column, rend,
3978 "pixbuf", STATUS_ICON_COLUMN,
3979 "visible", STATUS_ICON_VISIBLE_COLUMN,
3980 NULL);
3981 g_object_set(rend, "xalign", 0.0, "ypad", 0, NULL);
3982
3983 gtkblist->text_rend = rend = gtk_cell_renderer_text_new();
3984 gtk_tree_view_column_pack_start (column, rend, TRUE);
3985 gtk_tree_view_column_set_attributes(column, rend,
3986 "markup", NAME_COLUMN,
3987 NULL);
3988 g_signal_connect(G_OBJECT(rend), "edited", G_CALLBACK(gtk_blist_renderer_edited_cb), NULL);
3989 g_object_set(rend, "ypad", 0, "yalign", 0.5, NULL);
3990 #if GTK_CHECK_VERSION(2,6,0)
3991 gtk_tree_view_column_set_expand (column, TRUE);
3992 g_object_set(rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
3993 #endif
3994 gtk_tree_view_append_column(GTK_TREE_VIEW(gtkblist->treeview), column);
3995
3996 rend = gtk_cell_renderer_text_new();
3997 gtkblist->idle_column = gtk_tree_view_column_new_with_attributes("Idle", rend, "markup", IDLE_COLUMN, NULL);
3998 gtk_tree_view_append_column(GTK_TREE_VIEW(gtkblist->treeview), gtkblist->idle_column);
3999 g_object_set(rend, "xalign", 1.0, "ypad", 0, NULL);
4000
4001 rend = gtk_cell_renderer_pixbuf_new();
4002 gtkblist->buddy_icon_column = gtk_tree_view_column_new_with_attributes("Buddy Icon", rend, "pixbuf", BUDDY_ICON_COLUMN, NULL);
4003 g_object_set(rend, "xalign", 1.0, "ypad", 0, NULL);
4004 gtk_tree_view_append_column(GTK_TREE_VIEW(gtkblist->treeview), gtkblist->buddy_icon_column);
4005
4006 g_signal_connect(G_OBJECT(gtkblist->treeview), "row-activated", G_CALLBACK(gtk_blist_row_activated_cb), NULL);
4007 g_signal_connect(G_OBJECT(gtkblist->treeview), "row-expanded", G_CALLBACK(gtk_blist_row_expanded_cb), NULL);
4008 g_signal_connect(G_OBJECT(gtkblist->treeview), "row-collapsed", G_CALLBACK(gtk_blist_row_collapsed_cb), NULL);
4009 g_signal_connect(G_OBJECT(gtkblist->treeview), "button-press-event", G_CALLBACK(gtk_blist_button_press_cb), NULL);
4010 g_signal_connect(G_OBJECT(gtkblist->treeview), "key-press-event", G_CALLBACK(gtk_blist_key_press_cb), NULL);
4011 g_signal_connect(G_OBJECT(gtkblist->treeview), "popup-menu", G_CALLBACK(gaim_gtk_blist_popup_menu_cb), NULL);
4012
4013 /* Enable CTRL+F searching */
4014 gtk_tree_view_set_search_column(GTK_TREE_VIEW(gtkblist->treeview), NAME_COLUMN);
4015 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(gtkblist->treeview), _search_func, NULL, NULL);
4016
4017 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), sw, TRUE, TRUE, 0);
4018 gtk_container_add(GTK_CONTAINER(sw), gtkblist->treeview);
4019 gaim_gtk_blist_update_columns();
4020
4021 /* Create an empty vbox used for showing connection errors */
4022 gtkblist->error_buttons = gtk_vbox_new(FALSE, 0);
4023 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->error_buttons, FALSE, FALSE, 0);
4024
4025 /* Add the statusbox */
4026 gtkblist->statusbox = gtk_gaim_status_box_new();
4027 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->statusbox, FALSE, TRUE, 0);
4028 gtk_widget_set_name(gtkblist->statusbox, "gaim_gtkblist_statusbox");
4029 gtk_widget_show(gtkblist->statusbox);
4030
4031 /* set the Show Offline Buddies option. must be done
4032 * after the treeview or faceprint gets mad. -Robot101
4033 */
4034 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show Offline Buddies"))),
4035 gaim_prefs_get_bool("/gaim/gtk/blist/show_offline_buddies"));
4036
4037 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show Empty Groups"))),
4038 gaim_prefs_get_bool("/gaim/gtk/blist/show_empty_groups"));
4039
4040 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Tools/Mute Sounds"))),
4041 gaim_prefs_get_bool("/gaim/gtk/sound/mute"));
4042
4043 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show Buddy Details"))),
4044 gaim_prefs_get_bool("/gaim/gtk/blist/show_buddy_icons"));
4045
4046 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show Idle Times"))),
4047 gaim_prefs_get_bool("/gaim/gtk/blist/show_idle_time"));
4048
4049 if(!strcmp(gaim_prefs_get_string("/gaim/gtk/sound/method"), "none"))
4050 gtk_widget_set_sensitive(gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Mute Sounds")), FALSE);
4051
4052 /* Update some dynamic things */
4053 update_menu_bar(gtkblist);
4054 gaim_gtk_blist_update_plugin_actions();
4055 gaim_gtk_blist_update_sort_methods();
4056
4057 /* OK... let's show this bad boy. */
4058 gaim_gtk_blist_refresh(list);
4059 gaim_gtk_blist_restore_position();
4060 gtk_widget_show_all(GTK_WIDGET(gtkblist->window));
4061 gaim_blist_set_visible(gaim_prefs_get_bool("/gaim/gtk/blist/list_visible"));
4062
4063 /* start the refresh timer */
4064 gtkblist->refresh_timer = g_timeout_add(30000, (GSourceFunc)gaim_gtk_blist_refresh_timer, list);
4065
4066 handle = gaim_gtk_blist_get_handle();
4067
4068 /* things that affect how buddies are displayed */
4069 gaim_prefs_connect_callback(handle, "/gaim/gtk/blist/show_buddy_icons",
4070 _prefs_change_redo_list, NULL);
4071 gaim_prefs_connect_callback(handle, "/gaim/gtk/blist/show_idle_time",
4072 _prefs_change_redo_list, NULL);
4073 gaim_prefs_connect_callback(handle, "/gaim/gtk/blist/show_empty_groups",
4074 _prefs_change_redo_list, NULL);
4075 gaim_prefs_connect_callback(handle, "/gaim/gtk/blist/show_offline_buddies",
4076 _prefs_change_redo_list, NULL);
4077
4078 /* sorting */
4079 gaim_prefs_connect_callback(handle, "/gaim/gtk/blist/sort_type",
4080 _prefs_change_sort_method, NULL);
4081
4082 /* things that affect what columns are displayed */
4083 gaim_prefs_connect_callback(handle, "/gaim/gtk/blist/show_buddy_icons",
4084 show_buddy_icons_pref_cb, NULL);
4085 gaim_prefs_connect_callback(handle, "/gaim/gtk/blist/show_idle_time",
4086 show_buddy_icons_pref_cb, NULL);
4087
4088 /* menus */
4089 gaim_prefs_connect_callback(handle, "/gaim/gtk/sound/mute",
4090 gaim_gtk_blist_mute_pref_cb, NULL);
4091 gaim_prefs_connect_callback(handle, "/gaim/gtk/sound/method",
4092 gaim_gtk_blist_sound_method_pref_cb, NULL);
4093
4094 /* Setup some gaim signal handlers. */
4095 gaim_signal_connect(gaim_accounts_get_handle(), "account-enabled",
4096 gtkblist, GAIM_CALLBACK(account_modified), gtkblist);
4097 gaim_signal_connect(gaim_accounts_get_handle(), "account-disabled",
4098 gtkblist, GAIM_CALLBACK(account_modified), gtkblist);
4099 gaim_signal_connect(gaim_accounts_get_handle(), "account-removed",
4100 gtkblist, GAIM_CALLBACK(account_modified), gtkblist);
4101 gaim_signal_connect(gaim_accounts_get_handle(), "account-status-changed",
4102 gtkblist, GAIM_CALLBACK(account_status_changed), gtkblist);
4103
4104 gaim_signal_connect(gaim_gtk_account_get_handle(), "account-modified",
4105 gtkblist, GAIM_CALLBACK(account_modified), gtkblist);
4106
4107 gaim_signal_connect(gaim_connections_get_handle(), "signed-on",
4108 gtkblist, GAIM_CALLBACK(sign_on_off_cb), list);
4109 gaim_signal_connect(gaim_connections_get_handle(), "signed-off",
4110 gtkblist, GAIM_CALLBACK(sign_on_off_cb), list);
4111
4112 gaim_signal_connect(gaim_plugins_get_handle(), "plugin-load",
4113 gtkblist, GAIM_CALLBACK(plugin_changed_cb), NULL);
4114 gaim_signal_connect(gaim_plugins_get_handle(), "plugin-unload",
4115 gtkblist, GAIM_CALLBACK(plugin_changed_cb), NULL);
4116
4117 gaim_signal_connect(gaim_conversations_get_handle(), "conversation-updated",
4118 gtkblist, GAIM_CALLBACK(conversation_updated_cb),
4119 gtkblist);
4120 gaim_signal_connect(gaim_conversations_get_handle(), "deleting-conversation",
4121 gtkblist, GAIM_CALLBACK(conversation_deleting_cb),
4122 gtkblist);
4123
4124 /* emit our created signal */
4125 gaim_signal_emit(handle, "gtkblist-created", list);
4126 }
4127
4128 static void redo_buddy_list(GaimBuddyList *list, gboolean remove, gboolean rerender)
4129 {
4130 GaimBlistNode *node = list->root;
4131
4132 while (node)
4133 {
4134 /* This is only needed when we're reverting to a non-GTK+ sorted
4135 * status. We shouldn't need to remove otherwise.
4136 */
4137 if (remove && !GAIM_BLIST_NODE_IS_GROUP(node))
4138 gaim_gtk_blist_hide_node(list, node, FALSE);
4139
4140 if (GAIM_BLIST_NODE_IS_BUDDY(node))
4141 gaim_gtk_blist_update_buddy(list, node, rerender);
4142 else if (GAIM_BLIST_NODE_IS_CHAT(node))
4143 gaim_gtk_blist_update(list, node);
4144 node = gaim_blist_node_next(node, FALSE);
4145 }
4146
4147 /* There is no hash table if there is nothing in the buddy list to update */
4148 if (status_icon_hash_table) {
4149 g_hash_table_destroy(status_icon_hash_table);
4150 status_icon_hash_table = NULL;
4151 }
4152
4153 }
4154
4155 void gaim_gtk_blist_refresh(GaimBuddyList *list)
4156 {
4157 redo_buddy_list(list, FALSE, TRUE);
4158 }
4159
4160 void
4161 gaim_gtk_blist_update_refresh_timeout()
4162 {
4163 GaimBuddyList *blist;
4164 GaimGtkBuddyList *gtkblist;
4165
4166 blist = gaim_get_blist();
4167 gtkblist = GAIM_GTK_BLIST(gaim_get_blist());
4168
4169 gtkblist->refresh_timer = g_timeout_add(30000,(GSourceFunc)gaim_gtk_blist_refresh_timer, blist);
4170 }
4171
4172 static gboolean get_iter_from_node(GaimBlistNode *node, GtkTreeIter *iter) {
4173 struct _gaim_gtk_blist_node *gtknode = (struct _gaim_gtk_blist_node *)node->ui_data;
4174 GtkTreePath *path;
4175
4176 if (!gtknode) {
4177 return FALSE;
4178 }
4179
4180 if (!gtkblist) {
4181 gaim_debug_error("gtkblist", "get_iter_from_node was called, but we don't seem to have a blist\n");
4182 return FALSE;
4183 }
4184
4185 if (!gtknode->row)
4186 return FALSE;
4187
4188
4189 if ((path = gtk_tree_row_reference_get_path(gtknode->row)) == NULL)
4190 return FALSE;
4191
4192 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), iter, path)) {
4193 gtk_tree_path_free(path);
4194 return FALSE;
4195 }
4196 gtk_tree_path_free(path);
4197 return TRUE;
4198 }
4199
4200 static void gaim_gtk_blist_remove(GaimBuddyList *list, GaimBlistNode *node)
4201 {
4202 struct _gaim_gtk_blist_node *gtknode = node->ui_data;
4203
4204 gaim_request_close_with_handle(node);
4205
4206 gaim_gtk_blist_hide_node(list, node, TRUE);
4207
4208 if(node->parent)
4209 gaim_gtk_blist_update(list, node->parent);
4210
4211 /* There's something I don't understand here - Ethan */
4212 /* Ethan said that back in 2003, but this g_free has been left commented
4213 * out ever since. I can't find any reason at all why this is bad and
4214 * valgrind found several reasons why it's good. If this causes problems
4215 * comment it out again. Stu */
4216 /* Of course it still causes problems - this breaks dragging buddies into
4217 * contacts, the dragged buddy mysteriously 'disappears'. Stu. */
4218 /* I think it's fixed now. Stu. */
4219
4220 if(gtknode) {
4221 if(gtknode->recent_signonoff_timer > 0)
4222 gaim_timeout_remove(gtknode->recent_signonoff_timer);
4223
4224 g_free(node->ui_data);
4225 node->ui_data = NULL;
4226 }
4227 }
4228
4229 static gboolean do_selection_changed(GaimBlistNode *new_selection)
4230 {
4231 GaimBlistNode *old_selection = NULL;
4232
4233 /* test for gtkblist because crazy timeout means we can be called after the blist is gone */
4234 if (gtkblist && new_selection != gtkblist->selected_node) {
4235 old_selection = gtkblist->selected_node;
4236 gtkblist->selected_node = new_selection;
4237 if(new_selection)
4238 gaim_gtk_blist_update(NULL, new_selection);
4239 if(old_selection)
4240 gaim_gtk_blist_update(NULL, old_selection);
4241 }
4242
4243 return FALSE;
4244 }
4245
4246 static void gaim_gtk_blist_selection_changed(GtkTreeSelection *selection, gpointer data)
4247 {
4248 GaimBlistNode *new_selection = NULL;
4249 GtkTreeIter iter;
4250
4251 if(gtk_tree_selection_get_selected(selection, NULL, &iter)){
4252 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
4253 NODE_COLUMN, &new_selection, -1);
4254 }
4255
4256 /* we set this up as a timeout, otherwise the blist flickers */
4257 g_timeout_add(0, (GSourceFunc)do_selection_changed, new_selection);
4258 }
4259
4260 static gboolean insert_node(GaimBuddyList *list, GaimBlistNode *node, GtkTreeIter *iter)
4261 {
4262 GtkTreeIter parent_iter, cur, *curptr = NULL;
4263 struct _gaim_gtk_blist_node *gtknode = node->ui_data;
4264 GtkTreePath *newpath;
4265
4266 if(!iter)
4267 return FALSE;
4268
4269 if(node->parent && !get_iter_from_node(node->parent, &parent_iter))
4270 return FALSE;
4271
4272 if(get_iter_from_node(node, &cur))
4273 curptr = &cur;
4274
4275 if(GAIM_BLIST_NODE_IS_CONTACT(node) || GAIM_BLIST_NODE_IS_CHAT(node)) {
4276 current_sort_method->func(node, list, parent_iter, curptr, iter);
4277 } else {
4278 sort_method_none(node, list, parent_iter, curptr, iter);
4279 }
4280
4281 if(gtknode != NULL) {
4282 gtk_tree_row_reference_free(gtknode->row);
4283 } else {
4284 gaim_gtk_blist_new_node(node);
4285 gtknode = (struct _gaim_gtk_blist_node *)node->ui_data;
4286 }
4287
4288 newpath = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel),
4289 iter);
4290 gtknode->row =
4291 gtk_tree_row_reference_new(GTK_TREE_MODEL(gtkblist->treemodel),
4292 newpath);
4293
4294 gtk_tree_path_free(newpath);
4295
4296 gtk_tree_store_set(gtkblist->treemodel, iter,
4297 NODE_COLUMN, node,
4298 -1);
4299
4300 if(node->parent) {
4301 GtkTreePath *expand = NULL;
4302 struct _gaim_gtk_blist_node *gtkparentnode = node->parent->ui_data;
4303
4304 if(GAIM_BLIST_NODE_IS_GROUP(node->parent)) {
4305 if(!gaim_blist_node_get_bool(node->parent, "collapsed"))
4306 expand = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &parent_iter);
4307 } else if(GAIM_BLIST_NODE_IS_CONTACT(node->parent) &&
4308 gtkparentnode->contact_expanded) {
4309 expand = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &parent_iter);
4310 }
4311 if(expand) {
4312 gtk_tree_view_expand_row(GTK_TREE_VIEW(gtkblist->treeview), expand, FALSE);
4313 gtk_tree_path_free(expand);
4314 }
4315 }
4316
4317 return TRUE;
4318 }
4319
4320 /*This version of gaim_gtk_blist_update_group can take the original buddy
4321 or a group, but has much better algorithmic performance with a pre-known buddy*/
4322 static void gaim_gtk_blist_update_group(GaimBuddyList *list, GaimBlistNode *node)
4323 {
4324 GaimGroup *group;
4325 int count;
4326 gboolean show = FALSE;
4327 GaimBlistNode* gnode;
4328
4329 g_return_if_fail(node != NULL);
4330
4331 if (GAIM_BLIST_NODE_IS_GROUP(node))
4332 gnode = node;
4333 else if (GAIM_BLIST_NODE_IS_BUDDY(node))
4334 gnode = node->parent->parent;
4335 else if (GAIM_BLIST_NODE_IS_CONTACT(node) || GAIM_BLIST_NODE_IS_CHAT(node))
4336 gnode = node->parent;
4337 else
4338 return;
4339
4340 group = (GaimGroup*)gnode;
4341
4342 if(gaim_prefs_get_bool("/gaim/gtk/blist/show_offline_buddies"))
4343 count = gaim_blist_get_group_size(group, FALSE);
4344 else
4345 count = gaim_blist_get_group_online_count(group);
4346
4347 if (count > 0 || gaim_prefs_get_bool("/gaim/gtk/blist/show_empty_groups"))
4348 show = TRUE;
4349 else if (GAIM_BLIST_NODE_IS_BUDDY(node)){ /* Or chat? */
4350 if (buddy_is_displayable((GaimBuddy*)node))
4351 show = TRUE;}
4352
4353 if (show) {
4354 char *mark, *esc;
4355 GtkTreeIter iter;
4356
4357 if(!insert_node(list, gnode, &iter))
4358 return;
4359
4360 esc = g_markup_escape_text(group->name, -1);
4361 mark = g_strdup_printf("<span weight='bold'>%s</span> (%d/%d)",
4362 esc, gaim_blist_get_group_online_count(group),
4363 gaim_blist_get_group_size(group, FALSE));
4364 g_free(esc);
4365
4366 gtk_tree_store_set(gtkblist->treemodel, &iter,
4367 STATUS_ICON_COLUMN, NULL,
4368 STATUS_ICON_VISIBLE_COLUMN, FALSE,
4369 NAME_COLUMN, mark,
4370 NODE_COLUMN, gnode,
4371 -1);
4372 g_free(mark);
4373 } else {
4374 gaim_gtk_blist_hide_node(list, gnode, TRUE);
4375 }
4376 }
4377
4378 static void buddy_node(GaimBuddy *buddy, GtkTreeIter *iter, GaimBlistNode *node)
4379 {
4380 GaimPresence *presence;
4381 GdkPixbuf *status, *avatar;
4382 char *mark;
4383 char *idle = NULL;
4384 gboolean selected = (gtkblist->selected_node == node);
4385
4386 presence = gaim_buddy_get_presence(buddy);
4387
4388 status = gaim_gtk_blist_get_status_icon((GaimBlistNode*)buddy,
4389 (gaim_prefs_get_bool("/gaim/gtk/blist/show_buddy_icons")
4390 ? GAIM_STATUS_ICON_LARGE : GAIM_STATUS_ICON_SMALL));
4391
4392 avatar = gaim_gtk_blist_get_buddy_icon((GaimBlistNode *)buddy, TRUE, TRUE);
4393 mark = gaim_gtk_blist_get_name_markup(buddy, selected);
4394
4395 if (gaim_prefs_get_bool("/gaim/gtk/blist/show_idle_time") &&
4396 gaim_presence_is_idle(presence) &&
4397 !gaim_prefs_get_bool("/gaim/gtk/blist/show_buddy_icons"))
4398 {
4399 time_t idle_secs = gaim_presence_get_idle_time(presence);
4400
4401 if (idle_secs > 0)
4402 {
4403 time_t t;
4404 int ihrs, imin;
4405 time(&t);
4406 ihrs = (t - idle_secs) / 3600;
4407 imin = ((t - idle_secs) / 60) % 60;
4408 idle = g_strdup_printf("%d:%02d", ihrs, imin);
4409 }
4410 }
4411
4412 if (gaim_presence_is_idle(presence))
4413 {
4414 if (idle && !selected) {
4415 char *i2 = g_strdup_printf("<span color='%s'>%s</span>",
4416 dim_grey(), idle);
4417 g_free(idle);
4418 idle = i2;
4419 }
4420 }
4421
4422 gtk_tree_store_set(gtkblist->treemodel, iter,
4423 STATUS_ICON_COLUMN, status,
4424 STATUS_ICON_VISIBLE_COLUMN, TRUE,
4425 NAME_COLUMN, mark,
4426 IDLE_COLUMN, idle,
4427 BUDDY_ICON_COLUMN, avatar,
4428 -1);
4429
4430 g_free(mark);
4431 g_free(idle);
4432 if(status)
4433 g_object_unref(status);
4434 if(avatar)
4435 g_object_unref(avatar);
4436 }
4437
4438 /* This is a variation on the original gtk_blist_update_contact. Here we
4439 can know in advance which buddy has changed so we can just update that */
4440 static void gaim_gtk_blist_update_contact(GaimBuddyList *list, GaimBlistNode *node)
4441 {
4442 GaimBlistNode *cnode;
4443 GaimContact *contact;
4444 GaimBuddy *buddy;
4445 struct _gaim_gtk_blist_node *gtknode;
4446
4447 if (GAIM_BLIST_NODE_IS_BUDDY(node))
4448 cnode = node->parent;
4449 else
4450 cnode = node;
4451
4452 g_return_if_fail(GAIM_BLIST_NODE_IS_CONTACT(cnode));
4453
4454 /* First things first, update the group */
4455 if (GAIM_BLIST_NODE_IS_BUDDY(node))
4456 gaim_gtk_blist_update_group(list, node);
4457 else
4458 gaim_gtk_blist_update_group(list, cnode->parent);
4459
4460 contact = (GaimContact*)cnode;
4461 buddy = gaim_contact_get_priority_buddy(contact);
4462
4463 if (buddy_is_displayable(buddy))
4464 {
4465 GtkTreeIter iter;
4466
4467 if(!insert_node(list, cnode, &iter))
4468 return;
4469
4470 gtknode = (struct _gaim_gtk_blist_node *)cnode->ui_data;
4471
4472 if(gtknode->contact_expanded) {
4473 GdkPixbuf *status;
4474 char *mark;
4475
4476 status = gaim_gtk_blist_get_status_icon(cnode,
4477 (gaim_prefs_get_bool("/gaim/gtk/blist/show_buddy_icons") ?
4478 GAIM_STATUS_ICON_LARGE : GAIM_STATUS_ICON_SMALL));
4479
4480 mark = g_markup_escape_text(gaim_contact_get_alias(contact), -1);
4481
4482 gtk_tree_store_set(gtkblist->treemodel, &iter,
4483 STATUS_ICON_COLUMN, status,
4484 STATUS_ICON_VISIBLE_COLUMN, TRUE,
4485 NAME_COLUMN, mark,
4486 IDLE_COLUMN, NULL,
4487 BUDDY_ICON_COLUMN, NULL,
4488 -1);
4489 g_free(mark);
4490 if(status)
4491 g_object_unref(status);
4492 } else {
4493 buddy_node(buddy, &iter, cnode);
4494 }
4495 } else {
4496 gaim_gtk_blist_hide_node(list, cnode, TRUE);
4497 }
4498 }
4499
4500
4501
4502 static void gaim_gtk_blist_update_buddy(GaimBuddyList *list, GaimBlistNode *node, gboolean statusChange)
4503 {
4504 GaimBuddy *buddy;
4505 struct _gaim_gtk_blist_node *gtkparentnode;
4506 struct _gaim_gtk_blist_node *gtknode = node->ui_data;
4507
4508 g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
4509
4510 if (node->parent == NULL)
4511 return;
4512
4513 buddy = (GaimBuddy*)node;
4514
4515 if (statusChange)
4516 gaim_gtk_blist_update_buddy_status_icon_key(gtknode, buddy,
4517 (gaim_prefs_get_bool("/gaim/gtk/blist/show_buddy_icons")
4518 ? GAIM_STATUS_ICON_LARGE : GAIM_STATUS_ICON_SMALL));
4519
4520 /* First things first, update the contact */
4521 gaim_gtk_blist_update_contact(list, node);
4522
4523 gtkparentnode = (struct _gaim_gtk_blist_node *)node->parent->ui_data;
4524
4525 if (gtkparentnode->contact_expanded && buddy_is_displayable(buddy))
4526 {
4527 GtkTreeIter iter;
4528
4529 if (!insert_node(list, node, &iter))
4530 return;
4531
4532 buddy_node(buddy, &iter, node);
4533
4534 } else {
4535 gaim_gtk_blist_hide_node(list, node, TRUE);
4536 }
4537
4538 }
4539
4540 static void gaim_gtk_blist_update_chat(GaimBuddyList *list, GaimBlistNode *node)
4541 {
4542 GaimChat *chat;
4543
4544 g_return_if_fail(GAIM_BLIST_NODE_IS_CHAT(node));
4545
4546 /* First things first, update the group */
4547 gaim_gtk_blist_update_group(list, node->parent);
4548
4549 chat = (GaimChat*)node;
4550
4551 if(gaim_account_is_connected(chat->account)) {
4552 GtkTreeIter iter;
4553 GdkPixbuf *status;
4554 char *mark;
4555
4556 if(!insert_node(list, node, &iter))
4557 return;
4558
4559 status = gaim_gtk_blist_get_status_icon(node,
4560 (gaim_prefs_get_bool("/gaim/gtk/blist/show_buddy_icons") ?
4561 GAIM_STATUS_ICON_LARGE : GAIM_STATUS_ICON_SMALL));
4562
4563 mark = g_markup_escape_text(gaim_chat_get_name(chat), -1);
4564
4565 gtk_tree_store_set(gtkblist->treemodel, &iter,
4566 STATUS_ICON_COLUMN, status,
4567 STATUS_ICON_VISIBLE_COLUMN, TRUE,
4568 NAME_COLUMN, mark,
4569 -1);
4570
4571 g_free(mark);
4572 if(status)
4573 g_object_unref(status);
4574 } else {
4575 gaim_gtk_blist_hide_node(list, node, TRUE);
4576 }
4577 }
4578
4579 static void gaim_gtk_blist_update(GaimBuddyList *list, GaimBlistNode *node)
4580 {
4581 if(!gtkblist || !node)
4582 return;
4583
4584 if (node->ui_data == NULL)
4585 gaim_gtk_blist_new_node(node);
4586
4587 switch(node->type) {
4588 case GAIM_BLIST_GROUP_NODE:
4589 gaim_gtk_blist_update_group(list, node);
4590 break;
4591 case GAIM_BLIST_CONTACT_NODE:
4592 gaim_gtk_blist_update_contact(list, node);
4593 break;
4594 case GAIM_BLIST_BUDDY_NODE:
4595 gaim_gtk_blist_update_buddy(list, node, TRUE);
4596 break;
4597 case GAIM_BLIST_CHAT_NODE:
4598 gaim_gtk_blist_update_chat(list, node);
4599 break;
4600 case GAIM_BLIST_OTHER_NODE:
4601 return;
4602 }
4603
4604 #if !GTK_CHECK_VERSION(2,6,0)
4605 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(gtkblist->treeview));
4606 #endif
4607 }
4608
4609
4610 static void gaim_gtk_blist_destroy(GaimBuddyList *list)
4611 {
4612 if (!gtkblist)
4613 return;
4614
4615 gaim_signal_disconnect(gaim_connections_get_handle(), "signed-on",
4616 gtkblist, GAIM_CALLBACK(sign_on_off_cb));
4617 gaim_signal_disconnect(gaim_connections_get_handle(), "signed-off",
4618 gtkblist, GAIM_CALLBACK(sign_on_off_cb));
4619
4620 gtk_widget_destroy(gtkblist->window);
4621
4622 gaim_gtk_blist_tooltip_destroy();
4623
4624 if (gtkblist->refresh_timer)
4625 g_source_remove(gtkblist->refresh_timer);
4626 if (gtkblist->timeout)
4627 g_source_remove(gtkblist->timeout);
4628 if (gtkblist->drag_timeout)
4629 g_source_remove(gtkblist->drag_timeout);
4630
4631 g_hash_table_destroy(gtkblist->connection_errors);
4632 gtkblist->refresh_timer = 0;
4633 gtkblist->timeout = 0;
4634 gtkblist->drag_timeout = 0;
4635 gtkblist->window = gtkblist->vbox = gtkblist->treeview = NULL;
4636 gtkblist->treemodel = NULL;
4637 gtkblist->idle_column = NULL;
4638 gtkblist->buddy_icon_column = NULL;
4639 g_object_unref(G_OBJECT(gtkblist->ift));
4640 g_free(gtkblist);
4641 accountmenu = NULL;
4642 gtkblist = NULL;
4643
4644 gaim_prefs_disconnect_by_handle(gaim_gtk_blist_get_handle());
4645 }
4646
4647 static void gaim_gtk_blist_set_visible(GaimBuddyList *list, gboolean show)
4648 {
4649 if (!(gtkblist && gtkblist->window))
4650 return;
4651
4652 if (show) {
4653 if(!GAIM_WINDOW_ICONIFIED(gtkblist->window) && !GTK_WIDGET_VISIBLE(gtkblist->window))
4654 gaim_signal_emit(gaim_gtk_blist_get_handle(), "gtkblist-unhiding", gtkblist);
4655 gaim_gtk_blist_restore_position();
4656 gtk_window_present(GTK_WINDOW(gtkblist->window));
4657 } else {
4658 if(visibility_manager_count) {
4659 gaim_signal_emit(gaim_gtk_blist_get_handle(), "gtkblist-hiding", gtkblist);
4660 gtk_widget_hide(gtkblist->window);
4661 } else {
4662 if (!GTK_WIDGET_VISIBLE(gtkblist->window))
4663 gtk_widget_show(gtkblist->window);
4664 gtk_window_iconify(GTK_WINDOW(gtkblist->window));
4665 }
4666 }
4667 }
4668
4669 static GList *
4670 groups_tree(void)
4671 {
4672 GList *tmp = NULL;
4673 char *tmp2;
4674 GaimGroup *g;
4675 GaimBlistNode *gnode;
4676
4677 if (gaim_get_blist()->root == NULL)
4678 {
4679 tmp2 = g_strdup(_("Buddies"));
4680 tmp = g_list_append(tmp, tmp2);
4681 }
4682 else
4683 {
4684 for (gnode = gaim_get_blist()->root;
4685 gnode != NULL;
4686 gnode = gnode->next)
4687 {
4688 if (GAIM_BLIST_NODE_IS_GROUP(gnode))
4689 {
4690 g = (GaimGroup *)gnode;
4691 tmp2 = g->name;
4692 tmp = g_list_append(tmp, tmp2);
4693 }
4694 }
4695 }
4696
4697 return tmp;
4698 }
4699
4700 static void
4701 add_buddy_select_account_cb(GObject *w, GaimAccount *account,
4702 GaimGtkAddBuddyData *data)
4703 {
4704 /* Save our account */
4705 data->account = account;
4706 }
4707
4708 static void
4709 destroy_add_buddy_dialog_cb(GtkWidget *win, GaimGtkAddBuddyData *data)
4710 {
4711 g_free(data);
4712 }
4713
4714 static void
4715 add_buddy_cb(GtkWidget *w, int resp, GaimGtkAddBuddyData *data)
4716 {
4717 const char *grp, *who, *whoalias;
4718 GaimGroup *g;
4719 GaimBuddy *b;
4720 GaimConversation *c;
4721 GaimBuddyIcon *icon;
4722
4723 if (resp == GTK_RESPONSE_OK)
4724 {
4725 who = gtk_entry_get_text(GTK_ENTRY(data->entry));
4726 grp = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(data->combo)->entry));
4727 whoalias = gtk_entry_get_text(GTK_ENTRY(data->entry_for_alias));
4728 if (*whoalias == '\0')
4729 whoalias = NULL;
4730
4731 if ((g = gaim_find_group(grp)) == NULL)
4732 {
4733 g = gaim_group_new(grp);
4734 gaim_blist_add_group(g, NULL);
4735 }
4736
4737 b = gaim_buddy_new(data->account, who, whoalias);
4738 gaim_blist_add_buddy(b, NULL, g, NULL);
4739 gaim_account_add_buddy(data->account, b);
4740
4741 /*
4742 * XXX
4743 * It really seems like it would be better if the call to
4744 * gaim_account_add_buddy() and gaim_conversation_update() were done in
4745 * blist.c, possibly in the gaim_blist_add_buddy() function. Maybe
4746 * gaim_account_add_buddy() should be renamed to
4747 * gaim_blist_add_new_buddy() or something, and have it call
4748 * gaim_blist_add_buddy() after it creates it. --Mark
4749 *
4750 * No that's not good. blist.c should only deal with adding nodes to the
4751 * local list. We need a new, non-gtk file that calls both
4752 * gaim_account_add_buddy and gaim_blist_add_buddy().
4753 * Or something. --Mark
4754 */
4755
4756 c = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, who, data->account);
4757 if (c != NULL) {
4758 icon = gaim_conv_im_get_icon(GAIM_CONV_IM(c));
4759 if (icon != NULL)
4760 gaim_buddy_icon_update(icon);
4761 }
4762 }
4763
4764 gtk_widget_destroy(data->window);
4765 }
4766
4767 static void
4768 gaim_gtk_blist_request_add_buddy(GaimAccount *account, const char *username,
4769 const char *group, const char *alias)
4770 {
4771 GtkWidget *table;
4772 GtkWidget *label;
4773 GtkWidget *hbox;
4774 GtkWidget *vbox;
4775 GtkWidget *img;
4776 GaimGtkBuddyList *gtkblist;
4777 GaimGtkAddBuddyData *data = g_new0(GaimGtkAddBuddyData, 1);
4778
4779 data->account =
4780 (account != NULL
4781 ? account
4782 : gaim_connection_get_account(gaim_connections_get_all()->data));
4783
4784 img = gtk_image_new_from_stock(GAIM_STOCK_DIALOG_QUESTION,
4785 GTK_ICON_SIZE_DIALOG);
4786
4787 gtkblist = GAIM_GTK_BLIST(gaim_get_blist());
4788
4789 data->window = gtk_dialog_new_with_buttons(_("Add Buddy"),
4790 NULL, GTK_DIALOG_NO_SEPARATOR,
4791 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
4792 GTK_STOCK_ADD, GTK_RESPONSE_OK,
4793 NULL);
4794
4795 gtk_dialog_set_default_response(GTK_DIALOG(data->window), GTK_RESPONSE_OK);
4796 gtk_container_set_border_width(GTK_CONTAINER(data->window), GAIM_HIG_BOX_SPACE);
4797 gtk_window_set_resizable(GTK_WINDOW(data->window), FALSE);
4798 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(data->window)->vbox), GAIM_HIG_BORDER);
4799 gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(data->window)->vbox), GAIM_HIG_BOX_SPACE);
4800 gtk_window_set_role(GTK_WINDOW(data->window), "add_buddy");
4801 gtk_window_set_type_hint(GTK_WINDOW(data->window),
4802 GDK_WINDOW_TYPE_HINT_DIALOG);
4803
4804 hbox = gtk_hbox_new(FALSE, GAIM_HIG_BORDER);
4805 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(data->window)->vbox), hbox);
4806 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
4807 gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
4808
4809 vbox = gtk_vbox_new(FALSE, 0);
4810 gtk_container_add(GTK_CONTAINER(hbox), vbox);
4811
4812 label = gtk_label_new(
4813 _("Please enter the screen name of the person you would like "
4814 "to add to your buddy list. You may optionally enter an alias, "
4815 "or nickname, for the buddy. The alias will be displayed in "
4816 "place of the screen name whenever possible.\n"));
4817
4818 gtk_widget_set_size_request(GTK_WIDGET(label), 400, -1);
4819 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
4820 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
4821 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
4822
4823 hbox = gtk_hbox_new(FALSE, GAIM_HIG_BOX_SPACE);
4824 gtk_container_add(GTK_CONTAINER(vbox), hbox);
4825
4826 g_signal_connect(G_OBJECT(data->window), "destroy",
4827 G_CALLBACK(destroy_add_buddy_dialog_cb), data);
4828
4829 table = gtk_table_new(4, 2, FALSE);
4830 gtk_table_set_row_spacings(GTK_TABLE(table), 5);
4831 gtk_table_set_col_spacings(GTK_TABLE(table), 5);
4832 gtk_container_set_border_width(GTK_CONTAINER(table), 0);
4833 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
4834
4835 label = gtk_label_new(_("Screen name:"));
4836 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
4837 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 0, 1);
4838
4839 data->entry = gtk_entry_new();
4840 gtk_table_attach_defaults(GTK_TABLE(table), data->entry, 1, 2, 0, 1);
4841 gtk_widget_grab_focus(data->entry);
4842
4843 if (username != NULL)
4844 gtk_entry_set_text(GTK_ENTRY(data->entry), username);
4845 else
4846 gtk_dialog_set_response_sensitive(GTK_DIALOG(data->window),
4847 GTK_RESPONSE_OK, FALSE);
4848
4849 gtk_entry_set_activates_default (GTK_ENTRY(data->entry), TRUE);
4850 gaim_set_accessible_label (data->entry, label);
4851
4852 g_signal_connect(G_OBJECT(data->entry), "changed",
4853 G_CALLBACK(gaim_gtk_set_sensitive_if_input),
4854 data->window);
4855
4856 label = gtk_label_new(_("Alias:"));
4857 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
4858 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2);
4859
4860 data->entry_for_alias = gtk_entry_new();
4861 gtk_table_attach_defaults(GTK_TABLE(table),
4862 data->entry_for_alias, 1, 2, 1, 2);
4863
4864 if (alias != NULL)
4865 gtk_entry_set_text(GTK_ENTRY(data->entry_for_alias), alias);
4866
4867 if (username != NULL)
4868 gtk_widget_grab_focus(GTK_WIDGET(data->entry_for_alias));
4869
4870 gtk_entry_set_activates_default (GTK_ENTRY(data->entry_for_alias), TRUE);
4871 gaim_set_accessible_label (data->entry_for_alias, label);
4872
4873 label = gtk_label_new(_("Group:"));
4874 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
4875 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 2, 3);
4876
4877 data->combo = gtk_combo_new();
4878 gtk_combo_set_popdown_strings(GTK_COMBO(data->combo), groups_tree());
4879 gtk_table_attach_defaults(GTK_TABLE(table), data->combo, 1, 2, 2, 3);
4880 gaim_set_accessible_label (data->combo, label);
4881
4882 /* Set up stuff for the account box */
4883 label = gtk_label_new(_("Account:"));
4884 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
4885 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 3, 4);
4886
4887 data->account_box = gaim_gtk_account_option_menu_new(account, FALSE,
4888 G_CALLBACK(add_buddy_select_account_cb), NULL, data);
4889
4890 gtk_table_attach_defaults(GTK_TABLE(table), data->account_box, 1, 2, 3, 4);
4891 gaim_set_accessible_label (data->account_box, label);
4892 /* End of account box */
4893
4894 g_signal_connect(G_OBJECT(data->window), "response",
4895 G_CALLBACK(add_buddy_cb), data);
4896
4897 gtk_widget_show_all(data->window);
4898
4899 if (group != NULL)
4900 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(data->combo)->entry), group);
4901 }
4902
4903 static void
4904 add_chat_cb(GtkWidget *w, GaimGtkAddChatData *data)
4905 {
4906 GHashTable *components;
4907 GList *tmp;
4908 GaimChat *chat;
4909 GaimGroup *group;
4910 const char *group_name;
4911 const char *value;
4912
4913 components = g_hash_table_new_full(g_str_hash, g_str_equal,
4914 g_free, g_free);
4915
4916 for (tmp = data->entries; tmp; tmp = tmp->next)
4917 {
4918 if (g_object_get_data(tmp->data, "is_spin"))
4919 {
4920 g_hash_table_replace(components,
4921 g_strdup(g_object_get_data(tmp->data, "identifier")),
4922 g_strdup_printf("%d",
4923 gtk_spin_button_get_value_as_int(tmp->data)));
4924 }
4925 else
4926 {
4927 value = gtk_entry_get_text(tmp->data);
4928 if (*value != '\0')
4929 g_hash_table_replace(components,
4930 g_strdup(g_object_get_data(tmp->data, "identifier")),
4931 g_strdup(value));
4932 }
4933 }
4934
4935 chat = gaim_chat_new(data->account,
4936 gtk_entry_get_text(GTK_ENTRY(data->alias_entry)),
4937 components);
4938
4939 group_name = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(data->group_combo)->entry));
4940
4941 if ((group = gaim_find_group(group_name)) == NULL)
4942 {
4943 group = gaim_group_new(group_name);
4944 gaim_blist_add_group(group, NULL);
4945 }
4946
4947 if (chat != NULL)
4948 {
4949 gaim_blist_add_chat(chat, group, NULL);
4950 }
4951
4952 gtk_widget_destroy(data->window);
4953 g_free(data->default_chat_name);
4954 g_list_free(data->entries);
4955 g_free(data);
4956 }
4957
4958 static void
4959 add_chat_resp_cb(GtkWidget *w, int resp, GaimGtkAddChatData *data)
4960 {
4961 if (resp == GTK_RESPONSE_OK)
4962 {
4963 add_chat_cb(NULL, data);
4964 }
4965 else
4966 {
4967 gtk_widget_destroy(data->window);
4968 g_free(data->default_chat_name);
4969 g_list_free(data->entries);
4970 g_free(data);
4971 }
4972 }
4973
4974 /*
4975 * Check the values of all the text entry boxes. If any required input
4976 * strings are empty then don't allow the user to click on "OK."
4977 */
4978 static void
4979 addchat_set_sensitive_if_input_cb(GtkWidget *entry, gpointer user_data)
4980 {
4981 GaimGtkAddChatData *data;
4982 GList *tmp;
4983 const char *text;
4984 gboolean required;
4985 gboolean sensitive = TRUE;
4986
4987 data = user_data;
4988
4989 for (tmp = data->entries; tmp != NULL; tmp = tmp->next)
4990 {
4991 if (!g_object_get_data(tmp->data, "is_spin"))
4992 {
4993 required = GPOINTER_TO_INT(g_object_get_data(tmp->data, "required"));
4994 text = gtk_entry_get_text(tmp->data);
4995 if (required && (*text == '\0'))
4996 sensitive = FALSE;
4997 }
4998 }
4999
5000 gtk_dialog_set_response_sensitive(GTK_DIALOG(data->window), GTK_RESPONSE_OK, sensitive);
5001 }
5002
5003 static void
5004 rebuild_addchat_entries(GaimGtkAddChatData *data)
5005 {
5006 GaimConnection *gc;
5007 GList *list = NULL, *tmp = NULL;
5008 GHashTable *defaults = NULL;
5009 struct proto_chat_entry *pce;
5010 gboolean focus = TRUE;
5011
5012 g_return_if_fail(data->account != NULL);
5013
5014 gc = gaim_account_get_connection(data->account);
5015
5016 while (GTK_BOX(data->entries_box)->children)
5017 {
5018 gtk_container_remove(GTK_CONTAINER(data->entries_box),
5019 ((GtkBoxChild *)GTK_BOX(data->entries_box)->children->data)->widget);
5020 }
5021
5022 g_list_free(data->entries);
5023
5024 data->entries = NULL;
5025
5026 if (GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL)
5027 list = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info(gc);
5028
5029 if (GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults != NULL)
5030 defaults = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults(gc, data->default_chat_name);
5031
5032 for (tmp = list; tmp; tmp = tmp->next)
5033 {
5034 GtkWidget *label;
5035 GtkWidget *rowbox;
5036 GtkWidget *input;
5037
5038 pce = tmp->data;
5039
5040 rowbox = gtk_hbox_new(FALSE, 5);
5041 gtk_box_pack_start(GTK_BOX(data->entries_box), rowbox, FALSE, FALSE, 0);
5042
5043 label = gtk_label_new_with_mnemonic(pce->label);
5044 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
5045 gtk_size_group_add_widget(data->sg, label);
5046 gtk_box_pack_start(GTK_BOX(rowbox), label, FALSE, FALSE, 0);
5047
5048 if (pce->is_int)
5049 {
5050 GtkObject *adjust;
5051 adjust = gtk_adjustment_new(pce->min, pce->min, pce->max,
5052 1, 10, 10);
5053 input = gtk_spin_button_new(GTK_ADJUSTMENT(adjust), 1, 0);
5054 gtk_widget_set_size_request(input, 50, -1);
5055 gtk_box_pack_end(GTK_BOX(rowbox), input, FALSE, FALSE, 0);
5056 }
5057 else
5058 {
5059 char *value;
5060 input = gtk_entry_new();
5061 gtk_entry_set_activates_default(GTK_ENTRY(input), TRUE);
5062 value = g_hash_table_lookup(defaults, pce->identifier);
5063 if (value != NULL)
5064 gtk_entry_set_text(GTK_ENTRY(input), value);
5065 if (pce->secret)
5066 {
5067 gtk_entry_set_visibility(GTK_ENTRY(input), FALSE);
5068 gtk_entry_set_invisible_char(GTK_ENTRY(input), GAIM_INVISIBLE_CHAR);
5069 }
5070 gtk_box_pack_end(GTK_BOX(rowbox), input, TRUE, TRUE, 0);
5071 g_signal_connect(G_OBJECT(input), "changed",
5072 G_CALLBACK(addchat_set_sensitive_if_input_cb), data);
5073 }
5074
5075 /* Do the following for any type of input widget */
5076 if (focus)
5077 {
5078 gtk_widget_grab_focus(input);
5079 focus = FALSE;
5080 }
5081 gtk_label_set_mnemonic_widget(GTK_LABEL(label), input);
5082 gaim_set_accessible_label(input, label);
5083 g_object_set_data(G_OBJECT(input), "identifier", (gpointer)pce->identifier);
5084 g_object_set_data(G_OBJECT(input), "is_spin", GINT_TO_POINTER(pce->is_int));
5085 g_object_set_data(G_OBJECT(input), "required", GINT_TO_POINTER(pce->required));
5086 data->entries = g_list_append(data->entries, input);
5087
5088 g_free(pce);
5089 }
5090
5091 g_list_free(list);
5092 g_hash_table_destroy(defaults);
5093
5094 /* Set whether the "OK" button should be clickable initially */
5095 addchat_set_sensitive_if_input_cb(NULL, data);
5096
5097 gtk_widget_show_all(data->entries_box);
5098 }
5099
5100 static void
5101 addchat_select_account_cb(GObject *w, GaimAccount *account,
5102 GaimGtkAddChatData *data)
5103 {
5104 if (strcmp(gaim_account_get_protocol_id(data->account),
5105 gaim_account_get_protocol_id(account)) == 0)
5106 {
5107 data->account = account;
5108 }
5109 else
5110 {
5111 data->account = account;
5112 rebuild_addchat_entries(data);
5113 }
5114 }
5115
5116 static void
5117 gaim_gtk_blist_request_add_chat(GaimAccount *account, GaimGroup *group,
5118 const char *alias, const char *name)
5119 {
5120 GaimGtkAddChatData *data;
5121 GaimGtkBuddyList *gtkblist;
5122 GList *l;
5123 GaimConnection *gc;
5124 GtkWidget *label;
5125 GtkWidget *rowbox;
5126 GtkWidget *hbox;
5127 GtkWidget *vbox;
5128 GtkWidget *img;
5129
5130 if (account != NULL) {
5131 gc = gaim_account_get_connection(account);
5132
5133 if (GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->join_chat == NULL) {
5134 gaim_notify_error(gc, NULL, _("This protocol does not support chat rooms."), NULL);
5135 return;
5136 }
5137 } else {
5138 /* Find an account with chat capabilities */
5139 for (l = gaim_connections_get_all(); l != NULL; l = l->next) {
5140 gc = (GaimConnection *)l->data;
5141
5142 if (GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->join_chat != NULL) {
5143 account = gaim_connection_get_account(gc);
5144 break;
5145 }
5146 }
5147
5148 if (account == NULL) {
5149 gaim_notify_error(NULL, NULL,
5150 _("You are not currently signed on with any "
5151 "protocols that have the ability to chat."), NULL);
5152 return;
5153 }
5154 }
5155
5156 data = g_new0(GaimGtkAddChatData, 1);
5157 data->account = account;
5158 data->default_chat_name = g_strdup(name);
5159
5160 img = gtk_image_new_from_stock(GAIM_STOCK_DIALOG_QUESTION,
5161 GTK_ICON_SIZE_DIALOG);
5162
5163 gtkblist = GAIM_GTK_BLIST(gaim_get_blist());
5164
5165 data->sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
5166
5167 data->window = gtk_dialog_new_with_buttons(_("Add Chat"),
5168 NULL, GTK_DIALOG_NO_SEPARATOR,
5169 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
5170 GTK_STOCK_ADD, GTK_RESPONSE_OK,
5171 NULL);
5172
5173 gtk_dialog_set_default_response(GTK_DIALOG(data->window), GTK_RESPONSE_OK);
5174 gtk_container_set_border_width(GTK_CONTAINER(data->window), GAIM_HIG_BOX_SPACE);
5175 gtk_window_set_resizable(GTK_WINDOW(data->window), FALSE);
5176 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(data->window)->vbox), GAIM_HIG_BORDER);
5177 gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(data->window)->vbox), GAIM_HIG_BOX_SPACE);
5178 gtk_window_set_role(GTK_WINDOW(data->window), "add_chat");
5179 gtk_window_set_type_hint(GTK_WINDOW(data->window),
5180 GDK_WINDOW_TYPE_HINT_DIALOG);
5181
5182 hbox = gtk_hbox_new(FALSE, GAIM_HIG_BORDER);
5183 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(data->window)->vbox), hbox);
5184 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
5185 gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
5186
5187 vbox = gtk_vbox_new(FALSE, 5);
5188 gtk_container_add(GTK_CONTAINER(hbox), vbox);
5189
5190 label = gtk_label_new(
5191 _("Please enter an alias, and the appropriate information "
5192 "about the chat you would like to add to your buddy list.\n"));
5193 gtk_widget_set_size_request(label, 400, -1);
5194 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
5195 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
5196 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
5197
5198 rowbox = gtk_hbox_new(FALSE, 5);
5199 gtk_box_pack_start(GTK_BOX(vbox), rowbox, FALSE, FALSE, 0);
5200
5201 label = gtk_label_new(_("Account:"));
5202 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
5203 gtk_size_group_add_widget(data->sg, label);
5204 gtk_box_pack_start(GTK_BOX(rowbox), label, FALSE, FALSE, 0);
5205
5206 data->account_menu = gaim_gtk_account_option_menu_new(account, FALSE,
5207 G_CALLBACK(addchat_select_account_cb),
5208 chat_account_filter_func, data);
5209 gtk_box_pack_start(GTK_BOX(rowbox), data->account_menu, TRUE, TRUE, 0);
5210 gaim_set_accessible_label (data->account_menu, label);
5211
5212 data->entries_box = gtk_vbox_new(FALSE, 5);
5213 gtk_container_set_border_width(GTK_CONTAINER(data->entries_box), 0);
5214 gtk_box_pack_start(GTK_BOX(vbox), data->entries_box, TRUE, TRUE, 0);
5215
5216 rebuild_addchat_entries(data);
5217
5218 rowbox = gtk_hbox_new(FALSE, 5);
5219 gtk_box_pack_start(GTK_BOX(vbox), rowbox, FALSE, FALSE, 0);
5220
5221 label = gtk_label_new(_("Alias:"));
5222 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
5223 gtk_size_group_add_widget(data->sg, label);
5224 gtk_box_pack_start(GTK_BOX(rowbox), label, FALSE, FALSE, 0);
5225
5226 data->alias_entry = gtk_entry_new();
5227 if (alias != NULL)
5228 gtk_entry_set_text(GTK_ENTRY(data->alias_entry), alias);
5229 gtk_box_pack_end(GTK_BOX(rowbox), data->alias_entry, TRUE, TRUE, 0);
5230 gtk_entry_set_activates_default(GTK_ENTRY(data->alias_entry), TRUE);
5231 gaim_set_accessible_label (data->alias_entry, label);
5232
5233 rowbox = gtk_hbox_new(FALSE, 5);
5234 gtk_box_pack_start(GTK_BOX(vbox), rowbox, FALSE, FALSE, 0);
5235
5236 label = gtk_label_new(_("Group:"));
5237 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
5238 gtk_size_group_add_widget(data->sg, label);
5239 gtk_box_pack_start(GTK_BOX(rowbox), label, FALSE, FALSE, 0);
5240
5241 data->group_combo = gtk_combo_new();
5242 gtk_combo_set_popdown_strings(GTK_COMBO(data->group_combo), groups_tree());
5243 gtk_box_pack_end(GTK_BOX(rowbox), data->group_combo, TRUE, TRUE, 0);
5244
5245 if (group)
5246 {
5247 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(data->group_combo)->entry),
5248 group->name);
5249 }
5250 gaim_set_accessible_label (data->group_combo, label);
5251
5252 g_signal_connect(G_OBJECT(data->window), "response",
5253 G_CALLBACK(add_chat_resp_cb), data);
5254
5255 gtk_widget_show_all(data->window);
5256 }
5257
5258 static void
5259 add_group_cb(GaimConnection *gc, const char *group_name)
5260 {
5261 GaimGroup *group;
5262
5263 if ((group_name == NULL) || (*group_name == '\0'))
5264 return;
5265
5266 group = gaim_group_new(group_name);
5267 gaim_blist_add_group(group, NULL);
5268 }
5269
5270 static void
5271 gaim_gtk_blist_request_add_group(void)
5272 {
5273 gaim_request_input(NULL, _("Add Group"), NULL,
5274 _("Please enter the name of the group to be added."),
5275 NULL, FALSE, FALSE, NULL,
5276 _("Add"), G_CALLBACK(add_group_cb),
5277 _("Cancel"), NULL, NULL);
5278 }
5279
5280 void
5281 gaim_gtk_blist_toggle_visibility()
5282 {
5283 if (gtkblist && gtkblist->window) {
5284 if (GTK_WIDGET_VISIBLE(gtkblist->window)) {
5285 gaim_blist_set_visible(GAIM_WINDOW_ICONIFIED(gtkblist->window) || gtk_blist_obscured);
5286 } else {
5287 gaim_blist_set_visible(TRUE);
5288 }
5289 }
5290 }
5291
5292 void
5293 gaim_gtk_blist_visibility_manager_add()
5294 {
5295 visibility_manager_count++;
5296 gaim_debug_info("gtkblist", "added visibility manager: %d\n", visibility_manager_count);
5297 }
5298
5299 void
5300 gaim_gtk_blist_visibility_manager_remove()
5301 {
5302 if (visibility_manager_count)
5303 visibility_manager_count--;
5304 if (!visibility_manager_count)
5305 gaim_blist_set_visible(TRUE);
5306 gaim_debug_info("gtkblist", "removed visibility manager: %d\n", visibility_manager_count);
5307 }
5308
5309
5310 static GaimBlistUiOps blist_ui_ops =
5311 {
5312 gaim_gtk_blist_new_list,
5313 gaim_gtk_blist_new_node,
5314 gaim_gtk_blist_show,
5315 gaim_gtk_blist_update,
5316 gaim_gtk_blist_remove,
5317 gaim_gtk_blist_destroy,
5318 gaim_gtk_blist_set_visible,
5319 gaim_gtk_blist_request_add_buddy,
5320 gaim_gtk_blist_request_add_chat,
5321 gaim_gtk_blist_request_add_group
5322 };
5323
5324
5325 GaimBlistUiOps *
5326 gaim_gtk_blist_get_ui_ops(void)
5327 {
5328 return &blist_ui_ops;
5329 }
5330
5331 GaimGtkBuddyList *gaim_gtk_blist_get_default_gtk_blist()
5332 {
5333 return gtkblist;
5334 }
5335
5336 static void account_signon_cb(GaimConnection *gc, gpointer z)
5337 {
5338 GaimAccount *account = gaim_connection_get_account(gc);
5339 GaimBlistNode *gnode, *cnode;
5340 for(gnode = gaim_get_blist()->root; gnode; gnode = gnode->next)
5341 {
5342 if(!GAIM_BLIST_NODE_IS_GROUP(gnode))
5343 continue;
5344 for(cnode = gnode->child; cnode; cnode = cnode->next)
5345 {
5346 GaimChat *chat;
5347
5348 if(!GAIM_BLIST_NODE_IS_CHAT(cnode))
5349 continue;
5350
5351 chat = (GaimChat *)cnode;
5352
5353 if(chat->account != account)
5354 continue;
5355
5356 if(gaim_blist_node_get_bool((GaimBlistNode*)chat, "gtk-autojoin") ||
5357 (gaim_blist_node_get_string((GaimBlistNode*)chat,
5358 "gtk-autojoin") != NULL))
5359 serv_join_chat(gc, chat->components);
5360 }
5361 }
5362 }
5363
5364 void *
5365 gaim_gtk_blist_get_handle() {
5366 static int handle;
5367
5368 return &handle;
5369 }
5370
5371 static gboolean buddy_signonoff_timeout_cb(GaimBuddy *buddy)
5372 {
5373 struct _gaim_gtk_blist_node *gtknode = ((GaimBlistNode*)buddy)->ui_data;
5374
5375 gtknode->recent_signonoff = FALSE;
5376 gtknode->recent_signonoff_timer = 0;
5377
5378 gaim_gtk_blist_update(NULL, (GaimBlistNode*)buddy);
5379
5380 return FALSE;
5381 }
5382
5383 static void buddy_signonoff_cb(GaimBuddy *buddy)
5384 {
5385 struct _gaim_gtk_blist_node *gtknode;
5386
5387 if(!((GaimBlistNode*)buddy)->ui_data) {
5388 gaim_gtk_blist_new_node((GaimBlistNode*)buddy);
5389 }
5390
5391 gtknode = ((GaimBlistNode*)buddy)->ui_data;
5392
5393 gtknode->recent_signonoff = TRUE;
5394
5395 if(gtknode->recent_signonoff_timer > 0)
5396 gaim_timeout_remove(gtknode->recent_signonoff_timer);
5397 gtknode->recent_signonoff_timer = gaim_timeout_add(10000,
5398 (GSourceFunc)buddy_signonoff_timeout_cb, buddy);
5399 }
5400
5401 void gaim_gtk_blist_init(void)
5402 {
5403 void *gtk_blist_handle = gaim_gtk_blist_get_handle();
5404
5405 gaim_signal_connect(gaim_connections_get_handle(), "signed-on",
5406 gtk_blist_handle, GAIM_CALLBACK(account_signon_cb),
5407 NULL);
5408
5409 /* Initialize prefs */
5410 gaim_prefs_add_none("/gaim/gtk/blist");
5411 gaim_prefs_add_bool("/gaim/gtk/blist/show_buddy_icons", TRUE);
5412 gaim_prefs_add_bool("/gaim/gtk/blist/show_empty_groups", FALSE);
5413 gaim_prefs_add_bool("/gaim/gtk/blist/show_idle_time", TRUE);
5414 gaim_prefs_add_bool("/gaim/gtk/blist/show_offline_buddies", FALSE);
5415 gaim_prefs_add_bool("/gaim/gtk/blist/list_visible", TRUE);
5416 gaim_prefs_add_bool("/gaim/gtk/blist/list_maximized", FALSE);
5417 gaim_prefs_add_string("/gaim/gtk/blist/sort_type", "alphabetical");
5418 gaim_prefs_add_int("/gaim/gtk/blist/x", 0);
5419 gaim_prefs_add_int("/gaim/gtk/blist/y", 0);
5420 gaim_prefs_add_int("/gaim/gtk/blist/width", 309); /* Golden ratio, baby */
5421 gaim_prefs_add_int("/gaim/gtk/blist/height", 500); /* Golden ratio, baby */
5422 gaim_prefs_add_int("/gaim/gtk/blist/tooltip_delay", 500);
5423
5424 /* Register our signals */
5425 gaim_signal_register(gtk_blist_handle, "gtkblist-hiding",
5426 gaim_marshal_VOID__POINTER, NULL, 1,
5427 gaim_value_new(GAIM_TYPE_POINTER));
5428
5429 gaim_signal_register(gtk_blist_handle, "gtkblist-unhiding",
5430 gaim_marshal_VOID__POINTER, NULL, 1,
5431 gaim_value_new(GAIM_TYPE_SUBTYPE));
5432
5433 gaim_signal_register(gtk_blist_handle, "gtkblist-created",
5434 gaim_marshal_VOID__POINTER, NULL, 1,
5435 gaim_value_new(GAIM_TYPE_SUBTYPE,
5436 GAIM_SUBTYPE_BLIST));
5437
5438 gaim_signal_register(gtk_blist_handle, "drawing-tooltip",
5439 gaim_marshal_VOID__POINTER_POINTER_UINT, NULL, 3,
5440 gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_BLIST_NODE),
5441 gaim_value_new_outgoing(GAIM_TYPE_BOXED, "GString *"),
5442 gaim_value_new(GAIM_TYPE_BOOLEAN));
5443
5444
5445 gaim_signal_connect(gaim_blist_get_handle(), "buddy-signed-on", gtk_blist_handle, GAIM_CALLBACK(buddy_signonoff_cb), NULL);
5446 gaim_signal_connect(gaim_blist_get_handle(), "buddy-signed-off", gtk_blist_handle, GAIM_CALLBACK(buddy_signonoff_cb), NULL);
5447 gaim_signal_connect(gaim_blist_get_handle(), "buddy-privacy-changed", gtk_blist_handle, GAIM_CALLBACK(gaim_gtk_blist_update_privacy_cb), NULL);
5448 }
5449
5450 void
5451 gaim_gtk_blist_uninit(void) {
5452 gaim_signals_unregister_by_instance(gaim_gtk_blist_get_handle());
5453 }
5454
5455 /*********************************************************************
5456 * Buddy List sorting functions *
5457 *********************************************************************/
5458
5459 GList *gaim_gtk_blist_get_sort_methods()
5460 {
5461 return gaim_gtk_blist_sort_methods;
5462 }
5463
5464 void gaim_gtk_blist_sort_method_reg(const char *id, const char *name, gaim_gtk_blist_sort_function func)
5465 {
5466 struct gaim_gtk_blist_sort_method *method = g_new0(struct gaim_gtk_blist_sort_method, 1);
5467 method->id = g_strdup(id);
5468 method->name = g_strdup(name);
5469 method->func = func;
5470 gaim_gtk_blist_sort_methods = g_list_append(gaim_gtk_blist_sort_methods, method);
5471 gaim_gtk_blist_update_sort_methods();
5472 }
5473
5474 void gaim_gtk_blist_sort_method_unreg(const char *id){
5475 GList *l = gaim_gtk_blist_sort_methods;
5476
5477 while(l) {
5478 struct gaim_gtk_blist_sort_method *method = l->data;
5479 if(!strcmp(method->id, id)) {
5480 gaim_gtk_blist_sort_methods = g_list_delete_link(gaim_gtk_blist_sort_methods, l);
5481 g_free(method->id);
5482 g_free(method->name);
5483 g_free(method);
5484 break;
5485 }
5486 }
5487 gaim_gtk_blist_update_sort_methods();
5488 }
5489
5490 void gaim_gtk_blist_sort_method_set(const char *id){
5491 GList *l = gaim_gtk_blist_sort_methods;
5492
5493 if(!id)
5494 id = "none";
5495
5496 while (l && strcmp(((struct gaim_gtk_blist_sort_method*)l->data)->id, id))
5497 l = l->next;
5498
5499 if (l) {
5500 current_sort_method = l->data;
5501 } else if (!current_sort_method) {
5502 gaim_gtk_blist_sort_method_set("none");
5503 return;
5504 }
5505 if (!strcmp(id, "none")) {
5506 redo_buddy_list(gaim_get_blist(), TRUE, FALSE);
5507 } else {
5508 redo_buddy_list(gaim_get_blist(), FALSE, FALSE);
5509 }
5510 }
5511
5512 /******************************************
5513 ** Sort Methods
5514 ******************************************/
5515
5516 static void sort_method_none(GaimBlistNode *node, GaimBuddyList *blist, GtkTreeIter parent_iter, GtkTreeIter *cur, GtkTreeIter *iter)
5517 {
5518 GaimBlistNode *sibling = node->prev;
5519 GtkTreeIter sibling_iter;
5520
5521 if (cur != NULL) {
5522 *iter = *cur;
5523 return;
5524 }
5525
5526 while (sibling && !get_iter_from_node(sibling, &sibling_iter)) {
5527 sibling = sibling->prev;
5528 }
5529
5530 gtk_tree_store_insert_after(gtkblist->treemodel, iter,
5531 node->parent ? &parent_iter : NULL,
5532 sibling ? &sibling_iter : NULL);
5533 }
5534
5535 #if GTK_CHECK_VERSION(2,2,1)
5536
5537 static void sort_method_alphabetical(GaimBlistNode *node, GaimBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter)
5538 {
5539 GtkTreeIter more_z;
5540
5541 const char *my_name;
5542
5543 if(GAIM_BLIST_NODE_IS_CONTACT(node)) {
5544 my_name = gaim_contact_get_alias((GaimContact*)node);
5545 } else if(GAIM_BLIST_NODE_IS_CHAT(node)) {
5546 my_name = gaim_chat_get_name((GaimChat*)node);
5547 } else {
5548 sort_method_none(node, blist, groupiter, cur, iter);
5549 return;
5550 }
5551
5552
5553 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
5554 gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
5555 return;
5556 }
5557
5558 do {
5559 GValue val;
5560 GaimBlistNode *n;
5561 const char *this_name;
5562 int cmp;
5563
5564 val.g_type = 0;
5565 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &more_z, NODE_COLUMN, &val);
5566 n = g_value_get_pointer(&val);
5567
5568 if(GAIM_BLIST_NODE_IS_CONTACT(n)) {
5569 this_name = gaim_contact_get_alias((GaimContact*)n);
5570 } else if(GAIM_BLIST_NODE_IS_CHAT(n)) {
5571 this_name = gaim_chat_get_name((GaimChat*)n);
5572 } else {
5573 this_name = NULL;
5574 }
5575
5576 cmp = gaim_utf8_strcasecmp(my_name, this_name);
5577
5578 if(this_name && (cmp < 0 || (cmp == 0 && node < n))) {
5579 if(cur) {
5580 gtk_tree_store_move_before(gtkblist->treemodel, cur, &more_z);
5581 *iter = *cur;
5582 return;
5583 } else {
5584 gtk_tree_store_insert_before(gtkblist->treemodel, iter,
5585 &groupiter, &more_z);
5586 return;
5587 }
5588 }
5589 g_value_unset(&val);
5590 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL(gtkblist->treemodel), &more_z));
5591
5592 if(cur) {
5593 gtk_tree_store_move_before(gtkblist->treemodel, cur, NULL);
5594 *iter = *cur;
5595 return;
5596 } else {
5597 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
5598 return;
5599 }
5600 }
5601
5602 static void sort_method_status(GaimBlistNode *node, GaimBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter)
5603 {
5604 GtkTreeIter more_z;
5605
5606 GaimBuddy *my_buddy, *this_buddy;
5607
5608 if(GAIM_BLIST_NODE_IS_CONTACT(node)) {
5609 my_buddy = gaim_contact_get_priority_buddy((GaimContact*)node);
5610 } else if(GAIM_BLIST_NODE_IS_CHAT(node)) {
5611 if (cur != NULL) {
5612 *iter = *cur;
5613 return;
5614 }
5615
5616 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
5617 return;
5618 } else {
5619 sort_method_none(node, blist, groupiter, cur, iter);
5620 return;
5621 }
5622
5623
5624 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
5625 gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
5626 return;
5627 }
5628
5629 do {
5630 GValue val;
5631 GaimBlistNode *n;
5632 gint name_cmp;
5633 gint presence_cmp;
5634
5635 val.g_type = 0;
5636 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &more_z, NODE_COLUMN, &val);
5637 n = g_value_get_pointer(&val);
5638
5639 if(GAIM_BLIST_NODE_IS_CONTACT(n)) {
5640 this_buddy = gaim_contact_get_priority_buddy((GaimContact*)n);
5641 } else {
5642 this_buddy = NULL;
5643 }
5644
5645 name_cmp = gaim_utf8_strcasecmp(
5646 gaim_contact_get_alias(gaim_buddy_get_contact(my_buddy)),
5647 (this_buddy
5648 ? gaim_contact_get_alias(gaim_buddy_get_contact(this_buddy))
5649 : NULL));
5650
5651 presence_cmp = gaim_presence_compare(
5652 gaim_buddy_get_presence(my_buddy),
5653 this_buddy ? gaim_buddy_get_presence(this_buddy) : NULL);
5654
5655 if (this_buddy == NULL ||
5656 (presence_cmp < 0 ||
5657 (presence_cmp == 0 &&
5658 (name_cmp < 0 || (name_cmp == 0 && node < n)))))
5659 {
5660 if (cur != NULL)
5661 {
5662 gtk_tree_store_move_before(gtkblist->treemodel, cur, &more_z);
5663 *iter = *cur;
5664 return;
5665 }
5666 else
5667 {
5668 gtk_tree_store_insert_before(gtkblist->treemodel, iter,
5669 &groupiter, &more_z);
5670 return;
5671 }
5672 }
5673
5674 g_value_unset(&val);
5675 }
5676 while (gtk_tree_model_iter_next(GTK_TREE_MODEL(gtkblist->treemodel),
5677 &more_z));
5678
5679 if (cur) {
5680 gtk_tree_store_move_before(gtkblist->treemodel, cur, NULL);
5681 *iter = *cur;
5682 return;
5683 } else {
5684 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
5685 return;
5686 }
5687 }
5688
5689 static void sort_method_log(GaimBlistNode *node, GaimBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter)
5690 {
5691 GtkTreeIter more_z;
5692
5693 int log_size = 0, this_log_size = 0;
5694 const char *buddy_name, *this_buddy_name;
5695
5696 if(cur && (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtkblist->treemodel), &groupiter) == 1)) {
5697 *iter = *cur;
5698 return;
5699 }
5700
5701 if(GAIM_BLIST_NODE_IS_CONTACT(node)) {
5702 GaimBlistNode *n;
5703 for (n = node->child; n; n = n->next)
5704 log_size += gaim_log_get_total_size(GAIM_LOG_IM, ((GaimBuddy*)(n))->name, ((GaimBuddy*)(n))->account);
5705 buddy_name = gaim_contact_get_alias((GaimContact*)node);
5706 } else if(GAIM_BLIST_NODE_IS_CHAT(node)) {
5707 /* we don't have a reliable way of getting the log filename
5708 * from the chat info in the blist, yet */
5709 if (cur != NULL) {
5710 *iter = *cur;
5711 return;
5712 }
5713
5714 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
5715 return;
5716 } else {
5717 sort_method_none(node, blist, groupiter, cur, iter);
5718 return;
5719 }
5720
5721
5722 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
5723 gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
5724 return;
5725 }
5726
5727 do {
5728 GValue val;
5729 GaimBlistNode *n;
5730 GaimBlistNode *n2;
5731 int cmp;
5732
5733 val.g_type = 0;
5734 gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &more_z, NODE_COLUMN, &val);
5735 n = g_value_get_pointer(&val);
5736 this_log_size = 0;
5737
5738 if(GAIM_BLIST_NODE_IS_CONTACT(n)) {
5739 for (n2 = n->child; n2; n2 = n2->next)
5740 this_log_size += gaim_log_get_total_size(GAIM_LOG_IM, ((GaimBuddy*)(n2))->name, ((GaimBuddy*)(n2))->account);
5741 this_buddy_name = gaim_contact_get_alias((GaimContact*)n);
5742 } else {
5743 this_buddy_name = NULL;
5744 }
5745
5746 cmp = gaim_utf8_strcasecmp(buddy_name, this_buddy_name);
5747
5748 if (!GAIM_BLIST_NODE_IS_CONTACT(n) || log_size > this_log_size ||
5749 ((log_size == this_log_size) &&
5750 (cmp < 0 || (cmp == 0 && node < n)))) {
5751 if (cur != NULL) {
5752 gtk_tree_store_move_before(gtkblist->treemodel, cur, &more_z);
5753 *iter = *cur;
5754 return;
5755 } else {
5756 gtk_tree_store_insert_before(gtkblist->treemodel, iter,
5757 &groupiter, &more_z);
5758 return;
5759 }
5760 }
5761 g_value_unset(&val);
5762 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL(gtkblist->treemodel), &more_z));
5763
5764 if (cur != NULL) {
5765 gtk_tree_store_move_before(gtkblist->treemodel, cur, NULL);
5766 *iter = *cur;
5767 return;
5768 } else {
5769 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
5770 return;
5771 }
5772 }
5773
5774 #endif
5775
5776 static void
5777 plugin_act(GtkObject *obj, GaimPluginAction *pam)
5778 {
5779 if (pam && pam->callback)
5780 pam->callback(pam);
5781 }
5782
5783 static void
5784 build_plugin_actions(GtkWidget *menu, GaimPlugin *plugin)
5785 {
5786 GtkWidget *menuitem;
5787 GaimPluginAction *action = NULL;
5788 GList *actions, *l;
5789
5790 actions = GAIM_PLUGIN_ACTIONS(plugin, NULL);
5791
5792 for (l = actions; l != NULL; l = l->next)
5793 {
5794 if (l->data)
5795 {
5796 action = (GaimPluginAction *) l->data;
5797 action->plugin = plugin;
5798 action->context = NULL;
5799
5800 menuitem = gtk_menu_item_new_with_label(action->label);
5801 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
5802
5803 g_signal_connect(G_OBJECT(menuitem), "activate",
5804 G_CALLBACK(plugin_act), action);
5805 g_object_set_data_full(G_OBJECT(menuitem), "plugin_action",
5806 action,
5807 (GDestroyNotify)gaim_plugin_action_free);
5808 gtk_widget_show(menuitem);
5809 }
5810 else
5811 gaim_separator(menu);
5812 }
5813
5814 g_list_free(actions);
5815 }
5816
5817 static void
5818 modify_account_cb(GtkWidget *widget, gpointer data)
5819 {
5820 gaim_gtk_account_dialog_show(GAIM_GTK_MODIFY_ACCOUNT_DIALOG, data);
5821 }
5822
5823 static void
5824 enable_account_cb(GtkCheckMenuItem *widget, gpointer data)
5825 {
5826 GaimAccount *account = data;
5827 const GaimSavedStatus *saved_status;
5828
5829 saved_status = gaim_savedstatus_get_current();
5830 gaim_savedstatus_activate_for_account(saved_status, account);
5831
5832 gaim_account_set_enabled(account, GAIM_GTK_UI, TRUE);
5833 }
5834
5835 static void
5836 disable_account_cb(GtkCheckMenuItem *widget, gpointer data)
5837 {
5838 GaimAccount *account = data;
5839
5840 gaim_account_set_enabled(account, GAIM_GTK_UI, FALSE);
5841 }
5842
5843 void
5844 gaim_gtk_blist_update_accounts_menu(void)
5845 {
5846 GtkWidget *menuitem = NULL, *submenu = NULL;
5847 GList *l = NULL, *accounts = NULL;
5848 gboolean disabled_accounts = FALSE;
5849
5850 if (accountmenu == NULL)
5851 return;
5852
5853 /* Clear the old Accounts menu */
5854 for (l = gtk_container_get_children(GTK_CONTAINER(accountmenu)); l; l = l->next) {
5855 menuitem = l->data;
5856
5857 if (menuitem != gtk_item_factory_get_widget(gtkblist->ift, N_("/Accounts/Add\\/Edit")))
5858 gtk_container_remove(GTK_CONTAINER(accountmenu),
5859 menuitem);
5860 }
5861
5862 for (accounts = gaim_accounts_get_all(); accounts; accounts = accounts->next) {
5863 char *buf = NULL;
5864 GtkWidget *image = NULL;
5865 GaimConnection *gc = NULL;
5866 GaimAccount *account = NULL;
5867 GaimStatus *status = NULL;
5868 GdkPixbuf *pixbuf = NULL;
5869
5870 account = accounts->data;
5871
5872 if(gaim_account_get_enabled(account, GAIM_GTK_UI)) {
5873 buf = g_strconcat(gaim_account_get_username(account), " (",
5874 gaim_account_get_protocol_name(account), ")", NULL);
5875 menuitem = gtk_image_menu_item_new_with_label(buf);
5876 g_free(buf);
5877 status = gaim_account_get_active_status(account);
5878 pixbuf = gaim_gtk_create_prpl_icon_with_status(account, gaim_status_get_type(status), 0.5);
5879 if (pixbuf != NULL)
5880 {
5881 if (!gaim_account_is_connected(account))
5882 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf,
5883 0.0, FALSE);
5884 image = gtk_image_new_from_pixbuf(pixbuf);
5885 g_object_unref(G_OBJECT(pixbuf));
5886 gtk_widget_show(image);
5887 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
5888 }
5889 gtk_menu_shell_append(GTK_MENU_SHELL(accountmenu), menuitem);
5890 gtk_widget_show(menuitem);
5891
5892 submenu = gtk_menu_new();
5893 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
5894 gtk_widget_show(submenu);
5895
5896
5897 menuitem = gtk_menu_item_new_with_mnemonic(_("_Edit Account"));
5898 g_signal_connect(G_OBJECT(menuitem), "activate",
5899 G_CALLBACK(modify_account_cb), account);
5900 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
5901 gtk_widget_show(menuitem);
5902
5903 gaim_separator(submenu);
5904
5905 gc = gaim_account_get_connection(account);
5906 if (gc && GAIM_CONNECTION_IS_CONNECTED(gc)) {
5907 GaimPlugin *plugin = NULL;
5908
5909 plugin = gc->prpl;
5910 if (GAIM_PLUGIN_HAS_ACTIONS(plugin)) {
5911 GList *l, *ll = NULL;
5912 GaimPluginAction *action = NULL;
5913
5914 for (l = ll = GAIM_PLUGIN_ACTIONS(plugin, gc); l; l = l->next) {
5915 if (l->data) {
5916 action = (GaimPluginAction *)l->data;
5917 action->plugin = plugin;
5918 action->context = gc;
5919
5920 menuitem = gtk_menu_item_new_with_label(action->label);
5921 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
5922 g_signal_connect(G_OBJECT(menuitem), "activate",
5923 G_CALLBACK(plugin_act), action);
5924 g_object_set_data_full(G_OBJECT(menuitem), "plugin_action", action, (GDestroyNotify)gaim_plugin_action_free);
5925 gtk_widget_show(menuitem);
5926 } else
5927 gaim_separator(submenu);
5928 }
5929 } else {
5930 menuitem = gtk_menu_item_new_with_label(_("No actions available"));
5931 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
5932 gtk_widget_set_sensitive(menuitem, FALSE);
5933 gtk_widget_show(menuitem);
5934 }
5935 } else {
5936 menuitem = gtk_menu_item_new_with_label(_("No actions available"));
5937 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
5938 gtk_widget_set_sensitive(menuitem, FALSE);
5939 gtk_widget_show(menuitem);
5940 }
5941
5942 gaim_separator(submenu);
5943
5944 menuitem = gtk_menu_item_new_with_mnemonic(_("_Disable"));
5945 g_signal_connect(G_OBJECT(menuitem), "activate",
5946 G_CALLBACK(disable_account_cb), account);
5947 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
5948 gtk_widget_show(menuitem);
5949 } else {
5950 disabled_accounts = TRUE;
5951 }
5952 }
5953
5954 if(disabled_accounts) {
5955 gaim_separator(accountmenu);
5956 menuitem = gtk_menu_item_new_with_label(_("Enable Account"));
5957 gtk_menu_shell_append(GTK_MENU_SHELL(accountmenu), menuitem);
5958 gtk_widget_show(menuitem);
5959
5960 submenu = gtk_menu_new();
5961 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
5962 gtk_widget_show(submenu);
5963
5964 for (accounts = gaim_accounts_get_all(); accounts; accounts = accounts->next) {
5965 char *buf = NULL;
5966 GtkWidget *image = NULL;
5967 GaimAccount *account = NULL;
5968 GdkPixbuf *pixbuf = NULL;
5969
5970 account = accounts->data;
5971
5972 if(!gaim_account_get_enabled(account, GAIM_GTK_UI)) {
5973
5974 disabled_accounts = TRUE;
5975
5976 buf = g_strconcat(gaim_account_get_username(account), " (",
5977 gaim_account_get_protocol_name(account), ")", NULL);
5978 menuitem = gtk_image_menu_item_new_with_label(buf);
5979 g_free(buf);
5980 pixbuf = gaim_gtk_create_prpl_icon(account, 0.5);
5981 if (pixbuf != NULL)
5982 {
5983 if (!gaim_account_is_connected(account))
5984 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE);
5985 image = gtk_image_new_from_pixbuf(pixbuf);
5986 g_object_unref(G_OBJECT(pixbuf));
5987 gtk_widget_show(image);
5988 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
5989 }
5990 g_signal_connect(G_OBJECT(menuitem), "activate",
5991 G_CALLBACK(enable_account_cb), account);
5992 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
5993 gtk_widget_show(menuitem);
5994 }
5995 }
5996 }
5997 }
5998
5999 static GList *plugin_submenus = NULL;
6000
6001 void
6002 gaim_gtk_blist_update_plugin_actions(void)
6003 {
6004 GtkWidget *menuitem, *submenu;
6005 GaimPlugin *plugin = NULL;
6006 GList *l;
6007 GtkAccelGroup *accel_group;
6008
6009 GtkWidget *pluginmenu = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools"));
6010
6011 g_return_if_fail(pluginmenu != NULL);
6012
6013 /* Remove old plugin action submenus from the Tools menu */
6014 for (l = plugin_submenus; l; l = l->next)
6015 {
6016 GList *menuitems;
6017
6018 submenu = l->data;
6019
6020 menuitems = gtk_container_get_children(GTK_CONTAINER(submenu));
6021 while (menuitems != NULL)
6022 {
6023 GaimPluginAction *action;
6024 menuitem = menuitems->data;
6025 action = g_object_get_data(G_OBJECT(menuitem), "plugin_action");
6026 g_free(action);
6027 menuitems = g_list_delete_link(menuitems, menuitems);
6028 }
6029
6030 gtk_container_remove(GTK_CONTAINER(pluginmenu), GTK_WIDGET(submenu));
6031 }
6032 g_list_free(plugin_submenus);
6033 plugin_submenus = NULL;
6034
6035 accel_group = gtk_menu_get_accel_group(GTK_MENU(pluginmenu));
6036
6037 /* Add a submenu for each plugin with custom actions */
6038 for (l = gaim_plugins_get_loaded(); l; l = l->next) {
6039 char *path;
6040
6041 plugin = (GaimPlugin *) l->data;
6042
6043 if (GAIM_IS_PROTOCOL_PLUGIN(plugin))
6044 continue;
6045
6046 if (!GAIM_PLUGIN_HAS_ACTIONS(plugin))
6047 continue;
6048
6049 menuitem = gtk_image_menu_item_new_with_label(_(plugin->info->name));
6050 gtk_menu_shell_append(GTK_MENU_SHELL(pluginmenu), menuitem);
6051 gtk_widget_show(menuitem);
6052
6053 plugin_submenus = g_list_append(plugin_submenus, menuitem);
6054
6055 submenu = gtk_menu_new();
6056 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
6057 gtk_widget_show(submenu);
6058
6059 gtk_menu_set_accel_group(GTK_MENU(submenu), accel_group);
6060 path = g_strdup_printf("%s/Tools/%s", gtkblist->ift->path, plugin->info->name);
6061 gtk_menu_set_accel_path(GTK_MENU(submenu), path);
6062 g_free(path);
6063
6064 build_plugin_actions(submenu, plugin);
6065 }
6066 }
6067
6068 static void
6069 sortmethod_act(GtkCheckMenuItem *checkmenuitem, char *id)
6070 {
6071 if (gtk_check_menu_item_get_active(checkmenuitem))
6072 {
6073 gaim_gtk_set_cursor(gtkblist->window, GDK_WATCH);
6074 /* This is redundant. I think. */
6075 /* gaim_gtk_blist_sort_method_set(id); */
6076 gaim_prefs_set_string("/gaim/gtk/blist/sort_type", id);
6077
6078 gaim_gtk_clear_cursor(gtkblist->window);
6079 }
6080 }
6081
6082 void
6083 gaim_gtk_blist_update_sort_methods(void)
6084 {
6085 GtkWidget *menuitem = NULL, *activeitem = NULL;
6086 GaimGtkBlistSortMethod *method = NULL;
6087 GList *l;
6088 GSList *sl = NULL;
6089 GtkWidget *sortmenu;
6090 const char *m = gaim_prefs_get_string("/gaim/gtk/blist/sort_type");
6091
6092 if (gtkblist == NULL)
6093 return;
6094
6095 sortmenu = gtk_item_factory_get_widget(gtkblist->ift, N_("/Buddies/Sort Buddies"));
6096
6097 if (sortmenu == NULL)
6098 return;
6099
6100 /* Clear the old menu */
6101 for (l = gtk_container_get_children(GTK_CONTAINER(sortmenu)); l; l = l->next) {
6102 menuitem = l->data;
6103 gtk_widget_destroy(GTK_WIDGET(menuitem));
6104 }
6105
6106 for (l = gaim_gtk_blist_sort_methods; l; l = l->next) {
6107 method = (GaimGtkBlistSortMethod *) l->data;
6108 menuitem = gtk_radio_menu_item_new_with_label(sl, _(method->name));
6109 if (!strcmp(m, method->id))
6110 activeitem = menuitem;
6111 sl = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem));
6112 gtk_menu_shell_append(GTK_MENU_SHELL(sortmenu), menuitem);
6113 g_signal_connect(G_OBJECT(menuitem), "toggled",
6114 G_CALLBACK(sortmethod_act), method->id);
6115 gtk_widget_show(menuitem);
6116 }
6117 if (activeitem)
6118 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(activeitem), TRUE);
6119 }