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

source code of gtk/gtk/gtkwidgetfocus.c