1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * gimppopup.c
5 * Copyright (C) 2003-2014 Michael Natterer <mitch@gimp.org>
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21 #include "config.h"
22
23 #include <gegl.h>
24 #include <gtk/gtk.h>
25 #include <gdk/gdkkeysyms.h>
26
27 #include "libgimpwidgets/gimpwidgets.h"
28
29 #include "widgets-types.h"
30
31 #include "core/gimpmarshal.h"
32
33 #include "gimppopup.h"
34
35
36 enum
37 {
38 CANCEL,
39 CONFIRM,
40 LAST_SIGNAL
41 };
42
43
44 static gboolean gimp_popup_map_event (GtkWidget *widget,
45 GdkEventAny *event);
46 static gboolean gimp_popup_button_press (GtkWidget *widget,
47 GdkEventButton *bevent);
48 static gboolean gimp_popup_key_press (GtkWidget *widget,
49 GdkEventKey *kevent);
50
51 static void gimp_popup_real_cancel (GimpPopup *popup);
52 static void gimp_popup_real_confirm (GimpPopup *popup);
53
54
G_DEFINE_TYPE(GimpPopup,gimp_popup,GTK_TYPE_WINDOW)55 G_DEFINE_TYPE (GimpPopup, gimp_popup, GTK_TYPE_WINDOW)
56
57 #define parent_class gimp_popup_parent_class
58
59 static guint popup_signals[LAST_SIGNAL];
60
61
62 static void
63 gimp_popup_class_init (GimpPopupClass *klass)
64 {
65 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
66 GtkBindingSet *binding_set;
67
68 popup_signals[CANCEL] =
69 g_signal_new ("cancel",
70 G_OBJECT_CLASS_TYPE (klass),
71 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
72 G_STRUCT_OFFSET (GimpPopupClass, cancel),
73 NULL, NULL,
74 gimp_marshal_VOID__VOID,
75 G_TYPE_NONE, 0);
76
77 popup_signals[CONFIRM] =
78 g_signal_new ("confirm",
79 G_OBJECT_CLASS_TYPE (klass),
80 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
81 G_STRUCT_OFFSET (GimpPopupClass, confirm),
82 NULL, NULL,
83 gimp_marshal_VOID__VOID,
84 G_TYPE_NONE, 0);
85
86 widget_class->map_event = gimp_popup_map_event;
87 widget_class->button_press_event = gimp_popup_button_press;
88 widget_class->key_press_event = gimp_popup_key_press;
89
90 klass->cancel = gimp_popup_real_cancel;
91 klass->confirm = gimp_popup_real_confirm;
92
93 binding_set = gtk_binding_set_by_class (klass);
94
95 gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0,
96 "cancel", 0);
97 gtk_binding_entry_add_signal (binding_set, GDK_KEY_Return, 0,
98 "confirm", 0);
99 gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Enter, 0,
100 "confirm", 0);
101 gtk_binding_entry_add_signal (binding_set, GDK_KEY_ISO_Enter, 0,
102 "confirm", 0);
103 gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, 0,
104 "confirm", 0);
105 gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Space, 0,
106 "confirm", 0);
107 }
108
109 static void
gimp_popup_init(GimpPopup * popup)110 gimp_popup_init (GimpPopup *popup)
111 {
112 }
113
114 static void
gimp_popup_grab_notify(GtkWidget * widget,gboolean was_grabbed)115 gimp_popup_grab_notify (GtkWidget *widget,
116 gboolean was_grabbed)
117 {
118 if (was_grabbed)
119 return;
120
121 /* ignore grabs on one of our children, like a scrollbar */
122 if (gtk_widget_is_ancestor (gtk_grab_get_current (), widget))
123 return;
124
125 g_signal_emit (widget, popup_signals[CANCEL], 0);
126 }
127
128 static gboolean
gimp_popup_grab_broken_event(GtkWidget * widget,GdkEventGrabBroken * event)129 gimp_popup_grab_broken_event (GtkWidget *widget,
130 GdkEventGrabBroken *event)
131 {
132 gimp_popup_grab_notify (widget, FALSE);
133
134 return FALSE;
135 }
136
137 static gboolean
gimp_popup_map_event(GtkWidget * widget,G_GNUC_UNUSED GdkEventAny * event)138 gimp_popup_map_event (GtkWidget *widget,
139 G_GNUC_UNUSED GdkEventAny *event)
140 {
141 GTK_WIDGET_CLASS (parent_class)->map_event (widget, event);
142
143 /* grab with owner_events == TRUE so the popup's widgets can
144 * receive events. we filter away events outside this toplevel
145 * away in button_press()
146 */
147 if (gdk_pointer_grab (gtk_widget_get_window (widget), TRUE,
148 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
149 GDK_POINTER_MOTION_MASK,
150 NULL, NULL, GDK_CURRENT_TIME) == GDK_GRAB_SUCCESS)
151 {
152 if (gdk_keyboard_grab (gtk_widget_get_window (widget), TRUE,
153 GDK_CURRENT_TIME) == GDK_GRAB_SUCCESS)
154 {
155 gtk_grab_add (widget);
156
157 g_signal_connect (widget, "grab-notify",
158 G_CALLBACK (gimp_popup_grab_notify),
159 widget);
160 g_signal_connect (widget, "grab-broken-event",
161 G_CALLBACK (gimp_popup_grab_broken_event),
162 widget);
163
164 return FALSE;
165 }
166 else
167 {
168 gdk_display_pointer_ungrab (gtk_widget_get_display (widget),
169 GDK_CURRENT_TIME);
170 }
171 }
172
173 /* if we could not grab, destroy the popup instead of leaving it
174 * around uncloseable.
175 */
176 g_signal_emit (widget, popup_signals[CANCEL], 0);
177 return FALSE;
178 }
179
180 static gboolean
gimp_popup_button_press(GtkWidget * widget,GdkEventButton * bevent)181 gimp_popup_button_press (GtkWidget *widget,
182 GdkEventButton *bevent)
183 {
184 GtkWidget *event_widget;
185 gboolean cancel = FALSE;
186
187 event_widget = gtk_get_event_widget ((GdkEvent *) bevent);
188
189 if (event_widget == widget)
190 {
191 GtkAllocation allocation;
192
193 gtk_widget_get_allocation (widget, &allocation);
194
195 /* the event was on the popup, which can either be really on the
196 * popup or outside gimp (owner_events == TRUE, see map())
197 */
198 if (bevent->x < 0 ||
199 bevent->y < 0 ||
200 bevent->x > allocation.width ||
201 bevent->y > allocation.height)
202 {
203 /* the event was outsde gimp */
204
205 cancel = TRUE;
206 }
207 }
208 else if (gtk_widget_get_toplevel (event_widget) != widget)
209 {
210 /* the event was on a gimp widget, but not inside the popup */
211
212 cancel = TRUE;
213 }
214
215 if (cancel)
216 g_signal_emit (widget, popup_signals[CANCEL], 0);
217
218 return cancel;
219 }
220
221 static gboolean
gimp_popup_key_press(GtkWidget * widget,GdkEventKey * kevent)222 gimp_popup_key_press (GtkWidget *widget,
223 GdkEventKey *kevent)
224 {
225 GtkWidget *focus = gtk_window_get_focus (GTK_WINDOW (widget));
226 gboolean activate_binding = TRUE;
227
228 if (focus &&
229 (GTK_IS_EDITABLE (focus) ||
230 GTK_IS_TEXT_VIEW (focus)) &&
231 (kevent->keyval == GDK_KEY_space ||
232 kevent->keyval == GDK_KEY_KP_Space))
233 {
234 /* if a text widget has the focus, and the key was space,
235 * don't manually activate the binding to allow entering the
236 * space in the focus widget.
237 */
238 activate_binding = FALSE;
239 }
240
241 if (activate_binding)
242 {
243 GtkBindingSet *binding_set;
244
245 binding_set = gtk_binding_set_by_class (g_type_class_peek (GIMP_TYPE_POPUP));
246
247 /* invoke the popup's binding entries manually, because
248 * otherwise the focus widget (GtkTreeView e.g.) would consume
249 * it
250 */
251 if (gtk_binding_set_activate (binding_set,
252 kevent->keyval,
253 kevent->state,
254 GTK_OBJECT (widget)))
255 {
256 return TRUE;
257 }
258 }
259
260 return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, kevent);
261 }
262
263 static void
gimp_popup_real_cancel(GimpPopup * popup)264 gimp_popup_real_cancel (GimpPopup *popup)
265 {
266 GtkWidget *widget = GTK_WIDGET (popup);
267
268 if (gtk_grab_get_current () == widget)
269 gtk_grab_remove (widget);
270
271 gtk_widget_destroy (widget);
272 }
273
274 static void
gimp_popup_real_confirm(GimpPopup * popup)275 gimp_popup_real_confirm (GimpPopup *popup)
276 {
277 GtkWidget *widget = GTK_WIDGET (popup);
278
279 if (gtk_grab_get_current () == widget)
280 gtk_grab_remove (widget);
281
282 gtk_widget_destroy (widget);
283 }
284
285 void
gimp_popup_show(GimpPopup * popup,GtkWidget * widget)286 gimp_popup_show (GimpPopup *popup,
287 GtkWidget *widget)
288 {
289 GdkScreen *screen;
290 GtkRequisition requisition;
291 GtkAllocation allocation;
292 GdkRectangle rect;
293 gint monitor;
294 gint orig_x;
295 gint orig_y;
296 gint x;
297 gint y;
298
299 g_return_if_fail (GIMP_IS_POPUP (popup));
300 g_return_if_fail (GTK_IS_WIDGET (widget));
301
302 gtk_widget_size_request (GTK_WIDGET (popup), &requisition);
303
304 gtk_widget_get_allocation (widget, &allocation);
305 gdk_window_get_origin (gtk_widget_get_window (widget), &orig_x, &orig_y);
306
307 if (! gtk_widget_get_has_window (widget))
308 {
309 orig_x += allocation.x;
310 orig_y += allocation.y;
311 }
312
313 screen = gtk_widget_get_screen (widget);
314
315 monitor = gdk_screen_get_monitor_at_point (screen, orig_x, orig_y);
316 gdk_screen_get_monitor_workarea (screen, monitor, &rect);
317
318 if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
319 {
320 x = orig_x + allocation.width - requisition.width;
321
322 if (x < rect.x)
323 x -= allocation.width - requisition.width;
324 }
325 else
326 {
327 x = orig_x;
328
329 if (x + requisition.width > rect.x + rect.width)
330 x += allocation.width - requisition.width;
331 }
332
333 y = orig_y + allocation.height;
334
335 if (y + requisition.height > rect.y + rect.height)
336 y = orig_y - requisition.height;
337
338 gtk_window_set_screen (GTK_WINDOW (popup), screen);
339 gtk_window_set_transient_for (GTK_WINDOW (popup),
340 GTK_WINDOW (gtk_widget_get_toplevel (widget)));
341
342 gtk_window_move (GTK_WINDOW (popup), x, y);
343 gtk_widget_show (GTK_WIDGET (popup));
344 }
345