1 /*
2 * Copyright (C) 2009 - 2012 Vivien Malerba <malerba@gnome-db.org>
3 * Copyright (C) 2010 David King <davidk@openismus.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21 #include <string.h>
22 #include <gtk/gtk.h>
23 #include "popup-container.h"
24 #include <gdk/gdkkeysyms.h>
25
26 struct _PopupContainerPrivate {
27 PopupContainerPositionFunc position_func;
28 };
29
30 static void popup_container_class_init (PopupContainerClass *klass);
31 static void popup_container_init (PopupContainer *container,
32 PopupContainerClass *klass);
33 static void popup_container_dispose (GObject *object);
34 static void popup_container_show (GtkWidget *widget);
35 static void popup_container_hide (GtkWidget *widget);
36
37 static GObjectClass *parent_class = NULL;
38
39 /*
40 * PopupContainer class implementation
41 */
42
43 static void
popup_container_class_init(PopupContainerClass * klass)44 popup_container_class_init (PopupContainerClass *klass)
45 {
46 GObjectClass *object_class = G_OBJECT_CLASS (klass);
47 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
48
49 parent_class = g_type_class_peek_parent (klass);
50
51 object_class->dispose = popup_container_dispose;
52 widget_class->show = popup_container_show;
53 widget_class->hide = popup_container_hide;
54 }
55
56 static gboolean
delete_popup(G_GNUC_UNUSED GtkWidget * widget,PopupContainer * container)57 delete_popup (G_GNUC_UNUSED GtkWidget *widget, PopupContainer *container)
58 {
59 gtk_widget_hide (GTK_WIDGET (container));
60 gtk_grab_remove (GTK_WIDGET (container));
61 return TRUE;
62 }
63
64 static gboolean
key_press_popup(GtkWidget * widget,GdkEventKey * event,PopupContainer * container)65 key_press_popup (GtkWidget *widget, GdkEventKey *event, PopupContainer *container)
66 {
67 if (event->keyval != GDK_KEY_Escape)
68 return FALSE;
69
70 g_signal_stop_emission_by_name (widget, "key-press-event");
71 gtk_widget_hide (GTK_WIDGET (container));
72 gtk_grab_remove (GTK_WIDGET (container));
73 return TRUE;
74 }
75
76 static gboolean
button_press_popup(GtkWidget * widget,GdkEventButton * event,PopupContainer * container)77 button_press_popup (GtkWidget *widget, GdkEventButton *event, PopupContainer *container)
78 {
79 GtkWidget *child;
80
81 child = gtk_get_event_widget ((GdkEvent *) event);
82
83 /* We don't ask for button press events on the grab widget, so
84 * if an event is reported directly to the grab widget, it must
85 * be on a window outside the application (and thus we remove
86 * the popup window). Otherwise, we check if the widget is a child
87 * of the grab widget, and only remove the popup window if it
88 * is not.
89 */
90 if (child != widget) {
91 while (child) {
92 if (child == widget)
93 return FALSE;
94 child = gtk_widget_get_parent (child);
95 }
96 }
97 gtk_widget_hide (GTK_WIDGET (container));
98 gtk_grab_remove (GTK_WIDGET (container));
99 return TRUE;
100 }
101
102 static void
popup_container_init(PopupContainer * container,G_GNUC_UNUSED PopupContainerClass * klass)103 popup_container_init (PopupContainer *container, G_GNUC_UNUSED PopupContainerClass *klass)
104 {
105 container->priv = g_new0 (PopupContainerPrivate, 1);
106 container->priv->position_func = NULL;
107
108 gtk_widget_set_events (GTK_WIDGET (container),
109 gtk_widget_get_events (GTK_WIDGET (container)) | GDK_KEY_PRESS_MASK);
110 gtk_window_set_resizable (GTK_WINDOW (container), FALSE);
111 gtk_container_set_border_width (GTK_CONTAINER (container), 5);
112 g_signal_connect (G_OBJECT (container), "delete-event",
113 G_CALLBACK (delete_popup), container);
114 g_signal_connect (G_OBJECT (container), "key-press-event",
115 G_CALLBACK (key_press_popup), container);
116 g_signal_connect (G_OBJECT (container), "button-press-event",
117 G_CALLBACK (button_press_popup), container);
118
119 }
120
121 /* FIXME:
122 * - implement the show() virtual method with popup_grab_on_window()...
123 * - implement the position_popup()
124 */
125
126 static void
popup_container_dispose(GObject * object)127 popup_container_dispose (GObject *object)
128 {
129 PopupContainer *container = (PopupContainer *) object;
130
131 /* free memory */
132 if (container->priv) {
133 g_free (container->priv);
134 container->priv = NULL;
135 }
136
137 parent_class->dispose (object);
138 }
139
140 static void
default_position_func(G_GNUC_UNUSED PopupContainer * container,gint * out_x,gint * out_y)141 default_position_func (G_GNUC_UNUSED PopupContainer *container, gint *out_x, gint *out_y)
142 {
143 GdkDeviceManager *manager;
144 GdkDevice *pointer;
145 GtkWidget *widget;
146 widget = GTK_WIDGET (container);
147 manager = gdk_display_get_device_manager (gtk_widget_get_display (widget));
148 pointer = gdk_device_manager_get_client_pointer (manager);
149 gdk_device_get_position (pointer, NULL, out_x, out_y);
150 }
151
152 static gboolean
popup_grab_on_window(GtkWidget * widget,guint32 activate_time)153 popup_grab_on_window (GtkWidget *widget, guint32 activate_time)
154 {
155 GdkDeviceManager *manager;
156 GdkDevice *pointer;
157 GdkWindow *window;
158 window = gtk_widget_get_window (widget);
159 manager = gdk_display_get_device_manager (gtk_widget_get_display (widget));
160 pointer = gdk_device_manager_get_client_pointer (manager);
161 if (gdk_device_grab (pointer, window, GDK_OWNERSHIP_WINDOW, TRUE,
162 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
163 GDK_POINTER_MOTION_MASK,
164 NULL, activate_time) == GDK_GRAB_SUCCESS) {
165 GdkDevice *keyb;
166 keyb = gdk_device_get_associated_device (pointer);
167 if (gdk_device_grab (keyb, window, GDK_OWNERSHIP_WINDOW, TRUE,
168 GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK, NULL, activate_time) ==
169 GDK_GRAB_SUCCESS)
170 return TRUE;
171 else {
172 gdk_device_ungrab (pointer, activate_time);
173 return FALSE;
174 }
175 }
176 return FALSE;
177 }
178
179 static void
popup_container_show(GtkWidget * widget)180 popup_container_show (GtkWidget *widget)
181 {
182 PopupContainer *container = (PopupContainer *) widget;
183 gint x, y;
184
185 GTK_WIDGET_CLASS (parent_class)->show (widget);
186 if (container->priv->position_func)
187 container->priv->position_func (container, &x, &y);
188 else
189 default_position_func (container, &x, &y);
190 gtk_window_move (GTK_WINDOW (widget), x + 1, y + 1);
191 gtk_window_move (GTK_WINDOW (widget), x, y);
192
193 gtk_grab_add (widget);
194
195 GdkScreen *screen;
196 gint swidth, sheight;
197 gint root_x, root_y;
198 gint wwidth, wheight;
199 gboolean do_move = FALSE;
200 screen = gtk_window_get_screen (GTK_WINDOW (widget));
201 if (screen) {
202 swidth = gdk_screen_get_width (screen);
203 sheight = gdk_screen_get_height (screen);
204 }
205 else {
206 swidth = gdk_screen_width ();
207 sheight = gdk_screen_height ();
208 }
209 gtk_window_get_position (GTK_WINDOW (widget), &root_x, &root_y);
210 gtk_window_get_size (GTK_WINDOW (widget), &wwidth, &wheight);
211 if (root_x + wwidth > swidth) {
212 do_move = TRUE;
213 root_x = swidth - wwidth;
214 }
215 else if (root_x < 0) {
216 do_move = TRUE;
217 root_x = 0;
218 }
219 if (root_y + wheight > sheight) {
220 do_move = TRUE;
221 root_y = sheight - wheight;
222 }
223 else if (root_y < 0) {
224 do_move = TRUE;
225 root_y = 0;
226 }
227 if (do_move)
228 gtk_window_move (GTK_WINDOW (widget), root_x, root_y);
229
230 popup_grab_on_window (widget,
231 gtk_get_current_event_time ());
232 }
233
234 static void
popup_container_hide(GtkWidget * widget)235 popup_container_hide (GtkWidget *widget)
236 {
237 GTK_WIDGET_CLASS (parent_class)->hide (widget);
238 gtk_grab_remove (widget);
239 }
240
241 GType
popup_container_get_type(void)242 popup_container_get_type (void)
243 {
244 static GType type = 0;
245
246 if (G_UNLIKELY (type == 0)) {
247 static const GTypeInfo info = {
248 sizeof (PopupContainerClass),
249 (GBaseInitFunc) NULL,
250 (GBaseFinalizeFunc) NULL,
251 (GClassInitFunc) popup_container_class_init,
252 NULL,
253 NULL,
254 sizeof (PopupContainer),
255 0,
256 (GInstanceInitFunc) popup_container_init,
257 0
258 };
259
260 type = g_type_from_name ("GdauiPopupContainer");
261 if (!type)
262 type = g_type_register_static (GTK_TYPE_WINDOW, "GdauiPopupContainer",
263 &info, 0);
264 }
265 return type;
266 }
267
268 static void
popup_position(PopupContainer * container,gint * out_x,gint * out_y)269 popup_position (PopupContainer *container, gint *out_x, gint *out_y)
270 {
271 GtkWidget *poswidget;
272 poswidget = g_object_get_data (G_OBJECT (container), "__poswidget");
273
274 gint x, y;
275 GtkRequisition req;
276
277 gtk_widget_get_preferred_size (poswidget, NULL, &req);
278
279 GtkAllocation alloc;
280 gdk_window_get_origin (gtk_widget_get_window (poswidget), &x, &y);
281 gtk_widget_get_allocation (poswidget, &alloc);
282 x += alloc.x;
283 y += alloc.y;
284 y += alloc.height;
285
286 if (x < 0)
287 x = 0;
288
289 if (y < 0)
290 y = 0;
291
292 *out_x = x;
293 *out_y = y;
294 }
295
296 /**
297 * popup_container_new_with_func
298 *
299 * Returns:
300 */
301 GtkWidget *
popup_container_new(GtkWidget * position_widget)302 popup_container_new (GtkWidget *position_widget)
303 {
304 PopupContainer *container;
305 g_return_val_if_fail (GTK_IS_WIDGET (position_widget), NULL);
306
307 container = POPUP_CONTAINER (g_object_new (POPUP_CONTAINER_TYPE, "type", GTK_WINDOW_POPUP,
308 NULL));
309 g_object_set_data (G_OBJECT (container), "__poswidget", position_widget);
310 container->priv->position_func = popup_position;
311 return (GtkWidget*) container;
312 }
313
314
315 /**
316 * popup_container_new_with_func
317 *
318 * Returns:
319 */
320 GtkWidget *
popup_container_new_with_func(PopupContainerPositionFunc pos_func)321 popup_container_new_with_func (PopupContainerPositionFunc pos_func)
322 {
323 PopupContainer *container;
324
325 container = POPUP_CONTAINER (g_object_new (POPUP_CONTAINER_TYPE, "type", GTK_WINDOW_POPUP,
326 NULL));
327
328 container->priv->position_func = pos_func;
329 return (GtkWidget*) container;
330 }
331