2 * @file pidgintooltip.c Pidgin Tooltip 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
30 #include "pidgintooltip.h"
33 static gboolean enable_tooltips
;
34 static int tooltip_delay
= -1;
40 GdkRectangle tip_rect
;
42 PidginTooltipPaint paint_tooltip
;
49 PidginTooltipPaint paint_tooltip
;
52 PidginTooltipCreateForTree create_tooltip
;
56 PidginTooltipCreate create_tooltip
;
62 initialize_tooltip_delay()
64 #if GTK_CHECK_VERSION(2,14,0)
65 GtkSettings
*settings
;
68 if (tooltip_delay
!= -1)
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
);
77 tooltip_delay
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/blist/tooltip_delay");
78 enable_tooltips
= (tooltip_delay
!= 0);
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();
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
;
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
);
115 setup_tooltip_window(void)
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
);
133 setup_tooltip_window_position(gpointer data
, int w
, int h
)
136 int scr_w
, scr_h
, x
, y
, dy
;
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;
159 if ((y
+ h
+ 4) > scr_h
)
167 if (y
!= mon_size
.y
) {
169 x
-= (x
+ w
+ 5) - scr_w
;
170 else if (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
;
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();
206 setup_tooltip_window_position(userdata
, w
, h
);
210 reset_data_treepath(PidginTooltipData
*data
)
212 gtk_tree_path_free(data
->common
.treeview
.path
);
213 data
->common
.treeview
.path
= NULL
;
217 pidgin_tooltip_draw(PidginTooltipData
*data
)
219 GtkWidget
*tipwindow
;
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();
234 setup_tooltip_window_position(data
->userdata
, w
, h
);
238 pidgin_tooltip_draw_tree(PidginTooltipData
*data
)
240 GtkWidget
*tipwindow
;
241 GtkTreePath
*path
= NULL
;
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();
252 if (data
->common
.treeview
.path
) {
253 if (gtk_tree_path_compare(data
->common
.treeview
.path
, path
) == 0) {
254 gtk_tree_path_free(path
);
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
);
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
);
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
);
289 pidgin_tooltip_draw(data
);
294 row_motion_cb(GtkWidget
*tv
, GdkEventMotion
*event
, gpointer userdata
)
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
)
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
))
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
);
315 pidgin_tooltip_destroy();
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
);
328 widget_leave_cb(GtkWidget
*tv
, GdkEvent
*event
, gpointer userdata
)
330 pidgin_tooltip_destroy();
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
);
351 widget_motion_cb(GtkWidget
*widget
, GdkEvent
*event
, gpointer data
)
353 initialize_tooltip_delay();
355 pidgin_tooltip_destroy();
356 if (!enable_tooltips
)
359 pidgin_tooltip
.timeout
= g_timeout_add(tooltip_delay
, (GSourceFunc
)pidgin_tooltip_timeout
, data
);
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
);