1 /*
2 * @file gtkblist.c GTK+ BuddyList API
3 * @ingroup pidgin
4 */
5
6 /* pidgin
7 *
8 * Pidgin is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
25 *
26 */
27 #include "internal.h"
28 #include "pidgin.h"
29
30 #include "account.h"
31 #include "connection.h"
32 #include "core.h"
33 #include "debug.h"
34 #include "glibcompat.h"
35 #include "notify.h"
36 #include "prpl.h"
37 #include "prefs.h"
38 #include "plugin.h"
39 #include "request.h"
40 #include "signals.h"
41 #include "pidginstock.h"
42 #include "theme-loader.h"
43 #include "theme-manager.h"
44 #include "util.h"
45
46 #include "gtkaccount.h"
47 #include "gtkblist.h"
48 #include "gtkcellrendererexpander.h"
49 #include "gtkcertmgr.h"
50 #include "gtkconv.h"
51 #include "gtkdebug.h"
52 #include "gtkdialogs.h"
53 #include "gtkft.h"
54 #include "gtklog.h"
55 #include "gtkmenutray.h"
56 #include "gtkpounce.h"
57 #include "gtkplugin.h"
58 #include "gtkprefs.h"
59 #include "gtkprivacy.h"
60 #include "gtkroomlist.h"
61 #include "gtkstatusbox.h"
62 #include "gtkscrollbook.h"
63 #include "gtksmiley.h"
64 #include "gtkstyle.h"
65 #include "gtkblist-theme.h"
66 #include "gtkblist-theme-loader.h"
67 #include "gtkutils.h"
68 #include "pidgin/minidialog.h"
69 #include "pidgin/pidgintooltip.h"
70
71 #include <gdk/gdkkeysyms.h>
72 #include <gtk/gtk.h>
73 #include <gdk/gdk.h>
74
75 typedef struct
76 {
77 PurpleAccount *account;
78 GtkWidget *window;
79 GtkBox *vbox;
80 GtkWidget *account_menu;
81 GtkSizeGroup *sg;
82 } PidginBlistRequestData;
83
84 typedef struct
85 {
86 PidginBlistRequestData rq_data;
87 GtkWidget *combo;
88 GtkWidget *entry;
89 GtkWidget *entry_for_alias;
90 GtkWidget *entry_for_invite;
91
92 } PidginAddBuddyData;
93
94 typedef struct
95 {
96 PidginBlistRequestData rq_data;
97 gchar *default_chat_name;
98 GList *entries;
99 } PidginChatData;
100
101 typedef struct
102 {
103 PidginChatData chat_data;
104
105 GtkWidget *alias_entry;
106 GtkWidget *group_combo;
107 GtkWidget *autojoin;
108 GtkWidget *persistent;
109 } PidginAddChatData;
110
111 typedef struct
112 {
113 /** Used to hold error minidialogs. Gets packed
114 * inside PidginBuddyList.error_buttons
115 */
116 PidginScrollBook *error_scrollbook;
117
118 /** Pointer to the mini-dialog about having signed on elsewhere, if one
119 * is showing; @c NULL otherwise.
120 */
121 PidginMiniDialog *signed_on_elsewhere;
122
123 PidginBlistTheme *current_theme;
124
125 guint select_page_timeout; /**< The timeout for pidgin_blist_select_notebook_page_cb */
126 } PidginBuddyListPrivate;
127
128 #define PIDGIN_BUDDY_LIST_GET_PRIVATE(list) \
129 ((PidginBuddyListPrivate *)((list)->priv))
130
131 static GtkWidget *accountmenu = NULL;
132
133 static guint visibility_manager_count = 0;
134 static GdkVisibilityState gtk_blist_visibility = GDK_VISIBILITY_UNOBSCURED;
135 static gboolean gtk_blist_focused = FALSE;
136 static gboolean editing_blist = FALSE;
137
138 static GList *pidgin_blist_sort_methods = NULL;
139 static struct pidgin_blist_sort_method *current_sort_method = NULL;
140 static void sort_method_none(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
141
142 static void sort_method_alphabetical(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
143 static void sort_method_status(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
144 static void sort_method_log_activity(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter);
145 static PidginBuddyList *gtkblist = NULL;
146
147 static GList *groups_tree(void);
148 static gboolean pidgin_blist_refresh_timer(PurpleBuddyList *list);
149 static void pidgin_blist_update_buddy(PurpleBuddyList *list, PurpleBlistNode *node, gboolean status_change);
150 static void pidgin_blist_selection_changed(GtkTreeSelection *selection, gpointer data);
151 static void pidgin_blist_update(PurpleBuddyList *list, PurpleBlistNode *node);
152 static void pidgin_blist_update_group(PurpleBuddyList *list, PurpleBlistNode *node);
153 static void pidgin_blist_update_contact(PurpleBuddyList *list, PurpleBlistNode *node);
154 static char *pidgin_get_tooltip_text(PurpleBlistNode *node, gboolean full);
155 static const char *item_factory_translate_func (const char *path, gpointer func_data);
156 static gboolean get_iter_from_node(PurpleBlistNode *node, GtkTreeIter *iter);
157 static gboolean buddy_is_displayable(PurpleBuddy *buddy);
158 static void redo_buddy_list(PurpleBuddyList *list, gboolean remove, gboolean rerender);
159 static void pidgin_blist_collapse_contact_cb(GtkWidget *w, PurpleBlistNode *node);
160 static char *pidgin_get_group_title(PurpleBlistNode *gnode, gboolean expanded);
161 static void pidgin_blist_expand_contact_cb(GtkWidget *w, PurpleBlistNode *node);
162 static void set_urgent(void);
163
164 typedef enum {
165 PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE = 1 << 0, /* Whether there's pending message in a conversation */
166 PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK = 1 << 1, /* Whether there's a pending message in a chat that mentions our nick */
167 } PidginBlistNodeFlags;
168
169 typedef struct _pidgin_blist_node {
170 GtkTreeRowReference *row;
171 gboolean contact_expanded;
172 gboolean recent_signonoff;
173 gint recent_signonoff_timer;
174 struct {
175 PurpleConversation *conv;
176 time_t last_message; /* timestamp for last displayed message */
177 PidginBlistNodeFlags flags;
178 } conv;
179 } PidginBlistNode;
180
181 /***************************************************
182 * Callbacks *
183 ***************************************************/
gtk_blist_visibility_cb(GtkWidget * w,GdkEventVisibility * event,gpointer data)184 static gboolean gtk_blist_visibility_cb(GtkWidget *w, GdkEventVisibility *event, gpointer data)
185 {
186 GdkVisibilityState old_state = gtk_blist_visibility;
187 gtk_blist_visibility = event->state;
188
189 if (gtk_blist_visibility == GDK_VISIBILITY_FULLY_OBSCURED &&
190 old_state != GDK_VISIBILITY_FULLY_OBSCURED) {
191
192 /* no longer fully obscured */
193 pidgin_blist_refresh_timer(purple_get_blist());
194 }
195
196 /* continue to handle event normally */
197 return FALSE;
198 }
199
gtk_blist_window_state_cb(GtkWidget * w,GdkEventWindowState * event,gpointer data)200 static gboolean gtk_blist_window_state_cb(GtkWidget *w, GdkEventWindowState *event, gpointer data)
201 {
202 if(event->changed_mask & GDK_WINDOW_STATE_WITHDRAWN) {
203 if(event->new_window_state & GDK_WINDOW_STATE_WITHDRAWN)
204 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", FALSE);
205 else {
206 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", TRUE);
207 pidgin_blist_refresh_timer(purple_get_blist());
208 }
209 }
210
211 if(event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) {
212 if(event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED)
213 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", TRUE);
214 else
215 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", FALSE);
216 }
217
218 /* Refresh gtkblist if un-iconifying */
219 if (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED){
220 if (!(event->new_window_state & GDK_WINDOW_STATE_ICONIFIED))
221 pidgin_blist_refresh_timer(purple_get_blist());
222 }
223
224 return FALSE;
225 }
226
gtk_blist_delete_cb(GtkWidget * w,GdkEventAny * event,gpointer data)227 static gboolean gtk_blist_delete_cb(GtkWidget *w, GdkEventAny *event, gpointer data)
228 {
229 if(visibility_manager_count)
230 purple_blist_set_visible(FALSE);
231 else
232 purple_core_quit();
233
234 /* we handle everything, event should not propogate further */
235 return TRUE;
236 }
237
gtk_blist_configure_cb(GtkWidget * w,GdkEventConfigure * event,gpointer data)238 static gboolean gtk_blist_configure_cb(GtkWidget *w, GdkEventConfigure *event, gpointer data)
239 {
240 /* unfortunately GdkEventConfigure ignores the window gravity, but *
241 * the only way we have of setting the position doesn't. we have to *
242 * call get_position because it does pay attention to the gravity. *
243 * this is inefficient and I agree it sucks, but it's more likely *
244 * to work correctly. - Robot101 */
245 gint x, y;
246
247 /* check for visibility because when we aren't visible, this will *
248 * give us bogus (0,0) coordinates. - xOr */
249 if (GTK_WIDGET_VISIBLE(w))
250 gtk_window_get_position(GTK_WINDOW(w), &x, &y);
251 else
252 return FALSE; /* carry on normally */
253
254 #ifdef _WIN32
255 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
256 * when the window is being maximized */
257 if (gdk_window_get_state(w->window)
258 & GDK_WINDOW_STATE_MAXIMIZED) {
259 return FALSE;
260 }
261 #endif
262
263 /* don't save if nothing changed */
264 if (x == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/x") &&
265 y == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/y") &&
266 event->width == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width") &&
267 event->height == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/height")) {
268
269 return FALSE; /* carry on normally */
270 }
271
272 /* don't save off-screen positioning */
273 if (x + event->width < 0 ||
274 y + event->height < 0 ||
275 x > gdk_screen_width() ||
276 y > gdk_screen_height()) {
277
278 return FALSE; /* carry on normally */
279 }
280
281 /* ignore changes when maximized */
282 if(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized"))
283 return FALSE;
284
285 /* store the position */
286 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/x", x);
287 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/y", y);
288 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/width", event->width);
289 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/height", event->height);
290
291 /* continue to handle event normally */
292 return FALSE;
293 }
294
gtk_blist_menu_info_cb(GtkWidget * w,PurpleBuddy * b)295 static void gtk_blist_menu_info_cb(GtkWidget *w, PurpleBuddy *b)
296 {
297 PurpleAccount *account = purple_buddy_get_account(b);
298
299 pidgin_retrieve_user_info(purple_account_get_connection(account),
300 purple_buddy_get_name(b));
301 }
302
gtk_blist_menu_im_cb(GtkWidget * w,PurpleBuddy * b)303 static void gtk_blist_menu_im_cb(GtkWidget *w, PurpleBuddy *b)
304 {
305 pidgin_dialogs_im_with_user(purple_buddy_get_account(b),
306 purple_buddy_get_name(b));
307 }
308
309 #ifdef USE_VV
gtk_blist_menu_audio_call_cb(GtkWidget * w,PurpleBuddy * b)310 static void gtk_blist_menu_audio_call_cb(GtkWidget *w, PurpleBuddy *b)
311 {
312 purple_prpl_initiate_media(purple_buddy_get_account(b),
313 purple_buddy_get_name(b), PURPLE_MEDIA_AUDIO);
314 }
315
gtk_blist_menu_video_call_cb(GtkWidget * w,PurpleBuddy * b)316 static void gtk_blist_menu_video_call_cb(GtkWidget *w, PurpleBuddy *b)
317 {
318 /* if the buddy supports both audio and video, start a combined call,
319 otherwise start a pure video session */
320 if (purple_prpl_get_media_caps(purple_buddy_get_account(b),
321 purple_buddy_get_name(b)) &
322 PURPLE_MEDIA_CAPS_AUDIO_VIDEO) {
323 purple_prpl_initiate_media(purple_buddy_get_account(b),
324 purple_buddy_get_name(b), PURPLE_MEDIA_AUDIO | PURPLE_MEDIA_VIDEO);
325 } else {
326 purple_prpl_initiate_media(purple_buddy_get_account(b),
327 purple_buddy_get_name(b), PURPLE_MEDIA_VIDEO);
328 }
329 }
330
331 #endif
332
gtk_blist_menu_send_file_cb(GtkWidget * w,PurpleBuddy * b)333 static void gtk_blist_menu_send_file_cb(GtkWidget *w, PurpleBuddy *b)
334 {
335 PurpleAccount *account = purple_buddy_get_account(b);
336
337 serv_send_file(purple_account_get_connection(account),
338 purple_buddy_get_name(b), NULL);
339 }
340
gtk_blist_menu_move_to_cb(GtkWidget * w,PurpleBlistNode * node)341 static void gtk_blist_menu_move_to_cb(GtkWidget *w, PurpleBlistNode *node)
342 {
343 PurpleGroup *group = g_object_get_data(G_OBJECT(w), "groupnode");
344 purple_blist_add_contact((PurpleContact *)node, group, NULL);
345
346 }
347
gtk_blist_menu_autojoin_cb(GtkWidget * w,PurpleChat * chat)348 static void gtk_blist_menu_autojoin_cb(GtkWidget *w, PurpleChat *chat)
349 {
350 purple_blist_node_set_bool((PurpleBlistNode*)chat, "gtk-autojoin",
351 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)));
352 }
353
gtk_blist_menu_persistent_cb(GtkWidget * w,PurpleChat * chat)354 static void gtk_blist_menu_persistent_cb(GtkWidget *w, PurpleChat *chat)
355 {
356 purple_blist_node_set_bool((PurpleBlistNode*)chat, "gtk-persistent",
357 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)));
358 }
359
360 static PurpleConversation *
find_conversation_with_buddy(PurpleBuddy * buddy)361 find_conversation_with_buddy(PurpleBuddy *buddy)
362 {
363 PidginBlistNode *ui = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy));
364 if (ui)
365 return ui->conv.conv;
366 return purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
367 purple_buddy_get_name(buddy),
368 purple_buddy_get_account(buddy));
369 }
370
gtk_blist_join_chat(PurpleChat * chat)371 static void gtk_blist_join_chat(PurpleChat *chat)
372 {
373 PurpleAccount *account;
374 PurpleConversation *conv;
375 PurplePluginProtocolInfo *prpl_info;
376 GHashTable *components;
377 const char *name;
378 char *chat_name;
379
380 account = purple_chat_get_account(chat);
381 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(account)));
382
383 components = purple_chat_get_components(chat);
384
385 if (prpl_info && prpl_info->get_chat_name)
386 chat_name = prpl_info->get_chat_name(components);
387 else
388 chat_name = NULL;
389
390 if (chat_name)
391 name = chat_name;
392 else
393 name = purple_chat_get_name(chat);
394
395 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, name,
396 account);
397
398 if (conv != NULL) {
399 pidgin_conv_attach_to_conversation(conv);
400 purple_conversation_present(conv);
401 }
402
403 serv_join_chat(purple_account_get_connection(account), components);
404 g_free(chat_name);
405 }
406
gtk_blist_menu_join_cb(GtkWidget * w,PurpleChat * chat)407 static void gtk_blist_menu_join_cb(GtkWidget *w, PurpleChat *chat)
408 {
409 gtk_blist_join_chat(chat);
410 }
411
gtk_blist_renderer_editing_cancelled_cb(GtkCellRenderer * renderer,PurpleBuddyList * list)412 static void gtk_blist_renderer_editing_cancelled_cb(GtkCellRenderer *renderer, PurpleBuddyList *list)
413 {
414 editing_blist = FALSE;
415 g_object_set(G_OBJECT(renderer), "editable", FALSE, NULL);
416 pidgin_blist_refresh(list);
417 }
418
gtk_blist_renderer_editing_started_cb(GtkCellRenderer * renderer,GtkCellEditable * editable,gchar * path_str,gpointer user_data)419 static void gtk_blist_renderer_editing_started_cb(GtkCellRenderer *renderer,
420 GtkCellEditable *editable,
421 gchar *path_str,
422 gpointer user_data)
423 {
424 GtkTreeIter iter;
425 GtkTreePath *path = NULL;
426 PurpleBlistNode *node;
427 const char *text = NULL;
428
429 path = gtk_tree_path_new_from_string (path_str);
430 gtk_tree_model_get_iter (GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
431 gtk_tree_path_free (path);
432 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
433
434 switch (purple_blist_node_get_type(node)) {
435 case PURPLE_BLIST_CONTACT_NODE:
436 text = purple_contact_get_alias(PURPLE_CONTACT(node));
437 break;
438 case PURPLE_BLIST_BUDDY_NODE:
439 text = purple_buddy_get_alias(PURPLE_BUDDY(node));
440 break;
441 case PURPLE_BLIST_GROUP_NODE:
442 text = purple_group_get_name(PURPLE_GROUP(node));
443 break;
444 case PURPLE_BLIST_CHAT_NODE:
445 text = purple_chat_get_name(PURPLE_CHAT(node));
446 break;
447 default:
448 g_return_if_reached();
449 }
450
451 if (GTK_IS_ENTRY (editable)) {
452 GtkEntry *entry = GTK_ENTRY (editable);
453 gtk_entry_set_text(entry, text);
454 }
455 editing_blist = TRUE;
456 }
457
458 static void
gtk_blist_do_personize(GList * merges)459 gtk_blist_do_personize(GList *merges)
460 {
461 PurpleBlistNode *contact = NULL;
462 int max = 0;
463 GList *tmp;
464
465 /* First, we find the contact to merge the rest of the buddies into.
466 * This will be the contact with the most buddies in it; ties are broken
467 * by which contact is higher in the list
468 */
469 for (tmp = merges; tmp; tmp = tmp->next) {
470 PurpleBlistNode *node = tmp->data;
471 PurpleBlistNode *b;
472 PurpleBlistNodeType type;
473 int i = 0;
474
475 type = purple_blist_node_get_type(node);
476
477 if (type == PURPLE_BLIST_BUDDY_NODE) {
478 node = purple_blist_node_get_parent(node);
479 type = purple_blist_node_get_type(node);
480 }
481
482 if (type != PURPLE_BLIST_CONTACT_NODE)
483 continue;
484
485 for (b = purple_blist_node_get_first_child(node);
486 b;
487 b = purple_blist_node_get_sibling_next(b))
488 {
489 i++;
490 }
491
492 if (i > max) {
493 contact = node;
494 max = i;
495 }
496 }
497
498 if (contact == NULL)
499 return;
500
501 /* Merge all those buddies into this contact */
502 for (tmp = merges; tmp; tmp = tmp->next) {
503 PurpleBlistNode *node = tmp->data;
504 if (purple_blist_node_get_type(node) == PURPLE_BLIST_BUDDY_NODE)
505 node = purple_blist_node_get_parent(node);
506
507 if (node == contact)
508 continue;
509
510 purple_blist_merge_contact((PurpleContact *)node, contact);
511 }
512
513 /* And show the expanded contact, so the people know what's going on */
514 pidgin_blist_expand_contact_cb(NULL, contact);
515 g_list_free(merges);
516 }
517
518 static void
gtk_blist_auto_personize(PurpleBlistNode * group,const char * alias)519 gtk_blist_auto_personize(PurpleBlistNode *group, const char *alias)
520 {
521 PurpleBlistNode *contact;
522 PurpleBlistNode *buddy;
523 GList *merges = NULL;
524 int i = 0;
525 char *a = g_utf8_casefold(alias, -1);
526
527 for (contact = purple_blist_node_get_first_child(group);
528 contact != NULL;
529 contact = purple_blist_node_get_sibling_next(contact)) {
530 char *node_alias;
531 if (purple_blist_node_get_type(contact) != PURPLE_BLIST_CONTACT_NODE)
532 continue;
533
534 node_alias = g_utf8_casefold(purple_contact_get_alias((PurpleContact *)contact), -1);
535 if (node_alias && !g_utf8_collate(node_alias, a)) {
536 merges = g_list_append(merges, contact);
537 i++;
538 g_free(node_alias);
539 continue;
540 }
541 g_free(node_alias);
542
543 for (buddy = purple_blist_node_get_first_child(contact);
544 buddy;
545 buddy = purple_blist_node_get_sibling_next(buddy))
546 {
547 if (purple_blist_node_get_type(buddy) != PURPLE_BLIST_BUDDY_NODE)
548 continue;
549
550 node_alias = g_utf8_casefold(purple_buddy_get_alias(PURPLE_BUDDY(buddy)), -1);
551 if (node_alias && !g_utf8_collate(node_alias, a)) {
552 merges = g_list_append(merges, buddy);
553 i++;
554 g_free(node_alias);
555 break;
556 }
557 g_free(node_alias);
558 }
559 }
560 g_free(a);
561
562 if (i > 1)
563 {
564 char *msg = g_strdup_printf(ngettext("You have %d contact named %s. Would you like to merge them?", "You currently have %d contacts named %s. Would you like to merge them?", i), i, alias);
565 purple_request_action(NULL, NULL, msg, _("Merging these contacts will cause them to share a single entry on the buddy list and use a single conversation window. "
566 "You can separate them again by choosing 'Expand' from the contact's context menu"), 0, NULL, NULL, NULL,
567 merges, 2, _("_Yes"), PURPLE_CALLBACK(gtk_blist_do_personize), _("_No"), PURPLE_CALLBACK(g_list_free));
568 g_free(msg);
569 } else
570 g_list_free(merges);
571 }
572
gtk_blist_renderer_edited_cb(GtkCellRendererText * text_rend,char * arg1,char * arg2,PurpleBuddyList * list)573 static void gtk_blist_renderer_edited_cb(GtkCellRendererText *text_rend, char *arg1,
574 char *arg2, PurpleBuddyList *list)
575 {
576 GtkTreeIter iter;
577 GtkTreePath *path;
578 PurpleBlistNode *node;
579 PurpleGroup *dest;
580
581 editing_blist = FALSE;
582 path = gtk_tree_path_new_from_string (arg1);
583 gtk_tree_model_get_iter (GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
584 gtk_tree_path_free (path);
585 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
586 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(gtkblist->treeview), TRUE);
587 g_object_set(G_OBJECT(gtkblist->text_rend), "editable", FALSE, NULL);
588
589 switch (purple_blist_node_get_type(node))
590 {
591 case PURPLE_BLIST_CONTACT_NODE:
592 {
593 PurpleContact *contact = PURPLE_CONTACT(node);
594 struct _pidgin_blist_node *gtknode =
595 (struct _pidgin_blist_node *)purple_blist_node_get_ui_data(node);
596
597 /*
598 * XXX Using purple_contact_get_alias here breaks because we
599 * specifically want to check the contact alias only (i.e. not
600 * the priority buddy, which purple_contact_get_alias does).
601 * Adding yet another get_alias is evil, so figure this out
602 * later :-P
603 */
604 if (contact->alias || gtknode->contact_expanded) {
605 purple_blist_alias_contact(contact, arg2);
606 gtk_blist_auto_personize(purple_blist_node_get_parent(node), arg2);
607 } else {
608 PurpleBuddy *buddy = purple_contact_get_priority_buddy(contact);
609 purple_blist_alias_buddy(buddy, arg2);
610 serv_alias_buddy(buddy);
611 gtk_blist_auto_personize(purple_blist_node_get_parent(node), arg2);
612 }
613 }
614 break;
615
616 case PURPLE_BLIST_BUDDY_NODE:
617 {
618 PurpleGroup *group = purple_buddy_get_group(PURPLE_BUDDY(node));
619
620 purple_blist_alias_buddy(PURPLE_BUDDY(node), arg2);
621 serv_alias_buddy(PURPLE_BUDDY(node));
622 gtk_blist_auto_personize(PURPLE_BLIST_NODE(group), arg2);
623 }
624 break;
625 case PURPLE_BLIST_GROUP_NODE:
626 dest = purple_find_group(arg2);
627 if (dest != NULL && purple_utf8_strcasecmp(arg2, purple_group_get_name(PURPLE_GROUP(node)))) {
628 pidgin_dialogs_merge_groups(PURPLE_GROUP(node), arg2);
629 } else {
630 purple_blist_rename_group(PURPLE_GROUP(node), arg2);
631 }
632 break;
633 case PURPLE_BLIST_CHAT_NODE:
634 purple_blist_alias_chat(PURPLE_CHAT(node), arg2);
635 break;
636 default:
637 break;
638 }
639 pidgin_blist_refresh(list);
640 }
641
642 static void
chat_components_edit_ok(PurpleChat * chat,PurpleRequestFields * allfields)643 chat_components_edit_ok(PurpleChat *chat, PurpleRequestFields *allfields)
644 {
645 GList *groups, *fields;
646
647 for (groups = purple_request_fields_get_groups(allfields); groups; groups = groups->next) {
648 fields = purple_request_field_group_get_fields(groups->data);
649 for (; fields; fields = fields->next) {
650 PurpleRequestField *field = fields->data;
651 const char *id;
652 char *val;
653
654 id = purple_request_field_get_id(field);
655 if (purple_request_field_get_type(field) == PURPLE_REQUEST_FIELD_INTEGER)
656 val = g_strdup_printf("%d", purple_request_field_int_get_value(field));
657 else
658 val = g_strdup(purple_request_field_string_get_value(field));
659
660 if (!val) {
661 g_hash_table_remove(purple_chat_get_components(chat), id);
662 } else {
663 g_hash_table_replace(purple_chat_get_components(chat), g_strdup(id), val); /* val should not be free'd */
664 }
665 }
666 }
667 }
668
chat_components_edit(GtkWidget * w,PurpleBlistNode * node)669 static void chat_components_edit(GtkWidget *w, PurpleBlistNode *node)
670 {
671 PurpleRequestFields *fields = purple_request_fields_new();
672 PurpleRequestFieldGroup *group = purple_request_field_group_new(NULL);
673 PurpleRequestField *field;
674 GList *parts, *iter;
675 struct proto_chat_entry *pce;
676 PurpleConnection *gc;
677 PurpleChat *chat = (PurpleChat*)node;
678
679 purple_request_fields_add_group(fields, group);
680
681 gc = purple_account_get_connection(purple_chat_get_account(chat));
682 parts = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc))->chat_info(gc);
683
684 for (iter = parts; iter; iter = iter->next) {
685 pce = iter->data;
686 if (pce->is_int) {
687 int val;
688 const char *str = g_hash_table_lookup(purple_chat_get_components(chat), pce->identifier);
689 if (!str || sscanf(str, "%d", &val) != 1)
690 val = pce->min;
691 field = purple_request_field_int_new(pce->identifier, pce->label, val);
692 } else {
693 field = purple_request_field_string_new(pce->identifier, pce->label,
694 g_hash_table_lookup(purple_chat_get_components(chat), pce->identifier), FALSE);
695 if (pce->secret)
696 purple_request_field_string_set_masked(field, TRUE);
697 }
698
699 if (pce->required)
700 purple_request_field_set_required(field, TRUE);
701
702 purple_request_field_group_add_field(group, field);
703 g_free(pce);
704 }
705
706 g_list_free(parts);
707
708 purple_request_fields(NULL, _("Edit Chat"), NULL, _("Please update the necessary fields."),
709 fields, _("Save"), G_CALLBACK(chat_components_edit_ok), _("Cancel"), NULL,
710 NULL, NULL, NULL,
711 chat);
712 }
713
gtk_blist_menu_alias_cb(GtkWidget * w,PurpleBlistNode * node)714 static void gtk_blist_menu_alias_cb(GtkWidget *w, PurpleBlistNode *node)
715 {
716 GtkTreeIter iter;
717 GtkTreePath *path;
718
719 if (!(get_iter_from_node(node, &iter))) {
720 /* This is either a bug, or the buddy is in a collapsed contact */
721 node = purple_blist_node_get_parent(node);
722 if (!get_iter_from_node(node, &iter))
723 /* Now it's definitely a bug */
724 return;
725 }
726
727 pidgin_blist_tooltip_destroy();
728
729 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
730 g_object_set(G_OBJECT(gtkblist->text_rend), "editable", TRUE, NULL);
731 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(gtkblist->treeview), FALSE);
732 gtk_widget_grab_focus(gtkblist->treeview);
733 gtk_tree_view_set_cursor_on_cell(GTK_TREE_VIEW(gtkblist->treeview), path,
734 gtkblist->text_column, gtkblist->text_rend, TRUE);
735 gtk_tree_path_free(path);
736 }
737
gtk_blist_menu_bp_cb(GtkWidget * w,PurpleBuddy * b)738 static void gtk_blist_menu_bp_cb(GtkWidget *w, PurpleBuddy *b)
739 {
740 pidgin_pounce_editor_show(purple_buddy_get_account(b),
741 purple_buddy_get_name(b), NULL);
742 }
743
gtk_blist_menu_showlog_cb(GtkWidget * w,PurpleBlistNode * node)744 static void gtk_blist_menu_showlog_cb(GtkWidget *w, PurpleBlistNode *node)
745 {
746 PurpleLogType type;
747 PurpleAccount *account;
748 char *name = NULL;
749
750 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
751
752 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
753 PurpleBuddy *b = (PurpleBuddy*) node;
754 type = PURPLE_LOG_IM;
755 name = g_strdup(purple_buddy_get_name(b));
756 account = purple_buddy_get_account(b);
757 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
758 PurpleChat *c = PURPLE_CHAT(node);
759 PurplePluginProtocolInfo *prpl_info = NULL;
760 type = PURPLE_LOG_CHAT;
761 account = purple_chat_get_account(c);
762 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(account)));
763 if (prpl_info && prpl_info->get_chat_name) {
764 name = prpl_info->get_chat_name(purple_chat_get_components(c));
765 }
766 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
767 pidgin_log_show_contact(PURPLE_CONTACT(node));
768 pidgin_clear_cursor(gtkblist->window);
769 return;
770 } else {
771 pidgin_clear_cursor(gtkblist->window);
772
773 /* This callback should not have been registered for a node
774 * that doesn't match the type of one of the blocks above. */
775 g_return_if_reached();
776 }
777
778 if (name && account) {
779 pidgin_log_show(type, name, account);
780 pidgin_clear_cursor(gtkblist->window);
781 }
782
783 g_free(name);
784 }
785
gtk_blist_menu_showoffline_cb(GtkWidget * w,PurpleBlistNode * node)786 static void gtk_blist_menu_showoffline_cb(GtkWidget *w, PurpleBlistNode *node)
787 {
788 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
789 {
790 purple_blist_node_set_bool(node, "show_offline",
791 !purple_blist_node_get_bool(node, "show_offline"));
792 pidgin_blist_update(purple_get_blist(), node);
793 }
794 else if (PURPLE_BLIST_NODE_IS_CONTACT(node))
795 {
796 PurpleBlistNode *bnode;
797 gboolean setting = !purple_blist_node_get_bool(node, "show_offline");
798
799 purple_blist_node_set_bool(node, "show_offline", setting);
800 for (bnode = purple_blist_node_get_first_child(node);
801 bnode != NULL;
802 bnode = purple_blist_node_get_sibling_next(bnode))
803 {
804 purple_blist_node_set_bool(bnode, "show_offline", setting);
805 pidgin_blist_update(purple_get_blist(), bnode);
806 }
807 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
808 PurpleBlistNode *cnode, *bnode;
809 gboolean setting = !purple_blist_node_get_bool(node, "show_offline");
810
811 purple_blist_node_set_bool(node, "show_offline", setting);
812 for (cnode = purple_blist_node_get_first_child(node);
813 cnode != NULL;
814 cnode = purple_blist_node_get_sibling_next(cnode))
815 {
816 purple_blist_node_set_bool(cnode, "show_offline", setting);
817 for (bnode = purple_blist_node_get_first_child(cnode);
818 bnode != NULL;
819 bnode = purple_blist_node_get_sibling_next(bnode))
820 {
821 purple_blist_node_set_bool(bnode, "show_offline", setting);
822 pidgin_blist_update(purple_get_blist(), bnode);
823 }
824 }
825 }
826 }
827
gtk_blist_show_systemlog_cb(void)828 static void gtk_blist_show_systemlog_cb(void)
829 {
830 pidgin_syslog_show();
831 }
832
gtk_blist_show_onlinehelp_cb(void)833 static void gtk_blist_show_onlinehelp_cb(void)
834 {
835 purple_notify_uri(NULL, PURPLE_WEBSITE "documentation");
836 }
837
838 static void
gtk_blist_donate_cb(void)839 gtk_blist_donate_cb(void) {
840 purple_notify_uri(NULL, "https://imfreedom.org/donate/");
841 }
842
843 static void
do_join_chat(PidginChatData * data)844 do_join_chat(PidginChatData *data)
845 {
846 if (data)
847 {
848 GHashTable *components =
849 g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
850 GList *tmp;
851 PurpleChat *chat;
852
853 for (tmp = data->entries; tmp != NULL; tmp = tmp->next)
854 {
855 if (g_object_get_data(tmp->data, "is_spin"))
856 {
857 g_hash_table_replace(components,
858 g_strdup(g_object_get_data(tmp->data, "identifier")),
859 g_strdup_printf("%d",
860 gtk_spin_button_get_value_as_int(tmp->data)));
861 }
862 else
863 {
864 g_hash_table_replace(components,
865 g_strdup(g_object_get_data(tmp->data, "identifier")),
866 g_strdup(gtk_entry_get_text(tmp->data)));
867 }
868 }
869
870 chat = purple_chat_new(data->rq_data.account, NULL, components);
871 gtk_blist_join_chat(chat);
872 purple_blist_remove_chat(chat);
873 }
874 }
875
876 static void
do_joinchat(GtkWidget * dialog,int id,PidginChatData * info)877 do_joinchat(GtkWidget *dialog, int id, PidginChatData *info)
878 {
879 switch(id)
880 {
881 case GTK_RESPONSE_OK:
882 do_join_chat(info);
883 break;
884
885 case 1:
886 pidgin_roomlist_dialog_show_with_account(info->rq_data.account);
887 return;
888
889 break;
890 }
891
892 gtk_widget_destroy(GTK_WIDGET(dialog));
893 g_list_free(info->entries);
894 g_free(info);
895 }
896
897 /*
898 * Check the values of all the text entry boxes. If any required input
899 * strings are empty then don't allow the user to click on "OK."
900 */
901 static void
set_sensitive_if_input_cb(GtkWidget * entry,gpointer user_data)902 set_sensitive_if_input_cb(GtkWidget *entry, gpointer user_data)
903 {
904 PurplePluginProtocolInfo *prpl_info;
905 PurpleConnection *gc;
906 PidginChatData *data;
907 GList *tmp;
908 const char *text;
909 gboolean required;
910 gboolean sensitive = TRUE;
911
912 data = user_data;
913
914 for (tmp = data->entries; tmp != NULL; tmp = tmp->next)
915 {
916 if (!g_object_get_data(tmp->data, "is_spin"))
917 {
918 required = GPOINTER_TO_INT(g_object_get_data(tmp->data, "required"));
919 text = gtk_entry_get_text(tmp->data);
920 if (required && (*text == '\0'))
921 sensitive = FALSE;
922 }
923 }
924
925 gtk_dialog_set_response_sensitive(GTK_DIALOG(data->rq_data.window), GTK_RESPONSE_OK, sensitive);
926
927 gc = purple_account_get_connection(data->rq_data.account);
928 prpl_info = (gc != NULL) ? PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl) : NULL;
929 sensitive = (prpl_info != NULL && prpl_info->roomlist_get_list != NULL);
930
931 gtk_dialog_set_response_sensitive(GTK_DIALOG(data->rq_data.window), 1, sensitive);
932 }
933
934 static void
pidgin_blist_update_privacy_cb(PurpleBuddy * buddy)935 pidgin_blist_update_privacy_cb(PurpleBuddy *buddy)
936 {
937 struct _pidgin_blist_node *ui_data = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(buddy));
938 if (ui_data == NULL || ui_data->row == NULL)
939 return;
940 pidgin_blist_update_buddy(purple_get_blist(), PURPLE_BLIST_NODE(buddy), TRUE);
941 }
942
943 static gboolean
add_buddy_account_filter_func(PurpleAccount * account)944 add_buddy_account_filter_func(PurpleAccount *account)
945 {
946 PurpleConnection *gc = purple_account_get_connection(account);
947 PurplePluginProtocolInfo *prpl_info = NULL;
948
949 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
950
951 return (prpl_info->add_buddy != NULL);
952 }
953
954 static gboolean
chat_account_filter_func(PurpleAccount * account)955 chat_account_filter_func(PurpleAccount *account)
956 {
957 PurpleConnection *gc = purple_account_get_connection(account);
958 PurplePluginProtocolInfo *prpl_info = NULL;
959
960 if (gc == NULL)
961 return FALSE;
962
963 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
964
965 return (prpl_info->chat_info != NULL);
966 }
967
968 gboolean
pidgin_blist_joinchat_is_showable()969 pidgin_blist_joinchat_is_showable()
970 {
971 GList *c;
972 PurpleConnection *gc;
973
974 for (c = purple_connections_get_all(); c != NULL; c = c->next) {
975 gc = c->data;
976
977 if (chat_account_filter_func(purple_connection_get_account(gc)))
978 return TRUE;
979 }
980
981 return FALSE;
982 }
983
984 static GtkWidget *
make_blist_request_dialog(PidginBlistRequestData * data,PurpleAccount * account,const char * title,const char * window_role,const char * label_text,GCallback callback_func,PurpleFilterAccountFunc filter_func,GCallback response_cb)985 make_blist_request_dialog(PidginBlistRequestData *data, PurpleAccount *account,
986 const char *title, const char *window_role, const char *label_text,
987 GCallback callback_func, PurpleFilterAccountFunc filter_func,
988 GCallback response_cb)
989 {
990 GtkWidget *label;
991 GtkWidget *img;
992 GtkWidget *hbox;
993 GtkWidget *vbox;
994 GtkWindow *blist_window;
995 PidginBuddyList *gtkblist;
996
997 data->account = account;
998
999 img = gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION,
1000 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
1001
1002 gtkblist = PIDGIN_BLIST(purple_get_blist());
1003 blist_window = gtkblist ? GTK_WINDOW(gtkblist->window) : NULL;
1004
1005 data->window = gtk_dialog_new_with_buttons(title,
1006 blist_window, GTK_DIALOG_NO_SEPARATOR,
1007 NULL);
1008
1009 gtk_window_set_transient_for(GTK_WINDOW(data->window), blist_window);
1010 gtk_dialog_set_default_response(GTK_DIALOG(data->window), GTK_RESPONSE_OK);
1011 gtk_container_set_border_width(GTK_CONTAINER(data->window), PIDGIN_HIG_BOX_SPACE);
1012 gtk_window_set_resizable(GTK_WINDOW(data->window), FALSE);
1013 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(data->window)->vbox), PIDGIN_HIG_BORDER);
1014 gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(data->window)->vbox), PIDGIN_HIG_BOX_SPACE);
1015 gtk_window_set_role(GTK_WINDOW(data->window), window_role);
1016
1017 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
1018 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(data->window)->vbox), hbox);
1019 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
1020 gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
1021
1022 vbox = gtk_vbox_new(FALSE, 5);
1023 gtk_container_add(GTK_CONTAINER(hbox), vbox);
1024
1025 label = gtk_label_new(label_text);
1026
1027 gtk_widget_set_size_request(label, 400, -1);
1028 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1029 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
1030 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1031
1032 data->sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1033
1034 data->account_menu = pidgin_account_option_menu_new(account, FALSE,
1035 callback_func, filter_func, data);
1036 pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("A_ccount"), data->sg, data->account_menu, TRUE, NULL);
1037
1038 data->vbox = GTK_BOX(gtk_vbox_new(FALSE, 5));
1039 gtk_container_set_border_width(GTK_CONTAINER(data->vbox), 0);
1040 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(data->vbox), FALSE, FALSE, 0);
1041
1042 g_signal_connect(G_OBJECT(data->window), "response", response_cb, data);
1043
1044 g_object_unref(data->sg);
1045
1046 return vbox;
1047 }
1048
1049 static void
rebuild_chat_entries(PidginChatData * data,const char * default_chat_name)1050 rebuild_chat_entries(PidginChatData *data, const char *default_chat_name)
1051 {
1052 PurpleConnection *gc;
1053 GList *list = NULL, *tmp;
1054 GHashTable *defaults = NULL;
1055 struct proto_chat_entry *pce;
1056 gboolean focus = TRUE;
1057
1058 g_return_if_fail(data->rq_data.account != NULL);
1059
1060 gc = purple_account_get_connection(data->rq_data.account);
1061
1062 gtk_container_foreach(GTK_CONTAINER(data->rq_data.vbox), (GtkCallback)gtk_widget_destroy, NULL);
1063
1064 g_list_free(data->entries);
1065 data->entries = NULL;
1066
1067 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL)
1068 list = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info(gc);
1069
1070 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults != NULL)
1071 defaults = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults(gc, default_chat_name);
1072
1073 for (tmp = list; tmp; tmp = tmp->next)
1074 {
1075 GtkWidget *input;
1076
1077 pce = tmp->data;
1078
1079 if (pce->is_int)
1080 {
1081 GtkObject *adjust;
1082 adjust = gtk_adjustment_new(pce->min, pce->min, pce->max,
1083 1, 10, 10);
1084 input = gtk_spin_button_new(GTK_ADJUSTMENT(adjust), 1, 0);
1085 gtk_widget_set_size_request(input, 50, -1);
1086 pidgin_add_widget_to_vbox(GTK_BOX(data->rq_data.vbox), pce->label, data->rq_data.sg, input, FALSE, NULL);
1087 }
1088 else
1089 {
1090 char *value;
1091 input = gtk_entry_new();
1092 gtk_entry_set_activates_default(GTK_ENTRY(input), TRUE);
1093 value = g_hash_table_lookup(defaults, pce->identifier);
1094 if (value != NULL)
1095 gtk_entry_set_text(GTK_ENTRY(input), value);
1096 if (pce->secret)
1097 {
1098 gtk_entry_set_visibility(GTK_ENTRY(input), FALSE);
1099 #if !GTK_CHECK_VERSION(2,16,0)
1100 if (gtk_entry_get_invisible_char(GTK_ENTRY(input)) == '*')
1101 gtk_entry_set_invisible_char(GTK_ENTRY(input), PIDGIN_INVISIBLE_CHAR);
1102 #endif /* Less than GTK+ 2.16 */
1103 }
1104 pidgin_add_widget_to_vbox(data->rq_data.vbox, pce->label, data->rq_data.sg, input, TRUE, NULL);
1105 g_signal_connect(G_OBJECT(input), "changed",
1106 G_CALLBACK(set_sensitive_if_input_cb), data);
1107 }
1108
1109 /* Do the following for any type of input widget */
1110 if (focus)
1111 {
1112 gtk_widget_grab_focus(input);
1113 focus = FALSE;
1114 }
1115 g_object_set_data(G_OBJECT(input), "identifier", (gpointer)pce->identifier);
1116 g_object_set_data(G_OBJECT(input), "is_spin", GINT_TO_POINTER(pce->is_int));
1117 g_object_set_data(G_OBJECT(input), "required", GINT_TO_POINTER(pce->required));
1118 data->entries = g_list_append(data->entries, input);
1119
1120 g_free(pce);
1121 }
1122
1123 g_list_free(list);
1124 g_hash_table_destroy(defaults);
1125
1126 /* Set whether the "OK" button should be clickable initially */
1127 set_sensitive_if_input_cb(NULL, data);
1128
1129 gtk_widget_show_all(GTK_WIDGET(data->rq_data.vbox));
1130 }
1131
1132 static void
chat_select_account_cb(GObject * w,PurpleAccount * account,PidginChatData * data)1133 chat_select_account_cb(GObject *w, PurpleAccount *account,
1134 PidginChatData *data)
1135 {
1136 if (purple_strequal(purple_account_get_protocol_id(data->rq_data.account),
1137 purple_account_get_protocol_id(account)))
1138 {
1139 data->rq_data.account = account;
1140 }
1141 else
1142 {
1143 data->rq_data.account = account;
1144 rebuild_chat_entries(data, data->default_chat_name);
1145 }
1146 }
1147
1148 void
pidgin_blist_joinchat_show(void)1149 pidgin_blist_joinchat_show(void)
1150 {
1151 PidginChatData *data = NULL;
1152
1153 data = g_new0(PidginChatData, 1);
1154
1155 make_blist_request_dialog((PidginBlistRequestData *)data, NULL,
1156 _("Join a Chat"), "join_chat",
1157 _("Please enter the appropriate information about the chat "
1158 "you would like to join.\n"),
1159 G_CALLBACK(chat_select_account_cb),
1160 chat_account_filter_func, (GCallback)do_joinchat);
1161 gtk_dialog_add_buttons(GTK_DIALOG(data->rq_data.window),
1162 _("Room _List"), 1,
1163 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1164 PIDGIN_STOCK_CHAT, GTK_RESPONSE_OK, NULL);
1165 gtk_dialog_set_default_response(GTK_DIALOG(data->rq_data.window),
1166 GTK_RESPONSE_OK);
1167 data->default_chat_name = NULL;
1168 data->rq_data.account = pidgin_account_option_menu_get_selected(data->rq_data.account_menu);
1169
1170 rebuild_chat_entries(data, NULL);
1171
1172 gtk_widget_show_all(data->rq_data.window);
1173 }
1174
gtk_blist_row_expanded_cb(GtkTreeView * tv,GtkTreeIter * iter,GtkTreePath * path,gpointer user_data)1175 static void gtk_blist_row_expanded_cb(GtkTreeView *tv, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data)
1176 {
1177 PurpleBlistNode *node;
1178
1179 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), iter, NODE_COLUMN, &node, -1);
1180
1181 if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1182 char *title;
1183
1184 title = pidgin_get_group_title(node, TRUE);
1185
1186 gtk_tree_store_set(gtkblist->treemodel, iter,
1187 NAME_COLUMN, title,
1188 -1);
1189
1190 g_free(title);
1191
1192 purple_blist_node_set_bool(node, "collapsed", FALSE);
1193 pidgin_blist_tooltip_destroy();
1194 }
1195 }
1196
gtk_blist_row_collapsed_cb(GtkTreeView * tv,GtkTreeIter * iter,GtkTreePath * path,gpointer user_data)1197 static void gtk_blist_row_collapsed_cb(GtkTreeView *tv, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data)
1198 {
1199 PurpleBlistNode *node;
1200
1201 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), iter, NODE_COLUMN, &node, -1);
1202
1203 if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1204 char *title;
1205 struct _pidgin_blist_node *gtknode;
1206 PurpleBlistNode *cnode;
1207
1208 title = pidgin_get_group_title(node, FALSE);
1209
1210 gtk_tree_store_set(gtkblist->treemodel, iter,
1211 NAME_COLUMN, title,
1212 -1);
1213
1214 g_free(title);
1215
1216 purple_blist_node_set_bool(node, "collapsed", TRUE);
1217
1218 for(cnode = purple_blist_node_get_first_child(node); cnode; cnode = purple_blist_node_get_sibling_next(cnode)) {
1219 if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
1220 gtknode = purple_blist_node_get_ui_data(cnode);
1221 if (!gtknode->contact_expanded)
1222 continue;
1223 gtknode->contact_expanded = FALSE;
1224 pidgin_blist_update_contact(NULL, cnode);
1225 }
1226 }
1227 pidgin_blist_tooltip_destroy();
1228 } else if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1229 pidgin_blist_collapse_contact_cb(NULL, node);
1230 }
1231 }
1232
gtk_blist_row_activated_cb(GtkTreeView * tv,GtkTreePath * path,GtkTreeViewColumn * col,gpointer data)1233 static void gtk_blist_row_activated_cb(GtkTreeView *tv, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data) {
1234 PurpleBlistNode *node;
1235 GtkTreeIter iter;
1236
1237 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
1238 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1239
1240 if(PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1241 PurpleBuddy *buddy;
1242
1243 if(PURPLE_BLIST_NODE_IS_CONTACT(node))
1244 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
1245 else
1246 buddy = (PurpleBuddy*)node;
1247
1248 pidgin_dialogs_im_with_user(purple_buddy_get_account(buddy), purple_buddy_get_name(buddy));
1249 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
1250 gtk_blist_join_chat((PurpleChat *)node);
1251 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1252 /* if (gtk_tree_view_row_expanded(tv, path))
1253 gtk_tree_view_collapse_row(tv, path);
1254 else
1255 gtk_tree_view_expand_row(tv,path,FALSE);*/
1256 }
1257 }
1258
pidgin_blist_add_chat_cb(void)1259 static void pidgin_blist_add_chat_cb(void)
1260 {
1261 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
1262 GtkTreeIter iter;
1263 PurpleBlistNode *node;
1264
1265 if(gtk_tree_selection_get_selected(sel, NULL, &iter)){
1266 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1267 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
1268 purple_blist_request_add_chat(NULL, purple_buddy_get_group(PURPLE_BUDDY(node)), NULL, NULL);
1269 if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_CHAT(node))
1270 purple_blist_request_add_chat(NULL, purple_contact_get_group(PURPLE_CONTACT(node)), NULL, NULL);
1271 else if (PURPLE_BLIST_NODE_IS_GROUP(node))
1272 purple_blist_request_add_chat(NULL, (PurpleGroup*)node, NULL, NULL);
1273 }
1274 else {
1275 purple_blist_request_add_chat(NULL, NULL, NULL, NULL);
1276 }
1277 }
1278
pidgin_blist_add_buddy_cb(void)1279 static void pidgin_blist_add_buddy_cb(void)
1280 {
1281 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
1282 GtkTreeIter iter;
1283 PurpleBlistNode *node;
1284
1285 if(gtk_tree_selection_get_selected(sel, NULL, &iter)){
1286 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1287 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1288 PurpleGroup *group = purple_buddy_get_group(PURPLE_BUDDY(node));
1289 purple_blist_request_add_buddy(NULL, NULL, purple_group_get_name(group), NULL);
1290 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_CHAT(node)) {
1291 PurpleGroup *group = purple_contact_get_group(PURPLE_CONTACT(node));
1292 purple_blist_request_add_buddy(NULL, NULL, purple_group_get_name(group), NULL);
1293 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1294 purple_blist_request_add_buddy(NULL, NULL, purple_group_get_name(PURPLE_GROUP(node)), NULL);
1295 }
1296 }
1297 else {
1298 purple_blist_request_add_buddy(NULL, NULL, NULL, NULL);
1299 }
1300 }
1301
1302 static void
pidgin_blist_remove_cb(GtkWidget * w,PurpleBlistNode * node)1303 pidgin_blist_remove_cb (GtkWidget *w, PurpleBlistNode *node)
1304 {
1305 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1306 pidgin_dialogs_remove_buddy((PurpleBuddy*)node);
1307 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
1308 pidgin_dialogs_remove_chat((PurpleChat*)node);
1309 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1310 pidgin_dialogs_remove_group((PurpleGroup*)node);
1311 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1312 pidgin_dialogs_remove_contact((PurpleContact*)node);
1313 }
1314 }
1315
1316 struct _expand {
1317 GtkTreeView *treeview;
1318 GtkTreePath *path;
1319 PurpleBlistNode *node;
1320 };
1321
1322 static gboolean
scroll_to_expanded_cell(gpointer data)1323 scroll_to_expanded_cell(gpointer data)
1324 {
1325 struct _expand *ex = data;
1326 gtk_tree_view_scroll_to_cell(ex->treeview, ex->path, NULL, FALSE, 0, 0);
1327 pidgin_blist_update_contact(NULL, ex->node);
1328
1329 gtk_tree_path_free(ex->path);
1330 g_free(ex);
1331
1332 return FALSE;
1333 }
1334
1335 static void
pidgin_blist_expand_contact_cb(GtkWidget * w,PurpleBlistNode * node)1336 pidgin_blist_expand_contact_cb(GtkWidget *w, PurpleBlistNode *node)
1337 {
1338 struct _pidgin_blist_node *gtknode;
1339 GtkTreeIter iter, parent;
1340 PurpleBlistNode *bnode;
1341 GtkTreePath *path;
1342
1343 if(!PURPLE_BLIST_NODE_IS_CONTACT(node))
1344 return;
1345
1346 gtknode = purple_blist_node_get_ui_data(node);
1347
1348 gtknode->contact_expanded = TRUE;
1349
1350 for(bnode = purple_blist_node_get_first_child(node); bnode; bnode = purple_blist_node_get_sibling_next(bnode)) {
1351 pidgin_blist_update(NULL, bnode);
1352 }
1353
1354 /* This ensures that the bottom buddy is visible, i.e. not scrolled off the alignment */
1355 if (get_iter_from_node(node, &parent)) {
1356 struct _expand *ex = g_new0(struct _expand, 1);
1357
1358 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtkblist->treemodel), &iter, &parent,
1359 gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtkblist->treemodel), &parent) -1);
1360 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
1361
1362 /* Let the treeview draw so it knows where to scroll */
1363 ex->treeview = GTK_TREE_VIEW(gtkblist->treeview);
1364 ex->path = path;
1365 ex->node = purple_blist_node_get_first_child(node);
1366 g_idle_add(scroll_to_expanded_cell, ex);
1367 }
1368 }
1369
1370 static void
pidgin_blist_collapse_contact_cb(GtkWidget * w,PurpleBlistNode * node)1371 pidgin_blist_collapse_contact_cb(GtkWidget *w, PurpleBlistNode *node)
1372 {
1373 PurpleBlistNode *bnode;
1374 struct _pidgin_blist_node *gtknode;
1375
1376 if(!PURPLE_BLIST_NODE_IS_CONTACT(node))
1377 return;
1378
1379 gtknode = purple_blist_node_get_ui_data(node);
1380
1381 gtknode->contact_expanded = FALSE;
1382
1383 for(bnode = purple_blist_node_get_first_child(node); bnode; bnode = purple_blist_node_get_sibling_next(bnode)) {
1384 pidgin_blist_update(NULL, bnode);
1385 }
1386 }
1387
1388 static void
toggle_privacy(GtkWidget * widget,PurpleBlistNode * node)1389 toggle_privacy(GtkWidget *widget, PurpleBlistNode *node)
1390 {
1391 PurpleBuddy *buddy;
1392 PurpleAccount *account;
1393 gboolean permitted;
1394 const char *name;
1395
1396 if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
1397 return;
1398
1399 buddy = (PurpleBuddy *)node;
1400 account = purple_buddy_get_account(buddy);
1401 name = purple_buddy_get_name(buddy);
1402
1403 permitted = purple_privacy_check(account, name);
1404
1405 /* XXX: Perhaps ask whether to restore the previous lists where appropirate? */
1406
1407 if (permitted)
1408 purple_privacy_deny(account, name, FALSE, FALSE);
1409 else
1410 purple_privacy_allow(account, name, FALSE, FALSE);
1411
1412 pidgin_blist_update(purple_get_blist(), node);
1413 }
1414
pidgin_append_blist_node_privacy_menu(GtkWidget * menu,PurpleBlistNode * node)1415 void pidgin_append_blist_node_privacy_menu(GtkWidget *menu, PurpleBlistNode *node)
1416 {
1417 PurpleBuddy *buddy = (PurpleBuddy *)node;
1418 PurpleAccount *account;
1419 gboolean permitted;
1420
1421 account = purple_buddy_get_account(buddy);
1422 permitted = purple_privacy_check(account, purple_buddy_get_name(buddy));
1423
1424 pidgin_new_item_from_stock(menu, permitted ? _("_Block") : _("Un_block"),
1425 permitted ? PIDGIN_STOCK_TOOLBAR_BLOCK : PIDGIN_STOCK_TOOLBAR_UNBLOCK, G_CALLBACK(toggle_privacy),
1426 node, 0 ,0, NULL);
1427 }
1428
1429 void
pidgin_append_blist_node_proto_menu(GtkWidget * menu,PurpleConnection * gc,PurpleBlistNode * node)1430 pidgin_append_blist_node_proto_menu(GtkWidget *menu, PurpleConnection *gc,
1431 PurpleBlistNode *node)
1432 {
1433 GList *l, *ll;
1434 PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
1435
1436 if(!prpl_info || !prpl_info->blist_node_menu)
1437 return;
1438
1439 for(l = ll = prpl_info->blist_node_menu(node); l; l = l->next) {
1440 PurpleMenuAction *act = (PurpleMenuAction *) l->data;
1441 pidgin_append_menu_action(menu, act, node);
1442 }
1443 g_list_free(ll);
1444 }
1445
1446 void
pidgin_append_blist_node_extended_menu(GtkWidget * menu,PurpleBlistNode * node)1447 pidgin_append_blist_node_extended_menu(GtkWidget *menu, PurpleBlistNode *node)
1448 {
1449 GList *l, *ll;
1450
1451 for(l = ll = purple_blist_node_get_extended_menu(node); l; l = l->next) {
1452 PurpleMenuAction *act = (PurpleMenuAction *) l->data;
1453 pidgin_append_menu_action(menu, act, node);
1454 }
1455 g_list_free(ll);
1456 }
1457
1458
1459
1460 static void
pidgin_append_blist_node_move_to_menu(GtkWidget * menu,PurpleBlistNode * node)1461 pidgin_append_blist_node_move_to_menu(GtkWidget *menu, PurpleBlistNode *node)
1462 {
1463 GtkWidget *submenu;
1464 GtkWidget *menuitem;
1465 PurpleBlistNode *group;
1466
1467 menuitem = gtk_menu_item_new_with_label(_("Move to"));
1468 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1469 gtk_widget_show(menuitem);
1470
1471 submenu = gtk_menu_new();
1472 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
1473
1474 for (group = purple_blist_get_root(); group; group = purple_blist_node_get_sibling_next(group)) {
1475 if (!PURPLE_BLIST_NODE_IS_GROUP(group))
1476 continue;
1477 if (group == purple_blist_node_get_parent(node))
1478 continue;
1479 menuitem = pidgin_new_item_from_stock(submenu, purple_group_get_name((PurpleGroup *)group), NULL,
1480 G_CALLBACK(gtk_blist_menu_move_to_cb), node, 0, 0, NULL);
1481 g_object_set_data(G_OBJECT(menuitem), "groupnode", group);
1482 }
1483 gtk_widget_show_all(submenu);
1484 }
1485
1486 void
pidgin_blist_make_buddy_menu(GtkWidget * menu,PurpleBuddy * buddy,gboolean sub)1487 pidgin_blist_make_buddy_menu(GtkWidget *menu, PurpleBuddy *buddy, gboolean sub) {
1488 PurpleAccount *account = NULL;
1489 PurpleConnection *pc = NULL;
1490 PurplePluginProtocolInfo *prpl_info;
1491 PurpleContact *contact;
1492 PurpleBlistNode *node;
1493 gboolean contact_expanded = FALSE;
1494
1495 g_return_if_fail(menu);
1496 g_return_if_fail(buddy);
1497
1498 account = purple_buddy_get_account(buddy);
1499 pc = purple_account_get_connection(account);
1500 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(pc));
1501
1502 node = PURPLE_BLIST_NODE(buddy);
1503
1504 contact = purple_buddy_get_contact(buddy);
1505 if (contact) {
1506 PidginBlistNode *node = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(contact));
1507 contact_expanded = node->contact_expanded;
1508 }
1509
1510 if (prpl_info && prpl_info->get_info) {
1511 pidgin_new_item_from_stock(menu, _("Get _Info"), PIDGIN_STOCK_TOOLBAR_USER_INFO,
1512 G_CALLBACK(gtk_blist_menu_info_cb), buddy, 0, 0, NULL);
1513 }
1514 pidgin_new_item_from_stock(menu, _("I_M"), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW,
1515 G_CALLBACK(gtk_blist_menu_im_cb), buddy, 0, 0, NULL);
1516
1517 #ifdef USE_VV
1518 if (prpl_info && prpl_info->get_media_caps) {
1519 PurpleAccount *account = purple_buddy_get_account(buddy);
1520 const gchar *who = purple_buddy_get_name(buddy);
1521 PurpleMediaCaps caps = purple_prpl_get_media_caps(account, who);
1522 if (caps & PURPLE_MEDIA_CAPS_AUDIO) {
1523 pidgin_new_item_from_stock(menu, _("_Audio Call"),
1524 PIDGIN_STOCK_TOOLBAR_AUDIO_CALL,
1525 G_CALLBACK(gtk_blist_menu_audio_call_cb), buddy, 0, 0, NULL);
1526 }
1527 if (caps & PURPLE_MEDIA_CAPS_AUDIO_VIDEO) {
1528 pidgin_new_item_from_stock(menu, _("Audio/_Video Call"),
1529 PIDGIN_STOCK_TOOLBAR_VIDEO_CALL,
1530 G_CALLBACK(gtk_blist_menu_video_call_cb), buddy, 0, 0, NULL);
1531 } else if (caps & PURPLE_MEDIA_CAPS_VIDEO) {
1532 pidgin_new_item_from_stock(menu, _("_Video Call"),
1533 PIDGIN_STOCK_TOOLBAR_VIDEO_CALL,
1534 G_CALLBACK(gtk_blist_menu_video_call_cb), buddy, 0, 0, NULL);
1535 }
1536 }
1537
1538 #endif
1539
1540 if (prpl_info && prpl_info->send_file) {
1541 if (!prpl_info->can_receive_file ||
1542 prpl_info->can_receive_file(buddy->account->gc, buddy->name))
1543 {
1544 pidgin_new_item_from_stock(menu, _("_Send File..."),
1545 PIDGIN_STOCK_TOOLBAR_SEND_FILE,
1546 G_CALLBACK(gtk_blist_menu_send_file_cb),
1547 buddy, 0, 0, NULL);
1548 }
1549 }
1550
1551 pidgin_new_item_from_stock(menu, _("Add Buddy _Pounce..."), NULL,
1552 G_CALLBACK(gtk_blist_menu_bp_cb), buddy, 0, 0, NULL);
1553
1554 if (node->parent && node->parent->child->next &&
1555 !sub && !contact_expanded) {
1556 pidgin_new_item_from_stock(menu, _("View _Log"), NULL,
1557 G_CALLBACK(gtk_blist_menu_showlog_cb),
1558 contact, 0, 0, NULL);
1559 } else if (!sub) {
1560 pidgin_new_item_from_stock(menu, _("View _Log"), NULL,
1561 G_CALLBACK(gtk_blist_menu_showlog_cb), buddy, 0, 0, NULL);
1562 }
1563
1564 if (!PURPLE_BLIST_NODE_HAS_FLAG(node, PURPLE_BLIST_NODE_FLAG_NO_SAVE)) {
1565 gboolean show_offline = purple_blist_node_get_bool(node, "show_offline");
1566 pidgin_new_item_from_stock(menu, show_offline ? _("Hide When Offline") : _("Show When Offline"),
1567 NULL, G_CALLBACK(gtk_blist_menu_showoffline_cb), node, 0, 0, NULL);
1568 }
1569
1570 pidgin_append_blist_node_proto_menu(menu, buddy->account->gc, node);
1571 pidgin_append_blist_node_extended_menu(menu, node);
1572
1573 if (!contact_expanded && contact != NULL)
1574 pidgin_append_blist_node_move_to_menu(menu, (PurpleBlistNode *)contact);
1575
1576 if (node->parent && node->parent->child->next &&
1577 !sub && !contact_expanded) {
1578 pidgin_separator(menu);
1579 pidgin_append_blist_node_privacy_menu(menu, node);
1580 pidgin_new_item_from_stock(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1581 G_CALLBACK(gtk_blist_menu_alias_cb),
1582 contact, 0, 0, NULL);
1583 pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1584 G_CALLBACK(pidgin_blist_remove_cb),
1585 contact, 0, 0, NULL);
1586 } else if (!sub || contact_expanded) {
1587 pidgin_separator(menu);
1588 pidgin_append_blist_node_privacy_menu(menu, node);
1589 pidgin_new_item_from_stock(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1590 G_CALLBACK(gtk_blist_menu_alias_cb), buddy, 0, 0, NULL);
1591 pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1592 G_CALLBACK(pidgin_blist_remove_cb), buddy,
1593 0, 0, NULL);
1594 }
1595 }
1596
1597 static gboolean
gtk_blist_key_press_cb(GtkWidget * tv,GdkEventKey * event,gpointer data)1598 gtk_blist_key_press_cb(GtkWidget *tv, GdkEventKey *event, gpointer data)
1599 {
1600 PurpleBlistNode *node;
1601 GtkTreeIter iter, parent;
1602 GtkTreeSelection *sel;
1603 GtkTreePath *path;
1604
1605 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
1606 if(!gtk_tree_selection_get_selected(sel, NULL, &iter))
1607 return FALSE;
1608
1609 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1610
1611 if(event->state & GDK_CONTROL_MASK &&
1612 (event->keyval == 'o' || event->keyval == 'O')) {
1613 PurpleBuddy *buddy;
1614
1615 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1616 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
1617 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1618 buddy = (PurpleBuddy*)node;
1619 } else {
1620 return FALSE;
1621 }
1622 if(buddy)
1623 pidgin_retrieve_user_info(buddy->account->gc, buddy->name);
1624 } else {
1625 switch (event->keyval) {
1626 case GDK_F2:
1627 gtk_blist_menu_alias_cb(tv, node);
1628 break;
1629
1630 case GDK_Left:
1631 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
1632 if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv), path)) {
1633 /* Collapse the Group */
1634 gtk_tree_view_collapse_row(GTK_TREE_VIEW(tv), path);
1635 gtk_tree_path_free(path);
1636 return TRUE;
1637 } else {
1638 /* Select the Parent */
1639 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path)) {
1640 if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(gtkblist->treemodel), &parent, &iter)) {
1641 gtk_tree_path_free(path);
1642 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &parent);
1643 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv), path, NULL, FALSE);
1644 gtk_tree_path_free(path);
1645 return TRUE;
1646 }
1647 }
1648 }
1649 gtk_tree_path_free(path);
1650 break;
1651
1652 case GDK_Right:
1653 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
1654 if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv), path)) {
1655 /* Expand the Group */
1656 if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1657 pidgin_blist_expand_contact_cb(NULL, node);
1658 gtk_tree_path_free(path);
1659 return TRUE;
1660 } else if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1661 gtk_tree_view_expand_row(GTK_TREE_VIEW(tv), path, FALSE);
1662 gtk_tree_path_free(path);
1663 return TRUE;
1664 }
1665 } else {
1666 /* Select the First Child */
1667 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &parent, path)) {
1668 if (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtkblist->treemodel), &iter, &parent, 0)) {
1669 gtk_tree_path_free(path);
1670 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
1671 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv), path, NULL, FALSE);
1672 gtk_tree_path_free(path);
1673 return TRUE;
1674 }
1675 }
1676 }
1677 gtk_tree_path_free(path);
1678 break;
1679 }
1680 }
1681
1682 return FALSE;
1683 }
1684
1685 static void
set_node_custom_icon_cb(const gchar * filename,gpointer data)1686 set_node_custom_icon_cb(const gchar *filename, gpointer data)
1687 {
1688 if (filename) {
1689 PurpleBlistNode *node = (PurpleBlistNode*)data;
1690
1691 purple_buddy_icons_node_set_custom_icon_from_file(node,
1692 filename);
1693 }
1694 }
1695
1696 static void
set_node_custom_icon(GtkWidget * w,PurpleBlistNode * node)1697 set_node_custom_icon(GtkWidget *w, PurpleBlistNode *node)
1698 {
1699 /* This doesn't keep track of the returned dialog (so that successive
1700 * calls could be made to re-display that dialog). Do we want that? */
1701 GtkWidget *win = pidgin_buddy_icon_chooser_new(NULL, set_node_custom_icon_cb, node);
1702 gtk_widget_show_all(win);
1703 }
1704
1705 static void
remove_node_custom_icon(GtkWidget * w,PurpleBlistNode * node)1706 remove_node_custom_icon(GtkWidget *w, PurpleBlistNode *node)
1707 {
1708 purple_buddy_icons_node_set_custom_icon(node, NULL, 0);
1709 }
1710
1711 static void
add_buddy_icon_menu_items(GtkWidget * menu,PurpleBlistNode * node)1712 add_buddy_icon_menu_items(GtkWidget *menu, PurpleBlistNode *node)
1713 {
1714 GtkWidget *item;
1715
1716 pidgin_new_item_from_stock(menu, _("Set Custom Icon"), NULL,
1717 G_CALLBACK(set_node_custom_icon), node, 0,
1718 0, NULL);
1719
1720 item = pidgin_new_item_from_stock(menu, _("Remove Custom Icon"), NULL,
1721 G_CALLBACK(remove_node_custom_icon), node,
1722 0, 0, NULL);
1723 if (!purple_buddy_icons_node_has_custom_icon(node))
1724 gtk_widget_set_sensitive(item, FALSE);
1725 }
1726
1727 static GtkWidget *
create_group_menu(PurpleBlistNode * node,PurpleGroup * g)1728 create_group_menu (PurpleBlistNode *node, PurpleGroup *g)
1729 {
1730 GtkWidget *menu;
1731 GtkWidget *item;
1732
1733 menu = gtk_menu_new();
1734 item = pidgin_new_item_from_stock(menu, _("Add _Buddy..."), GTK_STOCK_ADD,
1735 G_CALLBACK(pidgin_blist_add_buddy_cb), node, 0, 0, NULL);
1736 gtk_widget_set_sensitive(item, purple_connections_get_all() != NULL);
1737 item = pidgin_new_item_from_stock(menu, _("Add C_hat..."), GTK_STOCK_ADD,
1738 G_CALLBACK(pidgin_blist_add_chat_cb), node, 0, 0, NULL);
1739 gtk_widget_set_sensitive(item, pidgin_blist_joinchat_is_showable());
1740 pidgin_new_item_from_stock(menu, _("_Delete Group"), GTK_STOCK_REMOVE,
1741 G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL);
1742 pidgin_new_item_from_stock(menu, _("_Rename"), NULL,
1743 G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
1744 if (!(purple_blist_node_get_flags(node) & PURPLE_BLIST_NODE_FLAG_NO_SAVE)) {
1745 gboolean show_offline = purple_blist_node_get_bool(node, "show_offline");
1746 pidgin_new_item_from_stock(menu, show_offline ? _("Hide When Offline") : _("Show When Offline"),
1747 NULL, G_CALLBACK(gtk_blist_menu_showoffline_cb), node, 0, 0, NULL);
1748 }
1749
1750 add_buddy_icon_menu_items(menu, node);
1751
1752 pidgin_append_blist_node_extended_menu(menu, node);
1753
1754 return menu;
1755 }
1756
1757 static GtkWidget *
create_chat_menu(PurpleBlistNode * node,PurpleChat * c)1758 create_chat_menu(PurpleBlistNode *node, PurpleChat *c)
1759 {
1760 GtkWidget *menu;
1761 gboolean autojoin, persistent;
1762
1763 menu = gtk_menu_new();
1764 autojoin = purple_blist_node_get_bool(node, "gtk-autojoin");
1765 persistent = purple_blist_node_get_bool(node, "gtk-persistent");
1766
1767 pidgin_new_item_from_stock(menu, _("_Join"), PIDGIN_STOCK_CHAT,
1768 G_CALLBACK(gtk_blist_menu_join_cb), node, 0, 0, NULL);
1769 pidgin_new_check_item(menu, _("Auto-Join"),
1770 G_CALLBACK(gtk_blist_menu_autojoin_cb), node, autojoin);
1771 pidgin_new_check_item(menu, _("Persistent"),
1772 G_CALLBACK(gtk_blist_menu_persistent_cb), node, persistent);
1773 pidgin_new_item_from_stock(menu, _("View _Log"), NULL,
1774 G_CALLBACK(gtk_blist_menu_showlog_cb), node, 0, 0, NULL);
1775
1776 pidgin_append_blist_node_proto_menu(menu, c->account->gc, node);
1777 pidgin_append_blist_node_extended_menu(menu, node);
1778
1779 pidgin_separator(menu);
1780
1781 pidgin_new_item_from_stock(menu, _("_Edit Settings..."), NULL,
1782 G_CALLBACK(chat_components_edit), node, 0, 0, NULL);
1783 pidgin_new_item_from_stock(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1784 G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
1785 pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1786 G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL);
1787
1788 add_buddy_icon_menu_items(menu, node);
1789
1790 return menu;
1791 }
1792
1793 static GtkWidget *
create_contact_menu(PurpleBlistNode * node)1794 create_contact_menu (PurpleBlistNode *node)
1795 {
1796 GtkWidget *menu;
1797
1798 menu = gtk_menu_new();
1799
1800 pidgin_new_item_from_stock(menu, _("View _Log"), NULL,
1801 G_CALLBACK(gtk_blist_menu_showlog_cb),
1802 node, 0, 0, NULL);
1803
1804 pidgin_separator(menu);
1805
1806 pidgin_new_item_from_stock(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS,
1807 G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
1808 pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
1809 G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL);
1810
1811 add_buddy_icon_menu_items(menu, node);
1812
1813 pidgin_separator(menu);
1814
1815 pidgin_new_item_from_stock(menu, _("_Collapse"), GTK_STOCK_ZOOM_OUT,
1816 G_CALLBACK(pidgin_blist_collapse_contact_cb),
1817 node, 0, 0, NULL);
1818
1819 pidgin_append_blist_node_extended_menu(menu, node);
1820 return menu;
1821 }
1822
1823 static GtkWidget *
create_buddy_menu(PurpleBlistNode * node,PurpleBuddy * b)1824 create_buddy_menu(PurpleBlistNode *node, PurpleBuddy *b)
1825 {
1826 struct _pidgin_blist_node *gtknode = (struct _pidgin_blist_node *)node->ui_data;
1827 GtkWidget *menu;
1828 GtkWidget *menuitem;
1829 gboolean show_offline = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies");
1830
1831 menu = gtk_menu_new();
1832 pidgin_blist_make_buddy_menu(menu, b, FALSE);
1833
1834 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1835 pidgin_separator(menu);
1836
1837 add_buddy_icon_menu_items(menu, node);
1838
1839 if(gtknode->contact_expanded) {
1840 pidgin_new_item_from_stock(menu, _("_Collapse"),
1841 GTK_STOCK_ZOOM_OUT,
1842 G_CALLBACK(pidgin_blist_collapse_contact_cb),
1843 node, 0, 0, NULL);
1844 } else {
1845 pidgin_new_item_from_stock(menu, _("_Expand"),
1846 GTK_STOCK_ZOOM_IN,
1847 G_CALLBACK(pidgin_blist_expand_contact_cb), node,
1848 0, 0, NULL);
1849 }
1850 if(node->child->next) {
1851 PurpleBlistNode *bnode;
1852
1853 for(bnode = node->child; bnode; bnode = bnode->next) {
1854 PurpleBuddy *buddy = (PurpleBuddy*)bnode;
1855 GdkPixbuf *buf;
1856 GtkWidget *submenu;
1857 GtkWidget *image;
1858
1859 if(buddy == b)
1860 continue;
1861 if(!buddy->account->gc)
1862 continue;
1863 if(!show_offline && !PURPLE_BUDDY_IS_ONLINE(buddy))
1864 continue;
1865
1866 menuitem = gtk_image_menu_item_new_with_label(buddy->name);
1867 buf = pidgin_create_prpl_icon(buddy->account,PIDGIN_PRPL_ICON_SMALL);
1868 image = gtk_image_new_from_pixbuf(buf);
1869 g_object_unref(G_OBJECT(buf));
1870 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem),
1871 image);
1872 gtk_widget_show(image);
1873 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1874 gtk_widget_show(menuitem);
1875
1876 submenu = gtk_menu_new();
1877 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
1878 gtk_widget_show(submenu);
1879
1880 pidgin_blist_make_buddy_menu(submenu, buddy, TRUE);
1881 }
1882 }
1883 }
1884 return menu;
1885 }
1886
1887 static gboolean
pidgin_blist_show_context_menu(PurpleBlistNode * node,GtkMenuPositionFunc func,GtkWidget * tv,guint button,guint32 time)1888 pidgin_blist_show_context_menu(PurpleBlistNode *node,
1889 GtkMenuPositionFunc func,
1890 GtkWidget *tv,
1891 guint button,
1892 guint32 time)
1893 {
1894 struct _pidgin_blist_node *gtknode;
1895 GtkWidget *menu = NULL;
1896 gboolean handled = FALSE;
1897
1898 gtknode = (struct _pidgin_blist_node *)node->ui_data;
1899
1900 /* Create a menu based on the thing we right-clicked on */
1901 if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
1902 PurpleGroup *g = (PurpleGroup *)node;
1903
1904 menu = create_group_menu(node, g);
1905 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
1906 PurpleChat *c = (PurpleChat *)node;
1907
1908 menu = create_chat_menu(node, c);
1909 } else if ((PURPLE_BLIST_NODE_IS_CONTACT(node)) && (gtknode->contact_expanded)) {
1910 menu = create_contact_menu(node);
1911 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1912 PurpleBuddy *b;
1913
1914 if (PURPLE_BLIST_NODE_IS_CONTACT(node))
1915 b = purple_contact_get_priority_buddy((PurpleContact*)node);
1916 else
1917 b = (PurpleBuddy *)node;
1918
1919 menu = create_buddy_menu(node, b);
1920 }
1921
1922 #ifdef _WIN32
1923 pidgin_blist_tooltip_destroy();
1924
1925 /* Unhook the tooltip-timeout since we don't want a tooltip
1926 * to appear and obscure the context menu we are about to show
1927 This is a workaround for GTK+ bug 107320. */
1928 if (gtkblist->timeout) {
1929 g_source_remove(gtkblist->timeout);
1930 gtkblist->timeout = 0;
1931 }
1932 #endif
1933
1934 /* Now display the menu */
1935 if (menu != NULL) {
1936 gtk_widget_show_all(menu);
1937 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, func, tv, button, time);
1938 handled = TRUE;
1939 }
1940
1941 return handled;
1942 }
1943
1944 static gboolean
gtk_blist_button_press_cb(GtkWidget * tv,GdkEventButton * event,gpointer user_data)1945 gtk_blist_button_press_cb(GtkWidget *tv, GdkEventButton *event, gpointer user_data)
1946 {
1947 GtkTreePath *path;
1948 PurpleBlistNode *node;
1949 GtkTreeIter iter;
1950 GtkTreeSelection *sel;
1951 PurplePlugin *prpl = NULL;
1952 PurplePluginProtocolInfo *prpl_info = NULL;
1953 struct _pidgin_blist_node *gtknode;
1954 gboolean handled = FALSE;
1955
1956 /* Here we figure out which node was clicked */
1957 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL))
1958 return FALSE;
1959 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
1960 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
1961 gtknode = (struct _pidgin_blist_node *)node->ui_data;
1962
1963 /* Right click draws a context menu */
1964 if ((event->button == 3) && (event->type == GDK_BUTTON_PRESS)) {
1965 handled = pidgin_blist_show_context_menu(node, NULL, tv, 3, event->time);
1966
1967 /* CTRL+middle click expands or collapse a contact */
1968 } else if ((event->button == 2) && (event->type == GDK_BUTTON_PRESS) &&
1969 (event->state & GDK_CONTROL_MASK) && (PURPLE_BLIST_NODE_IS_CONTACT(node))) {
1970 if (gtknode->contact_expanded)
1971 pidgin_blist_collapse_contact_cb(NULL, node);
1972 else
1973 pidgin_blist_expand_contact_cb(NULL, node);
1974 handled = TRUE;
1975
1976 /* Double middle click gets info */
1977 } else if ((event->button == 2) && (event->type == GDK_2BUTTON_PRESS) &&
1978 ((PURPLE_BLIST_NODE_IS_CONTACT(node)) || (PURPLE_BLIST_NODE_IS_BUDDY(node)))) {
1979 PurpleBuddy *b;
1980 if(PURPLE_BLIST_NODE_IS_CONTACT(node))
1981 b = purple_contact_get_priority_buddy((PurpleContact*)node);
1982 else
1983 b = (PurpleBuddy *)node;
1984
1985 prpl = purple_find_prpl(purple_account_get_protocol_id(b->account));
1986 if (prpl != NULL)
1987 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
1988
1989 if (prpl && prpl_info->get_info)
1990 pidgin_retrieve_user_info(b->account->gc, b->name);
1991 handled = TRUE;
1992 }
1993
1994 #if (1)
1995 /*
1996 * This code only exists because GTK+ doesn't work. If we return
1997 * FALSE here, as would be normal the event propoagates down and
1998 * somehow gets interpreted as the start of a drag event.
1999 *
2000 * Um, isn't it _normal_ to return TRUE here? Since the event
2001 * was handled? --Mark
2002 */
2003 if(handled) {
2004 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
2005 gtk_tree_selection_select_path(sel, path);
2006 gtk_tree_path_free(path);
2007 return TRUE;
2008 }
2009 #endif
2010 gtk_tree_path_free(path);
2011
2012 return FALSE;
2013 }
2014
2015 static gboolean
pidgin_blist_popup_menu_cb(GtkWidget * tv,void * user_data)2016 pidgin_blist_popup_menu_cb(GtkWidget *tv, void *user_data)
2017 {
2018 PurpleBlistNode *node;
2019 GtkTreeIter iter;
2020 GtkTreeSelection *sel;
2021 gboolean handled = FALSE;
2022
2023 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
2024 if (!gtk_tree_selection_get_selected(sel, NULL, &iter))
2025 return FALSE;
2026
2027 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
2028
2029 /* Shift+F10 draws a context menu */
2030 handled = pidgin_blist_show_context_menu(node, pidgin_treeview_popup_menu_position_func, tv, 0, GDK_CURRENT_TIME);
2031
2032 return handled;
2033 }
2034
pidgin_blist_buddy_details_cb(gpointer data,guint action,GtkWidget * item)2035 static void pidgin_blist_buddy_details_cb(gpointer data, guint action, GtkWidget *item)
2036 {
2037 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
2038
2039 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons",
2040 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
2041
2042 pidgin_clear_cursor(gtkblist->window);
2043 }
2044
pidgin_blist_show_idle_time_cb(gpointer data,guint action,GtkWidget * item)2045 static void pidgin_blist_show_idle_time_cb(gpointer data, guint action, GtkWidget *item)
2046 {
2047 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
2048
2049 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time",
2050 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
2051
2052 pidgin_clear_cursor(gtkblist->window);
2053 }
2054
pidgin_blist_show_protocol_icons_cb(gpointer data,guint action,GtkWidget * item)2055 static void pidgin_blist_show_protocol_icons_cb(gpointer data, guint action, GtkWidget *item)
2056 {
2057 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons",
2058 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
2059 }
2060
pidgin_blist_show_empty_groups_cb(gpointer data,guint action,GtkWidget * item)2061 static void pidgin_blist_show_empty_groups_cb(gpointer data, guint action, GtkWidget *item)
2062 {
2063 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
2064
2065 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups",
2066 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
2067
2068 pidgin_clear_cursor(gtkblist->window);
2069 }
2070
pidgin_blist_edit_mode_cb(gpointer callback_data,guint callback_action,GtkWidget * checkitem)2071 static void pidgin_blist_edit_mode_cb(gpointer callback_data, guint callback_action,
2072 GtkWidget *checkitem)
2073 {
2074 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
2075
2076 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies",
2077 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(checkitem)));
2078
2079 pidgin_clear_cursor(gtkblist->window);
2080 }
2081
pidgin_blist_mute_sounds_cb(gpointer data,guint action,GtkWidget * item)2082 static void pidgin_blist_mute_sounds_cb(gpointer data, guint action, GtkWidget *item)
2083 {
2084 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/sound/mute", GTK_CHECK_MENU_ITEM(item)->active);
2085 }
2086
2087 static void
pidgin_blist_mute_pref_cb(const char * name,PurplePrefType type,gconstpointer value,gpointer data)2088 pidgin_blist_mute_pref_cb(const char *name, PurplePrefType type,
2089 gconstpointer value, gpointer data)
2090 {
2091 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(gtkblist->ift,
2092 N_("/Tools/Mute Sounds"))), (gboolean)GPOINTER_TO_INT(value));
2093 }
2094
2095 static void
pidgin_blist_sound_method_pref_cb(const char * name,PurplePrefType type,gconstpointer value,gpointer data)2096 pidgin_blist_sound_method_pref_cb(const char *name, PurplePrefType type,
2097 gconstpointer value, gpointer data)
2098 {
2099 gboolean sensitive = TRUE;
2100
2101 if(purple_strequal(value, "none"))
2102 sensitive = FALSE;
2103
2104 gtk_widget_set_sensitive(gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Mute Sounds")), sensitive);
2105 }
2106
2107 static void
add_buddies_from_vcard(const char * prpl_id,PurpleGroup * group,GList * list,const char * alias)2108 add_buddies_from_vcard(const char *prpl_id, PurpleGroup *group, GList *list,
2109 const char *alias)
2110 {
2111 GList *l;
2112 PurpleAccount *account = NULL;
2113 PurpleConnection *gc;
2114
2115 if (list == NULL)
2116 return;
2117
2118 for (l = purple_connections_get_all(); l != NULL; l = l->next)
2119 {
2120 gc = (PurpleConnection *)l->data;
2121 account = purple_connection_get_account(gc);
2122
2123 if (purple_strequal(purple_account_get_protocol_id(account), prpl_id))
2124 break;
2125
2126 account = NULL;
2127 }
2128
2129 if (account != NULL)
2130 {
2131 for (l = list; l != NULL; l = l->next)
2132 {
2133 purple_blist_request_add_buddy(account, l->data,
2134 (group ? group->name : NULL),
2135 alias);
2136 }
2137 }
2138
2139 g_list_free_full(list, (GDestroyNotify)g_free);
2140 }
2141
2142 static gboolean
parse_vcard(const char * vcard,PurpleGroup * group)2143 parse_vcard(const char *vcard, PurpleGroup *group)
2144 {
2145 char *temp_vcard;
2146 char *s, *c;
2147 char *alias = NULL;
2148 GList *aims = NULL;
2149 GList *icqs = NULL;
2150 GList *jabbers = NULL;
2151
2152 s = temp_vcard = g_strdup(vcard);
2153
2154 while (*s != '\0' && strncmp(s, "END:vCard", strlen("END:vCard")))
2155 {
2156 char *field, *value;
2157
2158 field = s;
2159
2160 /* Grab the field */
2161 while (*s != '\r' && *s != '\n' && *s != '\0' && *s != ':')
2162 s++;
2163
2164 if (*s == '\r') s++;
2165 if (*s == '\n')
2166 {
2167 s++;
2168 continue;
2169 }
2170
2171 if (*s != '\0') *s++ = '\0';
2172
2173 if ((c = strchr(field, ';')) != NULL)
2174 *c = '\0';
2175
2176 /* Proceed to the end of the line */
2177 value = s;
2178
2179 while (*s != '\r' && *s != '\n' && *s != '\0')
2180 s++;
2181
2182 if (*s == '\r') *s++ = '\0';
2183 if (*s == '\n') *s++ = '\0';
2184
2185 /* We only want to worry about a few fields here. */
2186 if (purple_strequal(field, "FN"))
2187 alias = g_strdup(value);
2188 else if (purple_strequal(field, "X-AIM") || purple_strequal(field, "X-ICQ") ||
2189 purple_strequal(field, "X-JABBER"))
2190 {
2191 char **values = g_strsplit(value, ":", 0);
2192 char **im;
2193
2194 for (im = values; *im != NULL; im++)
2195 {
2196 if (purple_strequal(field, "X-AIM"))
2197 aims = g_list_append(aims, g_strdup(*im));
2198 else if (purple_strequal(field, "X-ICQ"))
2199 icqs = g_list_append(icqs, g_strdup(*im));
2200 else if (purple_strequal(field, "X-JABBER"))
2201 jabbers = g_list_append(jabbers, g_strdup(*im));
2202 }
2203
2204 g_strfreev(values);
2205 }
2206 }
2207
2208 g_free(temp_vcard);
2209
2210 if (aims == NULL && icqs == NULL && jabbers == NULL)
2211 {
2212 g_free(alias);
2213
2214 return FALSE;
2215 }
2216
2217 add_buddies_from_vcard("prpl-aim", group, aims, alias);
2218 add_buddies_from_vcard("prpl-icq", group, icqs, alias);
2219 add_buddies_from_vcard("prpl-jabber", group, jabbers, alias);
2220
2221 g_free(alias);
2222
2223 return TRUE;
2224 }
2225
2226 #ifdef _WIN32
pidgin_blist_drag_begin(GtkWidget * widget,GdkDragContext * drag_context,gpointer user_data)2227 static void pidgin_blist_drag_begin(GtkWidget *widget,
2228 GdkDragContext *drag_context, gpointer user_data)
2229 {
2230 pidgin_blist_tooltip_destroy();
2231
2232
2233 /* Unhook the tooltip-timeout since we don't want a tooltip
2234 * to appear and obscure the dragging operation.
2235 * This is a workaround for GTK+ bug 107320. */
2236 if (gtkblist->timeout) {
2237 g_source_remove(gtkblist->timeout);
2238 gtkblist->timeout = 0;
2239 }
2240 }
2241 #endif
2242
pidgin_blist_drag_data_get_cb(GtkWidget * widget,GdkDragContext * dc,GtkSelectionData * data,guint info,guint time,gpointer null)2243 static void pidgin_blist_drag_data_get_cb(GtkWidget *widget,
2244 GdkDragContext *dc,
2245 GtkSelectionData *data,
2246 guint info,
2247 guint time,
2248 gpointer null)
2249 {
2250
2251 if (data->target == gdk_atom_intern("PURPLE_BLIST_NODE", FALSE))
2252 {
2253 GtkTreeRowReference *ref = g_object_get_data(G_OBJECT(dc), "gtk-tree-view-source-row");
2254 GtkTreePath *sourcerow = gtk_tree_row_reference_get_path(ref);
2255 GtkTreeIter iter;
2256 PurpleBlistNode *node = NULL;
2257 if(!sourcerow)
2258 return;
2259 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, sourcerow);
2260 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
2261 gtk_selection_data_set (data,
2262 gdk_atom_intern ("PURPLE_BLIST_NODE", FALSE),
2263 8, /* bits */
2264 (void*)&node,
2265 sizeof (node));
2266
2267 gtk_tree_path_free(sourcerow);
2268 }
2269 else if (data->target == gdk_atom_intern("application/x-im-contact", FALSE))
2270 {
2271 GtkTreeRowReference *ref;
2272 GtkTreePath *sourcerow;
2273 GtkTreeIter iter;
2274 PurpleBlistNode *node = NULL;
2275 PurpleBuddy *buddy;
2276 PurpleConnection *gc;
2277 GString *str;
2278 const char *protocol;
2279
2280 ref = g_object_get_data(G_OBJECT(dc), "gtk-tree-view-source-row");
2281 sourcerow = gtk_tree_row_reference_get_path(ref);
2282
2283 if (!sourcerow)
2284 return;
2285
2286 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
2287 sourcerow);
2288 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
2289
2290 if (PURPLE_BLIST_NODE_IS_CONTACT(node))
2291 {
2292 buddy = purple_contact_get_priority_buddy((PurpleContact *)node);
2293 }
2294 else if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
2295 {
2296 gtk_tree_path_free(sourcerow);
2297 return;
2298 }
2299 else
2300 {
2301 buddy = (PurpleBuddy *)node;
2302 }
2303
2304 gc = purple_account_get_connection(buddy->account);
2305
2306 if (gc == NULL)
2307 {
2308 gtk_tree_path_free(sourcerow);
2309 return;
2310 }
2311
2312 protocol =
2313 PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->list_icon(buddy->account,
2314 buddy);
2315
2316 str = g_string_new(NULL);
2317 g_string_printf(str,
2318 "MIME-Version: 1.0\r\n"
2319 "Content-Type: application/x-im-contact\r\n"
2320 "X-IM-Protocol: %s\r\n"
2321 "X-IM-Username: %s\r\n",
2322 protocol,
2323 buddy->name);
2324
2325 if (buddy->alias != NULL)
2326 {
2327 g_string_append_printf(str,
2328 "X-IM-Alias: %s\r\n",
2329 buddy->alias);
2330 }
2331
2332 g_string_append(str, "\r\n");
2333
2334 gtk_selection_data_set(data,
2335 gdk_atom_intern("application/x-im-contact", FALSE),
2336 8, /* bits */
2337 (const guchar *)str->str,
2338 strlen(str->str) + 1);
2339
2340 g_string_free(str, TRUE);
2341 gtk_tree_path_free(sourcerow);
2342 }
2343 }
2344
pidgin_blist_drag_data_rcv_cb(GtkWidget * widget,GdkDragContext * dc,guint x,guint y,GtkSelectionData * sd,guint info,guint t)2345 static void pidgin_blist_drag_data_rcv_cb(GtkWidget *widget, GdkDragContext *dc, guint x, guint y,
2346 GtkSelectionData *sd, guint info, guint t)
2347 {
2348 if (gtkblist->drag_timeout) {
2349 g_source_remove(gtkblist->drag_timeout);
2350 gtkblist->drag_timeout = 0;
2351 }
2352
2353 if (sd->target == gdk_atom_intern("PURPLE_BLIST_NODE", FALSE) && sd->data) {
2354 PurpleBlistNode *n = NULL;
2355 GtkTreePath *path = NULL;
2356 GtkTreeViewDropPosition position;
2357 memcpy(&n, sd->data, sizeof(n));
2358 if(gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget), x, y, &path, &position)) {
2359 /* if we're here, I think it means the drop is ok */
2360 GtkTreeIter iter;
2361 PurpleBlistNode *node;
2362 struct _pidgin_blist_node *gtknode;
2363
2364 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2365 &iter, path);
2366 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel),
2367 &iter, NODE_COLUMN, &node, -1);
2368 gtknode = node->ui_data;
2369
2370 if (PURPLE_BLIST_NODE_IS_CONTACT(n)) {
2371 PurpleContact *c = (PurpleContact*)n;
2372 if (PURPLE_BLIST_NODE_IS_CONTACT(node) && gtknode->contact_expanded) {
2373 purple_blist_merge_contact(c, node);
2374 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node) ||
2375 PURPLE_BLIST_NODE_IS_CHAT(node)) {
2376 switch(position) {
2377 case GTK_TREE_VIEW_DROP_AFTER:
2378 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2379 purple_blist_add_contact(c, (PurpleGroup*)node->parent,
2380 node);
2381 break;
2382 case GTK_TREE_VIEW_DROP_BEFORE:
2383 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2384 purple_blist_add_contact(c, (PurpleGroup*)node->parent,
2385 node->prev);
2386 break;
2387 }
2388 } else if(PURPLE_BLIST_NODE_IS_GROUP(node)) {
2389 purple_blist_add_contact(c, (PurpleGroup*)node, NULL);
2390 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2391 purple_blist_merge_contact(c, node);
2392 }
2393 } else if (PURPLE_BLIST_NODE_IS_BUDDY(n)) {
2394 PurpleBuddy *b = (PurpleBuddy*)n;
2395 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2396 switch(position) {
2397 case GTK_TREE_VIEW_DROP_AFTER:
2398 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2399 purple_blist_add_buddy(b, (PurpleContact*)node->parent,
2400 (PurpleGroup*)node->parent->parent, node);
2401 break;
2402 case GTK_TREE_VIEW_DROP_BEFORE:
2403 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2404 purple_blist_add_buddy(b, (PurpleContact*)node->parent,
2405 (PurpleGroup*)node->parent->parent,
2406 node->prev);
2407 break;
2408 }
2409 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
2410 purple_blist_add_buddy(b, NULL, (PurpleGroup*)node->parent,
2411 NULL);
2412 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
2413 purple_blist_add_buddy(b, NULL, (PurpleGroup*)node, NULL);
2414 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
2415 if(gtknode->contact_expanded) {
2416 switch(position) {
2417 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2418 case GTK_TREE_VIEW_DROP_AFTER:
2419 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2420 purple_blist_add_buddy(b, (PurpleContact*)node,
2421 (PurpleGroup*)node->parent, NULL);
2422 break;
2423 case GTK_TREE_VIEW_DROP_BEFORE:
2424 purple_blist_add_buddy(b, NULL,
2425 (PurpleGroup*)node->parent, node->prev);
2426 break;
2427 }
2428 } else {
2429 switch(position) {
2430 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2431 case GTK_TREE_VIEW_DROP_AFTER:
2432 purple_blist_add_buddy(b, NULL,
2433 (PurpleGroup*)node->parent, NULL);
2434 break;
2435 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2436 case GTK_TREE_VIEW_DROP_BEFORE:
2437 purple_blist_add_buddy(b, NULL,
2438 (PurpleGroup*)node->parent, node->prev);
2439 break;
2440 }
2441 }
2442 }
2443 } else if (PURPLE_BLIST_NODE_IS_CHAT(n)) {
2444 PurpleChat *chat = (PurpleChat *)n;
2445 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2446 switch(position) {
2447 case GTK_TREE_VIEW_DROP_AFTER:
2448 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2449 case GTK_TREE_VIEW_DROP_BEFORE:
2450 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2451 purple_blist_add_chat(chat,
2452 (PurpleGroup*)node->parent->parent,
2453 node->parent);
2454 break;
2455 }
2456 } else if(PURPLE_BLIST_NODE_IS_CONTACT(node) ||
2457 PURPLE_BLIST_NODE_IS_CHAT(node)) {
2458 switch(position) {
2459 case GTK_TREE_VIEW_DROP_AFTER:
2460 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2461 purple_blist_add_chat(chat, (PurpleGroup*)node->parent, node);
2462 break;
2463 case GTK_TREE_VIEW_DROP_BEFORE:
2464 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2465 purple_blist_add_chat(chat, (PurpleGroup*)node->parent, node->prev);
2466 break;
2467 }
2468 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
2469 purple_blist_add_chat(chat, (PurpleGroup*)node, NULL);
2470 }
2471 } else if (PURPLE_BLIST_NODE_IS_GROUP(n)) {
2472 PurpleGroup *g = (PurpleGroup*)n;
2473 if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
2474 switch (position) {
2475 case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2476 case GTK_TREE_VIEW_DROP_AFTER:
2477 purple_blist_add_group(g, node);
2478 break;
2479 case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2480 case GTK_TREE_VIEW_DROP_BEFORE:
2481 purple_blist_add_group(g, node->prev);
2482 break;
2483 }
2484 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2485 purple_blist_add_group(g, node->parent->parent);
2486 } else if(PURPLE_BLIST_NODE_IS_CONTACT(node) ||
2487 PURPLE_BLIST_NODE_IS_CHAT(node)) {
2488 purple_blist_add_group(g, node->parent);
2489 }
2490 }
2491
2492 gtk_tree_path_free(path);
2493 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
2494 }
2495 }
2496 else if (sd->target == gdk_atom_intern("application/x-im-contact",
2497 FALSE) && sd->data)
2498 {
2499 PurpleGroup *group = NULL;
2500 GtkTreePath *path = NULL;
2501 GtkTreeViewDropPosition position;
2502 PurpleAccount *account;
2503 char *protocol = NULL;
2504 char *username = NULL;
2505 char *alias = NULL;
2506
2507 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
2508 x, y, &path, &position))
2509 {
2510 GtkTreeIter iter;
2511 PurpleBlistNode *node;
2512
2513 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2514 &iter, path);
2515 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel),
2516 &iter, NODE_COLUMN, &node, -1);
2517
2518 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
2519 {
2520 group = (PurpleGroup *)node->parent->parent;
2521 }
2522 else if (PURPLE_BLIST_NODE_IS_CHAT(node) ||
2523 PURPLE_BLIST_NODE_IS_CONTACT(node))
2524 {
2525 group = (PurpleGroup *)node->parent;
2526 }
2527 else if (PURPLE_BLIST_NODE_IS_GROUP(node))
2528 {
2529 group = (PurpleGroup *)node;
2530 }
2531 }
2532
2533 if (pidgin_parse_x_im_contact((const char *)sd->data, FALSE, &account,
2534 &protocol, &username, &alias))
2535 {
2536 if (account == NULL)
2537 {
2538 purple_notify_error(NULL, NULL,
2539 _("You are not currently signed on with an account that "
2540 "can add that buddy."), NULL);
2541 }
2542 else
2543 {
2544 purple_blist_request_add_buddy(account, username,
2545 (group ? group->name : NULL),
2546 alias);
2547 }
2548 }
2549
2550 g_free(username);
2551 g_free(protocol);
2552 g_free(alias);
2553
2554 if (path != NULL)
2555 gtk_tree_path_free(path);
2556
2557 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
2558 }
2559 else if (sd->target == gdk_atom_intern("text/x-vcard", FALSE) && sd->data)
2560 {
2561 gboolean result;
2562 PurpleGroup *group = NULL;
2563 GtkTreePath *path = NULL;
2564 GtkTreeViewDropPosition position;
2565
2566 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
2567 x, y, &path, &position))
2568 {
2569 GtkTreeIter iter;
2570 PurpleBlistNode *node;
2571
2572 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2573 &iter, path);
2574 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel),
2575 &iter, NODE_COLUMN, &node, -1);
2576
2577 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
2578 {
2579 group = (PurpleGroup *)node->parent->parent;
2580 }
2581 else if (PURPLE_BLIST_NODE_IS_CHAT(node) ||
2582 PURPLE_BLIST_NODE_IS_CONTACT(node))
2583 {
2584 group = (PurpleGroup *)node->parent;
2585 }
2586 else if (PURPLE_BLIST_NODE_IS_GROUP(node))
2587 {
2588 group = (PurpleGroup *)node;
2589 }
2590 }
2591
2592 result = parse_vcard((const gchar *)sd->data, group);
2593
2594 gtk_drag_finish(dc, result, (dc->action == GDK_ACTION_MOVE), t);
2595 } else if (sd->target == gdk_atom_intern("text/uri-list", FALSE) && sd->data) {
2596 GtkTreePath *path = NULL;
2597 GtkTreeViewDropPosition position;
2598
2599 if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget),
2600 x, y, &path, &position))
2601 {
2602 GtkTreeIter iter;
2603 PurpleBlistNode *node;
2604
2605 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel),
2606 &iter, path);
2607 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel),
2608 &iter, NODE_COLUMN, &node, -1);
2609
2610 if (PURPLE_BLIST_NODE_IS_BUDDY(node) || PURPLE_BLIST_NODE_IS_CONTACT(node)) {
2611 PurpleBuddy *b = PURPLE_BLIST_NODE_IS_BUDDY(node) ? PURPLE_BUDDY(node) : purple_contact_get_priority_buddy(PURPLE_CONTACT(node));
2612 pidgin_dnd_file_manage(sd, b->account, b->name);
2613 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
2614 } else {
2615 gtk_drag_finish(dc, FALSE, FALSE, t);
2616 }
2617 }
2618 }
2619 }
2620
2621 /* Altered from do_colorshift in gnome-panel */
2622 static void
do_alphashift(GdkPixbuf * pixbuf,int shift)2623 do_alphashift(GdkPixbuf *pixbuf, int shift)
2624 {
2625 gint i, j;
2626 gint width, height, padding;
2627 guchar *pixels;
2628 int val;
2629
2630 if (!gdk_pixbuf_get_has_alpha(pixbuf))
2631 return;
2632
2633 width = gdk_pixbuf_get_width(pixbuf);
2634 height = gdk_pixbuf_get_height(pixbuf);
2635 padding = gdk_pixbuf_get_rowstride(pixbuf) - width * 4;
2636 pixels = gdk_pixbuf_get_pixels(pixbuf);
2637
2638 for (i = 0; i < height; i++) {
2639 for (j = 0; j < width; j++) {
2640 pixels++;
2641 pixels++;
2642 pixels++;
2643 val = *pixels - shift;
2644 *(pixels++) = CLAMP(val, 0, 255);
2645 }
2646 pixels += padding;
2647 }
2648 }
2649
2650
pidgin_blist_get_buddy_icon(PurpleBlistNode * node,gboolean scaled,gboolean greyed)2651 static GdkPixbuf *pidgin_blist_get_buddy_icon(PurpleBlistNode *node,
2652 gboolean scaled, gboolean greyed)
2653 {
2654 gsize len;
2655 PurpleBuddy *buddy = NULL;
2656 PurpleGroup *group = NULL;
2657 const guchar *data = NULL;
2658 GdkPixbuf *buf, *ret = NULL;
2659 PurpleBuddyIcon *icon = NULL;
2660 PurpleAccount *account = NULL;
2661 PurpleContact *contact = NULL;
2662 PurpleStoredImage *custom_img;
2663 PurplePluginProtocolInfo *prpl_info = NULL;
2664 gint orig_width, orig_height, scale_width, scale_height;
2665
2666 if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
2667 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
2668 contact = (PurpleContact*)node;
2669 } else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2670 buddy = (PurpleBuddy*)node;
2671 contact = purple_buddy_get_contact(buddy);
2672 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
2673 group = (PurpleGroup*)node;
2674 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
2675 /* We don't need to do anything here. We just need to not fall
2676 * into the else block and return. */
2677 } else {
2678 return NULL;
2679 }
2680
2681 if (buddy) {
2682 account = purple_buddy_get_account(buddy);
2683 }
2684
2685 if(account && account->gc) {
2686 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(account->gc->prpl);
2687 }
2688
2689 #if 0
2690 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"))
2691 return NULL;
2692 #endif
2693
2694 /* If we have a contact then this is either a contact or a buddy and
2695 * we want to fetch the custom icon for the contact. If we don't have
2696 * a contact then this is a group or some other type of node and we
2697 * want to use that directly. */
2698 if (contact) {
2699 custom_img = purple_buddy_icons_node_find_custom_icon((PurpleBlistNode*)contact);
2700 } else {
2701 custom_img = purple_buddy_icons_node_find_custom_icon(node);
2702 }
2703
2704 if (custom_img) {
2705 data = purple_imgstore_get_data(custom_img);
2706 len = purple_imgstore_get_size(custom_img);
2707 }
2708
2709 if (data == NULL) {
2710 if (buddy) {
2711 /* Not sure I like this...*/
2712 if (!(icon = purple_buddy_icons_find(buddy->account, buddy->name)))
2713 return NULL;
2714 data = purple_buddy_icon_get_data(icon, &len);
2715 }
2716
2717 if(data == NULL)
2718 return NULL;
2719 }
2720
2721 buf = pidgin_pixbuf_from_data(data, len);
2722 purple_buddy_icon_unref(icon);
2723 if (!buf) {
2724 purple_debug_warning("gtkblist", "Couldn't load buddy icon "
2725 "on account %s (%s) buddyname=%s "
2726 "custom_img_data=%p\n",
2727 account ? purple_account_get_username(account) : "(no account)",
2728 account ? purple_account_get_protocol_id(account) : "(no account)",
2729 buddy ? purple_buddy_get_name(buddy) : "(no buddy)",
2730 custom_img ? purple_imgstore_get_data(custom_img) : NULL);
2731 purple_imgstore_unref(custom_img);
2732 return NULL;
2733 }
2734 purple_imgstore_unref(custom_img);
2735
2736 if (greyed) {
2737 gboolean offline = FALSE, idle = FALSE;
2738
2739 if (buddy) {
2740 PurplePresence *presence = purple_buddy_get_presence(buddy);
2741 if (!PURPLE_BUDDY_IS_ONLINE(buddy))
2742 offline = TRUE;
2743 if (purple_presence_is_idle(presence))
2744 idle = TRUE;
2745 } else if (group) {
2746 if (purple_blist_get_group_online_count(group) == 0)
2747 offline = TRUE;
2748 }
2749
2750 if (offline)
2751 gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.0, FALSE);
2752
2753 if (idle)
2754 gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.25, FALSE);
2755 }
2756
2757 /* I'd use the pidgin_buddy_icon_get_scale_size() thing, but it won't
2758 * tell me the original size, which I need for scaling purposes. */
2759 scale_width = orig_width = gdk_pixbuf_get_width(buf);
2760 scale_height = orig_height = gdk_pixbuf_get_height(buf);
2761
2762 if (prpl_info && prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_DISPLAY)
2763 purple_buddy_icon_get_scale_size(&prpl_info->icon_spec, &scale_width, &scale_height);
2764
2765 if (scaled || scale_height > 200 || scale_width > 200) {
2766 GdkPixbuf *tmpbuf;
2767 float scale_size = scaled ? 32.0 : 200.0;
2768 if(scale_height > scale_width) {
2769 scale_width = scale_size * (double)scale_width / (double)scale_height;
2770 scale_height = scale_size;
2771 } else {
2772 scale_height = scale_size * (double)scale_height / (double)scale_width;
2773 scale_width = scale_size;
2774 }
2775 /* Scale & round before making square, so rectangular (but
2776 * non-square) images get rounded corners too. */
2777 tmpbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_width, scale_height);
2778 gdk_pixbuf_fill(tmpbuf, 0x00000000);
2779 gdk_pixbuf_scale(buf, tmpbuf, 0, 0, scale_width, scale_height, 0, 0, (double)scale_width/(double)orig_width, (double)scale_height/(double)orig_height, GDK_INTERP_BILINEAR);
2780 if (pidgin_gdk_pixbuf_is_opaque(tmpbuf))
2781 pidgin_gdk_pixbuf_make_round(tmpbuf);
2782 ret = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_size, scale_size);
2783 gdk_pixbuf_fill(ret, 0x00000000);
2784 gdk_pixbuf_copy_area(tmpbuf, 0, 0, scale_width, scale_height, ret, (scale_size-scale_width)/2, (scale_size-scale_height)/2);
2785 g_object_unref(G_OBJECT(tmpbuf));
2786 } else {
2787 ret = gdk_pixbuf_scale_simple(buf,scale_width,scale_height, GDK_INTERP_BILINEAR);
2788 }
2789 g_object_unref(G_OBJECT(buf));
2790
2791 return ret;
2792 }
2793
2794 /* # - Status Icon
2795 * P - Protocol Icon
2796 * A - Buddy Icon
2797 * [ - SMALL_SPACE
2798 * = - LARGE_SPACE
2799 * +--- STATUS_SIZE +--- td->avatar_width
2800 * | +-- td->name_width |
2801 * +----+ +-------+ +---------+
2802 * | | | | | |
2803 * +-------------------------------------------+
2804 * | [ = [ |--- TOOLTIP_BORDER
2805 *name_height --+-| ######[BuddyName = PP [ AAAAAAAAAAA |--+
2806 * | | ######[ = PP [ AAAAAAAAAAA | |
2807 * STATUS SIZE -| | ######[[[[[[[[[[[[[[[[[[[[[ AAAAAAAAAAA | |
2808 * +--+-| ######[Account: So-and-so [ AAAAAAAAAAA | |-- td->avatar_height
2809 * | | [Idle: 4h 15m [ AAAAAAAAAAA | |
2810 * height --+ | [Foo: Bar, Baz [ AAAAAAAAAAA | |
2811 * | | [Status: Awesome [ AAAAAAAAAAA |--+
2812 * +----| [Stop: Hammer Time [ |
2813 * | [ [ |--- TOOLTIP_BORDER
2814 * +-------------------------------------------+
2815 * | | | |
2816 * | +----------------+ |
2817 * | | |
2818 * | +-- td->width |
2819 * | |
2820 * +---- TOOLTIP_BORDER +---- TOOLTIP_BORDER
2821 *
2822 *
2823 */
2824 #define STATUS_SIZE 16
2825 #define TOOLTIP_BORDER 12
2826 #define SMALL_SPACE 6
2827 #define LARGE_SPACE 12
2828 #define PRPL_SIZE 16
2829 struct tooltip_data {
2830 PangoLayout *layout;
2831 PangoLayout *name_layout;
2832 GdkPixbuf *prpl_icon;
2833 GdkPixbuf *status_icon;
2834 GdkPixbuf *avatar;
2835 gboolean avatar_is_prpl_icon;
2836 int avatar_width;
2837 int avatar_height;
2838 int name_height;
2839 int name_width;
2840 int width;
2841 int height;
2842 int padding;
2843 };
2844
create_pango_layout(const char * markup,int * width,int * height)2845 static PangoLayout * create_pango_layout(const char *markup, int *width, int *height)
2846 {
2847 PangoLayout *layout;
2848 int w, h;
2849
2850 layout = gtk_widget_create_pango_layout(gtkblist->tipwindow, NULL);
2851 pango_layout_set_markup(layout, markup, -1);
2852 pango_layout_set_wrap(layout, PANGO_WRAP_WORD);
2853 pango_layout_set_width(layout, 300000);
2854
2855 pango_layout_get_size (layout, &w, &h);
2856 if (width)
2857 *width = PANGO_PIXELS(w);
2858 if (height)
2859 *height = PANGO_PIXELS(h);
2860 return layout;
2861 }
2862
create_tip_for_account(PurpleAccount * account)2863 static struct tooltip_data * create_tip_for_account(PurpleAccount *account)
2864 {
2865 struct tooltip_data *td = g_new0(struct tooltip_data, 1);
2866 td->status_icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
2867 /* Yes, status_icon, not prpl_icon */
2868 if (purple_account_is_disconnected(account))
2869 gdk_pixbuf_saturate_and_pixelate(td->status_icon, td->status_icon, 0.0, FALSE);
2870 td->layout = create_pango_layout(purple_account_get_username(account), &td->width, &td->height);
2871 td->padding = SMALL_SPACE;
2872 return td;
2873 }
2874
create_tip_for_node(PurpleBlistNode * node,gboolean full)2875 static struct tooltip_data * create_tip_for_node(PurpleBlistNode *node, gboolean full)
2876 {
2877 struct tooltip_data *td = g_new0(struct tooltip_data, 1);
2878 PurpleAccount *account = NULL;
2879 char *tmp = NULL, *node_name = NULL, *tooltip_text = NULL;
2880
2881 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2882 account = ((PurpleBuddy*)(node))->account;
2883 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
2884 account = ((PurpleChat*)(node))->account;
2885 }
2886
2887 td->padding = TOOLTIP_BORDER;
2888 td->status_icon = pidgin_blist_get_status_icon(node, PIDGIN_STATUS_ICON_LARGE);
2889 td->avatar = pidgin_blist_get_buddy_icon(node, !full, FALSE);
2890 if (account != NULL) {
2891 td->prpl_icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
2892 }
2893 tooltip_text = pidgin_get_tooltip_text(node, full);
2894 if (tooltip_text && *tooltip_text) {
2895 td->layout = create_pango_layout(tooltip_text, &td->width, &td->height);
2896 }
2897
2898 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
2899 tmp = g_markup_escape_text(purple_buddy_get_name((PurpleBuddy*)node), -1);
2900 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
2901 tmp = g_markup_escape_text(purple_chat_get_name((PurpleChat*)node), -1);
2902 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
2903 tmp = g_markup_escape_text(purple_group_get_name((PurpleGroup*)node), -1);
2904 } else {
2905 /* I don't believe this can happen currently, I think
2906 * everything that calls this function checks for one of the
2907 * above node types first. */
2908 tmp = g_strdup(_("Unknown node type"));
2909 }
2910 node_name = g_strdup_printf("<span size='x-large' weight='bold'>%s</span>",
2911 tmp ? tmp : "");
2912 g_free(tmp);
2913
2914 td->name_layout = create_pango_layout(node_name, &td->name_width, &td->name_height);
2915 td->name_width += SMALL_SPACE + PRPL_SIZE;
2916 td->name_height = MAX(td->name_height, PRPL_SIZE + SMALL_SPACE);
2917 #if 0 /* PRPL Icon as avatar */
2918 if(!td->avatar && full) {
2919 td->avatar = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_LARGE);
2920 td->avatar_is_prpl_icon = TRUE;
2921 }
2922 #endif
2923
2924 if (td->avatar) {
2925 td->avatar_width = gdk_pixbuf_get_width(td->avatar);
2926 td->avatar_height = gdk_pixbuf_get_height(td->avatar);
2927 }
2928
2929 g_free(node_name);
2930 g_free(tooltip_text);
2931 return td;
2932 }
2933
2934 static gboolean
pidgin_blist_paint_tip(GtkWidget * widget,gpointer null)2935 pidgin_blist_paint_tip(GtkWidget *widget, gpointer null)
2936 {
2937 GtkStyle *style;
2938 int current_height, max_width;
2939 int max_text_width;
2940 int max_avatar_width;
2941 GList *l;
2942 int prpl_col = 0;
2943 GtkTextDirection dir = gtk_widget_get_direction(widget);
2944 int status_size = 0;
2945
2946 if(gtkblist->tooltipdata == NULL)
2947 return FALSE;
2948
2949 style = gtkblist->tipwindow->style;
2950
2951 max_text_width = 0;
2952 max_avatar_width = 0;
2953
2954 for(l = gtkblist->tooltipdata; l; l = l->next)
2955 {
2956 struct tooltip_data *td = l->data;
2957
2958 max_text_width = MAX(max_text_width,
2959 MAX(td->width, td->name_width));
2960 max_avatar_width = MAX(max_avatar_width, td->avatar_width);
2961 if (td->status_icon)
2962 status_size = STATUS_SIZE;
2963 }
2964
2965 max_width = TOOLTIP_BORDER + status_size + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER;
2966 if (dir == GTK_TEXT_DIR_RTL)
2967 prpl_col = TOOLTIP_BORDER + max_avatar_width + SMALL_SPACE;
2968 else
2969 prpl_col = TOOLTIP_BORDER + status_size + SMALL_SPACE + max_text_width - PRPL_SIZE;
2970
2971 current_height = 12;
2972 for(l = gtkblist->tooltipdata; l; l = l->next)
2973 {
2974 struct tooltip_data *td = l->data;
2975
2976 if (td->avatar && pidgin_gdk_pixbuf_is_opaque(td->avatar))
2977 {
2978 if (dir == GTK_TEXT_DIR_RTL)
2979 gtk_paint_flat_box(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
2980 NULL, gtkblist->tipwindow, "tooltip",
2981 TOOLTIP_BORDER -1, current_height -1, td->avatar_width +2, td->avatar_height + 2);
2982 else
2983 gtk_paint_flat_box(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
2984 NULL, gtkblist->tipwindow, "tooltip",
2985 max_width - (td->avatar_width+ TOOLTIP_BORDER)-1,
2986 current_height-1,td->avatar_width+2, td->avatar_height+2);
2987 }
2988
2989 if (td->status_icon) {
2990 if (dir == GTK_TEXT_DIR_RTL)
2991 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, td->status_icon,
2992 0, 0, max_width - TOOLTIP_BORDER - status_size, current_height, -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
2993 else
2994 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, td->status_icon,
2995 0, 0, TOOLTIP_BORDER, current_height, -1 , -1, GDK_RGB_DITHER_NONE, 0, 0);
2996 }
2997
2998 if(td->avatar) {
2999 if (dir == GTK_TEXT_DIR_RTL)
3000 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL,
3001 td->avatar, 0, 0, TOOLTIP_BORDER, current_height, -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
3002 else
3003 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL,
3004 td->avatar, 0, 0, max_width - (td->avatar_width + TOOLTIP_BORDER),
3005 current_height, -1 , -1, GDK_RGB_DITHER_NONE, 0, 0);
3006 }
3007
3008 if (!td->avatar_is_prpl_icon && td->prpl_icon)
3009 gdk_draw_pixbuf(GDK_DRAWABLE(gtkblist->tipwindow->window), NULL, td->prpl_icon,
3010 0, 0,
3011 prpl_col,
3012 current_height + ((td->name_height / 2) - (PRPL_SIZE / 2)),
3013 -1 , -1, GDK_RGB_DITHER_NONE, 0, 0);
3014
3015 if (td->name_layout) {
3016 if (dir == GTK_TEXT_DIR_RTL) {
3017 gtk_paint_layout(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, FALSE,
3018 NULL, gtkblist->tipwindow, "tooltip",
3019 max_width -(TOOLTIP_BORDER + status_size + SMALL_SPACE) - PANGO_PIXELS(300000),
3020 current_height, td->name_layout);
3021 } else {
3022 gtk_paint_layout (style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, FALSE,
3023 NULL, gtkblist->tipwindow, "tooltip",
3024 TOOLTIP_BORDER + status_size + SMALL_SPACE, current_height, td->name_layout);
3025 }
3026 }
3027
3028 if (td->layout) {
3029 if (dir != GTK_TEXT_DIR_RTL) {
3030 gtk_paint_layout (style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, FALSE,
3031 NULL, gtkblist->tipwindow, "tooltip",
3032 TOOLTIP_BORDER + status_size + SMALL_SPACE, current_height + td->name_height, td->layout);
3033 } else {
3034 gtk_paint_layout(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, FALSE,
3035 NULL, gtkblist->tipwindow, "tooltip",
3036 max_width - (TOOLTIP_BORDER + status_size + SMALL_SPACE) - PANGO_PIXELS(300000),
3037 current_height + td->name_height,
3038 td->layout);
3039 }
3040 }
3041
3042 current_height += MAX(td->name_height + td->height, td->avatar_height) + td->padding;
3043 }
3044 return FALSE;
3045 }
3046
3047 static void
pidgin_blist_destroy_tooltip_data(void)3048 pidgin_blist_destroy_tooltip_data(void)
3049 {
3050 while(gtkblist->tooltipdata) {
3051 struct tooltip_data *td = gtkblist->tooltipdata->data;
3052
3053 if(td->avatar)
3054 g_object_unref(td->avatar);
3055 if(td->status_icon)
3056 g_object_unref(td->status_icon);
3057 if(td->prpl_icon)
3058 g_object_unref(td->prpl_icon);
3059 if (td->layout)
3060 g_object_unref(td->layout);
3061 if (td->name_layout)
3062 g_object_unref(td->name_layout);
3063 g_free(td);
3064 gtkblist->tooltipdata = g_list_delete_link(gtkblist->tooltipdata, gtkblist->tooltipdata);
3065 }
3066 }
3067
pidgin_blist_tooltip_destroy()3068 void pidgin_blist_tooltip_destroy()
3069 {
3070 pidgin_blist_destroy_tooltip_data();
3071 pidgin_tooltip_destroy();
3072 }
3073
3074 static void
pidgin_blist_align_tooltip(struct tooltip_data * td,GtkWidget * widget)3075 pidgin_blist_align_tooltip(struct tooltip_data *td, GtkWidget *widget)
3076 {
3077 GtkTextDirection dir = gtk_widget_get_direction(widget);
3078
3079 if (dir == GTK_TEXT_DIR_RTL)
3080 {
3081 char* layout_name = purple_markup_strip_html(pango_layout_get_text(td->name_layout));
3082 PangoDirection dir = pango_find_base_dir(layout_name, -1);
3083 if (dir == PANGO_DIRECTION_RTL || dir == PANGO_DIRECTION_NEUTRAL)
3084 pango_layout_set_alignment(td->name_layout, PANGO_ALIGN_RIGHT);
3085 g_free(layout_name);
3086 pango_layout_set_alignment(td->layout, PANGO_ALIGN_RIGHT);
3087 }
3088 }
3089
3090 static gboolean
pidgin_blist_create_tooltip_for_node(GtkWidget * widget,gpointer data,int * w,int * h)3091 pidgin_blist_create_tooltip_for_node(GtkWidget *widget, gpointer data, int *w, int *h)
3092 {
3093 PurpleBlistNode *node = data;
3094 int width, height;
3095 GList *list;
3096 int max_text_width = 0;
3097 int max_avatar_width = 0;
3098 int status_size = 0;
3099
3100 if (gtkblist->tooltipdata) {
3101 gtkblist->tipwindow = NULL;
3102 pidgin_blist_destroy_tooltip_data();
3103 }
3104
3105 gtkblist->tipwindow = widget;
3106 if (PURPLE_BLIST_NODE_IS_CHAT(node) ||
3107 PURPLE_BLIST_NODE_IS_BUDDY(node)) {
3108 struct tooltip_data *td = create_tip_for_node(node, TRUE);
3109 pidgin_blist_align_tooltip(td, gtkblist->tipwindow);
3110 gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
3111 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
3112 PurpleGroup *group = (PurpleGroup*)node;
3113 GSList *accounts;
3114 struct tooltip_data *td = create_tip_for_node(node, TRUE);
3115 pidgin_blist_align_tooltip(td, gtkblist->tipwindow);
3116 gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
3117
3118 /* Accounts with buddies in group */
3119 accounts = purple_group_get_accounts(group);
3120 for (; accounts != NULL;
3121 accounts = g_slist_delete_link(accounts, accounts)) {
3122 PurpleAccount *account = accounts->data;
3123 td = create_tip_for_account(account);
3124 gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
3125 }
3126 } else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
3127 PurpleBlistNode *child;
3128 PurpleBuddy *b = purple_contact_get_priority_buddy((PurpleContact *)node);
3129
3130 for(child = node->child; child; child = child->next)
3131 {
3132 if(PURPLE_BLIST_NODE_IS_BUDDY(child) && buddy_is_displayable((PurpleBuddy*)child)) {
3133 struct tooltip_data *td = create_tip_for_node(child, (b == (PurpleBuddy*)child));
3134 pidgin_blist_align_tooltip(td, gtkblist->tipwindow);
3135 if (b == (PurpleBuddy *)child) {
3136 gtkblist->tooltipdata = g_list_prepend(gtkblist->tooltipdata, td);
3137 } else {
3138 gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
3139 }
3140 }
3141 }
3142 } else {
3143 return FALSE;
3144 }
3145
3146 height = 0;
3147 for (list = gtkblist->tooltipdata; list; list = list->next) {
3148 struct tooltip_data *td = list->data;
3149 max_text_width = MAX(max_text_width, MAX(td->width, td->name_width));
3150 max_avatar_width = MAX(max_avatar_width, td->avatar_width);
3151 height += MAX(MAX(STATUS_SIZE, td->avatar_height), td->height + td->name_height) + td->padding;
3152 if (td->status_icon)
3153 status_size = MAX(status_size, STATUS_SIZE);
3154 }
3155 height += TOOLTIP_BORDER;
3156 width = TOOLTIP_BORDER + status_size + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER;
3157
3158 if (w)
3159 *w = width;
3160 if (h)
3161 *h = height;
3162
3163 return TRUE;
3164 }
3165
pidgin_blist_expand_timeout(GtkWidget * tv)3166 static gboolean pidgin_blist_expand_timeout(GtkWidget *tv)
3167 {
3168 GtkTreePath *path;
3169 GtkTreeIter iter;
3170 PurpleBlistNode *node;
3171 struct _pidgin_blist_node *gtknode;
3172
3173 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), gtkblist->tip_rect.x, gtkblist->tip_rect.y + (gtkblist->tip_rect.height/2),
3174 &path, NULL, NULL, NULL))
3175 return FALSE;
3176 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
3177 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
3178
3179 if(!PURPLE_BLIST_NODE_IS_CONTACT(node)) {
3180 gtk_tree_path_free(path);
3181 return FALSE;
3182 }
3183
3184 gtknode = node->ui_data;
3185
3186 if (!gtknode->contact_expanded) {
3187 GtkTreeIter i;
3188
3189 pidgin_blist_expand_contact_cb(NULL, node);
3190
3191 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, >kblist->contact_rect);
3192 gdk_drawable_get_size(GDK_DRAWABLE(tv->window), &(gtkblist->contact_rect.width), NULL);
3193 gtkblist->mouseover_contact = node;
3194 gtk_tree_path_down (path);
3195 while (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &i, path)) {
3196 GdkRectangle rect;
3197 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &rect);
3198 gtkblist->contact_rect.height += rect.height;
3199 gtk_tree_path_next(path);
3200 }
3201 }
3202 gtk_tree_path_free(path);
3203 return FALSE;
3204 }
3205
buddy_is_displayable(PurpleBuddy * buddy)3206 static gboolean buddy_is_displayable(PurpleBuddy *buddy)
3207 {
3208 struct _pidgin_blist_node *gtknode;
3209
3210 if(!buddy || !PURPLE_BLIST_NODE_IS_VISIBLE(buddy))
3211 return FALSE;
3212
3213 gtknode = ((PurpleBlistNode*)buddy)->ui_data;
3214
3215 return (purple_account_is_connected(buddy->account) &&
3216 (purple_presence_is_online(buddy->presence) ||
3217 (gtknode && gtknode->recent_signonoff) ||
3218 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies") ||
3219 purple_blist_node_get_bool((PurpleBlistNode*)buddy, "show_offline")));
3220 }
3221
pidgin_blist_draw_tooltip(PurpleBlistNode * node,GtkWidget * widget)3222 void pidgin_blist_draw_tooltip(PurpleBlistNode *node, GtkWidget *widget)
3223 {
3224 pidgin_tooltip_show(widget, node, pidgin_blist_create_tooltip_for_node, pidgin_blist_paint_tip);
3225 }
3226
pidgin_blist_drag_motion_cb(GtkWidget * tv,GdkDragContext * drag_context,gint x,gint y,guint time,gpointer user_data)3227 static gboolean pidgin_blist_drag_motion_cb(GtkWidget *tv, GdkDragContext *drag_context,
3228 gint x, gint y, guint time, gpointer user_data)
3229 {
3230 GtkTreePath *path;
3231 int delay;
3232 GdkRectangle rect;
3233
3234 /*
3235 * When dragging a buddy into a contact, this is the delay before
3236 * the contact auto-expands.
3237 */
3238 delay = 900;
3239
3240 if (gtkblist->drag_timeout) {
3241 if ((y > gtkblist->tip_rect.y) && ((y - gtkblist->tip_rect.height) < gtkblist->tip_rect.y))
3242 return FALSE;
3243 /* We've left the cell. Remove the timeout and create a new one below */
3244 g_source_remove(gtkblist->drag_timeout);
3245 }
3246
3247 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), x, y, &path, NULL, NULL, NULL);
3248 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &rect);
3249
3250 if (path)
3251 gtk_tree_path_free(path);
3252
3253 /* Only autoexpand when in the middle of the cell to avoid annoying un-intended expands */
3254 if (y < rect.y + (rect.height / 3) ||
3255 y > rect.y + (2 * (rect.height /3)))
3256 return FALSE;
3257
3258 rect.height = rect.height / 3;
3259 rect.y += rect.height;
3260
3261 gtkblist->tip_rect = rect;
3262
3263 gtkblist->drag_timeout = g_timeout_add(delay, (GSourceFunc)pidgin_blist_expand_timeout, tv);
3264
3265 if (gtkblist->mouseover_contact) {
3266 if ((y < gtkblist->contact_rect.y) || ((y - gtkblist->contact_rect.height) > gtkblist->contact_rect.y)) {
3267 pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
3268 gtkblist->mouseover_contact = NULL;
3269 }
3270 }
3271
3272 return FALSE;
3273 }
3274
3275 static gboolean
pidgin_blist_create_tooltip(GtkWidget * widget,GtkTreePath * path,gpointer null,int * w,int * h)3276 pidgin_blist_create_tooltip(GtkWidget *widget, GtkTreePath *path,
3277 gpointer null, int *w, int *h)
3278 {
3279 GtkTreeIter iter;
3280 PurpleBlistNode *node;
3281 gboolean editable = FALSE;
3282
3283 /* If we're editing a cell (e.g. alias editing), don't show the tooltip */
3284 g_object_get(G_OBJECT(gtkblist->text_rend), "editable", &editable, NULL);
3285 if (editable)
3286 return FALSE;
3287
3288 if (gtkblist->tooltipdata) {
3289 gtkblist->tipwindow = NULL;
3290 pidgin_blist_destroy_tooltip_data();
3291 }
3292
3293 gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
3294 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
3295
3296 return pidgin_blist_create_tooltip_for_node(widget, node, w, h);
3297 }
3298
pidgin_blist_motion_cb(GtkWidget * tv,GdkEventMotion * event,gpointer null)3299 static gboolean pidgin_blist_motion_cb (GtkWidget *tv, GdkEventMotion *event, gpointer null)
3300 {
3301 if (gtkblist->mouseover_contact) {
3302 if ((event->y < gtkblist->contact_rect.y) || ((event->y - gtkblist->contact_rect.height) > gtkblist->contact_rect.y)) {
3303 pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
3304 gtkblist->mouseover_contact = NULL;
3305 }
3306 }
3307
3308 return FALSE;
3309 }
3310
pidgin_blist_leave_cb(GtkWidget * w,GdkEventCrossing * e,gpointer n)3311 static gboolean pidgin_blist_leave_cb (GtkWidget *w, GdkEventCrossing *e, gpointer n)
3312 {
3313 if (gtkblist->timeout) {
3314 g_source_remove(gtkblist->timeout);
3315 gtkblist->timeout = 0;
3316 }
3317
3318 if (gtkblist->drag_timeout) {
3319 g_source_remove(gtkblist->drag_timeout);
3320 gtkblist->drag_timeout = 0;
3321 }
3322
3323 if (gtkblist->mouseover_contact &&
3324 !((e->x > gtkblist->contact_rect.x) && (e->x < (gtkblist->contact_rect.x + gtkblist->contact_rect.width)) &&
3325 (e->y > gtkblist->contact_rect.y) && (e->y < (gtkblist->contact_rect.y + gtkblist->contact_rect.height)))) {
3326 pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
3327 gtkblist->mouseover_contact = NULL;
3328 }
3329 return FALSE;
3330 }
3331
3332 static void
toggle_debug(void)3333 toggle_debug(void)
3334 {
3335 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/debug/enabled",
3336 !purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/enabled"));
3337 }
3338
get_mood_icon_path(const char * mood)3339 static char *get_mood_icon_path(const char *mood)
3340 {
3341 char *path;
3342
3343 if (purple_strequal(mood, "busy")) {
3344 path = g_build_filename(DATADIR, "pixmaps", "pidgin",
3345 "status", "16", "busy.png", NULL);
3346 } else if (purple_strequal(mood, "hiptop")) {
3347 path = g_build_filename(DATADIR, "pixmaps", "pidgin",
3348 "emblems", "16", "hiptop.png", NULL);
3349 } else {
3350 char *filename = g_strdup_printf("%s.png", mood);
3351 path = g_build_filename(DATADIR, "pixmaps", "pidgin",
3352 "emotes", "small", filename, NULL);
3353 g_free(filename);
3354 }
3355 return path;
3356 }
3357
3358 static void
update_status_with_mood(PurpleAccount * account,const gchar * mood,const gchar * text)3359 update_status_with_mood(PurpleAccount *account, const gchar *mood,
3360 const gchar *text)
3361 {
3362 if (mood && *mood) {
3363 if (text) {
3364 purple_account_set_status(account, "mood", TRUE,
3365 PURPLE_MOOD_NAME, mood,
3366 PURPLE_MOOD_COMMENT, text,
3367 NULL);
3368 } else {
3369 purple_account_set_status(account, "mood", TRUE,
3370 PURPLE_MOOD_NAME, mood,
3371 NULL);
3372 }
3373 } else {
3374 purple_account_set_status(account, "mood", FALSE, NULL);
3375 }
3376 }
3377
3378 static void
edit_mood_cb(PurpleConnection * gc,PurpleRequestFields * fields)3379 edit_mood_cb(PurpleConnection *gc, PurpleRequestFields *fields)
3380 {
3381 PurpleRequestField *mood_field;
3382 GList *l;
3383
3384 mood_field = purple_request_fields_get_field(fields, "mood");
3385 l = purple_request_field_list_get_selected(mood_field);
3386
3387 if (l) {
3388 const char *mood = purple_request_field_list_get_data(mood_field, l->data);
3389
3390 if (gc) {
3391 const char *text;
3392 PurpleAccount *account = purple_connection_get_account(gc);
3393
3394 if (gc->flags & PURPLE_CONNECTION_SUPPORT_MOOD_MESSAGES) {
3395 PurpleRequestField *text_field;
3396 text_field = purple_request_fields_get_field(fields, "text");
3397 text = purple_request_field_string_get_value(text_field);
3398 } else {
3399 text = NULL;
3400 }
3401
3402 update_status_with_mood(account, mood, text);
3403 } else {
3404 GList *accounts = purple_accounts_get_all_active();
3405
3406 for (; accounts ; accounts = g_list_delete_link(accounts, accounts)) {
3407 PurpleAccount *account = (PurpleAccount *) accounts->data;
3408 PurpleConnection *gc = purple_account_get_connection(account);
3409
3410 if (gc && gc->flags & PURPLE_CONNECTION_SUPPORT_MOODS) {
3411 update_status_with_mood(account, mood, NULL);
3412 }
3413 }
3414 }
3415 }
3416 }
3417
3418 static void
global_moods_for_each(gpointer key,gpointer value,gpointer user_data)3419 global_moods_for_each(gpointer key, gpointer value, gpointer user_data)
3420 {
3421 GList **out_moods = (GList **) user_data;
3422 PurpleMood *mood = (PurpleMood *) value;
3423
3424 *out_moods = g_list_append(*out_moods, mood);
3425 }
3426
3427 static PurpleMood *
get_global_moods(void)3428 get_global_moods(void)
3429 {
3430 GHashTable *global_moods =
3431 g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
3432 GHashTable *mood_counts =
3433 g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
3434 GList *accounts = purple_accounts_get_all_active();
3435 PurpleMood *result = NULL;
3436 GList *out_moods = NULL;
3437 int i = 0;
3438 int num_accounts = 0;
3439
3440 for (; accounts ; accounts = g_list_delete_link(accounts, accounts)) {
3441 PurpleAccount *account = (PurpleAccount *) accounts->data;
3442 if (purple_account_is_connected(account)) {
3443 PurpleConnection *gc = purple_account_get_connection(account);
3444
3445 if (gc->flags & PURPLE_CONNECTION_SUPPORT_MOODS) {
3446 PurplePluginProtocolInfo *prpl_info =
3447 PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
3448 PurpleMood *mood = NULL;
3449
3450 /* PURPLE_CONNECTION_SUPPORT_MOODS would not be set if the prpl doesn't
3451 * have get_moods, so using PURPLE_PROTOCOL_PLUGIN_HAS_FUNC isn't necessary
3452 * here */
3453 for (mood = prpl_info->get_moods(account) ;
3454 mood->mood != NULL ; mood++) {
3455 int mood_count =
3456 GPOINTER_TO_INT(g_hash_table_lookup(mood_counts, mood->mood));
3457
3458 if (!g_hash_table_lookup(global_moods, mood->mood)) {
3459 g_hash_table_insert(global_moods, (gpointer)mood->mood, mood);
3460 }
3461 g_hash_table_insert(mood_counts, (gpointer)mood->mood,
3462 GINT_TO_POINTER(mood_count + 1));
3463 }
3464
3465 num_accounts++;
3466 }
3467 }
3468 }
3469
3470 g_hash_table_foreach(global_moods, global_moods_for_each, &out_moods);
3471 result = g_new0(PurpleMood, g_hash_table_size(global_moods) + 1);
3472
3473 while (out_moods) {
3474 PurpleMood *mood = (PurpleMood *) out_moods->data;
3475 int in_num_accounts =
3476 GPOINTER_TO_INT(g_hash_table_lookup(mood_counts, mood->mood));
3477
3478 if (in_num_accounts == num_accounts) {
3479 /* mood is present in all accounts supporting moods */
3480 result[i].mood = mood->mood;
3481 result[i].description = mood->description;
3482 i++;
3483 }
3484 out_moods = g_list_delete_link(out_moods, out_moods);
3485 }
3486
3487 g_hash_table_destroy(global_moods);
3488 g_hash_table_destroy(mood_counts);
3489
3490 return result;
3491 }
3492
3493 /* get current set mood for all mood-supporting accounts, or NULL if not set
3494 or not set to the same on all */
3495 static const gchar *
get_global_mood_status(void)3496 get_global_mood_status(void)
3497 {
3498 GList *accounts = purple_accounts_get_all_active();
3499 const gchar *found_mood = NULL;
3500
3501 for (; accounts ; accounts = g_list_delete_link(accounts, accounts)) {
3502 PurpleAccount *account = (PurpleAccount *) accounts->data;
3503
3504 if (purple_account_is_connected(account) &&
3505 (purple_account_get_connection(account)->flags &
3506 PURPLE_CONNECTION_SUPPORT_MOODS)) {
3507 PurplePresence *presence = purple_account_get_presence(account);
3508 PurpleStatus *status = purple_presence_get_status(presence, "mood");
3509 const gchar *curr_mood = purple_status_get_attr_string(status, PURPLE_MOOD_NAME);
3510
3511 if (found_mood != NULL && !purple_strequal(curr_mood, found_mood)) {
3512 /* found a different mood */
3513 found_mood = NULL;
3514 break;
3515 } else {
3516 found_mood = curr_mood;
3517 }
3518 }
3519 }
3520
3521 return found_mood;
3522 }
3523
3524 static void
set_mood_cb(GtkWidget * widget,PurpleAccount * account)3525 set_mood_cb(GtkWidget *widget, PurpleAccount *account)
3526 {
3527 const char *current_mood;
3528 PurpleRequestFields *fields;
3529 PurpleRequestFieldGroup *g;
3530 PurpleRequestField *f;
3531 PurpleConnection *gc = NULL;
3532 PurplePluginProtocolInfo *prpl_info = NULL;
3533 PurpleMood *mood;
3534 PurpleMood *global_moods = get_global_moods();
3535
3536 if (account) {
3537 PurplePresence *presence = purple_account_get_presence(account);
3538 PurpleStatus *status = purple_presence_get_status(presence, "mood");
3539 gc = purple_account_get_connection(account);
3540 g_return_if_fail(gc->prpl != NULL);
3541 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
3542 current_mood = purple_status_get_attr_string(status, PURPLE_MOOD_NAME);
3543 } else {
3544 current_mood = get_global_mood_status();
3545 }
3546
3547 fields = purple_request_fields_new();
3548 g = purple_request_field_group_new(NULL);
3549 f = purple_request_field_list_new("mood", _("Please select your mood from the list"));
3550
3551 purple_request_field_list_add(f, _("None"), "");
3552 if (current_mood == NULL)
3553 purple_request_field_list_add_selected(f, _("None"));
3554
3555 /* TODO: rlaager wants this sorted. */
3556 /* TODO: darkrain wants it sorted post-translation */
3557 if (account && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, get_moods))
3558 mood = prpl_info->get_moods(account);
3559 else
3560 mood = global_moods;
3561 for ( ; mood->mood != NULL ; mood++) {
3562 char *path;
3563
3564 if (mood->mood == NULL || mood->description == NULL)
3565 continue;
3566
3567 path = get_mood_icon_path(mood->mood);
3568 purple_request_field_list_add_icon(f, _(mood->description),
3569 path, (gpointer)mood->mood);
3570 g_free(path);
3571
3572 if (current_mood && purple_strequal(current_mood, mood->mood))
3573 purple_request_field_list_add_selected(f, _(mood->description));
3574 }
3575 purple_request_field_group_add_field(g, f);
3576
3577 purple_request_fields_add_group(fields, g);
3578
3579 /* if the connection allows setting a mood message */
3580 if (gc && (gc->flags & PURPLE_CONNECTION_SUPPORT_MOOD_MESSAGES)) {
3581 g = purple_request_field_group_new(NULL);
3582 f = purple_request_field_string_new("text",
3583 _("Message (optional)"), NULL, FALSE);
3584 purple_request_field_group_add_field(g, f);
3585 purple_request_fields_add_group(fields, g);
3586 }
3587
3588 purple_request_fields(gc, _("Edit User Mood"), _("Edit User Mood"),
3589 NULL, fields,
3590 _("OK"), G_CALLBACK(edit_mood_cb),
3591 _("Cancel"), NULL,
3592 gc ? purple_connection_get_account(gc) : NULL,
3593 NULL, NULL, gc);
3594
3595 g_free(global_moods);
3596 }
3597
3598 static void
set_mood_show(void)3599 set_mood_show(void)
3600 {
3601 set_mood_cb(NULL, NULL);
3602 }
3603
3604 /***************************************************
3605 * Crap *
3606 ***************************************************/
3607 static GtkItemFactoryEntry blist_menu[] =
3608 {
3609 /* NOTE: Do not set any accelerator to Control+O. It is mapped by
3610 gtk_blist_key_press_cb to "Get User Info" on the selected buddy. */
3611
3612 /* Buddies menu */
3613 { N_("/_Buddies"), NULL, NULL, 0, "<Branch>", NULL },
3614 { N_("/Buddies/New Instant _Message..."), "<CTL>M", pidgin_dialogs_im, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW },
3615 { N_("/Buddies/Join a _Chat..."), "<CTL>C", pidgin_blist_joinchat_show, 0, "<StockItem>", PIDGIN_STOCK_CHAT },
3616 { N_("/Buddies/Get User _Info..."), "<CTL>I", pidgin_dialogs_info, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_USER_INFO },
3617 { N_("/Buddies/View User _Log..."), "<CTL>L", pidgin_dialogs_log, 0, "<Item>", NULL },
3618 { "/Buddies/sep1", NULL, NULL, 0, "<Separator>", NULL },
3619 { N_("/Buddies/Sh_ow"), NULL, NULL, 0, "<Branch>", NULL},
3620 { N_("/Buddies/Show/_Offline Buddies"), NULL, pidgin_blist_edit_mode_cb, 1, "<CheckItem>", NULL },
3621 { N_("/Buddies/Show/_Empty Groups"), NULL, pidgin_blist_show_empty_groups_cb, 1, "<CheckItem>", NULL },
3622 { N_("/Buddies/Show/Buddy _Details"), NULL, pidgin_blist_buddy_details_cb, 1, "<CheckItem>", NULL },
3623 { N_("/Buddies/Show/Idle _Times"), NULL, pidgin_blist_show_idle_time_cb, 1, "<CheckItem>", NULL },
3624 { N_("/Buddies/Show/_Protocol Icons"), NULL, pidgin_blist_show_protocol_icons_cb, 1, "<CheckItem>", NULL },
3625 { N_("/Buddies/_Sort Buddies"), NULL, NULL, 0, "<Branch>", NULL },
3626 { "/Buddies/sep2", NULL, NULL, 0, "<Separator>", NULL },
3627 { N_("/Buddies/_Add Buddy..."), "<CTL>B", pidgin_blist_add_buddy_cb, 0, "<StockItem>", GTK_STOCK_ADD },
3628 { N_("/Buddies/Add C_hat..."), NULL, pidgin_blist_add_chat_cb, 0, "<StockItem>", GTK_STOCK_ADD },
3629 { N_("/Buddies/Add _Group..."), NULL, purple_blist_request_add_group, 0, "<StockItem>", GTK_STOCK_ADD },
3630 { "/Buddies/sep3", NULL, NULL, 0, "<Separator>", NULL },
3631 { N_("/Buddies/_Quit"), "<CTL>Q", purple_core_quit, 0, "<StockItem>", GTK_STOCK_QUIT },
3632
3633 /* Accounts menu */
3634 { N_("/_Accounts"), NULL, NULL, 0, "<Branch>", NULL },
3635 { N_("/Accounts/Manage Accounts"), "<CTL>A", pidgin_accounts_window_show, 0, "<Item>", NULL },
3636
3637 /* Tools */
3638 { N_("/_Tools"), NULL, NULL, 0, "<Branch>", NULL },
3639 { N_("/Tools/Buddy _Pounces"), NULL, pidgin_pounces_manager_show, 1, "<Item>", NULL },
3640 { N_("/Tools/_Certificates"), NULL, pidgin_certmgr_show, 0, "<Item>", NULL },
3641 { N_("/Tools/Custom Smile_ys"), "<CTL>Y", pidgin_smiley_manager_show, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_SMILEY },
3642 { N_("/Tools/Plu_gins"), "<CTL>U", pidgin_plugin_dialog_show, 2, "<StockItem>", PIDGIN_STOCK_TOOLBAR_PLUGINS },
3643 { N_("/Tools/Pr_eferences"), "<CTL>P", pidgin_prefs_show, 0, "<StockItem>", GTK_STOCK_PREFERENCES },
3644 { N_("/Tools/Pr_ivacy"), NULL, pidgin_privacy_dialog_show, 0, "<Item>", NULL },
3645 { N_("/Tools/Set _Mood"), "<CTL>D", set_mood_show, 0, "<Item>", NULL },
3646 { "/Tools/sep2", NULL, NULL, 0, "<Separator>", NULL },
3647 { N_("/Tools/_File Transfers"), "<CTL>T", pidgin_xfer_dialog_show, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_TRANSFER },
3648 { N_("/Tools/R_oom List"), NULL, pidgin_roomlist_dialog_show, 0, "<Item>", NULL },
3649 { N_("/Tools/System _Log"), NULL, gtk_blist_show_systemlog_cb, 3, "<Item>", NULL },
3650 { "/Tools/sep3", NULL, NULL, 0, "<Separator>", NULL },
3651 { N_("/Tools/Mute _Sounds"), NULL, pidgin_blist_mute_sounds_cb, 0, "<CheckItem>", NULL },
3652 /* Help */
3653 { N_("/_Help"), NULL, NULL, 0, "<Branch>", NULL },
3654 { N_("/Help/Online _Help"), "F1", gtk_blist_show_onlinehelp_cb, 0, "<StockItem>", GTK_STOCK_HELP },
3655 { N_("/Help/D_onate"), NULL, gtk_blist_donate_cb, 0, "<Item>", NULL },
3656 { "/Help/sep1", NULL, NULL, 0, "<Separator>", NULL },
3657 { N_("/Help/_Build Information"), NULL, pidgin_dialogs_buildinfo, 0, "<Item>", NULL },
3658 { N_("/Help/_Debug Window"), NULL, toggle_debug, 0, "<Item>", NULL },
3659 { N_("/Help/De_veloper Information"), NULL, pidgin_dialogs_developers, 0, "<Item>", NULL },
3660 { N_("/Help/_Plugin Information"), NULL, pidgin_dialogs_plugins_info, 0, "<Item>", NULL },
3661 { N_("/Help/_Translator Information"), NULL, pidgin_dialogs_translators, 0, "<Item>", NULL },
3662 { "/Help/sep2", NULL, NULL, 0, "<Separator>", NULL },
3663 { N_("/Help/_About"), NULL, pidgin_dialogs_about, 4, "<StockItem>", GTK_STOCK_ABOUT },
3664 };
3665
3666 /*********************************************************
3667 * Private Utility functions *
3668 *********************************************************/
3669
pidgin_get_tooltip_text(PurpleBlistNode * node,gboolean full)3670 static char *pidgin_get_tooltip_text(PurpleBlistNode *node, gboolean full)
3671 {
3672 GString *str = g_string_new("");
3673 PurplePlugin *prpl;
3674 PurplePluginProtocolInfo *prpl_info = NULL;
3675 char *tmp;
3676
3677 if (PURPLE_BLIST_NODE_IS_CHAT(node))
3678 {
3679 PurpleChat *chat;
3680 GList *connections;
3681 GList *cur;
3682 struct proto_chat_entry *pce;
3683 char *name, *value;
3684 PurpleConversation *conv;
3685 PidginBlistNode *bnode = node->ui_data;
3686
3687 chat = (PurpleChat *)node;
3688 prpl = purple_find_prpl(purple_account_get_protocol_id(chat->account));
3689 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
3690
3691 connections = purple_connections_get_all();
3692 if (connections && connections->next)
3693 {
3694 tmp = g_markup_escape_text(chat->account->username, -1);
3695 g_string_append_printf(str, _("<b>Account:</b> %s"), tmp);
3696 g_free(tmp);
3697 }
3698
3699 if (bnode && bnode->conv.conv) {
3700 conv = bnode->conv.conv;
3701 } else {
3702 char *chat_name;
3703 if (prpl_info && prpl_info->get_chat_name)
3704 chat_name = prpl_info->get_chat_name(chat->components);
3705 else
3706 chat_name = g_strdup(purple_chat_get_name(chat));
3707
3708 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, chat_name,
3709 chat->account);
3710 g_free(chat_name);
3711 }
3712
3713 if (conv && !purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv))) {
3714 g_string_append_printf(str, _("\n<b>Occupants:</b> %d"),
3715 g_list_length(purple_conv_chat_get_users(PURPLE_CONV_CHAT(conv))));
3716
3717 if (prpl_info && (prpl_info->options & OPT_PROTO_CHAT_TOPIC)) {
3718 const char *chattopic = purple_conv_chat_get_topic(PURPLE_CONV_CHAT(conv));
3719 char *topic = chattopic ? g_markup_escape_text(chattopic, -1) : NULL;
3720 g_string_append_printf(str, _("\n<b>Topic:</b> %s"), topic ? topic : _("(no topic set)"));
3721 g_free(topic);
3722 }
3723 }
3724
3725 if (prpl_info && prpl_info->chat_info != NULL)
3726 cur = prpl_info->chat_info(chat->account->gc);
3727 else
3728 cur = NULL;
3729
3730 while (cur != NULL)
3731 {
3732 pce = cur->data;
3733
3734 if (!pce->secret && (!pce->required &&
3735 g_hash_table_lookup(chat->components, pce->identifier) == NULL))
3736 {
3737 tmp = purple_text_strip_mnemonic(pce->label);
3738 name = g_markup_escape_text(tmp, -1);
3739 g_free(tmp);
3740 value = g_markup_escape_text(g_hash_table_lookup(
3741 chat->components, pce->identifier), -1);
3742 g_string_append_printf(str, "\n<b>%s</b> %s",
3743 name ? name : "",
3744 value ? value : "");
3745 g_free(name);
3746 g_free(value);
3747 }
3748
3749 g_free(pce);
3750 cur = g_list_delete_link(cur, cur);
3751 }
3752 }
3753 else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node))
3754 {
3755 /* NOTE: THIS FUNCTION IS NO LONGER CALLED FOR CONTACTS.
3756 * It is only called by create_tip_for_node(), and create_tip_for_node() is never called for a contact.
3757 */
3758 PurpleContact *c;
3759 PurpleBuddy *b;
3760 PurplePresence *presence;
3761 PurpleNotifyUserInfo *user_info;
3762 GList *connections;
3763 char *tmp;
3764 time_t idle_secs, signon;
3765
3766 if (PURPLE_BLIST_NODE_IS_CONTACT(node))
3767 {
3768 c = (PurpleContact *)node;
3769 b = purple_contact_get_priority_buddy(c);
3770 }
3771 else
3772 {
3773 b = (PurpleBuddy *)node;
3774 c = purple_buddy_get_contact(b);
3775 }
3776
3777 prpl = purple_find_prpl(purple_account_get_protocol_id(b->account));
3778 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
3779
3780 presence = purple_buddy_get_presence(b);
3781 user_info = purple_notify_user_info_new();
3782
3783 /* Account */
3784 connections = purple_connections_get_all();
3785 if (full && connections && connections->next)
3786 {
3787 tmp = g_markup_escape_text(purple_account_get_username(
3788 purple_buddy_get_account(b)), -1);
3789 purple_notify_user_info_add_pair(user_info, _("Account"), tmp);
3790 g_free(tmp);
3791 }
3792
3793 /* Alias */
3794 /* If there's not a contact alias, the node is being displayed with
3795 * this alias, so there's no point in showing it in the tooltip. */
3796 if (full && c && b->alias != NULL && b->alias[0] != '\0' &&
3797 (c->alias != NULL && c->alias[0] != '\0') &&
3798 !purple_strequal(c->alias, b->alias))
3799 {
3800 tmp = g_markup_escape_text(b->alias, -1);
3801 purple_notify_user_info_add_pair(user_info, _("Buddy Alias"), tmp);
3802 g_free(tmp);
3803 }
3804
3805 /* Nickname/Server Alias */
3806 /* I'd like to only show this if there's a contact or buddy
3807 * alias, but people often set long nicknames, which
3808 * get ellipsized, so the only way to see the whole thing is
3809 * to look at the tooltip. */
3810 if (full && b->server_alias != NULL && b->server_alias[0] != '\0')
3811 {
3812 tmp = g_markup_escape_text(b->server_alias, -1);
3813 purple_notify_user_info_add_pair(user_info, _("Nickname"), tmp);
3814 g_free(tmp);
3815 }
3816
3817 /* Logged In */
3818 signon = purple_presence_get_login_time(presence);
3819 if (full && PURPLE_BUDDY_IS_ONLINE(b) && signon > 0)
3820 {
3821 if (signon > time(NULL)) {
3822 /*
3823 * They signed on in the future?! Our local clock
3824 * must be wrong, show the actual date instead of
3825 * "4 days", etc.
3826 */
3827 tmp = g_strdup(purple_date_format_long(localtime(&signon)));
3828 } else
3829 tmp = purple_str_seconds_to_string(time(NULL) - signon);
3830 purple_notify_user_info_add_pair(user_info, _("Logged In"), tmp);
3831 g_free(tmp);
3832 }
3833
3834 /* Idle */
3835 if (purple_presence_is_idle(presence))
3836 {
3837 idle_secs = purple_presence_get_idle_time(presence);
3838 if (idle_secs > 0)
3839 {
3840 tmp = purple_str_seconds_to_string(time(NULL) - idle_secs);
3841 purple_notify_user_info_add_pair(user_info, _("Idle"), tmp);
3842 g_free(tmp);
3843 }
3844 }
3845
3846 /* Last Seen */
3847 if (full && c && !PURPLE_BUDDY_IS_ONLINE(b))
3848 {
3849 struct _pidgin_blist_node *gtknode = ((PurpleBlistNode *)c)->ui_data;
3850 PurpleBlistNode *bnode;
3851 int lastseen = 0;
3852
3853 if (gtknode && (!gtknode->contact_expanded || PURPLE_BLIST_NODE_IS_CONTACT(node)))
3854 {
3855 /* We're either looking at a buddy for a collapsed contact or
3856 * an expanded contact itself so we show the most recent
3857 * (largest) last_seen time for any of the buddies under
3858 * the contact. */
3859 for (bnode = ((PurpleBlistNode *)c)->child ; bnode != NULL ; bnode = bnode->next)
3860 {
3861 int value = purple_blist_node_get_int(bnode, "last_seen");
3862 if (value > lastseen)
3863 lastseen = value;
3864 }
3865 }
3866 else
3867 {
3868 /* We're dealing with a buddy under an expanded contact,
3869 * so we show the last_seen time for the buddy. */
3870 lastseen = purple_blist_node_get_int(&b->node, "last_seen");
3871 }
3872
3873 if (lastseen > 0)
3874 {
3875 tmp = purple_str_seconds_to_string(time(NULL) - lastseen);
3876 purple_notify_user_info_add_pair(user_info, _("Last Seen"), tmp);
3877 g_free(tmp);
3878 }
3879 }
3880
3881
3882 /* Offline? */
3883 /* FIXME: Why is this status special-cased by the core? --rlaager
3884 * FIXME: Alternatively, why not have the core do all of them? --rlaager */
3885 if (!PURPLE_BUDDY_IS_ONLINE(b)) {
3886 purple_notify_user_info_add_pair(user_info, _("Status"), _("Offline"));
3887 }
3888
3889 if (purple_account_is_connected(b->account) &&
3890 prpl_info && prpl_info->tooltip_text)
3891 {
3892 /* Additional text from the PRPL */
3893 prpl_info->tooltip_text(b, user_info, full);
3894 }
3895
3896 /* These are Easter Eggs. Patches to remove them will be rejected. */
3897 if (!g_ascii_strcasecmp(b->name, "robflynn"))
3898 purple_notify_user_info_add_pair(user_info, _("Description"), _("Spooky"));
3899 if (!g_ascii_strcasecmp(b->name, "seanegn"))
3900 purple_notify_user_info_add_pair(user_info, _("Status"), _("Awesome"));
3901 if (!g_ascii_strcasecmp(b->name, "chipx86"))
3902 purple_notify_user_info_add_pair(user_info, _("Status"), _("Rockin'"));
3903
3904 tmp = purple_notify_user_info_get_text_with_newline(user_info, "\n");
3905 g_string_append(str, tmp);
3906 g_free(tmp);
3907
3908 purple_notify_user_info_destroy(user_info);
3909 } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
3910 gint count;
3911 PurpleGroup *group = (PurpleGroup*)node;
3912 PurpleNotifyUserInfo *user_info;
3913
3914 user_info = purple_notify_user_info_new();
3915
3916 count = purple_blist_get_group_online_count(group);
3917
3918 if (count != 0) {
3919 /* Online buddies in group */
3920 tmp = g_strdup_printf("%d", count);
3921 purple_notify_user_info_add_pair(user_info,
3922 _("Online Buddies"),
3923 tmp);
3924 g_free(tmp);
3925 }
3926
3927 count = purple_blist_get_group_size(group, FALSE);
3928 if (count != 0) {
3929 /* Total buddies (from online accounts) in group */
3930 tmp = g_strdup_printf("%d", count);
3931 purple_notify_user_info_add_pair(user_info,
3932 _("Total Buddies"),
3933 tmp);
3934 g_free(tmp);
3935 }
3936
3937 tmp = purple_notify_user_info_get_text_with_newline(user_info, "\n");
3938 g_string_append(str, tmp);
3939 g_free(tmp);
3940
3941 purple_notify_user_info_destroy(user_info);
3942 }
3943
3944 purple_signal_emit(pidgin_blist_get_handle(), "drawing-tooltip",
3945 node, str, full);
3946
3947 return g_string_free(str, FALSE);
3948 }
3949
3950 static GHashTable *cached_emblems;
3951
_cleanup_cached_emblem(gpointer data,GObject * obj)3952 static void _cleanup_cached_emblem(gpointer data, GObject *obj) {
3953 g_hash_table_remove(cached_emblems, data);
3954 }
3955
_pidgin_blist_get_cached_emblem(gchar * path)3956 static GdkPixbuf * _pidgin_blist_get_cached_emblem(gchar *path) {
3957 GdkPixbuf *pb = g_hash_table_lookup(cached_emblems, path);
3958
3959 if (pb != NULL) {
3960 /* The caller gets a reference */
3961 g_object_ref(pb);
3962 g_free(path);
3963 } else {
3964 pb = pidgin_pixbuf_new_from_file(path);
3965 if (pb != NULL) {
3966 /* We don't want to own a ref to the pixbuf, but we need to keep clean up. */
3967 /* I'm not sure if it would be better to just keep our ref and not let the emblem ever be destroyed */
3968 g_object_weak_ref(G_OBJECT(pb), _cleanup_cached_emblem, path);
3969 g_hash_table_insert(cached_emblems, path, pb);
3970 } else
3971 g_free(path);
3972 }
3973
3974 return pb;
3975 }
3976
3977 GdkPixbuf *
pidgin_blist_get_emblem(PurpleBlistNode * node)3978 pidgin_blist_get_emblem(PurpleBlistNode *node)
3979 {
3980 PurpleBuddy *buddy = NULL;
3981 struct _pidgin_blist_node *gtknode = node->ui_data;
3982 PurplePlugin *prpl;
3983 PurplePluginProtocolInfo *prpl_info;
3984 const char *name = NULL;
3985 char *filename, *path;
3986 PurplePresence *p = NULL;
3987 PurpleStatus *tune;
3988
3989 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
3990 if(!gtknode->contact_expanded) {
3991 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
3992 }
3993 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
3994 buddy = (PurpleBuddy*)node;
3995 p = purple_buddy_get_presence(buddy);
3996 if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_MOBILE)) {
3997 /* This emblem comes from the small emoticon set now,
3998 * to reduce duplication. */
3999 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emotes",
4000 "small", "mobile.png", NULL);
4001 return _pidgin_blist_get_cached_emblem(path);
4002 }
4003
4004 if (((struct _pidgin_blist_node*)(node->parent->ui_data))->contact_expanded) {
4005 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"))
4006 return NULL;
4007 return pidgin_create_prpl_icon(((PurpleBuddy*)node)->account, PIDGIN_PRPL_ICON_SMALL);
4008 }
4009 } else {
4010 return NULL;
4011 }
4012
4013 g_return_val_if_fail(buddy != NULL, NULL);
4014
4015 if (!purple_privacy_check(buddy->account, purple_buddy_get_name(buddy))) {
4016 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", "blocked.png", NULL);
4017 return _pidgin_blist_get_cached_emblem(path);
4018 }
4019
4020 /* If we came through the contact code flow above, we didn't need
4021 * to get the presence until now. */
4022 if (p == NULL)
4023 p = purple_buddy_get_presence(buddy);
4024
4025 if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_MOBILE)) {
4026 /* This emblem comes from the small emoticon set now, to reduce duplication. */
4027 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emotes", "small", "mobile.png", NULL);
4028 return _pidgin_blist_get_cached_emblem(path);
4029 }
4030
4031 tune = purple_presence_get_status(p, "tune");
4032 if (tune && purple_status_is_active(tune)) {
4033 /* TODO: Replace "Tune" with generalized "Media" in 3.0. */
4034 if (purple_status_get_attr_string(tune, "game") != NULL) {
4035 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", "game.png", NULL);
4036 return _pidgin_blist_get_cached_emblem(path);
4037 }
4038 /* TODO: Replace "Tune" with generalized "Media" in 3.0. */
4039 if (purple_status_get_attr_string(tune, "office") != NULL) {
4040 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", "office.png", NULL);
4041 return _pidgin_blist_get_cached_emblem(path);
4042 }
4043 /* Regular old "tune" is the only one in all protocols. */
4044 /* This emblem comes from the small emoticon set now, to reduce duplication. */
4045 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emotes", "small", "music.png", NULL);
4046 return _pidgin_blist_get_cached_emblem(path);
4047 }
4048
4049 prpl = purple_find_prpl(purple_account_get_protocol_id(buddy->account));
4050 if (!prpl)
4051 return NULL;
4052
4053 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
4054 if (prpl_info && prpl_info->list_emblem)
4055 name = prpl_info->list_emblem(buddy);
4056
4057 if (name == NULL) {
4058 PurpleStatus *status;
4059
4060 if (!purple_presence_is_status_primitive_active(p, PURPLE_STATUS_MOOD))
4061 return NULL;
4062
4063 status = purple_presence_get_status(p, "mood");
4064 name = purple_status_get_attr_string(status, PURPLE_MOOD_NAME);
4065
4066 if (!(name && *name))
4067 return NULL;
4068
4069 path = get_mood_icon_path(name);
4070 } else {
4071 filename = g_strdup_printf("%s.png", name);
4072 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", filename, NULL);
4073 g_free(filename);
4074 }
4075
4076 /* _pidgin_blist_get_cached_emblem() assumes ownership of path */
4077 return _pidgin_blist_get_cached_emblem(path);
4078 }
4079
4080
4081 GdkPixbuf *
pidgin_blist_get_status_icon(PurpleBlistNode * node,PidginStatusIconSize size)4082 pidgin_blist_get_status_icon(PurpleBlistNode *node, PidginStatusIconSize size)
4083 {
4084 GdkPixbuf *ret;
4085 const char *icon = NULL;
4086 struct _pidgin_blist_node *gtknode = node->ui_data;
4087 struct _pidgin_blist_node *gtkbuddynode = NULL;
4088 PurpleBuddy *buddy = NULL;
4089 PurpleChat *chat = NULL;
4090 GtkIconSize icon_size = gtk_icon_size_from_name((size == PIDGIN_STATUS_ICON_LARGE) ? PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL :
4091 PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC);
4092
4093 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
4094 if(!gtknode->contact_expanded) {
4095 buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
4096 if (buddy != NULL)
4097 gtkbuddynode = ((PurpleBlistNode*)buddy)->ui_data;
4098 }
4099 } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
4100 buddy = (PurpleBuddy*)node;
4101 gtkbuddynode = node->ui_data;
4102 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
4103 chat = (PurpleChat*)node;
4104 } else {
4105 return NULL;
4106 }
4107
4108 if(buddy || chat) {
4109 PurpleAccount *account;
4110 PurplePlugin *prpl;
4111
4112 if(buddy)
4113 account = buddy->account;
4114 else
4115 account = chat->account;
4116
4117 prpl = purple_find_prpl(purple_account_get_protocol_id(account));
4118 if(!prpl)
4119 return NULL;
4120 }
4121
4122 if(buddy) {
4123 PurpleConversation *conv = find_conversation_with_buddy(buddy);
4124 PurplePresence *p;
4125 gboolean trans;
4126
4127 if(conv != NULL) {
4128 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
4129 if (gtkconv == NULL && size == PIDGIN_STATUS_ICON_SMALL) {
4130 PidginBlistNode *ui = buddy->node.ui_data;
4131 if (ui == NULL || (ui->conv.flags & PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE))
4132 return gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview),
4133 PIDGIN_STOCK_STATUS_MESSAGE, icon_size, "GtkTreeView");
4134 }
4135 }
4136
4137 p = purple_buddy_get_presence(buddy);
4138 trans = purple_presence_is_idle(p);
4139
4140 if (PURPLE_BUDDY_IS_ONLINE(buddy) && gtkbuddynode && gtkbuddynode->recent_signonoff)
4141 icon = PIDGIN_STOCK_STATUS_LOGIN;
4142 else if (gtkbuddynode && gtkbuddynode->recent_signonoff)
4143 icon = PIDGIN_STOCK_STATUS_LOGOUT;
4144 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_UNAVAILABLE))
4145 if (trans)
4146 icon = PIDGIN_STOCK_STATUS_BUSY_I;
4147 else
4148 icon = PIDGIN_STOCK_STATUS_BUSY;
4149 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_AWAY))
4150 if (trans)
4151 icon = PIDGIN_STOCK_STATUS_AWAY_I;
4152 else
4153 icon = PIDGIN_STOCK_STATUS_AWAY;
4154 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_EXTENDED_AWAY))
4155 if (trans)
4156 icon = PIDGIN_STOCK_STATUS_XA_I;
4157 else
4158 icon = PIDGIN_STOCK_STATUS_XA;
4159 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_OFFLINE))
4160 icon = PIDGIN_STOCK_STATUS_OFFLINE;
4161 else if (trans)
4162 icon = PIDGIN_STOCK_STATUS_AVAILABLE_I;
4163 else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_INVISIBLE))
4164 icon = PIDGIN_STOCK_STATUS_INVISIBLE;
4165 else
4166 icon = PIDGIN_STOCK_STATUS_AVAILABLE;
4167 } else if (chat) {
4168 icon = PIDGIN_STOCK_STATUS_CHAT;
4169 } else {
4170 icon = PIDGIN_STOCK_STATUS_PERSON;
4171 }
4172
4173 ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), icon,
4174 icon_size, "GtkTreeView");
4175 return ret;
4176 }
4177
4178 static const char *
theme_font_get_color_default(PidginThemeFont * font,const char * def)4179 theme_font_get_color_default(PidginThemeFont *font, const char *def)
4180 {
4181 const char *ret;
4182 if (!font || !(ret = pidgin_theme_font_get_color_describe(font)))
4183 ret = def;
4184 return ret;
4185 }
4186
4187 static const char *
theme_font_get_face_default(PidginThemeFont * font,const char * def)4188 theme_font_get_face_default(PidginThemeFont *font, const char *def)
4189 {
4190 const char *ret;
4191 if (!font || !(ret = pidgin_theme_font_get_font_face(font)))
4192 ret = def;
4193 return ret;
4194 }
4195
4196 gchar *
pidgin_blist_get_name_markup(PurpleBuddy * b,gboolean selected,gboolean aliased)4197 pidgin_blist_get_name_markup(PurpleBuddy *b, gboolean selected, gboolean aliased)
4198 {
4199 const char *name, *name_color, *name_font, *status_color, *status_font, *dim_grey;
4200 char *text = NULL;
4201 PurplePlugin *prpl;
4202 PurplePluginProtocolInfo *prpl_info = NULL;
4203 PurpleContact *contact;
4204 PurplePresence *presence;
4205 struct _pidgin_blist_node *gtkcontactnode = NULL;
4206 char *idletime = NULL, *statustext = NULL, *nametext = NULL;
4207 PurpleConversation *conv = find_conversation_with_buddy(b);
4208 gboolean hidden_conv = FALSE;
4209 gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
4210 PidginThemeFont *statusfont = NULL, *namefont = NULL;
4211 PidginBlistTheme *theme;
4212
4213 if (conv != NULL) {
4214 PidginBlistNode *ui = b->node.ui_data;
4215 if (ui) {
4216 if (ui->conv.flags & PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE)
4217 hidden_conv = TRUE;
4218 } else {
4219 if (PIDGIN_CONVERSATION(conv) == NULL)
4220 hidden_conv = TRUE;
4221 }
4222 }
4223
4224 /* XXX Good luck cleaning up this crap */
4225 contact = PURPLE_CONTACT(PURPLE_BLIST_NODE(b)->parent);
4226 if(contact)
4227 gtkcontactnode = purple_blist_node_get_ui_data(PURPLE_BLIST_NODE(contact));
4228
4229 /* Name */
4230 if (gtkcontactnode && !gtkcontactnode->contact_expanded && contact->alias)
4231 name = contact->alias;
4232 else
4233 name = purple_buddy_get_alias(b);
4234
4235 /* Raise a contact pre-draw signal here. THe callback will return an
4236 * escaped version of the name. */
4237 nametext = purple_signal_emit_return_1(pidgin_blist_get_handle(), "drawing-buddy", b);
4238
4239 if(!nametext)
4240 nametext = g_markup_escape_text(name, strlen(name));
4241
4242 presence = purple_buddy_get_presence(b);
4243
4244 /* Name is all that is needed */
4245 if (!aliased || biglist) {
4246
4247 /* Status Info */
4248 prpl = purple_find_prpl(purple_account_get_protocol_id(b->account));
4249
4250 if (prpl != NULL)
4251 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
4252
4253 if (prpl_info && prpl_info->status_text && b->account->gc) {
4254 char *tmp = prpl_info->status_text(b);
4255 const char *end;
4256
4257 if(tmp && !g_utf8_validate(tmp, -1, &end)) {
4258 char *new = g_strndup(tmp,
4259 g_utf8_pointer_to_offset(tmp, end));
4260 g_free(tmp);
4261 tmp = new;
4262 }
4263 if(tmp) {
4264 g_strdelimit(tmp, "\n", ' ');
4265 purple_str_strip_char(tmp, '\r');
4266 }
4267 statustext = tmp;
4268 }
4269
4270 if(!purple_presence_is_online(presence) && !statustext)
4271 statustext = g_strdup(_("Offline"));
4272
4273 /* Idle Text */
4274 if (purple_presence_is_idle(presence) && purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time")) {
4275 time_t idle_secs = purple_presence_get_idle_time(presence);
4276
4277 if (idle_secs > 0) {
4278 int iday, ihrs, imin;
4279 time_t t;
4280
4281 time(&t);
4282 iday = (t - idle_secs) / (24 * 60 * 60);
4283 ihrs = ((t - idle_secs) / 60 / 60) % 24;
4284 imin = ((t - idle_secs) / 60) % 60;
4285
4286 if (iday)
4287 idletime = g_strdup_printf(_("Idle %dd %dh %02dm"), iday, ihrs, imin);
4288 else if (ihrs)
4289 idletime = g_strdup_printf(_("Idle %dh %02dm"), ihrs, imin);
4290 else
4291 idletime = g_strdup_printf(_("Idle %dm"), imin);
4292
4293 } else
4294 idletime = g_strdup(_("Idle"));
4295 }
4296 }
4297
4298 /* choose the colors of the text */
4299 theme = pidgin_blist_get_theme();
4300 name_color = NULL;
4301
4302 dim_grey = pidgin_style_is_dark(NULL) ? "light slate grey" : "dim grey";
4303
4304 if (theme) {
4305 if (purple_presence_is_idle(presence)) {
4306 namefont = statusfont = pidgin_blist_theme_get_idle_text_info(theme);
4307 name_color = dim_grey;
4308 } else if (!purple_presence_is_online(presence)) {
4309 namefont = pidgin_blist_theme_get_offline_text_info(theme);
4310 name_color = dim_grey;
4311 statusfont = pidgin_blist_theme_get_status_text_info(theme);
4312 } else if (purple_presence_is_available(presence)) {
4313 namefont = pidgin_blist_theme_get_online_text_info(theme);
4314 statusfont = pidgin_blist_theme_get_status_text_info(theme);
4315 } else {
4316 namefont = pidgin_blist_theme_get_away_text_info(theme);
4317 statusfont = pidgin_blist_theme_get_status_text_info(theme);
4318 }
4319 } else {
4320 if (!selected
4321 && (purple_presence_is_idle(presence)
4322 || !purple_presence_is_online(presence)))
4323 {
4324 name_color = dim_grey;
4325 }
4326 }
4327
4328 name_color = theme_font_get_color_default(namefont, name_color);
4329 name_font = theme_font_get_face_default(namefont, "");
4330
4331 status_color = theme_font_get_color_default(statusfont, dim_grey);
4332 status_font = theme_font_get_face_default(statusfont, "");
4333
4334 if (aliased && selected) {
4335 if (theme) {
4336 name_color = "black";
4337 status_color = "black";
4338 } else {
4339 name_color = NULL;
4340 status_color = NULL;
4341 }
4342 }
4343
4344 if (hidden_conv) {
4345 char *tmp = nametext;
4346 nametext = g_strdup_printf("<b>%s</b>", tmp);
4347 g_free(tmp);
4348 }
4349
4350 /* Put it all together */
4351 if ((!aliased || biglist) && (statustext || idletime)) {
4352 /* using <span size='smaller'> breaks the status, so it must be seperated into <small><span>*/
4353 if (name_color) {
4354 text = g_strdup_printf("<span font_desc='%s' foreground='%s'>%s</span>\n"
4355 "<small><span font_desc='%s' foreground='%s'>%s%s%s</span></small>",
4356 name_font, name_color, nametext, status_font, status_color,
4357 idletime != NULL ? idletime : "",
4358 (idletime != NULL && statustext != NULL) ? " - " : "",
4359 statustext != NULL ? statustext : "");
4360 } else if (status_color) {
4361 text = g_strdup_printf("<span font_desc='%s'>%s</span>\n"
4362 "<small><span font_desc='%s' foreground='%s'>%s%s%s</span></small>",
4363 name_font, nametext, status_font, status_color,
4364 idletime != NULL ? idletime : "",
4365 (idletime != NULL && statustext != NULL) ? " - " : "",
4366 statustext != NULL ? statustext : "");
4367 } else {
4368 text = g_strdup_printf("<span font_desc='%s'>%s</span>\n"
4369 "<small><span font_desc='%s'>%s%s%s</span></small>",
4370 name_font, nametext, status_font,
4371 idletime != NULL ? idletime : "",
4372 (idletime != NULL && statustext != NULL) ? " - " : "",
4373 statustext != NULL ? statustext : "");
4374 }
4375 } else {
4376 if (name_color) {
4377 text = g_strdup_printf("<span font_desc='%s' color='%s'>%s</span>",
4378 name_font, name_color, nametext);
4379 } else {
4380 text = g_strdup_printf("<span font_desc='%s'>%s</span>", name_font,
4381 nametext);
4382 }
4383 }
4384 g_free(nametext);
4385 g_free(statustext);
4386 g_free(idletime);
4387
4388 if (hidden_conv) {
4389 char *tmp = text;
4390 text = g_strdup_printf("<b>%s</b>", tmp);
4391 g_free(tmp);
4392 }
4393
4394 return text;
4395 }
4396
pidgin_blist_restore_position(void)4397 static void pidgin_blist_restore_position(void)
4398 {
4399 int blist_x, blist_y, blist_width, blist_height;
4400
4401 blist_width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width");
4402
4403 /* if the window exists, is hidden, we're saving positions, and the
4404 * position is sane... */
4405 if (gtkblist && gtkblist->window &&
4406 !GTK_WIDGET_VISIBLE(gtkblist->window) && blist_width != 0) {
4407
4408 blist_x = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/x");
4409 blist_y = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/y");
4410 blist_height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/height");
4411
4412 /* ...check position is on screen... */
4413 if (blist_x >= gdk_screen_width())
4414 blist_x = gdk_screen_width() - 100;
4415 else if (blist_x + blist_width < 0)
4416 blist_x = 100;
4417
4418 if (blist_y >= gdk_screen_height())
4419 blist_y = gdk_screen_height() - 100;
4420 else if (blist_y + blist_height < 0)
4421 blist_y = 100;
4422
4423 /* ...and move it back. */
4424 gtk_window_move(GTK_WINDOW(gtkblist->window), blist_x, blist_y);
4425 gtk_window_resize(GTK_WINDOW(gtkblist->window), blist_width, blist_height);
4426 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized"))
4427 gtk_window_maximize(GTK_WINDOW(gtkblist->window));
4428 }
4429 }
4430
pidgin_blist_refresh_timer(PurpleBuddyList * list)4431 static gboolean pidgin_blist_refresh_timer(PurpleBuddyList *list)
4432 {
4433 PurpleBlistNode *gnode, *cnode;
4434
4435 if (gtk_blist_visibility == GDK_VISIBILITY_FULLY_OBSCURED
4436 || !GTK_WIDGET_VISIBLE(gtkblist->window))
4437 return TRUE;
4438
4439 for(gnode = list->root; gnode; gnode = gnode->next) {
4440 if(!PURPLE_BLIST_NODE_IS_GROUP(gnode))
4441 continue;
4442 for(cnode = gnode->child; cnode; cnode = cnode->next) {
4443 if(PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
4444 PurpleBuddy *buddy;
4445
4446 buddy = purple_contact_get_priority_buddy((PurpleContact*)cnode);
4447
4448 if (buddy &&
4449 purple_presence_is_idle(purple_buddy_get_presence(buddy)))
4450 pidgin_blist_update_contact(list, (PurpleBlistNode*)buddy);
4451 }
4452 }
4453 }
4454
4455 /* keep on going */
4456 return TRUE;
4457 }
4458
pidgin_blist_hide_node(PurpleBuddyList * list,PurpleBlistNode * node,gboolean update)4459 static void pidgin_blist_hide_node(PurpleBuddyList *list, PurpleBlistNode *node, gboolean update)
4460 {
4461 struct _pidgin_blist_node *gtknode = (struct _pidgin_blist_node *)node->ui_data;
4462 GtkTreeIter iter;
4463
4464 if (!gtknode || !gtknode->row || !gtkblist)
4465 return;
4466
4467 if(gtkblist->selected_node == node)
4468 gtkblist->selected_node = NULL;
4469 if (get_iter_from_node(node, &iter)) {
4470 gtk_tree_store_remove(gtkblist->treemodel, &iter);
4471 if(update && (PURPLE_BLIST_NODE_IS_CONTACT(node) ||
4472 PURPLE_BLIST_NODE_IS_BUDDY(node) || PURPLE_BLIST_NODE_IS_CHAT(node))) {
4473 pidgin_blist_update(list, node->parent);
4474 }
4475 }
4476 gtk_tree_row_reference_free(gtknode->row);
4477 gtknode->row = NULL;
4478 }
4479
4480 static const char *require_connection[] =
4481 {
4482 N_("/Buddies/New Instant Message..."),
4483 N_("/Buddies/Join a Chat..."),
4484 N_("/Buddies/Get User Info..."),
4485 N_("/Buddies/Add Buddy..."),
4486 N_("/Buddies/Add Chat..."),
4487 N_("/Buddies/Add Group..."),
4488 };
4489
4490 static const int require_connection_size = sizeof(require_connection)
4491 / sizeof(*require_connection);
4492
4493 /**
4494 * Rebuild dynamic menus and make menu items sensitive/insensitive
4495 * where appropriate.
4496 */
4497 static void
update_menu_bar(PidginBuddyList * gtkblist)4498 update_menu_bar(PidginBuddyList *gtkblist)
4499 {
4500 GtkWidget *widget;
4501 gboolean sensitive;
4502 int i;
4503
4504 g_return_if_fail(gtkblist != NULL);
4505
4506 pidgin_blist_update_accounts_menu();
4507
4508 sensitive = (purple_connections_get_all() != NULL);
4509
4510 for (i = 0; i < require_connection_size; i++)
4511 {
4512 widget = gtk_item_factory_get_widget(gtkblist->ift, require_connection[i]);
4513 gtk_widget_set_sensitive(widget, sensitive);
4514 }
4515
4516 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Buddies/Join a Chat..."));
4517 gtk_widget_set_sensitive(widget, pidgin_blist_joinchat_is_showable());
4518
4519 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Buddies/Add Chat..."));
4520 gtk_widget_set_sensitive(widget, pidgin_blist_joinchat_is_showable());
4521
4522 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Privacy"));
4523 gtk_widget_set_sensitive(widget, sensitive);
4524
4525 widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Room List"));
4526 gtk_widget_set_sensitive(widget, pidgin_roomlist_is_showable());
4527 }
4528
4529 static void
sign_on_off_cb(PurpleConnection * gc,PurpleBuddyList * blist)4530 sign_on_off_cb(PurpleConnection *gc, PurpleBuddyList *blist)
4531 {
4532 PidginBuddyList *gtkblist = PIDGIN_BLIST(blist);
4533
4534 update_menu_bar(gtkblist);
4535 }
4536
4537 static void
plugin_changed_cb(PurplePlugin * p,gpointer data)4538 plugin_changed_cb(PurplePlugin *p, gpointer data)
4539 {
4540 pidgin_blist_update_plugin_actions();
4541 }
4542
4543 static void
unseen_conv_menu(void)4544 unseen_conv_menu(void)
4545 {
4546 static GtkWidget *menu = NULL;
4547 GList *convs = NULL;
4548 GList *chats, *ims;
4549
4550 if (menu) {
4551 gtk_widget_destroy(menu);
4552 menu = NULL;
4553 }
4554
4555 ims = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_IM,
4556 PIDGIN_UNSEEN_TEXT, FALSE, 0);
4557
4558 chats = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_CHAT,
4559 PIDGIN_UNSEEN_NICK, FALSE, 0);
4560
4561 if(ims && chats)
4562 convs = g_list_concat(ims, chats);
4563 else if(ims && !chats)
4564 convs = ims;
4565 else if(!ims && chats)
4566 convs = chats;
4567
4568 if (!convs)
4569 /* no conversations added, don't show the menu */
4570 return;
4571
4572 menu = gtk_menu_new();
4573
4574 pidgin_conversations_fill_menu(menu, convs);
4575 g_list_free(convs);
4576 gtk_widget_show_all(menu);
4577 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3,
4578 gtk_get_current_event_time());
4579 }
4580
4581 static gboolean
menutray_press_cb(GtkWidget * widget,GdkEventButton * event)4582 menutray_press_cb(GtkWidget *widget, GdkEventButton *event)
4583 {
4584 GList *convs;
4585
4586 switch (event->button) {
4587 case 1:
4588 convs = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_IM,
4589 PIDGIN_UNSEEN_TEXT, FALSE, 1);
4590
4591 if(!convs)
4592 convs = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_CHAT,
4593 PIDGIN_UNSEEN_NICK, FALSE, 1);
4594 if (convs) {
4595 pidgin_conv_present_conversation((PurpleConversation*)convs->data);
4596 g_list_free(convs);
4597 }
4598 break;
4599 case 3:
4600 unseen_conv_menu();
4601 break;
4602 }
4603 return TRUE;
4604 }
4605
4606 static void
conversation_updated_cb(PurpleConversation * conv,PurpleConvUpdateType type,PidginBuddyList * gtkblist)4607 conversation_updated_cb(PurpleConversation *conv, PurpleConvUpdateType type,
4608 PidginBuddyList *gtkblist)
4609 {
4610 GList *convs = NULL;
4611 GList *ims, *chats;
4612 GList *l = NULL;
4613
4614 if (type != PURPLE_CONV_UPDATE_UNSEEN)
4615 return;
4616
4617 if(conv->account != NULL && conv->name != NULL) {
4618 PurpleBuddy *buddy = purple_find_buddy(conv->account, conv->name);
4619 if(buddy != NULL)
4620 pidgin_blist_update_buddy(NULL, (PurpleBlistNode *)buddy, TRUE);
4621 }
4622
4623 if (gtkblist->menutrayicon) {
4624 gtk_widget_destroy(gtkblist->menutrayicon);
4625 gtkblist->menutrayicon = NULL;
4626 }
4627
4628 ims = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_IM,
4629 PIDGIN_UNSEEN_TEXT, FALSE, 0);
4630
4631 chats = pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_CHAT,
4632 PIDGIN_UNSEEN_NICK, FALSE, 0);
4633
4634 if(ims && chats)
4635 convs = g_list_concat(ims, chats);
4636 else if(ims && !chats)
4637 convs = ims;
4638 else if(!ims && chats)
4639 convs = chats;
4640
4641 if (convs) {
4642 GtkWidget *img = NULL;
4643 GString *tooltip_text = NULL;
4644
4645 tooltip_text = g_string_new("");
4646 l = convs;
4647 while (l != NULL) {
4648 int count = 0;
4649 PidginConversation *gtkconv = PIDGIN_CONVERSATION((PurpleConversation *)l->data);
4650
4651 if(gtkconv)
4652 count = gtkconv->unseen_count;
4653 else if(purple_conversation_get_data(l->data, "unseen-count"))
4654 count = GPOINTER_TO_INT(purple_conversation_get_data(l->data, "unseen-count"));
4655
4656 g_string_append_printf(tooltip_text,
4657 ngettext("%d unread message from %s\n", "%d unread messages from %s\n", count),
4658 count, purple_conversation_get_title(l->data));
4659 l = l->next;
4660 }
4661 if(tooltip_text->len > 0) {
4662 /* get rid of the last newline */
4663 g_string_truncate(tooltip_text, tooltip_text->len -1);
4664 img = gtk_image_new_from_stock(PIDGIN_STOCK_TOOLBAR_PENDING,
4665 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
4666
4667 gtkblist->menutrayicon = gtk_event_box_new();
4668 gtk_container_add(GTK_CONTAINER(gtkblist->menutrayicon), img);
4669 gtk_widget_show(img);
4670 gtk_widget_show(gtkblist->menutrayicon);
4671 g_signal_connect(G_OBJECT(gtkblist->menutrayicon), "button-press-event", G_CALLBACK(menutray_press_cb), NULL);
4672
4673 pidgin_menu_tray_append(PIDGIN_MENU_TRAY(gtkblist->menutray), gtkblist->menutrayicon, tooltip_text->str);
4674 }
4675 g_string_free(tooltip_text, TRUE);
4676 g_list_free(convs);
4677 }
4678 }
4679
4680 static void
conversation_deleting_cb(PurpleConversation * conv,PidginBuddyList * gtkblist)4681 conversation_deleting_cb(PurpleConversation *conv, PidginBuddyList *gtkblist)
4682 {
4683 conversation_updated_cb(conv, PURPLE_CONV_UPDATE_UNSEEN, gtkblist);
4684 }
4685
4686 static void
conversation_deleted_update_ui_cb(PurpleConversation * conv,struct _pidgin_blist_node * ui)4687 conversation_deleted_update_ui_cb(PurpleConversation *conv, struct _pidgin_blist_node *ui)
4688 {
4689 if (ui->conv.conv != conv)
4690 return;
4691 ui->conv.conv = NULL;
4692 ui->conv.flags = 0;
4693 ui->conv.last_message = 0;
4694 }
4695
4696 static void
written_msg_update_ui_cb(PurpleAccount * account,const char * who,const char * message,PurpleConversation * conv,PurpleMessageFlags flag,PurpleBlistNode * node)4697 written_msg_update_ui_cb(PurpleAccount *account, const char *who, const char *message,
4698 PurpleConversation *conv, PurpleMessageFlags flag, PurpleBlistNode *node)
4699 {
4700 PidginBlistNode *ui = node->ui_data;
4701 if (ui->conv.conv != conv || !pidgin_conv_is_hidden(PIDGIN_CONVERSATION(conv)) ||
4702 !(flag & (PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_RECV)))
4703 return;
4704 ui->conv.flags |= PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE;
4705 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT
4706 && (flag & PURPLE_MESSAGE_NICK))
4707 ui->conv.flags |= PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK;
4708
4709 ui->conv.last_message = time(NULL); /* XXX: for lack of better data */
4710 pidgin_blist_update(purple_get_blist(), node);
4711 }
4712
4713 static void
displayed_msg_update_ui_cb(PidginConversation * gtkconv,PurpleBlistNode * node)4714 displayed_msg_update_ui_cb(PidginConversation *gtkconv, PurpleBlistNode *node)
4715 {
4716 PidginBlistNode *ui = node->ui_data;
4717 if (ui->conv.conv != gtkconv->active_conv)
4718 return;
4719 ui->conv.flags &= ~(PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE |
4720 PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK);
4721 pidgin_blist_update(purple_get_blist(), node);
4722 }
4723
4724 static void
conversation_created_cb(PurpleConversation * conv,PidginBuddyList * gtkblist)4725 conversation_created_cb(PurpleConversation *conv, PidginBuddyList *gtkblist)
4726 {
4727 switch (conv->type) {
4728 case PURPLE_CONV_TYPE_IM:
4729 {
4730 GSList *buddies = purple_find_buddies(conv->account, conv->name);
4731 while (buddies) {
4732 PurpleBlistNode *buddy = buddies->data;
4733 struct _pidgin_blist_node *ui = buddy->ui_data;
4734 buddies = g_slist_delete_link(buddies, buddies);
4735 if (!ui)
4736 continue;
4737 ui->conv.conv = conv;
4738 ui->conv.flags = 0;
4739 ui->conv.last_message = 0;
4740 purple_signal_connect(purple_conversations_get_handle(), "deleting-conversation",
4741 ui, PURPLE_CALLBACK(conversation_deleted_update_ui_cb), ui);
4742 purple_signal_connect(purple_conversations_get_handle(), "wrote-im-msg",
4743 ui, PURPLE_CALLBACK(written_msg_update_ui_cb), buddy);
4744 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-displayed",
4745 ui, PURPLE_CALLBACK(displayed_msg_update_ui_cb), buddy);
4746 }
4747 }
4748 break;
4749 default:
4750 break;
4751 }
4752 }
4753
4754 static void
chat_joined_cb(PurpleConversation * conv,PidginBuddyList * gtkblist)4755 chat_joined_cb(PurpleConversation *conv, PidginBuddyList *gtkblist)
4756 {
4757 switch (conv->type) {
4758 case PURPLE_CONV_TYPE_CHAT:
4759 {
4760 PurpleChat *chat = purple_blist_find_chat(conv->account, conv->name);
4761 struct _pidgin_blist_node *ui;
4762 if (!chat)
4763 break;
4764 ui = chat->node.ui_data;
4765 if (!ui)
4766 break;
4767 ui->conv.conv = conv;
4768 ui->conv.flags = 0;
4769 ui->conv.last_message = 0;
4770 purple_signal_connect(purple_conversations_get_handle(), "deleting-conversation",
4771 ui, PURPLE_CALLBACK(conversation_deleted_update_ui_cb), ui);
4772 purple_signal_connect(purple_conversations_get_handle(), "wrote-chat-msg",
4773 ui, PURPLE_CALLBACK(written_msg_update_ui_cb), chat);
4774 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-displayed",
4775 ui, PURPLE_CALLBACK(displayed_msg_update_ui_cb), chat);
4776 }
4777 break;
4778 default:
4779 break;
4780 }
4781 }
4782
4783 /**********************************************************************************
4784 * Public API Functions *
4785 **********************************************************************************/
4786
pidgin_blist_new_list(PurpleBuddyList * blist)4787 static void pidgin_blist_new_list(PurpleBuddyList *blist)
4788 {
4789 PidginBuddyList *gtkblist;
4790
4791 gtkblist = g_new0(PidginBuddyList, 1);
4792 gtkblist->connection_errors = g_hash_table_new_full(g_direct_hash,
4793 g_direct_equal, NULL, g_free);
4794 gtkblist->priv = g_new0(PidginBuddyListPrivate, 1);
4795
4796 blist->ui_data = gtkblist;
4797 }
4798
pidgin_blist_new_node(PurpleBlistNode * node)4799 static void pidgin_blist_new_node(PurpleBlistNode *node)
4800 {
4801 node->ui_data = g_new0(struct _pidgin_blist_node, 1);
4802 }
4803
pidgin_blist_node_is_contact_expanded(PurpleBlistNode * node)4804 gboolean pidgin_blist_node_is_contact_expanded(PurpleBlistNode *node)
4805 {
4806 if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
4807 node = node->parent;
4808 if (node == NULL)
4809 return FALSE;
4810 }
4811
4812 g_return_val_if_fail(PURPLE_BLIST_NODE_IS_CONTACT(node), FALSE);
4813
4814 return ((struct _pidgin_blist_node *)node->ui_data)->contact_expanded;
4815 }
4816
4817 enum {
4818 DRAG_BUDDY,
4819 DRAG_ROW,
4820 DRAG_VCARD,
4821 DRAG_TEXT,
4822 DRAG_URI,
4823 NUM_TARGETS
4824 };
4825
4826 static const char *
item_factory_translate_func(const char * path,gpointer func_data)4827 item_factory_translate_func (const char *path, gpointer func_data)
4828 {
4829 return _((char *)path);
4830 }
4831
pidgin_blist_setup_sort_methods()4832 void pidgin_blist_setup_sort_methods()
4833 {
4834 const char *id;
4835
4836 pidgin_blist_sort_method_reg("none", _("Manually"), sort_method_none);
4837 pidgin_blist_sort_method_reg("alphabetical", _("Alphabetically"), sort_method_alphabetical);
4838 pidgin_blist_sort_method_reg("status", _("By status"), sort_method_status);
4839 pidgin_blist_sort_method_reg("log_size", _("By recent log activity"), sort_method_log_activity);
4840
4841 id = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/blist/sort_type");
4842 if (id == NULL) {
4843 purple_debug_warning("gtkblist", "Sort method was NULL, resetting to alphabetical\n");
4844 id = "alphabetical";
4845 }
4846 pidgin_blist_sort_method_set(id);
4847 }
4848
_prefs_change_redo_list(const char * name,PurplePrefType type,gconstpointer val,gpointer data)4849 static void _prefs_change_redo_list(const char *name, PurplePrefType type,
4850 gconstpointer val, gpointer data)
4851 {
4852 GtkTreeSelection *sel;
4853 GtkTreeIter iter;
4854 PurpleBlistNode *node = NULL;
4855
4856 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
4857 if (gtk_tree_selection_get_selected(sel, NULL, &iter))
4858 {
4859 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
4860 }
4861
4862 redo_buddy_list(purple_get_blist(), FALSE, FALSE);
4863 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(gtkblist->treeview));
4864
4865 if (node)
4866 {
4867 struct _pidgin_blist_node *gtknode;
4868 GtkTreePath *path;
4869
4870 gtknode = node->ui_data;
4871 if (gtknode && gtknode->row)
4872 {
4873 path = gtk_tree_row_reference_get_path(gtknode->row);
4874 gtk_tree_selection_select_path(sel, path);
4875 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(gtkblist->treeview), path, NULL, FALSE, 0, 0);
4876 gtk_tree_path_free(path);
4877 }
4878 }
4879 }
4880
_prefs_change_sort_method(const char * pref_name,PurplePrefType type,gconstpointer val,gpointer data)4881 static void _prefs_change_sort_method(const char *pref_name, PurplePrefType type,
4882 gconstpointer val, gpointer data)
4883 {
4884 if(purple_strequal(pref_name, PIDGIN_PREFS_ROOT "/blist/sort_type"))
4885 pidgin_blist_sort_method_set(val);
4886 }
4887
pidgin_blist_select_notebook_page_cb(gpointer user_data)4888 static gboolean pidgin_blist_select_notebook_page_cb(gpointer user_data)
4889 {
4890 PidginBuddyList *gtkblist = (PidginBuddyList *)user_data;
4891 int errors = 0;
4892 GList *list = NULL;
4893 PidginBuddyListPrivate *priv;
4894
4895 priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
4896
4897 priv->select_page_timeout = 0;
4898
4899 /* this is far too ugly thanks to me not wanting to fix #3989 properly right now */
4900 if (priv->error_scrollbook != NULL) {
4901 errors = gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->error_scrollbook->notebook));
4902 }
4903 if ((list = purple_accounts_get_all_active()) != NULL || errors) {
4904 gtk_notebook_set_current_page(GTK_NOTEBOOK(gtkblist->notebook), 1);
4905 g_list_free(list);
4906 } else
4907 gtk_notebook_set_current_page(GTK_NOTEBOOK(gtkblist->notebook), 0);
4908
4909 return FALSE;
4910 }
4911
pidgin_blist_select_notebook_page(PidginBuddyList * gtkblist)4912 static void pidgin_blist_select_notebook_page(PidginBuddyList *gtkblist)
4913 {
4914 PidginBuddyListPrivate *priv;
4915
4916 priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
4917
4918 priv->select_page_timeout = purple_timeout_add(0, pidgin_blist_select_notebook_page_cb, gtkblist);
4919 }
4920
account_modified(PurpleAccount * account,PidginBuddyList * gtkblist)4921 static void account_modified(PurpleAccount *account, PidginBuddyList *gtkblist)
4922 {
4923 if (!gtkblist)
4924 return;
4925
4926 pidgin_blist_select_notebook_page(gtkblist);
4927 update_menu_bar(gtkblist);
4928 }
4929
4930 static void
account_actions_changed(PurpleAccount * account,gpointer data)4931 account_actions_changed(PurpleAccount *account, gpointer data)
4932 {
4933 pidgin_blist_update_accounts_menu();
4934 }
4935
4936 static void
account_status_changed(PurpleAccount * account,PurpleStatus * old,PurpleStatus * new,PidginBuddyList * gtkblist)4937 account_status_changed(PurpleAccount *account, PurpleStatus *old,
4938 PurpleStatus *new, PidginBuddyList *gtkblist)
4939 {
4940 if (!gtkblist)
4941 return;
4942
4943 account_modified(account, gtkblist);
4944 }
4945
4946 static gboolean
gtk_blist_window_key_press_cb(GtkWidget * w,GdkEventKey * event,PidginBuddyList * gtkblist)4947 gtk_blist_window_key_press_cb(GtkWidget *w, GdkEventKey *event, PidginBuddyList *gtkblist)
4948 {
4949 GtkWidget *widget;
4950
4951 if (!gtkblist)
4952 return FALSE;
4953
4954 /* clear any tooltips */
4955 pidgin_blist_tooltip_destroy();
4956
4957 widget = gtk_window_get_focus(GTK_WINDOW(gtkblist->window));
4958
4959 if (GTK_IS_IMHTML(widget) || GTK_IS_ENTRY(widget)) {
4960 if (gtk_bindings_activate(GTK_OBJECT(widget), event->keyval, event->state))
4961 return TRUE;
4962 }
4963 return FALSE;
4964 }
4965
4966 static gboolean
headline_box_enter_cb(GtkWidget * widget,GdkEventCrossing * event,PidginBuddyList * gtkblist)4967 headline_box_enter_cb(GtkWidget *widget, GdkEventCrossing *event, PidginBuddyList *gtkblist)
4968 {
4969 gdk_window_set_cursor(widget->window, gtkblist->hand_cursor);
4970 return FALSE;
4971 }
4972
4973 static gboolean
headline_box_leave_cb(GtkWidget * widget,GdkEventCrossing * event,PidginBuddyList * gtkblist)4974 headline_box_leave_cb(GtkWidget *widget, GdkEventCrossing *event, PidginBuddyList *gtkblist)
4975 {
4976 gdk_window_set_cursor(widget->window, gtkblist->arrow_cursor);
4977 return FALSE;
4978 }
4979
4980 static void
reset_headline(PidginBuddyList * gtkblist)4981 reset_headline(PidginBuddyList *gtkblist)
4982 {
4983 gtkblist->headline_callback = NULL;
4984 gtkblist->headline_data = NULL;
4985 gtkblist->headline_destroy = NULL;
4986 pidgin_set_urgent(GTK_WINDOW(gtkblist->window), FALSE);
4987 }
4988
4989 static gboolean
headline_click_callback(gpointer unused)4990 headline_click_callback(gpointer unused)
4991 {
4992 if (gtkblist->headline_callback)
4993 ((GSourceFunc) gtkblist->headline_callback)(gtkblist->headline_data);
4994 reset_headline(gtkblist);
4995 return FALSE;
4996 }
4997
4998 static gboolean
headline_close_press_cb(GtkButton * button,PidginBuddyList * gtkblist)4999 headline_close_press_cb(GtkButton *button, PidginBuddyList *gtkblist)
5000 {
5001 gtk_widget_hide(gtkblist->headline_hbox);
5002 return FALSE;
5003 }
5004
5005 static gboolean
headline_box_press_cb(GtkWidget * widget,GdkEventButton * event,PidginBuddyList * gtkblist)5006 headline_box_press_cb(GtkWidget *widget, GdkEventButton *event, PidginBuddyList *gtkblist)
5007 {
5008 gtk_widget_hide(gtkblist->headline_hbox);
5009 if (gtkblist->headline_callback)
5010 g_idle_add(headline_click_callback, NULL);
5011 else {
5012 if (gtkblist->headline_destroy)
5013 gtkblist->headline_destroy(gtkblist->headline_data);
5014 reset_headline(gtkblist);
5015 }
5016 return TRUE;
5017 }
5018
5019 /***********************************/
5020 /* Connection error handling stuff */
5021 /***********************************/
5022
5023 #define OBJECT_DATA_KEY_ACCOUNT "account"
5024 #define DO_NOT_CLEAR_ERROR "do-not-clear-error"
5025
5026 static gboolean
find_account_widget(GObject * widget,PurpleAccount * account)5027 find_account_widget(GObject *widget,
5028 PurpleAccount *account)
5029 {
5030 if (g_object_get_data(widget, OBJECT_DATA_KEY_ACCOUNT) == account)
5031 return 0; /* found */
5032 else
5033 return 1;
5034 }
5035
5036 static void
pack_prpl_icon_start(GtkWidget * box,PurpleAccount * account)5037 pack_prpl_icon_start(GtkWidget *box,
5038 PurpleAccount *account)
5039 {
5040 GdkPixbuf *pixbuf;
5041 GtkWidget *image;
5042
5043 pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
5044 if (pixbuf != NULL) {
5045 image = gtk_image_new_from_pixbuf(pixbuf);
5046 g_object_unref(pixbuf);
5047
5048 gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 0);
5049 }
5050 }
5051
5052 static void
add_error_dialog(PidginBuddyList * gtkblist,GtkWidget * dialog)5053 add_error_dialog(PidginBuddyList *gtkblist,
5054 GtkWidget *dialog)
5055 {
5056 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5057 gtk_container_add(GTK_CONTAINER(priv->error_scrollbook), dialog);
5058 }
5059
5060 static GtkWidget *
find_child_widget_by_account(GtkContainer * container,PurpleAccount * account)5061 find_child_widget_by_account(GtkContainer *container,
5062 PurpleAccount *account)
5063 {
5064 GList *l = NULL;
5065 GList *children = NULL;
5066 GtkWidget *ret = NULL;
5067 /* XXX: Workaround for the currently incomplete implementation of PidginScrollBook */
5068 if (PIDGIN_IS_SCROLL_BOOK(container))
5069 container = GTK_CONTAINER(PIDGIN_SCROLL_BOOK(container)->notebook);
5070 children = gtk_container_get_children(container);
5071 l = g_list_find_custom(children, account, (GCompareFunc) find_account_widget);
5072 if (l)
5073 ret = GTK_WIDGET(l->data);
5074 g_list_free(children);
5075 return ret;
5076 }
5077
5078 static void
remove_child_widget_by_account(GtkContainer * container,PurpleAccount * account)5079 remove_child_widget_by_account(GtkContainer *container,
5080 PurpleAccount *account)
5081 {
5082 GtkWidget *widget = find_child_widget_by_account(container, account);
5083 if(widget) {
5084 /* Since we are destroying the widget in response to a change in
5085 * error, we should not clear the error.
5086 */
5087 g_object_set_data(G_OBJECT(widget), DO_NOT_CLEAR_ERROR,
5088 GINT_TO_POINTER(TRUE));
5089 gtk_widget_destroy(widget);
5090 }
5091 }
5092
5093 /* Generic error buttons */
5094
5095 static void
generic_error_modify_cb(PurpleAccount * account)5096 generic_error_modify_cb(PurpleAccount *account)
5097 {
5098 purple_account_clear_current_error(account);
5099 pidgin_account_dialog_show(PIDGIN_MODIFY_ACCOUNT_DIALOG, account);
5100 }
5101
5102 static void
generic_error_enable_cb(PurpleAccount * account)5103 generic_error_enable_cb(PurpleAccount *account)
5104 {
5105 purple_account_clear_current_error(account);
5106 purple_account_set_enabled(account, purple_core_get_ui(), TRUE);
5107 }
5108
5109 static void
generic_error_destroy_cb(GtkObject * dialog,PurpleAccount * account)5110 generic_error_destroy_cb(GtkObject *dialog,
5111 PurpleAccount *account)
5112 {
5113 g_hash_table_remove(gtkblist->connection_errors, account);
5114 /* If the error dialog is being destroyed in response to the
5115 * account-error-changed signal, we don't want to clear the current
5116 * error.
5117 */
5118 if (g_object_get_data(G_OBJECT(dialog), DO_NOT_CLEAR_ERROR) == NULL)
5119 purple_account_clear_current_error(account);
5120 }
5121
5122 #define SSL_FAQ_URI "https://developer.pidgin.im/wiki/FAQssl"
5123
5124 static void
ssl_faq_clicked_cb(PidginMiniDialog * mini_dialog,GtkButton * button,gpointer ignored)5125 ssl_faq_clicked_cb(PidginMiniDialog *mini_dialog,
5126 GtkButton *button,
5127 gpointer ignored)
5128 {
5129 purple_notify_uri(NULL, SSL_FAQ_URI);
5130 }
5131
5132 static void
add_generic_error_dialog(PurpleAccount * account,const PurpleConnectionErrorInfo * err)5133 add_generic_error_dialog(PurpleAccount *account,
5134 const PurpleConnectionErrorInfo *err)
5135 {
5136 GtkWidget *mini_dialog;
5137 const char *username = purple_account_get_username(account);
5138 gboolean enabled =
5139 purple_account_get_enabled(account, purple_core_get_ui());
5140 char *primary;
5141
5142 if (enabled)
5143 primary = g_strdup_printf(_("%s disconnected"), username);
5144 else
5145 primary = g_strdup_printf(_("%s disabled"), username);
5146
5147 mini_dialog = pidgin_make_mini_dialog(NULL, PIDGIN_STOCK_DIALOG_ERROR,
5148 primary, err->description, account,
5149 (enabled ? _("Reconnect") : _("Re-enable")),
5150 (enabled ? PURPLE_CALLBACK(purple_account_connect)
5151 : PURPLE_CALLBACK(generic_error_enable_cb)),
5152 _("Modify Account"), PURPLE_CALLBACK(generic_error_modify_cb),
5153 NULL);
5154
5155 g_free(primary);
5156
5157 g_object_set_data(G_OBJECT(mini_dialog), OBJECT_DATA_KEY_ACCOUNT,
5158 account);
5159
5160 if(err->type == PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT)
5161 pidgin_mini_dialog_add_non_closing_button(PIDGIN_MINI_DIALOG(mini_dialog),
5162 _("SSL FAQs"), ssl_faq_clicked_cb, NULL);
5163
5164 g_signal_connect_after(mini_dialog, "destroy",
5165 (GCallback)generic_error_destroy_cb,
5166 account);
5167
5168 add_error_dialog(gtkblist, mini_dialog);
5169 }
5170
5171 static void
remove_generic_error_dialog(PurpleAccount * account)5172 remove_generic_error_dialog(PurpleAccount *account)
5173 {
5174 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5175 remove_child_widget_by_account(
5176 GTK_CONTAINER(priv->error_scrollbook), account);
5177 }
5178
5179
5180 static void
update_generic_error_message(PurpleAccount * account,const char * description)5181 update_generic_error_message(PurpleAccount *account,
5182 const char *description)
5183 {
5184 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5185 GtkWidget *mini_dialog = find_child_widget_by_account(
5186 GTK_CONTAINER(priv->error_scrollbook), account);
5187 pidgin_mini_dialog_set_description(PIDGIN_MINI_DIALOG(mini_dialog),
5188 description);
5189 }
5190
5191
5192 /* Notifications about accounts which were disconnected with
5193 * PURPLE_CONNECTION_ERROR_NAME_IN_USE
5194 */
5195
5196 typedef void (*AccountFunction)(PurpleAccount *);
5197
5198 static void
elsewhere_foreach_account(PidginMiniDialog * mini_dialog,AccountFunction f)5199 elsewhere_foreach_account(PidginMiniDialog *mini_dialog,
5200 AccountFunction f)
5201 {
5202 PurpleAccount *account;
5203 GList *labels = gtk_container_get_children(
5204 GTK_CONTAINER(mini_dialog->contents));
5205 GList *l;
5206
5207 for (l = labels; l; l = l->next) {
5208 account = g_object_get_data(G_OBJECT(l->data), OBJECT_DATA_KEY_ACCOUNT);
5209 if (account)
5210 f(account);
5211 else
5212 purple_debug_warning("gtkblist", "mini_dialog's child "
5213 "didn't have an account stored in it!");
5214 }
5215 g_list_free(labels);
5216 }
5217
5218 static void
enable_account(PurpleAccount * account)5219 enable_account(PurpleAccount *account)
5220 {
5221 purple_account_set_enabled(account, purple_core_get_ui(), TRUE);
5222 }
5223
5224 static void
reconnect_elsewhere_accounts(PidginMiniDialog * mini_dialog,GtkButton * button,gpointer unused)5225 reconnect_elsewhere_accounts(PidginMiniDialog *mini_dialog,
5226 GtkButton *button,
5227 gpointer unused)
5228 {
5229 elsewhere_foreach_account(mini_dialog, enable_account);
5230 }
5231
5232 static void
clear_elsewhere_errors(PidginMiniDialog * mini_dialog,gpointer unused)5233 clear_elsewhere_errors(PidginMiniDialog *mini_dialog,
5234 gpointer unused)
5235 {
5236 elsewhere_foreach_account(mini_dialog, purple_account_clear_current_error);
5237 }
5238
5239 static void
ensure_signed_on_elsewhere_minidialog(PidginBuddyList * gtkblist)5240 ensure_signed_on_elsewhere_minidialog(PidginBuddyList *gtkblist)
5241 {
5242 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5243 PidginMiniDialog *mini_dialog;
5244
5245 if(priv->signed_on_elsewhere)
5246 return;
5247
5248 mini_dialog = priv->signed_on_elsewhere =
5249 pidgin_mini_dialog_new(_("Welcome back!"), NULL, PIDGIN_STOCK_DISCONNECT);
5250
5251 pidgin_mini_dialog_add_button(mini_dialog, _("Re-enable"),
5252 reconnect_elsewhere_accounts, NULL);
5253
5254 /* Make dismissing the dialog clear the errors. The "destroy" signal
5255 * does not appear to fire at quit, which is fortunate!
5256 */
5257 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
5258 (GCallback) clear_elsewhere_errors, NULL);
5259
5260 add_error_dialog(gtkblist, GTK_WIDGET(mini_dialog));
5261
5262 /* Set priv->signed_on_elsewhere to NULL when the dialog is destroyed */
5263 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
5264 (GCallback) gtk_widget_destroyed, &(priv->signed_on_elsewhere));
5265 }
5266
5267 static void
update_signed_on_elsewhere_minidialog_title(void)5268 update_signed_on_elsewhere_minidialog_title(void)
5269 {
5270 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5271 PidginMiniDialog *mini_dialog = priv->signed_on_elsewhere;
5272 guint accounts;
5273 char *title;
5274
5275 if (mini_dialog == NULL)
5276 return;
5277
5278 accounts = pidgin_mini_dialog_get_num_children(mini_dialog);
5279 if (accounts == 0) {
5280 gtk_widget_destroy(GTK_WIDGET(mini_dialog));
5281 return;
5282 }
5283
5284 title = g_strdup_printf(
5285 ngettext("%d account was disabled because you signed on from another location:",
5286 "%d accounts were disabled because you signed on from another location:",
5287 accounts),
5288 accounts);
5289 pidgin_mini_dialog_set_description(mini_dialog, title);
5290 g_free(title);
5291 }
5292
5293 static GtkWidget *
create_account_label(PurpleAccount * account)5294 create_account_label(PurpleAccount *account)
5295 {
5296 GtkWidget *hbox, *label;
5297 const char *username = purple_account_get_username(account);
5298 char *markup;
5299
5300 hbox = gtk_hbox_new(FALSE, 6);
5301 g_object_set_data(G_OBJECT(hbox), OBJECT_DATA_KEY_ACCOUNT, account);
5302
5303 pack_prpl_icon_start(hbox, account);
5304
5305 label = gtk_label_new(NULL);
5306 markup = g_strdup_printf("<span size=\"smaller\">%s</span>", username);
5307 gtk_label_set_markup(GTK_LABEL(label), markup);
5308 g_free(markup);
5309 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
5310 g_object_set(G_OBJECT(label), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
5311 #if GTK_CHECK_VERSION(2,12,0)
5312 { /* avoid unused variable warnings on pre-2.12 Gtk */
5313 char *description =
5314 purple_account_get_current_error(account)->description;
5315 if (description != NULL && *description != '\0')
5316 gtk_widget_set_tooltip_text(label, description);
5317 }
5318 #endif
5319 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
5320
5321 return hbox;
5322 }
5323
5324 static void
add_to_signed_on_elsewhere(PurpleAccount * account)5325 add_to_signed_on_elsewhere(PurpleAccount *account)
5326 {
5327 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5328 PidginMiniDialog *mini_dialog;
5329 GtkWidget *account_label;
5330
5331 ensure_signed_on_elsewhere_minidialog(gtkblist);
5332 mini_dialog = priv->signed_on_elsewhere;
5333
5334 if(find_child_widget_by_account(GTK_CONTAINER(mini_dialog->contents), account))
5335 return;
5336
5337 account_label = create_account_label(account);
5338 gtk_box_pack_start(mini_dialog->contents, account_label, FALSE, FALSE, 0);
5339 gtk_widget_show_all(account_label);
5340
5341 update_signed_on_elsewhere_minidialog_title();
5342 }
5343
5344 static void
remove_from_signed_on_elsewhere(PurpleAccount * account)5345 remove_from_signed_on_elsewhere(PurpleAccount *account)
5346 {
5347 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5348 PidginMiniDialog *mini_dialog = priv->signed_on_elsewhere;
5349 if(mini_dialog == NULL)
5350 return;
5351
5352 remove_child_widget_by_account(GTK_CONTAINER(mini_dialog->contents), account);
5353
5354 update_signed_on_elsewhere_minidialog_title();
5355 }
5356
5357
5358 static void
update_signed_on_elsewhere_tooltip(PurpleAccount * account,const char * description)5359 update_signed_on_elsewhere_tooltip(PurpleAccount *account,
5360 const char *description)
5361 {
5362 #if GTK_CHECK_VERSION(2,12,0)
5363 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5364 GtkContainer *c = GTK_CONTAINER(priv->signed_on_elsewhere->contents);
5365 GtkWidget *label = find_child_widget_by_account(c, account);
5366 gtk_widget_set_tooltip_text(label, description);
5367 #endif
5368 }
5369
5370
5371 /* Call appropriate error notification code based on error types */
5372 static void
update_account_error_state(PurpleAccount * account,const PurpleConnectionErrorInfo * old,const PurpleConnectionErrorInfo * new,PidginBuddyList * gtkblist)5373 update_account_error_state(PurpleAccount *account,
5374 const PurpleConnectionErrorInfo *old,
5375 const PurpleConnectionErrorInfo *new,
5376 PidginBuddyList *gtkblist)
5377 {
5378 gboolean descriptions_differ;
5379 const char *desc;
5380
5381 if (old == NULL && new == NULL)
5382 return;
5383
5384 /* For backwards compatibility: */
5385 if (new)
5386 pidgin_blist_update_account_error_state(account, new->description);
5387 else
5388 pidgin_blist_update_account_error_state(account, NULL);
5389
5390 if (new != NULL)
5391 pidgin_blist_select_notebook_page(gtkblist);
5392
5393 if (old != NULL && new == NULL) {
5394 if(old->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE)
5395 remove_from_signed_on_elsewhere(account);
5396 else
5397 remove_generic_error_dialog(account);
5398 return;
5399 }
5400
5401 if (old == NULL && new != NULL) {
5402 if(new->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE)
5403 add_to_signed_on_elsewhere(account);
5404 else
5405 add_generic_error_dialog(account, new);
5406 return;
5407 }
5408
5409 /* else, new and old are both non-NULL */
5410
5411 descriptions_differ = !purple_strequal(old->description, new->description);
5412 desc = new->description;
5413
5414 switch (new->type) {
5415 case PURPLE_CONNECTION_ERROR_NAME_IN_USE:
5416 if (old->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE
5417 && descriptions_differ) {
5418 update_signed_on_elsewhere_tooltip(account, desc);
5419 } else {
5420 remove_generic_error_dialog(account);
5421 add_to_signed_on_elsewhere(account);
5422 }
5423 break;
5424 default:
5425 if (old->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE) {
5426 remove_from_signed_on_elsewhere(account);
5427 add_generic_error_dialog(account, new);
5428 } else if (descriptions_differ) {
5429 update_generic_error_message(account, desc);
5430 }
5431 break;
5432 }
5433 }
5434
5435 /* In case accounts are loaded before the blist (which they currently are),
5436 * let's call update_account_error_state ourselves on every account's current
5437 * state when the blist starts.
5438 */
5439 static void
show_initial_account_errors(PidginBuddyList * gtkblist)5440 show_initial_account_errors(PidginBuddyList *gtkblist)
5441 {
5442 GList *l = purple_accounts_get_all();
5443 PurpleAccount *account;
5444 const PurpleConnectionErrorInfo *err;
5445
5446 for (; l; l = l->next)
5447 {
5448 account = l->data;
5449 err = purple_account_get_current_error(account);
5450
5451 update_account_error_state(account, NULL, err, gtkblist);
5452 }
5453 }
5454
5455 void
pidgin_blist_update_account_error_state(PurpleAccount * account,const char * text)5456 pidgin_blist_update_account_error_state(PurpleAccount *account, const char *text)
5457 {
5458 /* connection_errors isn't actually used anywhere; it's just kept in
5459 * sync with reality in case a plugin uses it.
5460 */
5461 if (text == NULL)
5462 g_hash_table_remove(gtkblist->connection_errors, account);
5463 else
5464 g_hash_table_insert(gtkblist->connection_errors, account, g_strdup(text));
5465 }
5466
5467 static gboolean
paint_headline_hbox(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)5468 paint_headline_hbox (GtkWidget *widget,
5469 GdkEventExpose *event,
5470 gpointer user_data)
5471 {
5472 gtk_paint_flat_box (widget->style,
5473 widget->window,
5474 GTK_STATE_NORMAL,
5475 GTK_SHADOW_OUT,
5476 NULL,
5477 widget,
5478 "tooltip",
5479 widget->allocation.x + 1,
5480 widget->allocation.y + 1,
5481 widget->allocation.width - 2,
5482 widget->allocation.height - 2);
5483 return FALSE;
5484 }
5485
5486 /* This assumes there are not things like groupless buddies or multi-leveled groups.
5487 * I'm sure other things in this code assumes that also.
5488 */
5489 static void
treeview_style_set(GtkWidget * widget,GtkStyle * prev_style,gpointer data)5490 treeview_style_set (GtkWidget *widget,
5491 GtkStyle *prev_style,
5492 gpointer data)
5493 {
5494 PurpleBuddyList *list = data;
5495 PurpleBlistNode *node = list->root;
5496 while (node) {
5497 pidgin_blist_update_group(list, node);
5498 node = node->next;
5499 }
5500 }
5501
5502 static void
headline_style_set(GtkWidget * widget,GtkStyle * prev_style)5503 headline_style_set (GtkWidget *widget,
5504 GtkStyle *prev_style)
5505 {
5506 GtkTooltips *tooltips;
5507 GtkStyle *style;
5508
5509 if (gtkblist->changing_style)
5510 return;
5511
5512 tooltips = gtk_tooltips_new ();
5513 g_object_ref_sink (tooltips);
5514
5515 gtk_tooltips_force_window (tooltips);
5516 #if GTK_CHECK_VERSION(2, 12, 0)
5517 gtk_widget_set_name (tooltips->tip_window, "gtk-tooltips");
5518 #endif
5519 gtk_widget_ensure_style (tooltips->tip_window);
5520 style = gtk_widget_get_style (tooltips->tip_window);
5521
5522 gtkblist->changing_style = TRUE;
5523 gtk_widget_set_style (gtkblist->headline_hbox, style);
5524 gtkblist->changing_style = FALSE;
5525
5526 g_object_unref (tooltips);
5527 }
5528
5529 /******************************************/
5530 /* End of connection error handling stuff */
5531 /******************************************/
5532
5533 static int
blist_focus_cb(GtkWidget * widget,GdkEventFocus * event,PidginBuddyList * gtkblist)5534 blist_focus_cb(GtkWidget *widget, GdkEventFocus *event, PidginBuddyList *gtkblist)
5535 {
5536 if(event->in) {
5537 gtk_blist_focused = TRUE;
5538 pidgin_set_urgent(GTK_WINDOW(gtkblist->window), FALSE);
5539 } else {
5540 gtk_blist_focused = FALSE;
5541 }
5542 return 0;
5543 }
5544
5545 #if 0
5546 static GtkWidget *
5547 kiosk_page()
5548 {
5549 GtkWidget *ret = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
5550 GtkWidget *label;
5551 GtkWidget *entry;
5552 GtkWidget *bbox;
5553 GtkWidget *button;
5554
5555 label = gtk_label_new(NULL);
5556 gtk_box_pack_start(GTK_BOX(ret), label, TRUE, TRUE, 0);
5557
5558 label = gtk_label_new(NULL);
5559 gtk_label_set_markup(GTK_LABEL(label), _("<b>Username:</b>"));
5560 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
5561 gtk_box_pack_start(GTK_BOX(ret), label, FALSE, FALSE, 0);
5562 entry = gtk_entry_new();
5563 gtk_box_pack_start(GTK_BOX(ret), entry, FALSE, FALSE, 0);
5564
5565 label = gtk_label_new(NULL);
5566 gtk_label_set_markup(GTK_LABEL(label), _("<b>Password:</b>"));
5567 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
5568 gtk_box_pack_start(GTK_BOX(ret), label, FALSE, FALSE, 0);
5569 entry = gtk_entry_new();
5570 gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
5571 gtk_box_pack_start(GTK_BOX(ret), entry, FALSE, FALSE, 0);
5572
5573 label = gtk_label_new(" ");
5574 gtk_box_pack_start(GTK_BOX(ret), label, FALSE, FALSE, 0);
5575
5576 bbox = gtk_hbutton_box_new();
5577 button = gtk_button_new_with_mnemonic(_("_Login"));
5578 gtk_box_pack_start(GTK_BOX(ret), bbox, FALSE, FALSE, 0);
5579 gtk_container_add(GTK_CONTAINER(bbox), button);
5580
5581
5582 label = gtk_label_new(NULL);
5583 gtk_box_pack_start(GTK_BOX(ret), label, TRUE, TRUE, 0);
5584
5585 gtk_container_set_border_width(GTK_CONTAINER(ret), PIDGIN_HIG_BORDER);
5586
5587 gtk_widget_show_all(ret);
5588 return ret;
5589 }
5590 #endif
5591
5592 /* builds the blist layout according to to the current theme */
5593 static void
pidgin_blist_build_layout(PurpleBuddyList * list)5594 pidgin_blist_build_layout(PurpleBuddyList *list)
5595 {
5596 GtkTreeViewColumn *column;
5597 PidginBlistLayout *layout;
5598 PidginBlistTheme *theme;
5599 GtkCellRenderer *rend;
5600 gint i, status_icon = 0, text = 1, emblem = 2, protocol_icon = 3, buddy_icon = 4;
5601
5602
5603 column = gtkblist->text_column;
5604
5605 if ((theme = pidgin_blist_get_theme()) != NULL && (layout = pidgin_blist_theme_get_layout(theme)) != NULL) {
5606 status_icon = layout->status_icon ;
5607 text = layout->text;
5608 emblem = layout->emblem;
5609 protocol_icon = layout->protocol_icon;
5610 buddy_icon = layout->buddy_icon;
5611 }
5612
5613 gtk_tree_view_column_clear(column);
5614
5615 /* group */
5616 rend = pidgin_cell_renderer_expander_new();
5617 gtk_tree_view_column_pack_start(column, rend, FALSE);
5618 gtk_tree_view_column_set_attributes(column, rend,
5619 "visible", GROUP_EXPANDER_VISIBLE_COLUMN,
5620 "expander-visible", GROUP_EXPANDER_COLUMN,
5621 "sensitive", GROUP_EXPANDER_COLUMN,
5622 "cell-background-gdk", BGCOLOR_COLUMN,
5623 NULL);
5624
5625 /* contact */
5626 rend = pidgin_cell_renderer_expander_new();
5627 gtk_tree_view_column_pack_start(column, rend, FALSE);
5628 gtk_tree_view_column_set_attributes(column, rend,
5629 "visible", CONTACT_EXPANDER_VISIBLE_COLUMN,
5630 "expander-visible", CONTACT_EXPANDER_COLUMN,
5631 "sensitive", CONTACT_EXPANDER_COLUMN,
5632 "cell-background-gdk", BGCOLOR_COLUMN,
5633 NULL);
5634
5635 for (i = 0; i < 5; i++) {
5636
5637 if (status_icon == i) {
5638 /* status icons */
5639 rend = gtk_cell_renderer_pixbuf_new();
5640 gtk_tree_view_column_pack_start(column, rend, FALSE);
5641 gtk_tree_view_column_set_attributes(column, rend,
5642 "pixbuf", STATUS_ICON_COLUMN,
5643 "visible", STATUS_ICON_VISIBLE_COLUMN,
5644 "cell-background-gdk", BGCOLOR_COLUMN,
5645 NULL);
5646 g_object_set(rend, "xalign", 0.0, "xpad", 6, "ypad", 0, NULL);
5647
5648 } else if (text == i) {
5649 /* name */
5650 gtkblist->text_rend = rend = gtk_cell_renderer_text_new();
5651 gtk_tree_view_column_pack_start(column, rend, TRUE);
5652 gtk_tree_view_column_set_attributes(column, rend,
5653 "cell-background-gdk", BGCOLOR_COLUMN,
5654 "markup", NAME_COLUMN,
5655 NULL);
5656 g_signal_connect(G_OBJECT(rend), "editing-started", G_CALLBACK(gtk_blist_renderer_editing_started_cb), NULL);
5657 g_signal_connect(G_OBJECT(rend), "editing-canceled", G_CALLBACK(gtk_blist_renderer_editing_cancelled_cb), list);
5658 g_signal_connect(G_OBJECT(rend), "edited", G_CALLBACK(gtk_blist_renderer_edited_cb), list);
5659 g_object_set(rend, "ypad", 0, "yalign", 0.5, NULL);
5660 g_object_set(rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
5661
5662 /* idle */
5663 rend = gtk_cell_renderer_text_new();
5664 g_object_set(rend, "xalign", 1.0, "ypad", 0, NULL);
5665 gtk_tree_view_column_pack_start(column, rend, FALSE);
5666 gtk_tree_view_column_set_attributes(column, rend,
5667 "markup", IDLE_COLUMN,
5668 "visible", IDLE_VISIBLE_COLUMN,
5669 "cell-background-gdk", BGCOLOR_COLUMN,
5670 NULL);
5671 } else if (emblem == i) {
5672 /* emblem */
5673 rend = gtk_cell_renderer_pixbuf_new();
5674 g_object_set(rend, "xalign", 1.0, "yalign", 0.5, "ypad", 0, "xpad", 3, NULL);
5675 gtk_tree_view_column_pack_start(column, rend, FALSE);
5676 gtk_tree_view_column_set_attributes(column, rend, "pixbuf", EMBLEM_COLUMN,
5677 "cell-background-gdk", BGCOLOR_COLUMN,
5678 "visible", EMBLEM_VISIBLE_COLUMN, NULL);
5679
5680 } else if (protocol_icon == i) {
5681 /* protocol icon */
5682 rend = gtk_cell_renderer_pixbuf_new();
5683 gtk_tree_view_column_pack_start(column, rend, FALSE);
5684 gtk_tree_view_column_set_attributes(column, rend,
5685 "pixbuf", PROTOCOL_ICON_COLUMN,
5686 "visible", PROTOCOL_ICON_VISIBLE_COLUMN,
5687 "cell-background-gdk", BGCOLOR_COLUMN,
5688 NULL);
5689 g_object_set(rend, "xalign", 0.0, "xpad", 3, "ypad", 0, NULL);
5690
5691 } else if (buddy_icon == i) {
5692 /* buddy icon */
5693 rend = gtk_cell_renderer_pixbuf_new();
5694 g_object_set(rend, "xalign", 1.0, "ypad", 0, NULL);
5695 gtk_tree_view_column_pack_start(column, rend, FALSE);
5696 gtk_tree_view_column_set_attributes(column, rend, "pixbuf", BUDDY_ICON_COLUMN,
5697 "cell-background-gdk", BGCOLOR_COLUMN,
5698 "visible", BUDDY_ICON_VISIBLE_COLUMN,
5699 NULL);
5700 }
5701
5702 }/* end for loop */
5703
5704 }
5705
5706 static gboolean
pidgin_blist_search_equal_func(GtkTreeModel * model,gint column,const gchar * key,GtkTreeIter * iter,gpointer data)5707 pidgin_blist_search_equal_func(GtkTreeModel *model, gint column,
5708 const gchar *key, GtkTreeIter *iter, gpointer data)
5709 {
5710 PurpleBlistNode *node = NULL;
5711 gboolean res = TRUE;
5712 const char *compare = NULL;
5713
5714 if (!pidgin_tree_view_search_equal_func(model, column, key, iter, data))
5715 return FALSE;
5716
5717 /* If the search string does not match the displayed label, then look
5718 * at the alternate labels for the nodes and search in them. Currently,
5719 * alternate labels that make sense are usernames/email addresses for
5720 * buddies (but only for the ones who don't have a local alias).
5721 */
5722
5723 gtk_tree_model_get(model, iter, NODE_COLUMN, &node, -1);
5724 if (!node)
5725 return TRUE;
5726
5727 compare = NULL;
5728 if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
5729 PurpleBuddy *b = purple_contact_get_priority_buddy(PURPLE_CONTACT(node));
5730 if (!purple_buddy_get_local_buddy_alias(b))
5731 compare = purple_buddy_get_name(b);
5732 } else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
5733 if (!purple_buddy_get_local_buddy_alias(PURPLE_BUDDY(node)))
5734 compare = purple_buddy_get_name(PURPLE_BUDDY(node));
5735 }
5736
5737 if (compare) {
5738 char *tmp, *enteredstring;
5739 tmp = g_utf8_normalize(key, -1, G_NORMALIZE_DEFAULT);
5740 enteredstring = g_utf8_casefold(tmp, -1);
5741 g_free(tmp);
5742
5743 if (purple_str_has_prefix(compare, enteredstring))
5744 res = FALSE;
5745 g_free(enteredstring);
5746 }
5747
5748 return res;
5749 }
5750
pidgin_blist_show(PurpleBuddyList * list)5751 static void pidgin_blist_show(PurpleBuddyList *list)
5752 {
5753 PidginBuddyListPrivate *priv;
5754 void *handle;
5755 GtkTreeViewColumn *column;
5756 GtkWidget *menu;
5757 GtkWidget *ebox;
5758 GtkWidget *sep;
5759 GtkWidget *label;
5760 GtkWidget *close;
5761 char *pretty, *tmp;
5762 const char *theme_name;
5763 GtkAccelGroup *accel_group;
5764 GtkTreeSelection *selection;
5765 GtkTargetEntry dte[] = {{"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP, DRAG_ROW},
5766 {"application/x-im-contact", 0, DRAG_BUDDY},
5767 {"text/x-vcard", 0, DRAG_VCARD },
5768 {"text/uri-list", 0, DRAG_URI},
5769 {"text/plain", 0, DRAG_TEXT}};
5770 GtkTargetEntry ste[] = {{"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP, DRAG_ROW},
5771 {"application/x-im-contact", 0, DRAG_BUDDY},
5772 {"text/x-vcard", 0, DRAG_VCARD }};
5773 if (gtkblist && gtkblist->window) {
5774 purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_visible"));
5775 return;
5776 }
5777
5778 gtkblist = PIDGIN_BLIST(list);
5779 priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
5780
5781 if (priv->current_theme)
5782 g_object_unref(priv->current_theme);
5783
5784 theme_name = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/blist/theme");
5785 if (theme_name && *theme_name)
5786 priv->current_theme = g_object_ref(PIDGIN_BLIST_THEME(purple_theme_manager_find_theme(theme_name, "blist")));
5787 else
5788 priv->current_theme = NULL;
5789
5790 gtkblist->empty_avatar = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 32, 32);
5791 gdk_pixbuf_fill(gtkblist->empty_avatar, 0x00000000);
5792
5793 gtkblist->window = pidgin_create_window(_("Buddy List"), 0, "buddy_list", TRUE);
5794 g_signal_connect(G_OBJECT(gtkblist->window), "focus-in-event",
5795 G_CALLBACK(blist_focus_cb), gtkblist);
5796 g_signal_connect(G_OBJECT(gtkblist->window), "focus-out-event",
5797 G_CALLBACK(blist_focus_cb), gtkblist);
5798 GTK_WINDOW(gtkblist->window)->allow_shrink = TRUE;
5799
5800 gtkblist->main_vbox = gtk_vbox_new(FALSE, 0);
5801 gtk_widget_show(gtkblist->main_vbox);
5802 gtk_container_add(GTK_CONTAINER(gtkblist->window), gtkblist->main_vbox);
5803
5804 g_signal_connect(G_OBJECT(gtkblist->window), "delete_event", G_CALLBACK(gtk_blist_delete_cb), NULL);
5805 g_signal_connect(G_OBJECT(gtkblist->window), "configure_event", G_CALLBACK(gtk_blist_configure_cb), NULL);
5806 g_signal_connect(G_OBJECT(gtkblist->window), "visibility_notify_event", G_CALLBACK(gtk_blist_visibility_cb), NULL);
5807 g_signal_connect(G_OBJECT(gtkblist->window), "window_state_event", G_CALLBACK(gtk_blist_window_state_cb), NULL);
5808 g_signal_connect(G_OBJECT(gtkblist->window), "key_press_event", G_CALLBACK(gtk_blist_window_key_press_cb), gtkblist);
5809 gtk_widget_add_events(gtkblist->window, GDK_VISIBILITY_NOTIFY_MASK);
5810
5811 /******************************* Menu bar *************************************/
5812 accel_group = gtk_accel_group_new();
5813 gtk_window_add_accel_group(GTK_WINDOW (gtkblist->window), accel_group);
5814 g_object_unref(accel_group);
5815 gtkblist->ift = gtk_item_factory_new(GTK_TYPE_MENU_BAR, "<PurpleMain>", accel_group);
5816 gtk_item_factory_set_translate_func(gtkblist->ift,
5817 (GtkTranslateFunc)item_factory_translate_func,
5818 NULL, NULL);
5819 gtk_item_factory_create_items(gtkblist->ift, sizeof(blist_menu) / sizeof(*blist_menu),
5820 blist_menu, NULL);
5821 pidgin_load_accels();
5822 g_signal_connect(G_OBJECT(accel_group), "accel-changed", G_CALLBACK(pidgin_save_accels_cb), NULL);
5823
5824 menu = gtk_item_factory_get_widget(gtkblist->ift, "<PurpleMain>");
5825 gtkblist->menutray = pidgin_menu_tray_new();
5826 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtkblist->menutray);
5827 gtk_widget_show(gtkblist->menutray);
5828 gtk_widget_show(menu);
5829 gtk_box_pack_start(GTK_BOX(gtkblist->main_vbox), menu, FALSE, FALSE, 0);
5830
5831 accountmenu = gtk_item_factory_get_widget(gtkblist->ift, N_("/Accounts"));
5832
5833
5834 /****************************** Notebook *************************************/
5835 gtkblist->notebook = gtk_notebook_new();
5836 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(gtkblist->notebook), FALSE);
5837 gtk_notebook_set_show_border(GTK_NOTEBOOK(gtkblist->notebook), FALSE);
5838 gtk_box_pack_start(GTK_BOX(gtkblist->main_vbox), gtkblist->notebook, TRUE, TRUE, 0);
5839
5840 #if 0
5841 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist->notebook), kiosk_page(), NULL);
5842 #endif
5843
5844 /* Translators: Please maintain the use of -> and <- to refer to menu heirarchy */
5845 tmp = g_strdup_printf(_("<span weight='bold' size='larger'>Welcome to %s!</span>\n\n"
5846
5847 "You have no accounts enabled. Enable your IM accounts from the "
5848 "<b>Accounts</b> window at <b>Accounts->Manage Accounts</b>. Once you "
5849 "enable accounts, you'll be able to sign on, set your status, "
5850 "and talk to your friends."), PIDGIN_NAME);
5851 pretty = pidgin_make_pretty_arrows(tmp);
5852 g_free(tmp);
5853 label = gtk_label_new(NULL);
5854 gtk_widget_set_size_request(label, purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width") - 12, -1);
5855 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
5856 gtk_misc_set_alignment(GTK_MISC(label), 0.5, 0.2);
5857 gtk_label_set_markup(GTK_LABEL(label), pretty);
5858 g_free(pretty);
5859 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist->notebook),label, NULL);
5860 gtkblist->vbox = gtk_vbox_new(FALSE, 0);
5861 gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist->notebook), gtkblist->vbox, NULL);
5862 gtk_widget_show_all(gtkblist->notebook);
5863 pidgin_blist_select_notebook_page(gtkblist);
5864
5865 ebox = gtk_event_box_new();
5866 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), ebox, FALSE, FALSE, 0);
5867 gtkblist->headline_hbox = gtk_hbox_new(FALSE, 3);
5868 gtk_container_set_border_width(GTK_CONTAINER(gtkblist->headline_hbox), 6);
5869 gtk_container_add(GTK_CONTAINER(ebox), gtkblist->headline_hbox);
5870 gtkblist->headline_image = gtk_image_new_from_pixbuf(NULL);
5871 gtk_misc_set_alignment(GTK_MISC(gtkblist->headline_image), 0.0, 0);
5872 gtkblist->headline_label = gtk_label_new(NULL);
5873 gtk_widget_set_size_request(gtkblist->headline_label,
5874 purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width")-25,-1);
5875 gtk_label_set_line_wrap(GTK_LABEL(gtkblist->headline_label), TRUE);
5876 gtk_box_pack_start(GTK_BOX(gtkblist->headline_hbox), gtkblist->headline_image, FALSE, FALSE, 0);
5877 gtk_box_pack_start(GTK_BOX(gtkblist->headline_hbox), gtkblist->headline_label, TRUE, TRUE, 0);
5878 g_signal_connect(gtkblist->headline_label, /* connecting on headline_hbox doesn't work, because
5879 the signal is not emitted when theme is changed */
5880 "style-set",
5881 G_CALLBACK(headline_style_set),
5882 NULL);
5883 g_signal_connect (gtkblist->headline_hbox,
5884 "expose_event",
5885 G_CALLBACK (paint_headline_hbox),
5886 NULL);
5887 gtk_widget_set_name(gtkblist->headline_hbox, "gtk-tooltips");
5888
5889 gtkblist->headline_close = gtk_widget_render_icon(ebox, GTK_STOCK_CLOSE,
5890 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC), NULL);
5891 gtkblist->hand_cursor = gdk_cursor_new (GDK_HAND2);
5892 gtkblist->arrow_cursor = gdk_cursor_new (GDK_LEFT_PTR);
5893
5894 /* Close button. */
5895 close = gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
5896 close = pidgin_create_small_button(close);
5897 gtk_box_pack_start(GTK_BOX(gtkblist->headline_hbox), close, FALSE, FALSE, 0);
5898 #if GTK_CHECK_VERSION(2,12,0)
5899 gtk_widget_set_tooltip_text(close, _("Close"));
5900 #endif
5901 g_signal_connect(close, "clicked", G_CALLBACK(headline_close_press_cb), gtkblist);
5902
5903 g_signal_connect(G_OBJECT(ebox), "enter-notify-event", G_CALLBACK(headline_box_enter_cb), gtkblist);
5904 g_signal_connect(G_OBJECT(ebox), "leave-notify-event", G_CALLBACK(headline_box_leave_cb), gtkblist);
5905 g_signal_connect(G_OBJECT(ebox), "button-press-event", G_CALLBACK(headline_box_press_cb), gtkblist);
5906
5907 /****************************** GtkTreeView **********************************/
5908 gtkblist->treemodel = gtk_tree_store_new(BLIST_COLUMNS,
5909 GDK_TYPE_PIXBUF, /* Status icon */
5910 G_TYPE_BOOLEAN, /* Status icon visible */
5911 G_TYPE_STRING, /* Name */
5912 G_TYPE_STRING, /* Idle */
5913 G_TYPE_BOOLEAN, /* Idle visible */
5914 GDK_TYPE_PIXBUF, /* Buddy icon */
5915 G_TYPE_BOOLEAN, /* Buddy icon visible */
5916 G_TYPE_POINTER, /* Node */
5917 GDK_TYPE_COLOR, /* bgcolor */
5918 G_TYPE_BOOLEAN, /* Group expander */
5919 G_TYPE_BOOLEAN, /* Group expander visible */
5920 G_TYPE_BOOLEAN, /* Contact expander */
5921 G_TYPE_BOOLEAN, /* Contact expander visible */
5922 GDK_TYPE_PIXBUF, /* Emblem */
5923 G_TYPE_BOOLEAN, /* Emblem visible */
5924 GDK_TYPE_PIXBUF, /* Protocol icon */
5925 G_TYPE_BOOLEAN /* Protocol visible */
5926 );
5927
5928 gtkblist->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(gtkblist->treemodel));
5929
5930 gtk_widget_show(gtkblist->treeview);
5931 gtk_widget_set_name(gtkblist->treeview, "pidgin_blist_treeview");
5932
5933 g_signal_connect(gtkblist->treeview,
5934 "style-set",
5935 G_CALLBACK(treeview_style_set), list);
5936 /* Set up selection stuff */
5937 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
5938 g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(pidgin_blist_selection_changed), NULL);
5939
5940 /* Set up dnd */
5941 gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(gtkblist->treeview),
5942 GDK_BUTTON1_MASK, ste, 3,
5943 GDK_ACTION_COPY);
5944 gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(gtkblist->treeview),
5945 dte, 5,
5946 GDK_ACTION_COPY | GDK_ACTION_MOVE);
5947
5948 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-data-received", G_CALLBACK(pidgin_blist_drag_data_rcv_cb), NULL);
5949 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-data-get", G_CALLBACK(pidgin_blist_drag_data_get_cb), NULL);
5950 #ifdef _WIN32
5951 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-begin", G_CALLBACK(pidgin_blist_drag_begin), NULL);
5952 #endif
5953 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-motion", G_CALLBACK(pidgin_blist_drag_motion_cb), NULL);
5954 g_signal_connect(G_OBJECT(gtkblist->treeview), "motion-notify-event", G_CALLBACK(pidgin_blist_motion_cb), NULL);
5955 g_signal_connect(G_OBJECT(gtkblist->treeview), "leave-notify-event", G_CALLBACK(pidgin_blist_leave_cb), NULL);
5956
5957 /* Tooltips */
5958 pidgin_tooltip_setup_for_treeview(gtkblist->treeview, NULL,
5959 pidgin_blist_create_tooltip,
5960 pidgin_blist_paint_tip);
5961
5962 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(gtkblist->treeview), FALSE);
5963
5964 /* expander columns */
5965 column = gtk_tree_view_column_new();
5966 gtk_tree_view_append_column(GTK_TREE_VIEW(gtkblist->treeview), column);
5967 gtk_tree_view_column_set_visible(column, FALSE);
5968 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(gtkblist->treeview), column);
5969
5970 /* everything else column */
5971 gtkblist->text_column = gtk_tree_view_column_new ();
5972 gtk_tree_view_append_column(GTK_TREE_VIEW(gtkblist->treeview), gtkblist->text_column);
5973 pidgin_blist_build_layout(list);
5974
5975 g_signal_connect(G_OBJECT(gtkblist->treeview), "row-activated", G_CALLBACK(gtk_blist_row_activated_cb), NULL);
5976 g_signal_connect(G_OBJECT(gtkblist->treeview), "row-expanded", G_CALLBACK(gtk_blist_row_expanded_cb), NULL);
5977 g_signal_connect(G_OBJECT(gtkblist->treeview), "row-collapsed", G_CALLBACK(gtk_blist_row_collapsed_cb), NULL);
5978 g_signal_connect(G_OBJECT(gtkblist->treeview), "button-press-event", G_CALLBACK(gtk_blist_button_press_cb), NULL);
5979 g_signal_connect(G_OBJECT(gtkblist->treeview), "key-press-event", G_CALLBACK(gtk_blist_key_press_cb), NULL);
5980 g_signal_connect(G_OBJECT(gtkblist->treeview), "popup-menu", G_CALLBACK(pidgin_blist_popup_menu_cb), NULL);
5981
5982 /* Enable CTRL+F searching */
5983 gtk_tree_view_set_search_column(GTK_TREE_VIEW(gtkblist->treeview), NAME_COLUMN);
5984 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(gtkblist->treeview),
5985 pidgin_blist_search_equal_func, NULL, NULL);
5986
5987 gtk_box_pack_start(GTK_BOX(gtkblist->vbox),
5988 pidgin_make_scrollable(gtkblist->treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_NONE, -1, -1),
5989 TRUE, TRUE, 0);
5990
5991 sep = gtk_hseparator_new();
5992 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), sep, FALSE, FALSE, 0);
5993
5994 gtkblist->scrollbook = pidgin_scroll_book_new();
5995 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->scrollbook, FALSE, FALSE, 0);
5996
5997 /* Create an vbox which holds the scrollbook which is actually used to
5998 * display connection errors. The vbox needs to still exist for
5999 * backwards compatibility.
6000 */
6001 gtkblist->error_buttons = gtk_vbox_new(FALSE, 0);
6002 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->error_buttons, FALSE, FALSE, 0);
6003 gtk_container_set_border_width(GTK_CONTAINER(gtkblist->error_buttons), 0);
6004
6005 priv->error_scrollbook = PIDGIN_SCROLL_BOOK(pidgin_scroll_book_new());
6006 gtk_box_pack_start(GTK_BOX(gtkblist->error_buttons),
6007 GTK_WIDGET(priv->error_scrollbook), FALSE, FALSE, 0);
6008
6009
6010 /* Add the statusbox */
6011 gtkblist->statusbox = pidgin_status_box_new();
6012 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->statusbox, FALSE, TRUE, 0);
6013 gtk_widget_set_name(gtkblist->statusbox, "pidgin_blist_statusbox");
6014 gtk_container_set_border_width(GTK_CONTAINER(gtkblist->statusbox), 3);
6015 gtk_widget_show(gtkblist->statusbox);
6016
6017 /* set the Show Offline Buddies option. must be done
6018 * after the treeview or faceprint gets mad. -Robot101
6019 */
6020 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Offline Buddies"))),
6021 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies"));
6022
6023 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Empty Groups"))),
6024 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups"));
6025
6026 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Tools/Mute Sounds"))),
6027 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/sound/mute"));
6028
6029 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Buddy Details"))),
6030 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"));
6031
6032 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Idle Times"))),
6033 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time"));
6034
6035 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Protocol Icons"))),
6036 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"));
6037
6038 if(purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/sound/method"), "none"))
6039 gtk_widget_set_sensitive(gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Mute Sounds")), FALSE);
6040
6041 /* Update some dynamic things */
6042 update_menu_bar(gtkblist);
6043 pidgin_blist_update_plugin_actions();
6044 pidgin_blist_update_sort_methods();
6045
6046 /* OK... let's show this bad boy. */
6047 pidgin_blist_refresh(list);
6048 pidgin_blist_restore_position();
6049 gtk_widget_show_all(GTK_WIDGET(gtkblist->vbox));
6050 gtk_widget_realize(GTK_WIDGET(gtkblist->window));
6051 purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_visible"));
6052
6053 /* start the refresh timer */
6054 gtkblist->refresh_timer = purple_timeout_add_seconds(30, (GSourceFunc)pidgin_blist_refresh_timer, list);
6055
6056 handle = pidgin_blist_get_handle();
6057
6058 /* things that affect how buddies are displayed */
6059 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_buddy_icons",
6060 _prefs_change_redo_list, NULL);
6061 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_idle_time",
6062 _prefs_change_redo_list, NULL);
6063 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_empty_groups",
6064 _prefs_change_redo_list, NULL);
6065 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_offline_buddies",
6066 _prefs_change_redo_list, NULL);
6067 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_protocol_icons",
6068 _prefs_change_redo_list, NULL);
6069
6070 /* sorting */
6071 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/sort_type",
6072 _prefs_change_sort_method, NULL);
6073
6074 /* menus */
6075 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/sound/mute",
6076 pidgin_blist_mute_pref_cb, NULL);
6077 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/sound/method",
6078 pidgin_blist_sound_method_pref_cb, NULL);
6079
6080 /* Setup some purple signal handlers. */
6081
6082 handle = purple_accounts_get_handle();
6083 purple_signal_connect(handle, "account-enabled", gtkblist,
6084 PURPLE_CALLBACK(account_modified), gtkblist);
6085 purple_signal_connect(handle, "account-disabled", gtkblist,
6086 PURPLE_CALLBACK(account_modified), gtkblist);
6087 purple_signal_connect(handle, "account-removed", gtkblist,
6088 PURPLE_CALLBACK(account_modified), gtkblist);
6089 purple_signal_connect(handle, "account-status-changed", gtkblist,
6090 PURPLE_CALLBACK(account_status_changed),
6091 gtkblist);
6092 purple_signal_connect(handle, "account-error-changed", gtkblist,
6093 PURPLE_CALLBACK(update_account_error_state),
6094 gtkblist);
6095 purple_signal_connect(handle, "account-actions-changed", gtkblist,
6096 PURPLE_CALLBACK(account_actions_changed), NULL);
6097
6098 handle = pidgin_account_get_handle();
6099 purple_signal_connect(handle, "account-modified", gtkblist,
6100 PURPLE_CALLBACK(account_modified), gtkblist);
6101
6102 handle = purple_connections_get_handle();
6103 purple_signal_connect(handle, "signed-on", gtkblist,
6104 PURPLE_CALLBACK(sign_on_off_cb), list);
6105 purple_signal_connect(handle, "signed-off", gtkblist,
6106 PURPLE_CALLBACK(sign_on_off_cb), list);
6107
6108 handle = purple_plugins_get_handle();
6109 purple_signal_connect(handle, "plugin-load", gtkblist,
6110 PURPLE_CALLBACK(plugin_changed_cb), NULL);
6111 purple_signal_connect(handle, "plugin-unload", gtkblist,
6112 PURPLE_CALLBACK(plugin_changed_cb), NULL);
6113
6114 handle = purple_conversations_get_handle();
6115 purple_signal_connect(handle, "conversation-updated", gtkblist,
6116 PURPLE_CALLBACK(conversation_updated_cb),
6117 gtkblist);
6118 purple_signal_connect(handle, "deleting-conversation", gtkblist,
6119 PURPLE_CALLBACK(conversation_deleting_cb),
6120 gtkblist);
6121 purple_signal_connect(handle, "conversation-created", gtkblist,
6122 PURPLE_CALLBACK(conversation_created_cb),
6123 gtkblist);
6124 purple_signal_connect(handle, "chat-joined", gtkblist,
6125 PURPLE_CALLBACK(chat_joined_cb),
6126 gtkblist);
6127
6128 gtk_widget_hide(gtkblist->headline_hbox);
6129
6130 show_initial_account_errors(gtkblist);
6131
6132 /* emit our created signal */
6133 handle = pidgin_blist_get_handle();
6134 purple_signal_emit(handle, "gtkblist-created", list);
6135 }
6136
redo_buddy_list(PurpleBuddyList * list,gboolean remove,gboolean rerender)6137 static void redo_buddy_list(PurpleBuddyList *list, gboolean remove, gboolean rerender)
6138 {
6139 PurpleBlistNode *node;
6140
6141 gtkblist = PIDGIN_BLIST(list);
6142 if(!gtkblist || !gtkblist->treeview)
6143 return;
6144
6145 node = list->root;
6146
6147 while (node)
6148 {
6149 /* This is only needed when we're reverting to a non-GTK+ sorted
6150 * status. We shouldn't need to remove otherwise.
6151 */
6152 if (remove && !PURPLE_BLIST_NODE_IS_GROUP(node))
6153 pidgin_blist_hide_node(list, node, FALSE);
6154
6155 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
6156 pidgin_blist_update_buddy(list, node, rerender);
6157 else if (PURPLE_BLIST_NODE_IS_CHAT(node))
6158 pidgin_blist_update(list, node);
6159 else if (PURPLE_BLIST_NODE_IS_GROUP(node))
6160 pidgin_blist_update(list, node);
6161 node = purple_blist_node_next(node, FALSE);
6162 }
6163
6164 }
6165
pidgin_blist_refresh(PurpleBuddyList * list)6166 void pidgin_blist_refresh(PurpleBuddyList *list)
6167 {
6168 redo_buddy_list(list, FALSE, TRUE);
6169 }
6170
6171 void
pidgin_blist_update_refresh_timeout()6172 pidgin_blist_update_refresh_timeout()
6173 {
6174 PurpleBuddyList *blist;
6175 PidginBuddyList *gtkblist;
6176
6177 blist = purple_get_blist();
6178 gtkblist = PIDGIN_BLIST(purple_get_blist());
6179
6180 gtkblist->refresh_timer = purple_timeout_add_seconds(30,(GSourceFunc)pidgin_blist_refresh_timer, blist);
6181 }
6182
get_iter_from_node(PurpleBlistNode * node,GtkTreeIter * iter)6183 static gboolean get_iter_from_node(PurpleBlistNode *node, GtkTreeIter *iter) {
6184 struct _pidgin_blist_node *gtknode = (struct _pidgin_blist_node *)node->ui_data;
6185 GtkTreePath *path;
6186
6187 if (!gtknode) {
6188 return FALSE;
6189 }
6190
6191 if (!gtkblist) {
6192 purple_debug_error("gtkblist", "get_iter_from_node was called, but we don't seem to have a blist\n");
6193 return FALSE;
6194 }
6195
6196 if (!gtknode->row)
6197 return FALSE;
6198
6199
6200 if ((path = gtk_tree_row_reference_get_path(gtknode->row)) == NULL)
6201 return FALSE;
6202
6203 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), iter, path)) {
6204 gtk_tree_path_free(path);
6205 return FALSE;
6206 }
6207 gtk_tree_path_free(path);
6208 return TRUE;
6209 }
6210
pidgin_blist_remove(PurpleBuddyList * list,PurpleBlistNode * node)6211 static void pidgin_blist_remove(PurpleBuddyList *list, PurpleBlistNode *node)
6212 {
6213 struct _pidgin_blist_node *gtknode = node->ui_data;
6214
6215 purple_request_close_with_handle(node);
6216
6217 pidgin_blist_hide_node(list, node, TRUE);
6218
6219 if(node->parent)
6220 pidgin_blist_update(list, node->parent);
6221
6222 /* There's something I don't understand here - Ethan */
6223 /* Ethan said that back in 2003, but this g_free has been left commented
6224 * out ever since. I can't find any reason at all why this is bad and
6225 * valgrind found several reasons why it's good. If this causes problems
6226 * comment it out again. Stu */
6227 /* Of course it still causes problems - this breaks dragging buddies into
6228 * contacts, the dragged buddy mysteriously 'disappears'. Stu. */
6229 /* I think it's fixed now. Stu. */
6230
6231 if(gtknode) {
6232 if(gtknode->recent_signonoff_timer > 0)
6233 purple_timeout_remove(gtknode->recent_signonoff_timer);
6234
6235 purple_signals_disconnect_by_handle(node->ui_data);
6236 g_free(node->ui_data);
6237 node->ui_data = NULL;
6238 }
6239 }
6240
do_selection_changed(PurpleBlistNode * new_selection)6241 static gboolean do_selection_changed(PurpleBlistNode *new_selection)
6242 {
6243 PurpleBlistNode *old_selection = NULL;
6244
6245 /* test for gtkblist because crazy timeout means we can be called after the blist is gone */
6246 if (gtkblist && new_selection != gtkblist->selected_node) {
6247 old_selection = gtkblist->selected_node;
6248 gtkblist->selected_node = new_selection;
6249 if(new_selection)
6250 pidgin_blist_update(NULL, new_selection);
6251 if(old_selection)
6252 pidgin_blist_update(NULL, old_selection);
6253 }
6254
6255 return FALSE;
6256 }
6257
pidgin_blist_selection_changed(GtkTreeSelection * selection,gpointer data)6258 static void pidgin_blist_selection_changed(GtkTreeSelection *selection, gpointer data)
6259 {
6260 PurpleBlistNode *new_selection = NULL;
6261 GtkTreeIter iter;
6262
6263 if(gtk_tree_selection_get_selected(selection, NULL, &iter)){
6264 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
6265 NODE_COLUMN, &new_selection, -1);
6266 }
6267
6268 /* we set this up as a timeout, otherwise the blist flickers ...
6269 * but we don't do it for groups, because it causes total bizarness -
6270 * the previously selected buddy node might rendered at half height.
6271 */
6272 if ((new_selection != NULL) && PURPLE_BLIST_NODE_IS_GROUP(new_selection)) {
6273 do_selection_changed(new_selection);
6274 } else {
6275 g_timeout_add(0, (GSourceFunc)do_selection_changed, new_selection);
6276 }
6277 }
6278
insert_node(PurpleBuddyList * list,PurpleBlistNode * node,GtkTreeIter * iter)6279 static gboolean insert_node(PurpleBuddyList *list, PurpleBlistNode *node, GtkTreeIter *iter)
6280 {
6281 GtkTreeIter parent_iter, cur, *curptr = NULL;
6282 struct _pidgin_blist_node *gtknode = node->ui_data;
6283 GtkTreePath *newpath;
6284
6285 if(!iter)
6286 return FALSE;
6287
6288 /* XXX: it's not necessary, but let's silence a warning*/
6289 memset(&parent_iter, 0, sizeof(parent_iter));
6290
6291 if(node->parent && !get_iter_from_node(node->parent, &parent_iter))
6292 return FALSE;
6293
6294 if(get_iter_from_node(node, &cur))
6295 curptr = &cur;
6296
6297 if(PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_CHAT(node)) {
6298 current_sort_method->func(node, list, parent_iter, curptr, iter);
6299 } else {
6300 sort_method_none(node, list, parent_iter, curptr, iter);
6301 }
6302
6303 if(gtknode != NULL) {
6304 gtk_tree_row_reference_free(gtknode->row);
6305 } else {
6306 pidgin_blist_new_node(node);
6307 gtknode = (struct _pidgin_blist_node *)node->ui_data;
6308 }
6309
6310 newpath = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel),
6311 iter);
6312 gtknode->row =
6313 gtk_tree_row_reference_new(GTK_TREE_MODEL(gtkblist->treemodel),
6314 newpath);
6315
6316 gtk_tree_path_free(newpath);
6317
6318 if (!editing_blist)
6319 gtk_tree_store_set(gtkblist->treemodel, iter,
6320 NODE_COLUMN, node,
6321 -1);
6322
6323 if(node->parent) {
6324 GtkTreePath *expand = NULL;
6325 struct _pidgin_blist_node *gtkparentnode = node->parent->ui_data;
6326
6327 if(PURPLE_BLIST_NODE_IS_GROUP(node->parent)) {
6328 if(!purple_blist_node_get_bool(node->parent, "collapsed"))
6329 expand = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &parent_iter);
6330 } else if(PURPLE_BLIST_NODE_IS_CONTACT(node->parent) &&
6331 gtkparentnode->contact_expanded) {
6332 expand = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &parent_iter);
6333 }
6334 if(expand) {
6335 gtk_tree_view_expand_row(GTK_TREE_VIEW(gtkblist->treeview), expand, FALSE);
6336 gtk_tree_path_free(expand);
6337 }
6338 }
6339
6340 return TRUE;
6341 }
6342
pidgin_blist_group_has_show_offline_buddy(PurpleGroup * group)6343 static gboolean pidgin_blist_group_has_show_offline_buddy(PurpleGroup *group)
6344 {
6345 PurpleBlistNode *gnode, *cnode, *bnode;
6346
6347 gnode = (PurpleBlistNode *)group;
6348 for(cnode = gnode->child; cnode; cnode = cnode->next) {
6349 if(PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
6350 for(bnode = cnode->child; bnode; bnode = bnode->next) {
6351 PurpleBuddy *buddy = (PurpleBuddy *)bnode;
6352 if (purple_account_is_connected(buddy->account) &&
6353 purple_blist_node_get_bool(bnode, "show_offline"))
6354 return TRUE;
6355 }
6356 }
6357 }
6358 return FALSE;
6359 }
6360
6361 /* This version of pidgin_blist_update_group can take the original buddy or a
6362 * group, but has much better algorithmic performance with a pre-known buddy.
6363 */
pidgin_blist_update_group(PurpleBuddyList * list,PurpleBlistNode * node)6364 static void pidgin_blist_update_group(PurpleBuddyList *list,
6365 PurpleBlistNode *node)
6366 {
6367 gint count;
6368 PurpleGroup *group;
6369 PurpleBlistNode* gnode;
6370 gboolean show = FALSE, show_offline = FALSE;
6371
6372 g_return_if_fail(node != NULL);
6373
6374 if (editing_blist)
6375 return;
6376
6377 if (PURPLE_BLIST_NODE_IS_GROUP(node))
6378 gnode = node;
6379 else if (PURPLE_BLIST_NODE_IS_BUDDY(node))
6380 gnode = node->parent->parent;
6381 else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_CHAT(node))
6382 gnode = node->parent;
6383 else
6384 return;
6385
6386 group = (PurpleGroup*)gnode;
6387
6388 show_offline = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies");
6389
6390 if(show_offline)
6391 count = purple_blist_get_group_size(group, FALSE);
6392 else
6393 count = purple_blist_get_group_online_count(group);
6394
6395 if (!PURPLE_BLIST_NODE_IS_VISIBLE(gnode) || !PURPLE_BLIST_NODE_IS_VISIBLE(node))
6396 show = FALSE;
6397 else if (count > 0 || purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups"))
6398 show = TRUE;
6399 else if (PURPLE_BLIST_NODE_IS_BUDDY(node) && buddy_is_displayable((PurpleBuddy*)node)) { /* Or chat? */
6400 show = TRUE;
6401 } else if (!show_offline) {
6402 show = pidgin_blist_group_has_show_offline_buddy(group);
6403 }
6404 if (show) {
6405 gchar *title;
6406 gboolean biglist;
6407 GtkTreeIter iter;
6408 GtkTreePath *path;
6409 gboolean expanded;
6410 GdkColor *bgcolor = NULL;
6411 GdkPixbuf *avatar = NULL;
6412 PidginBlistTheme *theme = NULL;
6413
6414 if(!insert_node(list, gnode, &iter))
6415 return;
6416
6417 if ((theme = pidgin_blist_get_theme()) == NULL)
6418 bgcolor = NULL;
6419 else if (purple_blist_node_get_bool(gnode, "collapsed") || count <= 0)
6420 bgcolor = pidgin_blist_theme_get_collapsed_background_color(theme);
6421 else
6422 bgcolor = pidgin_blist_theme_get_expanded_background_color(theme);
6423
6424 path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
6425 expanded = gtk_tree_view_row_expanded(GTK_TREE_VIEW(gtkblist->treeview), path);
6426 gtk_tree_path_free(path);
6427
6428 title = pidgin_get_group_title(gnode, expanded);
6429 biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6430
6431 if (biglist) {
6432 avatar = pidgin_blist_get_buddy_icon(gnode, TRUE, TRUE);
6433 }
6434
6435 gtk_tree_store_set(gtkblist->treemodel, &iter,
6436 STATUS_ICON_VISIBLE_COLUMN, FALSE,
6437 STATUS_ICON_COLUMN, NULL,
6438 NAME_COLUMN, title,
6439 NODE_COLUMN, gnode,
6440 BGCOLOR_COLUMN, bgcolor,
6441 GROUP_EXPANDER_COLUMN, TRUE,
6442 GROUP_EXPANDER_VISIBLE_COLUMN, TRUE,
6443 CONTACT_EXPANDER_VISIBLE_COLUMN, FALSE,
6444 BUDDY_ICON_COLUMN, avatar,
6445 BUDDY_ICON_VISIBLE_COLUMN, biglist,
6446 IDLE_VISIBLE_COLUMN, FALSE,
6447 EMBLEM_VISIBLE_COLUMN, FALSE,
6448 -1);
6449 g_free(title);
6450 } else {
6451 pidgin_blist_hide_node(list, gnode, TRUE);
6452 }
6453 }
6454
pidgin_get_group_title(PurpleBlistNode * gnode,gboolean expanded)6455 static char *pidgin_get_group_title(PurpleBlistNode *gnode, gboolean expanded)
6456 {
6457 PurpleGroup *group;
6458 gboolean selected;
6459 char group_count[12] = "";
6460 char *mark, *esc = NULL;
6461 PurpleBlistNode *selected_node = NULL;
6462 GtkTreeIter iter;
6463 PidginThemeFont *pair;
6464 gchar const *text_color, *text_font;
6465 PidginBlistTheme *theme;
6466
6467 group = (PurpleGroup*)gnode;
6468
6469 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview)), NULL, &iter)) {
6470 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
6471 NODE_COLUMN, &selected_node, -1);
6472 }
6473 selected = (gnode == selected_node);
6474
6475 if (!expanded) {
6476 g_snprintf(group_count, sizeof(group_count), "%d/%d",
6477 purple_blist_get_group_online_count(group),
6478 purple_blist_get_group_size(group, FALSE));
6479 }
6480
6481 theme = pidgin_blist_get_theme();
6482 if (theme == NULL)
6483 pair = NULL;
6484 else if (expanded)
6485 pair = pidgin_blist_theme_get_expanded_text_info(theme);
6486 else
6487 pair = pidgin_blist_theme_get_collapsed_text_info(theme);
6488
6489
6490 text_color = selected ? NULL : theme_font_get_color_default(pair, NULL);
6491 text_font = theme_font_get_face_default(pair, "");
6492
6493 if(group != NULL) {
6494 esc = g_markup_escape_text(group->name, -1);
6495 }
6496
6497 if (text_color) {
6498 mark = g_strdup_printf("<span foreground='%s' font_desc='%s'><b>%s</b>%s%s%s</span>",
6499 text_color, text_font,
6500 esc ? esc : "",
6501 !expanded ? " <span weight='light'>(</span>" : "",
6502 group_count,
6503 !expanded ? "<span weight='light'>)</span>" : "");
6504 } else {
6505 mark = g_strdup_printf("<span font_desc='%s'><b>%s</b>%s%s%s</span>",
6506 text_font, esc ? esc : "",
6507 !expanded ? " <span weight='light'>(</span>" : "",
6508 group_count,
6509 !expanded ? "<span weight='light'>)</span>" : "");
6510 }
6511
6512 g_free(esc);
6513 return mark;
6514 }
6515
buddy_node(PurpleBuddy * buddy,GtkTreeIter * iter,PurpleBlistNode * node)6516 static void buddy_node(PurpleBuddy *buddy, GtkTreeIter *iter, PurpleBlistNode *node)
6517 {
6518 PurplePresence *presence = purple_buddy_get_presence(buddy);
6519 GdkPixbuf *status, *avatar, *emblem, *prpl_icon;
6520 GdkColor *color = NULL;
6521 char *mark;
6522 char *idle = NULL;
6523 gboolean expanded = ((struct _pidgin_blist_node *)(node->parent->ui_data))->contact_expanded;
6524 gboolean selected = (gtkblist->selected_node == node);
6525 gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6526 PidginBlistTheme *theme;
6527
6528 if (editing_blist)
6529 return;
6530
6531 status = pidgin_blist_get_status_icon((PurpleBlistNode*)buddy,
6532 biglist ? PIDGIN_STATUS_ICON_LARGE : PIDGIN_STATUS_ICON_SMALL);
6533
6534 /* Speed it up if we don't want buddy icons. */
6535 if(biglist)
6536 avatar = pidgin_blist_get_buddy_icon((PurpleBlistNode *)buddy, TRUE, TRUE);
6537 else
6538 avatar = NULL;
6539
6540 if (!avatar) {
6541 g_object_ref(G_OBJECT(gtkblist->empty_avatar));
6542 avatar = gtkblist->empty_avatar;
6543 } else if ((!PURPLE_BUDDY_IS_ONLINE(buddy) || purple_presence_is_idle(presence))) {
6544 do_alphashift(avatar, 77);
6545 }
6546
6547 emblem = pidgin_blist_get_emblem((PurpleBlistNode*) buddy);
6548 mark = pidgin_blist_get_name_markup(buddy, selected, TRUE);
6549
6550 theme = pidgin_blist_get_theme();
6551
6552 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time") &&
6553 purple_presence_is_idle(presence) && !biglist)
6554 {
6555 time_t idle_secs = purple_presence_get_idle_time(presence);
6556
6557 if (idle_secs > 0)
6558 {
6559 PidginThemeFont *pair = NULL;
6560 const gchar *textcolor;
6561 time_t t;
6562 int ihrs, imin;
6563 time(&t);
6564
6565 ihrs = (t - idle_secs) / 3600;
6566 imin = ((t - idle_secs) / 60) % 60;
6567
6568 if (selected)
6569 textcolor = NULL;
6570 else if (theme != NULL && (pair = pidgin_blist_theme_get_idle_text_info(theme)) != NULL)
6571 textcolor = pidgin_theme_font_get_color_describe(pair);
6572 else
6573 /* If no theme them default to making idle buddy names grey */
6574 textcolor = pidgin_style_is_dark(NULL) ? "light slate grey" : "dim grey";
6575
6576 if (textcolor) {
6577 idle = g_strdup_printf("<span color='%s' font_desc='%s'>%d:%02d</span>",
6578 textcolor, theme_font_get_face_default(pair, ""),
6579 ihrs, imin);
6580 } else {
6581 idle = g_strdup_printf("<span font_desc='%s'>%d:%02d</span>",
6582 theme_font_get_face_default(pair, ""),
6583 ihrs, imin);
6584 }
6585 }
6586 }
6587
6588 prpl_icon = pidgin_create_prpl_icon(buddy->account, PIDGIN_PRPL_ICON_SMALL);
6589
6590 if (theme != NULL)
6591 color = pidgin_blist_theme_get_contact_color(theme);
6592
6593 gtk_tree_store_set(gtkblist->treemodel, iter,
6594 STATUS_ICON_COLUMN, status,
6595 STATUS_ICON_VISIBLE_COLUMN, TRUE,
6596 NAME_COLUMN, mark,
6597 IDLE_COLUMN, idle,
6598 IDLE_VISIBLE_COLUMN, !biglist && idle,
6599 BUDDY_ICON_COLUMN, avatar,
6600 BUDDY_ICON_VISIBLE_COLUMN, biglist,
6601 EMBLEM_COLUMN, emblem,
6602 EMBLEM_VISIBLE_COLUMN, (emblem != NULL),
6603 PROTOCOL_ICON_COLUMN, prpl_icon,
6604 PROTOCOL_ICON_VISIBLE_COLUMN, purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"),
6605 BGCOLOR_COLUMN, color,
6606 CONTACT_EXPANDER_COLUMN, NULL,
6607 CONTACT_EXPANDER_VISIBLE_COLUMN, expanded,
6608 GROUP_EXPANDER_VISIBLE_COLUMN, FALSE,
6609 -1);
6610
6611 g_free(mark);
6612 g_free(idle);
6613 if(emblem)
6614 g_object_unref(emblem);
6615 if(status)
6616 g_object_unref(status);
6617 if(avatar)
6618 g_object_unref(avatar);
6619 if(prpl_icon)
6620 g_object_unref(prpl_icon);
6621 }
6622
6623 /* This is a variation on the original gtk_blist_update_contact. Here we
6624 can know in advance which buddy has changed so we can just update that */
pidgin_blist_update_contact(PurpleBuddyList * list,PurpleBlistNode * node)6625 static void pidgin_blist_update_contact(PurpleBuddyList *list, PurpleBlistNode *node)
6626 {
6627 PurpleBlistNode *cnode;
6628 PurpleContact *contact;
6629 PurpleBuddy *buddy;
6630 gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6631 struct _pidgin_blist_node *gtknode;
6632
6633 if (editing_blist)
6634 return;
6635
6636 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
6637 cnode = node->parent;
6638 else
6639 cnode = node;
6640
6641 g_return_if_fail(PURPLE_BLIST_NODE_IS_CONTACT(cnode));
6642
6643 /* First things first, update the group */
6644 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
6645 pidgin_blist_update_group(list, node);
6646 else
6647 pidgin_blist_update_group(list, cnode->parent);
6648
6649 contact = (PurpleContact*)cnode;
6650 buddy = purple_contact_get_priority_buddy(contact);
6651
6652 if (buddy_is_displayable(buddy))
6653 {
6654 GtkTreeIter iter;
6655
6656 if(!insert_node(list, cnode, &iter))
6657 return;
6658
6659 gtknode = (struct _pidgin_blist_node *)cnode->ui_data;
6660
6661 if(gtknode->contact_expanded) {
6662 GdkPixbuf *status;
6663 gchar *mark, *tmp;
6664 const gchar *fg_color, *font;
6665 GdkColor *color = NULL;
6666 PidginBlistTheme *theme;
6667 PidginThemeFont *pair;
6668 gboolean selected = (gtkblist->selected_node == cnode);
6669
6670 mark = g_markup_escape_text(purple_contact_get_alias(contact), -1);
6671
6672 theme = pidgin_blist_get_theme();
6673 if (theme == NULL)
6674 pair = NULL;
6675 else {
6676 pair = pidgin_blist_theme_get_contact_text_info(theme);
6677 color = pidgin_blist_theme_get_contact_color(theme);
6678 }
6679
6680 font = theme_font_get_face_default(pair, "");
6681 fg_color = selected ? NULL : theme_font_get_color_default(pair, NULL);
6682
6683 if (fg_color) {
6684 tmp = g_strdup_printf("<span font_desc='%s' color='%s'>%s</span>",
6685 font, fg_color, mark);
6686 } else {
6687 tmp = g_strdup_printf("<span font_desc='%s'>%s</span>", font,
6688 mark);
6689 }
6690 g_free(mark);
6691 mark = tmp;
6692
6693 status = pidgin_blist_get_status_icon(cnode,
6694 biglist? PIDGIN_STATUS_ICON_LARGE : PIDGIN_STATUS_ICON_SMALL);
6695
6696 gtk_tree_store_set(gtkblist->treemodel, &iter,
6697 STATUS_ICON_COLUMN, status,
6698 STATUS_ICON_VISIBLE_COLUMN, TRUE,
6699 NAME_COLUMN, mark,
6700 IDLE_COLUMN, NULL,
6701 IDLE_VISIBLE_COLUMN, FALSE,
6702 BGCOLOR_COLUMN, color,
6703 BUDDY_ICON_COLUMN, NULL,
6704 CONTACT_EXPANDER_COLUMN, TRUE,
6705 CONTACT_EXPANDER_VISIBLE_COLUMN, TRUE,
6706 GROUP_EXPANDER_VISIBLE_COLUMN, FALSE,
6707 -1);
6708 g_free(mark);
6709 if(status)
6710 g_object_unref(status);
6711 } else {
6712 buddy_node(buddy, &iter, cnode);
6713 }
6714 } else {
6715 pidgin_blist_hide_node(list, cnode, TRUE);
6716 }
6717 }
6718
6719
6720
pidgin_blist_update_buddy(PurpleBuddyList * list,PurpleBlistNode * node,gboolean status_change)6721 static void pidgin_blist_update_buddy(PurpleBuddyList *list, PurpleBlistNode *node, gboolean status_change)
6722 {
6723 PurpleBuddy *buddy;
6724 struct _pidgin_blist_node *gtkparentnode;
6725
6726 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
6727
6728 if (node->parent == NULL)
6729 return;
6730
6731 buddy = (PurpleBuddy*)node;
6732
6733 /* First things first, update the contact */
6734 pidgin_blist_update_contact(list, node);
6735
6736 gtkparentnode = (struct _pidgin_blist_node *)node->parent->ui_data;
6737
6738 if (gtkparentnode->contact_expanded && buddy_is_displayable(buddy))
6739 {
6740 GtkTreeIter iter;
6741
6742 if (!insert_node(list, node, &iter))
6743 return;
6744
6745 buddy_node(buddy, &iter, node);
6746
6747 } else {
6748 pidgin_blist_hide_node(list, node, TRUE);
6749 }
6750
6751 }
6752
pidgin_blist_update_chat(PurpleBuddyList * list,PurpleBlistNode * node)6753 static void pidgin_blist_update_chat(PurpleBuddyList *list, PurpleBlistNode *node)
6754 {
6755 PurpleChat *chat;
6756
6757 g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
6758
6759 if (editing_blist)
6760 return;
6761
6762 /* First things first, update the group */
6763 pidgin_blist_update_group(list, node->parent);
6764
6765 chat = (PurpleChat*)node;
6766
6767 if(purple_account_is_connected(chat->account)) {
6768 GtkTreeIter iter;
6769 GdkPixbuf *status, *avatar, *emblem, *prpl_icon;
6770 const gchar *color, *font;
6771 gchar *mark, *tmp;
6772 gboolean showicons = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6773 gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
6774 PidginBlistNode *ui;
6775 PurpleConversation *conv;
6776 gboolean hidden = FALSE;
6777 GdkColor *bgcolor = NULL;
6778 PidginThemeFont *pair;
6779 PidginBlistTheme *theme;
6780 gboolean selected = (gtkblist->selected_node == node);
6781 gboolean nick_said = FALSE;
6782
6783 if (!insert_node(list, node, &iter))
6784 return;
6785
6786 ui = node->ui_data;
6787 conv = ui->conv.conv;
6788 if (conv && pidgin_conv_is_hidden(PIDGIN_CONVERSATION(conv))) {
6789 hidden = (ui->conv.flags & PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE);
6790 nick_said = (ui->conv.flags & PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK);
6791 }
6792
6793 status = pidgin_blist_get_status_icon(node,
6794 biglist ? PIDGIN_STATUS_ICON_LARGE : PIDGIN_STATUS_ICON_SMALL);
6795 emblem = pidgin_blist_get_emblem(node);
6796
6797 /* Speed it up if we don't want buddy icons. */
6798 if(showicons)
6799 avatar = pidgin_blist_get_buddy_icon(node, TRUE, FALSE);
6800 else
6801 avatar = NULL;
6802
6803 mark = g_markup_escape_text(purple_chat_get_name(chat), -1);
6804
6805 theme = pidgin_blist_get_theme();
6806
6807 if (theme == NULL)
6808 pair = NULL;
6809 else if (nick_said)
6810 pair = pidgin_blist_theme_get_unread_message_nick_said_text_info(theme);
6811 else if (hidden)
6812 pair = pidgin_blist_theme_get_unread_message_text_info(theme);
6813 else pair = pidgin_blist_theme_get_online_text_info(theme);
6814
6815
6816 font = theme_font_get_face_default(pair, "");
6817 if (selected || !(color = theme_font_get_color_default(pair, NULL)))
6818 /* nick_said color is the same as gtkconv:tab-label-attention */
6819 color = (nick_said ? "#006aff" : NULL);
6820
6821 if (color) {
6822 tmp = g_strdup_printf("<span font_desc='%s' color='%s' weight='%s'>%s</span>",
6823 font, color, hidden ? "bold" : "normal", mark);
6824 } else {
6825 tmp = g_strdup_printf("<span font_desc='%s' weight='%s'>%s</span>",
6826 font, hidden ? "bold" : "normal", mark);
6827 }
6828 g_free(mark);
6829 mark = tmp;
6830
6831 prpl_icon = pidgin_create_prpl_icon(chat->account, PIDGIN_PRPL_ICON_SMALL);
6832
6833 if (theme != NULL)
6834 bgcolor = pidgin_blist_theme_get_contact_color(theme);
6835
6836 gtk_tree_store_set(gtkblist->treemodel, &iter,
6837 STATUS_ICON_COLUMN, status,
6838 STATUS_ICON_VISIBLE_COLUMN, TRUE,
6839 BUDDY_ICON_COLUMN, avatar ? avatar : gtkblist->empty_avatar,
6840 BUDDY_ICON_VISIBLE_COLUMN, showicons,
6841 EMBLEM_COLUMN, emblem,
6842 EMBLEM_VISIBLE_COLUMN, emblem != NULL,
6843 PROTOCOL_ICON_COLUMN, prpl_icon,
6844 PROTOCOL_ICON_VISIBLE_COLUMN, purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"),
6845 NAME_COLUMN, mark,
6846 BGCOLOR_COLUMN, bgcolor,
6847 GROUP_EXPANDER_VISIBLE_COLUMN, FALSE,
6848 -1);
6849
6850 g_free(mark);
6851 if(emblem)
6852 g_object_unref(emblem);
6853 if(status)
6854 g_object_unref(status);
6855 if(avatar)
6856 g_object_unref(avatar);
6857 if(prpl_icon)
6858 g_object_unref(prpl_icon);
6859
6860 } else {
6861 pidgin_blist_hide_node(list, node, TRUE);
6862 }
6863 }
6864
pidgin_blist_update(PurpleBuddyList * list,PurpleBlistNode * node)6865 static void pidgin_blist_update(PurpleBuddyList *list, PurpleBlistNode *node)
6866 {
6867 if (list)
6868 gtkblist = PIDGIN_BLIST(list);
6869 if(!gtkblist || !gtkblist->treeview || !node)
6870 return;
6871
6872 if (node->ui_data == NULL)
6873 pidgin_blist_new_node(node);
6874
6875 switch(node->type) {
6876 case PURPLE_BLIST_GROUP_NODE:
6877 pidgin_blist_update_group(list, node);
6878 break;
6879 case PURPLE_BLIST_CONTACT_NODE:
6880 pidgin_blist_update_contact(list, node);
6881 break;
6882 case PURPLE_BLIST_BUDDY_NODE:
6883 pidgin_blist_update_buddy(list, node, TRUE);
6884 break;
6885 case PURPLE_BLIST_CHAT_NODE:
6886 pidgin_blist_update_chat(list, node);
6887 break;
6888 case PURPLE_BLIST_OTHER_NODE:
6889 return;
6890 }
6891
6892 }
6893
pidgin_blist_destroy(PurpleBuddyList * list)6894 static void pidgin_blist_destroy(PurpleBuddyList *list)
6895 {
6896 PidginBuddyListPrivate *priv;
6897
6898 if (!list || !list->ui_data)
6899 return;
6900
6901 g_return_if_fail(list->ui_data == gtkblist);
6902
6903 purple_signals_disconnect_by_handle(gtkblist);
6904
6905 if (gtkblist->headline_close)
6906 g_object_unref(gtkblist->headline_close);
6907
6908 gtk_widget_destroy(gtkblist->window);
6909
6910 pidgin_blist_tooltip_destroy();
6911
6912 if (gtkblist->refresh_timer)
6913 purple_timeout_remove(gtkblist->refresh_timer);
6914 if (gtkblist->timeout)
6915 g_source_remove(gtkblist->timeout);
6916 if (gtkblist->drag_timeout)
6917 g_source_remove(gtkblist->drag_timeout);
6918
6919 g_hash_table_destroy(gtkblist->connection_errors);
6920 gtkblist->refresh_timer = 0;
6921 gtkblist->timeout = 0;
6922 gtkblist->drag_timeout = 0;
6923 gtkblist->window = gtkblist->vbox = gtkblist->treeview = NULL;
6924 g_object_unref(G_OBJECT(gtkblist->treemodel));
6925 gtkblist->treemodel = NULL;
6926 g_object_unref(G_OBJECT(gtkblist->ift));
6927 g_object_unref(G_OBJECT(gtkblist->empty_avatar));
6928
6929 gdk_cursor_unref(gtkblist->hand_cursor);
6930 gdk_cursor_unref(gtkblist->arrow_cursor);
6931 gtkblist->hand_cursor = NULL;
6932 gtkblist->arrow_cursor = NULL;
6933
6934 priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
6935 if (priv->current_theme)
6936 g_object_unref(priv->current_theme);
6937 if (priv->select_page_timeout)
6938 purple_timeout_remove(priv->select_page_timeout);
6939 g_free(priv);
6940
6941 g_free(gtkblist);
6942 accountmenu = NULL;
6943 gtkblist = NULL;
6944 purple_prefs_disconnect_by_handle(pidgin_blist_get_handle());
6945 }
6946
pidgin_blist_set_visible(PurpleBuddyList * list,gboolean show)6947 static void pidgin_blist_set_visible(PurpleBuddyList *list, gboolean show)
6948 {
6949 if (!(gtkblist && gtkblist->window))
6950 return;
6951
6952 if (show) {
6953 if(!PIDGIN_WINDOW_ICONIFIED(gtkblist->window) && !GTK_WIDGET_VISIBLE(gtkblist->window))
6954 purple_signal_emit(pidgin_blist_get_handle(), "gtkblist-unhiding", gtkblist);
6955 pidgin_blist_restore_position();
6956 gtk_window_present(GTK_WINDOW(gtkblist->window));
6957 } else {
6958 if(visibility_manager_count) {
6959 purple_signal_emit(pidgin_blist_get_handle(), "gtkblist-hiding", gtkblist);
6960 gtk_widget_hide(gtkblist->window);
6961 } else {
6962 if (!GTK_WIDGET_VISIBLE(gtkblist->window))
6963 gtk_widget_show(gtkblist->window);
6964 gtk_window_iconify(GTK_WINDOW(gtkblist->window));
6965 }
6966 }
6967 }
6968
6969 static GList *
groups_tree(void)6970 groups_tree(void)
6971 {
6972 static GList *list = NULL;
6973 char *tmp2;
6974 PurpleGroup *g;
6975 PurpleBlistNode *gnode;
6976
6977 g_list_free(list);
6978 list = NULL;
6979
6980 if (purple_get_blist()->root == NULL)
6981 {
6982 list = g_list_append(list, (gpointer)_("Buddies"));
6983 }
6984 else
6985 {
6986 for (gnode = purple_get_blist()->root;
6987 gnode != NULL;
6988 gnode = gnode->next)
6989 {
6990 if (PURPLE_BLIST_NODE_IS_GROUP(gnode))
6991 {
6992 g = (PurpleGroup *)gnode;
6993 tmp2 = g->name;
6994 list = g_list_append(list, tmp2);
6995 }
6996 }
6997 }
6998
6999 return list;
7000 }
7001
7002 static void
add_buddy_select_account_cb(GObject * w,PurpleAccount * account,PidginAddBuddyData * data)7003 add_buddy_select_account_cb(GObject *w, PurpleAccount *account,
7004 PidginAddBuddyData *data)
7005 {
7006 PurpleConnection *pc = NULL;
7007 PurplePlugin *prpl = NULL;
7008 PurplePluginProtocolInfo *prpl_info = NULL;
7009 gboolean invite_enabled = TRUE;
7010
7011 /* Save our account */
7012 data->rq_data.account = account;
7013
7014 if (account)
7015 pc = purple_account_get_connection(account);
7016 if (pc)
7017 prpl = purple_connection_get_prpl(pc);
7018 if (prpl)
7019 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
7020 if (prpl_info && !(prpl_info->options & OPT_PROTO_INVITE_MESSAGE))
7021 invite_enabled = FALSE;
7022
7023 gtk_widget_set_sensitive(data->entry_for_invite, invite_enabled);
7024 }
7025
7026 static void
destroy_add_buddy_dialog_cb(GtkWidget * win,PidginAddBuddyData * data)7027 destroy_add_buddy_dialog_cb(GtkWidget *win, PidginAddBuddyData *data)
7028 {
7029 g_free(data);
7030 }
7031
7032 static void
add_buddy_cb(GtkWidget * w,int resp,PidginAddBuddyData * data)7033 add_buddy_cb(GtkWidget *w, int resp, PidginAddBuddyData *data)
7034 {
7035 const char *grp, *who, *whoalias, *invite;
7036 PurpleAccount *account;
7037 PurpleGroup *g;
7038 PurpleBuddy *b;
7039 PurpleConversation *c;
7040 PurpleBuddyIcon *icon;
7041
7042 if (resp == GTK_RESPONSE_OK)
7043 {
7044 who = gtk_entry_get_text(GTK_ENTRY(data->entry));
7045 grp = pidgin_text_combo_box_entry_get_text(data->combo);
7046 whoalias = gtk_entry_get_text(GTK_ENTRY(data->entry_for_alias));
7047 if (*whoalias == '\0')
7048 whoalias = NULL;
7049 invite = gtk_entry_get_text(GTK_ENTRY(data->entry_for_invite));
7050 if (*invite == '\0')
7051 invite = NULL;
7052
7053 account = data->rq_data.account;
7054
7055 g = NULL;
7056 if ((grp != NULL) && (*grp != '\0'))
7057 {
7058 if ((g = purple_find_group(grp)) == NULL)
7059 {
7060 g = purple_group_new(grp);
7061 purple_blist_add_group(g, NULL);
7062 }
7063
7064 b = purple_find_buddy_in_group(account, who, g);
7065 }
7066 else if ((b = purple_find_buddy(account, who)) != NULL)
7067 {
7068 g = purple_buddy_get_group(b);
7069 }
7070
7071 if (b == NULL)
7072 {
7073 b = purple_buddy_new(account, who, whoalias);
7074 purple_blist_add_buddy(b, NULL, g, NULL);
7075 }
7076
7077 purple_account_add_buddy_with_invite(account, b, invite);
7078
7079 /* Offer to merge people with the same alias. */
7080 if (whoalias != NULL && g != NULL)
7081 gtk_blist_auto_personize((PurpleBlistNode *)g, whoalias);
7082
7083 /*
7084 * XXX
7085 * It really seems like it would be better if the call to
7086 * purple_account_add_buddy() and purple_conversation_update() were done in
7087 * blist.c, possibly in the purple_blist_add_buddy() function. Maybe
7088 * purple_account_add_buddy() should be renamed to
7089 * purple_blist_add_new_buddy() or something, and have it call
7090 * purple_blist_add_buddy() after it creates it. --Mark
7091 *
7092 * No that's not good. blist.c should only deal with adding nodes to the
7093 * local list. We need a new, non-gtk file that calls both
7094 * purple_account_add_buddy and purple_blist_add_buddy().
7095 * Or something. --Mark
7096 */
7097
7098 c = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, data->rq_data.account);
7099 if (c != NULL) {
7100 icon = purple_conv_im_get_icon(PURPLE_CONV_IM(c));
7101 if (icon != NULL)
7102 purple_buddy_icon_update(icon);
7103 }
7104 }
7105
7106 gtk_widget_destroy(data->rq_data.window);
7107 }
7108
7109 static void
pidgin_blist_request_add_buddy(PurpleAccount * account,const char * username,const char * group,const char * alias)7110 pidgin_blist_request_add_buddy(PurpleAccount *account, const char *username,
7111 const char *group, const char *alias)
7112 {
7113 PidginAddBuddyData *data = g_new0(PidginAddBuddyData, 1);
7114
7115 if (account == NULL)
7116 account = purple_connection_get_account(purple_connections_get_all()->data);
7117
7118 make_blist_request_dialog((PidginBlistRequestData *)data,
7119 account,
7120 _("Add Buddy"), "add_buddy",
7121 _("Add a buddy.\n"),
7122 G_CALLBACK(add_buddy_select_account_cb), add_buddy_account_filter_func,
7123 G_CALLBACK(add_buddy_cb));
7124 gtk_dialog_add_buttons(GTK_DIALOG(data->rq_data.window),
7125 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
7126 GTK_STOCK_ADD, GTK_RESPONSE_OK,
7127 NULL);
7128 gtk_dialog_set_default_response(GTK_DIALOG(data->rq_data.window),
7129 GTK_RESPONSE_OK);
7130
7131 g_signal_connect(G_OBJECT(data->rq_data.window), "destroy",
7132 G_CALLBACK(destroy_add_buddy_dialog_cb), data);
7133
7134 data->entry = gtk_entry_new();
7135
7136 pidgin_add_widget_to_vbox(data->rq_data.vbox, _("Buddy's _username:"),
7137 data->rq_data.sg, data->entry, TRUE, NULL);
7138 gtk_widget_grab_focus(data->entry);
7139
7140 if (username != NULL)
7141 gtk_entry_set_text(GTK_ENTRY(data->entry), username);
7142 else
7143 gtk_dialog_set_response_sensitive(GTK_DIALOG(data->rq_data.window),
7144 GTK_RESPONSE_OK, FALSE);
7145
7146 gtk_entry_set_activates_default (GTK_ENTRY(data->entry), TRUE);
7147
7148 g_signal_connect(G_OBJECT(data->entry), "changed",
7149 G_CALLBACK(pidgin_set_sensitive_if_input),
7150 data->rq_data.window);
7151
7152 data->entry_for_alias = gtk_entry_new();
7153 pidgin_add_widget_to_vbox(data->rq_data.vbox, _("(Optional) A_lias:"),
7154 data->rq_data.sg, data->entry_for_alias, TRUE,
7155 NULL);
7156
7157 if (alias != NULL)
7158 gtk_entry_set_text(GTK_ENTRY(data->entry_for_alias), alias);
7159
7160 if (username != NULL)
7161 gtk_widget_grab_focus(GTK_WIDGET(data->entry_for_alias));
7162
7163 data->entry_for_invite = gtk_entry_new();
7164 pidgin_add_widget_to_vbox(data->rq_data.vbox, _("(Optional) _Invite message:"),
7165 data->rq_data.sg, data->entry_for_invite, TRUE,
7166 NULL);
7167
7168 data->combo = pidgin_text_combo_box_entry_new(group, groups_tree());
7169 pidgin_add_widget_to_vbox(data->rq_data.vbox, _("Add buddy to _group:"),
7170 data->rq_data.sg, data->combo, TRUE, NULL);
7171
7172 gtk_widget_show_all(data->rq_data.window);
7173
7174 /* Force update of invite message entry sensitivity */
7175 add_buddy_select_account_cb(NULL, account, data);
7176 }
7177
7178 static void
add_chat_cb(GtkWidget * w,PidginAddChatData * data)7179 add_chat_cb(GtkWidget *w, PidginAddChatData *data)
7180 {
7181 GList *tmp;
7182 PurpleChat *chat;
7183 GHashTable *components;
7184
7185 components = g_hash_table_new_full(g_str_hash, g_str_equal,
7186 g_free, g_free);
7187
7188 for (tmp = data->chat_data.entries; tmp; tmp = tmp->next)
7189 {
7190 if (g_object_get_data(tmp->data, "is_spin"))
7191 {
7192 g_hash_table_replace(components,
7193 g_strdup(g_object_get_data(tmp->data, "identifier")),
7194 g_strdup_printf("%d",
7195 gtk_spin_button_get_value_as_int(tmp->data)));
7196 }
7197 else
7198 {
7199 const char *value = gtk_entry_get_text(tmp->data);
7200
7201 if (*value != '\0')
7202 g_hash_table_replace(components,
7203 g_strdup(g_object_get_data(tmp->data, "identifier")),
7204 g_strdup(value));
7205 }
7206 }
7207
7208 chat = purple_chat_new(data->chat_data.rq_data.account,
7209 gtk_entry_get_text(GTK_ENTRY(data->alias_entry)),
7210 components);
7211
7212 if (chat != NULL) {
7213 PurpleGroup *group;
7214 const char *group_name;
7215
7216 group_name = pidgin_text_combo_box_entry_get_text(data->group_combo);
7217
7218 group = NULL;
7219 if ((group_name != NULL) && (*group_name != '\0') &&
7220 ((group = purple_find_group(group_name)) == NULL))
7221 {
7222 group = purple_group_new(group_name);
7223 purple_blist_add_group(group, NULL);
7224 }
7225
7226 purple_blist_add_chat(chat, group, NULL);
7227
7228 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->autojoin)))
7229 purple_blist_node_set_bool((PurpleBlistNode*)chat, "gtk-autojoin", TRUE);
7230
7231 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->persistent)))
7232 purple_blist_node_set_bool((PurpleBlistNode*)chat, "gtk-persistent", TRUE);
7233 }
7234
7235 gtk_widget_destroy(data->chat_data.rq_data.window);
7236 g_free(data->chat_data.default_chat_name);
7237 g_list_free(data->chat_data.entries);
7238 g_free(data);
7239 }
7240
7241 static void
add_chat_resp_cb(GtkWidget * w,int resp,PidginAddChatData * data)7242 add_chat_resp_cb(GtkWidget *w, int resp, PidginAddChatData *data)
7243 {
7244 if (resp == GTK_RESPONSE_OK)
7245 {
7246 add_chat_cb(NULL, data);
7247 }
7248 else if (resp == 1)
7249 {
7250 pidgin_roomlist_dialog_show_with_account(data->chat_data.rq_data.account);
7251 }
7252 else
7253 {
7254 gtk_widget_destroy(data->chat_data.rq_data.window);
7255 g_free(data->chat_data.default_chat_name);
7256 g_list_free(data->chat_data.entries);
7257 g_free(data);
7258 }
7259 }
7260
7261 static void
pidgin_blist_request_add_chat(PurpleAccount * account,PurpleGroup * group,const char * alias,const char * name)7262 pidgin_blist_request_add_chat(PurpleAccount *account, PurpleGroup *group,
7263 const char *alias, const char *name)
7264 {
7265 PidginAddChatData *data;
7266 GList *l;
7267 PurpleConnection *gc;
7268 GtkBox *vbox;
7269
7270 if (account != NULL) {
7271 gc = purple_account_get_connection(account);
7272
7273 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->join_chat == NULL) {
7274 purple_notify_error(gc, NULL, _("This protocol does not support chat rooms."), NULL);
7275 return;
7276 }
7277 } else {
7278 /* Find an account with chat capabilities */
7279 for (l = purple_connections_get_all(); l != NULL; l = l->next) {
7280 gc = (PurpleConnection *)l->data;
7281
7282 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->join_chat != NULL) {
7283 account = purple_connection_get_account(gc);
7284 break;
7285 }
7286 }
7287
7288 if (account == NULL) {
7289 purple_notify_error(NULL, NULL,
7290 _("You are not currently signed on with any "
7291 "protocols that have the ability to chat."), NULL);
7292 return;
7293 }
7294 }
7295
7296 data = g_new0(PidginAddChatData, 1);
7297 vbox = GTK_BOX(make_blist_request_dialog((PidginBlistRequestData *)data, account,
7298 _("Add Chat"), "add_chat",
7299 _("Please enter an alias, and the appropriate information "
7300 "about the chat you would like to add to your buddy list.\n"),
7301 G_CALLBACK(chat_select_account_cb), chat_account_filter_func,
7302 G_CALLBACK(add_chat_resp_cb)));
7303 gtk_dialog_add_buttons(GTK_DIALOG(data->chat_data.rq_data.window),
7304 _("Room List"), 1,
7305 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
7306 GTK_STOCK_ADD, GTK_RESPONSE_OK,
7307 NULL);
7308 gtk_dialog_set_default_response(GTK_DIALOG(data->chat_data.rq_data.window),
7309 GTK_RESPONSE_OK);
7310
7311 data->chat_data.default_chat_name = g_strdup(name);
7312
7313 rebuild_chat_entries((PidginChatData *)data, name);
7314
7315 data->alias_entry = gtk_entry_new();
7316 if (alias != NULL)
7317 gtk_entry_set_text(GTK_ENTRY(data->alias_entry), alias);
7318 gtk_entry_set_activates_default(GTK_ENTRY(data->alias_entry), TRUE);
7319
7320 pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("A_lias:"),
7321 data->chat_data.rq_data.sg, data->alias_entry,
7322 TRUE, NULL);
7323 if (name != NULL)
7324 gtk_widget_grab_focus(data->alias_entry);
7325
7326 data->group_combo = pidgin_text_combo_box_entry_new(group ? group->name : NULL, groups_tree());
7327 pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("_Group:"),
7328 data->chat_data.rq_data.sg, data->group_combo,
7329 TRUE, NULL);
7330
7331 data->autojoin = gtk_check_button_new_with_mnemonic(_("Automatically _join when account connects"));
7332 data->persistent = gtk_check_button_new_with_mnemonic(_("_Remain in chat after window is closed"));
7333 gtk_box_pack_start(GTK_BOX(vbox), data->autojoin, FALSE, FALSE, 0);
7334 gtk_box_pack_start(GTK_BOX(vbox), data->persistent, FALSE, FALSE, 0);
7335
7336 gtk_widget_show_all(data->chat_data.rq_data.window);
7337 }
7338
7339 static void
add_group_cb(PurpleConnection * gc,const char * group_name)7340 add_group_cb(PurpleConnection *gc, const char *group_name)
7341 {
7342 PurpleGroup *group;
7343
7344 if ((group_name == NULL) || (*group_name == '\0'))
7345 return;
7346
7347 group = purple_group_new(group_name);
7348 purple_blist_add_group(group, NULL);
7349 }
7350
7351 static void
pidgin_blist_request_add_group(void)7352 pidgin_blist_request_add_group(void)
7353 {
7354 purple_request_input(NULL, _("Add Group"), NULL,
7355 _("Please enter the name of the group to be added."),
7356 NULL, FALSE, FALSE, NULL,
7357 _("Add"), G_CALLBACK(add_group_cb),
7358 _("Cancel"), NULL,
7359 NULL, NULL, NULL,
7360 NULL);
7361 }
7362
7363 void
pidgin_blist_toggle_visibility()7364 pidgin_blist_toggle_visibility()
7365 {
7366 if (gtkblist && gtkblist->window) {
7367 if (GTK_WIDGET_VISIBLE(gtkblist->window)) {
7368 /* make the buddy list visible if it is iconified or if it is
7369 * obscured and not currently focused (the focus part ensures
7370 * that we do something reasonable if the buddy list is obscured
7371 * by a window set to always be on top), otherwise hide the
7372 * buddy list
7373 */
7374 purple_blist_set_visible(PIDGIN_WINDOW_ICONIFIED(gtkblist->window) ||
7375 ((gtk_blist_visibility != GDK_VISIBILITY_UNOBSCURED) &&
7376 !gtk_blist_focused));
7377 } else {
7378 purple_blist_set_visible(TRUE);
7379 }
7380 }
7381 }
7382
7383 void
pidgin_blist_visibility_manager_add()7384 pidgin_blist_visibility_manager_add()
7385 {
7386 visibility_manager_count++;
7387 purple_debug_info("gtkblist", "added visibility manager: %d\n", visibility_manager_count);
7388 }
7389
7390 void
pidgin_blist_visibility_manager_remove()7391 pidgin_blist_visibility_manager_remove()
7392 {
7393 if (visibility_manager_count)
7394 visibility_manager_count--;
7395 if (!visibility_manager_count)
7396 purple_blist_set_visible(TRUE);
7397 purple_debug_info("gtkblist", "removed visibility manager: %d\n", visibility_manager_count);
7398 }
7399
pidgin_blist_add_alert(GtkWidget * widget)7400 void pidgin_blist_add_alert(GtkWidget *widget)
7401 {
7402 gtk_container_add(GTK_CONTAINER(gtkblist->scrollbook), widget);
7403 set_urgent();
7404 }
7405
7406 void
pidgin_blist_set_headline(const char * text,GdkPixbuf * pixbuf,GCallback callback,gpointer user_data,GDestroyNotify destroy)7407 pidgin_blist_set_headline(const char *text, GdkPixbuf *pixbuf, GCallback callback,
7408 gpointer user_data, GDestroyNotify destroy)
7409 {
7410 /* Destroy any existing headline first */
7411 if (gtkblist->headline_destroy)
7412 gtkblist->headline_destroy(gtkblist->headline_data);
7413
7414 gtk_label_set_markup(GTK_LABEL(gtkblist->headline_label), text);
7415 gtk_image_set_from_pixbuf(GTK_IMAGE(gtkblist->headline_image), pixbuf);
7416
7417 gtkblist->headline_callback = callback;
7418 gtkblist->headline_data = user_data;
7419 gtkblist->headline_destroy = destroy;
7420 if (text != NULL || pixbuf != NULL) {
7421 set_urgent();
7422 gtk_widget_show_all(gtkblist->headline_hbox);
7423 } else {
7424 gtk_widget_hide(gtkblist->headline_hbox);
7425 }
7426 }
7427
7428
7429 static void
set_urgent(void)7430 set_urgent(void)
7431 {
7432 if (gtkblist->window && !GTK_WIDGET_HAS_FOCUS(gtkblist->window))
7433 pidgin_set_urgent(GTK_WINDOW(gtkblist->window), TRUE);
7434 }
7435
7436 static PurpleBlistUiOps blist_ui_ops =
7437 {
7438 pidgin_blist_new_list,
7439 pidgin_blist_new_node,
7440 pidgin_blist_show,
7441 pidgin_blist_update,
7442 pidgin_blist_remove,
7443 pidgin_blist_destroy,
7444 pidgin_blist_set_visible,
7445 pidgin_blist_request_add_buddy,
7446 pidgin_blist_request_add_chat,
7447 pidgin_blist_request_add_group,
7448 NULL,
7449 NULL,
7450 NULL,
7451 NULL
7452 };
7453
7454
7455 PurpleBlistUiOps *
pidgin_blist_get_ui_ops(void)7456 pidgin_blist_get_ui_ops(void)
7457 {
7458 return &blist_ui_ops;
7459 }
7460
pidgin_blist_get_default_gtk_blist()7461 PidginBuddyList *pidgin_blist_get_default_gtk_blist()
7462 {
7463 return gtkblist;
7464 }
7465
autojoin_cb(PurpleConnection * gc,gpointer data)7466 static gboolean autojoin_cb(PurpleConnection *gc, gpointer data)
7467 {
7468 PurpleAccount *account = purple_connection_get_account(gc);
7469 PurpleBlistNode *gnode, *cnode;
7470 for(gnode = purple_get_blist()->root; gnode; gnode = gnode->next)
7471 {
7472 if(!PURPLE_BLIST_NODE_IS_GROUP(gnode))
7473 continue;
7474 for(cnode = gnode->child; cnode; cnode = cnode->next)
7475 {
7476 PurpleChat *chat;
7477
7478 if(!PURPLE_BLIST_NODE_IS_CHAT(cnode))
7479 continue;
7480
7481 chat = (PurpleChat *)cnode;
7482
7483 if(chat->account != account)
7484 continue;
7485
7486 if (purple_blist_node_get_bool((PurpleBlistNode*)chat, "gtk-autojoin"))
7487 serv_join_chat(gc, chat->components);
7488 }
7489 }
7490
7491 /* Stop processing; we handled the autojoins. */
7492 return TRUE;
7493 }
7494
7495 void *
pidgin_blist_get_handle()7496 pidgin_blist_get_handle() {
7497 static int handle;
7498
7499 return &handle;
7500 }
7501
buddy_signonoff_timeout_cb(PurpleBuddy * buddy)7502 static gboolean buddy_signonoff_timeout_cb(PurpleBuddy *buddy)
7503 {
7504 struct _pidgin_blist_node *gtknode = ((PurpleBlistNode*)buddy)->ui_data;
7505
7506 gtknode->recent_signonoff = FALSE;
7507 gtknode->recent_signonoff_timer = 0;
7508
7509 pidgin_blist_update(NULL, (PurpleBlistNode*)buddy);
7510
7511 return FALSE;
7512 }
7513
buddy_signonoff_cb(PurpleBuddy * buddy)7514 static void buddy_signonoff_cb(PurpleBuddy *buddy)
7515 {
7516 struct _pidgin_blist_node *gtknode;
7517
7518 if(!((PurpleBlistNode*)buddy)->ui_data) {
7519 pidgin_blist_new_node((PurpleBlistNode*)buddy);
7520 }
7521
7522 gtknode = ((PurpleBlistNode*)buddy)->ui_data;
7523
7524 gtknode->recent_signonoff = TRUE;
7525
7526 if(gtknode->recent_signonoff_timer > 0)
7527 purple_timeout_remove(gtknode->recent_signonoff_timer);
7528 gtknode->recent_signonoff_timer = purple_timeout_add_seconds(10,
7529 (GSourceFunc)buddy_signonoff_timeout_cb, buddy);
7530 }
7531
7532 void
pidgin_blist_set_theme(PidginBlistTheme * theme)7533 pidgin_blist_set_theme(PidginBlistTheme *theme)
7534 {
7535 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
7536 PurpleBuddyList *list = purple_get_blist();
7537
7538 if (theme != NULL)
7539 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/blist/theme",
7540 purple_theme_get_name(PURPLE_THEME(theme)));
7541 else
7542 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/blist/theme", "");
7543
7544 if (priv->current_theme)
7545 g_object_unref(priv->current_theme);
7546
7547 priv->current_theme = theme ? g_object_ref(theme) : NULL;
7548
7549 pidgin_blist_build_layout(list);
7550
7551 pidgin_blist_refresh(list);
7552 }
7553
7554
7555 PidginBlistTheme *
pidgin_blist_get_theme()7556 pidgin_blist_get_theme()
7557 {
7558 PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
7559
7560 return priv->current_theme;
7561 }
7562
pidgin_blist_init(void)7563 void pidgin_blist_init(void)
7564 {
7565 void *gtk_blist_handle = pidgin_blist_get_handle();
7566
7567 cached_emblems = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
7568
7569 /* Initialize prefs */
7570 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/blist");
7571 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons", TRUE);
7572 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups", FALSE);
7573 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time", TRUE);
7574 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies", FALSE);
7575 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons", FALSE);
7576 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", FALSE);
7577 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", FALSE);
7578 purple_prefs_add_string(PIDGIN_PREFS_ROOT "/blist/sort_type", "alphabetical");
7579 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/x", 0);
7580 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/y", 0);
7581 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/width", 250); /* Golden ratio, baby */
7582 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/height", 405); /* Golden ratio, baby */
7583 #if !GTK_CHECK_VERSION(2,14,0)
7584 /* This pref is used in pidgintooltip.c. */
7585 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay", 500);
7586 #endif
7587 purple_prefs_add_string(PIDGIN_PREFS_ROOT "/blist/theme", "");
7588
7589 purple_theme_manager_register_type(g_object_new(PIDGIN_TYPE_BLIST_THEME_LOADER, "type", "blist", NULL));
7590
7591 /* Register our signals */
7592 purple_signal_register(gtk_blist_handle, "gtkblist-hiding",
7593 purple_marshal_VOID__POINTER, NULL, 1,
7594 purple_value_new(PURPLE_TYPE_SUBTYPE,
7595 PURPLE_SUBTYPE_BLIST));
7596
7597 purple_signal_register(gtk_blist_handle, "gtkblist-unhiding",
7598 purple_marshal_VOID__POINTER, NULL, 1,
7599 purple_value_new(PURPLE_TYPE_SUBTYPE,
7600 PURPLE_SUBTYPE_BLIST));
7601
7602 purple_signal_register(gtk_blist_handle, "gtkblist-created",
7603 purple_marshal_VOID__POINTER, NULL, 1,
7604 purple_value_new(PURPLE_TYPE_SUBTYPE,
7605 PURPLE_SUBTYPE_BLIST));
7606
7607 purple_signal_register(gtk_blist_handle, "drawing-tooltip",
7608 purple_marshal_VOID__POINTER_POINTER_UINT, NULL, 3,
7609 purple_value_new(PURPLE_TYPE_SUBTYPE,
7610 PURPLE_SUBTYPE_BLIST_NODE),
7611 purple_value_new_outgoing(PURPLE_TYPE_BOXED, "GString *"),
7612 purple_value_new(PURPLE_TYPE_BOOLEAN));
7613
7614 purple_signal_register(gtk_blist_handle, "drawing-buddy",
7615 purple_marshal_POINTER__POINTER,
7616 purple_value_new(PURPLE_TYPE_STRING), 1,
7617 purple_value_new(PURPLE_TYPE_SUBTYPE,
7618 PURPLE_SUBTYPE_BLIST_BUDDY));
7619
7620 purple_signal_connect(purple_blist_get_handle(), "buddy-signed-on",
7621 gtk_blist_handle, PURPLE_CALLBACK(buddy_signonoff_cb), NULL);
7622 purple_signal_connect(purple_blist_get_handle(), "buddy-signed-off",
7623 gtk_blist_handle, PURPLE_CALLBACK(buddy_signonoff_cb), NULL);
7624 purple_signal_connect(purple_blist_get_handle(), "buddy-privacy-changed",
7625 gtk_blist_handle, PURPLE_CALLBACK(pidgin_blist_update_privacy_cb), NULL);
7626
7627 purple_signal_connect_priority(purple_connections_get_handle(), "autojoin",
7628 gtk_blist_handle, PURPLE_CALLBACK(autojoin_cb),
7629 NULL, PURPLE_SIGNAL_PRIORITY_HIGHEST);
7630 }
7631
7632 void
pidgin_blist_uninit(void)7633 pidgin_blist_uninit(void) {
7634 g_hash_table_destroy(cached_emblems);
7635
7636 purple_signals_unregister_by_instance(pidgin_blist_get_handle());
7637 purple_signals_disconnect_by_handle(pidgin_blist_get_handle());
7638 }
7639
7640 /*********************************************************************
7641 * Buddy List sorting functions *
7642 *********************************************************************/
7643
pidgin_blist_get_sort_methods()7644 GList *pidgin_blist_get_sort_methods()
7645 {
7646 return pidgin_blist_sort_methods;
7647 }
7648
pidgin_blist_sort_method_reg(const char * id,const char * name,pidgin_blist_sort_function func)7649 void pidgin_blist_sort_method_reg(const char *id, const char *name, pidgin_blist_sort_function func)
7650 {
7651 struct pidgin_blist_sort_method *method;
7652
7653 g_return_if_fail(id != NULL);
7654 g_return_if_fail(name != NULL);
7655 g_return_if_fail(func != NULL);
7656
7657 method = g_new0(struct pidgin_blist_sort_method, 1);
7658 method->id = g_strdup(id);
7659 method->name = g_strdup(name);
7660 method->func = func;
7661 pidgin_blist_sort_methods = g_list_append(pidgin_blist_sort_methods, method);
7662 pidgin_blist_update_sort_methods();
7663 }
7664
pidgin_blist_sort_method_unreg(const char * id)7665 void pidgin_blist_sort_method_unreg(const char *id)
7666 {
7667 GList *l = pidgin_blist_sort_methods;
7668
7669 g_return_if_fail(id != NULL);
7670
7671 while(l) {
7672 struct pidgin_blist_sort_method *method = l->data;
7673 if(purple_strequal(method->id, id)) {
7674 pidgin_blist_sort_methods = g_list_delete_link(pidgin_blist_sort_methods, l);
7675 g_free(method->id);
7676 g_free(method->name);
7677 g_free(method);
7678 break;
7679 }
7680 l = l->next;
7681 }
7682 pidgin_blist_update_sort_methods();
7683 }
7684
pidgin_blist_sort_method_set(const char * id)7685 void pidgin_blist_sort_method_set(const char *id){
7686 GList *l = pidgin_blist_sort_methods;
7687
7688 if(!id)
7689 id = "none";
7690
7691 while (l && !purple_strequal(((struct pidgin_blist_sort_method*)l->data)->id, id))
7692 l = l->next;
7693
7694 if (l) {
7695 current_sort_method = l->data;
7696 } else if (!current_sort_method) {
7697 pidgin_blist_sort_method_set("none");
7698 return;
7699 }
7700 if (purple_strequal(id, "none")) {
7701 redo_buddy_list(purple_get_blist(), TRUE, FALSE);
7702 } else {
7703 redo_buddy_list(purple_get_blist(), FALSE, FALSE);
7704 }
7705 }
7706
7707 /******************************************
7708 ** Sort Methods
7709 ******************************************/
7710
sort_method_none(PurpleBlistNode * node,PurpleBuddyList * blist,GtkTreeIter parent_iter,GtkTreeIter * cur,GtkTreeIter * iter)7711 static void sort_method_none(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter parent_iter, GtkTreeIter *cur, GtkTreeIter *iter)
7712 {
7713 PurpleBlistNode *sibling = node->prev;
7714 GtkTreeIter sibling_iter;
7715
7716 if (cur != NULL) {
7717 *iter = *cur;
7718 return;
7719 }
7720
7721 while (sibling && !get_iter_from_node(sibling, &sibling_iter)) {
7722 sibling = sibling->prev;
7723 }
7724
7725 gtk_tree_store_insert_after(gtkblist->treemodel, iter,
7726 node->parent ? &parent_iter : NULL,
7727 sibling ? &sibling_iter : NULL);
7728 }
7729
sort_method_alphabetical(PurpleBlistNode * node,PurpleBuddyList * blist,GtkTreeIter groupiter,GtkTreeIter * cur,GtkTreeIter * iter)7730 static void sort_method_alphabetical(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter)
7731 {
7732 GtkTreeIter more_z;
7733
7734 const char *my_name;
7735
7736 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
7737 my_name = purple_contact_get_alias((PurpleContact*)node);
7738 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
7739 my_name = purple_chat_get_name((PurpleChat*)node);
7740 } else {
7741 sort_method_none(node, blist, groupiter, cur, iter);
7742 return;
7743 }
7744
7745 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
7746 gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
7747 return;
7748 }
7749
7750 do {
7751 PurpleBlistNode *n;
7752 const char *this_name;
7753 int cmp;
7754
7755 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, NODE_COLUMN, &n, -1);
7756
7757 if(PURPLE_BLIST_NODE_IS_CONTACT(n)) {
7758 this_name = purple_contact_get_alias((PurpleContact*)n);
7759 } else if(PURPLE_BLIST_NODE_IS_CHAT(n)) {
7760 this_name = purple_chat_get_name((PurpleChat*)n);
7761 } else {
7762 this_name = NULL;
7763 }
7764
7765 cmp = purple_utf8_strcasecmp(my_name, this_name);
7766
7767 if(this_name && (cmp < 0 || (cmp == 0 && node < n))) {
7768 if(cur) {
7769 gtk_tree_store_move_before(gtkblist->treemodel, cur, &more_z);
7770 *iter = *cur;
7771 return;
7772 } else {
7773 gtk_tree_store_insert_before(gtkblist->treemodel, iter,
7774 &groupiter, &more_z);
7775 return;
7776 }
7777 }
7778 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL(gtkblist->treemodel), &more_z));
7779
7780 if(cur) {
7781 gtk_tree_store_move_before(gtkblist->treemodel, cur, NULL);
7782 *iter = *cur;
7783 return;
7784 } else {
7785 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7786 return;
7787 }
7788 }
7789
sort_method_status(PurpleBlistNode * node,PurpleBuddyList * blist,GtkTreeIter groupiter,GtkTreeIter * cur,GtkTreeIter * iter)7790 static void sort_method_status(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter)
7791 {
7792 GtkTreeIter more_z;
7793
7794 PurpleBuddy *my_buddy, *this_buddy;
7795
7796 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
7797 my_buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
7798 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
7799 if (cur != NULL) {
7800 *iter = *cur;
7801 return;
7802 }
7803
7804 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7805 return;
7806 } else {
7807 sort_method_alphabetical(node, blist, groupiter, cur, iter);
7808 return;
7809 }
7810
7811
7812 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
7813 gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
7814 return;
7815 }
7816
7817 do {
7818 PurpleBlistNode *n;
7819 gint name_cmp;
7820 gint presence_cmp;
7821
7822 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, NODE_COLUMN, &n, -1);
7823
7824 if(PURPLE_BLIST_NODE_IS_CONTACT(n)) {
7825 this_buddy = purple_contact_get_priority_buddy((PurpleContact*)n);
7826 } else {
7827 this_buddy = NULL;
7828 }
7829
7830 name_cmp = purple_utf8_strcasecmp(
7831 purple_contact_get_alias(purple_buddy_get_contact(my_buddy)),
7832 (this_buddy
7833 ? purple_contact_get_alias(purple_buddy_get_contact(this_buddy))
7834 : NULL));
7835
7836 presence_cmp = purple_presence_compare(
7837 purple_buddy_get_presence(my_buddy),
7838 this_buddy ? purple_buddy_get_presence(this_buddy) : NULL);
7839
7840 if (this_buddy == NULL ||
7841 (presence_cmp < 0 ||
7842 (presence_cmp == 0 &&
7843 (name_cmp < 0 || (name_cmp == 0 && node < n)))))
7844 {
7845 if (cur != NULL)
7846 {
7847 gtk_tree_store_move_before(gtkblist->treemodel, cur, &more_z);
7848 *iter = *cur;
7849 return;
7850 }
7851 else
7852 {
7853 gtk_tree_store_insert_before(gtkblist->treemodel, iter,
7854 &groupiter, &more_z);
7855 return;
7856 }
7857 }
7858 }
7859 while (gtk_tree_model_iter_next(GTK_TREE_MODEL(gtkblist->treemodel),
7860 &more_z));
7861
7862 if (cur) {
7863 gtk_tree_store_move_before(gtkblist->treemodel, cur, NULL);
7864 *iter = *cur;
7865 return;
7866 } else {
7867 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7868 return;
7869 }
7870 }
7871
sort_method_log_activity(PurpleBlistNode * node,PurpleBuddyList * blist,GtkTreeIter groupiter,GtkTreeIter * cur,GtkTreeIter * iter)7872 static void sort_method_log_activity(PurpleBlistNode *node, PurpleBuddyList *blist, GtkTreeIter groupiter, GtkTreeIter *cur, GtkTreeIter *iter)
7873 {
7874 GtkTreeIter more_z;
7875
7876 int activity_score = 0, this_log_activity_score = 0;
7877 const char *buddy_name, *this_buddy_name;
7878
7879 if(cur && (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtkblist->treemodel), &groupiter) == 1)) {
7880 *iter = *cur;
7881 return;
7882 }
7883
7884 if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
7885 PurpleBlistNode *n;
7886 PurpleBuddy *buddy;
7887 for (n = node->child; n; n = n->next) {
7888 buddy = (PurpleBuddy*)n;
7889 activity_score += purple_log_get_activity_score(PURPLE_LOG_IM, buddy->name, buddy->account);
7890 }
7891 buddy_name = purple_contact_get_alias((PurpleContact*)node);
7892 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
7893 /* we don't have a reliable way of getting the log filename
7894 * from the chat info in the blist, yet */
7895 if (cur != NULL) {
7896 *iter = *cur;
7897 return;
7898 }
7899
7900 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7901 return;
7902 } else {
7903 sort_method_none(node, blist, groupiter, cur, iter);
7904 return;
7905 }
7906
7907
7908 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
7909 gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
7910 return;
7911 }
7912
7913 do {
7914 PurpleBlistNode *n;
7915 PurpleBlistNode *n2;
7916 PurpleBuddy *buddy;
7917 int cmp;
7918
7919 gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, NODE_COLUMN, &n, -1);
7920 this_log_activity_score = 0;
7921
7922 if(PURPLE_BLIST_NODE_IS_CONTACT(n)) {
7923 for (n2 = n->child; n2; n2 = n2->next) {
7924 buddy = (PurpleBuddy*)n2;
7925 this_log_activity_score += purple_log_get_activity_score(PURPLE_LOG_IM, buddy->name, buddy->account);
7926 }
7927 this_buddy_name = purple_contact_get_alias((PurpleContact*)n);
7928 } else {
7929 this_buddy_name = NULL;
7930 }
7931
7932 cmp = purple_utf8_strcasecmp(buddy_name, this_buddy_name);
7933
7934 if (!PURPLE_BLIST_NODE_IS_CONTACT(n) || activity_score > this_log_activity_score ||
7935 ((activity_score == this_log_activity_score) &&
7936 (cmp < 0 || (cmp == 0 && node < n)))) {
7937 if (cur != NULL) {
7938 gtk_tree_store_move_before(gtkblist->treemodel, cur, &more_z);
7939 *iter = *cur;
7940 return;
7941 } else {
7942 gtk_tree_store_insert_before(gtkblist->treemodel, iter,
7943 &groupiter, &more_z);
7944 return;
7945 }
7946 }
7947 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL(gtkblist->treemodel), &more_z));
7948
7949 if (cur != NULL) {
7950 gtk_tree_store_move_before(gtkblist->treemodel, cur, NULL);
7951 *iter = *cur;
7952 return;
7953 } else {
7954 gtk_tree_store_append(gtkblist->treemodel, iter, &groupiter);
7955 return;
7956 }
7957 }
7958
7959 static void
plugin_act(GtkObject * obj,PurplePluginAction * pam)7960 plugin_act(GtkObject *obj, PurplePluginAction *pam)
7961 {
7962 if (pam && pam->callback)
7963 pam->callback(pam);
7964 }
7965
7966 static void
build_plugin_actions(GtkWidget * menu,PurplePlugin * plugin,gpointer context)7967 build_plugin_actions(GtkWidget *menu, PurplePlugin *plugin,
7968 gpointer context)
7969 {
7970 GtkWidget *menuitem;
7971 PurplePluginAction *action = NULL;
7972 GList *actions, *l;
7973
7974 actions = PURPLE_PLUGIN_ACTIONS(plugin, context);
7975
7976 for (l = actions; l != NULL; l = l->next)
7977 {
7978 if (l->data)
7979 {
7980 action = (PurplePluginAction *) l->data;
7981 action->plugin = plugin;
7982 action->context = context;
7983
7984 menuitem = gtk_menu_item_new_with_label(action->label);
7985 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
7986
7987 g_signal_connect(G_OBJECT(menuitem), "activate",
7988 G_CALLBACK(plugin_act), action);
7989 g_object_set_data_full(G_OBJECT(menuitem), "plugin_action",
7990 action,
7991 (GDestroyNotify)purple_plugin_action_free);
7992 gtk_widget_show(menuitem);
7993 }
7994 else
7995 pidgin_separator(menu);
7996 }
7997
7998 g_list_free(actions);
7999 }
8000
8001 static void
modify_account_cb(GtkWidget * widget,gpointer data)8002 modify_account_cb(GtkWidget *widget, gpointer data)
8003 {
8004 pidgin_account_dialog_show(PIDGIN_MODIFY_ACCOUNT_DIALOG, data);
8005 }
8006
8007 static void
enable_account_cb(GtkCheckMenuItem * widget,gpointer data)8008 enable_account_cb(GtkCheckMenuItem *widget, gpointer data)
8009 {
8010 PurpleAccount *account = data;
8011 const PurpleSavedStatus *saved_status;
8012
8013 saved_status = purple_savedstatus_get_current();
8014 purple_savedstatus_activate_for_account(saved_status, account);
8015
8016 purple_account_set_enabled(account, PIDGIN_UI, TRUE);
8017 }
8018
8019 static void
disable_account_cb(GtkCheckMenuItem * widget,gpointer data)8020 disable_account_cb(GtkCheckMenuItem *widget, gpointer data)
8021 {
8022 PurpleAccount *account = data;
8023
8024 purple_account_set_enabled(account, PIDGIN_UI, FALSE);
8025 }
8026
8027
8028
8029 void
pidgin_blist_update_accounts_menu(void)8030 pidgin_blist_update_accounts_menu(void)
8031 {
8032 GtkWidget *menuitem = NULL, *submenu = NULL;
8033 GtkAccelGroup *accel_group = NULL;
8034 GList *l = NULL, *accounts = NULL;
8035 gboolean disabled_accounts = FALSE;
8036 gboolean enabled_accounts = FALSE;
8037
8038 if (accountmenu == NULL)
8039 return;
8040
8041 /* Clear the old Accounts menu */
8042 for (l = gtk_container_get_children(GTK_CONTAINER(accountmenu)); l; l = g_list_delete_link(l, l)) {
8043 menuitem = l->data;
8044
8045 if (menuitem != gtk_item_factory_get_widget(gtkblist->ift, N_("/Accounts/Manage Accounts")))
8046 gtk_widget_destroy(menuitem);
8047 }
8048
8049 for (accounts = purple_accounts_get_all(); accounts; accounts = accounts->next) {
8050 char *buf = NULL;
8051 GtkWidget *image = NULL;
8052 PurpleAccount *account = NULL;
8053 GdkPixbuf *pixbuf = NULL;
8054
8055 account = accounts->data;
8056
8057 if(!purple_account_get_enabled(account, PIDGIN_UI)) {
8058 if (!disabled_accounts) {
8059 menuitem = gtk_menu_item_new_with_label(_("Enable Account"));
8060 gtk_menu_shell_append(GTK_MENU_SHELL(accountmenu), menuitem);
8061
8062 submenu = gtk_menu_new();
8063 gtk_menu_set_accel_group(GTK_MENU(submenu), accel_group);
8064 gtk_menu_set_accel_path(GTK_MENU(submenu), N_("<PurpleMain>/Accounts/Enable Account"));
8065 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
8066
8067 disabled_accounts = TRUE;
8068 }
8069
8070 buf = g_strconcat(purple_account_get_username(account), " (",
8071 purple_account_get_protocol_name(account), ")", NULL);
8072 menuitem = gtk_image_menu_item_new_with_label(buf);
8073 g_free(buf);
8074 pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
8075 if (pixbuf != NULL)
8076 {
8077 if (!purple_account_is_connected(account))
8078 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE);
8079 image = gtk_image_new_from_pixbuf(pixbuf);
8080 g_object_unref(G_OBJECT(pixbuf));
8081 gtk_widget_show(image);
8082 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
8083 }
8084 g_signal_connect(G_OBJECT(menuitem), "activate",
8085 G_CALLBACK(enable_account_cb), account);
8086 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8087 } else {
8088 enabled_accounts = TRUE;
8089 }
8090 }
8091
8092 if (!enabled_accounts) {
8093 gtk_widget_show_all(accountmenu);
8094 return;
8095 }
8096
8097 pidgin_separator(accountmenu);
8098 accel_group = gtk_menu_get_accel_group(GTK_MENU(accountmenu));
8099
8100 for (accounts = purple_accounts_get_all(); accounts; accounts = accounts->next) {
8101 char *buf = NULL;
8102 char *accel_path_buf = NULL;
8103 GtkWidget *image = NULL;
8104 PurpleConnection *gc = NULL;
8105 PurpleAccount *account = NULL;
8106 GdkPixbuf *pixbuf = NULL;
8107 PurplePlugin *plugin = NULL;
8108 PurplePluginProtocolInfo *prpl_info;
8109
8110 account = accounts->data;
8111
8112 if (!purple_account_get_enabled(account, PIDGIN_UI))
8113 continue;
8114
8115 buf = g_strconcat(purple_account_get_username(account), " (",
8116 purple_account_get_protocol_name(account), ")", NULL);
8117 menuitem = gtk_image_menu_item_new_with_label(buf);
8118 accel_path_buf = g_strconcat(N_("<PurpleMain>/Accounts/"), buf, NULL);
8119 g_free(buf);
8120 pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
8121 if (pixbuf != NULL) {
8122 if (!purple_account_is_connected(account))
8123 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf,
8124 0.0, FALSE);
8125 image = gtk_image_new_from_pixbuf(pixbuf);
8126 g_object_unref(G_OBJECT(pixbuf));
8127 gtk_widget_show(image);
8128 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
8129 }
8130 gtk_menu_shell_append(GTK_MENU_SHELL(accountmenu), menuitem);
8131
8132 submenu = gtk_menu_new();
8133 gtk_menu_set_accel_group(GTK_MENU(submenu), accel_group);
8134 gtk_menu_set_accel_path(GTK_MENU(submenu), accel_path_buf);
8135 g_free(accel_path_buf);
8136 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
8137
8138
8139 menuitem = gtk_menu_item_new_with_mnemonic(_("_Edit Account"));
8140 g_signal_connect(G_OBJECT(menuitem), "activate",
8141 G_CALLBACK(modify_account_cb), account);
8142 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8143
8144 pidgin_separator(submenu);
8145
8146 gc = purple_account_get_connection(account);
8147 plugin = gc && PURPLE_CONNECTION_IS_CONNECTED(gc) ? gc->prpl : NULL;
8148 prpl_info = plugin ? PURPLE_PLUGIN_PROTOCOL_INFO(plugin) : NULL;
8149
8150 if (prpl_info &&
8151 (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, get_moods) ||
8152 PURPLE_PLUGIN_HAS_ACTIONS(plugin))) {
8153 if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, get_moods) &&
8154 gc->flags & PURPLE_CONNECTION_SUPPORT_MOODS) {
8155
8156 if (purple_account_get_status(account, "mood")) {
8157 menuitem = gtk_menu_item_new_with_mnemonic(_("Set _Mood..."));
8158 g_signal_connect(G_OBJECT(menuitem), "activate",
8159 G_CALLBACK(set_mood_cb), account);
8160 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8161 }
8162 }
8163 if (PURPLE_PLUGIN_HAS_ACTIONS(plugin)) {
8164 build_plugin_actions(submenu, plugin, gc);
8165 }
8166 } else {
8167 menuitem = gtk_menu_item_new_with_label(_("No actions available"));
8168 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8169 gtk_widget_set_sensitive(menuitem, FALSE);
8170 }
8171
8172 pidgin_separator(submenu);
8173
8174 menuitem = gtk_menu_item_new_with_mnemonic(_("_Disable"));
8175 g_signal_connect(G_OBJECT(menuitem), "activate",
8176 G_CALLBACK(disable_account_cb), account);
8177 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), menuitem);
8178 }
8179 gtk_widget_show_all(accountmenu);
8180 }
8181
8182 static GList *plugin_submenus = NULL;
8183
8184 void
pidgin_blist_update_plugin_actions(void)8185 pidgin_blist_update_plugin_actions(void)
8186 {
8187 GtkWidget *menuitem, *submenu;
8188 PurplePlugin *plugin = NULL;
8189 GList *l;
8190 GtkAccelGroup *accel_group;
8191
8192 GtkWidget *pluginmenu = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools"));
8193
8194 g_return_if_fail(pluginmenu != NULL);
8195
8196 /* Remove old plugin action submenus from the Tools menu */
8197 for (l = plugin_submenus; l; l = l->next)
8198 gtk_widget_destroy(GTK_WIDGET(l->data));
8199 g_list_free(plugin_submenus);
8200 plugin_submenus = NULL;
8201
8202 accel_group = gtk_menu_get_accel_group(GTK_MENU(pluginmenu));
8203
8204 /* Add a submenu for each plugin with custom actions */
8205 for (l = purple_plugins_get_loaded(); l; l = l->next) {
8206 char *path;
8207
8208 plugin = (PurplePlugin *) l->data;
8209
8210 if (PURPLE_IS_PROTOCOL_PLUGIN(plugin))
8211 continue;
8212
8213 if (!PURPLE_PLUGIN_HAS_ACTIONS(plugin))
8214 continue;
8215
8216 menuitem = gtk_image_menu_item_new_with_label(_(plugin->info->name));
8217 gtk_menu_shell_append(GTK_MENU_SHELL(pluginmenu), menuitem);
8218
8219 plugin_submenus = g_list_append(plugin_submenus, menuitem);
8220
8221 submenu = gtk_menu_new();
8222 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
8223
8224 gtk_menu_set_accel_group(GTK_MENU(submenu), accel_group);
8225 path = g_strdup_printf("%s/Tools/%s", gtkblist->ift->path, plugin->info->name);
8226 gtk_menu_set_accel_path(GTK_MENU(submenu), path);
8227 g_free(path);
8228
8229 build_plugin_actions(submenu, plugin, NULL);
8230 }
8231 gtk_widget_show_all(pluginmenu);
8232 }
8233
8234 static void
sortmethod_act(GtkCheckMenuItem * checkmenuitem,char * id)8235 sortmethod_act(GtkCheckMenuItem *checkmenuitem, char *id)
8236 {
8237 if (gtk_check_menu_item_get_active(checkmenuitem))
8238 {
8239 pidgin_set_cursor(gtkblist->window, GDK_WATCH);
8240 /* This is redundant. I think. */
8241 /* pidgin_blist_sort_method_set(id); */
8242 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/blist/sort_type", id);
8243
8244 pidgin_clear_cursor(gtkblist->window);
8245 }
8246 }
8247
8248 void
pidgin_blist_update_sort_methods(void)8249 pidgin_blist_update_sort_methods(void)
8250 {
8251 GtkWidget *menuitem = NULL, *activeitem = NULL;
8252 PidginBlistSortMethod *method = NULL;
8253 GList *l;
8254 GSList *sl = NULL;
8255 GtkWidget *sortmenu;
8256 const char *m = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/blist/sort_type");
8257
8258 if ((gtkblist == NULL) || (gtkblist->ift == NULL))
8259 return;
8260
8261 g_return_if_fail(m != NULL);
8262
8263 sortmenu = gtk_item_factory_get_widget(gtkblist->ift, N_("/Buddies/Sort Buddies"));
8264
8265 if (sortmenu == NULL)
8266 return;
8267
8268 /* Clear the old menu */
8269 for (l = gtk_container_get_children(GTK_CONTAINER(sortmenu)); l; l = g_list_delete_link(l, l)) {
8270 menuitem = l->data;
8271 gtk_widget_destroy(GTK_WIDGET(menuitem));
8272 }
8273
8274 for (l = pidgin_blist_sort_methods; l; l = l->next) {
8275 method = (PidginBlistSortMethod *) l->data;
8276 menuitem = gtk_radio_menu_item_new_with_label(sl, _(method->name));
8277 if (purple_strequal(m, method->id))
8278 activeitem = menuitem;
8279 sl = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem));
8280 gtk_menu_shell_append(GTK_MENU_SHELL(sortmenu), menuitem);
8281 g_signal_connect(G_OBJECT(menuitem), "toggled",
8282 G_CALLBACK(sortmethod_act), method->id);
8283 gtk_widget_show(menuitem);
8284 }
8285 if (activeitem)
8286 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(activeitem), TRUE);
8287 }
8288