1. KeyBindingPanel

package is.codion.framework.demos.manual.keybinding;

import is.codion.framework.demos.manual.keybinding.KeyBindingModel.KeyBinding;
import is.codion.swing.common.ui.Windows;
import is.codion.swing.common.ui.component.table.FilteredTable;
import is.codion.swing.common.ui.laf.LookAndFeelComboBox;
import is.codion.swing.common.ui.laf.LookAndFeelProvider;

import com.formdev.flatlaf.intellijthemes.FlatAllIJThemes;

import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import java.awt.BorderLayout;
import java.util.Arrays;

import static is.codion.swing.common.ui.border.Borders.emptyBorder;
import static is.codion.swing.common.ui.component.Components.*;
import static is.codion.swing.common.ui.component.text.TextComponents.preferredTextFieldHeight;
import static is.codion.swing.common.ui.laf.LookAndFeelComboBox.lookAndFeelComboBox;
import static is.codion.swing.common.ui.layout.Layouts.borderLayout;

/**
 * A utility for displaying component action/input maps for installed look and feels.<br>
 * Based on <a href="https://tips4java.wordpress.com/2008/10/10/key-bindings/">KeyBindings.java by Rob Comick</a>
 * @author Rob Camick
 * @author bjorndarri
 */
public final class KeyBindingPanel extends JPanel {

  private final LookAndFeelComboBox lookAndFeelComboBox = lookAndFeelComboBox(true);
  private final KeyBindingModel keyBindingModel;
  private final FilteredTable<KeyBinding, Integer> table;
  private final JComboBox<String> componentComboBox;

  public KeyBindingPanel() {
    super(borderLayout());
    this.keyBindingModel = new KeyBindingModel(lookAndFeelComboBox.getModel());
    this.table = FilteredTable.builder(keyBindingModel.tableModel()).build();
    this.componentComboBox = comboBox(keyBindingModel.componentComboBoxModel())
            .preferredHeight(preferredTextFieldHeight())
            .preferredWidth(200)
            .build();
    setBorder(emptyBorder());
    add(flexibleGridLayoutPanel(1, 4)
            .add(label("Look & Feel")
                    .horizontalAlignment(SwingConstants.RIGHT)
                    .preferredWidth(100)
                    .build())
            .add(lookAndFeelComboBox)
            .add(label("Component")
                    .horizontalAlignment(SwingConstants.RIGHT)
                    .preferredWidth(100)
                    .build())
            .add(componentComboBox)
            .build(), BorderLayout.NORTH);
    add(new JScrollPane(table), BorderLayout.CENTER);
    add(table.searchField(), BorderLayout.SOUTH);
  }

  public static void main(String[] args) {
    System.setProperty("sun.awt.disablegrab", "true");
    Arrays.stream(FlatAllIJThemes.INFOS)
            .forEach(LookAndFeelProvider::addLookAndFeelProvider);
    SwingUtilities.invokeLater(() -> Windows.frame(new KeyBindingPanel())
            .title("Key Bindings")
            .defaultCloseOperation(JFrame.EXIT_ON_CLOSE)
            .centerFrame(true)
            .show());
  }
}

2. KeyBindingTableModel

package is.codion.framework.demos.manual.keybinding;

import is.codion.common.item.Item;
import is.codion.swing.common.model.component.combobox.FilteredComboBoxModel;
import is.codion.swing.common.model.component.table.FilteredTableColumn;
import is.codion.swing.common.model.component.table.FilteredTableModel;
import is.codion.swing.common.model.component.table.FilteredTableModel.ColumnValueProvider;
import is.codion.swing.common.ui.laf.LookAndFeelProvider;

import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;

import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;

final class KeyBindingModel {

  private static final Collection<String> EXCLUDED_COMPONENTS = asList("PopupMenuSeparator", "ToolBarSeparator", "DesktopIcon");

  private static final String PACKAGE = "javax.swing.";
  private static final String PRESSED = "pressed ";
  private static final String RELEASED = "released ";

  private static final int ACTION_COLUMN_INDEX = 0;
  private static final int WHEN_FOCUSED_COLUMN_INDEX = 1;
  private static final int WHEN_IN_FOCUSED_WINDOW_COLUMN_INDEX = 2;
  private static final int WHEN_ANCESTOR_COLUMN_INDEX = 3;

  private final FilteredTableModel<KeyBinding, Integer> tableModel;
  private final FilteredComboBoxModel<String> componentComboBoxModel;

  KeyBindingModel(FilteredComboBoxModel<Item<LookAndFeelProvider>> lookAndFeelComboBoxModel) {
    this.componentComboBoxModel = createComponentComboBoxModel(lookAndFeelComboBoxModel);
    this.componentComboBoxModel.refresh();
    this.tableModel = FilteredTableModel.builder(new KeyBindingColumnFactory(), new KeyBindingValueProvider())
            .itemSupplier(new KeyBindingItemSupplier())
            .build();
    bindEvents(lookAndFeelComboBoxModel);
  }

  FilteredComboBoxModel<String> componentComboBoxModel() {
    return componentComboBoxModel;
  }

  FilteredTableModel<KeyBinding, Integer> tableModel() {
    return tableModel;
  }

  private void bindEvents(FilteredComboBoxModel<Item<LookAndFeelProvider>> lookAndFeelComboBoxModel) {
    componentComboBoxModel.refresher().addRefreshListener(tableModel::refresh);
    componentComboBoxModel.addSelectionListener(component -> tableModel.refresh());
    lookAndFeelComboBoxModel.addSelectionListener(lookAndFeelProvider -> componentComboBoxModel.refresh());
  }

  private static String className(String componentName) {
    if (componentName.equals("JTableHeader")) {
      return PACKAGE + "table." + componentName;
    }

    return PACKAGE + componentName;
  }

  private static final class KeyBindingColumnFactory implements FilteredTableModel.ColumnFactory<Integer> {
    @Override
    public List<FilteredTableColumn<Integer>> createColumns() {
      FilteredTableColumn<Integer> action = FilteredTableColumn.builder(ACTION_COLUMN_INDEX)
              .headerValue("Action")
              .columnClass(String.class)
              .build();
      FilteredTableColumn<Integer> whenFocused = FilteredTableColumn.builder(WHEN_FOCUSED_COLUMN_INDEX)
              .headerValue("When Focused")
              .columnClass(String.class)
              .build();
      FilteredTableColumn<Integer> whenInFocusedWindow = FilteredTableColumn.builder(WHEN_IN_FOCUSED_WINDOW_COLUMN_INDEX)
              .headerValue("When in Focused Window")
              .columnClass(String.class)
              .build();
      FilteredTableColumn<Integer> whenAncestor = FilteredTableColumn.builder(WHEN_ANCESTOR_COLUMN_INDEX)
              .headerValue("When Ancestor")
              .columnClass(String.class)
              .build();

      return asList(action, whenFocused, whenInFocusedWindow, whenAncestor);
    }
  }

  static final class KeyBinding {

    private final String action;
    private final String whenFocused;
    private final String whenInFocusedWindow;
    private final String whenAncestor;

    private KeyBinding(String action, String whenFocused, String whenInFocusedWindow, String whenAncestor) {
      this.action = action;
      this.whenFocused = whenFocused;
      this.whenInFocusedWindow = whenInFocusedWindow;
      this.whenAncestor = whenAncestor;
    }

    private static KeyBinding create(Object actionKey, JComponent component) {
      return new KeyBinding(actionKey.toString(),
              keyStrokes(actionKey, component.getInputMap(JComponent.WHEN_FOCUSED)),
              keyStrokes(actionKey, component.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)),
              keyStrokes(actionKey, component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)));
    }

    private static String keyStrokes(Object actionKey, InputMap inputMap) {
      KeyStroke[] allKeys = inputMap.allKeys();
      if (allKeys == null) {
        return "";
      }

      return Arrays.stream(allKeys)
              .filter(keyStroke -> inputMap.get(keyStroke).equals(actionKey))
              .map(Objects::toString)
              .map(KeyBinding::movePressedReleased)
              .collect(joining(", "));
    }

    private static String movePressedReleased(String keyStroke) {
      if (keyStroke.contains(PRESSED)) {
        return keyStroke.replace(PRESSED, "") + " pressed";
      }
      if (keyStroke.contains(RELEASED)) {
        return keyStroke.replace(RELEASED, "") + " released";
      }

      return keyStroke;
    }
  }

  private final class KeyBindingItemSupplier implements Supplier<Collection<KeyBinding>> {

    @Override
    public Collection<KeyBinding> get() {
      String componentName = componentComboBoxModel.getSelectedItem();
      if (componentName == null) {
        return emptyList();
      }
      String componentClassName = className(componentName);
      try {
        JComponent component = (JComponent) Class.forName(componentClassName).getDeclaredConstructor().newInstance();
        ActionMap actionMap = component.getActionMap();
        Object[] allKeys = actionMap.allKeys();
        if (allKeys == null) {
          return emptyList();
        }

        return Arrays.stream(allKeys)
                .sorted(comparing(Objects::toString))
                .map(actionKey -> KeyBinding.create(actionKey, component))
                .collect(toList());
      }
      catch (Exception e) {
        throw new RuntimeException(e);
      }
    }
  }

  private static final class KeyBindingValueProvider implements ColumnValueProvider<KeyBinding, Integer> {

    @Override
    public Object value(KeyBinding keyBinding, Integer columnIdentifier) {
      switch (columnIdentifier) {
        case ACTION_COLUMN_INDEX:
          return keyBinding.action;
        case WHEN_FOCUSED_COLUMN_INDEX:
          return keyBinding.whenFocused;
        case WHEN_IN_FOCUSED_WINDOW_COLUMN_INDEX:
          return keyBinding.whenInFocusedWindow;
        case WHEN_ANCESTOR_COLUMN_INDEX:
          return keyBinding.whenAncestor;
        default:
          throw new IllegalArgumentException("Unknown identifier: " + columnIdentifier);
      }
    }
  }

  private static FilteredComboBoxModel<String> createComponentComboBoxModel(
          FilteredComboBoxModel<Item<LookAndFeelProvider>> lookAndFeelComboBoxModel) {
    FilteredComboBoxModel<String> comboBoxModel = new FilteredComboBoxModel<>();
    comboBoxModel.refresher().itemSupplier().set(new ComponentItemSupplier(lookAndFeelComboBoxModel));

    return comboBoxModel;
  }

  private static final class ComponentItemSupplier implements Supplier<Collection<String>> {

    private final FilteredComboBoxModel<Item<LookAndFeelProvider>> lookAndFeelComboBoxModel;

    private ComponentItemSupplier(FilteredComboBoxModel<Item<LookAndFeelProvider>> lookAndFeelComboBoxModel) {
      this.lookAndFeelComboBoxModel = lookAndFeelComboBoxModel;
    }

    @Override
    public Collection<String> get() {
      Item<LookAndFeelProvider> selectedItem = lookAndFeelComboBoxModel.getSelectedItem();
      if (selectedItem == null) {
        return emptyList();
      }

      LookAndFeelProvider lookAndFeelProvider = selectedItem.get();
      try {
        return lookAndFeelProvider.lookAndFeel().getDefaults().keySet().stream()
                .map(Object::toString)
                .map(ComponentItemSupplier::componentName)
                .filter(Optional::isPresent)
                .map(Optional::get)
                .sorted()
                .collect(toList());
      }
      catch (Exception e) {
        throw new RuntimeException(e);
      }
    }

    private static Optional<String> componentName(String key) {
      if (key.endsWith("UI") && key.indexOf(".") == -1) {
        String componentName = key.substring(0, key.length() - 2);
        if (!EXCLUDED_COMPONENTS.contains(componentName)) {
          return Optional.of("J" + componentName);
        }
      }

      return Optional.empty();
    }
  }
}