Merged in default (pull request #594)
[pidgin-git.git] / pidgin / gtknotify.c
blobbb36f9cc8f1ec5e95338d37a8f21ecb354664fb3
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 <gdk/gdkkeysyms.h>
22 #include <talkatu.h>
24 #include "internal.h"
25 #include "pidgin.h"
28 #include "account.h"
29 #include "connection.h"
30 #include "debug.h"
31 #include "prefs.h"
32 #include "pidginstock.h"
33 #include "util.h"
35 #include "gtk3compat.h"
36 #include "gtkblist.h"
37 #include "gtknotify.h"
38 #include "gtkpounce.h"
39 #include "gtkutils.h"
41 typedef struct
43 GtkWidget *window;
44 int count;
45 } PidginUserInfo;
47 typedef struct
49 PurpleAccount *account;
50 char *url;
51 GtkWidget *label;
52 int count;
53 gboolean purple_has_handle;
54 } PidginNotifyMailData;
56 typedef struct
58 PurpleAccount *account;
59 PurplePounce *pounce;
60 char *pouncee;
61 } PidginNotifyPounceData;
64 typedef struct
66 PurpleAccount *account;
67 GtkListStore *model;
68 GtkWidget *treeview;
69 GtkWidget *window;
70 gpointer user_data;
71 PurpleNotifySearchResults *results;
73 } PidginNotifySearchResultsData;
75 typedef struct
77 PurpleNotifySearchButton *button;
78 PidginNotifySearchResultsData *data;
80 } PidginNotifySearchResultsButtonData;
82 enum
84 PIDGIN_MAIL_ICON,
85 PIDGIN_MAIL_TEXT,
86 PIDGIN_MAIL_DATA,
87 COLUMNS_PIDGIN_MAIL
90 enum
92 PIDGIN_POUNCE_ICON,
93 PIDGIN_POUNCE_ALIAS,
94 PIDGIN_POUNCE_EVENT,
95 PIDGIN_POUNCE_TEXT,
96 PIDGIN_POUNCE_DATE,
97 PIDGIN_POUNCE_DATA,
98 COLUMNS_PIDGIN_POUNCE
102 typedef struct
105 * This must be first so PidginNotifyDialog can masquerade as the
106 * dialog widget.
108 GtkWidget *dialog;
109 GtkWidget *treeview;
110 GtkTreeStore *treemodel;
111 GtkLabel *label;
112 GtkWidget *open_button;
113 GtkWidget *dismiss_button;
114 GtkWidget *edit_button;
115 int total_count;
116 gboolean in_use;
117 } PidginNotifyDialog;
119 typedef enum
121 PIDGIN_NOTIFY_MAIL,
122 PIDGIN_NOTIFY_POUNCE,
123 PIDGIN_NOTIFY_TYPES
124 } PidginNotifyType;
126 static PidginNotifyDialog *mail_dialog = NULL;
127 static PidginNotifyDialog *pounce_dialog = NULL;
129 static PidginNotifyDialog *pidgin_create_notification_dialog(PidginNotifyType type);
130 static void *pidgin_notify_emails(PurpleConnection *gc, size_t count, gboolean detailed,
131 const char **subjects,
132 const char **froms, const char **tos,
133 const char **urls);
135 static void pidgin_close_notify(PurpleNotifyType type, void *ui_handle);
137 static void
138 message_response_cb(GtkDialog *dialog, gint id, GtkWidget *widget)
140 purple_notify_close(PURPLE_NOTIFY_MESSAGE, widget);
143 static void
144 pounce_response_close(PidginNotifyDialog *dialog)
146 GtkTreeIter iter;
147 PidginNotifyPounceData *pounce_data;
149 while (gtk_tree_model_get_iter_first(
150 GTK_TREE_MODEL(pounce_dialog->treemodel), &iter)) {
151 gtk_tree_model_get(GTK_TREE_MODEL(pounce_dialog->treemodel), &iter,
152 PIDGIN_POUNCE_DATA, &pounce_data,
153 -1);
154 gtk_tree_store_remove(dialog->treemodel, &iter);
156 g_free(pounce_data->pouncee);
157 g_free(pounce_data);
160 gtk_widget_destroy(pounce_dialog->dialog);
161 g_free(pounce_dialog);
162 pounce_dialog = NULL;
165 static void
166 delete_foreach(GtkTreeModel *model, GtkTreePath *path,
167 GtkTreeIter *iter, gpointer data)
169 PidginNotifyPounceData *pounce_data;
171 gtk_tree_model_get(model, iter,
172 PIDGIN_POUNCE_DATA, &pounce_data,
173 -1);
175 if (pounce_data != NULL) {
176 g_free(pounce_data->pouncee);
177 g_free(pounce_data);
181 static void
182 open_im_foreach(GtkTreeModel *model, GtkTreePath *path,
183 GtkTreeIter *iter, gpointer data)
185 PidginNotifyPounceData *pounce_data;
187 gtk_tree_model_get(model, iter,
188 PIDGIN_POUNCE_DATA, &pounce_data,
189 -1);
191 if (pounce_data != NULL) {
192 PurpleIMConversation *im;
194 im = purple_im_conversation_new(pounce_data->account, pounce_data->pouncee);
195 purple_conversation_present(PURPLE_CONVERSATION(im));
199 static void
200 append_to_list(GtkTreeModel *model, GtkTreePath *path,
201 GtkTreeIter *iter, gpointer data)
203 GList **list = data;
204 *list = g_list_prepend(*list, gtk_tree_path_copy(path));
207 static void
208 pounce_response_dismiss()
210 GtkTreeModel *model = GTK_TREE_MODEL(pounce_dialog->treemodel);
211 GtkTreeSelection *selection;
212 GtkTreeIter iter;
213 GtkTreeIter new_selection;
214 GList *list = NULL;
215 gboolean found_selection = FALSE;
217 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
218 gtk_tree_selection_selected_foreach(selection, delete_foreach, pounce_dialog);
219 gtk_tree_selection_selected_foreach(selection, append_to_list, &list);
221 g_return_if_fail(list != NULL);
223 if (list->next == NULL) {
224 gtk_tree_model_get_iter(model, &new_selection, list->data);
225 if (gtk_tree_model_iter_next(model, &new_selection))
226 found_selection = TRUE;
227 else {
228 /* This is the last thing in the list */
229 GtkTreePath *path;
231 /* Because gtk_tree_model_iter_prev doesn't exist... */
232 gtk_tree_model_get_iter(model, &new_selection, list->data);
233 path = gtk_tree_model_get_path(model, &new_selection);
234 if (gtk_tree_path_prev(path)) {
235 gtk_tree_model_get_iter(model, &new_selection, path);
236 found_selection = TRUE;
239 gtk_tree_path_free(path);
243 while (list) {
244 if (gtk_tree_model_get_iter(model, &iter, list->data)) {
245 gtk_tree_store_remove(GTK_TREE_STORE(pounce_dialog->treemodel), &iter);
247 gtk_tree_path_free(list->data);
248 list = g_list_delete_link(list, list);
251 if (gtk_tree_model_get_iter_first(model, &iter)) {
252 if (found_selection)
253 gtk_tree_selection_select_iter(selection, &new_selection);
254 else
255 gtk_tree_selection_select_iter(selection, &iter);
256 } else
257 pounce_response_close(pounce_dialog);
260 static void
261 pounce_response_open_ims()
263 GtkTreeSelection *selection;
265 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
266 gtk_tree_selection_selected_foreach(selection, open_im_foreach, pounce_dialog);
268 pounce_response_dismiss();
271 static void
272 pounce_response_edit_cb(GtkTreeModel *model, GtkTreePath *path,
273 GtkTreeIter *iter, gpointer data)
275 PidginNotifyPounceData *pounce_data;
276 PidginNotifyDialog *dialog = (PidginNotifyDialog*)data;
277 PurplePounce *pounce;
278 GList *list;
280 list = purple_pounces_get_all();
282 gtk_tree_model_get(GTK_TREE_MODEL(dialog->treemodel), iter,
283 PIDGIN_POUNCE_DATA, &pounce_data,
284 -1);
286 for (; list != NULL; list = list->next) {
287 pounce = list->data;
288 if (pounce == pounce_data->pounce) {
289 pidgin_pounce_editor_show(pounce_data->account, NULL, pounce_data->pounce);
290 return;
294 purple_debug_warning("gtknotify", "Pounce was destroyed.\n");
297 static void
298 pounce_response_cb(GtkDialog *dlg, gint id, PidginNotifyDialog *dialog)
300 GtkTreeSelection *selection = NULL;
302 switch (id) {
303 case GTK_RESPONSE_CLOSE:
304 case GTK_RESPONSE_DELETE_EVENT:
305 pounce_response_close(dialog);
306 break;
307 case GTK_RESPONSE_YES:
308 pounce_response_open_ims();
309 break;
310 case GTK_RESPONSE_NO:
311 pounce_response_dismiss();
312 break;
313 case GTK_RESPONSE_APPLY:
314 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
315 gtk_tree_selection_selected_foreach(selection, pounce_response_edit_cb,
316 dialog);
317 break;
321 static void
322 pounce_row_selected_cb(GtkTreeView *tv, GtkTreePath *path,
323 GtkTreeViewColumn *col, gpointer data)
325 GtkTreeSelection *selection;
326 int count;
328 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
330 count = gtk_tree_selection_count_selected_rows(selection);
332 if (count == 0) {
333 gtk_widget_set_sensitive(pounce_dialog->open_button, FALSE);
334 gtk_widget_set_sensitive(pounce_dialog->edit_button, FALSE);
335 gtk_widget_set_sensitive(pounce_dialog->dismiss_button, FALSE);
336 } else if (count == 1) {
337 GList *pounces;
338 GList *list;
339 PidginNotifyPounceData *pounce_data;
340 GtkTreeIter iter;
342 list = gtk_tree_selection_get_selected_rows(selection, NULL);
343 gtk_tree_model_get_iter(GTK_TREE_MODEL(pounce_dialog->treemodel),
344 &iter, list->data);
345 gtk_tree_model_get(GTK_TREE_MODEL(pounce_dialog->treemodel), &iter,
346 PIDGIN_POUNCE_DATA, &pounce_data,
347 -1);
348 g_list_free_full(list, (GDestroyNotify)gtk_tree_path_free);
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_free_full(row, g_free);
504 /* copy-paste from gtkrequest.c */
505 static void
506 pidgin_widget_decorate_account(GtkWidget *cont, PurpleAccount *account)
508 GtkWidget *image;
509 GdkPixbuf *pixbuf;
511 if (!account)
512 return;
514 pixbuf = pidgin_create_protocol_icon(account, PIDGIN_PROTOCOL_ICON_SMALL);
515 image = gtk_image_new_from_pixbuf(pixbuf);
516 g_object_unref(G_OBJECT(pixbuf));
518 gtk_widget_set_tooltip_text(image,
519 purple_account_get_username(account));
521 if (GTK_IS_DIALOG(cont)) {
522 gtk_box_pack_start(GTK_BOX(gtk_dialog_get_action_area(
523 GTK_DIALOG(cont))), image, FALSE, TRUE, 0);
524 gtk_box_reorder_child(GTK_BOX(gtk_dialog_get_action_area(
525 GTK_DIALOG(cont))), image, 0);
526 } else if (GTK_IS_BOX(cont)) {
527 gtk_widget_set_halign(image, GTK_ALIGN_START);
528 gtk_widget_set_valign(image, GTK_ALIGN_START);
529 gtk_box_pack_end(GTK_BOX(cont), image, FALSE, TRUE, 0);
531 gtk_widget_show(image);
534 static void *
535 pidgin_notify_message(PurpleNotifyMessageType type, const char *title,
536 const char *primary, const char *secondary,
537 PurpleRequestCommonParameters *cpar)
539 GtkWidget *dialog;
540 GtkWidget *hbox;
541 GtkWidget *label;
542 GtkWidget *img = NULL;
543 char label_text[2048];
544 const char *icon_name = NULL;
545 char *primary_esc, *secondary_esc;
547 switch (type)
549 case PURPLE_NOTIFY_MSG_ERROR:
550 icon_name = "dialog-error";
551 break;
553 case PURPLE_NOTIFY_MSG_WARNING:
554 icon_name = "dialog-warning";
555 break;
557 case PURPLE_NOTIFY_MSG_INFO:
558 icon_name = "dialog-information";
559 break;
561 default:
562 icon_name = NULL;
563 break;
566 if (icon_name != NULL)
568 img = gtk_image_new_from_icon_name(icon_name,
569 GTK_ICON_SIZE_DIALOG);
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 data = data2;
819 g_free(notification);
820 } else {
821 /* Clear out all mails for the account */
822 pidgin_notify_add_mail(mail_dialog->treemodel, account, NULL, NULL, 0, TRUE, NULL);
824 if (mail_dialog->total_count == 0) {
826 * There is no API to clear the headline specifically
827 * This will trigger reset_mail_dialog()
829 pidgin_blist_set_headline(NULL, NULL, NULL, NULL, NULL);
830 return NULL;
835 /* Select first item if nothing selected */
836 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(mail_dialog->treeview));
837 if ((gtk_tree_selection_count_selected_rows(sel) < 1)
838 && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(mail_dialog->treemodel), &iter)) {
839 gtk_tree_selection_select_iter(sel, &iter);
842 if (!gtk_widget_get_visible(mail_dialog->dialog)) {
843 char *label_text = g_strdup_printf(ngettext("<b>%d new email.</b>",
844 "<b>%d new emails.</b>",
845 mail_dialog->total_count), mail_dialog->total_count);
846 mail_dialog->in_use = TRUE; /* So that _set_headline doesn't accidentally
847 remove the notifications when replacing an
848 old notification. */
849 pidgin_blist_set_headline(label_text, "mail-unread",
850 G_CALLBACK(pidgin_notify_emails_present),
851 mail_dialog->dialog,
852 (GDestroyNotify)reset_mail_dialog);
853 mail_dialog->in_use = FALSE;
854 g_free(label_text);
855 } else if (!gtk_widget_has_focus(mail_dialog->dialog))
856 pidgin_set_urgent(GTK_WINDOW(mail_dialog->dialog), TRUE);
858 return data;
861 static gboolean
862 formatted_input_cb(GtkWidget *win, GdkEventKey *event, gpointer data)
864 if (event->keyval == GDK_KEY_Escape)
866 purple_notify_close(PURPLE_NOTIFY_FORMATTED, win);
868 return TRUE;
871 return FALSE;
874 static void *
875 pidgin_notify_formatted(const char *title, const char *primary,
876 const char *secondary, const char *text)
878 GtkWidget *window;
879 GtkWidget *vbox;
880 GtkWidget *label;
881 GtkWidget *button;
882 GtkWidget *sw;
883 GtkWidget *view;
884 GtkTextBuffer *buffer;
885 char label_text[2048];
886 char *linked_text, *primary_esc, *secondary_esc;
888 window = gtk_dialog_new();
889 gtk_window_set_title(GTK_WINDOW(window), title);
890 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
892 g_signal_connect(G_OBJECT(window), "delete_event",
893 G_CALLBACK(formatted_close_cb), NULL);
895 /* Setup the main vbox */
896 vbox = gtk_dialog_get_content_area(GTK_DIALOG(window));
898 /* Setup the descriptive label */
899 primary_esc = g_markup_escape_text(primary, -1);
900 secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
901 g_snprintf(label_text, sizeof(label_text),
902 "<span weight=\"bold\" size=\"larger\">%s</span>%s%s",
903 primary_esc,
904 (secondary ? "\n" : ""),
905 (secondary ? secondary_esc : ""));
906 g_free(primary_esc);
907 g_free(secondary_esc);
909 label = gtk_label_new(NULL);
911 gtk_label_set_markup(GTK_LABEL(label), label_text);
912 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
913 gtk_label_set_selectable(GTK_LABEL(label), TRUE);
914 gtk_label_set_xalign(GTK_LABEL(label), 0);
915 gtk_label_set_yalign(GTK_LABEL(label), 0);
916 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
917 gtk_widget_show(label);
919 /* Add the view */
920 sw = gtk_scrolled_window_new(NULL, NULL);
921 gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
923 buffer = talkatu_html_buffer_new();
924 view = talkatu_view_new_with_buffer(buffer);
925 gtk_container_add(GTK_CONTAINER(sw), view);
926 gtk_widget_set_name(view, "pidgin_notify_view");
927 gtk_widget_set_size_request(view, 300, 250);
928 gtk_widget_show_all(sw);
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 talkatu_markup_set_html(TALKATU_BUFFER(buffer), linked_text, -1);
942 g_free(linked_text);
944 g_object_set_data(G_OBJECT(window), "view-widget", 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_new0(PidginNotifySearchResultsData, 1);
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_widget_set_size_request(treeview, 500, 400);
1068 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)),
1069 GTK_SELECTION_SINGLE);
1070 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), TRUE);
1071 gtk_box_pack_start(GTK_BOX(vbox),
1072 pidgin_make_scrollable(treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1),
1073 TRUE, TRUE, 0);
1074 gtk_widget_show(treeview);
1076 renderer = gtk_cell_renderer_pixbuf_new();
1077 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview),
1078 -1, "", renderer, "pixbuf", 0, NULL);
1080 i = 1;
1081 for (columniter = results->columns; columniter != NULL; columniter = columniter->next) {
1082 PurpleNotifySearchColumn *column = columniter->data;
1083 renderer = gtk_cell_renderer_text_new();
1085 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview), -1,
1086 purple_notify_searchresult_column_get_title(column), renderer, "text", i, NULL);
1088 if (!purple_notify_searchresult_column_is_visible(column))
1089 gtk_tree_view_column_set_visible(gtk_tree_view_get_column(GTK_TREE_VIEW(treeview), i), FALSE);
1091 i++;
1094 for (l = results->buttons; l; l = l->next) {
1095 PurpleNotifySearchButton *b = l->data;
1096 GtkWidget *button = NULL;
1097 switch (b->type) {
1098 case PURPLE_NOTIFY_BUTTON_LABELED:
1099 if(b->label) {
1100 button = gtk_dialog_add_button(GTK_DIALOG(window), b->label, GTK_RESPONSE_NONE);
1101 } else {
1102 purple_debug_warning("gtknotify", "Missing button label\n");
1104 break;
1105 case PURPLE_NOTIFY_BUTTON_CONTINUE:
1106 button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_GO_FORWARD, GTK_RESPONSE_NONE);
1107 break;
1108 case PURPLE_NOTIFY_BUTTON_ADD:
1109 button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_ADD, GTK_RESPONSE_NONE);
1110 break;
1111 case PURPLE_NOTIFY_BUTTON_INFO:
1112 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_TOOLBAR_USER_INFO, GTK_RESPONSE_NONE);
1113 break;
1114 case PURPLE_NOTIFY_BUTTON_IM:
1115 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW, GTK_RESPONSE_NONE);
1116 break;
1117 case PURPLE_NOTIFY_BUTTON_JOIN:
1118 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_CHAT, GTK_RESPONSE_NONE);
1119 break;
1120 case PURPLE_NOTIFY_BUTTON_INVITE:
1121 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_INVITE, GTK_RESPONSE_NONE);
1122 break;
1123 default:
1124 purple_debug_warning("gtknotify", "Incorrect button type: %d\n", b->type);
1126 if (button != NULL) {
1127 PidginNotifySearchResultsButtonData *bd;
1129 bd = g_new0(PidginNotifySearchResultsButtonData, 1);
1130 bd->button = b;
1131 bd->data = data;
1133 g_signal_connect(G_OBJECT(button), "clicked",
1134 G_CALLBACK(searchresults_callback_wrapper_cb), bd);
1135 g_signal_connect_swapped(G_OBJECT(button), "destroy", G_CALLBACK(g_free), bd);
1139 /* Add the Close button */
1140 close_button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
1142 g_signal_connect_swapped(G_OBJECT(close_button), "clicked",
1143 G_CALLBACK(searchresults_close_cb), data);
1145 data->account = purple_connection_get_account(gc);
1146 data->model = model;
1147 data->treeview = treeview;
1148 data->window = window;
1150 /* Insert rows. */
1151 pidgin_notify_searchresults_new_rows(gc, results, data);
1153 /* Show the window */
1154 pidgin_auto_parent_window(window);
1156 gtk_widget_show(window);
1157 return data;
1160 /** Xerox'ed from Finch! How the tables have turned!! ;) **/
1161 /** User information. **/
1162 static GHashTable *userinfo;
1164 static char *
1165 userinfo_hash(PurpleAccount *account, const char *who)
1167 char key[256];
1168 snprintf(key, sizeof(key), "%s - %s", purple_account_get_username(account), purple_normalize(account, who));
1169 return g_utf8_strup(key, -1);
1172 static void
1173 remove_userinfo(GtkWidget *widget, gpointer key)
1175 PidginUserInfo *pinfo = g_hash_table_lookup(userinfo, key);
1177 while (pinfo->count--)
1178 purple_notify_close(PURPLE_NOTIFY_USERINFO, widget);
1180 g_hash_table_remove(userinfo, key);
1183 static void *
1184 pidgin_notify_userinfo(PurpleConnection *gc, const char *who,
1185 PurpleNotifyUserInfo *user_info)
1187 char *info;
1188 void *ui_handle;
1189 char *key = userinfo_hash(purple_connection_get_account(gc), who);
1190 PidginUserInfo *pinfo = NULL;
1192 if (!userinfo) {
1193 userinfo = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
1196 info = purple_notify_user_info_get_text_with_newline(user_info, "<br />");
1197 pinfo = g_hash_table_lookup(userinfo, key);
1198 if (pinfo != NULL) {
1199 GtkWidget *view = g_object_get_data(G_OBJECT(pinfo->window), "view-widget");
1200 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
1201 char *linked_text = purple_markup_linkify(info);
1202 talkatu_markup_set_html(TALKATU_BUFFER(buffer), linked_text, -1);
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 uri_escaped = purple_uri_escape_for_open(uri);
1342 web_browser = purple_prefs_get_string(PIDGIN_PREFS_ROOT
1343 "/browsers/browser");
1344 place = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/browsers/place");
1346 /* if they are running gnome, use the gnome web browser */
1347 if (purple_running_gnome() == TRUE) {
1348 char *tmp = g_find_program_in_path("xdg-open");
1349 if (tmp == NULL)
1350 argv = g_slist_append(argv, "gnome-open");
1351 else
1352 argv = g_slist_append(argv, "xdg-open");
1353 g_free(tmp);
1354 argv = g_slist_append(argv, uri_escaped);
1355 } else if (purple_running_osx() == TRUE) {
1356 argv = g_slist_append(argv, "open");
1357 argv = g_slist_append(argv, uri_escaped);
1358 } else if (purple_strequal(web_browser, "epiphany") ||
1359 purple_strequal(web_browser, "galeon"))
1361 argv = g_slist_append(argv, (gpointer)web_browser);
1363 if (place == PIDGIN_BROWSER_NEW_WINDOW)
1364 argv = g_slist_append(argv, "-w");
1365 else if (place == PIDGIN_BROWSER_NEW_TAB)
1366 argv = g_slist_append(argv, "-n");
1368 argv = g_slist_append(argv, uri_escaped);
1369 } else if (purple_strequal(web_browser, "xdg-open")) {
1370 argv = g_slist_append(argv, "xdg-open");
1371 argv = g_slist_append(argv, uri_escaped);
1372 } else if (purple_strequal(web_browser, "gnome-open")) {
1373 argv = g_slist_append(argv, "gnome-open");
1374 argv = g_slist_append(argv, uri_escaped);
1375 } else if (purple_strequal(web_browser, "kfmclient")) {
1376 argv = g_slist_append(argv, "kfmclient");
1377 argv = g_slist_append(argv, "openURL");
1378 argv = g_slist_append(argv, uri_escaped);
1380 * Does Konqueror have options to open in new tab
1381 * and/or current window?
1383 } else if (purple_strequal(web_browser, "mozilla") ||
1384 purple_strequal(web_browser, "mozilla-firebird") ||
1385 purple_strequal(web_browser, "firefox") ||
1386 purple_strequal(web_browser, "seamonkey"))
1388 argv = g_slist_append(argv, (gpointer)web_browser);
1389 argv = g_slist_append(argv, uri_escaped);
1391 g_assert(uri_custom == NULL);
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_NEW_TAB) {
1396 uri_custom = g_strdup_printf("openURL(%s,new-tab)",
1397 uri_escaped);
1400 if (uri_custom != NULL) {
1401 argv_remote = g_slist_append(argv_remote,
1402 (gpointer)web_browser);
1404 /* Firefox 0.9 and higher require a "-a firefox" option
1405 * when using -remote commands. This breaks older
1406 * versions of mozilla. So we include this other handly
1407 * little string when calling firefox. If the API for
1408 * remote calls changes any more in firefox then firefox
1409 * should probably be split apart from mozilla-firebird
1410 * and mozilla... but this is good for now.
1412 if (purple_strequal(web_browser, "firefox")) {
1413 argv_remote = g_slist_append(argv_remote, "-a");
1414 argv_remote = g_slist_append(argv_remote,
1415 "firefox");
1418 argv_remote = g_slist_append(argv_remote, "-remote");
1419 argv_remote = g_slist_append(argv_remote, uri_custom);
1421 } else if (purple_strequal(web_browser, "opera")) {
1422 argv = g_slist_append(argv, "opera");
1424 if (place == PIDGIN_BROWSER_NEW_WINDOW)
1425 argv = g_slist_append(argv, "-newwindow");
1426 else if (place == PIDGIN_BROWSER_NEW_TAB)
1427 argv = g_slist_append(argv, "-newpage");
1428 /* `opera -remote "openURL(%s)"` command causes Pidgin to hang,
1429 * if there is no Opera instance running.
1432 argv = g_slist_append(argv, uri_escaped);
1433 } else if (purple_strequal(web_browser, "google-chrome")) {
1434 /* Google Chrome doesn't have command-line arguments that
1435 * control the opening of links from external calls. This is
1436 * controlled solely from a preference within Google Chrome.
1438 argv = g_slist_append(argv, "google-chrome");
1439 argv = g_slist_append(argv, uri_escaped);
1440 } else if (purple_strequal(web_browser, "chrome")) {
1441 /* Chromium doesn't have command-line arguments that control
1442 * the opening of links from external calls. This is controlled
1443 * solely from a preference within Chromium.
1445 argv = g_slist_append(argv, "chrome");
1446 argv = g_slist_append(argv, uri_escaped);
1447 } else if (purple_strequal(web_browser, "chromium-browser")) {
1448 /* Chromium doesn't have command-line arguments that control the
1449 * opening of links from external calls. This is controlled
1450 * solely from a preference within Chromium.
1452 argv = g_slist_append(argv, "chromium-browser");
1453 argv = g_slist_append(argv, uri_escaped);
1454 } else if (purple_strequal(web_browser, "custom")) {
1455 GError *error = NULL;
1456 const char *usercmd_command;
1457 gint usercmd_argc, i;
1458 gboolean uri_added = FALSE;
1460 usercmd_command = purple_prefs_get_string(PIDGIN_PREFS_ROOT
1461 "/browsers/manual_command");
1463 if (usercmd_command == NULL || *usercmd_command == '\0') {
1464 purple_notify_error(NULL, NULL, _("Unable to open URL"),
1465 _("The 'Manual' browser command has been "
1466 "chosen, but no command has been set."), NULL);
1467 g_free(uri_escaped);
1468 return NULL;
1471 if (!g_shell_parse_argv(usercmd_command, &usercmd_argc,
1472 &usercmd_argv, &error))
1474 purple_notify_error(NULL, NULL, _("Unable to open URL: "
1475 "the 'Manual' browser command seems invalid."),
1476 error ? error->message : NULL, NULL);
1477 g_error_free(error);
1478 g_free(uri_escaped);
1479 return NULL;
1482 for (i = 0; i < usercmd_argc; i++) {
1483 gchar *cmd_part = usercmd_argv[i];
1485 if (uri_added || strstr(cmd_part, "%s") == NULL) {
1486 argv = g_slist_append(argv, cmd_part);
1487 continue;
1490 uri_custom = purple_strreplace(cmd_part, "%s",
1491 uri_escaped);
1492 argv = g_slist_append(argv, uri_custom);
1493 uri_added = TRUE;
1496 /* There is no "%s" in the browser command. Assume the user
1497 * wanted the URL tacked on to the end of the command.
1499 if (!uri_added)
1500 argv = g_slist_append(argv, uri_escaped);
1503 if (argv_remote != NULL) {
1504 /* try the remote command first */
1505 if (!uri_command(argv_remote, TRUE))
1506 uri_command(argv, FALSE);
1507 } else
1508 uri_command(argv, FALSE);
1510 g_strfreev(usercmd_argv);
1511 g_free(uri_escaped);
1512 g_free(uri_custom);
1513 g_slist_free(argv);
1514 g_slist_free(argv_remote);
1516 #else /* !_WIN32 */
1517 winpidgin_notify_uri(uri);
1518 #endif /* !_WIN32 */
1520 return NULL;
1523 void
1524 pidgin_notify_pounce_add(PurpleAccount *account, PurplePounce *pounce,
1525 const char *alias, const char *event, const char *message, const char *date)
1527 GdkPixbuf *icon;
1528 GtkTreeIter iter;
1529 PidginNotifyPounceData *pounce_data;
1530 gboolean first = (pounce_dialog == NULL);
1532 if (pounce_dialog == NULL)
1533 pounce_dialog = pidgin_create_notification_dialog(PIDGIN_NOTIFY_POUNCE);
1535 icon = pidgin_create_protocol_icon(account, PIDGIN_PROTOCOL_ICON_SMALL);
1537 pounce_data = g_new(PidginNotifyPounceData, 1);
1539 pounce_data->account = account;
1540 pounce_data->pounce = pounce;
1541 pounce_data->pouncee = g_strdup(purple_pounce_get_pouncee(pounce));
1543 gtk_tree_store_append(pounce_dialog->treemodel, &iter, NULL);
1545 gtk_tree_store_set(pounce_dialog->treemodel, &iter,
1546 PIDGIN_POUNCE_ICON, icon,
1547 PIDGIN_POUNCE_ALIAS, alias,
1548 PIDGIN_POUNCE_EVENT, event,
1549 PIDGIN_POUNCE_TEXT, (message != NULL)? message : _("No message"),
1550 PIDGIN_POUNCE_DATE, date,
1551 PIDGIN_POUNCE_DATA, pounce_data,
1552 -1);
1554 if (first) {
1555 GtkTreeSelection *selection =
1556 gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
1557 gtk_tree_selection_select_iter(selection, &iter);
1560 if (icon)
1561 g_object_unref(icon);
1563 gtk_widget_show_all(pounce_dialog->dialog);
1565 return;
1568 static PidginNotifyDialog *
1569 pidgin_create_notification_dialog(PidginNotifyType type)
1571 GtkTreeStore *model = NULL;
1572 GtkWidget *dialog = NULL;
1573 GtkWidget *label = NULL;
1574 GtkCellRenderer *rend;
1575 GtkTreeViewColumn *column;
1576 GtkWidget *button = NULL;
1577 GtkWidget *vbox = NULL;
1578 GtkTreeSelection *sel;
1579 PidginNotifyDialog *spec_dialog = NULL;
1581 g_return_val_if_fail(type < PIDGIN_NOTIFY_TYPES, NULL);
1583 if (type == PIDGIN_NOTIFY_MAIL) {
1584 g_return_val_if_fail(mail_dialog == NULL, mail_dialog);
1586 model = gtk_tree_store_new(COLUMNS_PIDGIN_MAIL,
1587 GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_POINTER);
1589 } else if (type == PIDGIN_NOTIFY_POUNCE) {
1590 g_return_val_if_fail(pounce_dialog == NULL, pounce_dialog);
1592 model = gtk_tree_store_new(COLUMNS_PIDGIN_POUNCE,
1593 GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
1594 G_TYPE_STRING, G_TYPE_POINTER);
1597 dialog = gtk_dialog_new();
1599 /* Vertical box */
1600 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1602 /* Setup the dialog */
1603 gtk_container_set_border_width(GTK_CONTAINER(dialog), PIDGIN_HIG_BOX_SPACE);
1604 gtk_container_set_border_width(GTK_CONTAINER(vbox), PIDGIN_HIG_BOX_SPACE);
1605 gtk_box_set_spacing(GTK_BOX(vbox), PIDGIN_HIG_BORDER);
1607 /* Golden ratio it up! */
1608 gtk_widget_set_size_request(dialog, 550, 400);
1610 spec_dialog = g_new0(PidginNotifyDialog, 1);
1611 spec_dialog->dialog = dialog;
1613 spec_dialog->treemodel = model;
1614 spec_dialog->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1615 g_object_unref(G_OBJECT(model));
1617 if (type == PIDGIN_NOTIFY_MAIL) {
1618 gtk_window_set_title(GTK_WINDOW(dialog), _("New Mail"));
1619 gtk_window_set_role(GTK_WINDOW(dialog), "new_mail_detailed");
1620 g_signal_connect(G_OBJECT(dialog), "focus-in-event",
1621 G_CALLBACK(mail_window_focus_cb), NULL);
1623 gtk_dialog_add_button(GTK_DIALOG(dialog),
1624 _("Open All Messages"), GTK_RESPONSE_ACCEPT);
1626 button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1627 PIDGIN_STOCK_OPEN_MAIL, GTK_RESPONSE_YES);
1628 spec_dialog->open_button = button;
1630 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(spec_dialog->treeview), FALSE);
1632 gtk_tree_view_set_search_column(GTK_TREE_VIEW(spec_dialog->treeview), PIDGIN_MAIL_TEXT);
1633 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(spec_dialog->treeview),
1634 pidgin_tree_view_search_equal_func, NULL, NULL);
1635 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(spec_dialog->treeview));
1636 gtk_tree_selection_set_mode(sel, GTK_SELECTION_BROWSE);
1638 g_signal_connect(G_OBJECT(dialog), "response",
1639 G_CALLBACK(email_response_cb), spec_dialog);
1640 g_signal_connect(G_OBJECT(sel), "changed",
1641 G_CALLBACK(selection_changed_cb), spec_dialog);
1642 g_signal_connect(G_OBJECT(spec_dialog->treeview), "row-activated", G_CALLBACK(email_row_activated_cb), NULL);
1644 column = gtk_tree_view_column_new();
1645 gtk_tree_view_column_set_resizable(column, TRUE);
1646 rend = gtk_cell_renderer_pixbuf_new();
1647 gtk_tree_view_column_pack_start(column, rend, FALSE);
1649 gtk_tree_view_column_set_attributes(column, rend, "pixbuf", PIDGIN_MAIL_ICON, NULL);
1650 rend = gtk_cell_renderer_text_new();
1651 gtk_tree_view_column_pack_start(column, rend, TRUE);
1652 gtk_tree_view_column_set_attributes(column, rend, "markup", PIDGIN_MAIL_TEXT, NULL);
1653 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1655 label = gtk_label_new(NULL);
1656 gtk_label_set_markup(GTK_LABEL(label), _("<span weight=\"bold\" size=\"larger\">You have mail!</span>"));
1658 } else if (type == PIDGIN_NOTIFY_POUNCE) {
1659 gtk_window_set_title(GTK_WINDOW(dialog), _("New Pounces"));
1661 button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1662 _("IM"), GTK_RESPONSE_YES);
1663 gtk_widget_set_sensitive(button, FALSE);
1664 spec_dialog->open_button = button;
1666 button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1667 PIDGIN_STOCK_MODIFY, GTK_RESPONSE_APPLY);
1668 gtk_widget_set_sensitive(button, FALSE);
1669 spec_dialog->edit_button = button;
1671 /* Translators: Make sure you translate "Dismiss" differently than
1672 "close"! This string is used in the "You have pounced" dialog
1673 that appears when one of your Buddy Pounces is triggered. In
1674 this context "Dismiss" means "I acknowledge that I've seen that
1675 this pounce was triggered--remove it from this list." Translating
1676 it as "Remove" is acceptable if you can't think of a more precise
1677 word. */
1678 button = gtk_dialog_add_button(GTK_DIALOG(dialog), _("Dismiss"),
1679 GTK_RESPONSE_NO);
1680 gtk_widget_set_sensitive(button, FALSE);
1681 spec_dialog->dismiss_button = button;
1683 g_signal_connect(G_OBJECT(dialog), "response",
1684 G_CALLBACK(pounce_response_cb), spec_dialog);
1686 column = gtk_tree_view_column_new();
1687 gtk_tree_view_column_set_title(column, _("Buddy"));
1688 gtk_tree_view_column_set_resizable(column, TRUE);
1689 rend = gtk_cell_renderer_pixbuf_new();
1690 gtk_tree_view_column_pack_start(column, rend, FALSE);
1692 gtk_tree_view_column_set_attributes(column, rend, "pixbuf", PIDGIN_POUNCE_ICON, NULL);
1693 rend = gtk_cell_renderer_text_new();
1694 gtk_tree_view_column_pack_start(column, rend, FALSE);
1695 gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_ALIAS);
1696 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1698 column = gtk_tree_view_column_new();
1699 gtk_tree_view_column_set_title(column, _("Event"));
1700 gtk_tree_view_column_set_resizable(column, TRUE);
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_EVENT);
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, _("Message"));
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_TEXT);
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, _("Date"));
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_DATE);
1720 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1722 label = gtk_label_new(NULL);
1723 gtk_label_set_markup(GTK_LABEL(label), _("<span weight=\"bold\" size=\"larger\">You have pounced!</span>"));
1725 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(spec_dialog->treeview));
1726 gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
1727 g_signal_connect(G_OBJECT(sel), "changed",
1728 G_CALLBACK(pounce_row_selected_cb), NULL);
1729 g_signal_connect(G_OBJECT(spec_dialog->treeview), "row-activated",
1730 G_CALLBACK(pounce_response_open_ims), NULL);
1733 gtk_dialog_add_button(GTK_DIALOG(dialog),
1734 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
1736 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1737 gtk_label_set_xalign(GTK_LABEL(label), 0);
1738 gtk_label_set_yalign(GTK_LABEL(label), 0);
1739 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1740 gtk_box_pack_start(GTK_BOX(vbox),
1741 pidgin_make_scrollable(spec_dialog->treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1),
1742 TRUE, TRUE, 2);
1744 return spec_dialog;
1747 static void
1748 signed_off_cb(PurpleConnection *gc, gpointer unused)
1750 /* Clear any pending emails for this account */
1751 pidgin_notify_emails(gc, 0, FALSE, NULL, NULL, NULL, NULL);
1753 if (mail_dialog != NULL && mail_dialog->total_count == 0)
1754 reset_mail_dialog(NULL);
1757 static void*
1758 pidgin_notify_get_handle(void)
1760 static int handle;
1761 return &handle;
1764 void pidgin_notify_init(void)
1766 void *handle = pidgin_notify_get_handle();
1768 purple_signal_connect(purple_connections_get_handle(), "signed-off",
1769 handle, PURPLE_CALLBACK(signed_off_cb), NULL);
1772 void pidgin_notify_uninit(void)
1774 purple_signals_disconnect_by_handle(pidgin_notify_get_handle());
1777 static PurpleNotifyUiOps ops =
1779 pidgin_notify_message,
1780 pidgin_notify_email,
1781 pidgin_notify_emails,
1782 pidgin_notify_formatted,
1783 pidgin_notify_searchresults,
1784 pidgin_notify_searchresults_new_rows,
1785 pidgin_notify_userinfo,
1786 pidgin_notify_uri,
1787 pidgin_close_notify,
1788 NULL,
1789 NULL,
1790 NULL,
1791 NULL
1794 PurpleNotifyUiOps *
1795 pidgin_notify_get_ui_ops(void)
1797 return &ops;