Add purple_util_write_data_to_*_file declarations
[pidgin-git.git] / pidgin / gtknotify.c
blob6ba9796fff2fcea29babe2b605ad23bddf5a1700
1 /* pidgin
3 * Pidgin is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
5 * source distribution.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
21 #include "internal.h"
22 #include "pidgin.h"
24 #include <gdk/gdkkeysyms.h>
26 #include "account.h"
27 #include "connection.h"
28 #include "debug.h"
29 #include "prefs.h"
30 #include "pidginstock.h"
31 #include "util.h"
33 #include "gtk3compat.h"
34 #include "gtkblist.h"
35 #include "gtknotify.h"
36 #include "gtkpounce.h"
37 #include "gtkutils.h"
38 #include "gtkwebview.h"
40 typedef struct
42 GtkWidget *window;
43 int count;
44 } PidginUserInfo;
46 typedef struct
48 PurpleAccount *account;
49 char *url;
50 GtkWidget *label;
51 int count;
52 gboolean purple_has_handle;
53 } PidginNotifyMailData;
55 typedef struct
57 PurpleAccount *account;
58 PurplePounce *pounce;
59 char *pouncee;
60 } PidginNotifyPounceData;
63 typedef struct
65 PurpleAccount *account;
66 GtkListStore *model;
67 GtkWidget *treeview;
68 GtkWidget *window;
69 gpointer user_data;
70 PurpleNotifySearchResults *results;
72 } PidginNotifySearchResultsData;
74 typedef struct
76 PurpleNotifySearchButton *button;
77 PidginNotifySearchResultsData *data;
79 } PidginNotifySearchResultsButtonData;
81 enum
83 PIDGIN_MAIL_ICON,
84 PIDGIN_MAIL_TEXT,
85 PIDGIN_MAIL_DATA,
86 COLUMNS_PIDGIN_MAIL
89 enum
91 PIDGIN_POUNCE_ICON,
92 PIDGIN_POUNCE_ALIAS,
93 PIDGIN_POUNCE_EVENT,
94 PIDGIN_POUNCE_TEXT,
95 PIDGIN_POUNCE_DATE,
96 PIDGIN_POUNCE_DATA,
97 COLUMNS_PIDGIN_POUNCE
101 typedef struct _PidginNotifyDialog
104 * This must be first so PidginNotifyDialog can masquerade as the
105 * dialog widget.
107 GtkWidget *dialog;
108 GtkWidget *treeview;
109 GtkTreeStore *treemodel;
110 GtkLabel *label;
111 GtkWidget *open_button;
112 GtkWidget *dismiss_button;
113 GtkWidget *edit_button;
114 int total_count;
115 gboolean in_use;
116 } PidginNotifyDialog;
118 typedef enum
120 PIDGIN_NOTIFY_MAIL,
121 PIDGIN_NOTIFY_POUNCE,
122 PIDGIN_NOTIFY_TYPES
123 } PidginNotifyType;
125 static PidginNotifyDialog *mail_dialog = NULL;
126 static PidginNotifyDialog *pounce_dialog = NULL;
128 static PidginNotifyDialog *pidgin_create_notification_dialog(PidginNotifyType type);
129 static void *pidgin_notify_emails(PurpleConnection *gc, size_t count, gboolean detailed,
130 const char **subjects,
131 const char **froms, const char **tos,
132 const char **urls);
134 static void pidgin_close_notify(PurpleNotifyType type, void *ui_handle);
136 static void
137 message_response_cb(GtkDialog *dialog, gint id, GtkWidget *widget)
139 purple_notify_close(PURPLE_NOTIFY_MESSAGE, widget);
142 static void
143 pounce_response_close(PidginNotifyDialog *dialog)
145 GtkTreeIter iter;
146 PidginNotifyPounceData *pounce_data;
148 while (gtk_tree_model_get_iter_first(
149 GTK_TREE_MODEL(pounce_dialog->treemodel), &iter)) {
150 gtk_tree_model_get(GTK_TREE_MODEL(pounce_dialog->treemodel), &iter,
151 PIDGIN_POUNCE_DATA, &pounce_data,
152 -1);
153 gtk_tree_store_remove(dialog->treemodel, &iter);
155 g_free(pounce_data->pouncee);
156 g_free(pounce_data);
159 gtk_widget_destroy(pounce_dialog->dialog);
160 g_free(pounce_dialog);
161 pounce_dialog = NULL;
164 static void
165 delete_foreach(GtkTreeModel *model, GtkTreePath *path,
166 GtkTreeIter *iter, gpointer data)
168 PidginNotifyPounceData *pounce_data;
170 gtk_tree_model_get(model, iter,
171 PIDGIN_POUNCE_DATA, &pounce_data,
172 -1);
174 if (pounce_data != NULL) {
175 g_free(pounce_data->pouncee);
176 g_free(pounce_data);
180 static void
181 open_im_foreach(GtkTreeModel *model, GtkTreePath *path,
182 GtkTreeIter *iter, gpointer data)
184 PidginNotifyPounceData *pounce_data;
186 gtk_tree_model_get(model, iter,
187 PIDGIN_POUNCE_DATA, &pounce_data,
188 -1);
190 if (pounce_data != NULL) {
191 PurpleIMConversation *im;
193 im = purple_im_conversation_new(pounce_data->account, pounce_data->pouncee);
194 purple_conversation_present(PURPLE_CONVERSATION(im));
198 static void
199 append_to_list(GtkTreeModel *model, GtkTreePath *path,
200 GtkTreeIter *iter, gpointer data)
202 GList **list = data;
203 *list = g_list_prepend(*list, gtk_tree_path_copy(path));
206 static void
207 pounce_response_dismiss()
209 GtkTreeModel *model = GTK_TREE_MODEL(pounce_dialog->treemodel);
210 GtkTreeSelection *selection;
211 GtkTreeIter iter;
212 GtkTreeIter new_selection;
213 GList *list = NULL;
214 gboolean found_selection = FALSE;
216 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
217 gtk_tree_selection_selected_foreach(selection, delete_foreach, pounce_dialog);
218 gtk_tree_selection_selected_foreach(selection, append_to_list, &list);
220 g_return_if_fail(list != NULL);
222 if (list->next == NULL) {
223 gtk_tree_model_get_iter(model, &new_selection, list->data);
224 if (gtk_tree_model_iter_next(model, &new_selection))
225 found_selection = TRUE;
226 else {
227 /* This is the last thing in the list */
228 GtkTreePath *path;
230 /* Because gtk_tree_model_iter_prev doesn't exist... */
231 gtk_tree_model_get_iter(model, &new_selection, list->data);
232 path = gtk_tree_model_get_path(model, &new_selection);
233 if (gtk_tree_path_prev(path)) {
234 gtk_tree_model_get_iter(model, &new_selection, path);
235 found_selection = TRUE;
238 gtk_tree_path_free(path);
242 while (list) {
243 if (gtk_tree_model_get_iter(model, &iter, list->data)) {
244 gtk_tree_store_remove(GTK_TREE_STORE(pounce_dialog->treemodel), &iter);
246 gtk_tree_path_free(list->data);
247 list = g_list_delete_link(list, list);
250 if (gtk_tree_model_get_iter_first(model, &iter)) {
251 if (found_selection)
252 gtk_tree_selection_select_iter(selection, &new_selection);
253 else
254 gtk_tree_selection_select_iter(selection, &iter);
255 } else
256 pounce_response_close(pounce_dialog);
259 static void
260 pounce_response_open_ims()
262 GtkTreeSelection *selection;
264 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
265 gtk_tree_selection_selected_foreach(selection, open_im_foreach, pounce_dialog);
267 pounce_response_dismiss();
270 static void
271 pounce_response_edit_cb(GtkTreeModel *model, GtkTreePath *path,
272 GtkTreeIter *iter, gpointer data)
274 PidginNotifyPounceData *pounce_data;
275 PidginNotifyDialog *dialog = (PidginNotifyDialog*)data;
276 PurplePounce *pounce;
277 GList *list;
279 list = purple_pounces_get_all();
281 gtk_tree_model_get(GTK_TREE_MODEL(dialog->treemodel), iter,
282 PIDGIN_POUNCE_DATA, &pounce_data,
283 -1);
285 for (; list != NULL; list = list->next) {
286 pounce = list->data;
287 if (pounce == pounce_data->pounce) {
288 pidgin_pounce_editor_show(pounce_data->account, NULL, pounce_data->pounce);
289 return;
293 purple_debug_warning("gtknotify", "Pounce was destroyed.\n");
296 static void
297 pounce_response_cb(GtkDialog *dlg, gint id, PidginNotifyDialog *dialog)
299 GtkTreeSelection *selection = NULL;
301 switch (id) {
302 case GTK_RESPONSE_CLOSE:
303 case GTK_RESPONSE_DELETE_EVENT:
304 pounce_response_close(dialog);
305 break;
306 case GTK_RESPONSE_YES:
307 pounce_response_open_ims();
308 break;
309 case GTK_RESPONSE_NO:
310 pounce_response_dismiss();
311 break;
312 case GTK_RESPONSE_APPLY:
313 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
314 gtk_tree_selection_selected_foreach(selection, pounce_response_edit_cb,
315 dialog);
316 break;
320 static void
321 pounce_row_selected_cb(GtkTreeView *tv, GtkTreePath *path,
322 GtkTreeViewColumn *col, gpointer data)
324 GtkTreeSelection *selection;
325 int count;
327 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
329 count = gtk_tree_selection_count_selected_rows(selection);
331 if (count == 0) {
332 gtk_widget_set_sensitive(pounce_dialog->open_button, FALSE);
333 gtk_widget_set_sensitive(pounce_dialog->edit_button, FALSE);
334 gtk_widget_set_sensitive(pounce_dialog->dismiss_button, FALSE);
335 } else if (count == 1) {
336 GList *pounces;
337 GList *list;
338 PidginNotifyPounceData *pounce_data;
339 GtkTreeIter iter;
341 list = gtk_tree_selection_get_selected_rows(selection, NULL);
342 gtk_tree_model_get_iter(GTK_TREE_MODEL(pounce_dialog->treemodel),
343 &iter, list->data);
344 gtk_tree_model_get(GTK_TREE_MODEL(pounce_dialog->treemodel), &iter,
345 PIDGIN_POUNCE_DATA, &pounce_data,
346 -1);
347 g_list_foreach(list, (GFunc)gtk_tree_path_free, NULL);
348 g_list_free(list);
350 pounces = purple_pounces_get_all();
351 for (; pounces != NULL; pounces = pounces->next) {
352 PurplePounce *pounce = pounces->data;
353 if (pounce == pounce_data->pounce) {
354 gtk_widget_set_sensitive(pounce_dialog->edit_button, TRUE);
355 break;
359 gtk_widget_set_sensitive(pounce_dialog->open_button, TRUE);
360 gtk_widget_set_sensitive(pounce_dialog->dismiss_button, TRUE);
361 } else {
362 gtk_widget_set_sensitive(pounce_dialog->open_button, TRUE);
363 gtk_widget_set_sensitive(pounce_dialog->edit_button, FALSE);
364 gtk_widget_set_sensitive(pounce_dialog->dismiss_button, TRUE);
370 static void
371 reset_mail_dialog(GtkDialog *unused)
373 g_return_if_fail(mail_dialog != NULL);
375 if (mail_dialog->in_use)
376 return;
377 gtk_widget_destroy(mail_dialog->dialog);
378 g_free(mail_dialog);
379 mail_dialog = NULL;
380 purple_signal_emit(purple_notify_get_handle(), "displaying-emails-clear");
383 gboolean
384 pidgin_notify_emails_pending()
386 return mail_dialog != NULL
387 && !gtk_widget_get_visible(mail_dialog->dialog);
390 void pidgin_notify_emails_present(void *data)
392 if (pidgin_notify_emails_pending()) {
393 gtk_widget_show_all(mail_dialog->dialog);
394 mail_dialog->in_use = TRUE;
395 pidgin_blist_set_headline(NULL, NULL, NULL, NULL, NULL);
396 mail_dialog->in_use = FALSE;
398 purple_signal_emit(purple_notify_get_handle(), "displaying-emails-clear");
401 static void
402 email_response_cb(GtkDialog *unused, gint id, PidginNotifyDialog *unused2)
404 PidginNotifyMailData *data = NULL;
405 GtkTreeModel *model = GTK_TREE_MODEL(mail_dialog->treemodel);
406 GtkTreeIter iter;
408 if (id == GTK_RESPONSE_YES)
410 /* A single row activated. Remove that row. */
411 GtkTreeSelection *selection;
413 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(mail_dialog->treeview));
415 if (gtk_tree_selection_get_selected(selection, NULL, &iter))
417 gtk_tree_model_get(model, &iter, PIDGIN_MAIL_DATA, &data, -1);
418 purple_notify_uri(NULL, data->url);
420 gtk_tree_store_remove(mail_dialog->treemodel, &iter);
421 if (data->purple_has_handle)
422 purple_notify_close(PURPLE_NOTIFY_EMAILS, data);
423 else
424 pidgin_close_notify(PURPLE_NOTIFY_EMAILS, data);
426 if (gtk_tree_model_get_iter_first(model, &iter))
427 return;
429 else
430 return;
432 else
434 /* Remove all the rows */
435 while (gtk_tree_model_get_iter_first(model, &iter))
437 gtk_tree_model_get(model, &iter, PIDGIN_MAIL_DATA, &data, -1);
439 if (id == GTK_RESPONSE_ACCEPT)
440 purple_notify_uri(NULL, data->url);
442 gtk_tree_store_remove(mail_dialog->treemodel, &iter);
443 if (data->purple_has_handle)
444 purple_notify_close(PURPLE_NOTIFY_EMAILS, data);
445 else
446 pidgin_close_notify(PURPLE_NOTIFY_EMAILS, data);
450 reset_mail_dialog(NULL);
453 static void
454 email_row_activated_cb(GtkTreeView *tv, GtkTreePath *path,
455 GtkTreeViewColumn *col, gpointer data)
457 email_response_cb(NULL, GTK_RESPONSE_YES, NULL);
460 static gboolean
461 formatted_close_cb(GtkWidget *win, GdkEvent *event, void *user_data)
463 purple_notify_close(PURPLE_NOTIFY_FORMATTED, win);
464 return FALSE;
467 static gboolean
468 searchresults_close_cb(PidginNotifySearchResultsData *data, GdkEvent *event, gpointer user_data)
470 purple_notify_close(PURPLE_NOTIFY_SEARCHRESULTS, data);
471 return FALSE;
474 static void
475 searchresults_callback_wrapper_cb(GtkWidget *widget, PidginNotifySearchResultsButtonData *bd)
477 PidginNotifySearchResultsData *data = bd->data;
479 GtkTreeSelection *selection;
480 GtkTreeModel *model;
481 GtkTreeIter iter;
482 PurpleNotifySearchButton *button;
483 GList *row = NULL;
484 gchar *str;
485 int i;
487 g_return_if_fail(data != NULL);
489 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(data->treeview));
491 if (gtk_tree_selection_get_selected(selection, &model, &iter))
493 for (i = 1; i < gtk_tree_model_get_n_columns(GTK_TREE_MODEL(model)); i++) {
494 gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, i, &str, -1);
495 row = g_list_append(row, str);
499 button = bd->button;
500 button->callback(purple_account_get_connection(data->account), row, data->user_data);
501 g_list_foreach(row, (GFunc)g_free, NULL);
502 g_list_free(row);
505 /* copy-paste from gtkrequest.c */
506 static void
507 pidgin_widget_decorate_account(GtkWidget *cont, PurpleAccount *account)
509 GtkWidget *image;
510 GdkPixbuf *pixbuf;
512 if (!account)
513 return;
515 pixbuf = pidgin_create_protocol_icon(account, PIDGIN_PROTOCOL_ICON_SMALL);
516 image = gtk_image_new_from_pixbuf(pixbuf);
517 g_object_unref(G_OBJECT(pixbuf));
519 gtk_widget_set_tooltip_text(image,
520 purple_account_get_username(account));
522 if (GTK_IS_DIALOG(cont)) {
523 gtk_box_pack_start(GTK_BOX(gtk_dialog_get_action_area(
524 GTK_DIALOG(cont))), image, FALSE, TRUE, 0);
525 gtk_box_reorder_child(GTK_BOX(gtk_dialog_get_action_area(
526 GTK_DIALOG(cont))), image, 0);
527 } else if (GTK_IS_BOX(cont)) {
528 gtk_widget_set_halign(image, GTK_ALIGN_START);
529 gtk_widget_set_valign(image, GTK_ALIGN_START);
530 gtk_box_pack_end(GTK_BOX(cont), image, FALSE, TRUE, 0);
532 gtk_widget_show(image);
535 static void *
536 pidgin_notify_message(PurpleNotifyMessageType type, const char *title,
537 const char *primary, const char *secondary,
538 PurpleRequestCommonParameters *cpar)
540 GtkWidget *dialog;
541 GtkWidget *hbox;
542 GtkWidget *label;
543 GtkWidget *img = NULL;
544 char label_text[2048];
545 const char *icon_name = NULL;
546 char *primary_esc, *secondary_esc;
548 switch (type)
550 case PURPLE_NOTIFY_MSG_ERROR:
551 icon_name = PIDGIN_STOCK_DIALOG_ERROR;
552 break;
554 case PURPLE_NOTIFY_MSG_WARNING:
555 icon_name = PIDGIN_STOCK_DIALOG_WARNING;
556 break;
558 case PURPLE_NOTIFY_MSG_INFO:
559 icon_name = PIDGIN_STOCK_DIALOG_INFO;
560 break;
562 default:
563 icon_name = NULL;
564 break;
567 if (icon_name != NULL)
569 img = gtk_image_new_from_stock(icon_name, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
570 gtk_widget_set_halign(img, GTK_ALIGN_START);
571 gtk_widget_set_valign(img, GTK_ALIGN_START);
574 dialog = gtk_dialog_new_with_buttons(title ? title : PIDGIN_ALERT_TITLE,
575 NULL, 0, GTK_STOCK_CLOSE,
576 GTK_RESPONSE_CLOSE, NULL);
578 gtk_window_set_role(GTK_WINDOW(dialog), "notify_dialog");
580 g_signal_connect(G_OBJECT(dialog), "response",
581 G_CALLBACK(message_response_cb), dialog);
583 gtk_container_set_border_width(GTK_CONTAINER(dialog), PIDGIN_HIG_BORDER);
584 gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
585 gtk_box_set_spacing(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
586 PIDGIN_HIG_BORDER);
587 gtk_container_set_border_width(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
588 PIDGIN_HIG_BOX_SPACE);
590 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PIDGIN_HIG_BORDER);
591 gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
592 hbox);
594 if (img != NULL)
595 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
597 pidgin_widget_decorate_account(hbox,
598 purple_request_cpar_get_account(cpar));
600 primary_esc = g_markup_escape_text(primary, -1);
601 secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
602 g_snprintf(label_text, sizeof(label_text),
603 "<span weight=\"bold\" size=\"larger\">%s</span>%s%s",
604 primary_esc, (secondary ? "\n\n" : ""),
605 (secondary ? secondary_esc : ""));
606 g_free(primary_esc);
607 g_free(secondary_esc);
609 label = gtk_label_new(NULL);
611 gtk_label_set_markup(GTK_LABEL(label), label_text);
612 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
613 gtk_label_set_selectable(GTK_LABEL(label), TRUE);
614 gtk_label_set_xalign(GTK_LABEL(label), 0);
615 gtk_label_set_yalign(GTK_LABEL(label), 0);
616 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
618 g_object_set_data(G_OBJECT(dialog), "pidgin-parent-from",
619 purple_request_cpar_get_parent_from(cpar));
620 pidgin_auto_parent_window(dialog);
622 gtk_widget_show_all(dialog);
624 return dialog;
627 static void
628 selection_changed_cb(GtkTreeSelection *sel, PidginNotifyDialog *dialog)
630 GtkTreeIter iter;
631 GtkTreeModel *model;
632 PidginNotifyMailData *data;
633 gboolean active = TRUE;
635 if (gtk_tree_selection_get_selected(sel, &model, &iter) == FALSE)
636 active = FALSE;
637 else
639 gtk_tree_model_get(model, &iter, PIDGIN_MAIL_DATA, &data, -1);
640 if (data->url == NULL)
641 active = FALSE;
644 gtk_widget_set_sensitive(dialog->open_button, active);
647 static void *
648 pidgin_notify_email(PurpleConnection *gc, const char *subject, const char *from,
649 const char *to, const char *url)
651 return pidgin_notify_emails(gc, 1, (subject != NULL),
652 (subject == NULL ? NULL : &subject),
653 (from == NULL ? NULL : &from),
654 (to == NULL ? NULL : &to),
655 (url == NULL ? NULL : &url));
658 static int
659 mail_window_focus_cb(GtkWidget *widget, GdkEventFocus *focus, gpointer null)
661 pidgin_set_urgent(GTK_WINDOW(widget), FALSE);
662 return 0;
665 /* count == 0 means this is a detailed mail notification.
666 * count > 0 mean non-detailed.
668 static void *
669 pidgin_notify_add_mail(GtkTreeStore *treemodel, PurpleAccount *account, char *notification, const char *url, int count, gboolean clear, gboolean *new_data)
671 PidginNotifyMailData *data = NULL;
672 GtkTreeIter iter;
673 GdkPixbuf *icon;
674 gboolean new_n = TRUE;
676 if (count > 0 || clear) {
677 /* Allow only one non-detailed email notification for each account */
678 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(treemodel), &iter)) {
679 gboolean advanced;
680 do {
681 advanced = FALSE;
682 gtk_tree_model_get(GTK_TREE_MODEL(treemodel), &iter,
683 PIDGIN_MAIL_DATA, &data, -1);
684 if (data && data->account == account) {
685 if (clear) {
686 advanced = gtk_tree_store_remove(treemodel, &iter);
687 mail_dialog->total_count -= data->count;
689 if (data->purple_has_handle)
690 purple_notify_close(PURPLE_NOTIFY_EMAILS, data);
691 else
692 pidgin_close_notify(PURPLE_NOTIFY_EMAILS, data);
693 /* We're completely done if we've processed all entries */
694 if (!advanced)
695 return NULL;
696 } else if (data->count > 0) {
697 new_n = FALSE;
698 g_free(data->url);
699 data->url = NULL;
700 mail_dialog->total_count -= data->count;
701 break;
704 } while (advanced || gtk_tree_model_iter_next(GTK_TREE_MODEL(treemodel), &iter));
708 if (clear)
709 return NULL;
711 icon = pidgin_create_protocol_icon(account, PIDGIN_PROTOCOL_ICON_MEDIUM);
713 if (new_n) {
714 data = g_new0(PidginNotifyMailData, 1);
715 data->purple_has_handle = TRUE;
716 gtk_tree_store_append(treemodel, &iter, NULL);
719 if (url != NULL)
720 data->url = g_strdup(url);
722 gtk_tree_store_set(treemodel, &iter,
723 PIDGIN_MAIL_ICON, icon,
724 PIDGIN_MAIL_TEXT, notification,
725 PIDGIN_MAIL_DATA, data,
726 -1);
727 data->account = account;
728 /* count == 0 indicates we're adding a single detailed e-mail */
729 data->count = count > 0 ? count : 1;
731 if (icon)
732 g_object_unref(icon);
734 if (new_data)
735 *new_data = new_n;
736 return data;
739 static void *
740 pidgin_notify_emails(PurpleConnection *gc, size_t count, gboolean detailed,
741 const char **subjects, const char **froms,
742 const char **tos, const char **urls)
744 char *notification;
745 PurpleAccount *account;
746 PidginNotifyMailData *data = NULL, *data2;
747 gboolean new_data = FALSE;
748 GtkTreeSelection *sel;
749 GtkTreeIter iter;
751 /* Don't bother updating if there aren't new emails and we don't have any displayed currently */
752 if (count == 0 && mail_dialog == NULL)
753 return NULL;
755 account = purple_connection_get_account(gc);
756 if (mail_dialog == NULL)
757 mail_dialog = pidgin_create_notification_dialog(PIDGIN_NOTIFY_MAIL);
759 mail_dialog->total_count += count;
760 if (detailed) {
761 for ( ; count; --count) {
762 char *to_text = NULL;
763 char *from_text = NULL;
764 char *subject_text = NULL;
765 char *tmp;
766 gboolean first = TRUE;
768 if (tos != NULL) {
769 tmp = g_markup_escape_text(*tos, -1);
770 to_text = g_strdup_printf("<b>%s</b>: %s\n", _("Account"), tmp);
771 g_free(tmp);
772 first = FALSE;
773 tos++;
775 if (froms != NULL) {
776 tmp = g_markup_escape_text(*froms, -1);
777 from_text = g_strdup_printf("%s<b>%s</b>: %s\n", first ? "<br>" : "", _("Sender"), tmp);
778 g_free(tmp);
779 first = FALSE;
780 froms++;
782 if (subjects != NULL) {
783 tmp = g_markup_escape_text(*subjects, -1);
784 subject_text = g_strdup_printf("%s<b>%s</b>: %s", first ? "<br>" : "", _("Subject"), tmp);
785 g_free(tmp);
786 first = FALSE;
787 subjects++;
789 #define SAFE(x) ((x) ? (x) : "")
790 notification = g_strdup_printf("%s%s%s", SAFE(to_text), SAFE(from_text), SAFE(subject_text));
791 #undef SAFE
792 g_free(to_text);
793 g_free(from_text);
794 g_free(subject_text);
795 (void)first;
797 /* If we don't keep track of this, will leak "data" for each of the notifications except the last */
798 data2 = pidgin_notify_add_mail(mail_dialog->treemodel, account, notification, urls ? *urls : NULL, 0, FALSE, &new_data);
799 if (data2 && new_data) {
800 if (data)
801 data->purple_has_handle = FALSE;
802 data = data2;
804 g_free(notification);
806 if (urls != NULL)
807 urls++;
809 } else {
810 if (count > 0) {
811 notification = g_strdup_printf(ngettext("%s has %d new message.",
812 "%s has %d new messages.",
813 (int)count),
814 *tos, (int)count);
815 data2 = pidgin_notify_add_mail(mail_dialog->treemodel, account, notification, urls ? *urls : NULL, count, FALSE, &new_data);
816 if (data2 && new_data) {
817 if (data)
818 data->purple_has_handle = FALSE;
819 data = data2;
821 g_free(notification);
822 } else {
823 /* Clear out all mails for the account */
824 pidgin_notify_add_mail(mail_dialog->treemodel, account, NULL, NULL, 0, TRUE, NULL);
826 if (mail_dialog->total_count == 0) {
828 * There is no API to clear the headline specifically
829 * This will trigger reset_mail_dialog()
831 pidgin_blist_set_headline(NULL, NULL, NULL, NULL, NULL);
832 return NULL;
837 /* Select first item if nothing selected */
838 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(mail_dialog->treeview));
839 if ((gtk_tree_selection_count_selected_rows(sel) < 1)
840 && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(mail_dialog->treemodel), &iter)) {
841 gtk_tree_selection_select_iter(sel, &iter);
844 if (!gtk_widget_get_visible(mail_dialog->dialog)) {
845 GdkPixbuf *pixbuf = gtk_widget_render_icon(mail_dialog->dialog, PIDGIN_STOCK_DIALOG_MAIL,
846 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL), NULL);
847 char *label_text = g_strdup_printf(ngettext("<b>%d new email.</b>",
848 "<b>%d new emails.</b>",
849 mail_dialog->total_count), mail_dialog->total_count);
850 mail_dialog->in_use = TRUE; /* So that _set_headline doesn't accidentally
851 remove the notifications when replacing an
852 old notification. */
853 pidgin_blist_set_headline(label_text,
854 pixbuf, G_CALLBACK(pidgin_notify_emails_present), mail_dialog->dialog,
855 (GDestroyNotify)reset_mail_dialog);
856 mail_dialog->in_use = FALSE;
857 g_free(label_text);
858 if (pixbuf)
859 g_object_unref(pixbuf);
860 } else if (!gtk_widget_has_focus(mail_dialog->dialog))
861 pidgin_set_urgent(GTK_WINDOW(mail_dialog->dialog), TRUE);
863 return data;
866 static gboolean
867 formatted_input_cb(GtkWidget *win, GdkEventKey *event, gpointer data)
869 if (event->keyval == GDK_KEY_Escape)
871 purple_notify_close(PURPLE_NOTIFY_FORMATTED, win);
873 return TRUE;
876 return FALSE;
879 static void *
880 pidgin_notify_formatted(const char *title, const char *primary,
881 const char *secondary, const char *text)
883 GtkWidget *window;
884 GtkWidget *vbox;
885 GtkWidget *label;
886 GtkWidget *button;
887 GtkWidget *web_view;
888 GtkWidget *frame;
889 char label_text[2048];
890 char *linked_text, *primary_esc, *secondary_esc;
892 window = gtk_dialog_new();
893 gtk_window_set_title(GTK_WINDOW(window), title);
894 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
896 g_signal_connect(G_OBJECT(window), "delete_event",
897 G_CALLBACK(formatted_close_cb), NULL);
899 /* Setup the main vbox */
900 vbox = gtk_dialog_get_content_area(GTK_DIALOG(window));
902 /* Setup the descriptive label */
903 primary_esc = g_markup_escape_text(primary, -1);
904 secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
905 g_snprintf(label_text, sizeof(label_text),
906 "<span weight=\"bold\" size=\"larger\">%s</span>%s%s",
907 primary_esc,
908 (secondary ? "\n" : ""),
909 (secondary ? secondary_esc : ""));
910 g_free(primary_esc);
911 g_free(secondary_esc);
913 label = gtk_label_new(NULL);
915 gtk_label_set_markup(GTK_LABEL(label), label_text);
916 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
917 gtk_label_set_selectable(GTK_LABEL(label), TRUE);
918 gtk_label_set_xalign(GTK_LABEL(label), 0);
919 gtk_label_set_yalign(GTK_LABEL(label), 0);
920 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
921 gtk_widget_show(label);
923 /* Add the webview */
924 frame = pidgin_create_webview(FALSE, &web_view, NULL);
925 gtk_widget_set_name(web_view, "pidgin_notify_webview");
926 gtk_widget_set_size_request(web_view, 300, 250);
927 gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
928 gtk_widget_show(frame);
930 /* Add the Close button. */
931 button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
932 gtk_widget_grab_focus(button);
934 g_signal_connect_swapped(G_OBJECT(button), "clicked",
935 G_CALLBACK(formatted_close_cb), window);
936 g_signal_connect(G_OBJECT(window), "key_press_event",
937 G_CALLBACK(formatted_input_cb), NULL);
939 /* Make sure URLs are clickable */
940 linked_text = purple_markup_linkify(text);
941 pidgin_webview_load_html_string(PIDGIN_WEBVIEW(web_view), linked_text);
942 g_free(linked_text);
944 g_object_set_data(G_OBJECT(window), "webview-widget", web_view);
946 /* Show the window */
947 pidgin_auto_parent_window(window);
949 gtk_widget_show(window);
951 return window;
954 static void
955 pidgin_notify_searchresults_new_rows(PurpleConnection *gc, PurpleNotifySearchResults *results,
956 void *data_)
958 PidginNotifySearchResultsData *data = data_;
959 GtkListStore *model = data->model;
960 GtkTreeIter iter;
961 GdkPixbuf *pixbuf;
962 GList *row, *column;
963 guint n;
965 gtk_list_store_clear(data->model);
967 pixbuf = pidgin_create_protocol_icon(purple_connection_get_account(gc), PIDGIN_PROTOCOL_ICON_SMALL);
969 for (row = results->rows; row != NULL; row = row->next) {
971 gtk_list_store_append(model, &iter);
972 gtk_list_store_set(model, &iter, 0, pixbuf, -1);
974 n = 1;
975 for (column = row->data; column != NULL; column = column->next) {
976 GValue v;
978 v.g_type = 0;
979 g_value_init(&v, G_TYPE_STRING);
980 g_value_set_string(&v, column->data);
981 gtk_list_store_set_value(model, &iter, n, &v);
982 n++;
986 if (pixbuf != NULL)
987 g_object_unref(pixbuf);
990 static void *
991 pidgin_notify_searchresults(PurpleConnection *gc, const char *title,
992 const char *primary, const char *secondary,
993 PurpleNotifySearchResults *results, gpointer user_data)
995 GtkWidget *window;
996 GtkWidget *treeview;
997 GtkWidget *close_button;
998 GType *col_types;
999 GtkListStore *model;
1000 GtkCellRenderer *renderer;
1001 guint col_num;
1002 GList *columniter;
1003 guint i;
1004 GList *l;
1006 GtkWidget *vbox;
1007 GtkWidget *label;
1008 PidginNotifySearchResultsData *data;
1009 char *label_text;
1010 char *primary_esc, *secondary_esc;
1012 g_return_val_if_fail(gc != NULL, NULL);
1013 g_return_val_if_fail(results != NULL, NULL);
1015 data = g_malloc(sizeof(PidginNotifySearchResultsData));
1016 data->user_data = user_data;
1017 data->results = results;
1019 /* Create the window */
1020 window = gtk_dialog_new();
1021 gtk_window_set_title(GTK_WINDOW(window), title ? title :_("Search Results"));
1022 gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER);
1023 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
1025 g_signal_connect_swapped(G_OBJECT(window), "delete_event",
1026 G_CALLBACK(searchresults_close_cb), data);
1028 /* Setup the main vbox */
1029 vbox = gtk_dialog_get_content_area(GTK_DIALOG(window));
1031 /* Setup the descriptive label */
1032 primary_esc = (primary != NULL) ? g_markup_escape_text(primary, -1) : NULL;
1033 secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
1034 label_text = g_strdup_printf(
1035 "<span weight=\"bold\" size=\"larger\">%s</span>%s%s",
1036 (primary ? primary_esc : ""),
1037 (primary && secondary ? "\n" : ""),
1038 (secondary ? secondary_esc : ""));
1039 g_free(primary_esc);
1040 g_free(secondary_esc);
1041 label = gtk_label_new(NULL);
1042 gtk_label_set_markup(GTK_LABEL(label), label_text);
1043 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1044 gtk_label_set_xalign(GTK_LABEL(label), 0);
1045 gtk_label_set_yalign(GTK_LABEL(label), 0);
1046 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1047 gtk_widget_show(label);
1048 g_free(label_text);
1050 /* +1 is for the automagically created Status column. */
1051 col_num = g_list_length(results->columns) + 1;
1053 /* Setup the list model */
1054 col_types = g_new0(GType, col_num);
1056 /* There always is this first column. */
1057 col_types[0] = GDK_TYPE_PIXBUF;
1058 for (i = 1; i < col_num; i++) {
1059 col_types[i] = G_TYPE_STRING;
1061 model = gtk_list_store_newv(col_num, col_types);
1062 g_free(col_types);
1064 /* Setup the treeview */
1065 treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1066 g_object_unref(G_OBJECT(model));
1067 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
1068 gtk_widget_set_size_request(treeview, 500, 400);
1069 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)),
1070 GTK_SELECTION_SINGLE);
1071 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), TRUE);
1072 gtk_box_pack_start(GTK_BOX(vbox),
1073 pidgin_make_scrollable(treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1),
1074 TRUE, TRUE, 0);
1075 gtk_widget_show(treeview);
1077 renderer = gtk_cell_renderer_pixbuf_new();
1078 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview),
1079 -1, "", renderer, "pixbuf", 0, NULL);
1081 i = 1;
1082 for (columniter = results->columns; columniter != NULL; columniter = columniter->next) {
1083 PurpleNotifySearchColumn *column = columniter->data;
1084 renderer = gtk_cell_renderer_text_new();
1086 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview), -1,
1087 purple_notify_searchresult_column_get_title(column), renderer, "text", i, NULL);
1089 if (!purple_notify_searchresult_column_is_visible(column))
1090 gtk_tree_view_column_set_visible(gtk_tree_view_get_column(GTK_TREE_VIEW(treeview), i), FALSE);
1092 i++;
1095 for (l = results->buttons; l; l = l->next) {
1096 PurpleNotifySearchButton *b = l->data;
1097 GtkWidget *button = NULL;
1098 switch (b->type) {
1099 case PURPLE_NOTIFY_BUTTON_LABELED:
1100 if(b->label) {
1101 button = gtk_dialog_add_button(GTK_DIALOG(window), b->label, GTK_RESPONSE_NONE);
1102 } else {
1103 purple_debug_warning("gtknotify", "Missing button label\n");
1105 break;
1106 case PURPLE_NOTIFY_BUTTON_CONTINUE:
1107 button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_GO_FORWARD, GTK_RESPONSE_NONE);
1108 break;
1109 case PURPLE_NOTIFY_BUTTON_ADD:
1110 button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_ADD, GTK_RESPONSE_NONE);
1111 break;
1112 case PURPLE_NOTIFY_BUTTON_INFO:
1113 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_TOOLBAR_USER_INFO, GTK_RESPONSE_NONE);
1114 break;
1115 case PURPLE_NOTIFY_BUTTON_IM:
1116 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW, GTK_RESPONSE_NONE);
1117 break;
1118 case PURPLE_NOTIFY_BUTTON_JOIN:
1119 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_CHAT, GTK_RESPONSE_NONE);
1120 break;
1121 case PURPLE_NOTIFY_BUTTON_INVITE:
1122 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_INVITE, GTK_RESPONSE_NONE);
1123 break;
1124 default:
1125 purple_debug_warning("gtknotify", "Incorrect button type: %d\n", b->type);
1127 if (button != NULL) {
1128 PidginNotifySearchResultsButtonData *bd;
1130 bd = g_new0(PidginNotifySearchResultsButtonData, 1);
1131 bd->button = b;
1132 bd->data = data;
1134 g_signal_connect(G_OBJECT(button), "clicked",
1135 G_CALLBACK(searchresults_callback_wrapper_cb), bd);
1136 g_signal_connect_swapped(G_OBJECT(button), "destroy", G_CALLBACK(g_free), bd);
1140 /* Add the Close button */
1141 close_button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
1143 g_signal_connect_swapped(G_OBJECT(close_button), "clicked",
1144 G_CALLBACK(searchresults_close_cb), data);
1146 data->account = purple_connection_get_account(gc);
1147 data->model = model;
1148 data->treeview = treeview;
1149 data->window = window;
1151 /* Insert rows. */
1152 pidgin_notify_searchresults_new_rows(gc, results, data);
1154 /* Show the window */
1155 pidgin_auto_parent_window(window);
1157 gtk_widget_show(window);
1158 return data;
1161 /** Xerox'ed from Finch! How the tables have turned!! ;) **/
1162 /** User information. **/
1163 static GHashTable *userinfo;
1165 static char *
1166 userinfo_hash(PurpleAccount *account, const char *who)
1168 char key[256];
1169 snprintf(key, sizeof(key), "%s - %s", purple_account_get_username(account), purple_normalize(account, who));
1170 return g_utf8_strup(key, -1);
1173 static void
1174 remove_userinfo(GtkWidget *widget, gpointer key)
1176 PidginUserInfo *pinfo = g_hash_table_lookup(userinfo, key);
1178 while (pinfo->count--)
1179 purple_notify_close(PURPLE_NOTIFY_USERINFO, widget);
1181 g_hash_table_remove(userinfo, key);
1184 static void *
1185 pidgin_notify_userinfo(PurpleConnection *gc, const char *who,
1186 PurpleNotifyUserInfo *user_info)
1188 char *info;
1189 void *ui_handle;
1190 char *key = userinfo_hash(purple_connection_get_account(gc), who);
1191 PidginUserInfo *pinfo = NULL;
1193 if (!userinfo) {
1194 userinfo = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
1197 info = purple_notify_user_info_get_text_with_newline(user_info, "<br />");
1198 pinfo = g_hash_table_lookup(userinfo, key);
1199 if (pinfo != NULL) {
1200 GtkWidget *webview = g_object_get_data(G_OBJECT(pinfo->window), "webview-widget");
1201 char *linked_text = purple_markup_linkify(info);
1202 pidgin_webview_load_html_string(PIDGIN_WEBVIEW(webview), linked_text);
1203 g_free(linked_text);
1204 g_free(key);
1205 ui_handle = pinfo->window;
1206 pinfo->count++;
1207 } else {
1208 char *primary = g_strdup_printf(_("Info for %s"), who);
1209 ui_handle = pidgin_notify_formatted(_("Buddy Information"), primary, NULL, info);
1210 g_signal_handlers_disconnect_by_func(G_OBJECT(ui_handle), G_CALLBACK(formatted_close_cb), NULL);
1211 g_signal_connect(G_OBJECT(ui_handle), "destroy", G_CALLBACK(remove_userinfo), key);
1212 g_free(primary);
1213 pinfo = g_new0(PidginUserInfo, 1);
1214 pinfo->window = ui_handle;
1215 pinfo->count = 1;
1216 g_hash_table_insert(userinfo, key, pinfo);
1218 g_free(info);
1219 return ui_handle;
1222 static void
1223 pidgin_close_notify(PurpleNotifyType type, void *ui_handle)
1225 if (type == PURPLE_NOTIFY_EMAIL || type == PURPLE_NOTIFY_EMAILS)
1227 PidginNotifyMailData *data = (PidginNotifyMailData *)ui_handle;
1229 if (data) {
1230 g_free(data->url);
1231 g_free(data);
1234 else if (type == PURPLE_NOTIFY_SEARCHRESULTS)
1236 PidginNotifySearchResultsData *data = (PidginNotifySearchResultsData *)ui_handle;
1238 gtk_widget_destroy(data->window);
1239 purple_notify_searchresults_free(data->results);
1241 g_free(data);
1243 else if (ui_handle != NULL)
1244 gtk_widget_destroy(GTK_WIDGET(ui_handle));
1247 #ifndef _WIN32
1248 static gboolean
1249 uri_command(GSList *arg_list, gboolean sync)
1251 gchar *tmp;
1252 GError *error = NULL;
1253 GSList *it;
1254 gchar **argv;
1255 gint argc, i;
1256 gchar *program;
1258 g_return_val_if_fail(arg_list != NULL, FALSE);
1260 program = arg_list->data;
1261 purple_debug_misc("gtknotify", "Executing %s (%s)\n", program,
1262 sync ? "sync" : "async");
1264 if (!purple_program_is_valid(program)) {
1265 purple_debug_error("gtknotify", "Command \"%s\" is invalid\n",
1266 program);
1267 tmp = g_strdup_printf(
1268 _("The browser command \"%s\" is invalid."),
1269 program ? program : "(null)");
1270 purple_notify_error(NULL, NULL, _("Unable to open URL"), tmp, NULL);
1271 g_free(tmp);
1273 return FALSE;
1276 argc = g_slist_length(arg_list);
1277 argv = g_new(gchar*, argc + 1);
1278 i = 0;
1279 for (it = arg_list; it; it = g_slist_next(it)) {
1280 if (purple_debug_is_verbose()) {
1281 purple_debug_misc("gtknotify", "argv[%d] = \"%s\"\n",
1282 i, (gchar*)it->data);
1284 argv[i++] = it->data;
1286 argv[i] = NULL;
1288 if (sync) {
1289 gint exit_status = 0;
1291 if (g_spawn_sync(NULL, argv, NULL, G_SPAWN_SEARCH_PATH |
1292 G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
1293 NULL, NULL, NULL, NULL, &exit_status, &error) &&
1294 exit_status == 0)
1296 g_free(argv);
1297 return TRUE;
1300 purple_debug_error("gtknotify",
1301 "Error launching \"%s\": %s (status: %d)\n", program,
1302 error ? error->message : "(null)", exit_status);
1303 tmp = g_strdup_printf(_("Error launching \"%s\": %s"), program,
1304 error ? error->message : "(null)");
1305 g_error_free(error);
1306 purple_notify_error(NULL, NULL, _("Unable to open URL"), tmp, NULL);
1307 g_free(tmp);
1309 g_free(argv);
1310 return FALSE;
1313 if (g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH |
1314 G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, NULL,
1315 NULL, NULL, &error))
1317 g_free(argv);
1318 return TRUE;
1321 purple_debug_warning("gtknotify", "Error launching \"%s\": %s\n",
1322 program, error ? error->message : "(null)");
1323 g_error_free(error);
1325 g_free(argv);
1326 return FALSE;
1328 #endif /* _WIN32 */
1330 static void *
1331 pidgin_notify_uri(const char *uri)
1333 #ifndef _WIN32
1334 const char *web_browser;
1335 int place;
1336 gchar *uri_escaped, *uri_custom = NULL;
1337 GSList *argv = NULL, *argv_remote = NULL;
1338 gchar **usercmd_argv = NULL;
1340 /* Replace some special characters like $ with their percent-encoded
1341 value. This shouldn't be necessary because we shell-escape the entire
1342 arg before exec'ing the browser, however, we had a report that a URL
1343 containing $(xterm) was causing xterm to start on his system. This is
1344 obviously a bug on his system, but it's pretty easy for us to protect
1345 against it. */
1346 uri_escaped = g_uri_escape_string(uri, ":;/%#,+?=&@", FALSE);
1348 web_browser = purple_prefs_get_string(PIDGIN_PREFS_ROOT
1349 "/browsers/browser");
1350 place = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/browsers/place");
1352 /* if they are running gnome, use the gnome web browser */
1353 if (purple_running_gnome() == TRUE) {
1354 char *tmp = g_find_program_in_path("xdg-open");
1355 if (tmp == NULL)
1356 argv = g_slist_append(argv, "gnome-open");
1357 else
1358 argv = g_slist_append(argv, "xdg-open");
1359 g_free(tmp);
1360 argv = g_slist_append(argv, uri_escaped);
1361 } else if (purple_running_osx() == TRUE) {
1362 argv = g_slist_append(argv, "open");
1363 argv = g_slist_append(argv, uri_escaped);
1364 } else if (!strcmp(web_browser, "epiphany") ||
1365 !strcmp(web_browser, "galeon"))
1367 argv = g_slist_append(argv, (gpointer)web_browser);
1369 if (place == PIDGIN_BROWSER_NEW_WINDOW)
1370 argv = g_slist_append(argv, "-w");
1371 else if (place == PIDGIN_BROWSER_NEW_TAB)
1372 argv = g_slist_append(argv, "-n");
1374 argv = g_slist_append(argv, uri_escaped);
1375 } else if (!strcmp(web_browser, "xdg-open")) {
1376 argv = g_slist_append(argv, "xdg-open");
1377 argv = g_slist_append(argv, uri_escaped);
1378 } else if (!strcmp(web_browser, "gnome-open")) {
1379 argv = g_slist_append(argv, "gnome-open");
1380 argv = g_slist_append(argv, uri_escaped);
1381 } else if (!strcmp(web_browser, "kfmclient")) {
1382 argv = g_slist_append(argv, "kfmclient");
1383 argv = g_slist_append(argv, "openURL");
1384 argv = g_slist_append(argv, uri_escaped);
1386 * Does Konqueror have options to open in new tab
1387 * and/or current window?
1389 } else if (!strcmp(web_browser, "mozilla") ||
1390 !strcmp(web_browser, "mozilla-firebird") ||
1391 !strcmp(web_browser, "firefox") ||
1392 !strcmp(web_browser, "seamonkey"))
1394 argv = g_slist_append(argv, (gpointer)web_browser);
1395 argv = g_slist_append(argv, uri_escaped);
1397 g_assert(uri_custom == NULL);
1398 if (place == PIDGIN_BROWSER_NEW_WINDOW) {
1399 uri_custom = g_strdup_printf("openURL(%s,new-window)",
1400 uri_escaped);
1401 } else if (place == PIDGIN_BROWSER_NEW_TAB) {
1402 uri_custom = g_strdup_printf("openURL(%s,new-tab)",
1403 uri_escaped);
1406 if (uri_custom != NULL) {
1407 argv_remote = g_slist_append(argv_remote,
1408 (gpointer)web_browser);
1410 /* Firefox 0.9 and higher require a "-a firefox" option
1411 * when using -remote commands. This breaks older
1412 * versions of mozilla. So we include this other handly
1413 * little string when calling firefox. If the API for
1414 * remote calls changes any more in firefox then firefox
1415 * should probably be split apart from mozilla-firebird
1416 * and mozilla... but this is good for now.
1418 if (!strcmp(web_browser, "firefox")) {
1419 argv_remote = g_slist_append(argv_remote, "-a");
1420 argv_remote = g_slist_append(argv_remote,
1421 "firefox");
1424 argv_remote = g_slist_append(argv_remote, "-remote");
1425 argv_remote = g_slist_append(argv_remote, uri_custom);
1427 } else if (!strcmp(web_browser, "opera")) {
1428 argv = g_slist_append(argv, "opera");
1430 if (place == PIDGIN_BROWSER_NEW_WINDOW)
1431 argv = g_slist_append(argv, "-newwindow");
1432 else if (place == PIDGIN_BROWSER_NEW_TAB)
1433 argv = g_slist_append(argv, "-newpage");
1434 /* `opera -remote "openURL(%s)"` command causes Pidgin to hang,
1435 * if there is no Opera instance running.
1438 argv = g_slist_append(argv, uri_escaped);
1439 } else if (!strcmp(web_browser, "google-chrome")) {
1440 /* Google Chrome doesn't have command-line arguments that
1441 * control the opening of links from external calls. This is
1442 * controlled solely from a preference within Google Chrome.
1444 argv = g_slist_append(argv, "google-chrome");
1445 argv = g_slist_append(argv, uri_escaped);
1446 } else if (!strcmp(web_browser, "chrome")) {
1447 /* Chromium doesn't have command-line arguments that control
1448 * the opening of links from external calls. This is controlled
1449 * solely from a preference within Chromium.
1451 argv = g_slist_append(argv, "chrome");
1452 argv = g_slist_append(argv, uri_escaped);
1453 } else if (!strcmp(web_browser, "chromium-browser")) {
1454 /* Chromium doesn't have command-line arguments that control the
1455 * opening of links from external calls. This is controlled
1456 * solely from a preference within Chromium.
1458 argv = g_slist_append(argv, "chromium-browser");
1459 argv = g_slist_append(argv, uri_escaped);
1460 } else if (!strcmp(web_browser, "custom")) {
1461 GError *error = NULL;
1462 const char *usercmd_command;
1463 gint usercmd_argc, i;
1464 gboolean uri_added = FALSE;
1466 usercmd_command = purple_prefs_get_string(PIDGIN_PREFS_ROOT
1467 "/browsers/manual_command");
1469 if (usercmd_command == NULL || *usercmd_command == '\0') {
1470 purple_notify_error(NULL, NULL, _("Unable to open URL"),
1471 _("The 'Manual' browser command has been "
1472 "chosen, but no command has been set."), NULL);
1473 g_free(uri_escaped);
1474 return NULL;
1477 if (!g_shell_parse_argv(usercmd_command, &usercmd_argc,
1478 &usercmd_argv, &error))
1480 purple_notify_error(NULL, NULL, _("Unable to open URL: "
1481 "the 'Manual' browser command seems invalid."),
1482 error ? error->message : NULL, NULL);
1483 g_error_free(error);
1484 g_free(uri_escaped);
1485 return NULL;
1488 for (i = 0; i < usercmd_argc; i++) {
1489 gchar *cmd_part = usercmd_argv[i];
1491 if (uri_added || strstr(cmd_part, "%s") == NULL) {
1492 argv = g_slist_append(argv, cmd_part);
1493 continue;
1496 uri_custom = purple_strreplace(cmd_part, "%s",
1497 uri_escaped);
1498 argv = g_slist_append(argv, uri_custom);
1499 uri_added = TRUE;
1502 /* There is no "%s" in the browser command. Assume the user
1503 * wanted the URL tacked on to the end of the command.
1505 if (!uri_added)
1506 argv = g_slist_append(argv, uri_escaped);
1509 if (argv_remote != NULL) {
1510 /* try the remote command first */
1511 if (!uri_command(argv_remote, TRUE))
1512 uri_command(argv, FALSE);
1513 } else
1514 uri_command(argv, FALSE);
1516 g_strfreev(usercmd_argv);
1517 g_free(uri_escaped);
1518 g_free(uri_custom);
1519 g_slist_free(argv);
1520 g_slist_free(argv_remote);
1522 #else /* !_WIN32 */
1523 winpidgin_notify_uri(uri);
1524 #endif /* !_WIN32 */
1526 return NULL;
1529 void
1530 pidgin_notify_pounce_add(PurpleAccount *account, PurplePounce *pounce,
1531 const char *alias, const char *event, const char *message, const char *date)
1533 GdkPixbuf *icon;
1534 GtkTreeIter iter;
1535 PidginNotifyPounceData *pounce_data;
1536 gboolean first = (pounce_dialog == NULL);
1538 if (pounce_dialog == NULL)
1539 pounce_dialog = pidgin_create_notification_dialog(PIDGIN_NOTIFY_POUNCE);
1541 icon = pidgin_create_protocol_icon(account, PIDGIN_PROTOCOL_ICON_SMALL);
1543 pounce_data = g_new(PidginNotifyPounceData, 1);
1545 pounce_data->account = account;
1546 pounce_data->pounce = pounce;
1547 pounce_data->pouncee = g_strdup(purple_pounce_get_pouncee(pounce));
1549 gtk_tree_store_append(pounce_dialog->treemodel, &iter, NULL);
1551 gtk_tree_store_set(pounce_dialog->treemodel, &iter,
1552 PIDGIN_POUNCE_ICON, icon,
1553 PIDGIN_POUNCE_ALIAS, alias,
1554 PIDGIN_POUNCE_EVENT, event,
1555 PIDGIN_POUNCE_TEXT, (message != NULL)? message : _("No message"),
1556 PIDGIN_POUNCE_DATE, date,
1557 PIDGIN_POUNCE_DATA, pounce_data,
1558 -1);
1560 if (first) {
1561 GtkTreeSelection *selection =
1562 gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
1563 gtk_tree_selection_select_iter(selection, &iter);
1566 if (icon)
1567 g_object_unref(icon);
1569 gtk_widget_show_all(pounce_dialog->dialog);
1571 return;
1574 static PidginNotifyDialog *
1575 pidgin_create_notification_dialog(PidginNotifyType type)
1577 GtkTreeStore *model = NULL;
1578 GtkWidget *dialog = NULL;
1579 GtkWidget *label = NULL;
1580 GtkCellRenderer *rend;
1581 GtkTreeViewColumn *column;
1582 GtkWidget *button = NULL;
1583 GtkWidget *vbox = NULL;
1584 GtkTreeSelection *sel;
1585 PidginNotifyDialog *spec_dialog = NULL;
1587 g_return_val_if_fail(type < PIDGIN_NOTIFY_TYPES, NULL);
1589 if (type == PIDGIN_NOTIFY_MAIL) {
1590 g_return_val_if_fail(mail_dialog == NULL, mail_dialog);
1592 model = gtk_tree_store_new(COLUMNS_PIDGIN_MAIL,
1593 GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_POINTER);
1595 } else if (type == PIDGIN_NOTIFY_POUNCE) {
1596 g_return_val_if_fail(pounce_dialog == NULL, pounce_dialog);
1598 model = gtk_tree_store_new(COLUMNS_PIDGIN_POUNCE,
1599 GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
1600 G_TYPE_STRING, G_TYPE_POINTER);
1603 dialog = gtk_dialog_new();
1605 /* Vertical box */
1606 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1608 /* Setup the dialog */
1609 gtk_container_set_border_width(GTK_CONTAINER(dialog), PIDGIN_HIG_BOX_SPACE);
1610 gtk_container_set_border_width(GTK_CONTAINER(vbox), PIDGIN_HIG_BOX_SPACE);
1611 gtk_box_set_spacing(GTK_BOX(vbox), PIDGIN_HIG_BORDER);
1613 /* Golden ratio it up! */
1614 gtk_widget_set_size_request(dialog, 550, 400);
1616 spec_dialog = g_new0(PidginNotifyDialog, 1);
1617 spec_dialog->dialog = dialog;
1619 spec_dialog->treemodel = model;
1620 spec_dialog->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1621 g_object_unref(G_OBJECT(model));
1623 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(spec_dialog->treeview), TRUE);
1625 if (type == PIDGIN_NOTIFY_MAIL) {
1626 gtk_window_set_title(GTK_WINDOW(dialog), _("New Mail"));
1627 gtk_window_set_role(GTK_WINDOW(dialog), "new_mail_detailed");
1628 g_signal_connect(G_OBJECT(dialog), "focus-in-event",
1629 G_CALLBACK(mail_window_focus_cb), NULL);
1631 gtk_dialog_add_button(GTK_DIALOG(dialog),
1632 _("Open All Messages"), GTK_RESPONSE_ACCEPT);
1634 button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1635 PIDGIN_STOCK_OPEN_MAIL, GTK_RESPONSE_YES);
1636 spec_dialog->open_button = button;
1638 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(spec_dialog->treeview), FALSE);
1640 gtk_tree_view_set_search_column(GTK_TREE_VIEW(spec_dialog->treeview), PIDGIN_MAIL_TEXT);
1641 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(spec_dialog->treeview),
1642 pidgin_tree_view_search_equal_func, NULL, NULL);
1643 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(spec_dialog->treeview));
1644 gtk_tree_selection_set_mode(sel, GTK_SELECTION_BROWSE);
1646 g_signal_connect(G_OBJECT(dialog), "response",
1647 G_CALLBACK(email_response_cb), spec_dialog);
1648 g_signal_connect(G_OBJECT(sel), "changed",
1649 G_CALLBACK(selection_changed_cb), spec_dialog);
1650 g_signal_connect(G_OBJECT(spec_dialog->treeview), "row-activated", G_CALLBACK(email_row_activated_cb), NULL);
1652 column = gtk_tree_view_column_new();
1653 gtk_tree_view_column_set_resizable(column, TRUE);
1654 rend = gtk_cell_renderer_pixbuf_new();
1655 gtk_tree_view_column_pack_start(column, rend, FALSE);
1657 gtk_tree_view_column_set_attributes(column, rend, "pixbuf", PIDGIN_MAIL_ICON, NULL);
1658 rend = gtk_cell_renderer_text_new();
1659 gtk_tree_view_column_pack_start(column, rend, TRUE);
1660 gtk_tree_view_column_set_attributes(column, rend, "markup", PIDGIN_MAIL_TEXT, NULL);
1661 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1663 label = gtk_label_new(NULL);
1664 gtk_label_set_markup(GTK_LABEL(label), _("<span weight=\"bold\" size=\"larger\">You have mail!</span>"));
1666 } else if (type == PIDGIN_NOTIFY_POUNCE) {
1667 gtk_window_set_title(GTK_WINDOW(dialog), _("New Pounces"));
1669 button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1670 _("IM"), GTK_RESPONSE_YES);
1671 gtk_widget_set_sensitive(button, FALSE);
1672 spec_dialog->open_button = button;
1674 button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1675 PIDGIN_STOCK_MODIFY, GTK_RESPONSE_APPLY);
1676 gtk_widget_set_sensitive(button, FALSE);
1677 spec_dialog->edit_button = button;
1679 /* Translators: Make sure you translate "Dismiss" differently than
1680 "close"! This string is used in the "You have pounced" dialog
1681 that appears when one of your Buddy Pounces is triggered. In
1682 this context "Dismiss" means "I acknowledge that I've seen that
1683 this pounce was triggered--remove it from this list." Translating
1684 it as "Remove" is acceptable if you can't think of a more precise
1685 word. */
1686 button = gtk_dialog_add_button(GTK_DIALOG(dialog), _("Dismiss"),
1687 GTK_RESPONSE_NO);
1688 gtk_widget_set_sensitive(button, FALSE);
1689 spec_dialog->dismiss_button = button;
1691 g_signal_connect(G_OBJECT(dialog), "response",
1692 G_CALLBACK(pounce_response_cb), spec_dialog);
1694 column = gtk_tree_view_column_new();
1695 gtk_tree_view_column_set_title(column, _("Buddy"));
1696 gtk_tree_view_column_set_resizable(column, TRUE);
1697 rend = gtk_cell_renderer_pixbuf_new();
1698 gtk_tree_view_column_pack_start(column, rend, FALSE);
1700 gtk_tree_view_column_set_attributes(column, rend, "pixbuf", PIDGIN_POUNCE_ICON, NULL);
1701 rend = gtk_cell_renderer_text_new();
1702 gtk_tree_view_column_pack_start(column, rend, FALSE);
1703 gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_ALIAS);
1704 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1706 column = gtk_tree_view_column_new();
1707 gtk_tree_view_column_set_title(column, _("Event"));
1708 gtk_tree_view_column_set_resizable(column, TRUE);
1709 rend = gtk_cell_renderer_text_new();
1710 gtk_tree_view_column_pack_start(column, rend, FALSE);
1711 gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_EVENT);
1712 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1714 column = gtk_tree_view_column_new();
1715 gtk_tree_view_column_set_title(column, _("Message"));
1716 gtk_tree_view_column_set_resizable(column, TRUE);
1717 rend = gtk_cell_renderer_text_new();
1718 gtk_tree_view_column_pack_start(column, rend, FALSE);
1719 gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_TEXT);
1720 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1722 column = gtk_tree_view_column_new();
1723 gtk_tree_view_column_set_title(column, _("Date"));
1724 gtk_tree_view_column_set_resizable(column, TRUE);
1725 rend = gtk_cell_renderer_text_new();
1726 gtk_tree_view_column_pack_start(column, rend, FALSE);
1727 gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_DATE);
1728 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1730 label = gtk_label_new(NULL);
1731 gtk_label_set_markup(GTK_LABEL(label), _("<span weight=\"bold\" size=\"larger\">You have pounced!</span>"));
1733 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(spec_dialog->treeview));
1734 gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
1735 g_signal_connect(G_OBJECT(sel), "changed",
1736 G_CALLBACK(pounce_row_selected_cb), NULL);
1737 g_signal_connect(G_OBJECT(spec_dialog->treeview), "row-activated",
1738 G_CALLBACK(pounce_response_open_ims), NULL);
1741 gtk_dialog_add_button(GTK_DIALOG(dialog),
1742 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
1744 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1745 gtk_label_set_xalign(GTK_LABEL(label), 0);
1746 gtk_label_set_yalign(GTK_LABEL(label), 0);
1747 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1748 gtk_box_pack_start(GTK_BOX(vbox),
1749 pidgin_make_scrollable(spec_dialog->treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1),
1750 TRUE, TRUE, 2);
1752 return spec_dialog;
1755 static void
1756 signed_off_cb(PurpleConnection *gc, gpointer unused)
1758 /* Clear any pending emails for this account */
1759 pidgin_notify_emails(gc, 0, FALSE, NULL, NULL, NULL, NULL);
1761 if (mail_dialog != NULL && mail_dialog->total_count == 0)
1762 reset_mail_dialog(NULL);
1765 static void*
1766 pidgin_notify_get_handle(void)
1768 static int handle;
1769 return &handle;
1772 void pidgin_notify_init(void)
1774 void *handle = pidgin_notify_get_handle();
1776 purple_signal_connect(purple_connections_get_handle(), "signed-off",
1777 handle, PURPLE_CALLBACK(signed_off_cb), NULL);
1780 void pidgin_notify_uninit(void)
1782 purple_signals_disconnect_by_handle(pidgin_notify_get_handle());
1785 static PurpleNotifyUiOps ops =
1787 pidgin_notify_message,
1788 pidgin_notify_email,
1789 pidgin_notify_emails,
1790 pidgin_notify_formatted,
1791 pidgin_notify_searchresults,
1792 pidgin_notify_searchresults_new_rows,
1793 pidgin_notify_userinfo,
1794 pidgin_notify_uri,
1795 pidgin_close_notify,
1796 NULL,
1797 NULL,
1798 NULL,
1799 NULL
1802 PurpleNotifyUiOps *
1803 pidgin_notify_get_ui_ops(void)
1805 return &ops;