diff pidgin/gtknotify.c @ 15374:5fe8042783c1

Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author Sean Egan <seanegan@gmail.com>
date Sat, 20 Jan 2007 02:32:10 +0000
parents
children 29e443e0613f
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtknotify.c	Sat Jan 20 02:32:10 2007 +0000
@@ -0,0 +1,1112 @@
+/**
+ * @file gtknotify.c GTK+ Notification API
+ * @ingroup gtkui
+ *
+ * gaim
+ *
+ * Gaim is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include "internal.h"
+#include "gtkgaim.h"
+
+#include <gdk/gdkkeysyms.h>
+
+#include "connection.h"
+#include "debug.h"
+#include "prefs.h"
+#include "gaimstock.h"
+#include "util.h"
+
+#include "gtkblist.h"
+#include "gtkimhtml.h"
+#include "gtknotify.h"
+#include "gtkutils.h"
+
+typedef struct
+{
+	GaimAccount *account;
+	char *url;
+	GtkWidget *label;
+	GtkTreeIter iter;
+	int count;
+} GaimNotifyMailData;
+
+typedef struct
+{
+	GaimAccount *account;
+	GtkListStore *model;
+	GtkWidget *treeview;
+	GtkWidget *window;
+	gpointer user_data;
+	GaimNotifySearchResults *results;
+
+} GaimNotifySearchResultsData;
+
+typedef struct
+{
+	GaimNotifySearchButton *button;
+	GaimNotifySearchResultsData *data;
+
+} GaimNotifySearchResultsButtonData;
+
+enum
+{
+	GAIM_MAIL_ICON,
+	GAIM_MAIL_TEXT,
+	GAIM_MAIL_DATA,
+	COLUMNS_GAIM_MAIL
+};
+
+typedef struct _GaimMailDialog GaimMailDialog;
+
+struct _GaimMailDialog
+{
+	GtkWidget *dialog;
+	GtkWidget *treeview;
+	GtkTreeStore *treemodel;
+	GtkLabel *label;
+	GtkWidget *open_button;
+	int total_count;
+	gboolean in_use;
+};
+
+static GaimMailDialog *mail_dialog = NULL;
+
+static void *gaim_gtk_notify_emails(GaimConnection *gc, size_t count, gboolean detailed,
+									const char **subjects,
+									const char **froms, const char **tos,
+									const char **urls);
+
+static void
+message_response_cb(GtkDialog *dialog, gint id, GtkWidget *widget)
+{
+	gaim_notify_close(GAIM_NOTIFY_MESSAGE, widget);
+}
+
+static void
+email_response_cb(GtkDialog *dlg, gint id, GaimMailDialog *dialog)
+{
+	GaimNotifyMailData *data = NULL;
+	GtkTreeIter iter;
+
+	if (id == GTK_RESPONSE_YES)
+	{
+		GtkTreeSelection *selection;
+
+		selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
+
+		if (gtk_tree_selection_get_selected(selection, NULL, &iter))
+		{
+			gtk_tree_model_get(GTK_TREE_MODEL(dialog->treemodel), &iter,
+								GAIM_MAIL_DATA, &data, -1);
+			gaim_notify_uri(NULL, data->url);
+
+			gtk_tree_store_remove(dialog->treemodel, &iter);
+			gaim_notify_close(GAIM_NOTIFY_EMAILS, data);
+
+			if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(mail_dialog->treemodel), &iter))
+				return;
+		}
+		else
+			return;
+	}
+	else
+	{
+		while (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(mail_dialog->treemodel), &iter))
+		{
+			gtk_tree_model_get(GTK_TREE_MODEL(dialog->treemodel), &iter,
+								GAIM_MAIL_DATA, &data, -1);
+
+			if (id == GTK_RESPONSE_ACCEPT)
+				gaim_notify_uri(NULL, data->url);
+
+			gtk_tree_store_remove(dialog->treemodel, &iter);
+			gaim_notify_close(GAIM_NOTIFY_EMAILS, data);
+		}
+	}
+	gtk_widget_destroy(dialog->dialog);
+	g_free(dialog);
+	mail_dialog = NULL;
+}
+
+static void
+reset_mail_dialog(GtkDialog *dialog)
+{
+	if (mail_dialog->in_use)
+		return;
+	gtk_widget_destroy(mail_dialog->dialog);
+	g_free(mail_dialog);
+	mail_dialog = NULL;
+}
+
+static void
+formatted_close_cb(GtkWidget *win, GdkEvent *event, void *user_data)
+{
+	gaim_notify_close(GAIM_NOTIFY_FORMATTED, win);
+}
+
+static void
+searchresults_close_cb(GaimNotifySearchResultsData *data, GdkEvent *event, gpointer user_data)
+{
+	gaim_notify_close(GAIM_NOTIFY_SEARCHRESULTS, data);
+}
+
+static void
+searchresults_callback_wrapper_cb(GtkWidget *widget, GaimNotifySearchResultsButtonData *bd)
+{
+	GaimNotifySearchResultsData *data = bd->data;
+
+	GtkTreeSelection *selection;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	GaimNotifySearchButton *button;
+	GList *row = NULL;
+	gchar *str;
+	int i;
+
+	g_return_if_fail(data != NULL);
+
+	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(data->treeview));
+
+	if (gtk_tree_selection_get_selected(selection, &model, &iter))
+	{
+		for (i = 1; i < gtk_tree_model_get_n_columns(GTK_TREE_MODEL(model)); i++) {
+			gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, i, &str, -1);
+			row = g_list_append(row, str);
+		}
+	}
+
+	button = bd->button;
+	button->callback(gaim_account_get_connection(data->account), row, data->user_data);
+	g_list_foreach(row, (GFunc)g_free, NULL);
+	g_list_free(row);
+}
+
+static void *
+gaim_gtk_notify_message(GaimNotifyMsgType type, const char *title,
+						const char *primary, const char *secondary)
+{
+	GtkWidget *dialog;
+	GtkWidget *hbox;
+	GtkWidget *label;
+	GtkWidget *img = NULL;
+	char label_text[2048];
+	const char *icon_name = NULL;
+	char *primary_esc, *secondary_esc;
+
+	switch (type)
+	{
+		case GAIM_NOTIFY_MSG_ERROR:
+			icon_name = GAIM_STOCK_DIALOG_ERROR;
+			break;
+
+		case GAIM_NOTIFY_MSG_WARNING:
+			icon_name = GAIM_STOCK_DIALOG_WARNING;
+			break;
+
+		case GAIM_NOTIFY_MSG_INFO:
+			icon_name = GAIM_STOCK_DIALOG_INFO;
+			break;
+
+		default:
+			icon_name = NULL;
+			break;
+	}
+
+	if (icon_name != NULL)
+	{
+		img = gtk_image_new_from_stock(icon_name, GTK_ICON_SIZE_DIALOG);
+		gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
+	}
+
+	dialog = gtk_dialog_new_with_buttons(title ? title : GAIM_ALERT_TITLE,
+										 NULL, 0, GTK_STOCK_CLOSE,
+										 GTK_RESPONSE_CLOSE, NULL);
+
+	gtk_window_set_role(GTK_WINDOW(dialog), "notify_dialog");
+
+	g_signal_connect(G_OBJECT(dialog), "response",
+					 G_CALLBACK(message_response_cb), dialog);
+
+	gtk_container_set_border_width(GTK_CONTAINER(dialog), GAIM_HIG_BORDER);
+	gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
+	gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
+	gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), GAIM_HIG_BORDER);
+	gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), GAIM_HIG_BOX_SPACE);
+
+	hbox = gtk_hbox_new(FALSE, GAIM_HIG_BORDER);
+	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox);
+
+	if (img != NULL)
+		gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
+
+	primary_esc = g_markup_escape_text(primary, -1);
+	secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
+	g_snprintf(label_text, sizeof(label_text),
+			   "<span weight=\"bold\" size=\"larger\">%s</span>\n\n%s",
+			   primary_esc, (secondary ? secondary_esc : ""));
+	g_free(primary_esc);
+	g_free(secondary_esc);
+
+	label = gtk_label_new(NULL);
+
+	gtk_label_set_markup(GTK_LABEL(label), label_text);
+	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+
+	gtk_widget_show_all(dialog);
+
+	return dialog;
+}
+
+static void
+selection_changed_cb(GtkTreeSelection *sel, GaimMailDialog *dialog)
+{
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+	GaimNotifyMailData *data;
+	gboolean active = TRUE;
+
+	if (gtk_tree_selection_get_selected(sel, &model, &iter) == FALSE)
+		active = FALSE;
+	else
+	{
+		gtk_tree_model_get(model, &iter, GAIM_MAIL_DATA, &data, -1);
+		if (data->url == NULL)
+			active = FALSE;
+	}
+
+	gtk_widget_set_sensitive(dialog->open_button, active);
+}
+
+static void *
+gaim_gtk_notify_email(GaimConnection *gc, const char *subject, const char *from,
+					  const char *to, const char *url)
+{
+	return gaim_gtk_notify_emails(gc, 1, (subject != NULL),
+								  (subject == NULL ? NULL : &subject),
+								  (from    == NULL ? NULL : &from),
+								  (to      == NULL ? NULL : &to),
+								  (url     == NULL ? NULL : &url));
+}
+
+static GtkWidget *
+gaim_gtk_get_mail_dialog()
+{
+	if (mail_dialog == NULL) {
+		GtkWidget *dialog = NULL;
+		GtkWidget *label;
+		GtkWidget *sw;
+		GtkCellRenderer *rend;
+		GtkTreeViewColumn *column;
+		GtkWidget *button = NULL;
+		GtkWidget *vbox = NULL;
+
+		dialog = gtk_dialog_new_with_buttons(_("New Mail"), NULL, 0,
+						     GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
+						     NULL);
+		gtk_window_set_role(GTK_WINDOW(dialog), "new_mail_detailed");
+
+		gtk_dialog_add_button(GTK_DIALOG(dialog),
+					 _("Open All Messages"), GTK_RESPONSE_ACCEPT);
+
+		button = gtk_dialog_add_button(GTK_DIALOG(dialog),
+						 GAIM_STOCK_OPEN_MAIL, GTK_RESPONSE_YES);
+
+		/* Setup the dialog */
+		gtk_container_set_border_width(GTK_CONTAINER(dialog), GAIM_HIG_BOX_SPACE);
+		gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), GAIM_HIG_BOX_SPACE);
+		gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
+		gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), GAIM_HIG_BORDER);
+
+		/* Vertical box */
+		vbox = GTK_DIALOG(dialog)->vbox;
+
+		/* Golden ratio it up! */
+		gtk_widget_set_size_request(dialog, 550, 400);
+
+		sw = gtk_scrolled_window_new(NULL, NULL);
+		gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_IN);
+		gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
+
+		mail_dialog = g_new0(GaimMailDialog, 1);
+		mail_dialog->dialog = dialog;
+		mail_dialog->open_button = button;
+
+		mail_dialog->treemodel = gtk_tree_store_new(COLUMNS_GAIM_MAIL,
+						GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_POINTER);
+		mail_dialog->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(mail_dialog->treemodel));
+		gtk_tree_view_set_search_column(GTK_TREE_VIEW(mail_dialog->treeview), GAIM_MAIL_TEXT);
+		gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(mail_dialog->treeview),
+			             gaim_gtk_tree_view_search_equal_func, NULL, NULL);
+
+		g_signal_connect(G_OBJECT(dialog), "response",
+						 G_CALLBACK(email_response_cb), mail_dialog);
+		g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(mail_dialog->treeview))),
+						 "changed", G_CALLBACK(selection_changed_cb), mail_dialog);
+
+		gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(mail_dialog->treeview), FALSE);
+		gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(mail_dialog->treeview), TRUE);
+		gtk_container_add(GTK_CONTAINER(sw), mail_dialog->treeview);
+
+		column = gtk_tree_view_column_new();
+		gtk_tree_view_column_set_resizable(column, TRUE);
+		rend = gtk_cell_renderer_pixbuf_new();
+		gtk_tree_view_column_pack_start(column, rend, FALSE);
+		gtk_tree_view_column_set_attributes(column, rend, "pixbuf", GAIM_MAIL_ICON, NULL);
+		rend = gtk_cell_renderer_text_new();
+		gtk_tree_view_column_pack_start(column, rend, TRUE);
+		gtk_tree_view_column_set_attributes(column, rend, "markup", GAIM_MAIL_TEXT, NULL);
+		gtk_tree_view_append_column(GTK_TREE_VIEW(mail_dialog->treeview), column);
+
+		label = gtk_label_new(NULL);
+		gtk_label_set_markup(GTK_LABEL(label), _("<span weight=\"bold\" size=\"larger\">You have mail!</span>"));
+		gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+		gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+		gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+		gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
+	}
+
+	return mail_dialog->dialog;
+}
+
+/* count == 0 means this is a detailed mail notification.
+ * count > 0 mean non-detailed.
+ */
+static void *
+gaim_gtk_notify_add_mail(GtkTreeStore *treemodel, GaimAccount *account, char *notification, const char *url, int count)
+{
+	GaimNotifyMailData *data = NULL;
+	GtkTreeIter iter;
+	GdkPixbuf *icon;
+	gboolean new_n = TRUE;
+
+	icon = gaim_gtk_create_prpl_icon(account, 1);
+
+	if (count > 0) {
+		/* Allow only one non-detailed email notification for each account */
+		if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(treemodel), &iter)) {
+			do {
+				gtk_tree_model_get(GTK_TREE_MODEL(treemodel), &iter,
+						GAIM_MAIL_DATA, &data, -1);
+				if (data->account == account && data->count > 0) {
+					new_n = FALSE;
+					g_free(data->url);
+					data->url = NULL;
+					mail_dialog->total_count -= data->count;
+					break;
+				}
+			} while (gtk_tree_model_iter_next(GTK_TREE_MODEL(treemodel), &iter));
+		}
+	}
+
+	if (new_n) {
+		data = g_new0(GaimNotifyMailData, 1);
+		gtk_tree_store_append(treemodel, &iter, NULL);
+	}
+
+	if (url != NULL)
+		data->url = g_strdup(url);
+
+	gtk_tree_store_set(treemodel, &iter,
+								GAIM_MAIL_ICON, icon,
+								GAIM_MAIL_TEXT, notification,
+								GAIM_MAIL_DATA, data,
+								-1);
+	data->iter = iter;              /* XXX: Do we use this for something? */
+	data->account = account;
+	data->count = count;
+	gtk_tree_model_get(GTK_TREE_MODEL(treemodel), &iter,
+						GAIM_MAIL_DATA, &data, -1);
+	if (icon)
+		g_object_unref(icon);
+	return data;
+}
+
+static void *
+gaim_gtk_notify_emails(GaimConnection *gc, size_t count, gboolean detailed,
+					   const char **subjects, const char **froms,
+					   const char **tos, const char **urls)
+{
+	GtkWidget *dialog = NULL;
+	char *notification;
+	GaimAccount *account;
+	GaimNotifyMailData *data = NULL;
+
+	account = gaim_connection_get_account(gc);
+	dialog = gaim_gtk_get_mail_dialog();  /* This creates mail_dialog if necessary */
+ 
+	mail_dialog->total_count += count;
+	if (detailed) {
+		while (count--) {
+			char *to_text = NULL;
+			char *from_text = NULL;
+			char *subject_text = NULL;
+			char *tmp;
+			gboolean first = TRUE;
+
+			if (tos != NULL) {
+				tmp = g_markup_escape_text(*tos, -1);
+				to_text = g_strdup_printf("<b>%s</b>: %s\n", _("Account"), tmp);
+				g_free(tmp);
+				first = FALSE;
+				tos++;
+			}
+			if (froms != NULL) {
+				tmp = g_markup_escape_text(*froms, -1);
+				from_text = g_strdup_printf("%s<b>%s</b>: %s\n", first ? "<br>" : "", _("Sender"), tmp);
+				g_free(tmp);
+				first = FALSE;
+				froms++;
+			}
+			if (subjects != NULL) {
+				tmp = g_markup_escape_text(*subjects, -1);
+				subject_text = g_strdup_printf("%s<b>%s</b>: %s", first ? "<br>" : "", _("Subject"), tmp);
+				g_free(tmp);
+				first = FALSE;
+				subjects++;
+			}
+#define SAFE(x) ((x) ? (x) : "")
+			notification = g_strdup_printf("%s%s%s", SAFE(to_text), SAFE(from_text), SAFE(subject_text));
+#undef SAFE
+			g_free(to_text);
+			g_free(from_text);
+			g_free(subject_text);
+
+			data = gaim_gtk_notify_add_mail(mail_dialog->treemodel, account, notification, urls ? *urls : NULL, 0);
+			g_free(notification);
+
+			if (urls != NULL)
+				urls++;
+		}
+	} else {
+		notification = g_strdup_printf(ngettext("%s has %d new message.",
+						   "%s has %d new messages.",
+						   (int)count),
+						   *tos, (int)count);
+		data = gaim_gtk_notify_add_mail(mail_dialog->treemodel, account, notification, urls ? *urls : NULL, count);
+		g_free(notification);
+	}
+
+	if (!GTK_WIDGET_VISIBLE(dialog)) {
+		GdkPixbuf *pixbuf = gtk_widget_render_icon(dialog, GAIM_STOCK_ICON_ONLINE_MSG,
+							   GTK_ICON_SIZE_BUTTON, NULL);
+		char *label_text = g_strdup_printf(ngettext("<b>You have %d new e-mail.</b>",
+							    "<b>You have %d new e-mails.</b>",
+							    mail_dialog->total_count), mail_dialog->total_count);
+		mail_dialog->in_use = TRUE;     /* So that _set_headline doesn't accidentally
+										   remove the notifications when replacing an
+										   old notification. */
+		gaim_gtk_blist_set_headline(label_text, 
+					    pixbuf, G_CALLBACK(gtk_widget_show_all), dialog,
+					    (GDestroyNotify)reset_mail_dialog);
+		mail_dialog->in_use = FALSE;
+		g_free(label_text);
+		if (pixbuf)
+			g_object_unref(pixbuf);
+	}
+
+	return NULL;
+}
+
+static gboolean
+formatted_input_cb(GtkWidget *win, GdkEventKey *event, gpointer data)
+{
+	if (event->keyval == GDK_Escape)
+	{
+		gaim_notify_close(GAIM_NOTIFY_FORMATTED, win);
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static void *
+gaim_gtk_notify_formatted(const char *title, const char *primary,
+						  const char *secondary, const char *text)
+{
+	GtkWidget *window;
+	GtkWidget *vbox;
+	GtkWidget *label;
+	GtkWidget *button;
+	GtkWidget *imhtml;
+	GtkWidget *frame;
+	int options = 0;
+	char label_text[2048];
+	char *linked_text, *primary_esc, *secondary_esc;
+
+	window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+	gtk_window_set_title(GTK_WINDOW(window), title);
+	gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
+	gtk_container_set_border_width(GTK_CONTAINER(window), GAIM_HIG_BORDER);
+
+	g_signal_connect(G_OBJECT(window), "delete_event",
+					 G_CALLBACK(formatted_close_cb), NULL);
+
+	/* Setup the main vbox */
+	vbox = gtk_vbox_new(FALSE, GAIM_HIG_BORDER);
+	gtk_container_add(GTK_CONTAINER(window), vbox);
+	gtk_widget_show(vbox);
+
+	/* Setup the descriptive label */
+	primary_esc = g_markup_escape_text(primary, -1);
+	secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
+	g_snprintf(label_text, sizeof(label_text),
+			   "<span weight=\"bold\" size=\"larger\">%s</span>%s%s",
+			   primary_esc,
+			   (secondary ? "\n" : ""),
+			   (secondary ? secondary_esc : ""));
+	g_free(primary_esc);
+	g_free(secondary_esc);
+
+	label = gtk_label_new(NULL);
+
+	gtk_label_set_markup(GTK_LABEL(label), label_text);
+	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+	gtk_widget_show(label);
+
+	/* Add the imhtml */
+	frame = gaim_gtk_create_imhtml(FALSE, &imhtml, NULL, NULL);
+	gtk_widget_set_name(imhtml, "gaim_gtknotify_imhtml");
+	gtk_imhtml_set_format_functions(GTK_IMHTML(imhtml),
+			gtk_imhtml_get_format_functions(GTK_IMHTML(imhtml)) | GTK_IMHTML_IMAGE);
+	gtk_widget_set_size_request(imhtml, 300, 250);
+	gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
+	gtk_widget_show(frame);
+
+	/* Add the Close button. */
+	button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
+	gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0);
+	gtk_widget_show(button);
+
+	g_signal_connect_swapped(G_OBJECT(button), "clicked",
+							 G_CALLBACK(formatted_close_cb), window);
+	g_signal_connect(G_OBJECT(window), "key_press_event",
+					 G_CALLBACK(formatted_input_cb), NULL);
+
+	/* Add the text to the gtkimhtml */
+	if (!gaim_prefs_get_bool("/gaim/gtk/conversations/show_incoming_formatting"))
+		options |= GTK_IMHTML_NO_COLOURS | GTK_IMHTML_NO_FONTS | GTK_IMHTML_NO_SIZES;
+
+	options |= GTK_IMHTML_NO_COMMENTS;
+	options |= GTK_IMHTML_NO_TITLE;
+	options |= GTK_IMHTML_NO_NEWLINE;
+	options |= GTK_IMHTML_NO_SCROLL;
+
+	/* Make sure URLs are clickable */
+	linked_text = gaim_markup_linkify(text);
+	gtk_imhtml_append_text(GTK_IMHTML(imhtml), linked_text, options);
+	g_free(linked_text);
+
+	/* Show the window */
+	gtk_widget_show(window);
+
+	return window;
+}
+
+static void
+gaim_gtk_notify_searchresults_new_rows(GaimConnection *gc, GaimNotifySearchResults *results,
+									   void *data_)
+{
+	GaimNotifySearchResultsData *data = data_;
+	GtkListStore *model = data->model;
+	GtkTreeIter iter;
+	GdkPixbuf *pixbuf;
+	guint col_num;
+	guint i;
+	guint j;
+
+	gtk_list_store_clear(data->model);
+
+	pixbuf = gaim_gtk_create_prpl_icon(gaim_connection_get_account(gc), 0.5);
+
+	/* +1 is for the automagically created Status column. */
+	col_num = gaim_notify_searchresults_get_columns_count(results) + 1;
+
+	for (i = 0; i < gaim_notify_searchresults_get_rows_count(results); i++) {
+		GList *row = gaim_notify_searchresults_row_get(results, i);
+
+		gtk_list_store_append(model, &iter);
+		gtk_list_store_set(model, &iter, 0, pixbuf, -1);
+
+		for (j = 1; j < col_num; j++) {
+			GValue v;
+			char *escaped = g_markup_escape_text(g_list_nth_data(row, j - 1), -1);
+
+			v.g_type = 0;
+			g_value_init(&v, G_TYPE_STRING);
+			g_value_set_string(&v, escaped);
+			gtk_list_store_set_value(model, &iter, j, &v);
+			g_free(escaped);
+		}
+	}
+
+	if (pixbuf != NULL)
+		g_object_unref(pixbuf);
+}
+
+static void *
+gaim_gtk_notify_searchresults(GaimConnection *gc, const char *title,
+							  const char *primary, const char *secondary,
+							  GaimNotifySearchResults *results, gpointer user_data)
+{
+	GtkWidget *window;
+	GtkWidget *treeview;
+	GtkWidget *close_button;
+	GType *col_types;
+	GtkListStore *model;
+	GtkCellRenderer *renderer;
+	guint col_num;
+	guint i;
+
+	GtkWidget *vbox;
+	GtkWidget *button_area;
+	GtkWidget *label;
+	GtkWidget *sw;
+	GaimNotifySearchResultsData *data;
+	char *label_text;
+	char *primary_esc, *secondary_esc;
+
+	g_return_val_if_fail(gc != NULL, NULL);
+	g_return_val_if_fail(results != NULL, NULL);
+
+	data = g_malloc(sizeof(GaimNotifySearchResultsData));
+	data->user_data = user_data;
+	data->results = results;
+
+	/* Create the window */
+	window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+	gtk_window_set_title(GTK_WINDOW(window), (title ? title :_("Search Results")));
+	gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
+	gtk_container_set_border_width(GTK_CONTAINER(window), GAIM_HIG_BORDER);
+
+	g_signal_connect_swapped(G_OBJECT(window), "delete_event",
+							 G_CALLBACK(searchresults_close_cb), data);
+
+	/* Setup the main vbox */
+	vbox = gtk_vbox_new(FALSE, GAIM_HIG_BORDER);
+	gtk_container_add(GTK_CONTAINER(window), vbox);
+	gtk_widget_show(vbox);
+
+	/* Setup the descriptive label */
+	primary_esc = (primary != NULL) ? g_markup_escape_text(primary, -1) : NULL;
+	secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
+	label_text = g_strdup_printf(
+			"<span weight=\"bold\" size=\"larger\">%s</span>%s%s",
+			(primary ? primary_esc : ""),
+			(primary && secondary ? "\n" : ""),
+			(secondary ? secondary_esc : ""));
+	g_free(primary_esc);
+	g_free(secondary_esc);
+	label = gtk_label_new(NULL);
+	gtk_label_set_markup(GTK_LABEL(label), label_text);
+	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+	gtk_widget_show(label);
+	g_free(label_text);
+
+	/* +1 is for the automagically created Status column. */
+	col_num = gaim_notify_searchresults_get_columns_count(results) + 1;
+
+	/* Setup the list model */
+	col_types = g_new0(GType, col_num);
+
+	/* There always is this first column. */
+	col_types[0] = GDK_TYPE_PIXBUF;
+	for (i = 1; i < col_num; i++) {
+		col_types[i] = G_TYPE_STRING;
+	}
+	model = gtk_list_store_newv(col_num, col_types);
+
+	/* Setup the scrolled window containing the treeview */
+	sw = gtk_scrolled_window_new(NULL, NULL);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
+								   GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
+										GTK_SHADOW_IN);
+	gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
+	gtk_widget_show(sw);
+
+	/* Setup the treeview */
+	treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
+	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
+	gtk_widget_set_size_request(treeview, 500, 400);
+	gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)),
+								GTK_SELECTION_SINGLE);
+	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), TRUE);
+	gtk_container_add(GTK_CONTAINER(sw), treeview);
+	gtk_widget_show(treeview);
+
+	renderer = gtk_cell_renderer_pixbuf_new();
+	gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview),
+					-1, "", renderer, "pixbuf", 0, NULL);
+
+	for (i = 1; i < col_num; i++) {
+		renderer = gtk_cell_renderer_text_new();
+
+		gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview), -1,
+				gaim_notify_searchresults_column_get_title(results, i-1),
+				renderer, "text", i, NULL);
+	}
+
+	/* Setup the button area */
+	button_area = gtk_hbutton_box_new();
+	gtk_box_pack_start(GTK_BOX(vbox), button_area, FALSE, FALSE, 0);
+	gtk_button_box_set_layout(GTK_BUTTON_BOX(button_area), GTK_BUTTONBOX_END);
+	gtk_box_set_spacing(GTK_BOX(button_area), GAIM_HIG_BORDER);
+	gtk_widget_show(button_area);
+
+	for (i = 0; i < g_list_length(results->buttons); i++) {
+		GaimNotifySearchButton *b = g_list_nth_data(results->buttons, i);
+		GtkWidget *button = NULL;
+		switch (b->type) {
+			case GAIM_NOTIFY_BUTTON_LABELED:
+				if(b->label) {
+					button = gtk_button_new_with_label(b->label);
+				} else {
+					gaim_debug_warning("gtknotify", "Missing button label");
+				}
+				break;
+			case GAIM_NOTIFY_BUTTON_CONTINUE:
+				button = gtk_button_new_from_stock(GTK_STOCK_GO_FORWARD);
+				break;
+			case GAIM_NOTIFY_BUTTON_ADD:
+				button = gtk_button_new_from_stock(GTK_STOCK_ADD);
+				break;
+			case GAIM_NOTIFY_BUTTON_INFO:
+				button = gtk_button_new_from_stock(GAIM_STOCK_INFO);
+				break;
+			case GAIM_NOTIFY_BUTTON_IM:
+				button = gtk_button_new_from_stock(GAIM_STOCK_IM);
+				break;
+			case GAIM_NOTIFY_BUTTON_JOIN:
+				button = gtk_button_new_from_stock(GAIM_STOCK_CHAT);
+				break;
+			case GAIM_NOTIFY_BUTTON_INVITE:
+				button = gtk_button_new_from_stock(GAIM_STOCK_INVITE);
+				break;
+			default:
+				gaim_debug_warning("gtknotify", "Incorrect button type: %d\n", b->type);
+		}
+		if (button != NULL) {
+			GaimNotifySearchResultsButtonData *bd;
+
+			gtk_box_pack_start(GTK_BOX(button_area), button, FALSE, FALSE, 0);
+			gtk_widget_show(button);
+
+			bd = g_new0(GaimNotifySearchResultsButtonData, 1);
+			bd->button = b;
+			bd->data = data;
+
+			g_signal_connect(G_OBJECT(button), "clicked",
+			                 G_CALLBACK(searchresults_callback_wrapper_cb), bd);
+			g_signal_connect_swapped(G_OBJECT(button), "destroy", G_CALLBACK(g_free), bd);
+		}
+	}
+
+	/* Add the Close button */
+	close_button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
+	gtk_box_pack_start(GTK_BOX(button_area), close_button, FALSE, FALSE, 0);
+	gtk_widget_show(close_button);
+
+	g_signal_connect_swapped(G_OBJECT(close_button), "clicked",
+	                         G_CALLBACK(searchresults_close_cb), data);
+
+	data->account = gc->account;
+	data->model = model;
+	data->treeview = treeview;
+	data->window = window;
+
+	/* Insert rows. */
+	gaim_gtk_notify_searchresults_new_rows(gc, results, data);
+
+	/* Show the window */
+	gtk_widget_show(window);
+	return data;
+}
+
+static void *
+gaim_gtk_notify_userinfo(GaimConnection *gc, const char *who,
+						 GaimNotifyUserInfo *user_info)
+{
+	char *primary, *info;
+	void *ui_handle;
+
+	primary = g_strdup_printf(_("Info for %s"), who);
+	info = gaim_notify_user_info_get_text_with_newline(user_info, "<br />");
+	ui_handle = gaim_gtk_notify_formatted(_("Buddy Information"), primary, NULL, info);
+	g_free(info);
+	g_free(primary);
+	return ui_handle;
+}
+
+static void
+gaim_gtk_close_notify(GaimNotifyType type, void *ui_handle)
+{
+	if (type == GAIM_NOTIFY_EMAIL || type == GAIM_NOTIFY_EMAILS)
+	{
+		GaimNotifyMailData *data = (GaimNotifyMailData *)ui_handle;
+
+		g_free(data->url);
+		g_free(data);
+	}
+	else if (type == GAIM_NOTIFY_SEARCHRESULTS)
+	{
+		GaimNotifySearchResultsData *data = (GaimNotifySearchResultsData *)ui_handle;
+
+		gtk_widget_destroy(data->window);
+		gaim_notify_searchresults_free(data->results);
+
+		g_free(data);
+	}
+	else if (ui_handle != NULL)
+		gtk_widget_destroy(GTK_WIDGET(ui_handle));
+}
+
+#ifndef _WIN32
+static gint
+uri_command(const char *command, gboolean sync)
+{
+	gchar *tmp;
+	GError *error = NULL;
+	gint ret = 0;
+
+	gaim_debug_misc("gtknotify", "Executing %s\n", command);
+
+	if (!gaim_program_is_valid(command))
+	{
+		tmp = g_strdup_printf(_("The browser command \"%s\" is invalid."),
+							  command ? command : "(none)");
+		gaim_notify_error(NULL, NULL, _("Unable to open URL"), tmp);
+		g_free(tmp);
+
+	}
+	else if (sync)
+	{
+		gint status;
+
+		if (!g_spawn_command_line_sync(command, NULL, NULL, &status, &error))
+		{
+			tmp = g_strdup_printf(_("Error launching \"%s\": %s"),
+										command, error->message);
+			gaim_notify_error(NULL, NULL, _("Unable to open URL"), tmp);
+			g_free(tmp);
+			g_error_free(error);
+		}
+		else
+			ret = status;
+	}
+	else
+	{
+		if (!g_spawn_command_line_async(command, &error))
+		{
+			tmp = g_strdup_printf(_("Error launching \"%s\": %s"),
+										command, error->message);
+			gaim_notify_error(NULL, NULL, _("Unable to open URL"), tmp);
+			g_free(tmp);
+			g_error_free(error);
+		}
+	}
+
+	return ret;
+}
+#endif /* _WIN32 */
+
+static void *
+gaim_gtk_notify_uri(const char *uri)
+{
+#ifndef _WIN32
+	char *escaped = g_shell_quote(uri);
+	char *command = NULL;
+	char *remote_command = NULL;
+	const char *web_browser;
+	int place;
+
+	web_browser = gaim_prefs_get_string("/gaim/gtk/browsers/browser");
+	place = gaim_prefs_get_int("/gaim/gtk/browsers/place");
+
+	/* if they are running gnome, use the gnome web browser */
+	if (gaim_running_gnome() == TRUE)
+	{
+		command = g_strdup_printf("gnome-open %s", escaped);
+	}
+	else if (gaim_running_osx() == TRUE)
+	{
+		command = g_strdup_printf("open %s", escaped);
+	}
+	else if (!strcmp(web_browser, "epiphany") ||
+		!strcmp(web_browser, "galeon"))
+	{
+		if (place == GAIM_BROWSER_NEW_WINDOW)
+			command = g_strdup_printf("%s -w %s", web_browser, escaped);
+		else if (place == GAIM_BROWSER_NEW_TAB)
+			command = g_strdup_printf("%s -n %s", web_browser, escaped);
+		else
+			command = g_strdup_printf("%s %s", web_browser, escaped);
+	}
+	else if (!strcmp(web_browser, "gnome-open"))
+	{
+		command = g_strdup_printf("gnome-open %s", escaped);
+	}
+	else if (!strcmp(web_browser, "kfmclient"))
+	{
+		command = g_strdup_printf("kfmclient openURL %s", escaped);
+		/*
+		 * Does Konqueror have options to open in new tab
+		 * and/or current window?
+		 */
+	}
+	else if (!strcmp(web_browser, "mozilla") ||
+			 !strcmp(web_browser, "mozilla-firebird") ||
+			 !strcmp(web_browser, "firefox") ||
+			 !strcmp(web_browser, "seamonkey"))
+	{
+		char *args = "";
+
+		command = g_strdup_printf("%s %s", web_browser, escaped);
+
+		/*
+		 * Firefox 0.9 and higher require a "-a firefox" option when
+		 * using -remote commands.  This breaks older versions of
+		 * mozilla.  So we include this other handly little string
+		 * when calling firefox.  If the API for remote calls changes
+		 * any more in firefox then firefox should probably be split
+		 * apart from mozilla-firebird and mozilla... but this is good
+		 * for now.
+		 */
+		if (!strcmp(web_browser, "firefox"))
+			args = "-a firefox";
+
+		if (place == GAIM_BROWSER_NEW_WINDOW)
+			remote_command = g_strdup_printf("%s %s -remote "
+											 "openURL(%s,new-window)",
+											 web_browser, args, escaped);
+		else if (place == GAIM_BROWSER_NEW_TAB)
+			remote_command = g_strdup_printf("%s %s -remote "
+											 "openURL(%s,new-tab)",
+											 web_browser, args, escaped);
+		else if (place == GAIM_BROWSER_CURRENT)
+			remote_command = g_strdup_printf("%s %s -remote "
+											 "openURL(%s)",
+											 web_browser, args, escaped);
+	}
+	else if (!strcmp(web_browser, "netscape"))
+	{
+		command = g_strdup_printf("netscape %s", escaped);
+
+		if (place == GAIM_BROWSER_NEW_WINDOW)
+		{
+			remote_command = g_strdup_printf("netscape -remote "
+											 "openURL(%s,new-window)",
+											 escaped);
+		}
+		else if (place == GAIM_BROWSER_CURRENT)
+		{
+			remote_command = g_strdup_printf("netscape -remote "
+											 "openURL(%s)", escaped);
+		}
+	}
+	else if (!strcmp(web_browser, "opera"))
+	{
+		if (place == GAIM_BROWSER_NEW_WINDOW)
+			command = g_strdup_printf("opera -newwindow %s", escaped);
+		else if (place == GAIM_BROWSER_NEW_TAB)
+			command = g_strdup_printf("opera -newpage %s", escaped);
+		else if (place == GAIM_BROWSER_CURRENT)
+		{
+			remote_command = g_strdup_printf("opera -remote "
+											 "openURL(%s)", escaped);
+			command = g_strdup_printf("opera %s", escaped);
+		}
+		else
+			command = g_strdup_printf("opera %s", escaped);
+
+	}
+	else if (!strcmp(web_browser, "custom"))
+	{
+		const char *web_command;
+
+		web_command = gaim_prefs_get_path("/gaim/gtk/browsers/command");
+
+		if (web_command == NULL || *web_command == '\0')
+		{
+			gaim_notify_error(NULL, NULL, _("Unable to open URL"),
+							  _("The 'Manual' browser command has been "
+								"chosen, but no command has been set."));
+			return NULL;
+		}
+
+		if (strstr(web_command, "%s"))
+			command = gaim_strreplace(web_command, "%s", escaped);
+		else
+		{
+			/*
+			 * There is no "%s" in the browser command.  Assume the user
+			 * wanted the URL tacked on to the end of the command.
+			 */
+			command = g_strdup_printf("%s %s", web_command, escaped);
+		}
+	}
+
+	g_free(escaped);
+
+	if (remote_command != NULL)
+	{
+		/* try the remote command first */
+		if (uri_command(remote_command, TRUE) != 0)
+			uri_command(command, FALSE);
+
+		g_free(remote_command);
+
+	}
+	else
+		uri_command(command, FALSE);
+
+	g_free(command);
+
+#else /* !_WIN32 */
+	gtkwgaim_notify_uri(uri);
+#endif /* !_WIN32 */
+
+	return NULL;
+}
+
+static GaimNotifyUiOps ops =
+{
+	gaim_gtk_notify_message,
+	gaim_gtk_notify_email,
+	gaim_gtk_notify_emails,
+	gaim_gtk_notify_formatted,
+	gaim_gtk_notify_searchresults,
+	gaim_gtk_notify_searchresults_new_rows,
+	gaim_gtk_notify_userinfo,
+	gaim_gtk_notify_uri,
+	gaim_gtk_close_notify
+};
+
+GaimNotifyUiOps *
+gaim_gtk_notify_get_ui_ops(void)
+{
+	return &ops;
+}