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