mark PurpleImageClass as private
[pidgin-git.git] / pidgin / gtknotify.c
blobf75e445b610463a6e8a319bc1100d08d6a01ac5e
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_foreach(list, (GFunc)gtk_tree_path_free, NULL);
349 g_list_free(list);
351 pounces = purple_pounces_get_all();
352 for (; pounces != NULL; pounces = pounces->next) {
353 PurplePounce *pounce = pounces->data;
354 if (pounce == pounce_data->pounce) {
355 gtk_widget_set_sensitive(pounce_dialog->edit_button, TRUE);
356 break;
360 gtk_widget_set_sensitive(pounce_dialog->open_button, TRUE);
361 gtk_widget_set_sensitive(pounce_dialog->dismiss_button, TRUE);
362 } else {
363 gtk_widget_set_sensitive(pounce_dialog->open_button, TRUE);
364 gtk_widget_set_sensitive(pounce_dialog->edit_button, FALSE);
365 gtk_widget_set_sensitive(pounce_dialog->dismiss_button, TRUE);
371 static void
372 reset_mail_dialog(GtkDialog *unused)
374 g_return_if_fail(mail_dialog != NULL);
376 if (mail_dialog->in_use)
377 return;
378 gtk_widget_destroy(mail_dialog->dialog);
379 g_free(mail_dialog);
380 mail_dialog = NULL;
381 purple_signal_emit(purple_notify_get_handle(), "displaying-emails-clear");
384 gboolean
385 pidgin_notify_emails_pending()
387 return mail_dialog != NULL
388 && !gtk_widget_get_visible(mail_dialog->dialog);
391 void pidgin_notify_emails_present(void *data)
393 if (pidgin_notify_emails_pending()) {
394 gtk_widget_show_all(mail_dialog->dialog);
395 mail_dialog->in_use = TRUE;
396 pidgin_blist_set_headline(NULL, NULL, NULL, NULL, NULL);
397 mail_dialog->in_use = FALSE;
399 purple_signal_emit(purple_notify_get_handle(), "displaying-emails-clear");
402 static void
403 email_response_cb(GtkDialog *unused, gint id, PidginNotifyDialog *unused2)
405 PidginNotifyMailData *data = NULL;
406 GtkTreeModel *model = GTK_TREE_MODEL(mail_dialog->treemodel);
407 GtkTreeIter iter;
409 if (id == GTK_RESPONSE_YES)
411 /* A single row activated. Remove that row. */
412 GtkTreeSelection *selection;
414 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(mail_dialog->treeview));
416 if (gtk_tree_selection_get_selected(selection, NULL, &iter))
418 gtk_tree_model_get(model, &iter, PIDGIN_MAIL_DATA, &data, -1);
419 purple_notify_uri(NULL, data->url);
421 gtk_tree_store_remove(mail_dialog->treemodel, &iter);
422 if (data->purple_has_handle)
423 purple_notify_close(PURPLE_NOTIFY_EMAILS, data);
424 else
425 pidgin_close_notify(PURPLE_NOTIFY_EMAILS, data);
427 if (gtk_tree_model_get_iter_first(model, &iter))
428 return;
430 else
431 return;
433 else
435 /* Remove all the rows */
436 while (gtk_tree_model_get_iter_first(model, &iter))
438 gtk_tree_model_get(model, &iter, PIDGIN_MAIL_DATA, &data, -1);
440 if (id == GTK_RESPONSE_ACCEPT)
441 purple_notify_uri(NULL, data->url);
443 gtk_tree_store_remove(mail_dialog->treemodel, &iter);
444 if (data->purple_has_handle)
445 purple_notify_close(PURPLE_NOTIFY_EMAILS, data);
446 else
447 pidgin_close_notify(PURPLE_NOTIFY_EMAILS, data);
451 reset_mail_dialog(NULL);
454 static void
455 email_row_activated_cb(GtkTreeView *tv, GtkTreePath *path,
456 GtkTreeViewColumn *col, gpointer data)
458 email_response_cb(NULL, GTK_RESPONSE_YES, NULL);
461 static gboolean
462 formatted_close_cb(GtkWidget *win, GdkEvent *event, void *user_data)
464 purple_notify_close(PURPLE_NOTIFY_FORMATTED, win);
465 return FALSE;
468 static gboolean
469 searchresults_close_cb(PidginNotifySearchResultsData *data, GdkEvent *event, gpointer user_data)
471 purple_notify_close(PURPLE_NOTIFY_SEARCHRESULTS, data);
472 return FALSE;
475 static void
476 searchresults_callback_wrapper_cb(GtkWidget *widget, PidginNotifySearchResultsButtonData *bd)
478 PidginNotifySearchResultsData *data = bd->data;
480 GtkTreeSelection *selection;
481 GtkTreeModel *model;
482 GtkTreeIter iter;
483 PurpleNotifySearchButton *button;
484 GList *row = NULL;
485 gchar *str;
486 int i;
488 g_return_if_fail(data != NULL);
490 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(data->treeview));
492 if (gtk_tree_selection_get_selected(selection, &model, &iter))
494 for (i = 1; i < gtk_tree_model_get_n_columns(GTK_TREE_MODEL(model)); i++) {
495 gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, i, &str, -1);
496 row = g_list_append(row, str);
500 button = bd->button;
501 button->callback(purple_account_get_connection(data->account), row, data->user_data);
502 g_list_foreach(row, (GFunc)g_free, NULL);
503 g_list_free(row);
506 /* copy-paste from gtkrequest.c */
507 static void
508 pidgin_widget_decorate_account(GtkWidget *cont, PurpleAccount *account)
510 GtkWidget *image;
511 GdkPixbuf *pixbuf;
513 if (!account)
514 return;
516 pixbuf = pidgin_create_protocol_icon(account, PIDGIN_PROTOCOL_ICON_SMALL);
517 image = gtk_image_new_from_pixbuf(pixbuf);
518 g_object_unref(G_OBJECT(pixbuf));
520 gtk_widget_set_tooltip_text(image,
521 purple_account_get_username(account));
523 if (GTK_IS_DIALOG(cont)) {
524 gtk_box_pack_start(GTK_BOX(gtk_dialog_get_action_area(
525 GTK_DIALOG(cont))), image, FALSE, TRUE, 0);
526 gtk_box_reorder_child(GTK_BOX(gtk_dialog_get_action_area(
527 GTK_DIALOG(cont))), image, 0);
528 } else if (GTK_IS_BOX(cont)) {
529 gtk_widget_set_halign(image, GTK_ALIGN_START);
530 gtk_widget_set_valign(image, GTK_ALIGN_START);
531 gtk_box_pack_end(GTK_BOX(cont), image, FALSE, TRUE, 0);
533 gtk_widget_show(image);
536 static void *
537 pidgin_notify_message(PurpleNotifyMessageType type, const char *title,
538 const char *primary, const char *secondary,
539 PurpleRequestCommonParameters *cpar)
541 GtkWidget *dialog;
542 GtkWidget *hbox;
543 GtkWidget *label;
544 GtkWidget *img = NULL;
545 char label_text[2048];
546 const char *icon_name = NULL;
547 char *primary_esc, *secondary_esc;
549 switch (type)
551 case PURPLE_NOTIFY_MSG_ERROR:
552 icon_name = "dialog-error";
553 break;
555 case PURPLE_NOTIFY_MSG_WARNING:
556 icon_name = "dialog-warning";
557 break;
559 case PURPLE_NOTIFY_MSG_INFO:
560 icon_name = "dialog-information";
561 break;
563 default:
564 icon_name = NULL;
565 break;
568 if (icon_name != NULL)
570 img = gtk_image_new_from_icon_name(icon_name,
571 GTK_ICON_SIZE_DIALOG);
572 gtk_widget_set_halign(img, GTK_ALIGN_START);
573 gtk_widget_set_valign(img, GTK_ALIGN_START);
576 dialog = gtk_dialog_new_with_buttons(title ? title : PIDGIN_ALERT_TITLE,
577 NULL, 0, GTK_STOCK_CLOSE,
578 GTK_RESPONSE_CLOSE, NULL);
580 gtk_window_set_role(GTK_WINDOW(dialog), "notify_dialog");
582 g_signal_connect(G_OBJECT(dialog), "response",
583 G_CALLBACK(message_response_cb), dialog);
585 gtk_container_set_border_width(GTK_CONTAINER(dialog), PIDGIN_HIG_BORDER);
586 gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
587 gtk_box_set_spacing(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
588 PIDGIN_HIG_BORDER);
589 gtk_container_set_border_width(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
590 PIDGIN_HIG_BOX_SPACE);
592 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PIDGIN_HIG_BORDER);
593 gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
594 hbox);
596 if (img != NULL)
597 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
599 pidgin_widget_decorate_account(hbox,
600 purple_request_cpar_get_account(cpar));
602 primary_esc = g_markup_escape_text(primary, -1);
603 secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
604 g_snprintf(label_text, sizeof(label_text),
605 "<span weight=\"bold\" size=\"larger\">%s</span>%s%s",
606 primary_esc, (secondary ? "\n\n" : ""),
607 (secondary ? secondary_esc : ""));
608 g_free(primary_esc);
609 g_free(secondary_esc);
611 label = gtk_label_new(NULL);
613 gtk_label_set_markup(GTK_LABEL(label), label_text);
614 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
615 gtk_label_set_selectable(GTK_LABEL(label), TRUE);
616 gtk_label_set_xalign(GTK_LABEL(label), 0);
617 gtk_label_set_yalign(GTK_LABEL(label), 0);
618 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
620 g_object_set_data(G_OBJECT(dialog), "pidgin-parent-from",
621 purple_request_cpar_get_parent_from(cpar));
622 pidgin_auto_parent_window(dialog);
624 gtk_widget_show_all(dialog);
626 return dialog;
629 static void
630 selection_changed_cb(GtkTreeSelection *sel, PidginNotifyDialog *dialog)
632 GtkTreeIter iter;
633 GtkTreeModel *model;
634 PidginNotifyMailData *data;
635 gboolean active = TRUE;
637 if (gtk_tree_selection_get_selected(sel, &model, &iter) == FALSE)
638 active = FALSE;
639 else
641 gtk_tree_model_get(model, &iter, PIDGIN_MAIL_DATA, &data, -1);
642 if (data->url == NULL)
643 active = FALSE;
646 gtk_widget_set_sensitive(dialog->open_button, active);
649 static void *
650 pidgin_notify_email(PurpleConnection *gc, const char *subject, const char *from,
651 const char *to, const char *url)
653 return pidgin_notify_emails(gc, 1, (subject != NULL),
654 (subject == NULL ? NULL : &subject),
655 (from == NULL ? NULL : &from),
656 (to == NULL ? NULL : &to),
657 (url == NULL ? NULL : &url));
660 static int
661 mail_window_focus_cb(GtkWidget *widget, GdkEventFocus *focus, gpointer null)
663 pidgin_set_urgent(GTK_WINDOW(widget), FALSE);
664 return 0;
667 /* count == 0 means this is a detailed mail notification.
668 * count > 0 mean non-detailed.
670 static void *
671 pidgin_notify_add_mail(GtkTreeStore *treemodel, PurpleAccount *account, char *notification, const char *url, int count, gboolean clear, gboolean *new_data)
673 PidginNotifyMailData *data = NULL;
674 GtkTreeIter iter;
675 GdkPixbuf *icon;
676 gboolean new_n = TRUE;
678 if (count > 0 || clear) {
679 /* Allow only one non-detailed email notification for each account */
680 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(treemodel), &iter)) {
681 gboolean advanced;
682 do {
683 advanced = FALSE;
684 gtk_tree_model_get(GTK_TREE_MODEL(treemodel), &iter,
685 PIDGIN_MAIL_DATA, &data, -1);
686 if (data && data->account == account) {
687 if (clear) {
688 advanced = gtk_tree_store_remove(treemodel, &iter);
689 mail_dialog->total_count -= data->count;
691 if (data->purple_has_handle)
692 purple_notify_close(PURPLE_NOTIFY_EMAILS, data);
693 else
694 pidgin_close_notify(PURPLE_NOTIFY_EMAILS, data);
695 /* We're completely done if we've processed all entries */
696 if (!advanced)
697 return NULL;
698 } else if (data->count > 0) {
699 new_n = FALSE;
700 g_free(data->url);
701 data->url = NULL;
702 mail_dialog->total_count -= data->count;
703 break;
706 } while (advanced || gtk_tree_model_iter_next(GTK_TREE_MODEL(treemodel), &iter));
710 if (clear)
711 return NULL;
713 icon = pidgin_create_protocol_icon(account, PIDGIN_PROTOCOL_ICON_MEDIUM);
715 if (new_n) {
716 data = g_new0(PidginNotifyMailData, 1);
717 data->purple_has_handle = TRUE;
718 gtk_tree_store_append(treemodel, &iter, NULL);
721 if (url != NULL)
722 data->url = g_strdup(url);
724 gtk_tree_store_set(treemodel, &iter,
725 PIDGIN_MAIL_ICON, icon,
726 PIDGIN_MAIL_TEXT, notification,
727 PIDGIN_MAIL_DATA, data,
728 -1);
729 data->account = account;
730 /* count == 0 indicates we're adding a single detailed e-mail */
731 data->count = count > 0 ? count : 1;
733 if (icon)
734 g_object_unref(icon);
736 if (new_data)
737 *new_data = new_n;
738 return data;
741 static void *
742 pidgin_notify_emails(PurpleConnection *gc, size_t count, gboolean detailed,
743 const char **subjects, const char **froms,
744 const char **tos, const char **urls)
746 char *notification;
747 PurpleAccount *account;
748 PidginNotifyMailData *data = NULL, *data2;
749 gboolean new_data = FALSE;
750 GtkTreeSelection *sel;
751 GtkTreeIter iter;
753 /* Don't bother updating if there aren't new emails and we don't have any displayed currently */
754 if (count == 0 && mail_dialog == NULL)
755 return NULL;
757 account = purple_connection_get_account(gc);
758 if (mail_dialog == NULL)
759 mail_dialog = pidgin_create_notification_dialog(PIDGIN_NOTIFY_MAIL);
761 mail_dialog->total_count += count;
762 if (detailed) {
763 for ( ; count; --count) {
764 char *to_text = NULL;
765 char *from_text = NULL;
766 char *subject_text = NULL;
767 char *tmp;
768 gboolean first = TRUE;
770 if (tos != NULL) {
771 tmp = g_markup_escape_text(*tos, -1);
772 to_text = g_strdup_printf("<b>%s</b>: %s\n", _("Account"), tmp);
773 g_free(tmp);
774 first = FALSE;
775 tos++;
777 if (froms != NULL) {
778 tmp = g_markup_escape_text(*froms, -1);
779 from_text = g_strdup_printf("%s<b>%s</b>: %s\n", first ? "<br>" : "", _("Sender"), tmp);
780 g_free(tmp);
781 first = FALSE;
782 froms++;
784 if (subjects != NULL) {
785 tmp = g_markup_escape_text(*subjects, -1);
786 subject_text = g_strdup_printf("%s<b>%s</b>: %s", first ? "<br>" : "", _("Subject"), tmp);
787 g_free(tmp);
788 first = FALSE;
789 subjects++;
791 #define SAFE(x) ((x) ? (x) : "")
792 notification = g_strdup_printf("%s%s%s", SAFE(to_text), SAFE(from_text), SAFE(subject_text));
793 #undef SAFE
794 g_free(to_text);
795 g_free(from_text);
796 g_free(subject_text);
797 (void)first;
799 /* If we don't keep track of this, will leak "data" for each of the notifications except the last */
800 data2 = pidgin_notify_add_mail(mail_dialog->treemodel, account, notification, urls ? *urls : NULL, 0, FALSE, &new_data);
801 if (data2 && new_data) {
802 if (data)
803 data->purple_has_handle = FALSE;
804 data = data2;
806 g_free(notification);
808 if (urls != NULL)
809 urls++;
811 } else {
812 if (count > 0) {
813 notification = g_strdup_printf(ngettext("%s has %d new message.",
814 "%s has %d new messages.",
815 (int)count),
816 *tos, (int)count);
817 data2 = pidgin_notify_add_mail(mail_dialog->treemodel, account, notification, urls ? *urls : NULL, count, FALSE, &new_data);
818 if (data2 && new_data) {
819 if (data)
820 data->purple_has_handle = FALSE;
821 data = data2;
823 g_free(notification);
824 } else {
825 /* Clear out all mails for the account */
826 pidgin_notify_add_mail(mail_dialog->treemodel, account, NULL, NULL, 0, TRUE, NULL);
828 if (mail_dialog->total_count == 0) {
830 * There is no API to clear the headline specifically
831 * This will trigger reset_mail_dialog()
833 pidgin_blist_set_headline(NULL, NULL, NULL, NULL, NULL);
834 return NULL;
839 /* Select first item if nothing selected */
840 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(mail_dialog->treeview));
841 if ((gtk_tree_selection_count_selected_rows(sel) < 1)
842 && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(mail_dialog->treemodel), &iter)) {
843 gtk_tree_selection_select_iter(sel, &iter);
846 if (!gtk_widget_get_visible(mail_dialog->dialog)) {
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, "mail-unread",
854 G_CALLBACK(pidgin_notify_emails_present),
855 mail_dialog->dialog,
856 (GDestroyNotify)reset_mail_dialog);
857 mail_dialog->in_use = FALSE;
858 g_free(label_text);
859 } else if (!gtk_widget_has_focus(mail_dialog->dialog))
860 pidgin_set_urgent(GTK_WINDOW(mail_dialog->dialog), TRUE);
862 return data;
865 static gboolean
866 formatted_input_cb(GtkWidget *win, GdkEventKey *event, gpointer data)
868 if (event->keyval == GDK_KEY_Escape)
870 purple_notify_close(PURPLE_NOTIFY_FORMATTED, win);
872 return TRUE;
875 return FALSE;
878 static void *
879 pidgin_notify_formatted(const char *title, const char *primary,
880 const char *secondary, const char *text)
882 GtkWidget *window;
883 GtkWidget *vbox;
884 GtkWidget *label;
885 GtkWidget *button;
886 GtkWidget *sw;
887 GtkWidget *view;
888 GtkTextBuffer *buffer;
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 view */
924 sw = gtk_scrolled_window_new(NULL, NULL);
925 gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
927 buffer = talkatu_html_buffer_new();
928 view = talkatu_view_new_with_buffer(buffer);
929 gtk_container_add(GTK_CONTAINER(sw), view);
930 gtk_widget_set_name(view, "pidgin_notify_view");
931 gtk_widget_set_size_request(view, 300, 250);
932 gtk_widget_show_all(sw);
934 /* Add the Close button. */
935 button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
936 gtk_widget_grab_focus(button);
938 g_signal_connect_swapped(G_OBJECT(button), "clicked",
939 G_CALLBACK(formatted_close_cb), window);
940 g_signal_connect(G_OBJECT(window), "key_press_event",
941 G_CALLBACK(formatted_input_cb), NULL);
943 /* Make sure URLs are clickable */
944 linked_text = purple_markup_linkify(text);
945 talkatu_markup_set_html(TALKATU_BUFFER(buffer), linked_text, -1);
946 g_free(linked_text);
948 g_object_set_data(G_OBJECT(window), "view-widget", view);
950 /* Show the window */
951 pidgin_auto_parent_window(window);
953 gtk_widget_show(window);
955 return window;
958 static void
959 pidgin_notify_searchresults_new_rows(PurpleConnection *gc, PurpleNotifySearchResults *results,
960 void *data_)
962 PidginNotifySearchResultsData *data = data_;
963 GtkListStore *model = data->model;
964 GtkTreeIter iter;
965 GdkPixbuf *pixbuf;
966 GList *row, *column;
967 guint n;
969 gtk_list_store_clear(data->model);
971 pixbuf = pidgin_create_protocol_icon(purple_connection_get_account(gc), PIDGIN_PROTOCOL_ICON_SMALL);
973 for (row = results->rows; row != NULL; row = row->next) {
975 gtk_list_store_append(model, &iter);
976 gtk_list_store_set(model, &iter, 0, pixbuf, -1);
978 n = 1;
979 for (column = row->data; column != NULL; column = column->next) {
980 GValue v;
982 v.g_type = 0;
983 g_value_init(&v, G_TYPE_STRING);
984 g_value_set_string(&v, column->data);
985 gtk_list_store_set_value(model, &iter, n, &v);
986 n++;
990 if (pixbuf != NULL)
991 g_object_unref(pixbuf);
994 static void *
995 pidgin_notify_searchresults(PurpleConnection *gc, const char *title,
996 const char *primary, const char *secondary,
997 PurpleNotifySearchResults *results, gpointer user_data)
999 GtkWidget *window;
1000 GtkWidget *treeview;
1001 GtkWidget *close_button;
1002 GType *col_types;
1003 GtkListStore *model;
1004 GtkCellRenderer *renderer;
1005 guint col_num;
1006 GList *columniter;
1007 guint i;
1008 GList *l;
1010 GtkWidget *vbox;
1011 GtkWidget *label;
1012 PidginNotifySearchResultsData *data;
1013 char *label_text;
1014 char *primary_esc, *secondary_esc;
1016 g_return_val_if_fail(gc != NULL, NULL);
1017 g_return_val_if_fail(results != NULL, NULL);
1019 data = g_malloc(sizeof(PidginNotifySearchResultsData));
1020 data->user_data = user_data;
1021 data->results = results;
1023 /* Create the window */
1024 window = gtk_dialog_new();
1025 gtk_window_set_title(GTK_WINDOW(window), title ? title :_("Search Results"));
1026 gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER);
1027 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
1029 g_signal_connect_swapped(G_OBJECT(window), "delete_event",
1030 G_CALLBACK(searchresults_close_cb), data);
1032 /* Setup the main vbox */
1033 vbox = gtk_dialog_get_content_area(GTK_DIALOG(window));
1035 /* Setup the descriptive label */
1036 primary_esc = (primary != NULL) ? g_markup_escape_text(primary, -1) : NULL;
1037 secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
1038 label_text = g_strdup_printf(
1039 "<span weight=\"bold\" size=\"larger\">%s</span>%s%s",
1040 (primary ? primary_esc : ""),
1041 (primary && secondary ? "\n" : ""),
1042 (secondary ? secondary_esc : ""));
1043 g_free(primary_esc);
1044 g_free(secondary_esc);
1045 label = gtk_label_new(NULL);
1046 gtk_label_set_markup(GTK_LABEL(label), label_text);
1047 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1048 gtk_label_set_xalign(GTK_LABEL(label), 0);
1049 gtk_label_set_yalign(GTK_LABEL(label), 0);
1050 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1051 gtk_widget_show(label);
1052 g_free(label_text);
1054 /* +1 is for the automagically created Status column. */
1055 col_num = g_list_length(results->columns) + 1;
1057 /* Setup the list model */
1058 col_types = g_new0(GType, col_num);
1060 /* There always is this first column. */
1061 col_types[0] = GDK_TYPE_PIXBUF;
1062 for (i = 1; i < col_num; i++) {
1063 col_types[i] = G_TYPE_STRING;
1065 model = gtk_list_store_newv(col_num, col_types);
1066 g_free(col_types);
1068 /* Setup the treeview */
1069 treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1070 g_object_unref(G_OBJECT(model));
1071 gtk_widget_set_size_request(treeview, 500, 400);
1072 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)),
1073 GTK_SELECTION_SINGLE);
1074 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), TRUE);
1075 gtk_box_pack_start(GTK_BOX(vbox),
1076 pidgin_make_scrollable(treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1),
1077 TRUE, TRUE, 0);
1078 gtk_widget_show(treeview);
1080 renderer = gtk_cell_renderer_pixbuf_new();
1081 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview),
1082 -1, "", renderer, "pixbuf", 0, NULL);
1084 i = 1;
1085 for (columniter = results->columns; columniter != NULL; columniter = columniter->next) {
1086 PurpleNotifySearchColumn *column = columniter->data;
1087 renderer = gtk_cell_renderer_text_new();
1089 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview), -1,
1090 purple_notify_searchresult_column_get_title(column), renderer, "text", i, NULL);
1092 if (!purple_notify_searchresult_column_is_visible(column))
1093 gtk_tree_view_column_set_visible(gtk_tree_view_get_column(GTK_TREE_VIEW(treeview), i), FALSE);
1095 i++;
1098 for (l = results->buttons; l; l = l->next) {
1099 PurpleNotifySearchButton *b = l->data;
1100 GtkWidget *button = NULL;
1101 switch (b->type) {
1102 case PURPLE_NOTIFY_BUTTON_LABELED:
1103 if(b->label) {
1104 button = gtk_dialog_add_button(GTK_DIALOG(window), b->label, GTK_RESPONSE_NONE);
1105 } else {
1106 purple_debug_warning("gtknotify", "Missing button label\n");
1108 break;
1109 case PURPLE_NOTIFY_BUTTON_CONTINUE:
1110 button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_GO_FORWARD, GTK_RESPONSE_NONE);
1111 break;
1112 case PURPLE_NOTIFY_BUTTON_ADD:
1113 button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_ADD, GTK_RESPONSE_NONE);
1114 break;
1115 case PURPLE_NOTIFY_BUTTON_INFO:
1116 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_TOOLBAR_USER_INFO, GTK_RESPONSE_NONE);
1117 break;
1118 case PURPLE_NOTIFY_BUTTON_IM:
1119 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW, GTK_RESPONSE_NONE);
1120 break;
1121 case PURPLE_NOTIFY_BUTTON_JOIN:
1122 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_CHAT, GTK_RESPONSE_NONE);
1123 break;
1124 case PURPLE_NOTIFY_BUTTON_INVITE:
1125 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_INVITE, GTK_RESPONSE_NONE);
1126 break;
1127 default:
1128 purple_debug_warning("gtknotify", "Incorrect button type: %d\n", b->type);
1130 if (button != NULL) {
1131 PidginNotifySearchResultsButtonData *bd;
1133 bd = g_new0(PidginNotifySearchResultsButtonData, 1);
1134 bd->button = b;
1135 bd->data = data;
1137 g_signal_connect(G_OBJECT(button), "clicked",
1138 G_CALLBACK(searchresults_callback_wrapper_cb), bd);
1139 g_signal_connect_swapped(G_OBJECT(button), "destroy", G_CALLBACK(g_free), bd);
1143 /* Add the Close button */
1144 close_button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
1146 g_signal_connect_swapped(G_OBJECT(close_button), "clicked",
1147 G_CALLBACK(searchresults_close_cb), data);
1149 data->account = purple_connection_get_account(gc);
1150 data->model = model;
1151 data->treeview = treeview;
1152 data->window = window;
1154 /* Insert rows. */
1155 pidgin_notify_searchresults_new_rows(gc, results, data);
1157 /* Show the window */
1158 pidgin_auto_parent_window(window);
1160 gtk_widget_show(window);
1161 return data;
1164 /** Xerox'ed from Finch! How the tables have turned!! ;) **/
1165 /** User information. **/
1166 static GHashTable *userinfo;
1168 static char *
1169 userinfo_hash(PurpleAccount *account, const char *who)
1171 char key[256];
1172 snprintf(key, sizeof(key), "%s - %s", purple_account_get_username(account), purple_normalize(account, who));
1173 return g_utf8_strup(key, -1);
1176 static void
1177 remove_userinfo(GtkWidget *widget, gpointer key)
1179 PidginUserInfo *pinfo = g_hash_table_lookup(userinfo, key);
1181 while (pinfo->count--)
1182 purple_notify_close(PURPLE_NOTIFY_USERINFO, widget);
1184 g_hash_table_remove(userinfo, key);
1187 static void *
1188 pidgin_notify_userinfo(PurpleConnection *gc, const char *who,
1189 PurpleNotifyUserInfo *user_info)
1191 char *info;
1192 void *ui_handle;
1193 char *key = userinfo_hash(purple_connection_get_account(gc), who);
1194 PidginUserInfo *pinfo = NULL;
1196 if (!userinfo) {
1197 userinfo = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
1200 info = purple_notify_user_info_get_text_with_newline(user_info, "<br />");
1201 pinfo = g_hash_table_lookup(userinfo, key);
1202 if (pinfo != NULL) {
1203 GtkWidget *view = g_object_get_data(G_OBJECT(pinfo->window), "view-widget");
1204 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
1205 char *linked_text = purple_markup_linkify(info);
1206 talkatu_markup_set_html(TALKATU_BUFFER(buffer), linked_text, -1);
1207 g_free(linked_text);
1208 g_free(key);
1209 ui_handle = pinfo->window;
1210 pinfo->count++;
1211 } else {
1212 char *primary = g_strdup_printf(_("Info for %s"), who);
1213 ui_handle = pidgin_notify_formatted(_("Buddy Information"), primary, NULL, info);
1214 g_signal_handlers_disconnect_by_func(G_OBJECT(ui_handle), G_CALLBACK(formatted_close_cb), NULL);
1215 g_signal_connect(G_OBJECT(ui_handle), "destroy", G_CALLBACK(remove_userinfo), key);
1216 g_free(primary);
1217 pinfo = g_new0(PidginUserInfo, 1);
1218 pinfo->window = ui_handle;
1219 pinfo->count = 1;
1220 g_hash_table_insert(userinfo, key, pinfo);
1222 g_free(info);
1223 return ui_handle;
1226 static void
1227 pidgin_close_notify(PurpleNotifyType type, void *ui_handle)
1229 if (type == PURPLE_NOTIFY_EMAIL || type == PURPLE_NOTIFY_EMAILS)
1231 PidginNotifyMailData *data = (PidginNotifyMailData *)ui_handle;
1233 if (data) {
1234 g_free(data->url);
1235 g_free(data);
1238 else if (type == PURPLE_NOTIFY_SEARCHRESULTS)
1240 PidginNotifySearchResultsData *data = (PidginNotifySearchResultsData *)ui_handle;
1242 gtk_widget_destroy(data->window);
1243 purple_notify_searchresults_free(data->results);
1245 g_free(data);
1247 else if (ui_handle != NULL)
1248 gtk_widget_destroy(GTK_WIDGET(ui_handle));
1251 #ifndef _WIN32
1252 static gboolean
1253 uri_command(GSList *arg_list, gboolean sync)
1255 gchar *tmp;
1256 GError *error = NULL;
1257 GSList *it;
1258 gchar **argv;
1259 gint argc, i;
1260 gchar *program;
1262 g_return_val_if_fail(arg_list != NULL, FALSE);
1264 program = arg_list->data;
1265 purple_debug_misc("gtknotify", "Executing %s (%s)\n", program,
1266 sync ? "sync" : "async");
1268 if (!purple_program_is_valid(program)) {
1269 purple_debug_error("gtknotify", "Command \"%s\" is invalid\n",
1270 program);
1271 tmp = g_strdup_printf(
1272 _("The browser command \"%s\" is invalid."),
1273 program ? program : "(null)");
1274 purple_notify_error(NULL, NULL, _("Unable to open URL"), tmp, NULL);
1275 g_free(tmp);
1277 return FALSE;
1280 argc = g_slist_length(arg_list);
1281 argv = g_new(gchar*, argc + 1);
1282 i = 0;
1283 for (it = arg_list; it; it = g_slist_next(it)) {
1284 if (purple_debug_is_verbose()) {
1285 purple_debug_misc("gtknotify", "argv[%d] = \"%s\"\n",
1286 i, (gchar*)it->data);
1288 argv[i++] = it->data;
1290 argv[i] = NULL;
1292 if (sync) {
1293 gint exit_status = 0;
1295 if (g_spawn_sync(NULL, argv, NULL, G_SPAWN_SEARCH_PATH |
1296 G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
1297 NULL, NULL, NULL, NULL, &exit_status, &error) &&
1298 exit_status == 0)
1300 g_free(argv);
1301 return TRUE;
1304 purple_debug_error("gtknotify",
1305 "Error launching \"%s\": %s (status: %d)\n", program,
1306 error ? error->message : "(null)", exit_status);
1307 tmp = g_strdup_printf(_("Error launching \"%s\": %s"), program,
1308 error ? error->message : "(null)");
1309 g_error_free(error);
1310 purple_notify_error(NULL, NULL, _("Unable to open URL"), tmp, NULL);
1311 g_free(tmp);
1313 g_free(argv);
1314 return FALSE;
1317 if (g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH |
1318 G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, NULL,
1319 NULL, NULL, &error))
1321 g_free(argv);
1322 return TRUE;
1325 purple_debug_warning("gtknotify", "Error launching \"%s\": %s\n",
1326 program, error ? error->message : "(null)");
1327 g_error_free(error);
1329 g_free(argv);
1330 return FALSE;
1332 #endif /* _WIN32 */
1334 static void *
1335 pidgin_notify_uri(const char *uri)
1337 #ifndef _WIN32
1338 const char *web_browser;
1339 int place;
1340 gchar *uri_escaped, *uri_custom = NULL;
1341 GSList *argv = NULL, *argv_remote = NULL;
1342 gchar **usercmd_argv = NULL;
1344 uri_escaped = purple_uri_escape_for_open(uri);
1346 web_browser = purple_prefs_get_string(PIDGIN_PREFS_ROOT
1347 "/browsers/browser");
1348 place = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/browsers/place");
1350 /* if they are running gnome, use the gnome web browser */
1351 if (purple_running_gnome() == TRUE) {
1352 char *tmp = g_find_program_in_path("xdg-open");
1353 if (tmp == NULL)
1354 argv = g_slist_append(argv, "gnome-open");
1355 else
1356 argv = g_slist_append(argv, "xdg-open");
1357 g_free(tmp);
1358 argv = g_slist_append(argv, uri_escaped);
1359 } else if (purple_running_osx() == TRUE) {
1360 argv = g_slist_append(argv, "open");
1361 argv = g_slist_append(argv, uri_escaped);
1362 } else if (purple_strequal(web_browser, "epiphany") ||
1363 purple_strequal(web_browser, "galeon"))
1365 argv = g_slist_append(argv, (gpointer)web_browser);
1367 if (place == PIDGIN_BROWSER_NEW_WINDOW)
1368 argv = g_slist_append(argv, "-w");
1369 else if (place == PIDGIN_BROWSER_NEW_TAB)
1370 argv = g_slist_append(argv, "-n");
1372 argv = g_slist_append(argv, uri_escaped);
1373 } else if (purple_strequal(web_browser, "xdg-open")) {
1374 argv = g_slist_append(argv, "xdg-open");
1375 argv = g_slist_append(argv, uri_escaped);
1376 } else if (purple_strequal(web_browser, "gnome-open")) {
1377 argv = g_slist_append(argv, "gnome-open");
1378 argv = g_slist_append(argv, uri_escaped);
1379 } else if (purple_strequal(web_browser, "kfmclient")) {
1380 argv = g_slist_append(argv, "kfmclient");
1381 argv = g_slist_append(argv, "openURL");
1382 argv = g_slist_append(argv, uri_escaped);
1384 * Does Konqueror have options to open in new tab
1385 * and/or current window?
1387 } else if (purple_strequal(web_browser, "mozilla") ||
1388 purple_strequal(web_browser, "mozilla-firebird") ||
1389 purple_strequal(web_browser, "firefox") ||
1390 purple_strequal(web_browser, "seamonkey"))
1392 argv = g_slist_append(argv, (gpointer)web_browser);
1393 argv = g_slist_append(argv, uri_escaped);
1395 g_assert(uri_custom == NULL);
1396 if (place == PIDGIN_BROWSER_NEW_WINDOW) {
1397 uri_custom = g_strdup_printf("openURL(%s,new-window)",
1398 uri_escaped);
1399 } else if (place == PIDGIN_BROWSER_NEW_TAB) {
1400 uri_custom = g_strdup_printf("openURL(%s,new-tab)",
1401 uri_escaped);
1404 if (uri_custom != NULL) {
1405 argv_remote = g_slist_append(argv_remote,
1406 (gpointer)web_browser);
1408 /* Firefox 0.9 and higher require a "-a firefox" option
1409 * when using -remote commands. This breaks older
1410 * versions of mozilla. So we include this other handly
1411 * little string when calling firefox. If the API for
1412 * remote calls changes any more in firefox then firefox
1413 * should probably be split apart from mozilla-firebird
1414 * and mozilla... but this is good for now.
1416 if (purple_strequal(web_browser, "firefox")) {
1417 argv_remote = g_slist_append(argv_remote, "-a");
1418 argv_remote = g_slist_append(argv_remote,
1419 "firefox");
1422 argv_remote = g_slist_append(argv_remote, "-remote");
1423 argv_remote = g_slist_append(argv_remote, uri_custom);
1425 } else if (purple_strequal(web_browser, "opera")) {
1426 argv = g_slist_append(argv, "opera");
1428 if (place == PIDGIN_BROWSER_NEW_WINDOW)
1429 argv = g_slist_append(argv, "-newwindow");
1430 else if (place == PIDGIN_BROWSER_NEW_TAB)
1431 argv = g_slist_append(argv, "-newpage");
1432 /* `opera -remote "openURL(%s)"` command causes Pidgin to hang,
1433 * if there is no Opera instance running.
1436 argv = g_slist_append(argv, uri_escaped);
1437 } else if (purple_strequal(web_browser, "google-chrome")) {
1438 /* Google Chrome doesn't have command-line arguments that
1439 * control the opening of links from external calls. This is
1440 * controlled solely from a preference within Google Chrome.
1442 argv = g_slist_append(argv, "google-chrome");
1443 argv = g_slist_append(argv, uri_escaped);
1444 } else if (purple_strequal(web_browser, "chrome")) {
1445 /* Chromium doesn't have command-line arguments that control
1446 * the opening of links from external calls. This is controlled
1447 * solely from a preference within Chromium.
1449 argv = g_slist_append(argv, "chrome");
1450 argv = g_slist_append(argv, uri_escaped);
1451 } else if (purple_strequal(web_browser, "chromium-browser")) {
1452 /* Chromium doesn't have command-line arguments that control the
1453 * opening of links from external calls. This is controlled
1454 * solely from a preference within Chromium.
1456 argv = g_slist_append(argv, "chromium-browser");
1457 argv = g_slist_append(argv, uri_escaped);
1458 } else if (purple_strequal(web_browser, "custom")) {
1459 GError *error = NULL;
1460 const char *usercmd_command;
1461 gint usercmd_argc, i;
1462 gboolean uri_added = FALSE;
1464 usercmd_command = purple_prefs_get_string(PIDGIN_PREFS_ROOT
1465 "/browsers/manual_command");
1467 if (usercmd_command == NULL || *usercmd_command == '\0') {
1468 purple_notify_error(NULL, NULL, _("Unable to open URL"),
1469 _("The 'Manual' browser command has been "
1470 "chosen, but no command has been set."), NULL);
1471 g_free(uri_escaped);
1472 return NULL;
1475 if (!g_shell_parse_argv(usercmd_command, &usercmd_argc,
1476 &usercmd_argv, &error))
1478 purple_notify_error(NULL, NULL, _("Unable to open URL: "
1479 "the 'Manual' browser command seems invalid."),
1480 error ? error->message : NULL, NULL);
1481 g_error_free(error);
1482 g_free(uri_escaped);
1483 return NULL;
1486 for (i = 0; i < usercmd_argc; i++) {
1487 gchar *cmd_part = usercmd_argv[i];
1489 if (uri_added || strstr(cmd_part, "%s") == NULL) {
1490 argv = g_slist_append(argv, cmd_part);
1491 continue;
1494 uri_custom = purple_strreplace(cmd_part, "%s",
1495 uri_escaped);
1496 argv = g_slist_append(argv, uri_custom);
1497 uri_added = TRUE;
1500 /* There is no "%s" in the browser command. Assume the user
1501 * wanted the URL tacked on to the end of the command.
1503 if (!uri_added)
1504 argv = g_slist_append(argv, uri_escaped);
1507 if (argv_remote != NULL) {
1508 /* try the remote command first */
1509 if (!uri_command(argv_remote, TRUE))
1510 uri_command(argv, FALSE);
1511 } else
1512 uri_command(argv, FALSE);
1514 g_strfreev(usercmd_argv);
1515 g_free(uri_escaped);
1516 g_free(uri_custom);
1517 g_slist_free(argv);
1518 g_slist_free(argv_remote);
1520 #else /* !_WIN32 */
1521 winpidgin_notify_uri(uri);
1522 #endif /* !_WIN32 */
1524 return NULL;
1527 void
1528 pidgin_notify_pounce_add(PurpleAccount *account, PurplePounce *pounce,
1529 const char *alias, const char *event, const char *message, const char *date)
1531 GdkPixbuf *icon;
1532 GtkTreeIter iter;
1533 PidginNotifyPounceData *pounce_data;
1534 gboolean first = (pounce_dialog == NULL);
1536 if (pounce_dialog == NULL)
1537 pounce_dialog = pidgin_create_notification_dialog(PIDGIN_NOTIFY_POUNCE);
1539 icon = pidgin_create_protocol_icon(account, PIDGIN_PROTOCOL_ICON_SMALL);
1541 pounce_data = g_new(PidginNotifyPounceData, 1);
1543 pounce_data->account = account;
1544 pounce_data->pounce = pounce;
1545 pounce_data->pouncee = g_strdup(purple_pounce_get_pouncee(pounce));
1547 gtk_tree_store_append(pounce_dialog->treemodel, &iter, NULL);
1549 gtk_tree_store_set(pounce_dialog->treemodel, &iter,
1550 PIDGIN_POUNCE_ICON, icon,
1551 PIDGIN_POUNCE_ALIAS, alias,
1552 PIDGIN_POUNCE_EVENT, event,
1553 PIDGIN_POUNCE_TEXT, (message != NULL)? message : _("No message"),
1554 PIDGIN_POUNCE_DATE, date,
1555 PIDGIN_POUNCE_DATA, pounce_data,
1556 -1);
1558 if (first) {
1559 GtkTreeSelection *selection =
1560 gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
1561 gtk_tree_selection_select_iter(selection, &iter);
1564 if (icon)
1565 g_object_unref(icon);
1567 gtk_widget_show_all(pounce_dialog->dialog);
1569 return;
1572 static PidginNotifyDialog *
1573 pidgin_create_notification_dialog(PidginNotifyType type)
1575 GtkTreeStore *model = NULL;
1576 GtkWidget *dialog = NULL;
1577 GtkWidget *label = NULL;
1578 GtkCellRenderer *rend;
1579 GtkTreeViewColumn *column;
1580 GtkWidget *button = NULL;
1581 GtkWidget *vbox = NULL;
1582 GtkTreeSelection *sel;
1583 PidginNotifyDialog *spec_dialog = NULL;
1585 g_return_val_if_fail(type < PIDGIN_NOTIFY_TYPES, NULL);
1587 if (type == PIDGIN_NOTIFY_MAIL) {
1588 g_return_val_if_fail(mail_dialog == NULL, mail_dialog);
1590 model = gtk_tree_store_new(COLUMNS_PIDGIN_MAIL,
1591 GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_POINTER);
1593 } else if (type == PIDGIN_NOTIFY_POUNCE) {
1594 g_return_val_if_fail(pounce_dialog == NULL, pounce_dialog);
1596 model = gtk_tree_store_new(COLUMNS_PIDGIN_POUNCE,
1597 GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
1598 G_TYPE_STRING, G_TYPE_POINTER);
1601 dialog = gtk_dialog_new();
1603 /* Vertical box */
1604 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1606 /* Setup the dialog */
1607 gtk_container_set_border_width(GTK_CONTAINER(dialog), PIDGIN_HIG_BOX_SPACE);
1608 gtk_container_set_border_width(GTK_CONTAINER(vbox), PIDGIN_HIG_BOX_SPACE);
1609 gtk_box_set_spacing(GTK_BOX(vbox), PIDGIN_HIG_BORDER);
1611 /* Golden ratio it up! */
1612 gtk_widget_set_size_request(dialog, 550, 400);
1614 spec_dialog = g_new0(PidginNotifyDialog, 1);
1615 spec_dialog->dialog = dialog;
1617 spec_dialog->treemodel = model;
1618 spec_dialog->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1619 g_object_unref(G_OBJECT(model));
1621 if (type == PIDGIN_NOTIFY_MAIL) {
1622 gtk_window_set_title(GTK_WINDOW(dialog), _("New Mail"));
1623 gtk_window_set_role(GTK_WINDOW(dialog), "new_mail_detailed");
1624 g_signal_connect(G_OBJECT(dialog), "focus-in-event",
1625 G_CALLBACK(mail_window_focus_cb), NULL);
1627 gtk_dialog_add_button(GTK_DIALOG(dialog),
1628 _("Open All Messages"), GTK_RESPONSE_ACCEPT);
1630 button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1631 PIDGIN_STOCK_OPEN_MAIL, GTK_RESPONSE_YES);
1632 spec_dialog->open_button = button;
1634 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(spec_dialog->treeview), FALSE);
1636 gtk_tree_view_set_search_column(GTK_TREE_VIEW(spec_dialog->treeview), PIDGIN_MAIL_TEXT);
1637 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(spec_dialog->treeview),
1638 pidgin_tree_view_search_equal_func, NULL, NULL);
1639 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(spec_dialog->treeview));
1640 gtk_tree_selection_set_mode(sel, GTK_SELECTION_BROWSE);
1642 g_signal_connect(G_OBJECT(dialog), "response",
1643 G_CALLBACK(email_response_cb), spec_dialog);
1644 g_signal_connect(G_OBJECT(sel), "changed",
1645 G_CALLBACK(selection_changed_cb), spec_dialog);
1646 g_signal_connect(G_OBJECT(spec_dialog->treeview), "row-activated", G_CALLBACK(email_row_activated_cb), NULL);
1648 column = gtk_tree_view_column_new();
1649 gtk_tree_view_column_set_resizable(column, TRUE);
1650 rend = gtk_cell_renderer_pixbuf_new();
1651 gtk_tree_view_column_pack_start(column, rend, FALSE);
1653 gtk_tree_view_column_set_attributes(column, rend, "pixbuf", PIDGIN_MAIL_ICON, NULL);
1654 rend = gtk_cell_renderer_text_new();
1655 gtk_tree_view_column_pack_start(column, rend, TRUE);
1656 gtk_tree_view_column_set_attributes(column, rend, "markup", PIDGIN_MAIL_TEXT, NULL);
1657 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1659 label = gtk_label_new(NULL);
1660 gtk_label_set_markup(GTK_LABEL(label), _("<span weight=\"bold\" size=\"larger\">You have mail!</span>"));
1662 } else if (type == PIDGIN_NOTIFY_POUNCE) {
1663 gtk_window_set_title(GTK_WINDOW(dialog), _("New Pounces"));
1665 button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1666 _("IM"), GTK_RESPONSE_YES);
1667 gtk_widget_set_sensitive(button, FALSE);
1668 spec_dialog->open_button = button;
1670 button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1671 PIDGIN_STOCK_MODIFY, GTK_RESPONSE_APPLY);
1672 gtk_widget_set_sensitive(button, FALSE);
1673 spec_dialog->edit_button = button;
1675 /* Translators: Make sure you translate "Dismiss" differently than
1676 "close"! This string is used in the "You have pounced" dialog
1677 that appears when one of your Buddy Pounces is triggered. In
1678 this context "Dismiss" means "I acknowledge that I've seen that
1679 this pounce was triggered--remove it from this list." Translating
1680 it as "Remove" is acceptable if you can't think of a more precise
1681 word. */
1682 button = gtk_dialog_add_button(GTK_DIALOG(dialog), _("Dismiss"),
1683 GTK_RESPONSE_NO);
1684 gtk_widget_set_sensitive(button, FALSE);
1685 spec_dialog->dismiss_button = button;
1687 g_signal_connect(G_OBJECT(dialog), "response",
1688 G_CALLBACK(pounce_response_cb), spec_dialog);
1690 column = gtk_tree_view_column_new();
1691 gtk_tree_view_column_set_title(column, _("Buddy"));
1692 gtk_tree_view_column_set_resizable(column, TRUE);
1693 rend = gtk_cell_renderer_pixbuf_new();
1694 gtk_tree_view_column_pack_start(column, rend, FALSE);
1696 gtk_tree_view_column_set_attributes(column, rend, "pixbuf", PIDGIN_POUNCE_ICON, NULL);
1697 rend = gtk_cell_renderer_text_new();
1698 gtk_tree_view_column_pack_start(column, rend, FALSE);
1699 gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_ALIAS);
1700 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1702 column = gtk_tree_view_column_new();
1703 gtk_tree_view_column_set_title(column, _("Event"));
1704 gtk_tree_view_column_set_resizable(column, TRUE);
1705 rend = gtk_cell_renderer_text_new();
1706 gtk_tree_view_column_pack_start(column, rend, FALSE);
1707 gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_EVENT);
1708 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1710 column = gtk_tree_view_column_new();
1711 gtk_tree_view_column_set_title(column, _("Message"));
1712 gtk_tree_view_column_set_resizable(column, TRUE);
1713 rend = gtk_cell_renderer_text_new();
1714 gtk_tree_view_column_pack_start(column, rend, FALSE);
1715 gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_TEXT);
1716 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1718 column = gtk_tree_view_column_new();
1719 gtk_tree_view_column_set_title(column, _("Date"));
1720 gtk_tree_view_column_set_resizable(column, TRUE);
1721 rend = gtk_cell_renderer_text_new();
1722 gtk_tree_view_column_pack_start(column, rend, FALSE);
1723 gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_DATE);
1724 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1726 label = gtk_label_new(NULL);
1727 gtk_label_set_markup(GTK_LABEL(label), _("<span weight=\"bold\" size=\"larger\">You have pounced!</span>"));
1729 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(spec_dialog->treeview));
1730 gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
1731 g_signal_connect(G_OBJECT(sel), "changed",
1732 G_CALLBACK(pounce_row_selected_cb), NULL);
1733 g_signal_connect(G_OBJECT(spec_dialog->treeview), "row-activated",
1734 G_CALLBACK(pounce_response_open_ims), NULL);
1737 gtk_dialog_add_button(GTK_DIALOG(dialog),
1738 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
1740 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1741 gtk_label_set_xalign(GTK_LABEL(label), 0);
1742 gtk_label_set_yalign(GTK_LABEL(label), 0);
1743 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1744 gtk_box_pack_start(GTK_BOX(vbox),
1745 pidgin_make_scrollable(spec_dialog->treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1),
1746 TRUE, TRUE, 2);
1748 return spec_dialog;
1751 static void
1752 signed_off_cb(PurpleConnection *gc, gpointer unused)
1754 /* Clear any pending emails for this account */
1755 pidgin_notify_emails(gc, 0, FALSE, NULL, NULL, NULL, NULL);
1757 if (mail_dialog != NULL && mail_dialog->total_count == 0)
1758 reset_mail_dialog(NULL);
1761 static void*
1762 pidgin_notify_get_handle(void)
1764 static int handle;
1765 return &handle;
1768 void pidgin_notify_init(void)
1770 void *handle = pidgin_notify_get_handle();
1772 purple_signal_connect(purple_connections_get_handle(), "signed-off",
1773 handle, PURPLE_CALLBACK(signed_off_cb), NULL);
1776 void pidgin_notify_uninit(void)
1778 purple_signals_disconnect_by_handle(pidgin_notify_get_handle());
1781 static PurpleNotifyUiOps ops =
1783 pidgin_notify_message,
1784 pidgin_notify_email,
1785 pidgin_notify_emails,
1786 pidgin_notify_formatted,
1787 pidgin_notify_searchresults,
1788 pidgin_notify_searchresults_new_rows,
1789 pidgin_notify_userinfo,
1790 pidgin_notify_uri,
1791 pidgin_close_notify,
1792 NULL,
1793 NULL,
1794 NULL,
1795 NULL
1798 PurpleNotifyUiOps *
1799 pidgin_notify_get_ui_ops(void)
1801 return &ops;