comparison 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
comparison
equal deleted inserted replaced
21789:cbf778310bdd 21877:6bf73aea6450
1 /**
2 * @file pidgintooltip.c Pidgin Tooltip API
3 * @ingroup pidgin
4 */
5
6 /* pidgin
7 *
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.
11 *
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.
16 *
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.
21 *
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
25 */
26
27 #include "internal.h"
28 #include "prefs.h"
29 #include "pidgin.h"
30 #include "pidgintooltip.h"
31
32 struct
33 {
34 GtkWidget *widget;
35 int timeout;
36 GdkRectangle tip_rect;
37 GtkWidget *tipwindow;
38 PidginTooltipPaint paint_tooltip;
39 } pidgin_tooltip;
40
41 typedef struct
42 {
43 GtkWidget *widget;
44 gpointer userdata;
45 PidginTooltipCreateForTree create_tooltip;
46 PidginTooltipPaint paint_tooltip;
47 GtkTreePath *path;
48 } PidginTooltipData;
49
50 static void
51 destroy_tooltip_data(PidginTooltipData *data)
52 {
53 gtk_tree_path_free(data->path);
54 g_free(data);
55 }
56
57 void pidgin_tooltip_destroy()
58 {
59 if (pidgin_tooltip.timeout > 0) {
60 g_source_remove(pidgin_tooltip.timeout);
61 pidgin_tooltip.timeout = 0;
62 }
63 if (pidgin_tooltip.tipwindow) {
64 gtk_widget_destroy(pidgin_tooltip.tipwindow);
65 pidgin_tooltip.tipwindow = NULL;
66 }
67 }
68
69 static void
70 pidgin_tooltip_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
71 {
72 if (pidgin_tooltip.paint_tooltip)
73 pidgin_tooltip.paint_tooltip(widget, event, data);
74 }
75
76 static void
77 setup_tooltip_window(gpointer data, int w, int h)
78 {
79 const char *name;
80 int sig;
81 int scr_w, scr_h, x, y;
82 #if GTK_CHECK_VERSION(2,2,0)
83 int mon_num;
84 GdkScreen *screen = NULL;
85 #endif
86 GdkRectangle mon_size;
87 GtkWidget *tipwindow = pidgin_tooltip.tipwindow;
88
89 name = gtk_window_get_title(GTK_WINDOW(pidgin_tooltip.widget));
90 gtk_widget_set_app_paintable(tipwindow, TRUE);
91 gtk_window_set_title(GTK_WINDOW(tipwindow), name ? name : _("Pidgin Tooltip"));
92 gtk_window_set_resizable(GTK_WINDOW(tipwindow), FALSE);
93 gtk_widget_set_name(tipwindow, "gtk-tooltips");
94 g_signal_connect(G_OBJECT(tipwindow), "expose_event",
95 G_CALLBACK(pidgin_tooltip_expose_event), data);
96
97 #if GTK_CHECK_VERSION(2,2,0)
98 gdk_display_get_pointer(gdk_display_get_default(), &screen, &x, &y, NULL);
99 mon_num = gdk_screen_get_monitor_at_point(screen, x, y);
100 gdk_screen_get_monitor_geometry(screen, mon_num, &mon_size);
101
102 scr_w = mon_size.width + mon_size.x;
103 scr_h = mon_size.height + mon_size.y;
104 #else
105 scr_w = gdk_screen_width();
106 scr_h = gdk_screen_height();
107 gdk_window_get_pointer(NULL, &x, &y, NULL);
108 mon_size.x = 0;
109 mon_size.y = 0;
110 #endif
111
112 #if GTK_CHECK_VERSION(2,2,0)
113 if (w > mon_size.width)
114 w = mon_size.width - 10;
115
116 if (h > mon_size.height)
117 h = mon_size.height - 10;
118 #endif
119 x -= ((w >> 1) + 4);
120
121 if ((y + h + 4) > scr_h)
122 y = y - h - 5;
123 else
124 y = y + 6;
125
126 if (y < mon_size.y)
127 y = mon_size.y;
128
129 if (y != mon_size.y) {
130 if ((x + w) > scr_w)
131 x -= (x + w + 5) - scr_w;
132 else if (x < mon_size.x)
133 x = mon_size.x;
134 } else {
135 x -= (w / 2 + 10);
136 if (x < mon_size.x)
137 x = mon_size.x;
138 }
139
140 gtk_widget_set_size_request(tipwindow, w, h);
141 gtk_window_move(GTK_WINDOW(tipwindow), x, y);
142 gtk_widget_show(tipwindow);
143
144 /* Hide the tooltip when the widget is destroyed */
145 sig = g_signal_connect(G_OBJECT(pidgin_tooltip.widget), "destroy", G_CALLBACK(pidgin_tooltip_destroy), NULL);
146 g_signal_connect_swapped(G_OBJECT(tipwindow), "destroy", G_CALLBACK(g_source_remove), GINT_TO_POINTER(sig));
147 }
148
149 void pidgin_tooltip_show(GtkWidget *widget, gpointer userdata,
150 PidginTooltipCreate create_tooltip, PidginTooltipPaint paint_tooltip)
151 {
152 GtkWidget *tipwindow;
153 int w, h;
154
155 pidgin_tooltip_destroy();
156 pidgin_tooltip.tipwindow = tipwindow = gtk_window_new(GTK_WINDOW_POPUP);
157 pidgin_tooltip.widget = gtk_widget_get_toplevel(widget);
158 pidgin_tooltip.paint_tooltip = paint_tooltip;
159 gtk_widget_ensure_style(tipwindow);
160
161 if (!create_tooltip(tipwindow, userdata, &w, &h)) {
162 pidgin_tooltip_destroy();
163 return;
164 }
165 setup_tooltip_window(userdata, w, h);
166 }
167
168 static void
169 pidgin_tooltip_draw(PidginTooltipData *data)
170 {
171 GtkWidget *tipwindow;
172 GtkTreePath *path = NULL;
173 int w, h;
174
175 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(data->widget),
176 pidgin_tooltip.tip_rect.x,
177 pidgin_tooltip.tip_rect.y + (pidgin_tooltip.tip_rect.height/2),
178 &path, NULL, NULL, NULL)) {
179 pidgin_tooltip_destroy();
180 return;
181 }
182
183 if (data->path) {
184 if (gtk_tree_path_compare(data->path, path) == 0)
185 return;
186 gtk_tree_path_free(data->path);
187 data->path = NULL;
188 }
189
190 pidgin_tooltip.tipwindow = tipwindow = gtk_window_new(GTK_WINDOW_POPUP);
191 pidgin_tooltip.widget = gtk_widget_get_toplevel(data->widget);
192 pidgin_tooltip.paint_tooltip = data->paint_tooltip;
193 gtk_widget_ensure_style(tipwindow);
194
195 if (!data->create_tooltip(tipwindow, path, data->userdata, &w, &h)) {
196 pidgin_tooltip_destroy();
197 gtk_tree_path_free(path);
198 return;
199 }
200
201 data->path = path;
202 setup_tooltip_window(data->userdata, w, h);
203 }
204
205 static gboolean
206 pidgin_tooltip_timeout(gpointer data)
207 {
208 pidgin_tooltip.timeout = 0;
209 pidgin_tooltip_draw(data);
210 return FALSE;
211 }
212
213 static gboolean
214 row_motion_cb(GtkWidget *tv, GdkEventMotion *event, gpointer userdata)
215 {
216 GtkTreePath *path;
217 int delay;
218
219 /* XXX: probably use something more generic? */
220 delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay");
221 if (delay == 0)
222 return FALSE;
223
224 if (pidgin_tooltip.timeout) {
225 if ((event->y >= pidgin_tooltip.tip_rect.y) && ((event->y - pidgin_tooltip.tip_rect.height) <= pidgin_tooltip.tip_rect.y))
226 return FALSE;
227 /* We've left the cell. Remove the timeout and create a new one below */
228 pidgin_tooltip_destroy();
229 }
230
231 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL);
232
233 if (path == NULL) {
234 pidgin_tooltip_destroy();
235 return FALSE;
236 }
237
238 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &pidgin_tooltip.tip_rect);
239
240 if (path)
241 gtk_tree_path_free(path);
242 pidgin_tooltip.timeout = g_timeout_add(delay, (GSourceFunc)pidgin_tooltip_timeout, userdata);
243
244 return FALSE;
245 }
246
247 static gboolean
248 row_leave_cb(GtkWidget *tv, GdkEvent *event, gpointer userdata)
249 {
250 pidgin_tooltip_destroy();
251 return FALSE;
252 }
253
254 gboolean pidgin_tooltip_setup_for_treeview(GtkWidget *tree, gpointer userdata,
255 PidginTooltipCreateForTree create_tooltip, PidginTooltipPaint paint_tooltip)
256 {
257 PidginTooltipData *tdata = g_new0(PidginTooltipData, 1);
258 tdata->widget = tree;
259 tdata->userdata = userdata;
260 tdata->create_tooltip = create_tooltip;
261 tdata->paint_tooltip = paint_tooltip;
262
263 g_signal_connect(G_OBJECT(tree), "motion-notify-event", G_CALLBACK(row_motion_cb), tdata);
264 g_signal_connect(G_OBJECT(tree), "leave-notify-event", G_CALLBACK(row_leave_cb), NULL);
265 g_signal_connect_swapped(G_OBJECT(tree), "destroy", G_CALLBACK(destroy_tooltip_data), tdata);
266 return TRUE;
267 }
268