Sections

 Home
 Dev Log
 Resume
 Free Media
 Riddles
 Bob's Gallery
 Links & Resources
 Contact


Projects

 Stem
 Nyx
 Kana Quizzer
 Keybinding Util
 Model Gallery
 Applets
 Nexus


Readme

Author: Damian Johnson
coffee.png Last Modified: 9/28/07
Compiled with Java version 1.6.0_02

This project consists of two tools for the use of customizable key bindings in Java applications. The first is an editor for creating or changing lists of bindings to be loaded from applications. The second is a chooser that can be used within applications to provide Swing dialogs for customizing the bindings. Keeping to the Unix philosophy I'm hoping for this to do one thing, and do it well. Please email me any bug reports, feature requests, or even notes saying if it's helpful or not.

Java Keybindings

First of all a quick explanation of Java key bindings may be in order. If you are already aware of keyboard listeners and input maps you can safely skip this section.

Bindings are generally defined as mappings of keystrokes to strings representing the actions they perform. Since this is a mapping the editor and chooser prevent duplicates among the keyboard shortcuts. Java provides two methods for detecting keyboard events:

  1. Key Listeners (java.awt.event.KeyAdaptor)
    Listeners can be attached to components to receive keyboard events. Unfortunately this can be rather tricky since it requires that all focusable components have a copy of the listener. This is the only way of capturing arbitrary keystrokes.
  2. Input Maps (javax.swing.InputMap)
    This is the preferred method, avoiding much of the focus complications. The JComponent's InputMap provides mappings between keystrokes and objects (usually strings), then an ActionMap maps between those objects and code to be executed.

For more information see Sun's explanation here.

keybindingEditor.png

Keybinding Editor

The editor is a tool for making or changing lists of key bindings that can be used within applications. Shortcuts can be clicked to edit their bindings and actions can be changed by double clicking on them. The rest of the controls are obvious with a little experimentation.

The folder and disk buttons are for loading and saving respectively. This editor supports several types of standard Java persistence including:

  • Serialized linked or normal hash maps
  • Serialized input maps
  • Properties mapping (java.util.Properties) – either as plain text or xml

If your application is only using a small list of bindings then this can also output the source code that generates the bindings (skipping the fuss of dealing with persistence). Please note that the only format that preserves ordering is serialization with a linked hash map.

subdialogs.png

The wrench button provides the configuration controls for the editor, which gives the following options:

  • Detected Keystrokes – Type of keyboard event detected when setting shortcuts (see java.awt.event.KeyEvent for what this means).
  • Indent Field Content – Type of information provided in the left column.
  • Disabling Keycode – Defined if and what key sets an undefined mapping when entered for the shortcut. Currently none of the persistence formats support undefined mappings.
  • Control Bindings – Keyboard shortcuts used by the editor itself.
  • Demo Chooser – Shows an example of how the current bindings would appear in a chooser dialog. This first gives the dialog to the right, providing options for how the chooser is displayed.

Keybinding Chooser

The chooser is a UI utility that can be included in applications to provide dialogs for changing the bindings (much as the JColorChooser allows the selection of colors). The dialog's appearance is fairly customizable, the image to the right showing choosers with a light and dark blue color scheme. The javadocs for these classes are rather extensive I'll just give an overview of the classes involved:

  • chooser.BindingEntry – Single row of the display, acting both as a display element and definition of a key binding.
  • chooser.BindingPanel – Abstract panel providing most utility functions for a list of bindings (adding, clearing, getting the map, etc). Its abstract components are methods to handle the functionality when the contents change or clicks are detected.
  • chooser.BindingAdaptor – KeyAdaptor used in the BindingChooser to detect keyboard events for changing the shortcuts.
  • chooser.BindingChooser – This is the implementation of the BindingPanel providing the ability to customize the shortcuts, display dialogs, and make sweeping changes to the appearance.
  • chooser.Persistence – Convenience methods for saving and loading key bindings.

Example Usage

The following is a simple program exemplifying the usage of this library. Customizable keybindings are associated with actions to change the panel's background. Changes to the bindings are preserved between runs.

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;

import javax.swing.*;

import chooser.BindingChooser;
import chooser.Persistence;

/**
 * Small demo for the keybindings util library. This presents a frame whose
 * background changes via customized keystrokes.
 */
public class KeybindingDemo extends JPanel {
  private static final long serialVersionUID = 0;
  
  // string constants associated with actions (displayed by chooser)
  private static final String SET_BG_RED = "Set Red";
  private static final String SET_BG_BLUE = "Set Blue";
  private static final String SET_BG_YELLOW = "Set Yellow";
  private static final String SET_BG_GREEN = "Set Green";
  private static final String SET_BG_WHITE = "Set White";
  
  // information for persistence
  private static final File BINDINGS_FILE = new File("savedBindings.xml");
  private static final Persistence BINDINGS_FORMAT = Persistence.PROPERTIES_XML;
  
  public static void main(String[] args) {
    JFrame frame = new JFrame("Keybinding Demo");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.add(new KeybindingDemo());
    frame.pack();
    frame.setVisible(true);
  }
  
  private KeybindingDemo() {
    setPreferredSize(new Dimension(200100));
    setBackground(Color.WHITE);
    
    // loads initial bindings
    Map <KeyStroke, String> initialBindings;
    try {
      // parses the bindings using the xml format of java.util.Properties
      initialBindings = BINDINGS_FORMAT.load(new FileInputStream(BINDINGS_FILE));
    catch (IOException exc) {
      // couldn't find the initial bindings
      System.out.println("Initial bindings not found: " + BINDINGS_FILE.getAbsolutePath());
      initialBindings = fabricateDefaults();
      
      // writes bindings so they can be loaded properly next time
      saveBindings(initialBindings);
    catch (ParseException exc) {
      // couldn't read the initial bindings (corrupt file?)
      System.out.println(exc.getMessage());
      initialBindings = fabricateDefaults();
    }
    
    // loads bindings into this panel's input map
    setBindings(initialBindings);
    
    // generates action portion of the bindings (ie, what they do)
    ActionMap am = getActionMap();
    am.put(SET_BG_RED, new DemoAction(Color.RED));
    am.put(SET_BG_BLUE, new DemoAction(Color.BLUE));
    am.put(SET_BG_YELLOW, new DemoAction(Color.YELLOW));
    am.put(SET_BG_GREEN, new DemoAction(Color.GREEN));
    am.put(SET_BG_WHITE, new DemoAction(Color.WHITE));
    
    // adds a button to present a chooser for changing the keybindings
    JButton chooserButton = new JButton("Show Chooser");
    chooserButton.setFocusable(false)// prevents stealing focus from panel
    chooserButton.addActionListener(new ChooserPopup(initialBindings));
    add(chooserButton);
  }
  
  // resets the panel's input map to a given set of bindings
  private void setBindings(Map <KeyStroke, String> bindings) {
    InputMap im = getInputMap();
    im.clear();
    
    for (KeyStroke shortcut : bindings.keySet()) {
      im.put(shortcut, bindings.get(shortcut));
    }
  }
  
  // saves set of keybindings
  private void saveBindings(Map <KeyStroke, String> bindings) {
    try {
      BINDINGS_FORMAT.save(new FileOutputStream(BINDINGS_FILE), bindings);
    catch (IOException exc) {
      System.out.println("Unable to save bindings to: " + BINDINGS_FILE.getAbsolutePath());
    }
  }
  
  /*
   * Usually you'll want the defaults to be loaded in a fail-safe fashion but
   * for this demo we'll just fabricate some bindings.
   */
  private static Map <KeyStroke, String> fabricateDefaults() {
    Map <KeyStroke, String> bindings = new HashMap <KeyStroke, String>();
    bindings.put(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0), SET_BG_RED);
    bindings.put(KeyStroke.getKeyStroke(KeyEvent.VK_B, 0), SET_BG_BLUE);
    bindings.put(KeyStroke.getKeyStroke(KeyEvent.VK_Y, 0), SET_BG_YELLOW);
    bindings.put(KeyStroke.getKeyStroke(KeyEvent.VK_G, 0), SET_BG_GREEN);
    bindings.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0), SET_BG_WHITE);
    return bindings;
  }
  
  // Simply sets the panel's background to a given color
  private class DemoAction extends AbstractAction {
    private static final long serialVersionUID = 0;
    private final Color bgColor;
    
    DemoAction(Color bgColor) {
      this.bgColor = bgColor;
    }
    
    public void actionPerformed(ActionEvent event) {
      KeybindingDemo.this.setBackground(this.bgColor);
    }
  }
  
  // Displays a dialog allowing user to customize keybindings
  private class ChooserPopup implements ActionListener {
    private Map <KeyStroke, String> previousBindings;
    
    ChooserPopup(Map <KeyStroke, String> initialBindings) {
      this.previousBindings = initialBindings;
    }
    
    public void actionPerformed(ActionEvent event) {
      Map <KeyStroke, String> output =
          BindingChooser.showDialog(KeybindingDemo.this, this.previousBindings);
      
      // output is null if canceled
      if (output != null) {
        setBindings(output);
        saveBindings(output);
        this.previousBindings = output;
      }
    }
  }
}