1 /* dzl-dock-manager.c
2 *
3 * Copyright (C) 2016 Christian Hergert <chergert@redhat.com>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program 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
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #define G_LOG_DOMAIN "dzl-dock-manager"
20
21 #include "config.h"
22
23 #include "panel/dzl-dock-manager.h"
24 #include "panel/dzl-dock-transient-grab.h"
25 #include "util/dzl-util-private.h"
26
27 typedef struct
28 {
29 GPtrArray *docks;
30 DzlDockTransientGrab *grab;
31 GHashTable *queued_focus_by_toplevel;
32 guint queued_handler;
33 gint pause_count;
34 } DzlDockManagerPrivate;
35
36 G_DEFINE_TYPE_WITH_PRIVATE (DzlDockManager, dzl_dock_manager, G_TYPE_OBJECT)
37
38 enum {
39 REGISTER_DOCK,
40 UNREGISTER_DOCK,
41 N_SIGNALS
42 };
43
44 static guint signals [N_SIGNALS];
45
46 static void
dzl_dock_manager_do_set_focus(DzlDockManager * self,GtkWidget * focus,GtkWidget * toplevel)47 dzl_dock_manager_do_set_focus (DzlDockManager *self,
48 GtkWidget *focus,
49 GtkWidget *toplevel)
50 {
51 DzlDockManagerPrivate *priv = dzl_dock_manager_get_instance_private (self);
52 g_autoptr(DzlDockTransientGrab) grab = NULL;
53 GtkWidget *parent;
54
55 g_assert (DZL_IS_DOCK_MANAGER (self));
56 g_assert (GTK_IS_WIDGET (focus));
57 g_assert (GTK_IS_WIDGET (toplevel));
58
59 if (priv->pause_count > 0)
60 return;
61
62 if (priv->grab != NULL)
63 {
64 /*
65 * If the current transient grab contains the new focus widget,
66 * then there is nothing for us to do now.
67 */
68 if (dzl_dock_transient_grab_is_descendant (priv->grab, focus))
69 return;
70 }
71
72 /*
73 * If their is a DzlDockItem in the hierarchy, create a new transient grab.
74 */
75 parent = focus;
76 while (GTK_IS_WIDGET (parent))
77 {
78 if (DZL_IS_DOCK_ITEM (parent))
79 {
80 /* If we reach a DockItem that doesn't have a manager set,
81 * then we are probably adding the widgetry to the window
82 * and grabing focus right now would be intrusive.
83 */
84 if (dzl_dock_item_get_manager (DZL_DOCK_ITEM (parent)) == NULL)
85 return;
86
87 if (grab == NULL)
88 grab = dzl_dock_transient_grab_new ();
89
90 dzl_dock_transient_grab_add_item (grab, DZL_DOCK_ITEM (parent));
91 }
92
93 if (GTK_IS_POPOVER (parent))
94 parent = gtk_popover_get_relative_to (GTK_POPOVER (parent));
95 else
96 parent = gtk_widget_get_parent (parent);
97 }
98
99 /*
100 * Steal common hierarchy so that we don't hide it when breaking grabs.
101 * Then release our previous grab.
102 */
103 if (priv->grab != NULL)
104 {
105 if (grab != NULL)
106 dzl_dock_transient_grab_steal_common_ancestors (grab, priv->grab);
107 dzl_dock_transient_grab_release (priv->grab);
108 g_clear_object (&priv->grab);
109 }
110
111 g_assert (priv->grab == NULL);
112
113 /* Start the grab process */
114 if (grab != NULL)
115 {
116 priv->grab = g_steal_pointer (&grab);
117 dzl_dock_transient_grab_acquire (priv->grab);
118 }
119 }
120
121 static gboolean
do_delayed_focus_update(gpointer user_data)122 do_delayed_focus_update (gpointer user_data)
123 {
124 DzlDockManager *self = user_data;
125 DzlDockManagerPrivate *priv = dzl_dock_manager_get_instance_private (self);
126 g_autoptr(GHashTable) hashtable = NULL;
127 GHashTableIter iter;
128 GtkWidget *toplevel;
129 GtkWidget *focus;
130
131 g_assert (DZL_IS_DOCK_MANAGER (self));
132
133 priv->queued_handler = 0;
134
135 hashtable = g_steal_pointer (&priv->queued_focus_by_toplevel);
136 g_hash_table_iter_init (&iter, hashtable);
137 while (g_hash_table_iter_next (&iter, (gpointer *)&toplevel, (gpointer *)&focus))
138 dzl_dock_manager_do_set_focus (self, focus, toplevel);
139
140 return G_SOURCE_REMOVE;
141 }
142
143 static void
dzl_dock_manager_set_focus(DzlDockManager * self,GtkWidget * focus,GtkWidget * toplevel)144 dzl_dock_manager_set_focus (DzlDockManager *self,
145 GtkWidget *focus,
146 GtkWidget *toplevel)
147 {
148 DzlDockManagerPrivate *priv = dzl_dock_manager_get_instance_private (self);
149
150 g_assert (DZL_IS_DOCK_MANAGER (self));
151 g_assert (GTK_IS_WINDOW (toplevel));
152
153 if (priv->queued_focus_by_toplevel == NULL)
154 priv->queued_focus_by_toplevel = g_hash_table_new (NULL, NULL);
155
156 /*
157 * Don't do anything if we get a NULL focus. Instead, wait for the focus
158 * to be updated with a widget.
159 */
160 if (focus == NULL)
161 {
162 g_hash_table_remove (priv->queued_focus_by_toplevel, toplevel);
163 return;
164 }
165
166 /*
167 * If focus is changing, we want to delay this until the end of the main
168 * loop cycle so that we don't do too much work when rapidly adding widgets
169 * to the hierarchy, as they may implicitly grab focus.
170 */
171 g_hash_table_insert (priv->queued_focus_by_toplevel, toplevel, focus);
172 dzl_clear_source (&priv->queued_handler);
173 priv->queued_handler = gdk_threads_add_idle (do_delayed_focus_update, self);
174 }
175
176 static void
dzl_dock_manager_hierarchy_changed(DzlDockManager * self,GtkWidget * old_toplevel,GtkWidget * widget)177 dzl_dock_manager_hierarchy_changed (DzlDockManager *self,
178 GtkWidget *old_toplevel,
179 GtkWidget *widget)
180 {
181 GtkWidget *toplevel;
182
183 g_assert (DZL_IS_DOCK_MANAGER (self));
184 g_assert (!old_toplevel || GTK_IS_WIDGET (old_toplevel));
185 g_assert (GTK_IS_WIDGET (widget));
186
187 if (GTK_IS_WINDOW (old_toplevel))
188 g_signal_handlers_disconnect_by_func (old_toplevel,
189 G_CALLBACK (dzl_dock_manager_set_focus),
190 self);
191
192 toplevel = gtk_widget_get_toplevel (widget);
193
194 if (GTK_IS_WINDOW (toplevel))
195 g_signal_connect_object (toplevel,
196 "set-focus",
197 G_CALLBACK (dzl_dock_manager_set_focus),
198 self,
199 G_CONNECT_SWAPPED);
200 }
201
202 static void
dzl_dock_manager_watch_toplevel(DzlDockManager * self,GtkWidget * widget)203 dzl_dock_manager_watch_toplevel (DzlDockManager *self,
204 GtkWidget *widget)
205 {
206 g_assert (DZL_IS_DOCK_MANAGER (self));
207 g_assert (GTK_IS_WIDGET (widget));
208
209 g_signal_connect_object (widget,
210 "hierarchy-changed",
211 G_CALLBACK (dzl_dock_manager_hierarchy_changed),
212 self,
213 G_CONNECT_SWAPPED);
214
215 dzl_dock_manager_hierarchy_changed (self, NULL, widget);
216 }
217
218 static void
dzl_dock_manager_weak_notify(gpointer data,GObject * where_the_object_was)219 dzl_dock_manager_weak_notify (gpointer data,
220 GObject *where_the_object_was)
221 {
222 DzlDockManager *self = data;
223 DzlDockManagerPrivate *priv = dzl_dock_manager_get_instance_private (self);
224
225 g_assert (DZL_IS_DOCK_MANAGER (self));
226
227 g_ptr_array_remove (priv->docks, where_the_object_was);
228 }
229
230 static void
dzl_dock_manager_real_register_dock(DzlDockManager * self,DzlDock * dock)231 dzl_dock_manager_real_register_dock (DzlDockManager *self,
232 DzlDock *dock)
233 {
234 DzlDockManagerPrivate *priv = dzl_dock_manager_get_instance_private (self);
235
236 g_return_if_fail (DZL_IS_DOCK_MANAGER (self));
237 g_return_if_fail (DZL_IS_DOCK (dock));
238
239 g_object_weak_ref (G_OBJECT (dock), dzl_dock_manager_weak_notify, self);
240 g_ptr_array_add (priv->docks, dock);
241 dzl_dock_manager_watch_toplevel (self, GTK_WIDGET (dock));
242 }
243
244 static void
dzl_dock_manager_real_unregister_dock(DzlDockManager * self,DzlDock * dock)245 dzl_dock_manager_real_unregister_dock (DzlDockManager *self,
246 DzlDock *dock)
247 {
248 DzlDockManagerPrivate *priv = dzl_dock_manager_get_instance_private (self);
249 guint i;
250
251 g_return_if_fail (DZL_IS_DOCK_MANAGER (self));
252 g_return_if_fail (DZL_IS_DOCK (dock));
253
254 for (i = 0; i < priv->docks->len; i++)
255 {
256 DzlDock *iter = g_ptr_array_index (priv->docks, i);
257
258 if (iter == dock)
259 {
260 g_object_weak_unref (G_OBJECT (dock), dzl_dock_manager_weak_notify, self);
261 g_ptr_array_remove_index (priv->docks, i);
262 break;
263 }
264 }
265 }
266
267 static void
dzl_dock_manager_finalize(GObject * object)268 dzl_dock_manager_finalize (GObject *object)
269 {
270 DzlDockManager *self = (DzlDockManager *)object;
271 DzlDockManagerPrivate *priv = dzl_dock_manager_get_instance_private (self);
272
273 g_clear_object (&priv->grab);
274
275 g_clear_pointer (&priv->queued_focus_by_toplevel, g_hash_table_unref);
276
277 dzl_clear_source (&priv->queued_handler);
278
279 while (priv->docks->len > 0)
280 {
281 DzlDock *dock = g_ptr_array_index (priv->docks, priv->docks->len - 1);
282
283 g_object_weak_unref (G_OBJECT (dock), dzl_dock_manager_weak_notify, self);
284 g_ptr_array_remove_index (priv->docks, priv->docks->len - 1);
285 }
286
287 g_clear_pointer (&priv->docks, g_ptr_array_unref);
288
289 G_OBJECT_CLASS (dzl_dock_manager_parent_class)->finalize (object);
290 }
291
292 static void
dzl_dock_manager_class_init(DzlDockManagerClass * klass)293 dzl_dock_manager_class_init (DzlDockManagerClass *klass)
294 {
295 GObjectClass *object_class = G_OBJECT_CLASS (klass);
296
297 object_class->finalize = dzl_dock_manager_finalize;
298
299 klass->register_dock = dzl_dock_manager_real_register_dock;
300 klass->unregister_dock = dzl_dock_manager_real_unregister_dock;
301
302 signals [REGISTER_DOCK] =
303 g_signal_new ("register-dock",
304 G_TYPE_FROM_CLASS (klass),
305 G_SIGNAL_RUN_LAST,
306 G_STRUCT_OFFSET (DzlDockManagerClass, register_dock),
307 NULL, NULL, NULL,
308 G_TYPE_NONE, 1, DZL_TYPE_DOCK);
309
310 signals [UNREGISTER_DOCK] =
311 g_signal_new ("unregister-dock",
312 G_TYPE_FROM_CLASS (klass),
313 G_SIGNAL_RUN_LAST,
314 G_STRUCT_OFFSET (DzlDockManagerClass, unregister_dock),
315 NULL, NULL, NULL,
316 G_TYPE_NONE, 1, DZL_TYPE_DOCK);
317 }
318
319 static void
dzl_dock_manager_init(DzlDockManager * self)320 dzl_dock_manager_init (DzlDockManager *self)
321 {
322 DzlDockManagerPrivate *priv = dzl_dock_manager_get_instance_private (self);
323
324 priv->docks = g_ptr_array_new ();
325 }
326
327 DzlDockManager *
dzl_dock_manager_new(void)328 dzl_dock_manager_new (void)
329 {
330 return g_object_new (DZL_TYPE_DOCK_MANAGER, NULL);
331 }
332
333 void
dzl_dock_manager_register_dock(DzlDockManager * self,DzlDock * dock)334 dzl_dock_manager_register_dock (DzlDockManager *self,
335 DzlDock *dock)
336 {
337 g_return_if_fail (DZL_IS_DOCK_MANAGER (self));
338 g_return_if_fail (DZL_IS_DOCK (dock));
339
340 g_signal_emit (self, signals [REGISTER_DOCK], 0, dock);
341 }
342
343 void
dzl_dock_manager_unregister_dock(DzlDockManager * self,DzlDock * dock)344 dzl_dock_manager_unregister_dock (DzlDockManager *self,
345 DzlDock *dock)
346 {
347 g_return_if_fail (DZL_IS_DOCK_MANAGER (self));
348 g_return_if_fail (DZL_IS_DOCK (dock));
349
350 g_signal_emit (self, signals [UNREGISTER_DOCK], 0, dock);
351 }
352
353 /**
354 * dzl_dock_manager_pause_grabs:
355 * @self: a #DzlDockManager
356 *
357 * Requests that the transient grab monitoring stop until
358 * dzl_dock_manager_unpause_grabs() is called.
359 *
360 * This might be useful while setting up UI so that you don't focus
361 * something unexpectedly.
362 *
363 * This function may be called multiple times and after an equivalent
364 * number of calls to dzl_dock_manager_unpause_grabs(), transient
365 * grab monitoring will continue.
366 *
367 * Since: 3.26
368 */
369 void
dzl_dock_manager_pause_grabs(DzlDockManager * self)370 dzl_dock_manager_pause_grabs (DzlDockManager *self)
371 {
372 DzlDockManagerPrivate *priv = dzl_dock_manager_get_instance_private (self);
373
374 g_return_if_fail (DZL_IS_DOCK_MANAGER (self));
375 g_return_if_fail (priv->pause_count >= 0);
376
377 priv->pause_count++;
378 }
379
380 /**
381 * dzl_dock_manager_unpause_grabs:
382 * @self: a #DzlDockManager
383 *
384 * Unpauses a previous call to dzl_dock_manager_pause_grabs().
385 *
386 * Once the pause count returns to zero, transient grab monitoring
387 * will be restored.
388 *
389 * Since: 3.26
390 */
391 void
dzl_dock_manager_unpause_grabs(DzlDockManager * self)392 dzl_dock_manager_unpause_grabs (DzlDockManager *self)
393 {
394 DzlDockManagerPrivate *priv = dzl_dock_manager_get_instance_private (self);
395
396 g_return_if_fail (DZL_IS_DOCK_MANAGER (self));
397 g_return_if_fail (priv->pause_count > 0);
398
399 priv->pause_count--;
400 }
401
402 void
dzl_dock_manager_release_transient_grab(DzlDockManager * self)403 dzl_dock_manager_release_transient_grab (DzlDockManager *self)
404 {
405 DzlDockManagerPrivate *priv = dzl_dock_manager_get_instance_private (self);
406
407 g_return_if_fail (DZL_IS_DOCK_MANAGER (self));
408
409 if (priv->grab != NULL)
410 {
411 g_autoptr(DzlDockTransientGrab) grab = g_steal_pointer (&priv->grab);
412 dzl_dock_transient_grab_cancel (grab);
413 }
414
415 dzl_clear_source (&priv->queued_handler);
416 }
417