-2

I apologise ahead of time of lack of reproducable example, the app i have is very big, and the problem seems to be related to weird combination of controls and focus switches, which i haven't been able to reproduce with a shorter program.

Basically, i have an text editor application (in java8 on windows 7) with a JFrame and a JTabbedPane with each tab containing a JSplitPane of text area and another tab displaying some information. I have implemented a tab switcher system like in Eclipse, Ctrl-Tab shows a list of editors in a modal JDialog. This is implemented as a KeyListener which keeps track of various keys pressed. The code looks something like this:

private TabsDialog td = new TabsDialog(mainFrame, true);

void onKeyPressCtrlTab()
{
  td.setVisible(true); //display tab dialog
  //After closing dialog, select correct tab
  TabContent tc = td.getSelectedTab();
  switchToTab(tc);
}

void onKeyRelease()
{
    dispatchDialog();
}

void dispatchDialog()
{
  if (td.isVisible())
  {
  td.setVisible(false);
  }
}

The problem is, if i invoke onKeyPressCtrlTab and onKeyRelease in quick succession by pressing and releasing Ctrl-Tab, the focus system of the main application stops working, i can click on the main window and select text with the mouse, but the caret in text components isn't being shown at all. I can't type any text into any of the text components either. Also, all requestFocusInWindow calls are failing.

The problem is reproducable every time, but sometimes it takes 5 of Ctrl-Tabs, sometimes longer.

I've traced the problem to Component#requestFocusHelper, the following call always fail after the problem appears:

final boolean requestFocusHelper (boolean temporary,
                                     boolean focusedWindowChangeAllowed,
                                     CausedFocusEvent.Cause cause)
...
        boolean success = peer.requestFocus
            (this, temporary, focusedWindowChangeAllowed, time, cause); //fails

It seems that calling a new modal dialog (for example JOptionPane#showConfirmDialog) restores the focus system, but this is not a great solution.

I have spend two days on this issue, but haven't been able to find any good solution. It hasn't helped to dispose and re-create the TabsDialog on every call. I suspect there's something wrong with how the dialogs are cleaned up / enabled in Windows.

I think i'm doing all work on AWT thread so i doubt that's the issue. There are "some" things going on when switching tabs, i have a splitpane which is set / restored since each tab remembers the position, but still, nothing that should affect the focus system in my opinion. I have a uncatched exception in thread filter, so no exceptions are supressed as far as i can see.

Appreciate any thoughts on the problem, and a potential solution (even workaround that restores the focus would be great)

Here's a demonstration of how it's supposed to work:

https://gist.github.com/siggemannen/4affdff4b1892a15e481c626a190efab

siggemannen
  • 3,884
  • 2
  • 6
  • 24
  • 1
    Creating a `JFrame`, a `JTabbedPane`, multiple `JSplitPanes`, and multiple `JOptionPanes` is a bit much to ask of volunteers since you're not willing to do so. Even if I created this GUI, would you be able to modify your application to incorporate what I coded? – Gilbert Le Blanc Jun 10 '23 at 16:22
  • I guess i'm looking for general ideas on what can go wrong with the focus and modal windows since this is not really my area of expertise. I'm working on a sample application, but haven't been able to reproduce the actual problem using this application yet. Will post it here if i'm successful @GilbertLeBlanc – siggemannen Jun 10 '23 at 16:28
  • 1
    Make sure all Swing component creation and execution happens on the Event Dispatch Thread. Other than that, run the code through a debugger. – Gilbert Le Blanc Jun 10 '23 at 16:33
  • *The problem is, if i invoke onKeyPressCtrlTab and onKeyRelease in quick succession by pressing and releasing Ctrl-Tab, the focus system of the main application stops working* - I'm not really understanding the need for the onKeyRelease logic. If you are using Ctrl_Tab to show a modal dialog, then clicking on the "X" should close the dialog and focus should return the the previously focused component. No need to handle a release key to close the dialog. Without an [mre] I'm not understanding the logic flow. – camickr Jun 10 '23 at 17:39
  • It's so one can cycle the tabs in the tab list. If you press ctrl-tab and without releasing press Tab again, it moves to next tab in history without closing the TabDialog. I will create a simple scratch program to demonstrate main behaviour – siggemannen Jun 10 '23 at 18:07
  • @camickr added a gist explaining the functionality. To use it, press Ctrl-Tab etc – siggemannen Jun 10 '23 at 18:21
  • Doesn't help. It is not an MRE and it uses 3rd party classes. My comments would be that Swing was designed to be used with Key Bindings (not a KeyListener). So I would suggest the binding for Ctrl_Tab would be added to the tabbed pane. It would simply display the dialog. Then you have bindings on the dialog for 1) TAB which would select the next editor in the list and 3) Ctrl released which would close the dialog and return the selected editor. – camickr Jun 10 '23 at 18:27
  • @camickr, but will it help, the problem isn't that the bindings aren't working, but rather than there seems to be some kind of unrelease of native focus in regards to modal dialogs. What's strange is that the simplified program works perfectly, while the more complicated hoses almost immediately. But i guess my question is not really good for stackoverflow, since i cannot provide a MRE – siggemannen Jun 10 '23 at 18:38
  • It doesn't help because you use 3rd party code. We have no idea if that code could be the problem. Also, Ctrl+Tab and Tab are focus change KeyStrokes on all Swing components. If it works on simple code but more complex code maybe this implies you are not handling these KeyStrokes properly for all components. Maybe try implement your functionality with different KeyStrokes. The way to debut is to start with the simple code and add in logic of your app and retest. Then when it stops working you have isolated the problem. – camickr Jun 11 '23 at 04:28

1 Answers1

1

A pure Swing solution would be to use Key Bindings, not a 3rd party tool to handle key events.

As I understand your requirement, here is my simple implementation using Key Bindings:

import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.event.*;
import javax.swing.plaf.*;
import java.util.*;

public class TabbedPaneDialog extends JPanel
{
    JTabbedPane tabbedPane;

    public TabbedPaneDialog()
    {
        setLayout( new BorderLayout() );

        tabbedPane = new JTabbedPane();
        add(tabbedPane);

        newTab( "File 1" );
        newTab( "File 2" );
        newTab( "File 3" );
        newTab( "File 4" );
        newTab( "File 5" );

        // remove Control+Tab as a Tab key

        Set newForwardKeys = new HashSet();
        newForwardKeys.add( KeyStroke.getKeyStroke("TAB") );
        tabbedPane.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, newForwardKeys);

        // Use Control+Tab to display dialog

        KeyStroke controlTab = KeyStroke.getKeyStroke("control TAB");
        InputMap im = tabbedPane.getInputMap(JTabbedPane.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        im.put(controlTab, "showDialog");
        tabbedPane.getActionMap().put("showDialog", new DialogAction("Show Dialog", tabbedPane));

    }

    private void newTab(String text)
    {
        JTextArea textArea = new JTextArea(10, 30);
        textArea.setText(text);
        JScrollPane scrollPane = new JScrollPane( textArea );
        tabbedPane.add( scrollPane, text );

        // remove Control+Tab as a Tab key

        Set newForwardKeys = new HashSet();
        newForwardKeys.add( KeyStroke.getKeyStroke("TAB") );
        textArea.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, newForwardKeys);

        // Use Control+Tab to display dialog

        KeyStroke controlTab = KeyStroke.getKeyStroke("control TAB");
        InputMap im = textArea.getInputMap(JTabbedPane.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        im.put(controlTab, "showDialog");
        textArea.getActionMap().put("showDialog", new DialogAction("Show Dialog", tabbedPane));
    }

    class DialogAction extends AbstractAction
    {
        private JTabbedPane tabbedPane;

        public DialogAction(String name, JTabbedPane tabbedPane)
        {
            putValue( Action.NAME, name );
            this.tabbedPane = tabbedPane;
        }

        public void actionPerformed(ActionEvent e)
        {
            SelectionDialog dialog = new SelectionDialog(tabbedPane);
        }
    }

    class SelectionDialog
    {
        private JTabbedPane tabbedPane;

        public SelectionDialog(JTabbedPane tabbedPane)
        {
            this.tabbedPane = tabbedPane;

            DefaultListModel<String> model = new DefaultListModel<>();

            for (int i = 0; i < tabbedPane.getTabCount(); i++)
            {
                model.addElement( tabbedPane.getTitleAt(i) );
            }

            JList<String> list = new JList<>(model);
            list.setSelectedIndex( tabbedPane.getSelectedIndex() );

            Set newForwardKeys = new HashSet();
            newForwardKeys.add( KeyStroke.getKeyStroke("TAB") );
            list.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, newForwardKeys);

            KeyStroke controlTab = KeyStroke.getKeyStroke("control TAB");
            InputMap im = list.getInputMap(JList.WHEN_FOCUSED);
            im.put(controlTab, "nextTab");
            list.getActionMap().put("nextTab", new NextTabAction("Next Tab", list));

            KeyStroke releasedControlTab = KeyStroke.getKeyStroke("released CONTROL");
            im = list.getInputMap(JList.WHEN_FOCUSED);
            im.put(releasedControlTab, "closeDialog");
            list.getActionMap().put("closeDialog", new CloseDialogAction("Close Dialog", list, tabbedPane));

            Window window = SwingUtilities.windowForComponent(tabbedPane);
            JDialog dialog = new JDialog(window);
            dialog.add(new JScrollPane(list));
            dialog.pack();
            dialog.setLocationRelativeTo( tabbedPane );
            dialog.setVisible(true);
        }

        class NextTabAction extends AbstractAction
        {
            private JList list;

            public NextTabAction(String name, JList list)
            {
                putValue( Action.NAME, name );
                this.list = list;
            }

            public void actionPerformed(ActionEvent e)
            {
                int selected = list.getSelectedIndex();
                selected++;

                if (selected == list.getModel().getSize())
                    selected = 0;

                list.setSelectedIndex( selected );
            }
        }

        class CloseDialogAction extends AbstractAction
        {
            private JList list;
            private JTabbedPane tabbedPane;

            public CloseDialogAction(String name, JList list, JTabbedPane tabbedPane)
            {
                putValue( Action.NAME, name );
                this.list = list;
                this.tabbedPane = tabbedPane;
            }

            public void actionPerformed(ActionEvent e)
            {
                int selected = list.getSelectedIndex();
                tabbedPane.setSelectedIndex( selected );

                Window window = SwingUtilities.windowForComponent(list);
                window.setVisible( false );

                JScrollPane scrollPane = (JScrollPane)tabbedPane.getComponentAt( selected );
                JTextArea textArea = (JTextArea)scrollPane.getViewport().getView();
                textArea.requestFocusInWindow();
            }
        }
    }

    public static void main(String args[])
    {
        JFrame frame = new JFrame("TabbedPane Dialog");
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.add(new TabbedPaneDialog());
        frame.pack();
        frame.setVisible(true);
    }
}
camickr
  • 321,443
  • 19
  • 166
  • 288
  • 1
    This is an awesome solution! It's for sure much better than my weird system! I will use it, but i also found the reason for the issue i was having, it seems to have something to do with how i was wrapping / unwrapping the tabbed control in a JSplitPane after switching tab, after removing the unwrapping, the problem went away – siggemannen Jun 14 '23 at 21:48