Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compute focus traversal distance for two Swing components within a window

Tags:

java

focus

swing

Original Post

I'd like to programmatically determine how many keyboard strokes (doesn't matter if tab and/or arrow keys) are necessary to go from one Swing component (owns focus) to another within the current window. Each stroke should add a distance of 1; if the component can't be reached, the result should be -1.

Since I wasn't able to find an utility method, I thought about the following signature:

public static int getFocusTraversalDistance( Component from, Component to )

Naively, I'd start with getting the Container of from via getFocusCycleRootAncestor(). Afterwards, I'd fetch the FocusTraversalPolicy with getFocusTraversalPolicy() and loop through the components using getComponentAfter(Container, Component) respectively getComponentBefore(Container, Component).

However, I'm not that familiar with the Swing/AWT focus subsystem and I wonder if there's a more elegant way?

Edit #1

The reason why I need this information is my master thesis, which I'm currently writing. The idea is to enhance GUI-based monkey testing with machine learning. Rather than picking random components, the trained model tries to "recommend" a component based on historical user/tester traces. One of the features I'm using for this is the focus traversal distance between the previous target component and a possible target component.

Edit #2

Thanks to the valuable input of camickr, I'm currently using the following algorithm:

public static int getFocusTraversalDistance( Component from, Component to ) {
    if ( from.equals( to ) ) {
        return 0;
    }

    final Container root = from.getFocusCycleRootAncestor();

    if ( root == null ) {
        return -1;
    }

    final FocusTraversalPolicy policy = root.getFocusTraversalPolicy();
    final HashSet<Component> visited = new HashSet<>();
    Component before = from;
    Component after = from;
    int distance = 1;

    while ( true ) {
        if ( before != null ) {
            visited.add( before );
            before = policy.getComponentBefore(
                    before.getFocusCycleRootAncestor(), before );
            if ( to.equals( before ) ) {
                return distance;
            }
        }

        if ( after != null ) {
            visited.add( after );
            after = policy.getComponentAfter(
                    after.getFocusCycleRootAncestor(), after );
            if ( to.equals( after ) ) {
                return distance;
            }
        }

        if ( before == null && after == null
                || visited.contains( before ) && visited.contains( after ) ) {
            return -1;
        }

        distance++;
    }
}

So far it seems to work, but practically non-focusable components may produce strange results. The AWT focus subsystem doc says that "[…] all Components return true from this [Component#isFocusable()] method." Even components such as JLabel return true, although (AFAIK) it practically can't gain focus and Component#hasFocus() is always false.

If someone's interested, I can setup a GitHub project with a full functional test suite.

like image 554
beatngu13 Avatar asked May 20 '17 15:05

beatngu13


1 Answers

this question is related to Swing only, don't worry—any help is appreciated

I already stated it sounds like a reasonable approach. The only think I'm not sure of is will the approach work when you have multiple nested panels. Or at least it may get more complicated if you have to keep get the focus traversal policy for each container.

You might try looking at the KeyboardFocusManager to see if it can help.

The only other approach I can think of is to actually tab through all the components on the form and add each component to an ArrayList. Then your getFocusTraversalDistance(...) would use the ArrayList instead of the FocusTraversalPolicy. Of course this has the problem that the component will actually gain focus and focusGained/focusLost code could be invoked.

Or maybe combine the two approaches. That is use your approach to use the focus traversal policy, but only do this one to build the ArrayList when the frame is created. Then your getFocusTraversalDistance(...) can reference the ArrayList, which should make it a little more efficient.

like image 72
camickr Avatar answered Oct 22 '22 01:10

camickr