forked from AuroraMiddleware/gtk
Implement tab/up-down/left-right focus sorting for widgets
basically do what GtkContainer already did.
This commit is contained in:
parent
6e9d857714
commit
c9fc201f77
@ -931,6 +931,7 @@ gtk_base_c_sources = \
|
||||
gtkvolumebutton.c \
|
||||
gtkviewport.c \
|
||||
gtkwidget.c \
|
||||
gtkwidgetfocus.c \
|
||||
gtkwidgetpath.c \
|
||||
gtkwindow.c \
|
||||
gtkwindowgroup.c \
|
||||
|
@ -7318,36 +7318,25 @@ gtk_widget_real_focus (GtkWidget *widget,
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
else if (_gtk_widget_get_first_child (widget) == NULL)
|
||||
{
|
||||
/* No children, no possibility to focus anything */
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* @widget can't be focused, but maybe one of its child widgets. */
|
||||
GtkWidget *focus_child = gtk_widget_get_focus_child (widget);
|
||||
GtkWidget *child;
|
||||
GPtrArray *focus_order = g_ptr_array_new ();
|
||||
gboolean ret = FALSE;
|
||||
|
||||
if (focus_child != NULL)
|
||||
{
|
||||
if (gtk_widget_child_focus (focus_child, direction))
|
||||
return TRUE;
|
||||
/* Try focusing any of the child widgets, depending on the given @direction */
|
||||
|
||||
child = focus_child;
|
||||
}
|
||||
else
|
||||
{
|
||||
child = _gtk_widget_get_first_child (widget);
|
||||
}
|
||||
gtk_widget_focus_sort (widget, direction, focus_order);
|
||||
ret = gtk_widget_focus_move (widget, direction, focus_order);
|
||||
|
||||
/* The current focus child didn't handle the focus, so lets'
|
||||
try all its siblings. If none of them accepts it, we simply
|
||||
have to return FALSE since we couldn't handle it either. */
|
||||
for (;
|
||||
child != NULL;
|
||||
child = _gtk_widget_get_next_sibling (child))
|
||||
{
|
||||
g_ptr_array_unref (focus_order);
|
||||
|
||||
if (_gtk_widget_is_drawable (child) &&
|
||||
gtk_widget_child_focus (child, direction))
|
||||
return TRUE;
|
||||
}
|
||||
if (ret)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
|
489
gtk/gtkwidgetfocus.c
Normal file
489
gtk/gtkwidgetfocus.c
Normal file
@ -0,0 +1,489 @@
|
||||
/* GTK - The GIMP Toolkit
|
||||
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "gtkwidgetprivate.h"
|
||||
#include "gtkwindow.h"
|
||||
|
||||
typedef struct _CompareInfo CompareInfo;
|
||||
|
||||
struct _CompareInfo
|
||||
{
|
||||
GtkWidget *widget;
|
||||
int x;
|
||||
int y;
|
||||
gboolean reverse : 1;
|
||||
};
|
||||
|
||||
/* Utility function, equivalent to g_list_reverse */
|
||||
static void
|
||||
reverse_ptr_array (GPtrArray *arr)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < arr->len / 2; i ++)
|
||||
{
|
||||
void *a = g_ptr_array_index (arr, i);
|
||||
void *b = g_ptr_array_index (arr, arr->len - 1 - i);
|
||||
|
||||
arr->pdata[i] = b;
|
||||
arr->pdata[arr->len - 1 - i] = a;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get coordinates of @widget's allocation with respect to
|
||||
* allocation of @container.
|
||||
*/
|
||||
static gboolean
|
||||
get_allocation_coords (GtkWidget *widget,
|
||||
GtkWidget *child,
|
||||
GdkRectangle *allocation)
|
||||
{
|
||||
gtk_widget_get_allocation (child, allocation);
|
||||
|
||||
return gtk_widget_translate_coordinates (child, widget,
|
||||
0, 0, &allocation->x, &allocation->y);
|
||||
}
|
||||
|
||||
static int
|
||||
tab_sort_func (gconstpointer a,
|
||||
gconstpointer b,
|
||||
gpointer user_data)
|
||||
{
|
||||
GtkAllocation child1_allocation, child2_allocation;
|
||||
const GtkWidget *child1 = *((GtkWidget **)a);
|
||||
const GtkWidget *child2 = *((GtkWidget **)b);
|
||||
GtkTextDirection text_direction = GPOINTER_TO_INT (user_data);
|
||||
int y1, y2;
|
||||
|
||||
_gtk_widget_get_allocation ((GtkWidget *) child1, &child1_allocation);
|
||||
_gtk_widget_get_allocation ((GtkWidget *) child2, &child2_allocation);
|
||||
|
||||
y1 = child1_allocation.y + child1_allocation.height / 2;
|
||||
y2 = child2_allocation.y + child2_allocation.height / 2;
|
||||
|
||||
if (y1 == y2)
|
||||
{
|
||||
int x1 = child1_allocation.x + child1_allocation.width / 2;
|
||||
int x2 = child2_allocation.x + child2_allocation.width / 2;
|
||||
|
||||
if (text_direction == GTK_TEXT_DIR_RTL)
|
||||
return (x1 < x2) ? 1 : ((x1 == x2) ? 0 : -1);
|
||||
else
|
||||
return (x1 < x2) ? -1 : ((x1 == x2) ? 0 : 1);
|
||||
}
|
||||
else
|
||||
return (y1 < y2) ? -1 : 1;
|
||||
}
|
||||
|
||||
static void
|
||||
focus_sort_tab (GtkWidget *widget,
|
||||
GtkDirectionType direction,
|
||||
GPtrArray *focus_order)
|
||||
{
|
||||
GtkTextDirection text_direction = _gtk_widget_get_direction (widget);
|
||||
|
||||
g_ptr_array_sort_with_data (focus_order, tab_sort_func, GINT_TO_POINTER (text_direction));
|
||||
|
||||
if (direction == GTK_DIR_TAB_BACKWARD)
|
||||
reverse_ptr_array (focus_order);
|
||||
}
|
||||
|
||||
/* Look for a child in @children that is intermediate between
|
||||
* the focus widget and container. This widget, if it exists,
|
||||
* acts as the starting widget for focus navigation.
|
||||
*/
|
||||
static GtkWidget *
|
||||
find_old_focus (GtkWidget *widget,
|
||||
GPtrArray *children)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < children->len; i ++)
|
||||
{
|
||||
GtkWidget *child = g_ptr_array_index (children, i);
|
||||
GtkWidget *child_ptr = child;
|
||||
|
||||
while (child_ptr && child_ptr != widget)
|
||||
{
|
||||
GtkWidget *parent;
|
||||
|
||||
parent = _gtk_widget_get_parent (child_ptr);
|
||||
|
||||
if (parent && (gtk_widget_get_focus_child (parent) != child_ptr))
|
||||
{
|
||||
child = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
child_ptr = parent;
|
||||
}
|
||||
|
||||
if (child)
|
||||
return child;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
old_focus_coords (GtkWidget *widget,
|
||||
GdkRectangle *old_focus_rect)
|
||||
{
|
||||
GtkWidget *toplevel = _gtk_widget_get_toplevel (widget);
|
||||
GtkWidget *old_focus;
|
||||
|
||||
if (GTK_IS_WINDOW (toplevel))
|
||||
{
|
||||
old_focus = gtk_window_get_focus (GTK_WINDOW (toplevel));
|
||||
if (old_focus)
|
||||
return get_allocation_coords (widget, old_focus, old_focus_rect);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
left_right_compare (gconstpointer a,
|
||||
gconstpointer b,
|
||||
gpointer user_data)
|
||||
{
|
||||
const GtkWidget *child1 = *((GtkWidget **)a);
|
||||
const GtkWidget *child2 = *((GtkWidget **)b);
|
||||
GdkRectangle allocation1;
|
||||
GdkRectangle allocation2;
|
||||
CompareInfo *compare = user_data;
|
||||
int x1, x2;
|
||||
|
||||
get_allocation_coords (compare->widget, (GtkWidget *)child1, &allocation1);
|
||||
get_allocation_coords (compare->widget, (GtkWidget *)child2, &allocation2);
|
||||
|
||||
x1 = allocation1.x + allocation1.width / 2;
|
||||
x2 = allocation2.x + allocation2.width / 2;
|
||||
|
||||
if (x1 == x2)
|
||||
{
|
||||
int y1 = abs (allocation1.y + allocation1.height / 2 - compare->y);
|
||||
int y2 = abs (allocation2.y + allocation2.height / 2 - compare->y);
|
||||
|
||||
if (compare->reverse)
|
||||
return (y1 < y2) ? 1 : ((y1 == y2) ? 0 : -1);
|
||||
else
|
||||
return (y1 < y2) ? -1 : ((y1 == y2) ? 0 : 1);
|
||||
}
|
||||
else
|
||||
return (x1 < x2) ? -1 : 1;
|
||||
}
|
||||
|
||||
static int
|
||||
up_down_compare (gconstpointer a,
|
||||
gconstpointer b,
|
||||
gpointer user_data)
|
||||
{
|
||||
const GtkWidget *child1 = *((GtkWidget **)a);
|
||||
const GtkWidget *child2 = *((GtkWidget **)b);
|
||||
GdkRectangle allocation1;
|
||||
GdkRectangle allocation2;
|
||||
CompareInfo *compare = user_data;
|
||||
int y1, y2;
|
||||
|
||||
get_allocation_coords (compare->widget, (GtkWidget *)child1, &allocation1);
|
||||
get_allocation_coords (compare->widget, (GtkWidget *)child2, &allocation2);
|
||||
|
||||
y1 = allocation1.y + allocation1.height / 2;
|
||||
y2 = allocation2.y + allocation2.height / 2;
|
||||
|
||||
if (y1 == y2)
|
||||
{
|
||||
int x1 = abs (allocation1.x + allocation1.width / 2 - compare->x);
|
||||
int x2 = abs (allocation2.x + allocation2.width / 2 - compare->x);
|
||||
|
||||
if (compare->reverse)
|
||||
return (x1 < x2) ? 1 : ((x1 == x2) ? 0 : -1);
|
||||
else
|
||||
return (x1 < x2) ? -1 : ((x1 == x2) ? 0 : 1);
|
||||
}
|
||||
else
|
||||
return (y1 < y2) ? -1 : 1;
|
||||
}
|
||||
|
||||
static void
|
||||
focus_sort_left_right (GtkWidget *widget,
|
||||
GtkDirectionType direction,
|
||||
GPtrArray *focus_order)
|
||||
{
|
||||
CompareInfo compare_info;
|
||||
GdkRectangle old_allocation;
|
||||
GtkWidget *old_focus = gtk_widget_get_focus_child (widget);
|
||||
|
||||
compare_info.widget = widget;
|
||||
compare_info.reverse = (direction == GTK_DIR_LEFT);
|
||||
|
||||
if (!old_focus)
|
||||
old_focus = find_old_focus (widget, focus_order);
|
||||
|
||||
if (old_focus && get_allocation_coords (widget, old_focus, &old_allocation))
|
||||
{
|
||||
int compare_y1;
|
||||
int compare_y2;
|
||||
int compare_x;
|
||||
int i;
|
||||
|
||||
/* Delete widgets from list that don't match minimum criteria */
|
||||
|
||||
compare_y1 = old_allocation.y;
|
||||
compare_y2 = old_allocation.y + old_allocation.height;
|
||||
|
||||
if (direction == GTK_DIR_LEFT)
|
||||
compare_x = old_allocation.x;
|
||||
else
|
||||
compare_x = old_allocation.x + old_allocation.width;
|
||||
|
||||
for (i = 0; i < focus_order->len; i ++)
|
||||
{
|
||||
GtkWidget *child = g_ptr_array_index (focus_order, i);
|
||||
int child_y1, child_y2;
|
||||
GdkRectangle child_allocation;
|
||||
|
||||
if (child != old_focus)
|
||||
{
|
||||
if (get_allocation_coords (widget, child, &child_allocation))
|
||||
{
|
||||
child_y1 = child_allocation.y;
|
||||
child_y2 = child_allocation.y + child_allocation.height;
|
||||
|
||||
if ((child_y2 <= compare_y1 || child_y1 >= compare_y2) /* No vertical overlap */ ||
|
||||
(direction == GTK_DIR_RIGHT && child_allocation.x + child_allocation.width < compare_x) || /* Not to left */
|
||||
(direction == GTK_DIR_LEFT && child_allocation.x > compare_x)) /* Not to right */
|
||||
{
|
||||
g_ptr_array_remove_index (focus_order, i);
|
||||
i --;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
g_ptr_array_remove_index (focus_order, i);
|
||||
i --;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compare_info.y = (compare_y1 + compare_y2) / 2;
|
||||
compare_info.x = old_allocation.x + old_allocation.width / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* No old focus widget, need to figure out starting x,y some other way
|
||||
*/
|
||||
GtkAllocation allocation;
|
||||
GdkRectangle old_focus_rect;
|
||||
|
||||
_gtk_widget_get_allocation (widget, &allocation);
|
||||
|
||||
if (old_focus_coords (widget, &old_focus_rect))
|
||||
{
|
||||
compare_info.y = old_focus_rect.y + old_focus_rect.height / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_gtk_widget_get_has_window (widget))
|
||||
compare_info.y = allocation.y + allocation.height / 2;
|
||||
else
|
||||
compare_info.y = allocation.height / 2;
|
||||
}
|
||||
|
||||
if (!_gtk_widget_get_has_window (widget))
|
||||
compare_info.x = (direction == GTK_DIR_RIGHT) ? allocation.x : allocation.x + allocation.width;
|
||||
else
|
||||
compare_info.x = (direction == GTK_DIR_RIGHT) ? 0 : allocation.width;
|
||||
}
|
||||
|
||||
|
||||
g_ptr_array_sort_with_data (focus_order, left_right_compare, &compare_info);
|
||||
|
||||
if (compare_info.reverse)
|
||||
reverse_ptr_array (focus_order);
|
||||
}
|
||||
|
||||
static void
|
||||
focus_sort_up_down (GtkWidget *widget,
|
||||
GtkDirectionType direction,
|
||||
GPtrArray *focus_order)
|
||||
{
|
||||
CompareInfo compare_info;
|
||||
GdkRectangle old_allocation;
|
||||
GtkWidget *old_focus = gtk_widget_get_focus_child (widget);
|
||||
|
||||
compare_info.widget = widget;
|
||||
compare_info.reverse = (direction == GTK_DIR_UP);
|
||||
|
||||
if (!old_focus)
|
||||
old_focus = find_old_focus (widget, focus_order);
|
||||
|
||||
if (old_focus && get_allocation_coords (widget, old_focus, &old_allocation))
|
||||
{
|
||||
int compare_x1;
|
||||
int compare_x2;
|
||||
int compare_y;
|
||||
int i;
|
||||
|
||||
/* Delete widgets from list that don't match minimum criteria */
|
||||
|
||||
compare_x1 = old_allocation.x;
|
||||
compare_x2 = old_allocation.x + old_allocation.width;
|
||||
|
||||
if (direction == GTK_DIR_UP)
|
||||
compare_y = old_allocation.y;
|
||||
else
|
||||
compare_y = old_allocation.y + old_allocation.height;
|
||||
|
||||
for (i = 0; i < focus_order->len; i ++)
|
||||
{
|
||||
GtkWidget *child = g_ptr_array_index (focus_order, i);
|
||||
int child_x1, child_x2;
|
||||
GdkRectangle child_allocation;
|
||||
|
||||
if (child != old_focus)
|
||||
{
|
||||
if (get_allocation_coords (widget, child, &child_allocation))
|
||||
{
|
||||
child_x1 = child_allocation.x;
|
||||
child_x2 = child_allocation.x + child_allocation.width;
|
||||
|
||||
if ((child_x2 <= compare_x1 || child_x1 >= compare_x2) /* No horizontal overlap */ ||
|
||||
(direction == GTK_DIR_DOWN && child_allocation.y + child_allocation.height < compare_y) || /* Not below */
|
||||
(direction == GTK_DIR_UP && child_allocation.y > compare_y)) /* Not above */
|
||||
{
|
||||
g_ptr_array_remove_index (focus_order, i);
|
||||
i --;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
g_ptr_array_remove_index (focus_order, i);
|
||||
i --;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compare_info.x = (compare_x1 + compare_x2) / 2;
|
||||
compare_info.y = old_allocation.y + old_allocation.height / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* No old focus widget, need to figure out starting x,y some other way
|
||||
*/
|
||||
GtkAllocation allocation;
|
||||
GdkRectangle old_focus_rect;
|
||||
|
||||
_gtk_widget_get_allocation (widget, &allocation);
|
||||
|
||||
if (old_focus_coords (widget, &old_focus_rect))
|
||||
{
|
||||
compare_info.x = old_focus_rect.x + old_focus_rect.width / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_gtk_widget_get_has_window (widget))
|
||||
compare_info.x = allocation.x + allocation.width / 2;
|
||||
else
|
||||
compare_info.x = allocation.width / 2;
|
||||
}
|
||||
|
||||
if (!_gtk_widget_get_has_window (widget))
|
||||
compare_info.y = (direction == GTK_DIR_DOWN) ? allocation.y : allocation.y + allocation.height;
|
||||
else
|
||||
compare_info.y = (direction == GTK_DIR_DOWN) ? 0 : + allocation.height;
|
||||
}
|
||||
|
||||
g_ptr_array_sort_with_data (focus_order, up_down_compare, &compare_info);
|
||||
|
||||
if (compare_info.reverse)
|
||||
reverse_ptr_array (focus_order);
|
||||
}
|
||||
|
||||
void
|
||||
gtk_widget_focus_sort (GtkWidget *widget,
|
||||
GtkDirectionType direction,
|
||||
GPtrArray *focus_order)
|
||||
{
|
||||
GtkWidget *child;
|
||||
|
||||
g_assert (focus_order->len == 0);
|
||||
|
||||
/* Initialize the list with all realized child widgets */
|
||||
for (child = _gtk_widget_get_first_child (widget);
|
||||
child != NULL;
|
||||
child = _gtk_widget_get_next_sibling (child))
|
||||
{
|
||||
if (_gtk_widget_get_realized (child))
|
||||
g_ptr_array_add (focus_order, child);
|
||||
}
|
||||
|
||||
/* Now sort that list depending on @direction */
|
||||
switch (direction)
|
||||
{
|
||||
case GTK_DIR_TAB_FORWARD:
|
||||
case GTK_DIR_TAB_BACKWARD:
|
||||
focus_sort_tab (widget, direction, focus_order);
|
||||
break;
|
||||
case GTK_DIR_UP:
|
||||
case GTK_DIR_DOWN:
|
||||
focus_sort_up_down (widget, direction, focus_order);
|
||||
break;
|
||||
case GTK_DIR_LEFT:
|
||||
case GTK_DIR_RIGHT:
|
||||
focus_sort_left_right (widget, direction, focus_order);
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
gtk_widget_focus_move (GtkWidget *widget,
|
||||
GtkDirectionType direction,
|
||||
GPtrArray *focus_order)
|
||||
{
|
||||
GtkWidget *focus_child = gtk_widget_get_focus_child (widget);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < focus_order->len; i ++)
|
||||
{
|
||||
GtkWidget *child = g_ptr_array_index (focus_order, i);
|
||||
|
||||
if (focus_child)
|
||||
{
|
||||
if (focus_child == child)
|
||||
{
|
||||
focus_child = NULL;
|
||||
|
||||
if (gtk_widget_child_focus (child, direction))
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
else if (_gtk_widget_is_drawable (child) &&
|
||||
gtk_widget_is_ancestor (child, widget))
|
||||
{
|
||||
if (gtk_widget_child_focus (child, direction))
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
@ -307,6 +307,13 @@ void gtk_widget_forall (GtkWidget
|
||||
|
||||
GtkWidget *gtk_widget_get_focus_child (GtkWidget *widget);
|
||||
|
||||
void gtk_widget_focus_sort (GtkWidget *widget,
|
||||
GtkDirectionType direction,
|
||||
GPtrArray *focus_order);
|
||||
gboolean gtk_widget_focus_move (GtkWidget *widget,
|
||||
GtkDirectionType direction,
|
||||
GPtrArray *focus_order);
|
||||
|
||||
/* inline getters */
|
||||
|
||||
static inline gboolean
|
||||
|
Loading…
Reference in New Issue
Block a user