Dutch translation updated (Gideon van Melle)
[pidgin-git.git] / pidgin / gtknotify.c
blob8d15e036fc80249937815936bbf2734468634cc5
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 PidginNotifySearchResultsData *data;
960 char *label_text;
961 char *primary_esc, *secondary_esc;
963 g_return_val_if_fail(gc != NULL, NULL);
964 g_return_val_if_fail(results != NULL, NULL);
966 data = g_malloc(sizeof(PidginNotifySearchResultsData));
967 data->user_data = user_data;
968 data->results = results;
970 /* Create the window */
971 window = gtk_dialog_new();
972 gtk_window_set_title(GTK_WINDOW(window), title ? title :_("Search Results"));
973 gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER);
974 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
976 g_signal_connect_swapped(G_OBJECT(window), "delete_event",
977 G_CALLBACK(searchresults_close_cb), data);
979 /* Setup the main vbox */
980 vbox = GTK_DIALOG(window)->vbox;
982 /* Setup the descriptive label */
983 primary_esc = (primary != NULL) ? g_markup_escape_text(primary, -1) : NULL;
984 secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
985 label_text = g_strdup_printf(
986 "<span weight=\"bold\" size=\"larger\">%s</span>%s%s",
987 (primary ? primary_esc : ""),
988 (primary && secondary ? "\n" : ""),
989 (secondary ? secondary_esc : ""));
990 g_free(primary_esc);
991 g_free(secondary_esc);
992 label = gtk_label_new(NULL);
993 gtk_label_set_markup(GTK_LABEL(label), label_text);
994 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
995 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
996 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
997 gtk_widget_show(label);
998 g_free(label_text);
1000 /* +1 is for the automagically created Status column. */
1001 col_num = g_list_length(results->columns) + 1;
1003 /* Setup the list model */
1004 col_types = g_new0(GType, col_num);
1006 /* There always is this first column. */
1007 col_types[0] = GDK_TYPE_PIXBUF;
1008 for (i = 1; i < col_num; i++) {
1009 col_types[i] = G_TYPE_STRING;
1011 model = gtk_list_store_newv(col_num, col_types);
1012 g_free(col_types);
1014 /* Setup the treeview */
1015 treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1016 g_object_unref(G_OBJECT(model));
1017 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
1018 gtk_widget_set_size_request(treeview, 500, 400);
1019 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)),
1020 GTK_SELECTION_SINGLE);
1021 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), TRUE);
1022 gtk_box_pack_start(GTK_BOX(vbox),
1023 pidgin_make_scrollable(treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1),
1024 TRUE, TRUE, 0);
1025 gtk_widget_show(treeview);
1027 renderer = gtk_cell_renderer_pixbuf_new();
1028 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview),
1029 -1, "", renderer, "pixbuf", 0, NULL);
1031 i = 1;
1032 for (columniter = results->columns; columniter != NULL; columniter = columniter->next) {
1033 PurpleNotifySearchColumn *column = columniter->data;
1034 renderer = gtk_cell_renderer_text_new();
1036 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview), -1,
1037 column->title, renderer, "text", i, NULL);
1038 i++;
1041 for (l = results->buttons; l; l = l->next) {
1042 PurpleNotifySearchButton *b = l->data;
1043 GtkWidget *button = NULL;
1044 switch (b->type) {
1045 case PURPLE_NOTIFY_BUTTON_LABELED:
1046 if(b->label) {
1047 button = gtk_button_new_with_label(b->label);
1048 } else {
1049 purple_debug_warning("gtknotify", "Missing button label\n");
1051 break;
1052 case PURPLE_NOTIFY_BUTTON_CONTINUE:
1053 button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_GO_FORWARD, GTK_RESPONSE_NONE);
1054 break;
1055 case PURPLE_NOTIFY_BUTTON_ADD:
1056 button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_ADD, GTK_RESPONSE_NONE);
1057 break;
1058 case PURPLE_NOTIFY_BUTTON_INFO:
1059 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_TOOLBAR_USER_INFO, GTK_RESPONSE_NONE);
1060 break;
1061 case PURPLE_NOTIFY_BUTTON_IM:
1062 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW, GTK_RESPONSE_NONE);
1063 break;
1064 case PURPLE_NOTIFY_BUTTON_JOIN:
1065 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_CHAT, GTK_RESPONSE_NONE);
1066 break;
1067 case PURPLE_NOTIFY_BUTTON_INVITE:
1068 button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_INVITE, GTK_RESPONSE_NONE);
1069 break;
1070 default:
1071 purple_debug_warning("gtknotify", "Incorrect button type: %d\n", b->type);
1073 if (button != NULL) {
1074 PidginNotifySearchResultsButtonData *bd;
1076 bd = g_new0(PidginNotifySearchResultsButtonData, 1);
1077 bd->button = b;
1078 bd->data = data;
1080 g_signal_connect(G_OBJECT(button), "clicked",
1081 G_CALLBACK(searchresults_callback_wrapper_cb), bd);
1082 g_signal_connect_swapped(G_OBJECT(button), "destroy", G_CALLBACK(g_free), bd);
1086 /* Add the Close button */
1087 close_button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
1089 g_signal_connect_swapped(G_OBJECT(close_button), "clicked",
1090 G_CALLBACK(searchresults_close_cb), data);
1092 data->account = gc->account;
1093 data->model = model;
1094 data->treeview = treeview;
1095 data->window = window;
1097 /* Insert rows. */
1098 pidgin_notify_searchresults_new_rows(gc, results, data);
1100 /* Show the window */
1101 pidgin_auto_parent_window(window);
1103 gtk_widget_show(window);
1104 return data;
1107 /** Xerox'ed from Finch! How the tables have turned!! ;) **/
1108 /** User information. **/
1109 static GHashTable *userinfo;
1111 static char *
1112 userinfo_hash(PurpleAccount *account, const char *who)
1114 char key[256];
1115 snprintf(key, sizeof(key), "%s - %s", purple_account_get_username(account), purple_normalize(account, who));
1116 return g_utf8_strup(key, -1);
1119 static void
1120 remove_userinfo(GtkWidget *widget, gpointer key)
1122 PidginUserInfo *pinfo = g_hash_table_lookup(userinfo, key);
1124 while (pinfo->count--)
1125 purple_notify_close(PURPLE_NOTIFY_USERINFO, widget);
1127 g_hash_table_remove(userinfo, key);
1130 static void *
1131 pidgin_notify_userinfo(PurpleConnection *gc, const char *who,
1132 PurpleNotifyUserInfo *user_info)
1134 char *info;
1135 void *ui_handle;
1136 char *key = userinfo_hash(purple_connection_get_account(gc), who);
1137 PidginUserInfo *pinfo = NULL;
1139 if (!userinfo) {
1140 userinfo = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
1143 info = purple_notify_user_info_get_text_with_newline(user_info, "<br />");
1144 pinfo = g_hash_table_lookup(userinfo, key);
1145 if (pinfo != NULL) {
1146 GtkIMHtml *imhtml = g_object_get_data(G_OBJECT(pinfo->window), "info-widget");
1147 char *linked_text = purple_markup_linkify(info);
1148 gtk_imhtml_clear(imhtml);
1149 gtk_imhtml_append_text(imhtml, linked_text, notify_imhtml_options());
1150 g_free(linked_text);
1151 g_free(key);
1152 ui_handle = pinfo->window;
1153 pinfo->count++;
1154 } else {
1155 char *primary = g_strdup_printf(_("Info for %s"), who);
1156 ui_handle = pidgin_notify_formatted(_("Buddy Information"), primary, NULL, info);
1157 g_signal_handlers_disconnect_by_func(G_OBJECT(ui_handle), G_CALLBACK(formatted_close_cb), NULL);
1158 g_signal_connect(G_OBJECT(ui_handle), "destroy", G_CALLBACK(remove_userinfo), key);
1159 g_free(primary);
1160 pinfo = g_new0(PidginUserInfo, 1);
1161 pinfo->window = ui_handle;
1162 pinfo->count = 1;
1163 g_hash_table_insert(userinfo, key, pinfo);
1165 g_free(info);
1166 return ui_handle;
1169 static void
1170 pidgin_close_notify(PurpleNotifyType type, void *ui_handle)
1172 if (type == PURPLE_NOTIFY_EMAIL || type == PURPLE_NOTIFY_EMAILS)
1174 PidginNotifyMailData *data = (PidginNotifyMailData *)ui_handle;
1176 if (data) {
1177 g_free(data->url);
1178 g_free(data);
1181 else if (type == PURPLE_NOTIFY_SEARCHRESULTS)
1183 PidginNotifySearchResultsData *data = (PidginNotifySearchResultsData *)ui_handle;
1185 gtk_widget_destroy(data->window);
1186 purple_notify_searchresults_free(data->results);
1188 g_free(data);
1190 else if (ui_handle != NULL)
1191 gtk_widget_destroy(GTK_WIDGET(ui_handle));
1194 #ifndef _WIN32
1195 static gint
1196 uri_command(const char *command, gboolean sync)
1198 gchar *tmp;
1199 GError *error = NULL;
1200 gint ret = 0;
1202 purple_debug_misc("gtknotify", "Executing %s\n", command);
1204 if (!purple_program_is_valid(command))
1206 tmp = g_strdup_printf(_("The browser command \"%s\" is invalid."),
1207 command ? command : "(none)");
1208 purple_notify_error(NULL, NULL, _("Unable to open URL"), tmp);
1209 g_free(tmp);
1212 else if (sync)
1214 gint status;
1216 if (!g_spawn_command_line_sync(command, NULL, NULL, &status, &error))
1218 tmp = g_strdup_printf(_("Error launching \"%s\": %s"),
1219 command, error->message);
1220 purple_notify_error(NULL, NULL, _("Unable to open URL"), tmp);
1221 g_free(tmp);
1222 g_error_free(error);
1224 else
1225 ret = status;
1227 else
1229 if (!g_spawn_command_line_async(command, &error))
1231 tmp = g_strdup_printf(_("Error launching \"%s\": %s"),
1232 command, error->message);
1233 purple_notify_error(NULL, NULL, _("Unable to open URL"), tmp);
1234 g_free(tmp);
1235 g_error_free(error);
1239 return ret;
1241 #endif /* _WIN32 */
1243 static void *
1244 pidgin_notify_uri(const char *uri)
1246 #ifndef _WIN32
1247 char *escaped = g_shell_quote(uri);
1248 char *command = NULL;
1249 char *remote_command = NULL;
1250 const char *web_browser;
1251 int place;
1253 web_browser = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/browsers/browser");
1254 place = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/browsers/place");
1256 /* if they are running gnome, use the gnome web browser */
1257 if (purple_running_gnome() == TRUE)
1259 char *tmp = g_find_program_in_path("xdg-open");
1260 if (tmp == NULL)
1261 command = g_strdup_printf("gnome-open %s", escaped);
1262 else
1263 command = g_strdup_printf("xdg-open %s", escaped);
1264 g_free(tmp);
1266 else if (purple_running_osx() == TRUE)
1268 command = g_strdup_printf("open %s", escaped);
1270 else if (!strcmp(web_browser, "epiphany") ||
1271 !strcmp(web_browser, "galeon"))
1273 if (place == PIDGIN_BROWSER_NEW_WINDOW)
1274 command = g_strdup_printf("%s -w %s", web_browser, escaped);
1275 else if (place == PIDGIN_BROWSER_NEW_TAB)
1276 command = g_strdup_printf("%s -n %s", web_browser, escaped);
1277 else
1278 command = g_strdup_printf("%s %s", web_browser, escaped);
1280 else if (!strcmp(web_browser, "xdg-open"))
1282 command = g_strdup_printf("xdg-open %s", escaped);
1284 else if (!strcmp(web_browser, "gnome-open"))
1286 command = g_strdup_printf("gnome-open %s", escaped);
1288 else if (!strcmp(web_browser, "kfmclient"))
1290 command = g_strdup_printf("kfmclient openURL %s", escaped);
1292 * Does Konqueror have options to open in new tab
1293 * and/or current window?
1296 else if (!strcmp(web_browser, "mozilla") ||
1297 !strcmp(web_browser, "mozilla-firebird") ||
1298 !strcmp(web_browser, "firefox") ||
1299 !strcmp(web_browser, "seamonkey"))
1301 char *args = "";
1303 command = g_strdup_printf("%s %s", web_browser, escaped);
1306 * Firefox 0.9 and higher require a "-a firefox" option when
1307 * using -remote commands. This breaks older versions of
1308 * mozilla. So we include this other handly little string
1309 * when calling firefox. If the API for remote calls changes
1310 * any more in firefox then firefox should probably be split
1311 * apart from mozilla-firebird and mozilla... but this is good
1312 * for now.
1314 if (!strcmp(web_browser, "firefox"))
1315 args = "-a firefox";
1317 if (place == PIDGIN_BROWSER_NEW_WINDOW)
1318 remote_command = g_strdup_printf("%s %s -remote "
1319 "openURL(%s,new-window)",
1320 web_browser, args, escaped);
1321 else if (place == PIDGIN_BROWSER_NEW_TAB)
1322 remote_command = g_strdup_printf("%s %s -remote "
1323 "openURL(%s,new-tab)",
1324 web_browser, args, escaped);
1325 else if (place == PIDGIN_BROWSER_CURRENT)
1326 remote_command = g_strdup_printf("%s %s -remote "
1327 "openURL(%s)",
1328 web_browser, args, escaped);
1330 else if (!strcmp(web_browser, "netscape"))
1332 command = g_strdup_printf("netscape %s", escaped);
1334 if (place == PIDGIN_BROWSER_NEW_WINDOW)
1336 remote_command = g_strdup_printf("netscape -remote "
1337 "openURL(%s,new-window)",
1338 escaped);
1340 else if (place == PIDGIN_BROWSER_CURRENT)
1342 remote_command = g_strdup_printf("netscape -remote "
1343 "openURL(%s)", escaped);
1346 else if (!strcmp(web_browser, "opera"))
1348 if (place == PIDGIN_BROWSER_NEW_WINDOW)
1349 command = g_strdup_printf("opera -newwindow %s", escaped);
1350 else if (place == PIDGIN_BROWSER_NEW_TAB)
1351 command = g_strdup_printf("opera -newpage %s", escaped);
1352 else if (place == PIDGIN_BROWSER_CURRENT)
1354 remote_command = g_strdup_printf("opera -remote "
1355 "openURL(%s)", escaped);
1356 command = g_strdup_printf("opera %s", escaped);
1358 else
1359 command = g_strdup_printf("opera %s", escaped);
1362 else if (!strcmp(web_browser, "google-chrome"))
1364 /* Google Chrome doesn't have command-line arguments that control the
1365 * opening of links from external calls. This is controlled solely from
1366 * a preference within Google Chrome. */
1367 command = g_strdup_printf("google-chrome %s", escaped);
1369 else if (!strcmp(web_browser, "chrome"))
1371 /* Chromium doesn't have command-line arguments that control the
1372 * opening of links from external calls. This is controlled solely from
1373 * a preference within Chromium. */
1374 command = g_strdup_printf("chrome %s", escaped);
1376 else if (!strcmp(web_browser, "chromium-browser"))
1378 /* Chromium doesn't have command-line arguments that control the
1379 * opening of links from external calls. This is controlled solely from
1380 * a preference within Chromium. */
1381 command = g_strdup_printf("chromium-browser %s", escaped);
1383 else if (!strcmp(web_browser, "custom"))
1385 const char *web_command;
1387 web_command = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/browsers/manual_command");
1389 if (web_command == NULL || *web_command == '\0')
1391 purple_notify_error(NULL, NULL, _("Unable to open URL"),
1392 _("The 'Manual' browser command has been "
1393 "chosen, but no command has been set."));
1394 return NULL;
1397 if (strstr(web_command, "%s"))
1398 command = purple_strreplace(web_command, "%s", escaped);
1399 else
1402 * There is no "%s" in the browser command. Assume the user
1403 * wanted the URL tacked on to the end of the command.
1405 command = g_strdup_printf("%s %s", web_command, escaped);
1409 g_free(escaped);
1411 if (remote_command != NULL)
1413 /* try the remote command first */
1414 if (uri_command(remote_command, TRUE) != 0)
1415 uri_command(command, FALSE);
1417 g_free(remote_command);
1420 else
1421 uri_command(command, FALSE);
1423 g_free(command);
1425 #else /* !_WIN32 */
1426 winpidgin_notify_uri(uri);
1427 #endif /* !_WIN32 */
1429 return NULL;
1432 void
1433 pidgin_notify_pounce_add(PurpleAccount *account, PurplePounce *pounce,
1434 const char *alias, const char *event, const char *message, const char *date)
1436 GdkPixbuf *icon;
1437 GtkTreeIter iter;
1438 PidginNotifyPounceData *pounce_data;
1439 gboolean first = (pounce_dialog == NULL);
1441 if (pounce_dialog == NULL)
1442 pounce_dialog = pidgin_create_notification_dialog(PIDGIN_NOTIFY_POUNCE);
1444 icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
1446 pounce_data = g_new(PidginNotifyPounceData, 1);
1448 pounce_data->account = account;
1449 pounce_data->pounce = pounce;
1450 pounce_data->pouncee = g_strdup(purple_pounce_get_pouncee(pounce));
1452 gtk_tree_store_append(pounce_dialog->treemodel, &iter, NULL);
1454 gtk_tree_store_set(pounce_dialog->treemodel, &iter,
1455 PIDGIN_POUNCE_ICON, icon,
1456 PIDGIN_POUNCE_ALIAS, alias,
1457 PIDGIN_POUNCE_EVENT, event,
1458 PIDGIN_POUNCE_TEXT, (message != NULL)? message : _("No message"),
1459 PIDGIN_POUNCE_DATE, date,
1460 PIDGIN_POUNCE_DATA, pounce_data,
1461 -1);
1463 if (first) {
1464 GtkTreeSelection *selection =
1465 gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
1466 gtk_tree_selection_select_iter(selection, &iter);
1469 if (icon)
1470 g_object_unref(icon);
1472 gtk_widget_show_all(pounce_dialog->dialog);
1474 return;
1477 static PidginNotifyDialog *
1478 pidgin_create_notification_dialog(PidginNotifyType type)
1480 GtkTreeStore *model = NULL;
1481 GtkWidget *dialog = NULL;
1482 GtkWidget *label = NULL;
1483 GtkCellRenderer *rend;
1484 GtkTreeViewColumn *column;
1485 GtkWidget *button = NULL;
1486 GtkWidget *vbox = NULL;
1487 GtkTreeSelection *sel;
1488 PidginNotifyDialog *spec_dialog = NULL;
1490 g_return_val_if_fail(type < PIDGIN_NOTIFY_TYPES, NULL);
1492 if (type == PIDGIN_NOTIFY_MAIL) {
1493 g_return_val_if_fail(mail_dialog == NULL, mail_dialog);
1495 model = gtk_tree_store_new(COLUMNS_PIDGIN_MAIL,
1496 GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_POINTER);
1498 } else if (type == PIDGIN_NOTIFY_POUNCE) {
1499 g_return_val_if_fail(pounce_dialog == NULL, pounce_dialog);
1501 model = gtk_tree_store_new(COLUMNS_PIDGIN_POUNCE,
1502 GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
1503 G_TYPE_STRING, G_TYPE_POINTER);
1506 dialog = gtk_dialog_new();
1508 /* Setup the dialog */
1509 gtk_container_set_border_width(GTK_CONTAINER(dialog), PIDGIN_HIG_BOX_SPACE);
1510 gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BOX_SPACE);
1511 gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
1512 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BORDER);
1514 /* Vertical box */
1515 vbox = GTK_DIALOG(dialog)->vbox;
1517 /* Golden ratio it up! */
1518 gtk_widget_set_size_request(dialog, 550, 400);
1520 spec_dialog = g_new0(PidginNotifyDialog, 1);
1521 spec_dialog->dialog = dialog;
1523 spec_dialog->treemodel = model;
1524 spec_dialog->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1525 g_object_unref(G_OBJECT(model));
1527 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(spec_dialog->treeview), TRUE);
1529 if (type == PIDGIN_NOTIFY_MAIL) {
1530 gtk_window_set_title(GTK_WINDOW(dialog), _("New Mail"));
1531 gtk_window_set_role(GTK_WINDOW(dialog), "new_mail_detailed");
1532 g_signal_connect(G_OBJECT(dialog), "focus-in-event",
1533 G_CALLBACK(mail_window_focus_cb), NULL);
1535 gtk_dialog_add_button(GTK_DIALOG(dialog),
1536 _("Open All Messages"), GTK_RESPONSE_ACCEPT);
1538 button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1539 PIDGIN_STOCK_OPEN_MAIL, GTK_RESPONSE_YES);
1540 spec_dialog->open_button = button;
1542 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(spec_dialog->treeview), FALSE);
1544 gtk_tree_view_set_search_column(GTK_TREE_VIEW(spec_dialog->treeview), PIDGIN_MAIL_TEXT);
1545 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(spec_dialog->treeview),
1546 pidgin_tree_view_search_equal_func, NULL, NULL);
1548 g_signal_connect(G_OBJECT(dialog), "response",
1549 G_CALLBACK(email_response_cb), spec_dialog);
1550 g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(spec_dialog->treeview))),
1551 "changed", G_CALLBACK(selection_changed_cb), spec_dialog);
1552 g_signal_connect(G_OBJECT(spec_dialog->treeview), "row-activated", G_CALLBACK(email_row_activated_cb), NULL);
1554 column = gtk_tree_view_column_new();
1555 gtk_tree_view_column_set_resizable(column, TRUE);
1556 rend = gtk_cell_renderer_pixbuf_new();
1557 gtk_tree_view_column_pack_start(column, rend, FALSE);
1559 gtk_tree_view_column_set_attributes(column, rend, "pixbuf", PIDGIN_MAIL_ICON, NULL);
1560 rend = gtk_cell_renderer_text_new();
1561 gtk_tree_view_column_pack_start(column, rend, TRUE);
1562 gtk_tree_view_column_set_attributes(column, rend, "markup", PIDGIN_MAIL_TEXT, NULL);
1563 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1565 label = gtk_label_new(NULL);
1566 gtk_label_set_markup(GTK_LABEL(label), _("<span weight=\"bold\" size=\"larger\">You have mail!</span>"));
1568 } else if (type == PIDGIN_NOTIFY_POUNCE) {
1569 gtk_window_set_title(GTK_WINDOW(dialog), _("New Pounces"));
1571 button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1572 _("IM"), GTK_RESPONSE_YES);
1573 gtk_widget_set_sensitive(button, FALSE);
1574 spec_dialog->open_button = button;
1576 button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1577 PIDGIN_STOCK_MODIFY, GTK_RESPONSE_APPLY);
1578 gtk_widget_set_sensitive(button, FALSE);
1579 spec_dialog->edit_button = button;
1581 /* Translators: Make sure you translate "Dismiss" differently than
1582 "close"! This string is used in the "You have pounced" dialog
1583 that appears when one of your Buddy Pounces is triggered. In
1584 this context "Dismiss" means "I acknowledge that I've seen that
1585 this pounce was triggered--remove it from this list." Translating
1586 it as "Remove" is acceptable if you can't think of a more precise
1587 word. */
1588 button = gtk_dialog_add_button(GTK_DIALOG(dialog), _("Dismiss"),
1589 GTK_RESPONSE_NO);
1590 gtk_widget_set_sensitive(button, FALSE);
1591 spec_dialog->dismiss_button = button;
1593 g_signal_connect(G_OBJECT(dialog), "response",
1594 G_CALLBACK(pounce_response_cb), spec_dialog);
1596 column = gtk_tree_view_column_new();
1597 gtk_tree_view_column_set_title(column, _("Buddy"));
1598 gtk_tree_view_column_set_resizable(column, TRUE);
1599 rend = gtk_cell_renderer_pixbuf_new();
1600 gtk_tree_view_column_pack_start(column, rend, FALSE);
1602 gtk_tree_view_column_set_attributes(column, rend, "pixbuf", PIDGIN_POUNCE_ICON, NULL);
1603 rend = gtk_cell_renderer_text_new();
1604 gtk_tree_view_column_pack_start(column, rend, FALSE);
1605 gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_ALIAS);
1606 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1608 column = gtk_tree_view_column_new();
1609 gtk_tree_view_column_set_title(column, _("Event"));
1610 gtk_tree_view_column_set_resizable(column, TRUE);
1611 rend = gtk_cell_renderer_text_new();
1612 gtk_tree_view_column_pack_start(column, rend, FALSE);
1613 gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_EVENT);
1614 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1616 column = gtk_tree_view_column_new();
1617 gtk_tree_view_column_set_title(column, _("Message"));
1618 gtk_tree_view_column_set_resizable(column, TRUE);
1619 rend = gtk_cell_renderer_text_new();
1620 gtk_tree_view_column_pack_start(column, rend, FALSE);
1621 gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_TEXT);
1622 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1624 column = gtk_tree_view_column_new();
1625 gtk_tree_view_column_set_title(column, _("Date"));
1626 gtk_tree_view_column_set_resizable(column, TRUE);
1627 rend = gtk_cell_renderer_text_new();
1628 gtk_tree_view_column_pack_start(column, rend, FALSE);
1629 gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_DATE);
1630 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1632 label = gtk_label_new(NULL);
1633 gtk_label_set_markup(GTK_LABEL(label), _("<span weight=\"bold\" size=\"larger\">You have pounced!</span>"));
1635 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(spec_dialog->treeview));
1636 gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
1637 g_signal_connect(G_OBJECT(sel), "changed",
1638 G_CALLBACK(pounce_row_selected_cb), NULL);
1639 g_signal_connect(G_OBJECT(spec_dialog->treeview), "row-activated",
1640 G_CALLBACK(pounce_response_open_ims), NULL);
1643 button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1644 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
1646 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1647 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
1648 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1649 gtk_box_pack_start(GTK_BOX(vbox),
1650 pidgin_make_scrollable(spec_dialog->treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1),
1651 TRUE, TRUE, 2);
1653 return spec_dialog;
1656 static void
1657 signed_off_cb(PurpleConnection *gc, gpointer unused)
1659 /* Clear any pending emails for this account */
1660 pidgin_notify_emails(gc, 0, FALSE, NULL, NULL, NULL, NULL);
1662 if (mail_dialog != NULL && mail_dialog->total_count == 0)
1663 reset_mail_dialog(NULL);
1666 static void*
1667 pidgin_notify_get_handle(void)
1669 static int handle;
1670 return &handle;
1673 void pidgin_notify_init(void)
1675 void *handle = pidgin_notify_get_handle();
1677 purple_signal_connect(purple_connections_get_handle(), "signed-off",
1678 handle, PURPLE_CALLBACK(signed_off_cb), NULL);
1681 void pidgin_notify_uninit(void)
1683 purple_signals_disconnect_by_handle(pidgin_notify_get_handle());
1686 static PurpleNotifyUiOps ops =
1688 pidgin_notify_message,
1689 pidgin_notify_email,
1690 pidgin_notify_emails,
1691 pidgin_notify_formatted,
1692 pidgin_notify_searchresults,
1693 pidgin_notify_searchresults_new_rows,
1694 pidgin_notify_userinfo,
1695 pidgin_notify_uri,
1696 pidgin_close_notify,
1697 NULL,
1698 NULL,
1699 NULL,
1700 NULL
1703 PurpleNotifyUiOps *
1704 pidgin_notify_get_ui_ops(void)
1706 return &ops;