I have been working on this for a while and this is the best I could do, but know it could be better.
I have a grid of buttons displayed that are on three separate JPanels. Each of those have FocusCycleRoot set to true. I want tab/ctrl-tab to cycle through each root set, and I want enter/escape to move between the panels. As it stands I have the enter working, and the escape will take you to the last item in the upper root cycle.
Within the EnterKeyListener, I was not able to determine a reference to the JPanel above the current one. So I just went to the first item in the current root cycle, turned off all root cycles, went back one more, and then turned all root cycles back on.
I would prefer that when I press escape the focus moved to the first item in the previous root cycle, but could only get it to the last one.
The KeyboardFocusManager.getCurrentKeyboardFocusManager() handles focus. It uses a focus traversal policy to decide what to do with your key events.
Which keys to use are defined by setting the focus traversal keys. Here�s an idiom from the Java Tutorial:
I have this working using two traversal policies, one for traversing the roots ( panels) and a standard anonymous one for traversing the components (buttons) but it should be possible to write it using a single more complex policy if you wanted to. I just did it this way because the default policy works fine for the buttons.
One thing strangely missing from the traversal policy is any methods that handle up cycle (or down cycle) events. The way I solved this was to add an implementation of KeyEventPostProcessor to the traversal policy and handle the up cycle keys in its postProcess(KeyEvent e) method. The up and down cycles are already handled by the KeyFocusManager but you need somewhere to traverse forward and backward on your up cycle. Here's a working policy that you can slot into your code subject to adding the code above and assigning the policy to your root panels. Don't forget to set a LayoutTraversalPolicy for the buttons:
It's not any simpler than your solution but using policies and the KeyboardFocusManager is probably closer to the intended use of the APIs. This version also solves your problem with getting to the first button.
If you wanted to continue doing things the way you are you could experiment with LayoutFocusTraversalPolicy.setImplicitDownCycle(false); and then explicitly handle your down cycles rather than clearing and resetting your roots.