1 /*
2  * Copyright © 2020 Red Hat, Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * SPDX-License-Identifier: LGPL-2.1-or-later
18  */
19 
20 #include "config.h"
21 
22 #import "GdkMacosWindow.h"
23 
24 #include "gdkinternals.h"
25 #include "gdkpopupprivate.h"
26 
27 #include "gdkmacosdisplay-private.h"
28 #include "gdkmacosmonitor.h"
29 #include "gdkmacospopupsurface-private.h"
30 #include "gdkmacosutils-private.h"
31 
32 struct _GdkMacosPopupSurface
33 {
34   GdkMacosSurface parent_instance;
35   GdkPopupLayout *layout;
36 };
37 
38 struct _GdkMacosPopupSurfaceClass
39 {
40   GdkMacosSurfaceClass parent_class;
41 };
42 
43 static void
gdk_macos_popup_surface_layout(GdkMacosPopupSurface * self,int width,int height,GdkPopupLayout * layout)44 gdk_macos_popup_surface_layout (GdkMacosPopupSurface *self,
45                                 int                   width,
46                                 int                   height,
47                                 GdkPopupLayout       *layout)
48 {
49   GdkMonitor *monitor;
50   GdkRectangle bounds;
51   GdkRectangle final_rect;
52   int x, y;
53 
54   g_assert (GDK_IS_MACOS_POPUP_SURFACE (self));
55   g_assert (layout != NULL);
56   g_assert (GDK_SURFACE (self)->parent);
57 
58   gdk_popup_layout_ref (layout);
59   g_clear_pointer (&self->layout, gdk_popup_layout_unref);
60   self->layout = layout;
61 
62   monitor = gdk_surface_get_layout_monitor (GDK_SURFACE (self),
63                                             self->layout,
64                                             gdk_macos_monitor_get_workarea);
65   if (monitor == NULL)
66     monitor = _gdk_macos_surface_get_best_monitor (GDK_MACOS_SURFACE (self));
67   gdk_macos_monitor_get_workarea (monitor, &bounds);
68 
69   gdk_popup_layout_get_shadow_width (layout,
70                                      &self->parent_instance.shadow_left,
71                                      &self->parent_instance.shadow_right,
72                                      &self->parent_instance.shadow_top,
73                                      &self->parent_instance.shadow_bottom);
74 
75   gdk_surface_layout_popup_helper (GDK_SURFACE (self),
76                                    width,
77                                    height,
78                                    self->parent_instance.shadow_left,
79                                    self->parent_instance.shadow_right,
80                                    self->parent_instance.shadow_top,
81                                    self->parent_instance.shadow_bottom,
82                                    monitor,
83                                    &bounds,
84                                    self->layout,
85                                    &final_rect);
86 
87   gdk_surface_get_origin (GDK_SURFACE (self)->parent, &x, &y);
88 
89   x += final_rect.x;
90   y += final_rect.y;
91 
92   if (final_rect.width != GDK_SURFACE (self)->width ||
93       final_rect.height != GDK_SURFACE (self)->height)
94     _gdk_macos_surface_move_resize (GDK_MACOS_SURFACE (self),
95                                     x,
96                                     y,
97                                     final_rect.width,
98                                     final_rect.height);
99   else if (x != GDK_MACOS_SURFACE (self)->root_x ||
100            y != GDK_MACOS_SURFACE (self)->root_y)
101     _gdk_macos_surface_move (GDK_MACOS_SURFACE (self), x, y);
102   else
103     return;
104 
105   gdk_surface_invalidate_rect (GDK_SURFACE (self), NULL);
106 }
107 
108 static void
show_popup(GdkMacosPopupSurface * self)109 show_popup (GdkMacosPopupSurface *self)
110 {
111   _gdk_macos_surface_show (GDK_MACOS_SURFACE (self));
112 }
113 
114 static void
show_grabbing_popup(GdkSeat * seat,GdkSurface * surface,gpointer user_data)115 show_grabbing_popup (GdkSeat    *seat,
116                      GdkSurface *surface,
117                      gpointer    user_data)
118 {
119   show_popup (GDK_MACOS_POPUP_SURFACE (surface));
120 }
121 
122 static gboolean
gdk_macos_popup_surface_present(GdkPopup * popup,int width,int height,GdkPopupLayout * layout)123 gdk_macos_popup_surface_present (GdkPopup       *popup,
124                                  int             width,
125                                  int             height,
126                                  GdkPopupLayout *layout)
127 {
128   GdkMacosPopupSurface *self = (GdkMacosPopupSurface *)popup;
129 
130   g_assert (GDK_IS_MACOS_POPUP_SURFACE (self));
131 
132   gdk_macos_popup_surface_layout (self, width, height, layout);
133 
134   if (GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self)))
135     return TRUE;
136 
137   if (GDK_SURFACE (self)->autohide)
138     {
139       GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (popup));
140       GdkSeat *seat = gdk_display_get_default_seat (display);
141 
142       gdk_seat_grab (seat,
143                      GDK_SURFACE (self),
144                      GDK_SEAT_CAPABILITY_ALL,
145                      TRUE,
146                      NULL, NULL,
147                      show_grabbing_popup,
148                      NULL);
149     }
150   else
151     {
152       show_popup (GDK_MACOS_POPUP_SURFACE (self));
153     }
154 
155   GDK_MACOS_SURFACE (self)->did_initial_present = TRUE;
156 
157   return GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self));
158 }
159 
160 static GdkGravity
gdk_macos_popup_surface_get_surface_anchor(GdkPopup * popup)161 gdk_macos_popup_surface_get_surface_anchor (GdkPopup *popup)
162 {
163   return GDK_SURFACE (popup)->popup.surface_anchor;
164 }
165 
166 static GdkGravity
gdk_macos_popup_surface_get_rect_anchor(GdkPopup * popup)167 gdk_macos_popup_surface_get_rect_anchor (GdkPopup *popup)
168 {
169   return GDK_SURFACE (popup)->popup.rect_anchor;
170 }
171 
172 static int
gdk_macos_popup_surface_get_position_x(GdkPopup * popup)173 gdk_macos_popup_surface_get_position_x (GdkPopup *popup)
174 {
175   return GDK_SURFACE (popup)->x;
176 }
177 
178 static int
gdk_macos_popup_surface_get_position_y(GdkPopup * popup)179 gdk_macos_popup_surface_get_position_y (GdkPopup *popup)
180 {
181   return GDK_SURFACE (popup)->y;
182 }
183 
184 static void
popup_interface_init(GdkPopupInterface * iface)185 popup_interface_init (GdkPopupInterface *iface)
186 {
187   iface->present = gdk_macos_popup_surface_present;
188   iface->get_surface_anchor = gdk_macos_popup_surface_get_surface_anchor;
189   iface->get_rect_anchor = gdk_macos_popup_surface_get_rect_anchor;
190   iface->get_position_x = gdk_macos_popup_surface_get_position_x;
191   iface->get_position_y = gdk_macos_popup_surface_get_position_y;
192 }
193 
194 G_DEFINE_TYPE_WITH_CODE (GdkMacosPopupSurface, _gdk_macos_popup_surface, GDK_TYPE_MACOS_SURFACE,
195                          G_IMPLEMENT_INTERFACE (GDK_TYPE_POPUP, popup_interface_init))
196 
197 enum {
198   PROP_0,
199   LAST_PROP,
200 };
201 
202 static void
_gdk_macos_popup_surface_finalize(GObject * object)203 _gdk_macos_popup_surface_finalize (GObject *object)
204 {
205   GdkMacosPopupSurface *self = (GdkMacosPopupSurface *)object;
206 
207   g_clear_object (&GDK_SURFACE (self)->parent);
208   g_clear_pointer (&self->layout, gdk_popup_layout_unref);
209 
210   G_OBJECT_CLASS (_gdk_macos_popup_surface_parent_class)->finalize (object);
211 }
212 
213 static void
_gdk_macos_popup_surface_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)214 _gdk_macos_popup_surface_get_property (GObject    *object,
215                                        guint       prop_id,
216                                        GValue     *value,
217                                        GParamSpec *pspec)
218 {
219   GdkSurface *surface = GDK_SURFACE (object);
220 
221   switch (prop_id)
222     {
223     case LAST_PROP + GDK_POPUP_PROP_PARENT:
224       g_value_set_object (value, surface->parent);
225       break;
226 
227     case LAST_PROP + GDK_POPUP_PROP_AUTOHIDE:
228       g_value_set_boolean (value, surface->autohide);
229       break;
230 
231     default:
232       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
233     }
234 }
235 
236 static void
_gdk_macos_popup_surface_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)237 _gdk_macos_popup_surface_set_property (GObject      *object,
238                                        guint         prop_id,
239                                        const GValue *value,
240                                        GParamSpec   *pspec)
241 {
242   GdkSurface *surface = GDK_SURFACE (object);
243 
244   switch (prop_id)
245     {
246     case LAST_PROP + GDK_POPUP_PROP_PARENT:
247       surface->parent = g_value_dup_object (value);
248       if (surface->parent)
249         surface->parent->children = g_list_prepend (surface->parent->children, surface);
250       break;
251 
252     case LAST_PROP + GDK_POPUP_PROP_AUTOHIDE:
253       surface->autohide = g_value_get_boolean (value);
254       break;
255 
256     default:
257       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
258     }
259 }
260 
261 static void
_gdk_macos_popup_surface_class_init(GdkMacosPopupSurfaceClass * klass)262 _gdk_macos_popup_surface_class_init (GdkMacosPopupSurfaceClass *klass)
263 {
264   GObjectClass *object_class = G_OBJECT_CLASS (klass);
265 
266   object_class->finalize = _gdk_macos_popup_surface_finalize;
267   object_class->get_property = _gdk_macos_popup_surface_get_property;
268   object_class->set_property = _gdk_macos_popup_surface_set_property;
269 
270   gdk_popup_install_properties (object_class, 1);
271 }
272 
273 static void
_gdk_macos_popup_surface_init(GdkMacosPopupSurface * self)274 _gdk_macos_popup_surface_init (GdkMacosPopupSurface *self)
275 {
276 }
277 
278 GdkMacosSurface *
_gdk_macos_popup_surface_new(GdkMacosDisplay * display,GdkSurface * parent,GdkFrameClock * frame_clock,int x,int y,int width,int height)279 _gdk_macos_popup_surface_new (GdkMacosDisplay *display,
280                               GdkSurface      *parent,
281                               GdkFrameClock   *frame_clock,
282                               int              x,
283                               int              y,
284                               int              width,
285                               int              height)
286 {
287   GDK_BEGIN_MACOS_ALLOC_POOL;
288 
289   GdkMacosWindow *window;
290   GdkMacosSurface *self;
291   NSScreen *screen;
292   NSUInteger style_mask;
293   NSRect content_rect;
294   NSRect screen_rect;
295   int nx;
296   int ny;
297 
298   g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
299   g_return_val_if_fail (!frame_clock || GDK_IS_FRAME_CLOCK (frame_clock), NULL);
300   g_return_val_if_fail (!parent || GDK_IS_MACOS_SURFACE (parent), NULL);
301 
302   style_mask = NSWindowStyleMaskBorderless;
303 
304   _gdk_macos_display_to_display_coords (display, x, y, &nx, &ny);
305 
306   screen = _gdk_macos_display_get_screen_at_display_coords (display, nx, ny);
307   screen_rect = [screen frame];
308   nx -= screen_rect.origin.x;
309   ny -= screen_rect.origin.y;
310   content_rect = NSMakeRect (nx, ny - height, width, height);
311 
312   window = [[GdkMacosWindow alloc] initWithContentRect:content_rect
313                                              styleMask:style_mask
314                                                backing:NSBackingStoreBuffered
315                                                  defer:NO
316                                                 screen:screen];
317 
318   [window setOpaque:NO];
319   [window setBackgroundColor:[NSColor clearColor]];
320   [window setDecorated:NO];
321 
322 #if 0
323   /* NOTE: We could set these to be popup level, but then
324    * [NSApp orderedWindows] would not give us the windows
325    * back with the stacking order applied.
326    */
327   [window setLevel:NSPopUpMenuWindowLevel];
328 #endif
329 
330   self = g_object_new (GDK_TYPE_MACOS_POPUP_SURFACE,
331                        "display", display,
332                        "frame-clock", frame_clock,
333                        "native", window,
334                        "parent", parent,
335                        NULL);
336 
337   GDK_END_MACOS_ALLOC_POOL;
338 
339   return g_steal_pointer (&self);
340 }
341 
342 void
_gdk_macos_popup_surface_attach_to_parent(GdkMacosPopupSurface * self)343 _gdk_macos_popup_surface_attach_to_parent (GdkMacosPopupSurface *self)
344 {
345   GdkSurface *surface = (GdkSurface *)self;
346 
347   g_return_if_fail (GDK_IS_MACOS_POPUP_SURFACE (self));
348 
349   if (GDK_SURFACE_DESTROYED (surface))
350     return;
351 
352   if (surface->parent != NULL && !GDK_SURFACE_DESTROYED (surface->parent))
353     {
354       NSWindow *parent = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface->parent));
355       NSWindow *window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
356 
357       [parent addChildWindow:window ordered:NSWindowAbove];
358 
359       _gdk_macos_display_clear_sorting (GDK_MACOS_DISPLAY (surface->display));
360     }
361 }
362 
363 void
_gdk_macos_popup_surface_detach_from_parent(GdkMacosPopupSurface * self)364 _gdk_macos_popup_surface_detach_from_parent (GdkMacosPopupSurface *self)
365 {
366   GdkSurface *surface = (GdkSurface *)self;
367 
368   g_return_if_fail (GDK_IS_MACOS_POPUP_SURFACE (self));
369 
370   if (GDK_SURFACE_DESTROYED (surface))
371     return;
372 
373   if (surface->parent != NULL && !GDK_SURFACE_DESTROYED (surface->parent))
374     {
375       NSWindow *parent = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface->parent));
376       NSWindow *window = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (self));
377 
378       [parent removeChildWindow:window];
379 
380       _gdk_macos_display_clear_sorting (GDK_MACOS_DISPLAY (surface->display));
381     }
382 }
383 
384 void
_gdk_macos_popup_surface_reposition(GdkMacosPopupSurface * self)385 _gdk_macos_popup_surface_reposition (GdkMacosPopupSurface *self)
386 {
387   g_return_if_fail (GDK_IS_MACOS_POPUP_SURFACE (self));
388 
389   if (self->layout == NULL ||
390       !gdk_surface_get_mapped (GDK_SURFACE (self)) ||
391       GDK_SURFACE (self)->parent == NULL)
392     return;
393 
394   gdk_macos_popup_surface_layout (self,
395                                   GDK_SURFACE (self)->width,
396                                   GDK_SURFACE (self)->height,
397                                   self->layout);
398 }
399