// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.collide.client.util.dom.eventcapture;
import com.google.collide.client.util.Elements;
import com.google.collide.client.util.JsIntegerMap;
import com.google.collide.client.util.SignalEventUtils;
import com.google.collide.client.util.input.CharCodeWithModifiers;
import com.google.common.base.Preconditions;
import org.waveprotocol.wave.client.common.util.SignalEvent;
import elemental.events.Event;
import elemental.events.EventListener;
import elemental.html.Element;
/**
* Provides a mean for registering global hot key bindings, particularly
* spring-loaded hot keys.
*
* <p>All hot keys depend on CTRL being pressed, while other modifiers are not,
* so as not to interfere with regular typing.
*/
public class GlobalHotKey {
/**
* Container for one entry in the HotKey database.
*/
public static class Data {
private final String description;
private final Handler handler;
private final CharCodeWithModifiers key;
private Data(CharCodeWithModifiers key, Handler handler, String description) {
this.key = key;
this.handler = handler;
this.description = description;
}
public String getDescription() {
return description;
}
public Handler getHandler() {
return handler;
}
public CharCodeWithModifiers getKey() {
return key;
}
}
/**
* A handler interface to receive event callbacks.
*/
public interface Handler {
/**
* Called when a hot key is initially pressed.
*
* @param event the underlying event
*/
void onKeyDown(SignalEvent event);
}
private static int handlerCount = 0;
private static JsIntegerMap<Data> handlers;
// Human readable descriptions for key codes.
private static CaptureReleaser remover;
/**
* Registers a handler to receive notification when the key corresponding to
* {@code keyCode} is used.
*
* <p>Only one handler can be tied to a particular key code. Attempting to
* register a previously registered code will result in an assertion being
* raised.
*
* @param key the key code with modifiers
* @param handler a callback handler
* @param description short human readable description for the action.
*/
public static void register(CharCodeWithModifiers key, Handler handler, String description) {
if (handlers == null) {
remover = addEventListeners();
handlers = JsIntegerMap.create();
}
Data handle = new Data(key, handler, description);
int keyDigest = key.getKeyDigest();
Preconditions.checkState(
handlers.get(keyDigest) == null, "Only one handler can be registered per a key");
handlers.put(keyDigest, handle);
++handlerCount;
}
/**
* Unregisters a previously registered handler for a particular keyCode.
*/
public static void unregister(CharCodeWithModifiers key) {
int keyDigest = key.getKeyDigest();
Preconditions.checkState(
handlers.get(keyDigest) != null, "No handler is register for this key");
handlers.erase(keyDigest);
if (--handlerCount == 0) {
remover.release();
remover = null;
handlers = null;
}
}
private static CaptureReleaser addEventListeners() {
final EventListener downListener = new EventListener() {
@Override
public void handleEvent(Event event) {
SignalEvent signalEvent = SignalEventUtils.create(event, false);
if (signalEvent == null) {
return;
}
int keyDigest = CharCodeWithModifiers.computeKeyDigest(signalEvent);
final Data data = handlers.get(keyDigest);
if (data == null) {
return;
}
Handler handler = data.getHandler();
handler.onKeyDown(signalEvent);
event.preventDefault();
}
};
// Attach the listeners.
final Element documentElement = Elements.getDocument().getDocumentElement();
documentElement.addEventListener(Event.KEYDOWN, downListener, true);
final CaptureReleaser downRemover = new CaptureReleaser() {
@Override
public void release() {
documentElement.removeEventListener(Event.KEYDOWN, downListener, true);
}
};
return new CaptureReleaser() {
@Override
public void release() {
downRemover.release();
}
};
}
/**
* This class is automatically instantiated as a singleton through the
* {@link #register} method.
*/
private GlobalHotKey() {
// Do nothing
}
}