// -*- mode:java; encoding:utf-8 -*-
// vim:set fileencoding=utf-8:
// @homepage@

package example;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import javax.swing.*;
import javax.swing.text.JTextComponent;

public final class MainPanel extends JPanel {
  private final JPanel panel = new JPanel(new BorderLayout(2, 2));
  private final JTextArea textarea = new JTextArea();
  private final JButton nb = new JButton("NORTH");
  private final JButton sb = new JButton("SOUTH");
  private final JButton wb = new JButton("WEST");
  private final JButton eb = new JButton("EAST");
  private final JScrollPane scroll = new JScrollPane(textarea);
  private final Box box = Box.createHorizontalBox();
  private final JCheckBox check = new JCheckBox("setEditable", true);

  private MainPanel() {
    super(new BorderLayout(5, 5));
    panel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
    panel.add(scroll);
    panel.add(nb, BorderLayout.NORTH);
    panel.add(sb, BorderLayout.SOUTH);
    panel.add(wb, BorderLayout.WEST);
    panel.add(eb, BorderLayout.EAST);

    setFocusTraversalPolicyProvider(true);
    // frame.setFocusTraversalPolicy(policy);
    // setFocusTraversalPolicy(policy);
    // setFocusCycleRoot(true);

    FocusTraversalPolicy policy0 = getFocusTraversalPolicy();
    FocusTraversalPolicy policy1 = new CustomFocusTraversalPolicy(Arrays.asList(eb, wb, sb, nb));
    FocusTraversalPolicy policy2 = new LayoutFocusTraversalPolicy() {
      @Override protected boolean accept(Component c) {
        return c instanceof JTextComponent ? ((JTextComponent) c).isEditable() : super.accept(c);
      }
    };

    ButtonGroup bg = new ButtonGroup();
    box.setBorder(BorderFactory.createTitledBorder("FocusTraversalPolicy"));
    Stream.of(new FocusTraversalPolicyChangeAction("Default", policy0),
              new FocusTraversalPolicyChangeAction("Custom", policy1),
              new FocusTraversalPolicyChangeAction("Layout", policy2))
        .map(JRadioButton::new)
        .forEach(rb -> {
          bg.add(rb);
          box.add(rb);
          box.add(Box.createHorizontalStrut(3));
        });
    ((JRadioButton) bg.getElements().nextElement()).setSelected(true);
    box.add(Box.createHorizontalGlue());

    check.setHorizontalAlignment(SwingConstants.RIGHT);
    check.addActionListener(e -> {
      textarea.setEditable(check.isSelected());
      debugPrint();
    });
    add(panel);
    add(box, BorderLayout.NORTH);
    add(check, BorderLayout.SOUTH);
    setPreferredSize(new Dimension(320, 240));
    EventQueue.invokeLater(this::debugPrint);
  }

  protected final class FocusTraversalPolicyChangeAction extends AbstractAction {
    private final FocusTraversalPolicy policy;

    protected FocusTraversalPolicyChangeAction(String name, FocusTraversalPolicy p) {
      super(name);
      this.policy = p;
    }

    @Override public void actionPerformed(ActionEvent e) {
      setFocusTraversalPolicy(policy);
      debugPrint();
    }
  }

  protected void debugPrint() {
    Container w = getTopLevelAncestor();
    textarea.setText(String.join("\n",
        debugString("frame", w),
        debugString("this", this),
        debugString("panel", panel),
        debugString("box", box),
        debugString("scroll", scroll),
        debugString("textarea", textarea),
        debugString("eb", eb)));
  }

  private static String debugString(String label, Container c) {
    return String.join("\n",
        "---- " + label + " ----",
        "  isFocusCycleRoot: " + c.isFocusCycleRoot(),
        "  isFocusTraversalPolicySet: " + c.isFocusTraversalPolicySet(),
        "  isFocusTraversalPolicyProvider: " + c.isFocusTraversalPolicyProvider());
  }

  public static void main(String[] args) {
    EventQueue.invokeLater(MainPanel::createAndShowGui);
  }

  private static void createAndShowGui() {
    try {
      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
      ex.printStackTrace();
      Toolkit.getDefaultToolkit().beep();
    }
    JFrame frame = new JFrame("@title@");
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    frame.getContentPane().add(new MainPanel());
    frame.pack();
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }
}

class CustomFocusTraversalPolicy extends FocusTraversalPolicy {
  private final List<? extends Component> order;

  protected CustomFocusTraversalPolicy(List<? extends Component> order) {
    super();
    this.order = order;
  }

  @Override public Component getFirstComponent(Container focusCycleRoot) {
    return order.get(0);
  }

  @Override public Component getLastComponent(Container focusCycleRoot) {
    return order.get(order.size() - 1);
  }

  @Override public Component getComponentAfter(Container focusCycleRoot, Component cmp) {
    int i = order.indexOf(cmp);
    return order.get((i + 1) % order.size());
  }

  @Override public Component getComponentBefore(Container focusCycleRoot, Component cmp) {
    int i = order.indexOf(cmp);
    return order.get((i - 1 + order.size()) % order.size());
  }

  @Override public Component getDefaultComponent(Container focusCycleRoot) {
    return order.get(0);
  }
}

@@ -434,17 +434,17 @@
 rea text
-a
+A
 rea = ne
@@ -713,25 +713,25 @@
 ollPane(text
-a
+A
 rea);%0A  priv
@@ -2214,24 +2214,8 @@
 %0A   
- ((JRadioButton)
  bg.
@@ -2241,17 +2241,16 @@
 lement()
-)
 .setSele
@@ -2400,25 +2400,25 @@
 %7B%0A      text
-a
+A
 rea.setEdita
@@ -3129,17 +3129,17 @@
     text
-a
+A
 rea.setT
@@ -3181,17 +3181,18 @@
 String(%22
-f
+JF
 rame%22, w
@@ -3250,17 +3250,18 @@
 String(%22
-p
+JP
 anel%22, p
@@ -3288,17 +3288,17 @@
 String(%22
-b
+B
 ox%22, box
@@ -3321,22 +3321,27 @@
 String(%22
-s
+JS
 croll
+Pane
 %22, scrol
@@ -3365,21 +3365,22 @@
 String(%22
-texta
+JTextA
 rea%22, te
@@ -3381,17 +3381,17 @@
 a%22, text
-a
+A
 rea),%0A  

remove redundant cast