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