Tab Order on a DIALOG

Tab Order on a DIALOG

Postby xProgrammer » Sun Mar 09, 2008 9:29 pm

Hi Antonio

It would be wonderful if you could see if there is any way to control the tab order on a dialog. I would be equally happy to be able to either control the tab order or, alternatively, to merely exclude some controls (in my case several BUTTONs) from being in the tab list. At present the tab order is strictly in row column order regardless of the order in which the controls are instantiated. This applies to a number of DIALOGs in my application.

Thanks
Doug
(xProgrammer)
User avatar
xProgrammer
 
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Postby xProgrammer » Thu Mar 13, 2008 8:26 pm

Antonio

I have done some research on this one too. Here is some documentation I found that I think might prove very helpful:
[quote]
I started going over a patch from Jonathan and Tim this morning to fix some problems with focusing, and I realized, that the patch, while it was fixing a few bugs, was basically fixing them in the wrong way.

In order to figure out what was wrong, I had to study the focus code and write up some documentation for myself. So here, is some documenation on how focusing works - mostly from the perspective of somebody writing a new container widget with a custom ::focus method, along with a description of some problems in focusing currently, and my proposed
solutions.


Focus Navigation in GTK+
========================

The focus widget, in GTK+ is the widget to which keystrokes are directed. Each toplevel window may have a focus widget within it, and when that toplevel window is focused by the window manager, all keystrokes are sent to the focus widget for that toplevel window.

Like most other toolkits, GTK+ allows moving the focus widget with the keyboard. Tab and Shift-Tab are used to move the focus forward and backwards through all focusable widgets. However, GTK+ also allows focus navigation using the arrow keys. The arrow keys move the focus around the window directionally, instead of forwards and backwards
through the focus chain.

The other way that GTK+ differs from other toolkits is that the focus chain (and the arrow key behavior) is not a static object. It is, instead determined dynamically by making callbacks on containers that then decide the ordering of their children in the focus chain.


There are essentially three places where state is stored in GTK+ that relates to focusing:

- the HAS_FOCUS flag on each widget stores whether the
widget is the current focus - that is, not only that
it is the focus widget for its toplevel window, but
also that the toplevel widget has the focus from
the window manager.

gtk_widget_focus_in() and gtk_widget_focus_out() give
notification of the widget having HAS_FOCUS set and
unset.

- Each toplevel window stores a pointer the current focus widget
for that window, if any, in window->focus_window.

- If the focus widget is a descendent of a container,
than container->focus_child hold the immediate
child in which the focus widget is to be found.

Note that, at least in the static case, the focus_child pointers contain no information beyond that provided by window->focus_window; they are just an optimization.



The actual keyboard navigation is done by gtk_container_focus(), a virtual function. When the toplevel window receives a key press that it interprets as moving the focus, it converts it to one value of an enumeration:

Code: Select all  Expand view
GTK_DIR_FORWARDS
GTK_DIR_BACKWARDS
GTK_UP
GTK_DOWN
GTK_LEFT
GTK_RIGHT


And then calls gtk_container_focus(window, direction). These calls to gtk_container_focus() propagate down recursively and move the focus widget.


The basic plan of an implementation of gtk_container_focus() is:

1) it identifies the current focused location within the
container. This might be:

- a child container that has the focused widget for
the toplevel widget in it. (container->focus_child == child)
- a non-container child that has the focus.
- the container itself if it is the focused widget for its
toplevel. Some widgets, for instance have multiple conceptual
locations within the container itself - for instance, the
GtkCList has a focus location for each row.

2) it identifies a chain of focusable locations within the widget;
this contents and ordering of the chain may depend on the direction
and the current focused location. The only constraint is that it
produces a reasonable result for the user, and if there is a
current focused location, it must be in the chain.

3) it iterates through the chain starting at the location
after current focused widget, or the first widget in the chain, if
there is no focused widget. For each location it:

- If location is the widget itself, it calls
gtk_widget_grab_focus() on the widget if it isn't
already the focus widget and returns TRUE.

[ Note that this doesn't work currently - currently
you always have to call grab_focus() - see below. ]

- If the location is a child container, it calls
gtk_container_focus (child, dir) on the child, and
if that returns TRUE, returns TRUE.

- If the location is a non-container child that has CAN_FOCUS
set, it calls gtk_widget_grab_focus() on the child
and returns TRUE.

4) If the iteration in 3) did not have a result, return FALSE

Note that the "chain" above can be purely conceptual - there is no need to actually create such a list, though a robust implementation will deal with the fact that calling gtk_container_focus() on the child could result in the widget heirarchy being changed. [ Should we simply disallow this as pathalogical? ]


To put it a little less abstractly, ::focus moves the focus between focusable locations in the widget, and if that motion takes the focus out of the container, it returns FALSE.


The default gtk_container_focus() implementation uses as its chain:

a) if the container is CAN_FOCUS, only the container itself

b) if the container is not CAN_FOCUS, the children of
the container, sorted geometrically:

DIR_RIGHT/DIR_FORWARD: in rows, left->right top->bottom
DIR_FORWARD/DIR_BACKWARDS: in rows, right->left bottom->top
DIR_DOWN: in columns, top->bottom, left->right
DIR_UP: in columns, bottom->top, right->left


Current Problems and Proposed solutions
=======================================

1) One problem that exists throughout a bunch of the GTK+ focusing code is that it is using GTK_WIDGET_HAS_FOCUS() to determine if a widget is the focused widget within its toplevel. This is wrong, and GTK_WIDGET_HAS_FOCUS() should only be used to determine if the focus indicator should be drawn for a widget.

I'd like to add a gtk_widget_is_focus() call that checks to see if the widget is the focus widget for its toplevel window.

And then we need to go through and systematically evaluate all calls to GTK_WIDGET_HAS_FOCUS.


2) The biggest problem right now is that the responsibility of maintaining the focus chain is not kept completely within the core parts of GTK+, but also is partially the responsibility of the implementation of GtkContainer::focus.

In particular, there are two constraints on the ::focus implementation.

- If container->focus_child is currently set for the container,
container->focus_child must be set to NULL before calling
gtk_widget_grab_focus() or gtk_container_focus() again.

- The implementation must do one of:

- Call gtk_widget_grab_focus() on a widget
- Receive a TRUE return when it calls gtk_container_focus() on
a child.
- Return FALSE

So, basically, the focus chain is partially destroyed during ::focus propagation and then recreated when gtk_widget_grab_focus() is called.

I believe simply forbidding containers to call tk_container_set_focus_child() themselves, and doing all updating of the focus chain if in gtk_grab_widget_focus() works fine.

There is no reason to expose partially invalid focus chains.


3) Another problem is that currently the HAS_FOCUS flag is maintained not by GTK+, but by the widget-specific implementations of gtk_widget_focus_in() and gtk_widget_focus_out(). For instance, gtk_button_focus_in() looks like:

Code: Select all  Expand view
static gint
gtk_button_focus_in (GtkWidget     *widget,
           GdkEventFocus *event)
{
  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_BUTTON (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);
  gtk_widget_draw_focus (widget);

  return FALSE;
}

This is just silly. GTK+ should take care of updating the flag itself. We can change GTK+ to do that without breaking backwards compatibility without any problems.


4) There are two focus states for GtkWindow - focus on a child, and no focus. This ends up with the fact that for every toplevel window, the focus chain has a NULL state with the focus nowhere. This is a bug - it should
be impossible to Tab the focus to this location.


5) Hitting the down arrow key within a hbox moves right, and so forth. This is quite confusing to users. Moving down from an hbox should move off the end of the hbox and to the next widget downwards in the parent container. Fixing this basically means revising the default container focus implementation to remove the wrapping around when you get to one side of the container.


6) Finally, overriding the focus chain from an application is crazily hard. Even if focus chain maintainence is fixed, it still is a lot of nonobvious code to override ::focus.

So, I want to add the ability to store a custom focus chain on a container.

Code: Select all  Expand view
/* Set the focus chain to the given list of widgets. A copy
  * of the list is made. The widgets must be descendents
  * but not necessarily immediate children of the container
  *
  * If there are focusable descendents of the container
  * which are not in the focus chain or descendents of
  * widgets in the focus chain, then focus behavior
  * will be unpredictable.
  */
gtk_container_focus_chain_set (GtkContainer   *container,
                                GList          *widgets);

/* Restore default focus chain handling */
gtk_container_focus_chain_unset (GtkContainer *container);

(Note that unset and empty are different.)

If focus chain is set, then ::focus for DIR_BACKWARDS/FORWARDS moves among the widgets in the focus chain, not the children of the container, with the ordering being given by the order in the list.

Handling of DIR_UP/DOWN/LEFT/RIGHT is done geometrically, as currently, but again uses the widgets in the focus chain, not the child widgets.


Jonathan and Tim's patch
========================

[ Here mostly to get some feedback from Tim. ]

The patch I was evaluating (attached to this message for the reference enthusiastic readers) had essentially three components:

- It fixed a problem where the implementation of the
defualt behavior for gtk_container_focus() was split
between the default handler and the focus() implementation
for the parent, which made it impossible to implement
non-default container focus behaviors properly.

- It fixed a bunch of problems with GtkNotebook and GtkCList.

- It fiddled around some with rebuilding the focus chain.

Code: Select all  Expand view
@@ -2634,11 +2634,20 @@
   {
     widget = GTK_WINDOW (toplevel)->focus_widget;
    
-     if (widget == focus_widget)
+     if (widget == focus_widget &&
+         GTK_CONTAINER (toplevel)->focus_child &&
+         GTK_CONTAINER (focus_widget->parent)->focus_child == focus_widget)

{
- /* We call gtk_window_set_focus() here so that the
- * toplevel window can request the focus if necessary.
- * This is needed when the toplevel is a GtkPlug
+ /* if focus should be grabbed for a widget that already
+ * has the focus, with the GtkContainer.focus_child chain
+ * being intact, we call gtk_window_set_focus() here so that
+ * the toplevel window can request the focus if necessary,
+ * this is needed when the toplevel is a GtkPlug.
+ * if focus should be grabbed for a widget that already
+ * has the focus, and the GtkContainer.focus_child chain
+ * being broken, we're probably in GtkContainer::focus
+ * propagation, and a widget wants to keep its focus, but
* has to rebuild the chain.

The idea of the original code is that if we have a GtkPlug
(a toplevel inside a toplevel, essentially), then clicking
on plug->focus_widget might need to trigger some action on
the GtkPlug that requests the focus from the toplevel container.

I think the idea of the change is to workaround the fact that
when moving from one logical focused location to another
within the container, you need to call gtk_widget_grab_focus(container)
to rebuild the focus chain - apparently the call to
gtk_window_set_focus() was confusing things somehow, though
I don't see how.

The first two changes are still good, the third change I don't think is necessary, especially if we fix things so that
gtk_widget_grab_focus() is the only thing that ever changes ]the focus chain.
User avatar
xProgrammer
 
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Postby xProgrammer » Thu Mar 13, 2008 8:49 pm

A bit more:

#
procedure Set_Focus_Chain (Container : access Gtk_Container_Record; Focusable_Widgets : Gtk.Widget.Widget_List.Glist);
Set the chain of widgets that can take the focus for a given Container. The list should be freed by the user. This list indicates in which order the widgets will get the focus when the user presses tab or the arrow keys to move from one widget to the next.
#
procedure Get_Focus_Chain (Container : access Gtk_Container_Record; Focusable_Widgets : out Gtk.Widget.Widget_List.Glist; Success : out Boolean);
Retrieves the focus chain of the container, if one has been set explicitly. If no focus chain has been explicitly set, GTK+ computes the focus chain based on the positions of the children. In that case, GTK+ stores null in Focusable_Widgets and returns FALSE. The returned list must be freed by the user.

Looks better in the original which is:

https://libre.adacore.com/GtkAda/docs/2 ... _Chain_10_
User avatar
xProgrammer
 
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Postby Antonio Linares » Thu Mar 13, 2008 9:21 pm

Doug,

We have been busy working on the FWH 8.03 build but now that we have published it, we plan to spend some time working on FiveLinux, to review your recently proposed topics.
regards, saludos

Antonio Linares
www.fivetechsoft.com
User avatar
Antonio Linares
Site Admin
 
Posts: 42099
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain

Postby xProgrammer » Fri Mar 14, 2008 2:41 am

Thanks Antonio. That would be greatly appreciated. I am achieving more and more but there are a few little issues remaining. I hope that my "research" proves to be of some help.

Regards
Doug
(xProgrammer)
User avatar
xProgrammer
 
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Postby xProgrammer » Sun Mar 16, 2008 2:09 am

Hi Antonio

After some more reading on the subject of tab order, I think we have to call the Following function:

Code: Select all  Expand view
gtk_container_set_focus_chain ()

void                gtk_container_set_focus_chain       (GtkContainer *container,
                                                         GList *focusable_widgets);


Sets a focus chain, overriding the one computed automatically by GTK+.

In principle each widget in the chain should be a descendant of the container, but this is not enforced by this method, since it's allowed to set the focus chain before you pack the widgets, or have a widget in the chain that isn't always packed. The necessary checks are done when the focus chain is actually traversed.

container :
a GtkContainer

focusable_widgets :
the new focus chain

So we need the GtkContainer rather than the DIALOG, and we need to create a GList of widgets.

GList info at http://library.gnome.org/devel/glib/sta ... html#GList

GtkContainer info at
http://library.gnome.org/devel/gtk/stab ... ocus-chain
User avatar
xProgrammer
 
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia


Return to FiveLinux / FiveDroid (Android)

Who is online

Users browsing this forum: No registered users and 6 guests