1. KeyBindingPanel

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

import is.codion.framework.demos.manual.keybinding.KeyBindingModel.KeyBinding;
import is.codion.framework.demos.manual.keybinding.KeyBindingModel.KeyBindingColumns.Id;
import is.codion.swing.common.ui.Windows;
import is.codion.swing.common.ui.component.table.FilterTable;
import is.codion.swing.common.ui.component.table.FilterTableColumn;
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 java.util.List;

import static is.codion.framework.demos.manual.keybinding.KeyBindingModel.KeyBindingColumns.Id.*;
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;
import static java.util.Arrays.asList;

/**
 * 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 FilterTable<KeyBinding, Id> table;
  private final JComboBox<String> componentComboBox;

  public KeyBindingPanel() {
    super(borderLayout());
    this.keyBindingModel = new KeyBindingModel(lookAndFeelComboBox.getModel());
    this.table = FilterTable.builder(keyBindingModel.tableModel(), createColumns()).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);
  }

  private static List<FilterTableColumn<Id>> createColumns() {
    FilterTableColumn<Id> action = FilterTableColumn.builder(ACTION_COLUMN)
            .headerValue("Action")
            .build();
    FilterTableColumn<Id> whenFocused = FilterTableColumn.builder(WHEN_FOCUSED_COLUMN)
            .headerValue("When Focused")
            .build();
    FilterTableColumn<Id> whenInFocusedWindow = FilterTableColumn.builder(WHEN_IN_FOCUSED_WINDOW_COLUMN)
            .headerValue("When in Focused Window")
            .build();
    FilterTableColumn<Id> whenAncestor = FilterTableColumn.builder(WHEN_ANCESTOR_COLUMN)
            .headerValue("When Ancestor")
            .build();

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

  public static void main(String[] args) {
    System.setProperty("sun.awt.disablegrab", "true");
    Arrays.stream(FlatAllIJThemes.INFOS)
            .forEach(LookAndFeelProvider::addLookAndFeel);
    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.framework.demos.manual.keybinding.KeyBindingModel.KeyBindingColumns.Id;
import is.codion.swing.common.model.component.combobox.FilterComboBoxModel;
import is.codion.swing.common.model.component.table.FilterTableModel;
import is.codion.swing.common.model.component.table.FilterTableModel.Columns;
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 is.codion.swing.common.model.component.combobox.FilterComboBoxModel.filterComboBoxModel;
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 final FilterTableModel<KeyBinding, Id> tableModel;
  private final FilterComboBoxModel<String> componentComboBoxModel;

  KeyBindingModel(FilterComboBoxModel<Item<LookAndFeelProvider>> lookAndFeelComboBoxModel) {
    this.componentComboBoxModel = createComponentComboBoxModel(lookAndFeelComboBoxModel);
    this.componentComboBoxModel.refresh();
    this.tableModel = FilterTableModel.builder(new KeyBindingColumns())
            .items(new KeyBindingItems())
            .build();
    bindEvents(lookAndFeelComboBoxModel);
  }

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

  FilterTableModel<KeyBinding, Id> tableModel() {
    return tableModel;
  }

  private void bindEvents(FilterComboBoxModel<Item<LookAndFeelProvider>> lookAndFeelComboBoxModel) {
    componentComboBoxModel.refresher().refreshEvent().addListener(tableModel::refresh);
    componentComboBoxModel.selectionEvent().addListener(tableModel::refresh);
    lookAndFeelComboBoxModel.selectionEvent().addListener(componentComboBoxModel::refresh);
  }

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

    return PACKAGE + componentName;
  }

  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 KeyBindingItems 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);
      }
    }
  }

  public static final class KeyBindingColumns implements Columns<KeyBinding, Id> {

    public enum Id {
      ACTION_COLUMN,
      WHEN_FOCUSED_COLUMN,
      WHEN_IN_FOCUSED_WINDOW_COLUMN,
      WHEN_ANCESTOR_COLUMN
    }

    private static final List<Id> IDENTIFIERS = Arrays.asList(Id.values());

    @Override
    public List<Id> identifiers() {
      return IDENTIFIERS;
    }

    @Override
    public Class<?> columnClass(Id identifier) {
      return String.class;
    }

    @Override
    public Object value(KeyBinding keyBinding, Id identifier) {
      switch (identifier) {
        case ACTION_COLUMN:
          return keyBinding.action;
        case WHEN_FOCUSED_COLUMN:
          return keyBinding.whenFocused;
        case WHEN_IN_FOCUSED_WINDOW_COLUMN:
          return keyBinding.whenInFocusedWindow;
        case WHEN_ANCESTOR_COLUMN:
          return keyBinding.whenAncestor;
        default:
          throw new IllegalArgumentException("Unknown identifier: " + identifier);
      }
    }
  }

  private static FilterComboBoxModel<String> createComponentComboBoxModel(
          FilterComboBoxModel<Item<LookAndFeelProvider>> lookAndFeelComboBoxModel) {
    FilterComboBoxModel<String> comboBoxModel = filterComboBoxModel();
    comboBoxModel.refresher().items().set(new ComponentItems(lookAndFeelComboBoxModel));

    return comboBoxModel;
  }

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

    private final FilterComboBoxModel<Item<LookAndFeelProvider>> lookAndFeelComboBoxModel;

    private ComponentItems(FilterComboBoxModel<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(ComponentItems::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();
    }
  }
}