1 /**
2 * @file gtknotify.c GTK+ Notification 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 #include "internal.h"
27 #include "pidgin.h"
28
29 #include <gdk/gdkkeysyms.h>
30
31 #include "account.h"
32 #include "connection.h"
33 #include "debug.h"
34 #include "glibcompat.h"
35 #include "prefs.h"
36 #include "pidginstock.h"
37 #include "util.h"
38
39 #include "gtkblist.h"
40 #include "gtkimhtml.h"
41 #include "gtknotify.h"
42 #include "gtkpounce.h"
43 #include "gtkutils.h"
44
45 typedef struct
46 {
47 GtkWidget *window;
48 int count;
49 } PidginUserInfo;
50
51 typedef struct
52 {
53 PurpleAccount *account;
54 char *url;
55 GtkWidget *label;
56 int count;
57 gboolean purple_has_handle;
58 } PidginNotifyMailData;
59
60 typedef struct
61 {
62 PurpleAccount *account;
63 PurplePounce *pounce;
64 char *pouncee;
65 } PidginNotifyPounceData;
66
67
68 typedef struct
69 {
70 PurpleAccount *account;
71 GtkListStore *model;
72 GtkWidget *treeview;
73 GtkWidget *window;
74 gpointer user_data;
75 PurpleNotifySearchResults *results;
76
77 } PidginNotifySearchResultsData;
78
79 typedef struct
80 {
81 PurpleNotifySearchButton *button;
82 PidginNotifySearchResultsData *data;
83
84 } PidginNotifySearchResultsButtonData;
85
86 enum
87 {
88 PIDGIN_MAIL_ICON,
89 PIDGIN_MAIL_TEXT,
90 PIDGIN_MAIL_DATA,
91 COLUMNS_PIDGIN_MAIL
92 };
93
94 enum
95 {
96 PIDGIN_POUNCE_ICON,
97 PIDGIN_POUNCE_ALIAS,
98 PIDGIN_POUNCE_EVENT,
99 PIDGIN_POUNCE_TEXT,
100 PIDGIN_POUNCE_DATE,
101 PIDGIN_POUNCE_DATA,
102 COLUMNS_PIDGIN_POUNCE
103 };
104
105
106 typedef struct _PidginNotifyDialog
107 {
108 /*
109 * This must be first so PidginNotifyDialog can masquerade as the
110 * dialog widget.
111 */
112 GtkWidget *dialog;
113 GtkWidget *treeview;
114 GtkTreeStore *treemodel;
115 GtkLabel *label;
116 GtkWidget *open_button;
117 GtkWidget *dismiss_button;
118 GtkWidget *edit_button;
119 int total_count;
120 gboolean in_use;
121 } PidginNotifyDialog;
122
123 typedef enum
124 {
125 PIDGIN_NOTIFY_MAIL,
126 PIDGIN_NOTIFY_POUNCE,
127 PIDGIN_NOTIFY_TYPES
128 } PidginNotifyType;
129
130 static PidginNotifyDialog *mail_dialog = NULL;
131 static PidginNotifyDialog *pounce_dialog = NULL;
132
133 static PidginNotifyDialog *pidgin_create_notification_dialog(PidginNotifyType type);
134 static void *pidgin_notify_emails(PurpleConnection *gc, size_t count, gboolean detailed,
135 const char **subjects,
136 const char **froms, const char **tos,
137 const char **urls);
138
139 static void pidgin_close_notify(PurpleNotifyType type, void *ui_handle);
140
141 static void
message_response_cb(GtkDialog * dialog,gint id,GtkWidget * widget)142 message_response_cb(GtkDialog *dialog, gint id, GtkWidget *widget)
143 {
144 purple_notify_close(PURPLE_NOTIFY_MESSAGE, widget);
145 }
146
147 static void
pounce_response_close(PidginNotifyDialog * dialog)148 pounce_response_close(PidginNotifyDialog *dialog)
149 {
150 GtkTreeIter iter;
151 PidginNotifyPounceData *pounce_data;
152
153 while (gtk_tree_model_get_iter_first(
154 GTK_TREE_MODEL(pounce_dialog->treemodel), &iter)) {
155 gtk_tree_model_get(GTK_TREE_MODEL(pounce_dialog->treemodel), &iter,
156 PIDGIN_POUNCE_DATA, &pounce_data,
157 -1);
158 gtk_tree_store_remove(dialog->treemodel, &iter);
159
160 g_free(pounce_data->pouncee);
161 g_free(pounce_data);
162 }
163
164 gtk_widget_destroy(pounce_dialog->dialog);
165 g_free(pounce_dialog);
166 pounce_dialog = NULL;
167 }
168
169 static void
delete_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)170 delete_foreach(GtkTreeModel *model, GtkTreePath *path,
171 GtkTreeIter *iter, gpointer data)
172 {
173 PidginNotifyPounceData *pounce_data;
174
175 gtk_tree_model_get(model, iter,
176 PIDGIN_POUNCE_DATA, &pounce_data,
177 -1);
178
179 if (pounce_data != NULL) {
180 g_free(pounce_data->pouncee);
181 g_free(pounce_data);
182 }
183 }
184
185 static void
open_im_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)186 open_im_foreach(GtkTreeModel *model, GtkTreePath *path,
187 GtkTreeIter *iter, gpointer data)
188 {
189 PidginNotifyPounceData *pounce_data;
190
191 gtk_tree_model_get(model, iter,
192 PIDGIN_POUNCE_DATA, &pounce_data,
193 -1);
194
195 if (pounce_data != NULL) {
196 PurpleConversation *conv;
197
198 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM,
199 pounce_data->account, pounce_data->pouncee);
200 purple_conversation_present(conv);
201 }
202 }
203
204 static void
append_to_list(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)205 append_to_list(GtkTreeModel *model, GtkTreePath *path,
206 GtkTreeIter *iter, gpointer data)
207 {
208 GList **list = data;
209 *list = g_list_prepend(*list, gtk_tree_path_copy(path));
210 }
211
212 static void
pounce_response_dismiss()213 pounce_response_dismiss()
214 {
215 GtkTreeModel *model = GTK_TREE_MODEL(pounce_dialog->treemodel);
216 GtkTreeSelection *selection;
217 GtkTreeIter iter;
218 GtkTreeIter new_selection;
219 GList *list = NULL;
220 gboolean found_selection = FALSE;
221
222 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
223 gtk_tree_selection_selected_foreach(selection, delete_foreach, pounce_dialog);
224 gtk_tree_selection_selected_foreach(selection, append_to_list, &list);
225
226 g_return_if_fail(list != NULL);
227
228 if (list->next == NULL) {
229 gtk_tree_model_get_iter(model, &new_selection, list->data);
230 if (gtk_tree_model_iter_next(model, &new_selection))
231 found_selection = TRUE;
232 else {
233 /* This is the last thing in the list */
234 GtkTreePath *path;
235
236 /* Because gtk_tree_model_iter_prev doesn't exist... */
237 gtk_tree_model_get_iter(model, &new_selection, list->data);
238 path = gtk_tree_model_get_path(model, &new_selection);
239 if (gtk_tree_path_prev(path)) {
240 gtk_tree_model_get_iter(model, &new_selection, path);
241 found_selection = TRUE;
242 }
243
244 gtk_tree_path_free(path);
245 }
246 }
247
248 while (list) {
249 if (gtk_tree_model_get_iter(model, &iter, list->data)) {
250 gtk_tree_store_remove(GTK_TREE_STORE(pounce_dialog->treemodel), &iter);
251 }
252 gtk_tree_path_free(list->data);
253 list = g_list_delete_link(list, list);
254 }
255
256 if (gtk_tree_model_get_iter_first(model, &iter)) {
257 if (found_selection)
258 gtk_tree_selection_select_iter(selection, &new_selection);
259 else
260 gtk_tree_selection_select_iter(selection, &iter);
261 } else
262 pounce_response_close(pounce_dialog);
263 }
264
265 static void
pounce_response_open_ims()266 pounce_response_open_ims()
267 {
268 GtkTreeSelection *selection;
269
270 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
271 gtk_tree_selection_selected_foreach(selection, open_im_foreach, pounce_dialog);
272
273 pounce_response_dismiss();
274 }
275
276 static void
pounce_response_edit_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)277 pounce_response_edit_cb(GtkTreeModel *model, GtkTreePath *path,
278 GtkTreeIter *iter, gpointer data)
279 {
280 PidginNotifyPounceData *pounce_data;
281 PidginNotifyDialog *dialog = (PidginNotifyDialog*)data;
282 PurplePounce *pounce;
283 GList *list;
284
285 list = purple_pounces_get_all();
286
287 gtk_tree_model_get(GTK_TREE_MODEL(dialog->treemodel), iter,
288 PIDGIN_POUNCE_DATA, &pounce_data,
289 -1);
290
291 for (; list != NULL; list = list->next) {
292 pounce = list->data;
293 if (pounce == pounce_data->pounce) {
294 pidgin_pounce_editor_show(pounce_data->account, NULL, pounce_data->pounce);
295 return;
296 }
297 }
298
299 purple_debug_warning("gtknotify", "Pounce was destroyed.\n");
300 }
301
302 static void
pounce_response_cb(GtkDialog * dlg,gint id,PidginNotifyDialog * dialog)303 pounce_response_cb(GtkDialog *dlg, gint id, PidginNotifyDialog *dialog)
304 {
305 GtkTreeSelection *selection = NULL;
306
307 switch (id) {
308 case GTK_RESPONSE_CLOSE:
309 case GTK_RESPONSE_DELETE_EVENT:
310 pounce_response_close(dialog);
311 break;
312 case GTK_RESPONSE_YES:
313 pounce_response_open_ims();
314 break;
315 case GTK_RESPONSE_NO:
316 pounce_response_dismiss();
317 break;
318 case GTK_RESPONSE_APPLY:
319 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
320 gtk_tree_selection_selected_foreach(selection, pounce_response_edit_cb,
321 dialog);
322 break;
323 }
324 }
325
326 static void
pounce_row_selected_cb(GtkTreeView * tv,GtkTreePath * path,GtkTreeViewColumn * col,gpointer data)327 pounce_row_selected_cb(GtkTreeView *tv, GtkTreePath *path,
328 GtkTreeViewColumn *col, gpointer data)
329 {
330 GtkTreeSelection *selection;
331 int count;
332
333 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
334
335 count = gtk_tree_selection_count_selected_rows(selection);
336
337 if (count == 0) {
338 gtk_widget_set_sensitive(pounce_dialog->open_button, FALSE);
339 gtk_widget_set_sensitive(pounce_dialog->edit_button, FALSE);
340 gtk_widget_set_sensitive(pounce_dialog->dismiss_button, FALSE);
341 } else if (count == 1) {
342 GList *pounces;
343 GList *list;
344 PidginNotifyPounceData *pounce_data;
345 GtkTreeIter iter;
346
347 list = gtk_tree_selection_get_selected_rows(selection, NULL);
348 gtk_tree_model_get_iter(GTK_TREE_MODEL(pounce_dialog->treemodel),
349 &iter, list->data);
350 gtk_tree_model_get(GTK_TREE_MODEL(pounce_dialog->treemodel), &iter,
351 PIDGIN_POUNCE_DATA, &pounce_data,
352 -1);
353 g_list_free_full(list, (GDestroyNotify)gtk_tree_path_free);
354
355 pounces = purple_pounces_get_all();
356 for (; pounces != NULL; pounces = pounces->next) {
357 PurplePounce *pounce = pounces->data;
358 if (pounce == pounce_data->pounce) {
359 gtk_widget_set_sensitive(pounce_dialog->edit_button, TRUE);
360 break;
361 }
362 }
363
364 gtk_widget_set_sensitive(pounce_dialog->open_button, TRUE);
365 gtk_widget_set_sensitive(pounce_dialog->dismiss_button, TRUE);
366 } else {
367 gtk_widget_set_sensitive(pounce_dialog->open_button, TRUE);
368 gtk_widget_set_sensitive(pounce_dialog->edit_button, FALSE);
369 gtk_widget_set_sensitive(pounce_dialog->dismiss_button, TRUE);
370 }
371
372
373 }
374
375 static void
reset_mail_dialog(GtkDialog * unused)376 reset_mail_dialog(GtkDialog *unused)
377 {
378 if (mail_dialog->in_use)
379 return;
380 gtk_widget_destroy(mail_dialog->dialog);
381 g_free(mail_dialog);
382 mail_dialog = NULL;
383 }
384
385 static void
email_response_cb(GtkDialog * unused,gint id,PidginNotifyDialog * unused2)386 email_response_cb(GtkDialog *unused, gint id, PidginNotifyDialog *unused2)
387 {
388 PidginNotifyMailData *data = NULL;
389 GtkTreeModel *model = GTK_TREE_MODEL(mail_dialog->treemodel);
390 GtkTreeIter iter;
391
392 if (id == GTK_RESPONSE_YES)
393 {
394 /* A single row activated. Remove that row. */
395 GtkTreeSelection *selection;
396
397 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(mail_dialog->treeview));
398
399 if (gtk_tree_selection_get_selected(selection, NULL, &iter))
400 {
401 gtk_tree_model_get(model, &iter, PIDGIN_MAIL_DATA, &data, -1);
402 purple_notify_uri(NULL, data->url);
403
404 gtk_tree_store_remove(mail_dialog->treemodel, &iter);
405 if (data->purple_has_handle)
406 purple_notify_close(PURPLE_NOTIFY_EMAILS, data);
407 else
408 pidgin_close_notify(PURPLE_NOTIFY_EMAILS, data);
409
410 if (gtk_tree_model_get_iter_first(model, &iter))
411 return;
412 }
413 else
414 return;
415 }
416 else
417 {
418 /* Remove all the rows */
419 while (gtk_tree_model_get_iter_first(model, &iter))
420 {
421 gtk_tree_model_get(model, &iter, PIDGIN_MAIL_DATA, &data, -1);
422
423 if (id == GTK_RESPONSE_ACCEPT)
424 purple_notify_uri(NULL, data->url);
425
426 gtk_tree_store_remove(mail_dialog->treemodel, &iter);
427 if (data->purple_has_handle)
428 purple_notify_close(PURPLE_NOTIFY_EMAILS, data);
429 else
430 pidgin_close_notify(PURPLE_NOTIFY_EMAILS, data);
431 }
432 }
433
434 reset_mail_dialog(NULL);
435 }
436
437 static void
email_row_activated_cb(GtkTreeView * tv,GtkTreePath * path,GtkTreeViewColumn * col,gpointer data)438 email_row_activated_cb(GtkTreeView *tv, GtkTreePath *path,
439 GtkTreeViewColumn *col, gpointer data)
440 {
441 email_response_cb(NULL, GTK_RESPONSE_YES, NULL);
442 }
443
444 static gboolean
formatted_close_cb(GtkWidget * win,GdkEvent * event,void * user_data)445 formatted_close_cb(GtkWidget *win, GdkEvent *event, void *user_data)
446 {
447 purple_notify_close(PURPLE_NOTIFY_FORMATTED, win);
448 return FALSE;
449 }
450
451 static gboolean
searchresults_close_cb(PidginNotifySearchResultsData * data,GdkEvent * event,gpointer user_data)452 searchresults_close_cb(PidginNotifySearchResultsData *data, GdkEvent *event, gpointer user_data)
453 {
454 purple_notify_close(PURPLE_NOTIFY_SEARCHRESULTS, data);
455 return FALSE;
456 }
457
458 static void
searchresults_callback_wrapper_cb(GtkWidget * widget,PidginNotifySearchResultsButtonData * bd)459 searchresults_callback_wrapper_cb(GtkWidget *widget, PidginNotifySearchResultsButtonData *bd)
460 {
461 PidginNotifySearchResultsData *data = bd->data;
462
463 GtkTreeSelection *selection;
464 GtkTreeModel *model;
465 GtkTreeIter iter;
466 PurpleNotifySearchButton *button;
467 GList *row = NULL;
468 gchar *str;
469 int i;
470
471 g_return_if_fail(data != NULL);
472
473 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(data->treeview));
474
475 if (gtk_tree_selection_get_selected(selection, &model, &iter))
476 {
477 for (i = 1; i < gtk_tree_model_get_n_columns(GTK_TREE_MODEL(model)); i++) {
478 gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, i, &str, -1);
479 row = g_list_append(row, str);
480 }
481 }
482
483 button = bd->button;
484 button->callback(purple_account_get_connection(data->account), row, data->user_data);
485 g_list_free_full(row, (GDestroyNotify)g_free);
486 }
487
488 static void *
pidgin_notify_message(PurpleNotifyMsgType type,const char * title,const char * primary,const char * secondary)489 pidgin_notify_message(PurpleNotifyMsgType type, const char *title,
490 const char *primary, const char *secondary)
491 {
492 GtkWidget *dialog;
493 GtkWidget *hbox;
494 GtkWidget *label;
495 GtkWidget *img = NULL;
496 char label_text[2048];
497 const char *icon_name = NULL;
498 char *primary_esc, *secondary_esc;
499
500 switch (type)
501 {
502 case PURPLE_NOTIFY_MSG_ERROR:
503 icon_name = PIDGIN_STOCK_DIALOG_ERROR;
504 break;
505
506 case PURPLE_NOTIFY_MSG_WARNING:
507 icon_name = PIDGIN_STOCK_DIALOG_WARNING;
508 break;
509
510 case PURPLE_NOTIFY_MSG_INFO:
511 icon_name = PIDGIN_STOCK_DIALOG_INFO;
512 break;
513
514 default:
515 icon_name = NULL;
516 break;
517 }
518
519 if (icon_name != NULL)
520 {
521 img = gtk_image_new_from_stock(icon_name, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
522 gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
523 }
524
525 dialog = gtk_dialog_new_with_buttons(title ? title : PIDGIN_ALERT_TITLE,
526 NULL, 0, GTK_STOCK_CLOSE,
527 GTK_RESPONSE_CLOSE, NULL);
528
529 gtk_window_set_role(GTK_WINDOW(dialog), "notify_dialog");
530
531 g_signal_connect(G_OBJECT(dialog), "response",
532 G_CALLBACK(message_response_cb), dialog);
533
534 gtk_container_set_border_width(GTK_CONTAINER(dialog), PIDGIN_HIG_BORDER);
535 gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
536 gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
537 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BORDER);
538 gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BOX_SPACE);
539
540 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
541 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox);
542
543 if (img != NULL)
544 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
545
546 primary_esc = g_markup_escape_text(primary, -1);
547 secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
548 g_snprintf(label_text, sizeof(label_text),
549 "<span weight=\"bold\" size=\"larger\">%s</span>%s%s",
550 primary_esc, (secondary ? "\n\n" : ""),
551 (secondary ? secondary_esc : ""));
552 g_free(primary_esc);
553 g_free(secondary_esc);
554
555 label = gtk_label_new(NULL);
556
557 gtk_label_set_markup(GTK_LABEL(label), label_text);
558 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
559 gtk_label_set_selectable(GTK_LABEL(label), TRUE);
560 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
561 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
562
563 pidgin_auto_parent_window(dialog);
564
565 gtk_widget_show_all(dialog);
566
567 return dialog;
568 }
569
570 static void
selection_changed_cb(GtkTreeSelection * sel,PidginNotifyDialog * dialog)571 selection_changed_cb(GtkTreeSelection *sel, PidginNotifyDialog *dialog)
572 {
573 GtkTreeIter iter;
574 GtkTreeModel *model;
575 PidginNotifyMailData *data;
576 gboolean active = TRUE;
577
578 if (gtk_tree_selection_get_selected(sel, &model, &iter) == FALSE)
579 active = FALSE;
580 else
581 {
582 gtk_tree_model_get(model, &iter, PIDGIN_MAIL_DATA, &data, -1);
583 if (data->url == NULL)
584 active = FALSE;
585 }
586
587 gtk_widget_set_sensitive(dialog->open_button, active);
588 }
589
590 static void *
pidgin_notify_email(PurpleConnection * gc,const char * subject,const char * from,const char * to,const char * url)591 pidgin_notify_email(PurpleConnection *gc, const char *subject, const char *from,
592 const char *to, const char *url)
593 {
594 return pidgin_notify_emails(gc, 1, (subject != NULL),
595 (subject == NULL ? NULL : &subject),
596 (from == NULL ? NULL : &from),
597 (to == NULL ? NULL : &to),
598 (url == NULL ? NULL : &url));
599 }
600
601 static int
mail_window_focus_cb(GtkWidget * widget,GdkEventFocus * focus,gpointer null)602 mail_window_focus_cb(GtkWidget *widget, GdkEventFocus *focus, gpointer null)
603 {
604 pidgin_set_urgent(GTK_WINDOW(widget), FALSE);
605 return 0;
606 }
607
608 /* count == 0 means this is a detailed mail notification.
609 * count > 0 mean non-detailed.
610 */
611 static void *
pidgin_notify_add_mail(GtkTreeStore * treemodel,PurpleAccount * account,char * notification,const char * url,int count,gboolean clear,gboolean * new_data)612 pidgin_notify_add_mail(GtkTreeStore *treemodel, PurpleAccount *account, char *notification, const char *url, int count, gboolean clear, gboolean *new_data)
613 {
614 PidginNotifyMailData *data = NULL;
615 GtkTreeIter iter;
616 GdkPixbuf *icon;
617 gboolean new_n = TRUE;
618
619 if (count > 0 || clear) {
620 /* Allow only one non-detailed email notification for each account */
621 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(treemodel), &iter)) {
622 gboolean advanced;
623 do {
624 advanced = FALSE;
625 gtk_tree_model_get(GTK_TREE_MODEL(treemodel), &iter,
626 PIDGIN_MAIL_DATA, &data, -1);
627 if (data && data->account == account) {
628 if (clear) {
629 advanced = gtk_tree_store_remove(treemodel, &iter);
630 mail_dialog->total_count -= data->count;
631
632 if (data->purple_has_handle)
633 purple_notify_close(PURPLE_NOTIFY_EMAILS, data);
634 else
635 pidgin_close_notify(PURPLE_NOTIFY_EMAILS, data);
636 /* We're completely done if we've processed all entries */
637 if (!advanced)
638 return NULL;
639 } else if (data->count > 0) {
640 new_n = FALSE;
641 g_free(data->url);
642 data->url = NULL;
643 mail_dialog->total_count -= data->count;
644 break;
645 }
646 }
647 } while (advanced || gtk_tree_model_iter_next(GTK_TREE_MODEL(treemodel), &iter));
648 }
649 }
650
651 if (clear)
652 return NULL;
653
654 icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_MEDIUM);
655
656 if (new_n) {
657 data = g_new0(PidginNotifyMailData, 1);
658 data->purple_has_handle = TRUE;
659 gtk_tree_store_append(treemodel, &iter, NULL);
660 }
661
662 if (url != NULL)
663 data->url = g_strdup(url);
664
665 gtk_tree_store_set(treemodel, &iter,
666 PIDGIN_MAIL_ICON, icon,
667 PIDGIN_MAIL_TEXT, notification,
668 PIDGIN_MAIL_DATA, data,
669 -1);
670 data->account = account;
671 /* count == 0 indicates we're adding a single detailed e-mail */
672 data->count = count > 0 ? count : 1;
673
674 if (icon)
675 g_object_unref(icon);
676
677 if (new_data)
678 *new_data = new_n;
679 return data;
680 }
681
682 static void *
pidgin_notify_emails(PurpleConnection * gc,size_t count,gboolean detailed,const char ** subjects,const char ** froms,const char ** tos,const char ** urls)683 pidgin_notify_emails(PurpleConnection *gc, size_t count, gboolean detailed,
684 const char **subjects, const char **froms,
685 const char **tos, const char **urls)
686 {
687 char *notification;
688 PurpleAccount *account;
689 PidginNotifyMailData *data = NULL, *data2;
690 gboolean new_data = FALSE;
691
692 /* Don't bother updating if there aren't new emails and we don't have any displayed currently */
693 if (count == 0 && mail_dialog == NULL)
694 return NULL;
695
696 account = purple_connection_get_account(gc);
697 if (mail_dialog == NULL)
698 mail_dialog = pidgin_create_notification_dialog(PIDGIN_NOTIFY_MAIL);
699
700 mail_dialog->total_count += count;
701 if (detailed) {
702 for ( ; count; --count) {
703 char *to_text = NULL;
704 char *from_text = NULL;
705 char *subject_text = NULL;
706 char *tmp;
707 gboolean first = TRUE;
708
709 if (tos != NULL) {
710 tmp = g_markup_escape_text(*tos, -1);
711 to_text = g_strdup_printf("<b>%s</b>: %s\n", _("Account"), tmp);
712 g_free(tmp);
713 first = FALSE;
714 tos++;
715 }
716 if (froms != NULL) {
717 tmp = g_markup_escape_text(*froms, -1);
718 from_text = g_strdup_printf("%s<b>%s</b>: %s\n", first ? "<br>" : "", _("Sender"), tmp);
719 g_free(tmp);
720 first = FALSE;
721 froms++;
722 }
723 if (subjects != NULL) {
724 tmp = g_markup_escape_text(*subjects, -1);
725 subject_text = g_strdup_printf("%s<b>%s</b>: %s", first ? "<br>" : "", _("Subject"), tmp);
726 g_free(tmp);
727 /* this is a dead assignment, but if you add another row you'll
728 * need it, so I commented it out for now.
729 */
730 /* first = FALSE; */
731 subjects++;
732 }
733 #define SAFE(x) ((x) ? (x) : "")
734 notification = g_strdup_printf("%s%s%s", SAFE(to_text), SAFE(from_text), SAFE(subject_text));
735 #undef SAFE
736 g_free(to_text);
737 g_free(from_text);
738 g_free(subject_text);
739
740 /* If we don't keep track of this, will leak "data" for each of the notifications except the last */
741 data2 = pidgin_notify_add_mail(mail_dialog->treemodel, account, notification, urls ? *urls : NULL, 0, FALSE, &new_data);
742 if (data2 && new_data) {
743 if (data)
744 data->purple_has_handle = FALSE;
745 data = data2;
746 }
747 g_free(notification);
748
749 if (urls != NULL)
750 urls++;
751 }
752 } else {
753 if (count > 0) {
754 notification = g_strdup_printf(ngettext("%s has %d new message.",
755 "%s has %d new messages.",
756 (int)count),
757 tos != NULL ? *tos : NULL, (int)count);
758 data2 = pidgin_notify_add_mail(mail_dialog->treemodel, account, notification, urls ? *urls : NULL, count, FALSE, &new_data);
759 if (data2 && new_data) {
760 if (data)
761 data->purple_has_handle = FALSE;
762 data = data2;
763 }
764 g_free(notification);
765 } else {
766 /* Clear out all mails for the account */
767 pidgin_notify_add_mail(mail_dialog->treemodel, account, NULL, NULL, 0, TRUE, NULL);
768
769 if (mail_dialog->total_count == 0) {
770 /*
771 * There is no API to clear the headline specifically
772 * This will trigger reset_mail_dialog()
773 */
774 pidgin_blist_set_headline(NULL, NULL, NULL, NULL, NULL);
775 return NULL;
776 }
777 }
778 }
779
780 if (!GTK_WIDGET_VISIBLE(mail_dialog->dialog)) {
781 GdkPixbuf *pixbuf = gtk_widget_render_icon(mail_dialog->dialog, PIDGIN_STOCK_DIALOG_MAIL,
782 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL), NULL);
783 char *label_text = g_strdup_printf(ngettext("<b>%d new email.</b>",
784 "<b>%d new emails.</b>",
785 mail_dialog->total_count), mail_dialog->total_count);
786 mail_dialog->in_use = TRUE; /* So that _set_headline doesn't accidentally
787 remove the notifications when replacing an
788 old notification. */
789 pidgin_blist_set_headline(label_text,
790 pixbuf, G_CALLBACK(gtk_widget_show_all), mail_dialog->dialog,
791 (GDestroyNotify)reset_mail_dialog);
792 mail_dialog->in_use = FALSE;
793 g_free(label_text);
794 if (pixbuf)
795 g_object_unref(pixbuf);
796 } else if (!GTK_WIDGET_HAS_FOCUS(mail_dialog->dialog))
797 pidgin_set_urgent(GTK_WINDOW(mail_dialog->dialog), TRUE);
798
799 return data;
800 }
801
802 static gboolean
formatted_input_cb(GtkWidget * win,GdkEventKey * event,gpointer data)803 formatted_input_cb(GtkWidget *win, GdkEventKey *event, gpointer data)
804 {
805 if (event->keyval == GDK_Escape)
806 {
807 purple_notify_close(PURPLE_NOTIFY_FORMATTED, win);
808
809 return TRUE;
810 }
811
812 return FALSE;
813 }
814
815 static GtkIMHtmlOptions
notify_imhtml_options(void)816 notify_imhtml_options(void)
817 {
818 GtkIMHtmlOptions options = 0;
819
820 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/show_incoming_formatting"))
821 options |= GTK_IMHTML_NO_COLOURS | GTK_IMHTML_NO_FONTS | GTK_IMHTML_NO_SIZES;
822
823 options |= GTK_IMHTML_NO_COMMENTS;
824 options |= GTK_IMHTML_NO_TITLE;
825 options |= GTK_IMHTML_NO_NEWLINE;
826 options |= GTK_IMHTML_NO_SCROLL;
827 return options;
828 }
829
830 static void *
pidgin_notify_formatted(const char * title,const char * primary,const char * secondary,const char * text)831 pidgin_notify_formatted(const char *title, const char *primary,
832 const char *secondary, const char *text)
833 {
834 GtkWidget *window;
835 GtkWidget *vbox;
836 GtkWidget *label;
837 GtkWidget *button;
838 GtkWidget *imhtml;
839 GtkWidget *frame;
840 char label_text[2048];
841 char *linked_text, *primary_esc, *secondary_esc;
842
843 window = gtk_dialog_new();
844 gtk_window_set_title(GTK_WINDOW(window), title);
845 gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER);
846 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
847
848 g_signal_connect(G_OBJECT(window), "delete_event",
849 G_CALLBACK(formatted_close_cb), NULL);
850
851 /* Setup the main vbox */
852 vbox = GTK_DIALOG(window)->vbox;
853
854 /* Setup the descriptive label */
855 primary_esc = g_markup_escape_text(primary, -1);
856 secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
857 g_snprintf(label_text, sizeof(label_text),
858 "<span weight=\"bold\" size=\"larger\">%s</span>%s%s",
859 primary_esc,
860 (secondary ? "\n" : ""),
861 (secondary ? secondary_esc : ""));
862 g_free(primary_esc);
863 g_free(secondary_esc);
864
865 label = gtk_label_new(NULL);
866
867 gtk_label_set_markup(GTK_LABEL(label), label_text);
868 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
869 gtk_label_set_selectable(GTK_LABEL(label), TRUE);
870 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
871 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
872 gtk_widget_show(label);
873
874 /* Add the imhtml */
875 frame = pidgin_create_imhtml(FALSE, &imhtml, NULL, NULL);
876 gtk_widget_set_name(imhtml, "pidgin_notify_imhtml");
877 gtk_imhtml_set_format_functions(GTK_IMHTML(imhtml),
878 gtk_imhtml_get_format_functions(GTK_IMHTML(imhtml)) | GTK_IMHTML_IMAGE);
879 gtk_widget_set_size_request(imhtml, 300, 250);
880 gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
881 gtk_widget_show(frame);
882
883 /* Add the Close button. */
884 button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
885 gtk_widget_grab_focus(button);
886
887 g_signal_connect_swapped(G_OBJECT(button), "clicked",
888 G_CALLBACK(formatted_close_cb), window);
889 g_signal_connect(G_OBJECT(window), "key_press_event",
890 G_CALLBACK(formatted_input_cb), NULL);
891
892 /* Make sure URLs are clickable */
893 linked_text = purple_markup_linkify(text);
894 gtk_imhtml_append_text(GTK_IMHTML(imhtml), linked_text, notify_imhtml_options());
895 g_free(linked_text);
896
897 g_object_set_data(G_OBJECT(window), "info-widget", imhtml);
898
899 /* Show the window */
900 pidgin_auto_parent_window(window);
901
902 gtk_widget_show(window);
903
904 return window;
905 }
906
907 static void
pidgin_notify_searchresults_new_rows(PurpleConnection * gc,PurpleNotifySearchResults * results,void * data_)908 pidgin_notify_searchresults_new_rows(PurpleConnection *gc, PurpleNotifySearchResults *results,
909 void *data_)
910 {
911 PidginNotifySearchResultsData *data = data_;
912 GtkTreeSelection *selection;
913 GtkListStore *model = data->model;
914 GtkTreeIter iter;
915 GdkPixbuf *pixbuf;
916 GList *row, *column;
917 gchar *previous_selection = NULL;
918 guint n;
919
920 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(data->treeview));
921 if (gtk_tree_selection_get_selected(selection, NULL, &iter))
922 gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 1, &previous_selection, -1);
923
924 gtk_list_store_clear(data->model);
925
926 pixbuf = pidgin_create_prpl_icon(purple_connection_get_account(gc), PIDGIN_PRPL_ICON_SMALL);
927
928 for (row = results->rows; row != NULL; row = row->next) {
929
930 gtk_list_store_append(model, &iter);
931 gtk_list_store_set(model, &iter, 0, pixbuf, -1);
932
933 n = 1;
934 column = row->data;
935 /* Select this row, if the first column matches the previously
936 * selected row OR if there is only one row in the results. */
937 if (!g_strcmp0(previous_selection, column->data) ||
938 (row == results->rows && !row->next))
939 gtk_tree_selection_select_iter(selection, &iter);
940
941 for (; column != NULL; column = column->next) {
942 GValue v;
943
944 v.g_type = 0;
945 g_value_init(&v, G_TYPE_STRING);
946
947 g_value_set_static_string(&v, column->data);
948 gtk_list_store_set_value(model, &iter, n, &v);
949 n++;
950 }
951 }
952
953 /* The first set of results need to stick around, as we reference
954 * the buttons from there. But any updates from later calls to
955 * purple_notify_searchresults_new_rows() must be freed. */
956 if (results != data->results)
957 purple_notify_searchresults_free(results);
958
959 g_free(previous_selection);
960
961 if (pixbuf != NULL)
962 g_object_unref(pixbuf);
963 }
964
965 static void *
pidgin_notify_searchresults(PurpleConnection * gc,const char * title,const char * primary,const char * secondary,PurpleNotifySearchResults * results,gpointer user_data)966 pidgin_notify_searchresults(PurpleConnection *gc, const char *title,
967 const char *primary, const char *secondary,
968 PurpleNotifySearchResults *results, gpointer user_data)
969 {
970 GtkWidget *window;
971 GtkWidget *treeview;
972 GtkWidget *close_button;
973 GType *col_types;
974 GtkListStore *model;
975 GtkCellRenderer *renderer;
976 guint col_num;
977 GList *columniter;
978 guint i;
979 GList *l;
980
981 GtkWidget *vbox;
982 GtkWidget *label;
983 PidginNotifySearchResultsData *data;
984 char *label_text;
985 char *primary_esc, *secondary_esc;
986
987 g_return_val_if_fail(gc != NULL, NULL);
988 g_return_val_if_fail(results != NULL, NULL);
989
990 data = g_malloc(sizeof(PidginNotifySearchResultsData));
991 data->user_data = user_data;
992 data->results = results;
993
994 /* Create the window */
995 window = gtk_dialog_new();
996 gtk_window_set_title(GTK_WINDOW(window), title ? title :_("Search Results"));
997 gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER);
998 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
999
1000 g_signal_connect_swapped(G_OBJECT(window), "delete_event",
1001 G_CALLBACK(searchresults_close_cb), data);
1002
1003 /* Setup the main vbox */
1004 vbox = GTK_DIALOG(window)->vbox;
1005
1006 /* Setup the descriptive label */
1007 primary_esc = (primary != NULL) ? g_markup_escape_text(primary, -1) : NULL;
1008 secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
1009 label_text = g_strdup_printf(
1010 "<span weight=\"bold\" size=\"larger\">%s</span>%s%s",
1011 (primary ? primary_esc : ""),
1012 (primary && secondary ? "\n" : ""),
1013 (secondary ? secondary_esc : ""));
1014 g_free(primary_esc);
1015 g_free(secondary_esc);
1016 label = gtk_label_new(NULL);
1017 gtk_label_set_markup(GTK_LABEL(label), label_text);
1018 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1019 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
1020 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1021 gtk_widget_show(label);
1022 g_free(label_text);
1023
1024 /* +1 is for the automagically created Status column. */
1025 col_num = g_list_length(results->columns) + 1;
1026
1027 /* Setup the list model */
1028 col_types = g_new0(GType, col_num);
1029
1030 /* There always is this first column. */
1031 col_types[0] = GDK_TYPE_PIXBUF;
1032 for (i = 1; i < col_num; i++) {
1033 col_types[i] = G_TYPE_STRING;
1034 }
1035 model = gtk_list_store_newv(col_num, col_types);
1036 g_free(col_types);
1037
1038 /* Setup the treeview */
1039 treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1040 g_object_unref(G_OBJECT(model));
1041 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
1042 gtk_widget_set_size_request(treeview, 500, 400);
1043 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)),
1044 GTK_SELECTION_SINGLE);
1045 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), TRUE);
1046 gtk_box_pack_start(GTK_BOX(vbox),
1047 pidgin_make_scrollable(treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1),
1048 TRUE, TRUE, 0);
1049 gtk_widget_show(treeview);
1050
1051 renderer = gtk_cell_renderer_pixbuf_new();
1052 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview),
1053 -1, "", renderer, "pixbuf", 0, NULL);
1054
1055 i = 1;
1056 for (columniter = results->columns; columniter != NULL; columniter = columniter->next) {
1057 PurpleNotifySearchColumn *column = columniter->data;
1058 renderer = gtk_cell_renderer_text_new();
1059
1060 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview), -1,
1061 column->title, renderer, "text", i, NULL);
1062 i++;
1063 }
1064
1065 for (l = results->buttons; l; l = l->next) {
1066 PurpleNotifySearchButton *b = l->data;
1067 GtkWidget *button = NULL;
1068 switch (b->type) {
1069 case PURPLE_NOTIFY_BUTTON_LABELED:
1070 if(b->label) {
1071 button = gtk_dialog_add_button(GTK_DIALOG(window), b->label, GTK_RESPONSE_NONE);
1072 } else {
1073 purple_debug_warning("gtknotify", "Missing button label\n");
1074 }
1075 break;
1076 case PURPLE_NOTIFY_BUTTON_CONTINUE:
1077 button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_GO_FORWARD, GTK_RESPONSE_NONE);
1078 break;
1079 case PURPLE_NOTIFY_BUTTON_ADD:
1080 button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_ADD, GTK_RESPONSE_NONE);
1081 break;
1082 case PURPLE_NOTIFY_BUTTON_INFO:
1083 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_TOOLBAR_USER_INFO, GTK_RESPONSE_NONE);
1084 break;
1085 case PURPLE_NOTIFY_BUTTON_IM:
1086 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW, GTK_RESPONSE_NONE);
1087 break;
1088 case PURPLE_NOTIFY_BUTTON_JOIN:
1089 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_CHAT, GTK_RESPONSE_NONE);
1090 break;
1091 case PURPLE_NOTIFY_BUTTON_INVITE:
1092 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_INVITE, GTK_RESPONSE_NONE);
1093 break;
1094 default:
1095 purple_debug_warning("gtknotify", "Incorrect button type: %d\n", b->type);
1096 }
1097 if (button != NULL) {
1098 PidginNotifySearchResultsButtonData *bd;
1099
1100 bd = g_new0(PidginNotifySearchResultsButtonData, 1);
1101 bd->button = b;
1102 bd->data = data;
1103
1104 g_signal_connect(G_OBJECT(button), "clicked",
1105 G_CALLBACK(searchresults_callback_wrapper_cb), bd);
1106 g_signal_connect_swapped(G_OBJECT(button), "destroy", G_CALLBACK(g_free), bd);
1107 }
1108 }
1109
1110 /* Add the Close button */
1111 close_button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
1112
1113 g_signal_connect_swapped(G_OBJECT(close_button), "clicked",
1114 G_CALLBACK(searchresults_close_cb), data);
1115
1116 data->account = gc->account;
1117 data->model = model;
1118 data->treeview = treeview;
1119 data->window = window;
1120
1121 /* Insert rows. */
1122 pidgin_notify_searchresults_new_rows(gc, results, data);
1123
1124 /* Show the window */
1125 pidgin_auto_parent_window(window);
1126
1127 gtk_widget_show(window);
1128 return data;
1129 }
1130
1131 /** Xerox'ed from Finch! How the tables have turned!! ;) **/
1132 /** User information. **/
1133 static GHashTable *userinfo;
1134
1135 static char *
userinfo_hash(PurpleAccount * account,const char * who)1136 userinfo_hash(PurpleAccount *account, const char *who)
1137 {
1138 char key[256];
1139 snprintf(key, sizeof(key), "%s - %s", purple_account_get_username(account), purple_normalize(account, who));
1140 return g_utf8_strup(key, -1);
1141 }
1142
1143 static void
remove_userinfo(GtkWidget * widget,gpointer key)1144 remove_userinfo(GtkWidget *widget, gpointer key)
1145 {
1146 PidginUserInfo *pinfo = g_hash_table_lookup(userinfo, key);
1147
1148 while (pinfo->count--)
1149 purple_notify_close(PURPLE_NOTIFY_USERINFO, widget);
1150
1151 g_hash_table_remove(userinfo, key);
1152 }
1153
1154 static void *
pidgin_notify_userinfo(PurpleConnection * gc,const char * who,PurpleNotifyUserInfo * user_info)1155 pidgin_notify_userinfo(PurpleConnection *gc, const char *who,
1156 PurpleNotifyUserInfo *user_info)
1157 {
1158 char *info;
1159 void *ui_handle;
1160 char *key = userinfo_hash(purple_connection_get_account(gc), who);
1161 PidginUserInfo *pinfo = NULL;
1162
1163 if (!userinfo) {
1164 userinfo = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
1165 }
1166
1167 info = purple_notify_user_info_get_text_with_newline(user_info, "<br />");
1168 pinfo = g_hash_table_lookup(userinfo, key);
1169 if (pinfo != NULL) {
1170 GtkIMHtml *imhtml = g_object_get_data(G_OBJECT(pinfo->window), "info-widget");
1171 char *linked_text = purple_markup_linkify(info);
1172 gtk_imhtml_clear(imhtml);
1173 gtk_imhtml_append_text(imhtml, linked_text, notify_imhtml_options());
1174 g_free(linked_text);
1175 g_free(key);
1176 ui_handle = pinfo->window;
1177 pinfo->count++;
1178 } else {
1179 char *primary = g_strdup_printf(_("Info for %s"), who);
1180 ui_handle = pidgin_notify_formatted(_("Buddy Information"), primary, NULL, info);
1181 g_signal_handlers_disconnect_by_func(G_OBJECT(ui_handle), G_CALLBACK(formatted_close_cb), NULL);
1182 g_signal_connect(G_OBJECT(ui_handle), "destroy", G_CALLBACK(remove_userinfo), key);
1183 g_free(primary);
1184 pinfo = g_new0(PidginUserInfo, 1);
1185 pinfo->window = ui_handle;
1186 pinfo->count = 1;
1187 g_hash_table_insert(userinfo, key, pinfo);
1188 }
1189 g_free(info);
1190 return ui_handle;
1191 }
1192
1193 static void
pidgin_close_notify(PurpleNotifyType type,void * ui_handle)1194 pidgin_close_notify(PurpleNotifyType type, void *ui_handle)
1195 {
1196 if (type == PURPLE_NOTIFY_EMAIL || type == PURPLE_NOTIFY_EMAILS)
1197 {
1198 PidginNotifyMailData *data = (PidginNotifyMailData *)ui_handle;
1199
1200 if (data) {
1201 g_free(data->url);
1202 g_free(data);
1203 }
1204 }
1205 else if (type == PURPLE_NOTIFY_SEARCHRESULTS)
1206 {
1207 PidginNotifySearchResultsData *data = (PidginNotifySearchResultsData *)ui_handle;
1208
1209 gtk_widget_destroy(data->window);
1210 purple_notify_searchresults_free(data->results);
1211
1212 g_free(data);
1213 }
1214 else if (ui_handle != NULL)
1215 gtk_widget_destroy(GTK_WIDGET(ui_handle));
1216 }
1217
1218 #ifndef _WIN32
1219 static gboolean
uri_command(GSList * arg_list,gboolean sync)1220 uri_command(GSList *arg_list, gboolean sync)
1221 {
1222 gchar *tmp;
1223 GError *error = NULL;
1224 GSList *it;
1225 gchar **argv;
1226 gint argc, i;
1227 gchar *program;
1228
1229 g_return_val_if_fail(arg_list != NULL, FALSE);
1230
1231 program = arg_list->data;
1232 purple_debug_misc("gtknotify", "Executing %s (%s)\n", program,
1233 sync ? "sync" : "async");
1234
1235 if (!purple_program_is_valid(program)) {
1236 purple_debug_error("gtknotify", "Command \"%s\" is invalid\n",
1237 program);
1238 tmp = g_strdup_printf(
1239 _("The browser command \"%s\" is invalid."),
1240 program ? program : "(null)");
1241 purple_notify_error(NULL, NULL, _("Unable to open URL"), tmp);
1242 g_free(tmp);
1243
1244 return FALSE;
1245 }
1246
1247 argc = g_slist_length(arg_list);
1248 argv = g_new(gchar*, argc + 1);
1249 i = 0;
1250 for (it = arg_list; it; it = g_slist_next(it)) {
1251 if (purple_debug_is_verbose()) {
1252 purple_debug_misc("gtknotify", "argv[%d] = \"%s\"\n",
1253 i, (gchar*)it->data);
1254 }
1255 argv[i++] = it->data;
1256 }
1257 argv[i] = NULL;
1258
1259 if (sync) {
1260 gint exit_status = 0;
1261
1262 if (g_spawn_sync(NULL, argv, NULL, G_SPAWN_SEARCH_PATH |
1263 G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
1264 NULL, NULL, NULL, NULL, &exit_status, &error) &&
1265 exit_status == 0)
1266 {
1267 g_free(argv);
1268 return TRUE;
1269 }
1270
1271 purple_debug_error("gtknotify",
1272 "Error launching \"%s\": %s (status: %d)\n", program,
1273 error ? error->message : "(null)", exit_status);
1274 tmp = g_strdup_printf(_("Error launching \"%s\": %s"), program,
1275 error ? error->message : "(null)");
1276 g_error_free(error);
1277 purple_notify_error(NULL, NULL, _("Unable to open URL"), tmp);
1278 g_free(tmp);
1279
1280 g_free(argv);
1281 return FALSE;
1282 }
1283
1284 if (g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH |
1285 G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, NULL,
1286 NULL, NULL, &error))
1287 {
1288 g_free(argv);
1289 return TRUE;
1290 }
1291
1292 purple_debug_warning("gtknotify", "Error launching \"%s\": %s\n",
1293 program, error ? error->message : "(null)");
1294 g_error_free(error);
1295
1296 g_free(argv);
1297 return FALSE;
1298 }
1299 #endif /* _WIN32 */
1300
1301 static void *
pidgin_notify_uri(const char * uri)1302 pidgin_notify_uri(const char *uri)
1303 {
1304 #ifndef _WIN32
1305 const char *web_browser;
1306 int place;
1307 gchar *uri_escaped, *uri_custom = NULL;
1308 GSList *argv = NULL, *argv_remote = NULL;
1309 gchar **usercmd_argv = NULL;
1310
1311 uri_escaped = purple_uri_escape_for_open(uri);
1312
1313 web_browser = purple_prefs_get_string(PIDGIN_PREFS_ROOT
1314 "/browsers/browser");
1315 place = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/browsers/place");
1316
1317 /* if they are running gnome, use the gnome web browser */
1318 if (purple_running_gnome() == TRUE) {
1319 char *tmp = g_find_program_in_path("xdg-open");
1320 if (tmp == NULL)
1321 argv = g_slist_append(argv, "gnome-open");
1322 else
1323 argv = g_slist_append(argv, "xdg-open");
1324 g_free(tmp);
1325 argv = g_slist_append(argv, uri_escaped);
1326 } else if (purple_running_osx() == TRUE) {
1327 argv = g_slist_append(argv, "open");
1328 argv = g_slist_append(argv, uri_escaped);
1329 } else if (purple_strequal(web_browser, "epiphany") ||
1330 purple_strequal(web_browser, "galeon"))
1331 {
1332 argv = g_slist_append(argv, (gpointer)web_browser);
1333
1334 if (place == PIDGIN_BROWSER_NEW_WINDOW)
1335 argv = g_slist_append(argv, "-w");
1336 else if (place == PIDGIN_BROWSER_NEW_TAB)
1337 argv = g_slist_append(argv, "-n");
1338
1339 argv = g_slist_append(argv, uri_escaped);
1340 } else if (purple_strequal(web_browser, "xdg-open")) {
1341 argv = g_slist_append(argv, "xdg-open");
1342 argv = g_slist_append(argv, uri_escaped);
1343 } else if (purple_strequal(web_browser, "gnome-open")) {
1344 argv = g_slist_append(argv, "gnome-open");
1345 argv = g_slist_append(argv, uri_escaped);
1346 } else if (purple_strequal(web_browser, "kfmclient")) {
1347 argv = g_slist_append(argv, "kfmclient");
1348 argv = g_slist_append(argv, "openURL");
1349 argv = g_slist_append(argv, uri_escaped);
1350 /*
1351 * Does Konqueror have options to open in new tab
1352 * and/or current window?
1353 */
1354 } else if (purple_strequal(web_browser, "firefox")) {
1355 argv = g_slist_append(argv, "firefox");
1356 if (place == PIDGIN_BROWSER_NEW_WINDOW) {
1357 argv = g_slist_append(argv, "--new-window");
1358 } else if (place == PIDGIN_BROWSER_NEW_TAB) {
1359 argv = g_slist_append(argv, "--new-tab");
1360 }
1361 argv = g_slist_append(argv, uri_escaped);
1362 } else if (purple_strequal(web_browser, "mozilla") ||
1363 purple_strequal(web_browser, "mozilla-firebird") ||
1364 purple_strequal(web_browser, "seamonkey"))
1365 {
1366 argv = g_slist_append(argv, (gpointer)web_browser);
1367 argv = g_slist_append(argv, uri_escaped);
1368
1369 g_assert(uri_custom == NULL);
1370 if (place == PIDGIN_BROWSER_NEW_WINDOW) {
1371 uri_custom = g_strdup_printf("openURL(%s,new-window)",
1372 uri_escaped);
1373 } else if (place == PIDGIN_BROWSER_NEW_TAB) {
1374 uri_custom = g_strdup_printf("openURL(%s,new-tab)",
1375 uri_escaped);
1376 } else if (place == PIDGIN_BROWSER_CURRENT) {
1377 uri_custom = g_strdup_printf("openURL(%s)",
1378 uri_escaped);
1379 }
1380
1381 if (uri_custom != NULL) {
1382 argv_remote = g_slist_append(argv_remote,
1383 (gpointer)web_browser);
1384
1385 argv_remote = g_slist_append(argv_remote, "-remote");
1386 argv_remote = g_slist_append(argv_remote, uri_custom);
1387 }
1388 } else if (purple_strequal(web_browser, "netscape")) {
1389 argv = g_slist_append(argv, "netscape");
1390 argv = g_slist_append(argv, uri_escaped);
1391
1392 if (place == PIDGIN_BROWSER_NEW_WINDOW) {
1393 uri_custom = g_strdup_printf("openURL(%s,new-window)",
1394 uri_escaped);
1395 } else if (place == PIDGIN_BROWSER_CURRENT) {
1396 uri_custom = g_strdup_printf("openURL(%s)",
1397 uri_escaped);
1398 }
1399
1400 if (uri_custom) {
1401 argv_remote = g_slist_append(argv_remote, "netscape");
1402 argv_remote = g_slist_append(argv_remote, "-remote");
1403 argv_remote = g_slist_append(argv_remote, uri_custom);
1404 }
1405 } else if (purple_strequal(web_browser, "opera")) {
1406 argv = g_slist_append(argv, "opera");
1407
1408 if (place == PIDGIN_BROWSER_NEW_WINDOW)
1409 argv = g_slist_append(argv, "-newwindow");
1410 else if (place == PIDGIN_BROWSER_NEW_TAB)
1411 argv = g_slist_append(argv, "-newpage");
1412 else if (place == PIDGIN_BROWSER_CURRENT)
1413 argv = g_slist_append(argv, "-activetab");
1414 /* `opera -remote "openURL(%s)"` command causes Pidgin's hang,
1415 * if there is no Opera instance running.
1416 */
1417
1418 argv = g_slist_append(argv, uri_escaped);
1419 } else if (purple_strequal(web_browser, "google-chrome")) {
1420 /* Google Chrome doesn't have command-line arguments that
1421 * control the opening of links from external calls. This is
1422 * controlled solely from a preference within Google Chrome.
1423 */
1424 argv = g_slist_append(argv, "google-chrome");
1425 argv = g_slist_append(argv, uri_escaped);
1426 } else if (purple_strequal(web_browser, "chrome")) {
1427 /* Chromium doesn't have command-line arguments that control
1428 * the opening of links from external calls. This is controlled
1429 * solely from a preference within Chromium.
1430 */
1431 argv = g_slist_append(argv, "chrome");
1432 argv = g_slist_append(argv, uri_escaped);
1433 } else if (purple_strequal(web_browser, "chromium-browser")) {
1434 /* Chromium doesn't have command-line arguments that control the
1435 * opening of links from external calls. This is controlled
1436 * solely from a preference within Chromium.
1437 */
1438 argv = g_slist_append(argv, "chromium-browser");
1439 argv = g_slist_append(argv, uri_escaped);
1440 } else if (purple_strequal(web_browser, "custom")) {
1441 GError *error = NULL;
1442 const char *usercmd_command;
1443 gint usercmd_argc, i;
1444 gboolean uri_added = FALSE;
1445
1446 usercmd_command = purple_prefs_get_string(PIDGIN_PREFS_ROOT
1447 "/browsers/manual_command");
1448
1449 if (usercmd_command == NULL || *usercmd_command == '\0') {
1450 purple_notify_error(NULL, NULL, _("Unable to open URL"),
1451 _("The 'Manual' browser command has been "
1452 "chosen, but no command has been set."));
1453 g_free(uri_escaped);
1454 return NULL;
1455 }
1456
1457 if (!g_shell_parse_argv(usercmd_command, &usercmd_argc,
1458 &usercmd_argv, &error))
1459 {
1460 purple_notify_error(NULL, NULL, _("Unable to open URL: "
1461 "the 'Manual' browser command seems invalid."),
1462 error ? error->message : NULL);
1463 g_error_free(error);
1464 g_free(uri_escaped);
1465 return NULL;
1466 }
1467
1468 for (i = 0; i < usercmd_argc; i++) {
1469 gchar *cmd_part = usercmd_argv[i];
1470
1471 if (uri_added || strstr(cmd_part, "%s") == NULL) {
1472 argv = g_slist_append(argv, cmd_part);
1473 continue;
1474 }
1475
1476 uri_custom = purple_strreplace(cmd_part, "%s",
1477 uri_escaped);
1478 argv = g_slist_append(argv, uri_custom);
1479 uri_added = TRUE;
1480 }
1481
1482 /* There is no "%s" in the browser command. Assume the user
1483 * wanted the URL tacked on to the end of the command.
1484 */
1485 if (!uri_added)
1486 argv = g_slist_append(argv, uri_escaped);
1487 }
1488
1489 if (argv_remote != NULL) {
1490 /* try the remote command first */
1491 if (!uri_command(argv_remote, TRUE))
1492 uri_command(argv, FALSE);
1493 } else
1494 uri_command(argv, FALSE);
1495
1496 g_strfreev(usercmd_argv);
1497 g_free(uri_escaped);
1498 g_free(uri_custom);
1499 g_slist_free(argv);
1500 g_slist_free(argv_remote);
1501
1502 #else /* !_WIN32 */
1503 winpidgin_notify_uri(uri);
1504 #endif /* !_WIN32 */
1505
1506 return NULL;
1507 }
1508
1509 void
pidgin_notify_pounce_add(PurpleAccount * account,PurplePounce * pounce,const char * alias,const char * event,const char * message,const char * date)1510 pidgin_notify_pounce_add(PurpleAccount *account, PurplePounce *pounce,
1511 const char *alias, const char *event, const char *message, const char *date)
1512 {
1513 GdkPixbuf *icon;
1514 GtkTreeIter iter;
1515 PidginNotifyPounceData *pounce_data;
1516 gboolean first = (pounce_dialog == NULL);
1517
1518 if (pounce_dialog == NULL)
1519 pounce_dialog = pidgin_create_notification_dialog(PIDGIN_NOTIFY_POUNCE);
1520
1521 icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
1522
1523 pounce_data = g_new(PidginNotifyPounceData, 1);
1524
1525 pounce_data->account = account;
1526 pounce_data->pounce = pounce;
1527 pounce_data->pouncee = g_strdup(purple_pounce_get_pouncee(pounce));
1528
1529 gtk_tree_store_append(pounce_dialog->treemodel, &iter, NULL);
1530
1531 gtk_tree_store_set(pounce_dialog->treemodel, &iter,
1532 PIDGIN_POUNCE_ICON, icon,
1533 PIDGIN_POUNCE_ALIAS, alias,
1534 PIDGIN_POUNCE_EVENT, event,
1535 PIDGIN_POUNCE_TEXT, (message != NULL)? message : _("No message"),
1536 PIDGIN_POUNCE_DATE, date,
1537 PIDGIN_POUNCE_DATA, pounce_data,
1538 -1);
1539
1540 if (first) {
1541 GtkTreeSelection *selection =
1542 gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
1543 gtk_tree_selection_select_iter(selection, &iter);
1544 }
1545
1546 if (icon)
1547 g_object_unref(icon);
1548
1549 gtk_widget_show_all(pounce_dialog->dialog);
1550
1551 return;
1552 }
1553
1554 static PidginNotifyDialog *
pidgin_create_notification_dialog(PidginNotifyType type)1555 pidgin_create_notification_dialog(PidginNotifyType type)
1556 {
1557 GtkTreeStore *model = NULL;
1558 GtkWidget *dialog = NULL;
1559 GtkWidget *label = NULL;
1560 GtkCellRenderer *rend;
1561 GtkTreeViewColumn *column;
1562 GtkWidget *button = NULL;
1563 GtkWidget *vbox = NULL;
1564 GtkTreeSelection *sel;
1565 PidginNotifyDialog *spec_dialog = NULL;
1566
1567 g_return_val_if_fail(type < PIDGIN_NOTIFY_TYPES, NULL);
1568
1569 if (type == PIDGIN_NOTIFY_MAIL) {
1570 g_return_val_if_fail(mail_dialog == NULL, mail_dialog);
1571
1572 model = gtk_tree_store_new(COLUMNS_PIDGIN_MAIL,
1573 GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_POINTER);
1574
1575 } else if (type == PIDGIN_NOTIFY_POUNCE) {
1576 g_return_val_if_fail(pounce_dialog == NULL, pounce_dialog);
1577
1578 model = gtk_tree_store_new(COLUMNS_PIDGIN_POUNCE,
1579 GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
1580 G_TYPE_STRING, G_TYPE_POINTER);
1581 }
1582
1583 dialog = gtk_dialog_new();
1584
1585 /* Setup the dialog */
1586 gtk_container_set_border_width(GTK_CONTAINER(dialog), PIDGIN_HIG_BOX_SPACE);
1587 gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BOX_SPACE);
1588 gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
1589 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BORDER);
1590
1591 /* Vertical box */
1592 vbox = GTK_DIALOG(dialog)->vbox;
1593
1594 /* Golden ratio it up! */
1595 gtk_widget_set_size_request(dialog, 550, 400);
1596
1597 spec_dialog = g_new0(PidginNotifyDialog, 1);
1598 spec_dialog->dialog = dialog;
1599
1600 spec_dialog->treemodel = model;
1601 spec_dialog->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1602 g_object_unref(G_OBJECT(model));
1603
1604 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(spec_dialog->treeview), TRUE);
1605
1606 if (type == PIDGIN_NOTIFY_MAIL) {
1607 gtk_window_set_title(GTK_WINDOW(dialog), _("New Mail"));
1608 gtk_window_set_role(GTK_WINDOW(dialog), "new_mail_detailed");
1609 g_signal_connect(G_OBJECT(dialog), "focus-in-event",
1610 G_CALLBACK(mail_window_focus_cb), NULL);
1611
1612 gtk_dialog_add_button(GTK_DIALOG(dialog),
1613 _("Open All Messages"), GTK_RESPONSE_ACCEPT);
1614
1615 button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1616 PIDGIN_STOCK_OPEN_MAIL, GTK_RESPONSE_YES);
1617 spec_dialog->open_button = button;
1618
1619 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(spec_dialog->treeview), FALSE);
1620
1621 gtk_tree_view_set_search_column(GTK_TREE_VIEW(spec_dialog->treeview), PIDGIN_MAIL_TEXT);
1622 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(spec_dialog->treeview),
1623 pidgin_tree_view_search_equal_func, NULL, NULL);
1624
1625 g_signal_connect(G_OBJECT(dialog), "response",
1626 G_CALLBACK(email_response_cb), spec_dialog);
1627 g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(spec_dialog->treeview))),
1628 "changed", G_CALLBACK(selection_changed_cb), spec_dialog);
1629 g_signal_connect(G_OBJECT(spec_dialog->treeview), "row-activated", G_CALLBACK(email_row_activated_cb), NULL);
1630
1631 column = gtk_tree_view_column_new();
1632 gtk_tree_view_column_set_resizable(column, TRUE);
1633 rend = gtk_cell_renderer_pixbuf_new();
1634 gtk_tree_view_column_pack_start(column, rend, FALSE);
1635
1636 gtk_tree_view_column_set_attributes(column, rend, "pixbuf", PIDGIN_MAIL_ICON, NULL);
1637 rend = gtk_cell_renderer_text_new();
1638 gtk_tree_view_column_pack_start(column, rend, TRUE);
1639 gtk_tree_view_column_set_attributes(column, rend, "markup", PIDGIN_MAIL_TEXT, NULL);
1640 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1641
1642 label = gtk_label_new(NULL);
1643 gtk_label_set_markup(GTK_LABEL(label), _("<span weight=\"bold\" size=\"larger\">You have mail!</span>"));
1644
1645 } else if (type == PIDGIN_NOTIFY_POUNCE) {
1646 gtk_window_set_title(GTK_WINDOW(dialog), _("New Pounces"));
1647
1648 button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1649 _("IM"), GTK_RESPONSE_YES);
1650 gtk_widget_set_sensitive(button, FALSE);
1651 spec_dialog->open_button = button;
1652
1653 button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1654 PIDGIN_STOCK_MODIFY, GTK_RESPONSE_APPLY);
1655 gtk_widget_set_sensitive(button, FALSE);
1656 spec_dialog->edit_button = button;
1657
1658 /* Translators: Make sure you translate "Dismiss" differently than
1659 "close"! This string is used in the "You have pounced" dialog
1660 that appears when one of your Buddy Pounces is triggered. In
1661 this context "Dismiss" means "I acknowledge that I've seen that
1662 this pounce was triggered--remove it from this list." Translating
1663 it as "Remove" is acceptable if you can't think of a more precise
1664 word. */
1665 button = gtk_dialog_add_button(GTK_DIALOG(dialog), _("Dismiss"),
1666 GTK_RESPONSE_NO);
1667 gtk_widget_set_sensitive(button, FALSE);
1668 spec_dialog->dismiss_button = button;
1669
1670 g_signal_connect(G_OBJECT(dialog), "response",
1671 G_CALLBACK(pounce_response_cb), spec_dialog);
1672
1673 column = gtk_tree_view_column_new();
1674 gtk_tree_view_column_set_title(column, _("Buddy"));
1675 gtk_tree_view_column_set_resizable(column, TRUE);
1676 rend = gtk_cell_renderer_pixbuf_new();
1677 gtk_tree_view_column_pack_start(column, rend, FALSE);
1678
1679 gtk_tree_view_column_set_attributes(column, rend, "pixbuf", PIDGIN_POUNCE_ICON, NULL);
1680 rend = gtk_cell_renderer_text_new();
1681 gtk_tree_view_column_pack_start(column, rend, FALSE);
1682 gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_ALIAS);
1683 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1684
1685 column = gtk_tree_view_column_new();
1686 gtk_tree_view_column_set_title(column, _("Event"));
1687 gtk_tree_view_column_set_resizable(column, TRUE);
1688 rend = gtk_cell_renderer_text_new();
1689 gtk_tree_view_column_pack_start(column, rend, FALSE);
1690 gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_EVENT);
1691 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1692
1693 column = gtk_tree_view_column_new();
1694 gtk_tree_view_column_set_title(column, _("Message"));
1695 gtk_tree_view_column_set_resizable(column, TRUE);
1696 rend = gtk_cell_renderer_text_new();
1697 gtk_tree_view_column_pack_start(column, rend, FALSE);
1698 gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_TEXT);
1699 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1700
1701 column = gtk_tree_view_column_new();
1702 gtk_tree_view_column_set_title(column, _("Date"));
1703 gtk_tree_view_column_set_resizable(column, TRUE);
1704 rend = gtk_cell_renderer_text_new();
1705 gtk_tree_view_column_pack_start(column, rend, FALSE);
1706 gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_DATE);
1707 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1708
1709 label = gtk_label_new(NULL);
1710 gtk_label_set_markup(GTK_LABEL(label), _("<span weight=\"bold\" size=\"larger\">You have pounced!</span>"));
1711
1712 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(spec_dialog->treeview));
1713 gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
1714 g_signal_connect(G_OBJECT(sel), "changed",
1715 G_CALLBACK(pounce_row_selected_cb), NULL);
1716 g_signal_connect(G_OBJECT(spec_dialog->treeview), "row-activated",
1717 G_CALLBACK(pounce_response_open_ims), NULL);
1718 }
1719
1720 gtk_dialog_add_button(GTK_DIALOG(dialog),
1721 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
1722
1723 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1724 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
1725 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1726 gtk_box_pack_start(GTK_BOX(vbox),
1727 pidgin_make_scrollable(spec_dialog->treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1),
1728 TRUE, TRUE, 2);
1729
1730 return spec_dialog;
1731 }
1732
1733 static void
signed_off_cb(PurpleConnection * gc,gpointer unused)1734 signed_off_cb(PurpleConnection *gc, gpointer unused)
1735 {
1736 /* Clear any pending emails for this account */
1737 pidgin_notify_emails(gc, 0, FALSE, NULL, NULL, NULL, NULL);
1738
1739 if (mail_dialog != NULL && mail_dialog->total_count == 0)
1740 reset_mail_dialog(NULL);
1741 }
1742
1743 static void*
pidgin_notify_get_handle(void)1744 pidgin_notify_get_handle(void)
1745 {
1746 static int handle;
1747 return &handle;
1748 }
1749
pidgin_notify_init(void)1750 void pidgin_notify_init(void)
1751 {
1752 void *handle = pidgin_notify_get_handle();
1753
1754 purple_signal_connect(purple_connections_get_handle(), "signed-off",
1755 handle, PURPLE_CALLBACK(signed_off_cb), NULL);
1756 }
1757
pidgin_notify_uninit(void)1758 void pidgin_notify_uninit(void)
1759 {
1760 purple_signals_disconnect_by_handle(pidgin_notify_get_handle());
1761 }
1762
1763 static PurpleNotifyUiOps ops =
1764 {
1765 pidgin_notify_message,
1766 pidgin_notify_email,
1767 pidgin_notify_emails,
1768 pidgin_notify_formatted,
1769 pidgin_notify_searchresults,
1770 pidgin_notify_searchresults_new_rows,
1771 pidgin_notify_userinfo,
1772 pidgin_notify_uri,
1773 pidgin_close_notify,
1774 NULL,
1775 NULL,
1776 NULL,
1777 NULL
1778 };
1779
1780 PurpleNotifyUiOps *
pidgin_notify_get_ui_ops(void)1781 pidgin_notify_get_ui_ops(void)
1782 {
1783 return &ops;
1784 }
1785