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