Branch for 2.7.7.
[pidgin-git.git] / pidgin / gtknotify.c
blob83884fbbb1f7e1e93be62dd11077bea73e5dbf03
1 /**
2 * @file gtknotify.c GTK+ Notification API
3 * @ingroup pidgin
4 */
6 /* pidgin
8 * Pidgin is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
26 #include "internal.h"
27 #include "pidgin.h"
29 #include <gdk/gdkkeysyms.h>
31 #include "account.h"
32 #include "connection.h"
33 #include "debug.h"
34 #include "prefs.h"
35 #include "pidginstock.h"
36 #include "util.h"
38 #include "gtkblist.h"
39 #include "gtkimhtml.h"
40 #include "gtknotify.h"
41 #include "gtkpounce.h"
42 #include "gtkutils.h"
44 typedef struct
46 GtkWidget *window;
47 int count;
48 } PidginUserInfo;
50 typedef struct
52 PurpleAccount *account;
53 char *url;
54 GtkWidget *label;
55 int count;
56 gboolean purple_has_handle;
57 } PidginNotifyMailData;
59 typedef struct
61 PurpleAccount *account;
62 PurplePounce *pounce;
63 char *pouncee;
64 } PidginNotifyPounceData;
67 typedef struct
69 PurpleAccount *account;
70 GtkListStore *model;
71 GtkWidget *treeview;
72 GtkWidget *window;
73 gpointer user_data;
74 PurpleNotifySearchResults *results;
76 } PidginNotifySearchResultsData;
78 typedef struct
80 PurpleNotifySearchButton *button;
81 PidginNotifySearchResultsData *data;
83 } PidginNotifySearchResultsButtonData;
85 enum
87 PIDGIN_MAIL_ICON,
88 PIDGIN_MAIL_TEXT,
89 PIDGIN_MAIL_DATA,
90 COLUMNS_PIDGIN_MAIL
93 enum
95 PIDGIN_POUNCE_ICON,
96 PIDGIN_POUNCE_ALIAS,
97 PIDGIN_POUNCE_EVENT,
98 PIDGIN_POUNCE_TEXT,
99 PIDGIN_POUNCE_DATE,
100 PIDGIN_POUNCE_DATA,
101 COLUMNS_PIDGIN_POUNCE
105 typedef struct _PidginNotifyDialog
108 * This must be first so PidginNotifyDialog can masquerade as the
109 * dialog widget.
111 GtkWidget *dialog;
112 GtkWidget *treeview;
113 GtkTreeStore *treemodel;
114 GtkLabel *label;
115 GtkWidget *open_button;
116 GtkWidget *dismiss_button;
117 GtkWidget *edit_button;
118 int total_count;
119 gboolean in_use;
120 } PidginNotifyDialog;
122 typedef enum
124 PIDGIN_NOTIFY_MAIL,
125 PIDGIN_NOTIFY_POUNCE,
126 PIDGIN_NOTIFY_TYPES
127 } PidginNotifyType;
129 static PidginNotifyDialog *mail_dialog = NULL;
130 static PidginNotifyDialog *pounce_dialog = NULL;
132 static PidginNotifyDialog *pidgin_create_notification_dialog(PidginNotifyType type);
133 static void *pidgin_notify_emails(PurpleConnection *gc, size_t count, gboolean detailed,
134 const char **subjects,
135 const char **froms, const char **tos,
136 const char **urls);
138 static void pidgin_close_notify(PurpleNotifyType type, void *ui_handle);
140 static void
141 message_response_cb(GtkDialog *dialog, gint id, GtkWidget *widget)
143 purple_notify_close(PURPLE_NOTIFY_MESSAGE, widget);
146 static void
147 pounce_response_close(PidginNotifyDialog *dialog)
149 GtkTreeIter iter;
150 PidginNotifyPounceData *pounce_data;
152 while (gtk_tree_model_get_iter_first(
153 GTK_TREE_MODEL(pounce_dialog->treemodel), &iter)) {
154 gtk_tree_model_get(GTK_TREE_MODEL(pounce_dialog->treemodel), &iter,
155 PIDGIN_POUNCE_DATA, &pounce_data,
156 -1);
157 gtk_tree_store_remove(dialog->treemodel, &iter);
159 g_free(pounce_data->pouncee);
160 g_free(pounce_data);
163 gtk_widget_destroy(pounce_dialog->dialog);
164 g_free(pounce_dialog);
165 pounce_dialog = NULL;
168 static void
169 delete_foreach(GtkTreeModel *model, GtkTreePath *path,
170 GtkTreeIter *iter, gpointer data)
172 PidginNotifyPounceData *pounce_data;
174 gtk_tree_model_get(model, iter,
175 PIDGIN_POUNCE_DATA, &pounce_data,
176 -1);
178 if (pounce_data != NULL) {
179 g_free(pounce_data->pouncee);
180 g_free(pounce_data);
184 static void
185 open_im_foreach(GtkTreeModel *model, GtkTreePath *path,
186 GtkTreeIter *iter, gpointer data)
188 PidginNotifyPounceData *pounce_data;
190 gtk_tree_model_get(model, iter,
191 PIDGIN_POUNCE_DATA, &pounce_data,
192 -1);
194 if (pounce_data != NULL) {
195 PurpleConversation *conv;
197 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM,
198 pounce_data->account, pounce_data->pouncee);
199 purple_conversation_present(conv);
203 static void
204 append_to_list(GtkTreeModel *model, GtkTreePath *path,
205 GtkTreeIter *iter, gpointer data)
207 GList **list = data;
208 *list = g_list_prepend(*list, gtk_tree_path_copy(path));
211 static void
212 pounce_response_dismiss()
214 GtkTreeModel *model = GTK_TREE_MODEL(pounce_dialog->treemodel);
215 GtkTreeSelection *selection;
216 GtkTreeIter iter;
217 GtkTreeIter new_selection;
218 GList *list = NULL;
219 gboolean found_selection = FALSE;
221 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
222 gtk_tree_selection_selected_foreach(selection, delete_foreach, pounce_dialog);
223 gtk_tree_selection_selected_foreach(selection, append_to_list, &list);
225 g_return_if_fail(list != NULL);
227 if (list->next == NULL) {
228 gtk_tree_model_get_iter(model, &new_selection, list->data);
229 if (gtk_tree_model_iter_next(model, &new_selection))
230 found_selection = TRUE;
231 else {
232 /* This is the last thing in the list */
233 GtkTreePath *path;
235 /* Because gtk_tree_model_iter_prev doesn't exist... */
236 gtk_tree_model_get_iter(model, &new_selection, list->data);
237 path = gtk_tree_model_get_path(model, &new_selection);
238 if (gtk_tree_path_prev(path)) {
239 gtk_tree_model_get_iter(model, &new_selection, path);
240 found_selection = TRUE;
243 gtk_tree_path_free(path);
247 while (list) {
248 if (gtk_tree_model_get_iter(model, &iter, list->data)) {
249 gtk_tree_store_remove(GTK_TREE_STORE(pounce_dialog->treemodel), &iter);
251 gtk_tree_path_free(list->data);
252 list = g_list_delete_link(list, list);
255 if (gtk_tree_model_get_iter_first(model, &iter)) {
256 if (found_selection)
257 gtk_tree_selection_select_iter(selection, &new_selection);
258 else
259 gtk_tree_selection_select_iter(selection, &iter);
260 } else
261 pounce_response_close(pounce_dialog);
264 static void
265 pounce_response_open_ims()
267 GtkTreeSelection *selection;
269 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
270 gtk_tree_selection_selected_foreach(selection, open_im_foreach, pounce_dialog);
272 pounce_response_dismiss();
275 static void
276 pounce_response_edit_cb(GtkTreeModel *model, GtkTreePath *path,
277 GtkTreeIter *iter, gpointer data)
279 PidginNotifyPounceData *pounce_data;
280 PidginNotifyDialog *dialog = (PidginNotifyDialog*)data;
281 PurplePounce *pounce;
282 GList *list;
284 list = purple_pounces_get_all();
286 gtk_tree_model_get(GTK_TREE_MODEL(dialog->treemodel), iter,
287 PIDGIN_POUNCE_DATA, &pounce_data,
288 -1);
290 for (; list != NULL; list = list->next) {
291 pounce = list->data;
292 if (pounce == pounce_data->pounce) {
293 pidgin_pounce_editor_show(pounce_data->account, NULL, pounce_data->pounce);
294 return;
298 purple_debug_warning("gtknotify", "Pounce was destroyed.\n");
301 static void
302 pounce_response_cb(GtkDialog *dlg, gint id, PidginNotifyDialog *dialog)
304 GtkTreeSelection *selection = NULL;
306 switch (id) {
307 case GTK_RESPONSE_CLOSE:
308 case GTK_RESPONSE_DELETE_EVENT:
309 pounce_response_close(dialog);
310 break;
311 case GTK_RESPONSE_YES:
312 pounce_response_open_ims();
313 break;
314 case GTK_RESPONSE_NO:
315 pounce_response_dismiss();
316 break;
317 case GTK_RESPONSE_APPLY:
318 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
319 gtk_tree_selection_selected_foreach(selection, pounce_response_edit_cb,
320 dialog);
321 break;
325 static void
326 pounce_row_selected_cb(GtkTreeView *tv, GtkTreePath *path,
327 GtkTreeViewColumn *col, gpointer data)
329 GtkTreeSelection *selection;
330 int count;
332 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
334 count = gtk_tree_selection_count_selected_rows(selection);
336 if (count == 0) {
337 gtk_widget_set_sensitive(pounce_dialog->open_button, FALSE);
338 gtk_widget_set_sensitive(pounce_dialog->edit_button, FALSE);
339 gtk_widget_set_sensitive(pounce_dialog->dismiss_button, FALSE);
340 } else if (count == 1) {
341 GList *pounces;
342 GList *list;
343 PidginNotifyPounceData *pounce_data;
344 GtkTreeIter iter;
346 list = gtk_tree_selection_get_selected_rows(selection, NULL);
347 gtk_tree_model_get_iter(GTK_TREE_MODEL(pounce_dialog->treemodel),
348 &iter, list->data);
349 gtk_tree_model_get(GTK_TREE_MODEL(pounce_dialog->treemodel), &iter,
350 PIDGIN_POUNCE_DATA, &pounce_data,
351 -1);
352 g_list_foreach(list, (GFunc)gtk_tree_path_free, NULL);
353 g_list_free(list);
355 pounces = purple_pounces_get_all();
356 for (; pounces != NULL; pounces = pounces->next) {
357 PurplePounce *pounce = pounces->data;
358 if (pounce == pounce_data->pounce) {
359 gtk_widget_set_sensitive(pounce_dialog->edit_button, TRUE);
360 break;
364 gtk_widget_set_sensitive(pounce_dialog->open_button, TRUE);
365 gtk_widget_set_sensitive(pounce_dialog->dismiss_button, TRUE);
366 } else {
367 gtk_widget_set_sensitive(pounce_dialog->open_button, TRUE);
368 gtk_widget_set_sensitive(pounce_dialog->edit_button, FALSE);
369 gtk_widget_set_sensitive(pounce_dialog->dismiss_button, TRUE);
375 static void
376 reset_mail_dialog(GtkDialog *unused)
378 if (mail_dialog->in_use)
379 return;
380 gtk_widget_destroy(mail_dialog->dialog);
381 g_free(mail_dialog);
382 mail_dialog = NULL;
385 static void
386 email_response_cb(GtkDialog *unused, gint id, PidginNotifyDialog *unused2)
388 PidginNotifyMailData *data = NULL;
389 GtkTreeModel *model = GTK_TREE_MODEL(mail_dialog->treemodel);
390 GtkTreeIter iter;
392 if (id == GTK_RESPONSE_YES)
394 /* A single row activated. Remove that row. */
395 GtkTreeSelection *selection;
397 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(mail_dialog->treeview));
399 if (gtk_tree_selection_get_selected(selection, NULL, &iter))
401 gtk_tree_model_get(model, &iter, PIDGIN_MAIL_DATA, &data, -1);
402 purple_notify_uri(NULL, data->url);
404 gtk_tree_store_remove(mail_dialog->treemodel, &iter);
405 if (data->purple_has_handle)
406 purple_notify_close(PURPLE_NOTIFY_EMAILS, data);
407 else
408 pidgin_close_notify(PURPLE_NOTIFY_EMAILS, data);
410 if (gtk_tree_model_get_iter_first(model, &iter))
411 return;
413 else
414 return;
416 else
418 /* Remove all the rows */
419 while (gtk_tree_model_get_iter_first(model, &iter))
421 gtk_tree_model_get(model, &iter, PIDGIN_MAIL_DATA, &data, -1);
423 if (id == GTK_RESPONSE_ACCEPT)
424 purple_notify_uri(NULL, data->url);
426 gtk_tree_store_remove(mail_dialog->treemodel, &iter);
427 if (data->purple_has_handle)
428 purple_notify_close(PURPLE_NOTIFY_EMAILS, data);
429 else
430 pidgin_close_notify(PURPLE_NOTIFY_EMAILS, data);
434 reset_mail_dialog(NULL);
437 static void
438 email_row_activated_cb(GtkTreeView *tv, GtkTreePath *path,
439 GtkTreeViewColumn *col, gpointer data)
441 email_response_cb(NULL, GTK_RESPONSE_YES, NULL);
444 static gboolean
445 formatted_close_cb(GtkWidget *win, GdkEvent *event, void *user_data)
447 purple_notify_close(PURPLE_NOTIFY_FORMATTED, win);
448 return FALSE;
451 static gboolean
452 searchresults_close_cb(PidginNotifySearchResultsData *data, GdkEvent *event, gpointer user_data)
454 purple_notify_close(PURPLE_NOTIFY_SEARCHRESULTS, data);
455 return FALSE;
458 static void
459 searchresults_callback_wrapper_cb(GtkWidget *widget, PidginNotifySearchResultsButtonData *bd)
461 PidginNotifySearchResultsData *data = bd->data;
463 GtkTreeSelection *selection;
464 GtkTreeModel *model;
465 GtkTreeIter iter;
466 PurpleNotifySearchButton *button;
467 GList *row = NULL;
468 gchar *str;
469 int i;
471 g_return_if_fail(data != NULL);
473 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(data->treeview));
475 if (gtk_tree_selection_get_selected(selection, &model, &iter))
477 for (i = 1; i < gtk_tree_model_get_n_columns(GTK_TREE_MODEL(model)); i++) {
478 gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, i, &str, -1);
479 row = g_list_append(row, str);
483 button = bd->button;
484 button->callback(purple_account_get_connection(data->account), row, data->user_data);
485 g_list_foreach(row, (GFunc)g_free, NULL);
486 g_list_free(row);
489 static void *
490 pidgin_notify_message(PurpleNotifyMsgType type, const char *title,
491 const char *primary, const char *secondary)
493 GtkWidget *dialog;
494 GtkWidget *hbox;
495 GtkWidget *label;
496 GtkWidget *img = NULL;
497 char label_text[2048];
498 const char *icon_name = NULL;
499 char *primary_esc, *secondary_esc;
501 switch (type)
503 case PURPLE_NOTIFY_MSG_ERROR:
504 icon_name = PIDGIN_STOCK_DIALOG_ERROR;
505 break;
507 case PURPLE_NOTIFY_MSG_WARNING:
508 icon_name = PIDGIN_STOCK_DIALOG_WARNING;
509 break;
511 case PURPLE_NOTIFY_MSG_INFO:
512 icon_name = PIDGIN_STOCK_DIALOG_INFO;
513 break;
515 default:
516 icon_name = NULL;
517 break;
520 if (icon_name != NULL)
522 img = gtk_image_new_from_stock(icon_name, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
523 gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
526 dialog = gtk_dialog_new_with_buttons(title ? title : PIDGIN_ALERT_TITLE,
527 NULL, 0, GTK_STOCK_CLOSE,
528 GTK_RESPONSE_CLOSE, NULL);
530 gtk_window_set_role(GTK_WINDOW(dialog), "notify_dialog");
532 g_signal_connect(G_OBJECT(dialog), "response",
533 G_CALLBACK(message_response_cb), dialog);
535 gtk_container_set_border_width(GTK_CONTAINER(dialog), PIDGIN_HIG_BORDER);
536 gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
537 gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
538 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BORDER);
539 gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BOX_SPACE);
541 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
542 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox);
544 if (img != NULL)
545 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
547 primary_esc = g_markup_escape_text(primary, -1);
548 secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
549 g_snprintf(label_text, sizeof(label_text),
550 "<span weight=\"bold\" size=\"larger\">%s</span>%s%s",
551 primary_esc, (secondary ? "\n\n" : ""),
552 (secondary ? secondary_esc : ""));
553 g_free(primary_esc);
554 g_free(secondary_esc);
556 label = gtk_label_new(NULL);
558 gtk_label_set_markup(GTK_LABEL(label), label_text);
559 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
560 gtk_label_set_selectable(GTK_LABEL(label), TRUE);
561 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
562 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
564 pidgin_auto_parent_window(dialog);
566 gtk_widget_show_all(dialog);
568 return dialog;
571 static void
572 selection_changed_cb(GtkTreeSelection *sel, PidginNotifyDialog *dialog)
574 GtkTreeIter iter;
575 GtkTreeModel *model;
576 PidginNotifyMailData *data;
577 gboolean active = TRUE;
579 if (gtk_tree_selection_get_selected(sel, &model, &iter) == FALSE)
580 active = FALSE;
581 else
583 gtk_tree_model_get(model, &iter, PIDGIN_MAIL_DATA, &data, -1);
584 if (data->url == NULL)
585 active = FALSE;
588 gtk_widget_set_sensitive(dialog->open_button, active);
591 static void *
592 pidgin_notify_email(PurpleConnection *gc, const char *subject, const char *from,
593 const char *to, const char *url)
595 return pidgin_notify_emails(gc, 1, (subject != NULL),
596 (subject == NULL ? NULL : &subject),
597 (from == NULL ? NULL : &from),
598 (to == NULL ? NULL : &to),
599 (url == NULL ? NULL : &url));
602 static int
603 mail_window_focus_cb(GtkWidget *widget, GdkEventFocus *focus, gpointer null)
605 pidgin_set_urgent(GTK_WINDOW(widget), FALSE);
606 return 0;
609 /* count == 0 means this is a detailed mail notification.
610 * count > 0 mean non-detailed.
612 static void *
613 pidgin_notify_add_mail(GtkTreeStore *treemodel, PurpleAccount *account, char *notification, const char *url, int count, gboolean clear, gboolean *new_data)
615 PidginNotifyMailData *data = NULL;
616 GtkTreeIter iter;
617 GdkPixbuf *icon;
618 gboolean new_n = TRUE;
620 if (count > 0 || clear) {
621 /* Allow only one non-detailed email notification for each account */
622 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(treemodel), &iter)) {
623 gboolean advanced;
624 do {
625 advanced = FALSE;
626 gtk_tree_model_get(GTK_TREE_MODEL(treemodel), &iter,
627 PIDGIN_MAIL_DATA, &data, -1);
628 if (data && data->account == account) {
629 if (clear) {
630 advanced = gtk_tree_store_remove(treemodel, &iter);
631 mail_dialog->total_count -= data->count;
633 if (data->purple_has_handle)
634 purple_notify_close(PURPLE_NOTIFY_EMAILS, data);
635 else
636 pidgin_close_notify(PURPLE_NOTIFY_EMAILS, data);
637 /* We're completely done if we've processed all entries */
638 if (!advanced)
639 return NULL;
640 } else if (data->count > 0) {
641 new_n = FALSE;
642 g_free(data->url);
643 data->url = NULL;
644 mail_dialog->total_count -= data->count;
645 break;
648 } while (advanced || gtk_tree_model_iter_next(GTK_TREE_MODEL(treemodel), &iter));
652 if (clear)
653 return NULL;
655 icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_MEDIUM);
657 if (new_n) {
658 data = g_new0(PidginNotifyMailData, 1);
659 data->purple_has_handle = TRUE;
660 gtk_tree_store_append(treemodel, &iter, NULL);
663 if (url != NULL)
664 data->url = g_strdup(url);
666 gtk_tree_store_set(treemodel, &iter,
667 PIDGIN_MAIL_ICON, icon,
668 PIDGIN_MAIL_TEXT, notification,
669 PIDGIN_MAIL_DATA, data,
670 -1);
671 data->account = account;
672 /* count == 0 indicates we're adding a single detailed e-mail */
673 data->count = count > 0 ? count : 1;
675 if (icon)
676 g_object_unref(icon);
678 if (new_data)
679 *new_data = new_n;
680 return data;
683 static void *
684 pidgin_notify_emails(PurpleConnection *gc, size_t count, gboolean detailed,
685 const char **subjects, const char **froms,
686 const char **tos, const char **urls)
688 char *notification;
689 PurpleAccount *account;
690 PidginNotifyMailData *data = NULL, *data2;
691 gboolean new_data = FALSE;
693 /* Don't bother updating if there aren't new emails and we don't have any displayed currently */
694 if (count == 0 && mail_dialog == NULL)
695 return NULL;
697 account = purple_connection_get_account(gc);
698 if (mail_dialog == NULL)
699 mail_dialog = pidgin_create_notification_dialog(PIDGIN_NOTIFY_MAIL);
701 mail_dialog->total_count += count;
702 if (detailed) {
703 for ( ; count; --count) {
704 char *to_text = NULL;
705 char *from_text = NULL;
706 char *subject_text = NULL;
707 char *tmp;
708 gboolean first = TRUE;
710 if (tos != NULL) {
711 tmp = g_markup_escape_text(*tos, -1);
712 to_text = g_strdup_printf("<b>%s</b>: %s\n", _("Account"), tmp);
713 g_free(tmp);
714 first = FALSE;
715 tos++;
717 if (froms != NULL) {
718 tmp = g_markup_escape_text(*froms, -1);
719 from_text = g_strdup_printf("%s<b>%s</b>: %s\n", first ? "<br>" : "", _("Sender"), tmp);
720 g_free(tmp);
721 first = FALSE;
722 froms++;
724 if (subjects != NULL) {
725 tmp = g_markup_escape_text(*subjects, -1);
726 subject_text = g_strdup_printf("%s<b>%s</b>: %s", first ? "<br>" : "", _("Subject"), tmp);
727 g_free(tmp);
728 first = FALSE;
729 subjects++;
731 #define SAFE(x) ((x) ? (x) : "")
732 notification = g_strdup_printf("%s%s%s", SAFE(to_text), SAFE(from_text), SAFE(subject_text));
733 #undef SAFE
734 g_free(to_text);
735 g_free(from_text);
736 g_free(subject_text);
738 /* If we don't keep track of this, will leak "data" for each of the notifications except the last */
739 data2 = pidgin_notify_add_mail(mail_dialog->treemodel, account, notification, urls ? *urls : NULL, 0, FALSE, &new_data);
740 if (data2 && new_data) {
741 if (data)
742 data->purple_has_handle = FALSE;
743 data = data2;
745 g_free(notification);
747 if (urls != NULL)
748 urls++;
750 } else {
751 if (count > 0) {
752 notification = g_strdup_printf(ngettext("%s has %d new message.",
753 "%s has %d new messages.",
754 (int)count),
755 *tos, (int)count);
756 data2 = pidgin_notify_add_mail(mail_dialog->treemodel, account, notification, urls ? *urls : NULL, count, FALSE, &new_data);
757 if (data2 && new_data) {
758 if (data)
759 data->purple_has_handle = FALSE;
760 data = data2;
762 g_free(notification);
763 } else {
764 /* Clear out all mails for the account */
765 pidgin_notify_add_mail(mail_dialog->treemodel, account, NULL, NULL, 0, TRUE, NULL);
767 if (mail_dialog->total_count == 0) {
769 * There is no API to clear the headline specifically
770 * This will trigger reset_mail_dialog()
772 pidgin_blist_set_headline(NULL, NULL, NULL, NULL, NULL);
773 return NULL;
778 if (!GTK_WIDGET_VISIBLE(mail_dialog->dialog)) {
779 GdkPixbuf *pixbuf = gtk_widget_render_icon(mail_dialog->dialog, PIDGIN_STOCK_DIALOG_MAIL,
780 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL), NULL);
781 char *label_text = g_strdup_printf(ngettext("<b>%d new email.</b>",
782 "<b>%d new emails.</b>",
783 mail_dialog->total_count), mail_dialog->total_count);
784 mail_dialog->in_use = TRUE; /* So that _set_headline doesn't accidentally
785 remove the notifications when replacing an
786 old notification. */
787 pidgin_blist_set_headline(label_text,
788 pixbuf, G_CALLBACK(gtk_widget_show_all), mail_dialog->dialog,
789 (GDestroyNotify)reset_mail_dialog);
790 mail_dialog->in_use = FALSE;
791 g_free(label_text);
792 if (pixbuf)
793 g_object_unref(pixbuf);
794 } else if (!GTK_WIDGET_HAS_FOCUS(mail_dialog->dialog))
795 pidgin_set_urgent(GTK_WINDOW(mail_dialog->dialog), TRUE);
797 return data;
800 static gboolean
801 formatted_input_cb(GtkWidget *win, GdkEventKey *event, gpointer data)
803 if (event->keyval == GDK_Escape)
805 purple_notify_close(PURPLE_NOTIFY_FORMATTED, win);
807 return TRUE;
810 return FALSE;
813 static GtkIMHtmlOptions
814 notify_imhtml_options(void)
816 GtkIMHtmlOptions options = 0;
818 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/show_incoming_formatting"))
819 options |= GTK_IMHTML_NO_COLOURS | GTK_IMHTML_NO_FONTS | GTK_IMHTML_NO_SIZES;
821 options |= GTK_IMHTML_NO_COMMENTS;
822 options |= GTK_IMHTML_NO_TITLE;
823 options |= GTK_IMHTML_NO_NEWLINE;
824 options |= GTK_IMHTML_NO_SCROLL;
825 return options;
828 static void *
829 pidgin_notify_formatted(const char *title, const char *primary,
830 const char *secondary, const char *text)
832 GtkWidget *window;
833 GtkWidget *vbox;
834 GtkWidget *label;
835 GtkWidget *button;
836 GtkWidget *imhtml;
837 GtkWidget *frame;
838 char label_text[2048];
839 char *linked_text, *primary_esc, *secondary_esc;
841 window = gtk_dialog_new();
842 gtk_window_set_title(GTK_WINDOW(window), title);
843 gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER);
844 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
846 g_signal_connect(G_OBJECT(window), "delete_event",
847 G_CALLBACK(formatted_close_cb), NULL);
849 /* Setup the main vbox */
850 vbox = GTK_DIALOG(window)->vbox;
852 /* Setup the descriptive label */
853 primary_esc = g_markup_escape_text(primary, -1);
854 secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
855 g_snprintf(label_text, sizeof(label_text),
856 "<span weight=\"bold\" size=\"larger\">%s</span>%s%s",
857 primary_esc,
858 (secondary ? "\n" : ""),
859 (secondary ? secondary_esc : ""));
860 g_free(primary_esc);
861 g_free(secondary_esc);
863 label = gtk_label_new(NULL);
865 gtk_label_set_markup(GTK_LABEL(label), label_text);
866 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
867 gtk_label_set_selectable(GTK_LABEL(label), TRUE);
868 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
869 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
870 gtk_widget_show(label);
872 /* Add the imhtml */
873 frame = pidgin_create_imhtml(FALSE, &imhtml, NULL, NULL);
874 gtk_widget_set_name(imhtml, "pidgin_notify_imhtml");
875 gtk_imhtml_set_format_functions(GTK_IMHTML(imhtml),
876 gtk_imhtml_get_format_functions(GTK_IMHTML(imhtml)) | GTK_IMHTML_IMAGE);
877 gtk_widget_set_size_request(imhtml, 300, 250);
878 gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
879 gtk_widget_show(frame);
881 /* Add the Close button. */
882 button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
883 gtk_widget_grab_focus(button);
885 g_signal_connect_swapped(G_OBJECT(button), "clicked",
886 G_CALLBACK(formatted_close_cb), window);
887 g_signal_connect(G_OBJECT(window), "key_press_event",
888 G_CALLBACK(formatted_input_cb), NULL);
890 /* Make sure URLs are clickable */
891 linked_text = purple_markup_linkify(text);
892 gtk_imhtml_append_text(GTK_IMHTML(imhtml), linked_text, notify_imhtml_options());
893 g_free(linked_text);
895 g_object_set_data(G_OBJECT(window), "info-widget", imhtml);
897 /* Show the window */
898 pidgin_auto_parent_window(window);
900 gtk_widget_show(window);
902 return window;
905 static void
906 pidgin_notify_searchresults_new_rows(PurpleConnection *gc, PurpleNotifySearchResults *results,
907 void *data_)
909 PidginNotifySearchResultsData *data = data_;
910 GtkListStore *model = data->model;
911 GtkTreeIter iter;
912 GdkPixbuf *pixbuf;
913 GList *row, *column;
914 guint n;
916 gtk_list_store_clear(data->model);
918 pixbuf = pidgin_create_prpl_icon(purple_connection_get_account(gc), 0.5);
920 for (row = results->rows; row != NULL; row = row->next) {
922 gtk_list_store_append(model, &iter);
923 gtk_list_store_set(model, &iter, 0, pixbuf, -1);
925 n = 1;
926 for (column = row->data; column != NULL; column = column->next) {
927 GValue v;
929 v.g_type = 0;
930 g_value_init(&v, G_TYPE_STRING);
931 g_value_set_string(&v, column->data);
932 gtk_list_store_set_value(model, &iter, n, &v);
933 n++;
937 if (pixbuf != NULL)
938 g_object_unref(pixbuf);
941 static void *
942 pidgin_notify_searchresults(PurpleConnection *gc, const char *title,
943 const char *primary, const char *secondary,
944 PurpleNotifySearchResults *results, gpointer user_data)
946 GtkWidget *window;
947 GtkWidget *treeview;
948 GtkWidget *close_button;
949 GType *col_types;
950 GtkListStore *model;
951 GtkCellRenderer *renderer;
952 guint col_num;
953 GList *columniter;
954 guint i;
955 GList *l;
957 GtkWidget *vbox;
958 GtkWidget *label;
959 GtkWidget *sw;
960 PidginNotifySearchResultsData *data;
961 char *label_text;
962 char *primary_esc, *secondary_esc;
964 g_return_val_if_fail(gc != NULL, NULL);
965 g_return_val_if_fail(results != NULL, NULL);
967 data = g_malloc(sizeof(PidginNotifySearchResultsData));
968 data->user_data = user_data;
969 data->results = results;
971 /* Create the window */
972 window = gtk_dialog_new();
973 gtk_window_set_title(GTK_WINDOW(window), title ? title :_("Search Results"));
974 gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER);
975 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
977 g_signal_connect_swapped(G_OBJECT(window), "delete_event",
978 G_CALLBACK(searchresults_close_cb), data);
980 /* Setup the main vbox */
981 vbox = GTK_DIALOG(window)->vbox;
983 /* Setup the descriptive label */
984 primary_esc = (primary != NULL) ? g_markup_escape_text(primary, -1) : NULL;
985 secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
986 label_text = g_strdup_printf(
987 "<span weight=\"bold\" size=\"larger\">%s</span>%s%s",
988 (primary ? primary_esc : ""),
989 (primary && secondary ? "\n" : ""),
990 (secondary ? secondary_esc : ""));
991 g_free(primary_esc);
992 g_free(secondary_esc);
993 label = gtk_label_new(NULL);
994 gtk_label_set_markup(GTK_LABEL(label), label_text);
995 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
996 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
997 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
998 gtk_widget_show(label);
999 g_free(label_text);
1001 /* +1 is for the automagically created Status column. */
1002 col_num = g_list_length(results->columns) + 1;
1004 /* Setup the list model */
1005 col_types = g_new0(GType, col_num);
1007 /* There always is this first column. */
1008 col_types[0] = GDK_TYPE_PIXBUF;
1009 for (i = 1; i < col_num; i++) {
1010 col_types[i] = G_TYPE_STRING;
1012 model = gtk_list_store_newv(col_num, col_types);
1013 g_free(col_types);
1015 /* Setup the scrolled window containing the treeview */
1016 sw = gtk_scrolled_window_new(NULL, NULL);
1017 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
1018 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
1019 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
1020 GTK_SHADOW_IN);
1021 gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
1022 gtk_widget_show(sw);
1024 /* Setup the treeview */
1025 treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1026 g_object_unref(G_OBJECT(model));
1027 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
1028 gtk_widget_set_size_request(treeview, 500, 400);
1029 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)),
1030 GTK_SELECTION_SINGLE);
1031 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), TRUE);
1032 gtk_container_add(GTK_CONTAINER(sw), treeview);
1033 gtk_widget_show(treeview);
1035 renderer = gtk_cell_renderer_pixbuf_new();
1036 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview),
1037 -1, "", renderer, "pixbuf", 0, NULL);
1039 i = 1;
1040 for (columniter = results->columns; columniter != NULL; columniter = columniter->next) {
1041 PurpleNotifySearchColumn *column = columniter->data;
1042 renderer = gtk_cell_renderer_text_new();
1044 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview), -1,
1045 column->title, renderer, "text", i, NULL);
1046 i++;
1049 for (l = results->buttons; l; l = l->next) {
1050 PurpleNotifySearchButton *b = l->data;
1051 GtkWidget *button = NULL;
1052 switch (b->type) {
1053 case PURPLE_NOTIFY_BUTTON_LABELED:
1054 if(b->label) {
1055 button = gtk_button_new_with_label(b->label);
1056 } else {
1057 purple_debug_warning("gtknotify", "Missing button label\n");
1059 break;
1060 case PURPLE_NOTIFY_BUTTON_CONTINUE:
1061 button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_GO_FORWARD, GTK_RESPONSE_NONE);
1062 break;
1063 case PURPLE_NOTIFY_BUTTON_ADD:
1064 button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_ADD, GTK_RESPONSE_NONE);
1065 break;
1066 case PURPLE_NOTIFY_BUTTON_INFO:
1067 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_TOOLBAR_USER_INFO, GTK_RESPONSE_NONE);
1068 break;
1069 case PURPLE_NOTIFY_BUTTON_IM:
1070 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW, GTK_RESPONSE_NONE);
1071 break;
1072 case PURPLE_NOTIFY_BUTTON_JOIN:
1073 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_CHAT, GTK_RESPONSE_NONE);
1074 break;
1075 case PURPLE_NOTIFY_BUTTON_INVITE:
1076 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_INVITE, GTK_RESPONSE_NONE);
1077 break;
1078 default:
1079 purple_debug_warning("gtknotify", "Incorrect button type: %d\n", b->type);
1081 if (button != NULL) {
1082 PidginNotifySearchResultsButtonData *bd;
1084 bd = g_new0(PidginNotifySearchResultsButtonData, 1);
1085 bd->button = b;
1086 bd->data = data;
1088 g_signal_connect(G_OBJECT(button), "clicked",
1089 G_CALLBACK(searchresults_callback_wrapper_cb), bd);
1090 g_signal_connect_swapped(G_OBJECT(button), "destroy", G_CALLBACK(g_free), bd);
1094 /* Add the Close button */
1095 close_button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
1097 g_signal_connect_swapped(G_OBJECT(close_button), "clicked",
1098 G_CALLBACK(searchresults_close_cb), data);
1100 data->account = gc->account;
1101 data->model = model;
1102 data->treeview = treeview;
1103 data->window = window;
1105 /* Insert rows. */
1106 pidgin_notify_searchresults_new_rows(gc, results, data);
1108 /* Show the window */
1109 pidgin_auto_parent_window(window);
1111 gtk_widget_show(window);
1112 return data;
1115 /** Xerox'ed from Finch! How the tables have turned!! ;) **/
1116 /** User information. **/
1117 static GHashTable *userinfo;
1119 static char *
1120 userinfo_hash(PurpleAccount *account, const char *who)
1122 char key[256];
1123 snprintf(key, sizeof(key), "%s - %s", purple_account_get_username(account), purple_normalize(account, who));
1124 return g_utf8_strup(key, -1);
1127 static void
1128 remove_userinfo(GtkWidget *widget, gpointer key)
1130 PidginUserInfo *pinfo = g_hash_table_lookup(userinfo, key);
1132 while (pinfo->count--)
1133 purple_notify_close(PURPLE_NOTIFY_USERINFO, widget);
1135 g_hash_table_remove(userinfo, key);
1138 static void *
1139 pidgin_notify_userinfo(PurpleConnection *gc, const char *who,
1140 PurpleNotifyUserInfo *user_info)
1142 char *info;
1143 void *ui_handle;
1144 char *key = userinfo_hash(purple_connection_get_account(gc), who);
1145 PidginUserInfo *pinfo = NULL;
1147 if (!userinfo) {
1148 userinfo = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
1151 info = purple_notify_user_info_get_text_with_newline(user_info, "<br />");
1152 pinfo = g_hash_table_lookup(userinfo, key);
1153 if (pinfo != NULL) {
1154 GtkIMHtml *imhtml = g_object_get_data(G_OBJECT(pinfo->window), "info-widget");
1155 char *linked_text = purple_markup_linkify(info);
1156 gtk_imhtml_clear(imhtml);
1157 gtk_imhtml_append_text(imhtml, linked_text, notify_imhtml_options());
1158 g_free(linked_text);
1159 g_free(key);
1160 ui_handle = pinfo->window;
1161 pinfo->count++;
1162 } else {
1163 char *primary = g_strdup_printf(_("Info for %s"), who);
1164 ui_handle = pidgin_notify_formatted(_("Buddy Information"), primary, NULL, info);
1165 g_signal_handlers_disconnect_by_func(G_OBJECT(ui_handle), G_CALLBACK(formatted_close_cb), NULL);
1166 g_signal_connect(G_OBJECT(ui_handle), "destroy", G_CALLBACK(remove_userinfo), key);
1167 g_free(primary);
1168 pinfo = g_new0(PidginUserInfo, 1);
1169 pinfo->window = ui_handle;
1170 pinfo->count = 1;
1171 g_hash_table_insert(userinfo, key, pinfo);
1173 g_free(info);
1174 return ui_handle;
1177 static void
1178 pidgin_close_notify(PurpleNotifyType type, void *ui_handle)
1180 if (type == PURPLE_NOTIFY_EMAIL || type == PURPLE_NOTIFY_EMAILS)
1182 PidginNotifyMailData *data = (PidginNotifyMailData *)ui_handle;
1184 if (data) {
1185 g_free(data->url);
1186 g_free(data);
1189 else if (type == PURPLE_NOTIFY_SEARCHRESULTS)
1191 PidginNotifySearchResultsData *data = (PidginNotifySearchResultsData *)ui_handle;
1193 gtk_widget_destroy(data->window);
1194 purple_notify_searchresults_free(data->results);
1196 g_free(data);
1198 else if (ui_handle != NULL)
1199 gtk_widget_destroy(GTK_WIDGET(ui_handle));
1202 #ifndef _WIN32
1203 static gint
1204 uri_command(const char *command, gboolean sync)
1206 gchar *tmp;
1207 GError *error = NULL;
1208 gint ret = 0;
1210 purple_debug_misc("gtknotify", "Executing %s\n", command);
1212 if (!purple_program_is_valid(command))
1214 tmp = g_strdup_printf(_("The browser command \"%s\" is invalid."),
1215 command ? command : "(none)");
1216 purple_notify_error(NULL, NULL, _("Unable to open URL"), tmp);
1217 g_free(tmp);
1220 else if (sync)
1222 gint status;
1224 if (!g_spawn_command_line_sync(command, NULL, NULL, &status, &error))
1226 tmp = g_strdup_printf(_("Error launching \"%s\": %s"),
1227 command, error->message);
1228 purple_notify_error(NULL, NULL, _("Unable to open URL"), tmp);
1229 g_free(tmp);
1230 g_error_free(error);
1232 else
1233 ret = status;
1235 else
1237 if (!g_spawn_command_line_async(command, &error))
1239 tmp = g_strdup_printf(_("Error launching \"%s\": %s"),
1240 command, error->message);
1241 purple_notify_error(NULL, NULL, _("Unable to open URL"), tmp);
1242 g_free(tmp);
1243 g_error_free(error);
1247 return ret;
1249 #endif /* _WIN32 */
1251 static void *
1252 pidgin_notify_uri(const char *uri)
1254 #ifndef _WIN32
1255 char *escaped = g_shell_quote(uri);
1256 char *command = NULL;
1257 char *remote_command = NULL;
1258 const char *web_browser;
1259 int place;
1261 web_browser = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/browsers/browser");
1262 place = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/browsers/place");
1264 /* if they are running gnome, use the gnome web browser */
1265 if (purple_running_gnome() == TRUE)
1267 char *tmp = g_find_program_in_path("xdg-open");
1268 if (tmp == NULL)
1269 command = g_strdup_printf("gnome-open %s", escaped);
1270 else
1271 command = g_strdup_printf("xdg-open %s", escaped);
1272 g_free(tmp);
1274 else if (purple_running_osx() == TRUE)
1276 command = g_strdup_printf("open %s", escaped);
1278 else if (!strcmp(web_browser, "epiphany") ||
1279 !strcmp(web_browser, "galeon"))
1281 if (place == PIDGIN_BROWSER_NEW_WINDOW)
1282 command = g_strdup_printf("%s -w %s", web_browser, escaped);
1283 else if (place == PIDGIN_BROWSER_NEW_TAB)
1284 command = g_strdup_printf("%s -n %s", web_browser, escaped);
1285 else
1286 command = g_strdup_printf("%s %s", web_browser, escaped);
1288 else if (!strcmp(web_browser, "xdg-open"))
1290 command = g_strdup_printf("xdg-open %s", escaped);
1292 else if (!strcmp(web_browser, "gnome-open"))
1294 command = g_strdup_printf("gnome-open %s", escaped);
1296 else if (!strcmp(web_browser, "kfmclient"))
1298 command = g_strdup_printf("kfmclient openURL %s", escaped);
1300 * Does Konqueror have options to open in new tab
1301 * and/or current window?
1304 else if (!strcmp(web_browser, "mozilla") ||
1305 !strcmp(web_browser, "mozilla-firebird") ||
1306 !strcmp(web_browser, "firefox") ||
1307 !strcmp(web_browser, "seamonkey"))
1309 char *args = "";
1311 command = g_strdup_printf("%s %s", web_browser, escaped);
1314 * Firefox 0.9 and higher require a "-a firefox" option when
1315 * using -remote commands. This breaks older versions of
1316 * mozilla. So we include this other handly little string
1317 * when calling firefox. If the API for remote calls changes
1318 * any more in firefox then firefox should probably be split
1319 * apart from mozilla-firebird and mozilla... but this is good
1320 * for now.
1322 if (!strcmp(web_browser, "firefox"))
1323 args = "-a firefox";
1325 if (place == PIDGIN_BROWSER_NEW_WINDOW)
1326 remote_command = g_strdup_printf("%s %s -remote "
1327 "openURL(%s,new-window)",
1328 web_browser, args, escaped);
1329 else if (place == PIDGIN_BROWSER_NEW_TAB)
1330 remote_command = g_strdup_printf("%s %s -remote "
1331 "openURL(%s,new-tab)",
1332 web_browser, args, escaped);
1333 else if (place == PIDGIN_BROWSER_CURRENT)
1334 remote_command = g_strdup_printf("%s %s -remote "
1335 "openURL(%s)",
1336 web_browser, args, escaped);
1338 else if (!strcmp(web_browser, "netscape"))
1340 command = g_strdup_printf("netscape %s", escaped);
1342 if (place == PIDGIN_BROWSER_NEW_WINDOW)
1344 remote_command = g_strdup_printf("netscape -remote "
1345 "openURL(%s,new-window)",
1346 escaped);
1348 else if (place == PIDGIN_BROWSER_CURRENT)
1350 remote_command = g_strdup_printf("netscape -remote "
1351 "openURL(%s)", escaped);
1354 else if (!strcmp(web_browser, "opera"))
1356 if (place == PIDGIN_BROWSER_NEW_WINDOW)
1357 command = g_strdup_printf("opera -newwindow %s", escaped);
1358 else if (place == PIDGIN_BROWSER_NEW_TAB)
1359 command = g_strdup_printf("opera -newpage %s", escaped);
1360 else if (place == PIDGIN_BROWSER_CURRENT)
1362 remote_command = g_strdup_printf("opera -remote "
1363 "openURL(%s)", escaped);
1364 command = g_strdup_printf("opera %s", escaped);
1366 else
1367 command = g_strdup_printf("opera %s", escaped);
1370 else if (!strcmp(web_browser, "google-chrome"))
1372 /* Google Chrome doesn't have command-line arguments that control the
1373 * opening of links from external calls. This is controlled solely from
1374 * a preference within Google Chrome. */
1375 command = g_strdup_printf("google-chrome %s", escaped);
1377 else if (!strcmp(web_browser, "chrome"))
1379 /* Chromium doesn't have command-line arguments that control the
1380 * opening of links from external calls. This is controlled solely from
1381 * a preference within Chromium. */
1382 command = g_strdup_printf("chrome %s", escaped);
1384 else if (!strcmp(web_browser, "chromium-browser"))
1386 /* Chromium doesn't have command-line arguments that control the
1387 * opening of links from external calls. This is controlled solely from
1388 * a preference within Chromium. */
1389 command = g_strdup_printf("chromium-browser %s", escaped);
1391 else if (!strcmp(web_browser, "custom"))
1393 const char *web_command;
1395 web_command = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/browsers/manual_command");
1397 if (web_command == NULL || *web_command == '\0')
1399 purple_notify_error(NULL, NULL, _("Unable to open URL"),
1400 _("The 'Manual' browser command has been "
1401 "chosen, but no command has been set."));
1402 return NULL;
1405 if (strstr(web_command, "%s"))
1406 command = purple_strreplace(web_command, "%s", escaped);
1407 else
1410 * There is no "%s" in the browser command. Assume the user
1411 * wanted the URL tacked on to the end of the command.
1413 command = g_strdup_printf("%s %s", web_command, escaped);
1417 g_free(escaped);
1419 if (remote_command != NULL)
1421 /* try the remote command first */
1422 if (uri_command(remote_command, TRUE) != 0)
1423 uri_command(command, FALSE);
1425 g_free(remote_command);
1428 else
1429 uri_command(command, FALSE);
1431 g_free(command);
1433 #else /* !_WIN32 */
1434 winpidgin_notify_uri(uri);
1435 #endif /* !_WIN32 */
1437 return NULL;
1440 void
1441 pidgin_notify_pounce_add(PurpleAccount *account, PurplePounce *pounce,
1442 const char *alias, const char *event, const char *message, const char *date)
1444 GdkPixbuf *icon;
1445 GtkTreeIter iter;
1446 PidginNotifyPounceData *pounce_data;
1447 gboolean first = (pounce_dialog == NULL);
1449 if (pounce_dialog == NULL)
1450 pounce_dialog = pidgin_create_notification_dialog(PIDGIN_NOTIFY_POUNCE);
1452 icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
1454 pounce_data = g_new(PidginNotifyPounceData, 1);
1456 pounce_data->account = account;
1457 pounce_data->pounce = pounce;
1458 pounce_data->pouncee = g_strdup(purple_pounce_get_pouncee(pounce));
1460 gtk_tree_store_append(pounce_dialog->treemodel, &iter, NULL);
1462 gtk_tree_store_set(pounce_dialog->treemodel, &iter,
1463 PIDGIN_POUNCE_ICON, icon,
1464 PIDGIN_POUNCE_ALIAS, alias,
1465 PIDGIN_POUNCE_EVENT, event,
1466 PIDGIN_POUNCE_TEXT, (message != NULL)? message : _("No message"),
1467 PIDGIN_POUNCE_DATE, date,
1468 PIDGIN_POUNCE_DATA, pounce_data,
1469 -1);
1471 if (first) {
1472 GtkTreeSelection *selection =
1473 gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
1474 gtk_tree_selection_select_iter(selection, &iter);
1477 if (icon)
1478 g_object_unref(icon);
1480 gtk_widget_show_all(pounce_dialog->dialog);
1482 return;
1485 static PidginNotifyDialog *
1486 pidgin_create_notification_dialog(PidginNotifyType type)
1488 GtkTreeStore *model = NULL;
1489 GtkWidget *dialog = NULL;
1490 GtkWidget *label = NULL;
1491 GtkWidget *sw;
1492 GtkCellRenderer *rend;
1493 GtkTreeViewColumn *column;
1494 GtkWidget *button = NULL;
1495 GtkWidget *vbox = NULL;
1496 GtkTreeSelection *sel;
1497 PidginNotifyDialog *spec_dialog = NULL;
1499 g_return_val_if_fail(type < PIDGIN_NOTIFY_TYPES, NULL);
1501 if (type == PIDGIN_NOTIFY_MAIL) {
1502 g_return_val_if_fail(mail_dialog == NULL, mail_dialog);
1504 model = gtk_tree_store_new(COLUMNS_PIDGIN_MAIL,
1505 GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_POINTER);
1507 } else if (type == PIDGIN_NOTIFY_POUNCE) {
1508 g_return_val_if_fail(pounce_dialog == NULL, pounce_dialog);
1510 model = gtk_tree_store_new(COLUMNS_PIDGIN_POUNCE,
1511 GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
1512 G_TYPE_STRING, G_TYPE_POINTER);
1515 dialog = gtk_dialog_new();
1517 /* Setup the dialog */
1518 gtk_container_set_border_width(GTK_CONTAINER(dialog), PIDGIN_HIG_BOX_SPACE);
1519 gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BOX_SPACE);
1520 gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
1521 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BORDER);
1523 /* Vertical box */
1524 vbox = GTK_DIALOG(dialog)->vbox;
1526 /* Golden ratio it up! */
1527 gtk_widget_set_size_request(dialog, 550, 400);
1529 sw = gtk_scrolled_window_new(NULL, NULL);
1530 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_IN);
1531 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
1533 spec_dialog = g_new0(PidginNotifyDialog, 1);
1534 spec_dialog->dialog = dialog;
1536 spec_dialog->treemodel = model;
1537 spec_dialog->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1538 g_object_unref(G_OBJECT(model));
1540 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(spec_dialog->treeview), TRUE);
1541 gtk_container_add(GTK_CONTAINER(sw), spec_dialog->treeview);
1543 if (type == PIDGIN_NOTIFY_MAIL) {
1544 gtk_window_set_title(GTK_WINDOW(dialog), _("New Mail"));
1545 gtk_window_set_role(GTK_WINDOW(dialog), "new_mail_detailed");
1546 g_signal_connect(G_OBJECT(dialog), "focus-in-event",
1547 G_CALLBACK(mail_window_focus_cb), NULL);
1549 gtk_dialog_add_button(GTK_DIALOG(dialog),
1550 _("Open All Messages"), GTK_RESPONSE_ACCEPT);
1552 button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1553 PIDGIN_STOCK_OPEN_MAIL, GTK_RESPONSE_YES);
1554 spec_dialog->open_button = button;
1556 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(spec_dialog->treeview), FALSE);
1558 gtk_tree_view_set_search_column(GTK_TREE_VIEW(spec_dialog->treeview), PIDGIN_MAIL_TEXT);
1559 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(spec_dialog->treeview),
1560 pidgin_tree_view_search_equal_func, NULL, NULL);
1562 g_signal_connect(G_OBJECT(dialog), "response",
1563 G_CALLBACK(email_response_cb), spec_dialog);
1564 g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(spec_dialog->treeview))),
1565 "changed", G_CALLBACK(selection_changed_cb), spec_dialog);
1566 g_signal_connect(G_OBJECT(spec_dialog->treeview), "row-activated", G_CALLBACK(email_row_activated_cb), NULL);
1568 column = gtk_tree_view_column_new();
1569 gtk_tree_view_column_set_resizable(column, TRUE);
1570 rend = gtk_cell_renderer_pixbuf_new();
1571 gtk_tree_view_column_pack_start(column, rend, FALSE);
1573 gtk_tree_view_column_set_attributes(column, rend, "pixbuf", PIDGIN_MAIL_ICON, NULL);
1574 rend = gtk_cell_renderer_text_new();
1575 gtk_tree_view_column_pack_start(column, rend, TRUE);
1576 gtk_tree_view_column_set_attributes(column, rend, "markup", PIDGIN_MAIL_TEXT, NULL);
1577 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1579 label = gtk_label_new(NULL);
1580 gtk_label_set_markup(GTK_LABEL(label), _("<span weight=\"bold\" size=\"larger\">You have mail!</span>"));
1582 } else if (type == PIDGIN_NOTIFY_POUNCE) {
1583 gtk_window_set_title(GTK_WINDOW(dialog), _("New Pounces"));
1585 button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1586 _("IM"), GTK_RESPONSE_YES);
1587 gtk_widget_set_sensitive(button, FALSE);
1588 spec_dialog->open_button = button;
1590 button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1591 PIDGIN_STOCK_MODIFY, GTK_RESPONSE_APPLY);
1592 gtk_widget_set_sensitive(button, FALSE);
1593 spec_dialog->edit_button = button;
1595 button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1596 _("Dismiss"), GTK_RESPONSE_NO);
1597 gtk_widget_set_sensitive(button, FALSE);
1598 spec_dialog->dismiss_button = button;
1600 g_signal_connect(G_OBJECT(dialog), "response",
1601 G_CALLBACK(pounce_response_cb), spec_dialog);
1603 column = gtk_tree_view_column_new();
1604 gtk_tree_view_column_set_title(column, _("Buddy"));
1605 gtk_tree_view_column_set_resizable(column, TRUE);
1606 rend = gtk_cell_renderer_pixbuf_new();
1607 gtk_tree_view_column_pack_start(column, rend, FALSE);
1609 gtk_tree_view_column_set_attributes(column, rend, "pixbuf", PIDGIN_POUNCE_ICON, NULL);
1610 rend = gtk_cell_renderer_text_new();
1611 gtk_tree_view_column_pack_start(column, rend, FALSE);
1612 gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_ALIAS);
1613 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1615 column = gtk_tree_view_column_new();
1616 gtk_tree_view_column_set_title(column, _("Event"));
1617 gtk_tree_view_column_set_resizable(column, TRUE);
1618 rend = gtk_cell_renderer_text_new();
1619 gtk_tree_view_column_pack_start(column, rend, FALSE);
1620 gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_EVENT);
1621 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1623 column = gtk_tree_view_column_new();
1624 gtk_tree_view_column_set_title(column, _("Message"));
1625 gtk_tree_view_column_set_resizable(column, TRUE);
1626 rend = gtk_cell_renderer_text_new();
1627 gtk_tree_view_column_pack_start(column, rend, FALSE);
1628 gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_TEXT);
1629 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1631 column = gtk_tree_view_column_new();
1632 gtk_tree_view_column_set_title(column, _("Date"));
1633 gtk_tree_view_column_set_resizable(column, TRUE);
1634 rend = gtk_cell_renderer_text_new();
1635 gtk_tree_view_column_pack_start(column, rend, FALSE);
1636 gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_DATE);
1637 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1639 label = gtk_label_new(NULL);
1640 gtk_label_set_markup(GTK_LABEL(label), _("<span weight=\"bold\" size=\"larger\">You have pounced!</span>"));
1642 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(spec_dialog->treeview));
1643 gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
1644 g_signal_connect(G_OBJECT(sel), "changed",
1645 G_CALLBACK(pounce_row_selected_cb), NULL);
1646 g_signal_connect(G_OBJECT(spec_dialog->treeview), "row-activated",
1647 G_CALLBACK(pounce_response_open_ims), NULL);
1650 button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1651 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
1653 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1654 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
1655 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1656 gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 2);
1658 return spec_dialog;
1661 static void
1662 signed_off_cb(PurpleConnection *gc, gpointer unused)
1664 /* Clear any pending emails for this account */
1665 pidgin_notify_emails(gc, 0, FALSE, NULL, NULL, NULL, NULL);
1667 if (mail_dialog != NULL && mail_dialog->total_count == 0)
1668 reset_mail_dialog(NULL);
1671 static void*
1672 pidgin_notify_get_handle(void)
1674 static int handle;
1675 return &handle;
1678 void pidgin_notify_init(void)
1680 void *handle = pidgin_notify_get_handle();
1682 purple_signal_connect(purple_connections_get_handle(), "signed-off",
1683 handle, PURPLE_CALLBACK(signed_off_cb), NULL);
1686 void pidgin_notify_uninit(void)
1688 purple_signals_disconnect_by_handle(pidgin_notify_get_handle());
1691 static PurpleNotifyUiOps ops =
1693 pidgin_notify_message,
1694 pidgin_notify_email,
1695 pidgin_notify_emails,
1696 pidgin_notify_formatted,
1697 pidgin_notify_searchresults,
1698 pidgin_notify_searchresults_new_rows,
1699 pidgin_notify_userinfo,
1700 pidgin_notify_uri,
1701 pidgin_close_notify,
1702 NULL,
1703 NULL,
1704 NULL,
1705 NULL
1708 PurpleNotifyUiOps *
1709 pidgin_notify_get_ui_ops(void)
1711 return &ops;