/*
* Spadger - an open source discussion forum system.
*
* Copyright (C) 2010 The Spadger Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.ajiaojr.spadger.client;
import java.util.HashMap;
import java.util.Map;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
/**
* Manages hot keys.
*
* <p>
* This class maintains a static map of all the hot key sequences and their
* respective commands, and upon a key press, it checks if the key, and maybe
* the previously pressed ones form the first part of any registered sequences.
*
* <p>
* The reason this is implemented this way instead of directly registering every
* key press listeners to the DOM is so that we do not have a huge number of
* listeners hanging around. Listeners can potentially have a huge impact to the
* performance of the system.
*
* <p>
* Doing so however means it is impossible to register more than 1 {@code
* Command} to a sequence. While this may sound quite restrictive, registering
* more than one command to one hot key sequence is seldom used in real-world
* cases.
*
* @author The Spadger Team
*/
public class HotKeysManager {
private static Timer timer = new Timer() {
@Override
public void run() {
possibleHotKeySequences.clear();
possibleHotKeySequences.putAll(hotKeySequences);
}
};
/**
* Hot key sequence expiration time, in milliseconds.
*/
public static int HOT_KEY_SEQUENCE_EXPIRATION = 2000;
private static HandlerRegistration hotKeysHandlerRegistration;
private static Map<Integer[], Command> hotKeySequences = new HashMap<Integer[], Command>();
private static Map<Integer[], Command> possibleHotKeySequences = new HashMap<Integer[], Command>();
/**
* Activates all the hot keys registered.
*/
public static void activateHotKeys() {
timer.run();
hotKeysHandlerRegistration = Event
.addNativePreviewHandler(new NativePreviewHandler() {
@Override
public void onPreviewNativeEvent(NativePreviewEvent event) {
if (event.getTypeInt() == Event.ONKEYPRESS) {
int keyCode = event.getNativeEvent().getKeyCode();
// Now we see what we can do with key sequences.
// The logic is trivial:
// 1. We iterate through the possible key sequences, and see if
// the first of any of the the remaining keys match our current
// key code
// 2. If it does, check the following 2 possibilities:
// 2.1 if it's already the last key, execute the command.
// 2.2 if there are more keys remaining, reset the timer so that
// the user is given another HOT_KEY_SEQUENCE_EXPIRATION to
// attempt the next key.
// 3. If it doesn't, it gets discarded.
// 4. Always put all available hot key sequences to the list.
for (Integer[] keyCodes : possibleHotKeySequences.keySet()) {
if (keyCodes[0] == keyCode) {
if (keyCodes.length == 1) {
possibleHotKeySequences.get(keyCodes).execute();
timer.cancel();
timer.run();
} else {
Integer[] newKeyCodes = new Integer[keyCodes.length - 1];
for (int i = 0; i < newKeyCodes.length; i++) {
newKeyCodes[i] = keyCodes[i + 1];
}
possibleHotKeySequences.put(newKeyCodes,
possibleHotKeySequences.get(keyCodes));
timer.cancel();
timer.schedule(HOT_KEY_SEQUENCE_EXPIRATION);
}
}
possibleHotKeySequences.remove(keyCodes);
}
possibleHotKeySequences.putAll(hotKeySequences);
}
}
});
}
/**
* De-activates all the hot keys registered.
*/
public static void deactivateHotKeys() {
if (hotKeysHandlerRegistration != null)
hotKeysHandlerRegistration.removeHandler();
}
/**
* Registers a hot key.
*
* @param keyCode
* the key code of the hot key.
* @param command
* the command to be executed.
*/
public static void registerHotKey(int keyCode, Command command) {
registerHotKeySequence(new int[] { keyCode }, command);
}
/**
* Registers a hot key sequence.
*
* @param keyCodes
* the key codes in the order they are supposed to be pressed.
* @param command
* the command to be executed.
*/
public static void registerHotKeySequence(int[] keyCodes, Command command) {
Integer[] _keyCodes = new Integer[keyCodes.length];
for (int i = 0; i < keyCodes.length; i++) {
_keyCodes[i] = keyCodes[i];
}
hotKeySequences.put(_keyCodes, command);
}
}