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