1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
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 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
18 #include "adw-focus-private.h"
19
20 typedef struct _CompareInfo CompareInfo;
21
22 enum Axis {
23 HORIZONTAL = 0,
24 VERTICAL = 1
25 };
26
27 struct _CompareInfo
28 {
29 GtkWidget *widget;
30 int x;
31 int y;
32 guint reverse : 1;
33 guint axis : 1;
34 };
35
36 static inline void
get_axis_info(const graphene_rect_t * bounds,int axis,int * start,int * end)37 get_axis_info (const graphene_rect_t *bounds,
38 int axis,
39 int *start,
40 int *end)
41 {
42 if (axis == HORIZONTAL) {
43 *start = bounds->origin.x;
44 *end = bounds->size.width;
45 } else if (axis == VERTICAL) {
46 *start = bounds->origin.y;
47 *end = bounds->size.height;
48 } else {
49 g_assert_not_reached ();
50 }
51 }
52
53 /* Utility function, equivalent to g_list_reverse */
54 static void
reverse_ptr_array(GPtrArray * arr)55 reverse_ptr_array (GPtrArray *arr)
56 {
57 int i;
58
59 for (i = 0; i < arr->len / 2; i ++) {
60 void *a = g_ptr_array_index (arr, i);
61 void *b = g_ptr_array_index (arr, arr->len - 1 - i);
62
63 arr->pdata[i] = b;
64 arr->pdata[arr->len - 1 - i] = a;
65 }
66 }
67
68 static int
tab_sort_func(gconstpointer a,gconstpointer b,gpointer user_data)69 tab_sort_func (gconstpointer a,
70 gconstpointer b,
71 gpointer user_data)
72 {
73 graphene_rect_t child_bounds1, child_bounds2;
74 GtkWidget *child1 = *((GtkWidget **) a);
75 GtkWidget *child2 = *((GtkWidget **) b);
76 GtkTextDirection text_direction = GPOINTER_TO_INT (user_data);
77 float y1, y2;
78
79 if (!gtk_widget_compute_bounds (child1, gtk_widget_get_parent (child1), &child_bounds1) ||
80 !gtk_widget_compute_bounds (child2, gtk_widget_get_parent (child2), &child_bounds2))
81 return 0;
82
83 y1 = child_bounds1.origin.y + (child_bounds1.size.height / 2.0f);
84 y2 = child_bounds2.origin.y + (child_bounds2.size.height / 2.0f);
85
86 if (y1 == y2) {
87 const float x1 = child_bounds1.origin.x + (child_bounds1.size.width / 2.0f);
88 const float x2 = child_bounds2.origin.x + (child_bounds2.size.width / 2.0f);
89
90 if (text_direction == GTK_TEXT_DIR_RTL)
91 return (x1 < x2) ? 1 : ((x1 == x2) ? 0 : -1);
92 else
93 return (x1 < x2) ? -1 : ((x1 == x2) ? 0 : 1);
94 }
95
96 return (y1 < y2) ? -1 : 1;
97 }
98
99 static void
focus_sort_tab(GtkWidget * widget,GtkDirectionType direction,GPtrArray * focus_order)100 focus_sort_tab (GtkWidget *widget,
101 GtkDirectionType direction,
102 GPtrArray *focus_order)
103 {
104 GtkTextDirection text_direction = gtk_widget_get_direction (widget);
105
106 g_ptr_array_sort_with_data (focus_order, tab_sort_func, GINT_TO_POINTER (text_direction));
107
108 if (direction == GTK_DIR_TAB_BACKWARD)
109 reverse_ptr_array (focus_order);
110 }
111
112 /* Look for a child in @children that is intermediate between
113 * the focus widget and container. This widget, if it exists,
114 * acts as the starting widget for focus navigation.
115 */
116 static GtkWidget *
find_old_focus(GtkWidget * widget,GPtrArray * children)117 find_old_focus (GtkWidget *widget,
118 GPtrArray *children)
119 {
120 int i;
121
122 for (i = 0; i < children->len; i ++) {
123 GtkWidget *child = g_ptr_array_index (children, i);
124 GtkWidget *child_ptr = child;
125
126 while (child_ptr && child_ptr != widget) {
127 GtkWidget *parent = gtk_widget_get_parent (child_ptr);
128
129 if (parent && (gtk_widget_get_focus_child (parent) != child_ptr)) {
130 child = NULL;
131 break;
132 }
133
134 child_ptr = parent;
135 }
136
137 if (child)
138 return child;
139 }
140
141 return NULL;
142 }
143
144 static gboolean
old_focus_coords(GtkWidget * widget,graphene_rect_t * old_focus_bounds)145 old_focus_coords (GtkWidget *widget,
146 graphene_rect_t *old_focus_bounds)
147 {
148 GtkWidget *old_focus;
149
150 old_focus = gtk_root_get_focus (gtk_widget_get_root (widget));
151 if (old_focus)
152 return gtk_widget_compute_bounds (old_focus, widget, old_focus_bounds);
153
154 return FALSE;
155 }
156
157 static int
axis_compare(gconstpointer a,gconstpointer b,gpointer user_data)158 axis_compare (gconstpointer a,
159 gconstpointer b,
160 gpointer user_data)
161 {
162 graphene_rect_t bounds1;
163 graphene_rect_t bounds2;
164 CompareInfo *compare = user_data;
165 int start1, end1;
166 int start2, end2;
167
168 if (!gtk_widget_compute_bounds (*((GtkWidget **) a), compare->widget, &bounds1) ||
169 !gtk_widget_compute_bounds (*((GtkWidget **) b), compare->widget, &bounds2))
170 return 0;
171
172 get_axis_info (&bounds1, compare->axis, &start1, &end1);
173 get_axis_info (&bounds2, compare->axis, &start2, &end2);
174
175 start1 = start1 + (end1 / 2);
176 start2 = start2 + (end2 / 2);
177
178 if (start1 == start2) {
179 int x1, x2;
180
181 /* Now use origin/bounds to compare the 2 widgets on the other axis */
182 get_axis_info (&bounds1, 1 - compare->axis, &start1, &end1);
183 get_axis_info (&bounds2, 1 - compare->axis, &start2, &end2);
184
185 x1 = abs (start1 + (end1 / 2) - compare->x);
186 x2 = abs (start2 + (end2 / 2) - compare->x);
187
188 if (compare->reverse)
189 return (x1 < x2) ? 1 : ((x1 == x2) ? 0 : -1);
190 else
191 return (x1 < x2) ? -1 : ((x1 == x2) ? 0 : 1);
192 }
193
194 return (start1 < start2) ? -1 : 1;
195 }
196
197 static void
focus_sort_left_right(GtkWidget * widget,GtkDirectionType direction,GPtrArray * focus_order)198 focus_sort_left_right (GtkWidget *widget,
199 GtkDirectionType direction,
200 GPtrArray *focus_order)
201 {
202 CompareInfo compare_info;
203 GtkWidget *old_focus = gtk_widget_get_focus_child (widget);
204 graphene_rect_t old_bounds;
205
206 compare_info.widget = widget;
207 compare_info.reverse = (direction == GTK_DIR_LEFT);
208
209 if (!old_focus)
210 old_focus = find_old_focus (widget, focus_order);
211
212 if (old_focus && gtk_widget_compute_bounds (old_focus, widget, &old_bounds)) {
213 float compare_y1;
214 float compare_y2;
215 float compare_x;
216 int i;
217
218 /* Delete widgets from list that don't match minimum criteria */
219
220 compare_y1 = old_bounds.origin.y;
221 compare_y2 = old_bounds.origin.y + old_bounds.size.height;
222
223 if (direction == GTK_DIR_LEFT)
224 compare_x = old_bounds.origin.x;
225 else
226 compare_x = old_bounds.origin.x + old_bounds.size.width;
227
228 for (i = 0; i < focus_order->len; i++) {
229 GtkWidget *child = g_ptr_array_index (focus_order, i);
230
231 if (child != old_focus) {
232 graphene_rect_t child_bounds;
233
234 if (gtk_widget_compute_bounds (child, widget, &child_bounds)) {
235 const float child_y1 = child_bounds.origin.y;
236 const float child_y2 = child_bounds.origin.y + child_bounds.size.height;
237
238 if ((child_y2 <= compare_y1 || child_y1 >= compare_y2) /* No vertical overlap */ ||
239 (direction == GTK_DIR_RIGHT && child_bounds.origin.x + child_bounds.size.width < compare_x) || /* Not to left */
240 (direction == GTK_DIR_LEFT && child_bounds.origin.x > compare_x)) /* Not to right */ {
241 g_ptr_array_remove_index (focus_order, i);
242 i --;
243 }
244 } else {
245 g_ptr_array_remove_index (focus_order, i);
246 i --;
247 }
248 }
249 }
250
251 compare_info.y = (compare_y1 + compare_y2) / 2;
252 compare_info.x = old_bounds.origin.x + (old_bounds.size.width / 2.0f);
253 } else {
254 /* No old focus widget, need to figure out starting x,y some other way
255 */
256 graphene_rect_t bounds;
257 GtkWidget *parent;
258 graphene_rect_t old_focus_bounds;
259
260 parent = gtk_widget_get_parent (widget);
261 if (!gtk_widget_compute_bounds (widget, parent ? parent : widget, &bounds))
262 graphene_rect_init (&bounds, 0, 0, 0, 0);
263
264 if (old_focus_coords (widget, &old_focus_bounds)) {
265 compare_info.y = old_focus_bounds.origin.y + (old_focus_bounds.size.height / 2.0f);
266 } else {
267 if (!GTK_IS_NATIVE (widget))
268 compare_info.y = bounds.origin.y + bounds.size.height;
269 else
270 compare_info.y = bounds.size.height / 2.0f;
271 }
272
273 if (!GTK_IS_NATIVE (widget))
274 compare_info.x = (direction == GTK_DIR_RIGHT) ? bounds.origin.x : bounds.origin.x + bounds.size.width;
275 else
276 compare_info.x = (direction == GTK_DIR_RIGHT) ? 0 : bounds.size.width;
277 }
278
279 compare_info.axis = HORIZONTAL;
280 g_ptr_array_sort_with_data (focus_order, axis_compare, &compare_info);
281
282 if (compare_info.reverse)
283 reverse_ptr_array (focus_order);
284 }
285
286 static void
focus_sort_up_down(GtkWidget * widget,GtkDirectionType direction,GPtrArray * focus_order)287 focus_sort_up_down (GtkWidget *widget,
288 GtkDirectionType direction,
289 GPtrArray *focus_order)
290 {
291 CompareInfo compare_info;
292 GtkWidget *old_focus = gtk_widget_get_focus_child (widget);
293 graphene_rect_t old_bounds;
294
295 compare_info.widget = widget;
296 compare_info.reverse = (direction == GTK_DIR_UP);
297
298 if (!old_focus)
299 old_focus = find_old_focus (widget, focus_order);
300
301 if (old_focus && gtk_widget_compute_bounds (old_focus, widget, &old_bounds)) {
302 float compare_x1;
303 float compare_x2;
304 float compare_y;
305 int i;
306
307 /* Delete widgets from list that don't match minimum criteria */
308
309 compare_x1 = old_bounds.origin.x;
310 compare_x2 = old_bounds.origin.x + old_bounds.size.width;
311
312 if (direction == GTK_DIR_UP)
313 compare_y = old_bounds.origin.y;
314 else
315 compare_y = old_bounds.origin.y + old_bounds.size.height;
316
317 for (i = 0; i < focus_order->len; i++) {
318 GtkWidget *child = g_ptr_array_index (focus_order, i);
319
320 if (child != old_focus) {
321 graphene_rect_t child_bounds;
322
323 if (gtk_widget_compute_bounds (child, widget, &child_bounds)) {
324 const float child_x1 = child_bounds.origin.x;
325 const float child_x2 = child_bounds.origin.x + child_bounds.size.width;
326
327 if ((child_x2 <= compare_x1 || child_x1 >= compare_x2) /* No horizontal overlap */ ||
328 (direction == GTK_DIR_DOWN && child_bounds.origin.y + child_bounds.size.height < compare_y) || /* Not below */
329 (direction == GTK_DIR_UP && child_bounds.origin.y > compare_y)) /* Not above */ {
330 g_ptr_array_remove_index (focus_order, i);
331 i --;
332 }
333 } else {
334 g_ptr_array_remove_index (focus_order, i);
335 i --;
336 }
337 }
338 }
339
340 compare_info.x = (compare_x1 + compare_x2) / 2;
341 compare_info.y = old_bounds.origin.y + (old_bounds.size.height / 2.0f);
342 } else {
343 /* No old focus widget, need to figure out starting x,y some other way
344 */
345 GtkWidget *parent;
346 graphene_rect_t bounds;
347 graphene_rect_t old_focus_bounds;
348
349 parent = gtk_widget_get_parent (widget);
350 if (!gtk_widget_compute_bounds (widget, parent ? parent : widget, &bounds))
351 graphene_rect_init (&bounds, 0, 0, 0, 0);
352
353 if (old_focus_coords (widget, &old_focus_bounds)) {
354 compare_info.x = old_focus_bounds.origin.x + (old_focus_bounds.size.width / 2.0f);
355 } else {
356 if (!GTK_IS_NATIVE (widget))
357 compare_info.x = bounds.origin.x + (bounds.size.width / 2.0f);
358 else
359 compare_info.x = bounds.size.width / 2.0f;
360 }
361
362 if (!GTK_IS_NATIVE (widget))
363 compare_info.y = (direction == GTK_DIR_DOWN) ? bounds.origin.y : bounds.origin.y + bounds.size.height;
364 else
365 compare_info.y = (direction == GTK_DIR_DOWN) ? 0 : + bounds.size.height;
366 }
367
368 compare_info.axis = VERTICAL;
369 g_ptr_array_sort_with_data (focus_order, axis_compare, &compare_info);
370
371 if (compare_info.reverse)
372 reverse_ptr_array (focus_order);
373 }
374
375 static void
focus_sort(GtkWidget * widget,GtkDirectionType direction,GPtrArray * focus_order)376 focus_sort (GtkWidget *widget,
377 GtkDirectionType direction,
378 GPtrArray *focus_order)
379 {
380 GtkWidget *child;
381
382 g_assert (focus_order != NULL);
383
384 if (focus_order->len == 0) {
385 /* Initialize the list with all visible child widgets */
386 for (child = gtk_widget_get_first_child (widget);
387 child != NULL;
388 child = gtk_widget_get_next_sibling (child)) {
389 if (gtk_widget_get_mapped (child) &&
390 gtk_widget_get_sensitive (child))
391 g_ptr_array_add (focus_order, child);
392 }
393 }
394
395 /* Now sort that list depending on @direction */
396 switch (direction) {
397 case GTK_DIR_TAB_FORWARD:
398 case GTK_DIR_TAB_BACKWARD:
399 focus_sort_tab (widget, direction, focus_order);
400 break;
401 case GTK_DIR_UP:
402 case GTK_DIR_DOWN:
403 focus_sort_up_down (widget, direction, focus_order);
404 break;
405 case GTK_DIR_LEFT:
406 case GTK_DIR_RIGHT:
407 focus_sort_left_right (widget, direction, focus_order);
408 break;
409 default:
410 g_assert_not_reached ();
411 }
412 }
413
414
415 static gboolean
focus_move(GtkWidget * widget,GtkDirectionType direction)416 focus_move (GtkWidget *widget,
417 GtkDirectionType direction)
418 {
419 GPtrArray *focus_order;
420 GtkWidget *focus_child = gtk_widget_get_focus_child (widget);
421 int i;
422 gboolean ret = FALSE;
423
424 focus_order = g_ptr_array_new ();
425 focus_sort (widget, direction, focus_order);
426
427 for (i = 0; i < focus_order->len && !ret; i++) {
428 GtkWidget *child = g_ptr_array_index (focus_order, i);
429
430 if (focus_child) {
431 if (focus_child == child) {
432 focus_child = NULL;
433 ret = gtk_widget_child_focus (child, direction);
434 }
435 } else if (gtk_widget_get_mapped (child) &&
436 gtk_widget_is_ancestor (child, widget)) {
437 ret = gtk_widget_child_focus (child, direction);
438 }
439 }
440
441 g_ptr_array_unref (focus_order);
442
443 return ret;
444 }
445
446 gboolean
adw_widget_focus_child(GtkWidget * widget,GtkDirectionType direction)447 adw_widget_focus_child (GtkWidget *widget,
448 GtkDirectionType direction)
449 {
450 return focus_move (widget, direction);
451 }
452
453 gboolean
adw_widget_grab_focus_self(GtkWidget * widget)454 adw_widget_grab_focus_self (GtkWidget *widget)
455 {
456 if (!gtk_widget_get_focusable (widget))
457 return FALSE;
458
459 gtk_root_set_focus (gtk_widget_get_root (widget), widget);
460
461 return TRUE;
462 }
463
464 gboolean
adw_widget_grab_focus_child(GtkWidget * widget)465 adw_widget_grab_focus_child (GtkWidget *widget)
466 {
467 GtkWidget *child;
468
469 for (child = gtk_widget_get_first_child (widget);
470 child != NULL;
471 child = gtk_widget_get_next_sibling (child))
472 if (gtk_widget_grab_focus (child))
473 return TRUE;
474
475 return FALSE;
476 }
477