2 * @file gtknotify.c GTK+ Notification API
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
29 #include <gdk/gdkkeysyms.h>
32 #include "connection.h"
35 #include "pidginstock.h"
39 #include "gtkimhtml.h"
40 #include "gtknotify.h"
41 #include "gtkpounce.h"
52 PurpleAccount
*account
;
56 gboolean purple_has_handle
;
57 } PidginNotifyMailData
;
61 PurpleAccount
*account
;
64 } PidginNotifyPounceData
;
69 PurpleAccount
*account
;
74 PurpleNotifySearchResults
*results
;
76 } PidginNotifySearchResultsData
;
80 PurpleNotifySearchButton
*button
;
81 PidginNotifySearchResultsData
*data
;
83 } PidginNotifySearchResultsButtonData
;
101 COLUMNS_PIDGIN_POUNCE
105 typedef struct _PidginNotifyDialog
108 * This must be first so PidginNotifyDialog can masquerade as the
113 GtkTreeStore
*treemodel
;
115 GtkWidget
*open_button
;
116 GtkWidget
*dismiss_button
;
117 GtkWidget
*edit_button
;
120 } PidginNotifyDialog
;
125 PIDGIN_NOTIFY_POUNCE
,
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
,
138 static void pidgin_close_notify(PurpleNotifyType type
, void *ui_handle
);
141 message_response_cb(GtkDialog
*dialog
, gint id
, GtkWidget
*widget
)
143 purple_notify_close(PURPLE_NOTIFY_MESSAGE
, widget
);
147 pounce_response_close(PidginNotifyDialog
*dialog
)
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
,
157 gtk_tree_store_remove(dialog
->treemodel
, &iter
);
159 g_free(pounce_data
->pouncee
);
163 gtk_widget_destroy(pounce_dialog
->dialog
);
164 g_free(pounce_dialog
);
165 pounce_dialog
= NULL
;
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
,
178 if (pounce_data
!= NULL
) {
179 g_free(pounce_data
->pouncee
);
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
,
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
);
204 append_to_list(GtkTreeModel
*model
, GtkTreePath
*path
,
205 GtkTreeIter
*iter
, gpointer data
)
208 *list
= g_list_prepend(*list
, gtk_tree_path_copy(path
));
212 pounce_response_dismiss()
214 GtkTreeModel
*model
= GTK_TREE_MODEL(pounce_dialog
->treemodel
);
215 GtkTreeSelection
*selection
;
217 GtkTreeIter new_selection
;
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
;
232 /* This is the last thing in the list */
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
);
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
)) {
257 gtk_tree_selection_select_iter(selection
, &new_selection
);
259 gtk_tree_selection_select_iter(selection
, &iter
);
261 pounce_response_close(pounce_dialog
);
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();
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
;
284 list
= purple_pounces_get_all();
286 gtk_tree_model_get(GTK_TREE_MODEL(dialog
->treemodel
), iter
,
287 PIDGIN_POUNCE_DATA
, &pounce_data
,
290 for (; list
!= NULL
; list
= list
->next
) {
292 if (pounce
== pounce_data
->pounce
) {
293 pidgin_pounce_editor_show(pounce_data
->account
, NULL
, pounce_data
->pounce
);
298 purple_debug_warning("gtknotify", "Pounce was destroyed.\n");
302 pounce_response_cb(GtkDialog
*dlg
, gint id
, PidginNotifyDialog
*dialog
)
304 GtkTreeSelection
*selection
= NULL
;
307 case GTK_RESPONSE_CLOSE
:
308 case GTK_RESPONSE_DELETE_EVENT
:
309 pounce_response_close(dialog
);
311 case GTK_RESPONSE_YES
:
312 pounce_response_open_ims();
314 case GTK_RESPONSE_NO
:
315 pounce_response_dismiss();
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
,
326 pounce_row_selected_cb(GtkTreeView
*tv
, GtkTreePath
*path
,
327 GtkTreeViewColumn
*col
, gpointer data
)
329 GtkTreeSelection
*selection
;
332 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog
->treeview
));
334 count
= gtk_tree_selection_count_selected_rows(selection
);
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) {
343 PidginNotifyPounceData
*pounce_data
;
346 list
= gtk_tree_selection_get_selected_rows(selection
, NULL
);
347 gtk_tree_model_get_iter(GTK_TREE_MODEL(pounce_dialog
->treemodel
),
349 gtk_tree_model_get(GTK_TREE_MODEL(pounce_dialog
->treemodel
), &iter
,
350 PIDGIN_POUNCE_DATA
, &pounce_data
,
352 g_list_foreach(list
, (GFunc
)gtk_tree_path_free
, NULL
);
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
);
364 gtk_widget_set_sensitive(pounce_dialog
->open_button
, TRUE
);
365 gtk_widget_set_sensitive(pounce_dialog
->dismiss_button
, TRUE
);
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
);
376 reset_mail_dialog(GtkDialog
*unused
)
378 if (mail_dialog
->in_use
)
380 gtk_widget_destroy(mail_dialog
->dialog
);
386 email_response_cb(GtkDialog
*unused
, gint id
, PidginNotifyDialog
*unused2
)
388 PidginNotifyMailData
*data
= NULL
;
389 GtkTreeModel
*model
= GTK_TREE_MODEL(mail_dialog
->treemodel
);
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
);
408 pidgin_close_notify(PURPLE_NOTIFY_EMAILS
, data
);
410 if (gtk_tree_model_get_iter_first(model
, &iter
))
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
);
430 pidgin_close_notify(PURPLE_NOTIFY_EMAILS
, data
);
434 reset_mail_dialog(NULL
);
438 email_row_activated_cb(GtkTreeView
*tv
, GtkTreePath
*path
,
439 GtkTreeViewColumn
*col
, gpointer data
)
441 email_response_cb(NULL
, GTK_RESPONSE_YES
, NULL
);
445 formatted_close_cb(GtkWidget
*win
, GdkEvent
*event
, void *user_data
)
447 purple_notify_close(PURPLE_NOTIFY_FORMATTED
, win
);
452 searchresults_close_cb(PidginNotifySearchResultsData
*data
, GdkEvent
*event
, gpointer user_data
)
454 purple_notify_close(PURPLE_NOTIFY_SEARCHRESULTS
, data
);
459 searchresults_callback_wrapper_cb(GtkWidget
*widget
, PidginNotifySearchResultsButtonData
*bd
)
461 PidginNotifySearchResultsData
*data
= bd
->data
;
463 GtkTreeSelection
*selection
;
466 PurpleNotifySearchButton
*button
;
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
);
484 button
->callback(purple_account_get_connection(data
->account
), row
, data
->user_data
);
485 g_list_foreach(row
, (GFunc
)g_free
, NULL
);
490 pidgin_notify_message(PurpleNotifyMsgType type
, const char *title
,
491 const char *primary
, const char *secondary
)
496 GtkWidget
*img
= NULL
;
497 char label_text
[2048];
498 const char *icon_name
= NULL
;
499 char *primary_esc
, *secondary_esc
;
503 case PURPLE_NOTIFY_MSG_ERROR
:
504 icon_name
= PIDGIN_STOCK_DIALOG_ERROR
;
507 case PURPLE_NOTIFY_MSG_WARNING
:
508 icon_name
= PIDGIN_STOCK_DIALOG_WARNING
;
511 case PURPLE_NOTIFY_MSG_INFO
:
512 icon_name
= PIDGIN_STOCK_DIALOG_INFO
;
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
);
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
: ""));
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
);
572 selection_changed_cb(GtkTreeSelection
*sel
, PidginNotifyDialog
*dialog
)
576 PidginNotifyMailData
*data
;
577 gboolean active
= TRUE
;
579 if (gtk_tree_selection_get_selected(sel
, &model
, &iter
) == FALSE
)
583 gtk_tree_model_get(model
, &iter
, PIDGIN_MAIL_DATA
, &data
, -1);
584 if (data
->url
== NULL
)
588 gtk_widget_set_sensitive(dialog
->open_button
, active
);
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
));
603 mail_window_focus_cb(GtkWidget
*widget
, GdkEventFocus
*focus
, gpointer null
)
605 pidgin_set_urgent(GTK_WINDOW(widget
), FALSE
);
609 /* count == 0 means this is a detailed mail notification.
610 * count > 0 mean non-detailed.
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
;
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
)) {
626 gtk_tree_model_get(GTK_TREE_MODEL(treemodel
), &iter
,
627 PIDGIN_MAIL_DATA
, &data
, -1);
628 if (data
&& data
->account
== account
) {
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
);
636 pidgin_close_notify(PURPLE_NOTIFY_EMAILS
, data
);
637 /* We're completely done if we've processed all entries */
640 } else if (data
->count
> 0) {
644 mail_dialog
->total_count
-= data
->count
;
648 } while (advanced
|| gtk_tree_model_iter_next(GTK_TREE_MODEL(treemodel
), &iter
));
655 icon
= pidgin_create_prpl_icon(account
, PIDGIN_PRPL_ICON_MEDIUM
);
658 data
= g_new0(PidginNotifyMailData
, 1);
659 data
->purple_has_handle
= TRUE
;
660 gtk_tree_store_append(treemodel
, &iter
, 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
,
671 data
->account
= account
;
672 /* count == 0 indicates we're adding a single detailed e-mail */
673 data
->count
= count
> 0 ? count
: 1;
676 g_object_unref(icon
);
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
)
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
)
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
;
703 for ( ; count
; --count
) {
704 char *to_text
= NULL
;
705 char *from_text
= NULL
;
706 char *subject_text
= NULL
;
708 gboolean first
= TRUE
;
711 tmp
= g_markup_escape_text(*tos
, -1);
712 to_text
= g_strdup_printf("<b>%s</b>: %s\n", _("Account"), tmp
);
718 tmp
= g_markup_escape_text(*froms
, -1);
719 from_text
= g_strdup_printf("%s<b>%s</b>: %s\n", first
? "<br>" : "", _("Sender"), tmp
);
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
);
731 #define SAFE(x) ((x) ? (x) : "")
732 notification
= g_strdup_printf("%s%s%s", SAFE(to_text
), SAFE(from_text
), SAFE(subject_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
) {
742 data
->purple_has_handle
= FALSE
;
745 g_free(notification
);
752 notification
= g_strdup_printf(ngettext("%s has %d new message.",
753 "%s has %d new messages.",
756 data2
= pidgin_notify_add_mail(mail_dialog
->treemodel
, account
, notification
, urls
? *urls
: NULL
, count
, FALSE
, &new_data
);
757 if (data2
&& new_data
) {
759 data
->purple_has_handle
= FALSE
;
762 g_free(notification
);
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
);
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
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
;
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
);
801 formatted_input_cb(GtkWidget
*win
, GdkEventKey
*event
, gpointer data
)
803 if (event
->keyval
== GDK_Escape
)
805 purple_notify_close(PURPLE_NOTIFY_FORMATTED
, win
);
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
;
829 pidgin_notify_formatted(const char *title
, const char *primary
,
830 const char *secondary
, const char *text
)
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",
858 (secondary
? "\n" : ""),
859 (secondary
? secondary_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
);
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());
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
);
906 pidgin_notify_searchresults_new_rows(PurpleConnection
*gc
, PurpleNotifySearchResults
*results
,
909 PidginNotifySearchResultsData
*data
= data_
;
910 GtkListStore
*model
= data
->model
;
916 gtk_list_store_clear(data
->model
);
918 pixbuf
= pidgin_create_prpl_icon(purple_connection_get_account(gc
), PIDGIN_PRPL_ICON_SMALL
);
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);
926 for (column
= row
->data
; column
!= NULL
; column
= column
->next
) {
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
);
938 g_object_unref(pixbuf
);
942 pidgin_notify_searchresults(PurpleConnection
*gc
, const char *title
,
943 const char *primary
, const char *secondary
,
944 PurpleNotifySearchResults
*results
, gpointer user_data
)
948 GtkWidget
*close_button
;
951 GtkCellRenderer
*renderer
;
959 PidginNotifySearchResultsData
*data
;
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
: ""));
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
);
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
);
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),
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
);
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
);
1041 for (l
= results
->buttons
; l
; l
= l
->next
) {
1042 PurpleNotifySearchButton
*b
= l
->data
;
1043 GtkWidget
*button
= NULL
;
1045 case PURPLE_NOTIFY_BUTTON_LABELED
:
1047 button
= gtk_button_new_with_label(b
->label
);
1049 purple_debug_warning("gtknotify", "Missing button label\n");
1052 case PURPLE_NOTIFY_BUTTON_CONTINUE
:
1053 button
= gtk_dialog_add_button(GTK_DIALOG(window
), GTK_STOCK_GO_FORWARD
, GTK_RESPONSE_NONE
);
1055 case PURPLE_NOTIFY_BUTTON_ADD
:
1056 button
= gtk_dialog_add_button(GTK_DIALOG(window
), GTK_STOCK_ADD
, GTK_RESPONSE_NONE
);
1058 case PURPLE_NOTIFY_BUTTON_INFO
:
1059 button
= gtk_dialog_add_button(GTK_DIALOG(window
), PIDGIN_STOCK_TOOLBAR_USER_INFO
, GTK_RESPONSE_NONE
);
1061 case PURPLE_NOTIFY_BUTTON_IM
:
1062 button
= gtk_dialog_add_button(GTK_DIALOG(window
), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW
, GTK_RESPONSE_NONE
);
1064 case PURPLE_NOTIFY_BUTTON_JOIN
:
1065 button
= gtk_dialog_add_button(GTK_DIALOG(window
), PIDGIN_STOCK_CHAT
, GTK_RESPONSE_NONE
);
1067 case PURPLE_NOTIFY_BUTTON_INVITE
:
1068 button
= gtk_dialog_add_button(GTK_DIALOG(window
), PIDGIN_STOCK_INVITE
, GTK_RESPONSE_NONE
);
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);
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
;
1098 pidgin_notify_searchresults_new_rows(gc
, results
, data
);
1100 /* Show the window */
1101 pidgin_auto_parent_window(window
);
1103 gtk_widget_show(window
);
1107 /** Xerox'ed from Finch! How the tables have turned!! ;) **/
1108 /** User information. **/
1109 static GHashTable
*userinfo
;
1112 userinfo_hash(PurpleAccount
*account
, const char *who
)
1115 snprintf(key
, sizeof(key
), "%s - %s", purple_account_get_username(account
), purple_normalize(account
, who
));
1116 return g_utf8_strup(key
, -1);
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
);
1131 pidgin_notify_userinfo(PurpleConnection
*gc
, const char *who
,
1132 PurpleNotifyUserInfo
*user_info
)
1136 char *key
= userinfo_hash(purple_connection_get_account(gc
), who
);
1137 PidginUserInfo
*pinfo
= NULL
;
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
);
1152 ui_handle
= pinfo
->window
;
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
);
1160 pinfo
= g_new0(PidginUserInfo
, 1);
1161 pinfo
->window
= ui_handle
;
1163 g_hash_table_insert(userinfo
, key
, pinfo
);
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
;
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
);
1190 else if (ui_handle
!= NULL
)
1191 gtk_widget_destroy(GTK_WIDGET(ui_handle
));
1196 uri_command(GSList
*arg_list
, gboolean sync
)
1199 GError
*error
= NULL
;
1205 g_return_val_if_fail(arg_list
!= NULL
, FALSE
);
1207 program
= arg_list
->data
;
1208 purple_debug_misc("gtknotify", "Executing %s (%s)\n", program
,
1209 sync
? "sync" : "async");
1211 if (!purple_program_is_valid(program
)) {
1212 purple_debug_error("gtknotify", "Command \"%s\" is invalid\n",
1214 tmp
= g_strdup_printf(
1215 _("The browser command \"%s\" is invalid."),
1216 program
? program
: "(null)");
1217 purple_notify_error(NULL
, NULL
, _("Unable to open URL"), tmp
);
1223 argc
= g_slist_length(arg_list
);
1224 argv
= g_new(gchar
*, argc
+ 1);
1226 for (it
= arg_list
; it
; it
= g_slist_next(it
)) {
1227 if (purple_debug_is_verbose()) {
1228 purple_debug_misc("gtknotify", "argv[%d] = \"%s\"\n",
1229 i
, (gchar
*)it
->data
);
1231 argv
[i
++] = it
->data
;
1236 gint exit_status
= 0;
1238 if (g_spawn_sync(NULL
, argv
, NULL
, G_SPAWN_SEARCH_PATH
|
1239 G_SPAWN_STDOUT_TO_DEV_NULL
| G_SPAWN_STDERR_TO_DEV_NULL
,
1240 NULL
, NULL
, NULL
, NULL
, &exit_status
, &error
) &&
1247 purple_debug_error("gtknotify",
1248 "Error launching \"%s\": %s (status: %d)\n", program
,
1249 error
? error
->message
: "(null)", exit_status
);
1250 tmp
= g_strdup_printf(_("Error launching \"%s\": %s"), program
,
1251 error
? error
->message
: "(null)");
1252 g_error_free(error
);
1253 purple_notify_error(NULL
, NULL
, _("Unable to open URL"), tmp
);
1260 if (g_spawn_async(NULL
, argv
, NULL
, G_SPAWN_SEARCH_PATH
|
1261 G_SPAWN_STDOUT_TO_DEV_NULL
| G_SPAWN_STDERR_TO_DEV_NULL
, NULL
,
1262 NULL
, NULL
, &error
))
1268 purple_debug_warning("gtknotify", "Error launching \"%s\": %s\n",
1269 program
, error
? error
->message
: "(null)");
1270 g_error_free(error
);
1278 pidgin_notify_uri(const char *uri
)
1281 const char *web_browser
;
1283 gchar
*uri_escaped
, *uri_custom
= NULL
;
1284 GSList
*argv
= NULL
, *argv_remote
= NULL
;
1285 gchar
**usercmd_argv
= NULL
;
1287 /* Replace some special characters like $ with their percent-encoded
1288 value. This shouldn't be necessary because we shell-escape the entire
1289 arg before exec'ing the browser, however, we had a report that a URL
1290 containing $(xterm) was causing xterm to start on his system. This is
1291 obviously a bug on his system, but it's pretty easy for us to protect
1293 uri_escaped
= g_uri_escape_string(uri
, ":;/%#,+?=&@", FALSE
);
1295 web_browser
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
1296 "/browsers/browser");
1297 place
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/browsers/place");
1299 /* if they are running gnome, use the gnome web browser */
1300 if (purple_running_gnome() == TRUE
) {
1301 char *tmp
= g_find_program_in_path("xdg-open");
1303 argv
= g_slist_append(argv
, "gnome-open");
1305 argv
= g_slist_append(argv
, "xdg-open");
1307 argv
= g_slist_append(argv
, uri_escaped
);
1308 } else if (purple_running_osx() == TRUE
) {
1309 argv
= g_slist_append(argv
, "open");
1310 argv
= g_slist_append(argv
, uri_escaped
);
1311 } else if (purple_strequal(web_browser
, "epiphany") ||
1312 purple_strequal(web_browser
, "galeon"))
1314 argv
= g_slist_append(argv
, (gpointer
)web_browser
);
1316 if (place
== PIDGIN_BROWSER_NEW_WINDOW
)
1317 argv
= g_slist_append(argv
, "-w");
1318 else if (place
== PIDGIN_BROWSER_NEW_TAB
)
1319 argv
= g_slist_append(argv
, "-n");
1321 argv
= g_slist_append(argv
, uri_escaped
);
1322 } else if (purple_strequal(web_browser
, "xdg-open")) {
1323 argv
= g_slist_append(argv
, "xdg-open");
1324 argv
= g_slist_append(argv
, uri_escaped
);
1325 } else if (purple_strequal(web_browser
, "gnome-open")) {
1326 argv
= g_slist_append(argv
, "gnome-open");
1327 argv
= g_slist_append(argv
, uri_escaped
);
1328 } else if (purple_strequal(web_browser
, "kfmclient")) {
1329 argv
= g_slist_append(argv
, "kfmclient");
1330 argv
= g_slist_append(argv
, "openURL");
1331 argv
= g_slist_append(argv
, uri_escaped
);
1333 * Does Konqueror have options to open in new tab
1334 * and/or current window?
1336 } else if (purple_strequal(web_browser
, "mozilla") ||
1337 purple_strequal(web_browser
, "mozilla-firebird") ||
1338 purple_strequal(web_browser
, "firefox") ||
1339 purple_strequal(web_browser
, "seamonkey"))
1341 argv
= g_slist_append(argv
, (gpointer
)web_browser
);
1342 argv
= g_slist_append(argv
, uri_escaped
);
1344 g_assert(uri_custom
== NULL
);
1345 if (place
== PIDGIN_BROWSER_NEW_WINDOW
) {
1346 uri_custom
= g_strdup_printf("openURL(%s,new-window)",
1348 } else if (place
== PIDGIN_BROWSER_NEW_TAB
) {
1349 uri_custom
= g_strdup_printf("openURL(%s,new-tab)",
1351 } else if (place
== PIDGIN_BROWSER_CURRENT
) {
1352 uri_custom
= g_strdup_printf("openURL(%s)",
1356 if (uri_custom
!= NULL
) {
1357 argv_remote
= g_slist_append(argv_remote
,
1358 (gpointer
)web_browser
);
1360 /* Firefox 0.9 and higher require a "-a firefox" option
1361 * when using -remote commands. This breaks older
1362 * versions of mozilla. So we include this other handly
1363 * little string when calling firefox. If the API for
1364 * remote calls changes any more in firefox then firefox
1365 * should probably be split apart from mozilla-firebird
1366 * and mozilla... but this is good for now.
1368 if (purple_strequal(web_browser
, "firefox")) {
1369 argv_remote
= g_slist_append(argv_remote
, "-a");
1370 argv_remote
= g_slist_append(argv_remote
,
1374 argv_remote
= g_slist_append(argv_remote
, "-remote");
1375 argv_remote
= g_slist_append(argv_remote
, uri_custom
);
1377 } else if (purple_strequal(web_browser
, "netscape")) {
1378 argv
= g_slist_append(argv
, "netscape");
1379 argv
= g_slist_append(argv
, uri_escaped
);
1381 if (place
== PIDGIN_BROWSER_NEW_WINDOW
) {
1382 uri_custom
= g_strdup_printf("openURL(%s,new-window)",
1384 } else if (place
== PIDGIN_BROWSER_CURRENT
) {
1385 uri_custom
= g_strdup_printf("openURL(%s)",
1390 argv_remote
= g_slist_append(argv_remote
, "netscape");
1391 argv_remote
= g_slist_append(argv_remote
, "-remote");
1392 argv_remote
= g_slist_append(argv_remote
, uri_custom
);
1394 } else if (purple_strequal(web_browser
, "opera")) {
1395 argv
= g_slist_append(argv
, "opera");
1397 if (place
== PIDGIN_BROWSER_NEW_WINDOW
)
1398 argv
= g_slist_append(argv
, "-newwindow");
1399 else if (place
== PIDGIN_BROWSER_NEW_TAB
)
1400 argv
= g_slist_append(argv
, "-newpage");
1401 else if (place
== PIDGIN_BROWSER_CURRENT
)
1402 argv
= g_slist_append(argv
, "-activetab");
1403 /* `opera -remote "openURL(%s)"` command causes Pidgin's hang,
1404 * if there is no Opera instance running.
1407 argv
= g_slist_append(argv
, uri_escaped
);
1408 } else if (purple_strequal(web_browser
, "google-chrome")) {
1409 /* Google Chrome doesn't have command-line arguments that
1410 * control the opening of links from external calls. This is
1411 * controlled solely from a preference within Google Chrome.
1413 argv
= g_slist_append(argv
, "google-chrome");
1414 argv
= g_slist_append(argv
, uri_escaped
);
1415 } else if (purple_strequal(web_browser
, "chrome")) {
1416 /* Chromium doesn't have command-line arguments that control
1417 * the opening of links from external calls. This is controlled
1418 * solely from a preference within Chromium.
1420 argv
= g_slist_append(argv
, "chrome");
1421 argv
= g_slist_append(argv
, uri_escaped
);
1422 } else if (purple_strequal(web_browser
, "chromium-browser")) {
1423 /* Chromium doesn't have command-line arguments that control the
1424 * opening of links from external calls. This is controlled
1425 * solely from a preference within Chromium.
1427 argv
= g_slist_append(argv
, "chromium-browser");
1428 argv
= g_slist_append(argv
, uri_escaped
);
1429 } else if (purple_strequal(web_browser
, "custom")) {
1430 GError
*error
= NULL
;
1431 const char *usercmd_command
;
1432 gint usercmd_argc
, i
;
1433 gboolean uri_added
= FALSE
;
1435 usercmd_command
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
1436 "/browsers/manual_command");
1438 if (usercmd_command
== NULL
|| *usercmd_command
== '\0') {
1439 purple_notify_error(NULL
, NULL
, _("Unable to open URL"),
1440 _("The 'Manual' browser command has been "
1441 "chosen, but no command has been set."));
1442 g_free(uri_escaped
);
1446 if (!g_shell_parse_argv(usercmd_command
, &usercmd_argc
,
1447 &usercmd_argv
, &error
))
1449 purple_notify_error(NULL
, NULL
, _("Unable to open URL: "
1450 "the 'Manual' browser command seems invalid."),
1451 error
? error
->message
: NULL
);
1452 g_error_free(error
);
1453 g_free(uri_escaped
);
1457 for (i
= 0; i
< usercmd_argc
; i
++) {
1458 gchar
*cmd_part
= usercmd_argv
[i
];
1460 if (uri_added
|| strstr(cmd_part
, "%s") == NULL
) {
1461 argv
= g_slist_append(argv
, cmd_part
);
1465 uri_custom
= purple_strreplace(cmd_part
, "%s",
1467 argv
= g_slist_append(argv
, uri_custom
);
1471 /* There is no "%s" in the browser command. Assume the user
1472 * wanted the URL tacked on to the end of the command.
1475 argv
= g_slist_append(argv
, uri_escaped
);
1478 if (argv_remote
!= NULL
) {
1479 /* try the remote command first */
1480 if (!uri_command(argv_remote
, TRUE
))
1481 uri_command(argv
, FALSE
);
1483 uri_command(argv
, FALSE
);
1485 g_strfreev(usercmd_argv
);
1486 g_free(uri_escaped
);
1489 g_slist_free(argv_remote
);
1492 winpidgin_notify_uri(uri
);
1493 #endif /* !_WIN32 */
1499 pidgin_notify_pounce_add(PurpleAccount
*account
, PurplePounce
*pounce
,
1500 const char *alias
, const char *event
, const char *message
, const char *date
)
1504 PidginNotifyPounceData
*pounce_data
;
1505 gboolean first
= (pounce_dialog
== NULL
);
1507 if (pounce_dialog
== NULL
)
1508 pounce_dialog
= pidgin_create_notification_dialog(PIDGIN_NOTIFY_POUNCE
);
1510 icon
= pidgin_create_prpl_icon(account
, PIDGIN_PRPL_ICON_SMALL
);
1512 pounce_data
= g_new(PidginNotifyPounceData
, 1);
1514 pounce_data
->account
= account
;
1515 pounce_data
->pounce
= pounce
;
1516 pounce_data
->pouncee
= g_strdup(purple_pounce_get_pouncee(pounce
));
1518 gtk_tree_store_append(pounce_dialog
->treemodel
, &iter
, NULL
);
1520 gtk_tree_store_set(pounce_dialog
->treemodel
, &iter
,
1521 PIDGIN_POUNCE_ICON
, icon
,
1522 PIDGIN_POUNCE_ALIAS
, alias
,
1523 PIDGIN_POUNCE_EVENT
, event
,
1524 PIDGIN_POUNCE_TEXT
, (message
!= NULL
)? message
: _("No message"),
1525 PIDGIN_POUNCE_DATE
, date
,
1526 PIDGIN_POUNCE_DATA
, pounce_data
,
1530 GtkTreeSelection
*selection
=
1531 gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog
->treeview
));
1532 gtk_tree_selection_select_iter(selection
, &iter
);
1536 g_object_unref(icon
);
1538 gtk_widget_show_all(pounce_dialog
->dialog
);
1543 static PidginNotifyDialog
*
1544 pidgin_create_notification_dialog(PidginNotifyType type
)
1546 GtkTreeStore
*model
= NULL
;
1547 GtkWidget
*dialog
= NULL
;
1548 GtkWidget
*label
= NULL
;
1549 GtkCellRenderer
*rend
;
1550 GtkTreeViewColumn
*column
;
1551 GtkWidget
*button
= NULL
;
1552 GtkWidget
*vbox
= NULL
;
1553 GtkTreeSelection
*sel
;
1554 PidginNotifyDialog
*spec_dialog
= NULL
;
1556 g_return_val_if_fail(type
< PIDGIN_NOTIFY_TYPES
, NULL
);
1558 if (type
== PIDGIN_NOTIFY_MAIL
) {
1559 g_return_val_if_fail(mail_dialog
== NULL
, mail_dialog
);
1561 model
= gtk_tree_store_new(COLUMNS_PIDGIN_MAIL
,
1562 GDK_TYPE_PIXBUF
, G_TYPE_STRING
, G_TYPE_POINTER
);
1564 } else if (type
== PIDGIN_NOTIFY_POUNCE
) {
1565 g_return_val_if_fail(pounce_dialog
== NULL
, pounce_dialog
);
1567 model
= gtk_tree_store_new(COLUMNS_PIDGIN_POUNCE
,
1568 GDK_TYPE_PIXBUF
, G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_STRING
,
1569 G_TYPE_STRING
, G_TYPE_POINTER
);
1572 dialog
= gtk_dialog_new();
1574 /* Setup the dialog */
1575 gtk_container_set_border_width(GTK_CONTAINER(dialog
), PIDGIN_HIG_BOX_SPACE
);
1576 gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog
)->vbox
), PIDGIN_HIG_BOX_SPACE
);
1577 gtk_dialog_set_has_separator(GTK_DIALOG(dialog
), FALSE
);
1578 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog
)->vbox
), PIDGIN_HIG_BORDER
);
1581 vbox
= GTK_DIALOG(dialog
)->vbox
;
1583 /* Golden ratio it up! */
1584 gtk_widget_set_size_request(dialog
, 550, 400);
1586 spec_dialog
= g_new0(PidginNotifyDialog
, 1);
1587 spec_dialog
->dialog
= dialog
;
1589 spec_dialog
->treemodel
= model
;
1590 spec_dialog
->treeview
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(model
));
1591 g_object_unref(G_OBJECT(model
));
1593 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(spec_dialog
->treeview
), TRUE
);
1595 if (type
== PIDGIN_NOTIFY_MAIL
) {
1596 gtk_window_set_title(GTK_WINDOW(dialog
), _("New Mail"));
1597 gtk_window_set_role(GTK_WINDOW(dialog
), "new_mail_detailed");
1598 g_signal_connect(G_OBJECT(dialog
), "focus-in-event",
1599 G_CALLBACK(mail_window_focus_cb
), NULL
);
1601 gtk_dialog_add_button(GTK_DIALOG(dialog
),
1602 _("Open All Messages"), GTK_RESPONSE_ACCEPT
);
1604 button
= gtk_dialog_add_button(GTK_DIALOG(dialog
),
1605 PIDGIN_STOCK_OPEN_MAIL
, GTK_RESPONSE_YES
);
1606 spec_dialog
->open_button
= button
;
1608 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(spec_dialog
->treeview
), FALSE
);
1610 gtk_tree_view_set_search_column(GTK_TREE_VIEW(spec_dialog
->treeview
), PIDGIN_MAIL_TEXT
);
1611 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(spec_dialog
->treeview
),
1612 pidgin_tree_view_search_equal_func
, NULL
, NULL
);
1614 g_signal_connect(G_OBJECT(dialog
), "response",
1615 G_CALLBACK(email_response_cb
), spec_dialog
);
1616 g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(spec_dialog
->treeview
))),
1617 "changed", G_CALLBACK(selection_changed_cb
), spec_dialog
);
1618 g_signal_connect(G_OBJECT(spec_dialog
->treeview
), "row-activated", G_CALLBACK(email_row_activated_cb
), NULL
);
1620 column
= gtk_tree_view_column_new();
1621 gtk_tree_view_column_set_resizable(column
, TRUE
);
1622 rend
= gtk_cell_renderer_pixbuf_new();
1623 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
1625 gtk_tree_view_column_set_attributes(column
, rend
, "pixbuf", PIDGIN_MAIL_ICON
, NULL
);
1626 rend
= gtk_cell_renderer_text_new();
1627 gtk_tree_view_column_pack_start(column
, rend
, TRUE
);
1628 gtk_tree_view_column_set_attributes(column
, rend
, "markup", PIDGIN_MAIL_TEXT
, NULL
);
1629 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog
->treeview
), column
);
1631 label
= gtk_label_new(NULL
);
1632 gtk_label_set_markup(GTK_LABEL(label
), _("<span weight=\"bold\" size=\"larger\">You have mail!</span>"));
1634 } else if (type
== PIDGIN_NOTIFY_POUNCE
) {
1635 gtk_window_set_title(GTK_WINDOW(dialog
), _("New Pounces"));
1637 button
= gtk_dialog_add_button(GTK_DIALOG(dialog
),
1638 _("IM"), GTK_RESPONSE_YES
);
1639 gtk_widget_set_sensitive(button
, FALSE
);
1640 spec_dialog
->open_button
= button
;
1642 button
= gtk_dialog_add_button(GTK_DIALOG(dialog
),
1643 PIDGIN_STOCK_MODIFY
, GTK_RESPONSE_APPLY
);
1644 gtk_widget_set_sensitive(button
, FALSE
);
1645 spec_dialog
->edit_button
= button
;
1647 /* Translators: Make sure you translate "Dismiss" differently than
1648 "close"! This string is used in the "You have pounced" dialog
1649 that appears when one of your Buddy Pounces is triggered. In
1650 this context "Dismiss" means "I acknowledge that I've seen that
1651 this pounce was triggered--remove it from this list." Translating
1652 it as "Remove" is acceptable if you can't think of a more precise
1654 button
= gtk_dialog_add_button(GTK_DIALOG(dialog
), _("Dismiss"),
1656 gtk_widget_set_sensitive(button
, FALSE
);
1657 spec_dialog
->dismiss_button
= button
;
1659 g_signal_connect(G_OBJECT(dialog
), "response",
1660 G_CALLBACK(pounce_response_cb
), spec_dialog
);
1662 column
= gtk_tree_view_column_new();
1663 gtk_tree_view_column_set_title(column
, _("Buddy"));
1664 gtk_tree_view_column_set_resizable(column
, TRUE
);
1665 rend
= gtk_cell_renderer_pixbuf_new();
1666 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
1668 gtk_tree_view_column_set_attributes(column
, rend
, "pixbuf", PIDGIN_POUNCE_ICON
, NULL
);
1669 rend
= gtk_cell_renderer_text_new();
1670 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
1671 gtk_tree_view_column_add_attribute(column
, rend
, "text", PIDGIN_POUNCE_ALIAS
);
1672 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog
->treeview
), column
);
1674 column
= gtk_tree_view_column_new();
1675 gtk_tree_view_column_set_title(column
, _("Event"));
1676 gtk_tree_view_column_set_resizable(column
, TRUE
);
1677 rend
= gtk_cell_renderer_text_new();
1678 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
1679 gtk_tree_view_column_add_attribute(column
, rend
, "text", PIDGIN_POUNCE_EVENT
);
1680 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog
->treeview
), column
);
1682 column
= gtk_tree_view_column_new();
1683 gtk_tree_view_column_set_title(column
, _("Message"));
1684 gtk_tree_view_column_set_resizable(column
, TRUE
);
1685 rend
= gtk_cell_renderer_text_new();
1686 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
1687 gtk_tree_view_column_add_attribute(column
, rend
, "text", PIDGIN_POUNCE_TEXT
);
1688 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog
->treeview
), column
);
1690 column
= gtk_tree_view_column_new();
1691 gtk_tree_view_column_set_title(column
, _("Date"));
1692 gtk_tree_view_column_set_resizable(column
, TRUE
);
1693 rend
= gtk_cell_renderer_text_new();
1694 gtk_tree_view_column_pack_start(column
, rend
, FALSE
);
1695 gtk_tree_view_column_add_attribute(column
, rend
, "text", PIDGIN_POUNCE_DATE
);
1696 gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog
->treeview
), column
);
1698 label
= gtk_label_new(NULL
);
1699 gtk_label_set_markup(GTK_LABEL(label
), _("<span weight=\"bold\" size=\"larger\">You have pounced!</span>"));
1701 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(spec_dialog
->treeview
));
1702 gtk_tree_selection_set_mode(sel
, GTK_SELECTION_MULTIPLE
);
1703 g_signal_connect(G_OBJECT(sel
), "changed",
1704 G_CALLBACK(pounce_row_selected_cb
), NULL
);
1705 g_signal_connect(G_OBJECT(spec_dialog
->treeview
), "row-activated",
1706 G_CALLBACK(pounce_response_open_ims
), NULL
);
1709 gtk_dialog_add_button(GTK_DIALOG(dialog
),
1710 GTK_STOCK_CLOSE
, GTK_RESPONSE_CLOSE
);
1712 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
1713 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0);
1714 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 0);
1715 gtk_box_pack_start(GTK_BOX(vbox
),
1716 pidgin_make_scrollable(spec_dialog
->treeview
, GTK_POLICY_AUTOMATIC
, GTK_POLICY_ALWAYS
, GTK_SHADOW_IN
, -1, -1),
1723 signed_off_cb(PurpleConnection
*gc
, gpointer unused
)
1725 /* Clear any pending emails for this account */
1726 pidgin_notify_emails(gc
, 0, FALSE
, NULL
, NULL
, NULL
, NULL
);
1728 if (mail_dialog
!= NULL
&& mail_dialog
->total_count
== 0)
1729 reset_mail_dialog(NULL
);
1733 pidgin_notify_get_handle(void)
1739 void pidgin_notify_init(void)
1741 void *handle
= pidgin_notify_get_handle();
1743 purple_signal_connect(purple_connections_get_handle(), "signed-off",
1744 handle
, PURPLE_CALLBACK(signed_off_cb
), NULL
);
1747 void pidgin_notify_uninit(void)
1749 purple_signals_disconnect_by_handle(pidgin_notify_get_handle());
1752 static PurpleNotifyUiOps ops
=
1754 pidgin_notify_message
,
1755 pidgin_notify_email
,
1756 pidgin_notify_emails
,
1757 pidgin_notify_formatted
,
1758 pidgin_notify_searchresults
,
1759 pidgin_notify_searchresults_new_rows
,
1760 pidgin_notify_userinfo
,
1762 pidgin_close_notify
,
1770 pidgin_notify_get_ui_ops(void)