Compute focus traversal distance for two Swing components within a window

Viewed 240

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.

1 Answers
Related