diff pidgin/pidgintooltip.c @ 21877:6bf73aea6450

Some utility functions for showing tooltips. This is used by the buddylist, the roomlist and the infopane in the conversation window. The userlist in chats can also use this, I think. It works OK, but it may be possible to make the code a bit nicer. Any suggestions?
author Sadrul Habib Chowdhury <imadil@gmail.com>
date Thu, 06 Dec 2007 07:33:53 +0000
parents
children b93c099dcfe4
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pidgintooltip.c	Thu Dec 06 07:33:53 2007 +0000
@@ -0,0 +1,268 @@
+/**
+ * @file pidgintooltip.c Pidgin Tooltip API
+ * @ingroup pidgin
+ */
+
+/* pidgin
+ *
+ * Pidgin 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., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#include "internal.h"
+#include "prefs.h"
+#include "pidgin.h"
+#include "pidgintooltip.h"
+
+struct
+{
+	GtkWidget *widget;
+	int timeout;
+	GdkRectangle tip_rect;
+	GtkWidget *tipwindow;
+	PidginTooltipPaint paint_tooltip;
+} pidgin_tooltip;
+
+typedef struct
+{
+	GtkWidget *widget;
+	gpointer userdata;
+	PidginTooltipCreateForTree create_tooltip;
+	PidginTooltipPaint paint_tooltip;
+	GtkTreePath *path;
+} PidginTooltipData;
+
+static void
+destroy_tooltip_data(PidginTooltipData *data)
+{
+	gtk_tree_path_free(data->path);
+	g_free(data);
+}
+
+void pidgin_tooltip_destroy()
+{
+	if (pidgin_tooltip.timeout > 0) {
+		g_source_remove(pidgin_tooltip.timeout);
+		pidgin_tooltip.timeout = 0;
+	}
+	if (pidgin_tooltip.tipwindow) {
+		gtk_widget_destroy(pidgin_tooltip.tipwindow);
+		pidgin_tooltip.tipwindow = NULL;
+	}
+}
+
+static void
+pidgin_tooltip_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
+{
+	if (pidgin_tooltip.paint_tooltip)
+		pidgin_tooltip.paint_tooltip(widget, event, data);
+}
+
+static void
+setup_tooltip_window(gpointer data, int w, int h)
+{
+	const char *name;
+	int sig;
+	int scr_w, scr_h, x, y;
+#if GTK_CHECK_VERSION(2,2,0)
+	int mon_num;
+	GdkScreen *screen = NULL;
+#endif
+	GdkRectangle mon_size;
+	GtkWidget *tipwindow = pidgin_tooltip.tipwindow;
+
+	name = gtk_window_get_title(GTK_WINDOW(pidgin_tooltip.widget));
+	gtk_widget_set_app_paintable(tipwindow, TRUE);
+	gtk_window_set_title(GTK_WINDOW(tipwindow), name ? name : _("Pidgin Tooltip"));
+	gtk_window_set_resizable(GTK_WINDOW(tipwindow), FALSE);
+	gtk_widget_set_name(tipwindow, "gtk-tooltips");
+	g_signal_connect(G_OBJECT(tipwindow), "expose_event",
+			G_CALLBACK(pidgin_tooltip_expose_event), data);
+
+#if GTK_CHECK_VERSION(2,2,0)
+	gdk_display_get_pointer(gdk_display_get_default(), &screen, &x, &y, NULL);
+	mon_num = gdk_screen_get_monitor_at_point(screen, x, y);
+	gdk_screen_get_monitor_geometry(screen, mon_num, &mon_size);
+
+	scr_w = mon_size.width + mon_size.x;
+	scr_h = mon_size.height + mon_size.y;
+#else
+	scr_w = gdk_screen_width();
+	scr_h = gdk_screen_height();
+	gdk_window_get_pointer(NULL, &x, &y, NULL);
+	mon_size.x = 0;
+	mon_size.y = 0;
+#endif
+
+#if GTK_CHECK_VERSION(2,2,0)
+	if (w > mon_size.width)
+		w = mon_size.width - 10;
+
+	if (h > mon_size.height)
+		h = mon_size.height - 10;
+#endif
+	x -= ((w >> 1) + 4);
+
+	if ((y + h + 4) > scr_h)
+		y = y - h - 5;
+	else
+		y = y + 6;
+
+	if (y < mon_size.y)
+		y = mon_size.y;
+
+	if (y != mon_size.y) {
+		if ((x + w) > scr_w)
+			x -= (x + w + 5) - scr_w;
+		else if (x < mon_size.x)
+			x = mon_size.x;
+	} else {
+		x -= (w / 2 + 10);
+		if (x < mon_size.x)
+			x = mon_size.x;
+	}
+
+	gtk_widget_set_size_request(tipwindow, w, h);
+	gtk_window_move(GTK_WINDOW(tipwindow), x, y);
+	gtk_widget_show(tipwindow);
+
+	/* Hide the tooltip when the widget is destroyed */
+	sig = g_signal_connect(G_OBJECT(pidgin_tooltip.widget), "destroy", G_CALLBACK(pidgin_tooltip_destroy), NULL);
+	g_signal_connect_swapped(G_OBJECT(tipwindow), "destroy", G_CALLBACK(g_source_remove), GINT_TO_POINTER(sig));
+}
+
+void pidgin_tooltip_show(GtkWidget *widget, gpointer userdata,
+		PidginTooltipCreate create_tooltip, PidginTooltipPaint paint_tooltip)
+{
+	GtkWidget *tipwindow;
+	int w, h;
+
+	pidgin_tooltip_destroy();
+	pidgin_tooltip.tipwindow = tipwindow = gtk_window_new(GTK_WINDOW_POPUP);
+	pidgin_tooltip.widget = gtk_widget_get_toplevel(widget);
+	pidgin_tooltip.paint_tooltip = paint_tooltip;
+	gtk_widget_ensure_style(tipwindow);
+
+	if (!create_tooltip(tipwindow, userdata, &w, &h)) {
+		pidgin_tooltip_destroy();
+		return;
+	}
+	setup_tooltip_window(userdata, w, h);
+}
+
+static void
+pidgin_tooltip_draw(PidginTooltipData *data)
+{
+	GtkWidget *tipwindow;
+	GtkTreePath *path = NULL;
+	int w, h;
+
+	if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(data->widget),
+				pidgin_tooltip.tip_rect.x,
+				pidgin_tooltip.tip_rect.y + (pidgin_tooltip.tip_rect.height/2),
+				&path, NULL, NULL, NULL)) {
+		pidgin_tooltip_destroy();
+		return;
+	}
+
+	if (data->path) {
+		if (gtk_tree_path_compare(data->path, path) == 0)
+			return;
+		gtk_tree_path_free(data->path);
+		data->path = NULL;
+	}
+
+	pidgin_tooltip.tipwindow = tipwindow = gtk_window_new(GTK_WINDOW_POPUP);
+	pidgin_tooltip.widget = gtk_widget_get_toplevel(data->widget);
+	pidgin_tooltip.paint_tooltip = data->paint_tooltip;
+	gtk_widget_ensure_style(tipwindow);
+
+	if (!data->create_tooltip(tipwindow, path, data->userdata, &w, &h)) {
+		pidgin_tooltip_destroy();
+		gtk_tree_path_free(path);
+		return;
+	}
+
+	data->path = path;
+	setup_tooltip_window(data->userdata, w, h);
+}
+
+static gboolean
+pidgin_tooltip_timeout(gpointer data)
+{
+	pidgin_tooltip.timeout = 0;
+	pidgin_tooltip_draw(data);
+	return FALSE;
+}
+
+static gboolean
+row_motion_cb(GtkWidget *tv, GdkEventMotion *event, gpointer userdata)
+{
+	GtkTreePath *path;
+	int delay;
+
+	/* XXX: probably use something more generic? */
+	delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay");
+	if (delay == 0)
+		return FALSE;
+
+	if (pidgin_tooltip.timeout) {
+		if ((event->y >= pidgin_tooltip.tip_rect.y) && ((event->y - pidgin_tooltip.tip_rect.height) <= pidgin_tooltip.tip_rect.y))
+			return FALSE;
+		/* We've left the cell.  Remove the timeout and create a new one below */
+		pidgin_tooltip_destroy();
+	}
+
+	gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL);
+
+	if (path == NULL) {
+		pidgin_tooltip_destroy();
+		return FALSE;
+	}
+
+	gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &pidgin_tooltip.tip_rect);
+
+	if (path)
+		gtk_tree_path_free(path);
+	pidgin_tooltip.timeout = g_timeout_add(delay, (GSourceFunc)pidgin_tooltip_timeout, userdata);
+
+	return FALSE;
+}
+
+static gboolean
+row_leave_cb(GtkWidget *tv, GdkEvent *event, gpointer userdata)
+{
+	pidgin_tooltip_destroy();
+	return FALSE;
+}
+
+gboolean pidgin_tooltip_setup_for_treeview(GtkWidget *tree, gpointer userdata,
+		PidginTooltipCreateForTree create_tooltip, PidginTooltipPaint paint_tooltip)
+{
+	PidginTooltipData *tdata = g_new0(PidginTooltipData, 1);
+	tdata->widget = tree;
+	tdata->userdata = userdata;
+	tdata->create_tooltip = create_tooltip;
+	tdata->paint_tooltip = paint_tooltip;
+
+	g_signal_connect(G_OBJECT(tree), "motion-notify-event", G_CALLBACK(row_motion_cb), tdata);
+	g_signal_connect(G_OBJECT(tree), "leave-notify-event", G_CALLBACK(row_leave_cb), NULL);
+	g_signal_connect_swapped(G_OBJECT(tree), "destroy", G_CALLBACK(destroy_tooltip_data), tdata);
+	return TRUE;
+}
+