Branch for 2.7.7.
[pidgin-git.git] / pidgin / pidgintooltip.c
blobd97e1945c3aadaf3d67801cb1ea3746e79a3dc66
1 /**
2 * @file pidgintooltip.c Pidgin Tooltip 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
27 #include "internal.h"
28 #include "prefs.h"
29 #include "pidgin.h"
30 #include "pidgintooltip.h"
31 #include "debug.h"
33 static gboolean enable_tooltips;
34 static int tooltip_delay = -1;
36 struct
38 GtkWidget *widget;
39 int timeout;
40 GdkRectangle tip_rect;
41 GtkWidget *tipwindow;
42 PidginTooltipPaint paint_tooltip;
43 } pidgin_tooltip;
45 typedef struct
47 GtkWidget *widget;
48 gpointer userdata;
49 PidginTooltipPaint paint_tooltip;
50 union {
51 struct {
52 PidginTooltipCreateForTree create_tooltip;
53 GtkTreePath *path;
54 } treeview;
55 struct {
56 PidginTooltipCreate create_tooltip;
57 } widget;
58 } common;
59 } PidginTooltipData;
61 static void
62 initialize_tooltip_delay()
64 #if GTK_CHECK_VERSION(2,14,0)
65 GtkSettings *settings;
66 #endif
68 if (tooltip_delay != -1)
69 return;
71 #if GTK_CHECK_VERSION(2,14,0)
72 settings = gtk_settings_get_default();
74 g_object_get(settings, "gtk-enable-tooltips", &enable_tooltips, NULL);
75 g_object_get(settings, "gtk-tooltip-timeout", &tooltip_delay, NULL);
76 #else
77 tooltip_delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay");
78 enable_tooltips = (tooltip_delay != 0);
79 #endif
82 static void
83 destroy_tooltip_data(PidginTooltipData *data)
85 if (data->common.treeview.path)
86 gtk_tree_path_free(data->common.treeview.path);
87 pidgin_tooltip_destroy();
88 g_free(data);
91 void pidgin_tooltip_destroy()
93 if (pidgin_tooltip.timeout > 0) {
94 g_source_remove(pidgin_tooltip.timeout);
95 pidgin_tooltip.timeout = 0;
97 if (pidgin_tooltip.tipwindow) {
98 gtk_widget_destroy(pidgin_tooltip.tipwindow);
99 pidgin_tooltip.tipwindow = NULL;
103 static gboolean
104 pidgin_tooltip_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
106 if (pidgin_tooltip.paint_tooltip) {
107 gtk_paint_flat_box(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
108 NULL, widget, "tooltip", 0, 0, -1, -1);
109 pidgin_tooltip.paint_tooltip(widget, data);
111 return FALSE;
114 static GtkWidget*
115 setup_tooltip_window(void)
117 const char *name;
118 GtkWidget *tipwindow;
120 tipwindow = gtk_window_new(GTK_WINDOW_POPUP);
121 name = gtk_window_get_title(GTK_WINDOW(pidgin_tooltip.widget));
122 gtk_window_set_type_hint(GTK_WINDOW(tipwindow), GDK_WINDOW_TYPE_HINT_TOOLTIP);
123 gtk_widget_set_app_paintable(tipwindow, TRUE);
124 gtk_window_set_title(GTK_WINDOW(tipwindow), name ? name : _("Pidgin Tooltip"));
125 gtk_window_set_resizable(GTK_WINDOW(tipwindow), FALSE);
126 gtk_widget_set_name(tipwindow, "gtk-tooltips");
127 gtk_widget_ensure_style(tipwindow);
128 gtk_widget_realize(tipwindow);
129 return tipwindow;
132 static void
133 setup_tooltip_window_position(gpointer data, int w, int h)
135 int sig;
136 int scr_w, scr_h, x, y, dy;
137 int mon_num;
138 GdkScreen *screen = NULL;
139 GdkRectangle mon_size;
140 GtkWidget *tipwindow = pidgin_tooltip.tipwindow;
142 gdk_display_get_pointer(gdk_display_get_default(), &screen, &x, &y, NULL);
143 mon_num = gdk_screen_get_monitor_at_point(screen, x, y);
144 gdk_screen_get_monitor_geometry(screen, mon_num, &mon_size);
146 scr_w = mon_size.width + mon_size.x;
147 scr_h = mon_size.height + mon_size.y;
149 dy = gdk_display_get_default_cursor_size(gdk_display_get_default()) / 2;
151 if (w > mon_size.width)
152 w = mon_size.width - 10;
154 if (h > mon_size.height)
155 h = mon_size.height - 10;
157 x -= ((w >> 1) + 4);
159 if ((y + h + 4) > scr_h)
160 y = y - h - dy - 5;
161 else
162 y = y + dy + 6;
164 if (y < mon_size.y)
165 y = mon_size.y;
167 if (y != mon_size.y) {
168 if ((x + w) > scr_w)
169 x -= (x + w + 5) - scr_w;
170 else if (x < mon_size.x)
171 x = mon_size.x;
172 } else {
173 x -= (w / 2 + 10);
174 if (x < mon_size.x)
175 x = mon_size.x;
178 gtk_widget_set_size_request(tipwindow, w, h);
179 gtk_window_move(GTK_WINDOW(tipwindow), x, y);
180 gtk_widget_show(tipwindow);
182 g_signal_connect(G_OBJECT(tipwindow), "expose_event",
183 G_CALLBACK(pidgin_tooltip_expose_event), data);
185 /* Hide the tooltip when the widget is destroyed */
186 sig = g_signal_connect(G_OBJECT(pidgin_tooltip.widget), "destroy", G_CALLBACK(pidgin_tooltip_destroy), NULL);
187 g_signal_connect_swapped(G_OBJECT(tipwindow), "destroy", G_CALLBACK(g_source_remove), GINT_TO_POINTER(sig));
190 void pidgin_tooltip_show(GtkWidget *widget, gpointer userdata,
191 PidginTooltipCreate create_tooltip, PidginTooltipPaint paint_tooltip)
193 GtkWidget *tipwindow;
194 int w, h;
196 pidgin_tooltip_destroy();
198 pidgin_tooltip.widget = gtk_widget_get_toplevel(widget);
199 pidgin_tooltip.tipwindow = tipwindow = setup_tooltip_window();
200 pidgin_tooltip.paint_tooltip = paint_tooltip;
202 if (!create_tooltip(tipwindow, userdata, &w, &h)) {
203 pidgin_tooltip_destroy();
204 return;
206 setup_tooltip_window_position(userdata, w, h);
209 static void
210 reset_data_treepath(PidginTooltipData *data)
212 gtk_tree_path_free(data->common.treeview.path);
213 data->common.treeview.path = NULL;
216 static void
217 pidgin_tooltip_draw(PidginTooltipData *data)
219 GtkWidget *tipwindow;
220 int w, h;
222 pidgin_tooltip_destroy();
224 pidgin_tooltip.widget = gtk_widget_get_toplevel(data->widget);
225 pidgin_tooltip.tipwindow = tipwindow = setup_tooltip_window();
226 pidgin_tooltip.paint_tooltip = data->paint_tooltip;
228 if (!data->common.widget.create_tooltip(tipwindow, data->userdata, &w, &h)) {
229 if (tipwindow == pidgin_tooltip.tipwindow)
230 pidgin_tooltip_destroy();
231 return;
234 setup_tooltip_window_position(data->userdata, w, h);
237 static void
238 pidgin_tooltip_draw_tree(PidginTooltipData *data)
240 GtkWidget *tipwindow;
241 GtkTreePath *path = NULL;
242 int w, h;
244 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(data->widget),
245 pidgin_tooltip.tip_rect.x,
246 pidgin_tooltip.tip_rect.y + (pidgin_tooltip.tip_rect.height/2),
247 &path, NULL, NULL, NULL)) {
248 pidgin_tooltip_destroy();
249 return;
252 if (data->common.treeview.path) {
253 if (gtk_tree_path_compare(data->common.treeview.path, path) == 0) {
254 gtk_tree_path_free(path);
255 return;
257 gtk_tree_path_free(data->common.treeview.path);
258 data->common.treeview.path = NULL;
261 pidgin_tooltip_destroy();
263 pidgin_tooltip.widget = gtk_widget_get_toplevel(data->widget);
264 pidgin_tooltip.tipwindow = tipwindow = setup_tooltip_window();
265 pidgin_tooltip.paint_tooltip = data->paint_tooltip;
267 if (!data->common.treeview.create_tooltip(tipwindow, path, data->userdata, &w, &h)) {
268 if (tipwindow == pidgin_tooltip.tipwindow)
269 pidgin_tooltip_destroy();
270 gtk_tree_path_free(path);
271 return;
274 setup_tooltip_window_position(data->userdata, w, h);
276 data->common.treeview.path = path;
277 g_signal_connect_swapped(G_OBJECT(pidgin_tooltip.tipwindow), "destroy",
278 G_CALLBACK(reset_data_treepath), data);
281 static gboolean
282 pidgin_tooltip_timeout(gpointer data)
284 PidginTooltipData *tdata = data;
285 pidgin_tooltip.timeout = 0;
286 if (GTK_IS_TREE_VIEW(tdata->widget))
287 pidgin_tooltip_draw_tree(data);
288 else
289 pidgin_tooltip_draw(data);
290 return FALSE;
293 static gboolean
294 row_motion_cb(GtkWidget *tv, GdkEventMotion *event, gpointer userdata)
296 GtkTreePath *path;
298 if (event->window != gtk_tree_view_get_bin_window(GTK_TREE_VIEW(tv)))
299 return FALSE; /* The cursor is probably on the TreeView's header. */
301 initialize_tooltip_delay();
302 if (!enable_tooltips)
303 return FALSE;
305 if (pidgin_tooltip.timeout) {
306 if ((event->y >= pidgin_tooltip.tip_rect.y) && ((event->y - pidgin_tooltip.tip_rect.height) <= pidgin_tooltip.tip_rect.y))
307 return FALSE;
308 /* We've left the cell. Remove the timeout and create a new one below */
309 pidgin_tooltip_destroy();
312 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL);
314 if (path == NULL) {
315 pidgin_tooltip_destroy();
316 return FALSE;
319 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &pidgin_tooltip.tip_rect);
320 gtk_tree_path_free(path);
322 pidgin_tooltip.timeout = g_timeout_add(tooltip_delay, (GSourceFunc)pidgin_tooltip_timeout, userdata);
324 return FALSE;
327 static gboolean
328 widget_leave_cb(GtkWidget *tv, GdkEvent *event, gpointer userdata)
330 pidgin_tooltip_destroy();
331 return FALSE;
334 gboolean pidgin_tooltip_setup_for_treeview(GtkWidget *tree, gpointer userdata,
335 PidginTooltipCreateForTree create_tooltip, PidginTooltipPaint paint_tooltip)
337 PidginTooltipData *tdata = g_new0(PidginTooltipData, 1);
338 tdata->widget = tree;
339 tdata->userdata = userdata;
340 tdata->common.treeview.create_tooltip = create_tooltip;
341 tdata->paint_tooltip = paint_tooltip;
343 g_signal_connect(G_OBJECT(tree), "motion-notify-event", G_CALLBACK(row_motion_cb), tdata);
344 g_signal_connect(G_OBJECT(tree), "leave-notify-event", G_CALLBACK(widget_leave_cb), NULL);
345 g_signal_connect(G_OBJECT(tree), "scroll-event", G_CALLBACK(widget_leave_cb), NULL);
346 g_signal_connect_swapped(G_OBJECT(tree), "destroy", G_CALLBACK(destroy_tooltip_data), tdata);
347 return TRUE;
350 static gboolean
351 widget_motion_cb(GtkWidget *widget, GdkEvent *event, gpointer data)
353 initialize_tooltip_delay();
355 pidgin_tooltip_destroy();
356 if (!enable_tooltips)
357 return FALSE;
359 pidgin_tooltip.timeout = g_timeout_add(tooltip_delay, (GSourceFunc)pidgin_tooltip_timeout, data);
360 return FALSE;
363 gboolean pidgin_tooltip_setup_for_widget(GtkWidget *widget, gpointer userdata,
364 PidginTooltipCreate create_tooltip, PidginTooltipPaint paint_tooltip)
366 PidginTooltipData *wdata = g_new0(PidginTooltipData, 1);
367 wdata->widget = widget;
368 wdata->userdata = userdata;
369 wdata->common.widget.create_tooltip = create_tooltip;
370 wdata->paint_tooltip = paint_tooltip;
372 g_signal_connect(G_OBJECT(widget), "motion-notify-event", G_CALLBACK(widget_motion_cb), wdata);
373 g_signal_connect(G_OBJECT(widget), "leave-notify-event", G_CALLBACK(widget_leave_cb), NULL);
374 g_signal_connect(G_OBJECT(widget), "scroll-event", G_CALLBACK(widget_leave_cb), NULL);
375 g_signal_connect_swapped(G_OBJECT(widget), "destroy", G_CALLBACK(destroy_tooltip_data), wdata);
376 return TRUE;