Initial hotkey infra
- Duplicate keyhandler into hotkeyhandler - Define some view to handle hot keys, like AppView and MenuBarView - Catalog app binds some menus to hot keys - Bind all normal keys a-z with alt - Relates #823 - Relates #826
This commit is contained in:
@@ -229,12 +229,24 @@ public class TerminalUI {
|
||||
|
||||
private void handleKeyEvent(KeyEvent event) {
|
||||
log.trace("handleKeyEvent {}", event);
|
||||
if (rootView != null && rootView.hasFocus()) {
|
||||
KeyHandler handler = rootView.getKeyHandler();
|
||||
|
||||
if (rootView != null) {
|
||||
// if hotkeys consume, we're done
|
||||
KeyHandler handler = rootView.getHotKeyHandler();
|
||||
if (handler != null) {
|
||||
KeyHandlerResult result = handler.handle(KeyHandler.argsOf(event));
|
||||
if (result.focus() != null) {
|
||||
setFocus(result.focus());
|
||||
if (result.consumed()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// continue with one having focus
|
||||
if (rootView.hasFocus()) {
|
||||
handler = rootView.getKeyHandler();
|
||||
if (handler != null) {
|
||||
KeyHandlerResult result = handler.handle(KeyHandler.argsOf(event));
|
||||
if (result.focus() != null) {
|
||||
setFocus(result.focus());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ public abstract class AbstractView extends AbstractControl implements View {
|
||||
private int layer;
|
||||
private EventLoop eventLoop;
|
||||
private Map<Integer, KeyBindingValue> keyBindings = new HashMap<>();
|
||||
private Map<Integer, KeyBindingValue> hotKeyBindings = new HashMap<>();
|
||||
private Map<Integer, MouseBindingValue> mouseBindings = new HashMap<>();
|
||||
|
||||
public AbstractView() {
|
||||
@@ -180,6 +181,25 @@ public abstract class AbstractView extends AbstractControl implements View {
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyHandler getHotKeyHandler() {
|
||||
log.trace("getHotKeyHandler() {}", this);
|
||||
KeyHandler handler = args -> {
|
||||
KeyEvent event = args.event();
|
||||
boolean consumed = false;
|
||||
Integer key = event.key();
|
||||
if (key != null) {
|
||||
KeyBindingValue keyBindingValue = getHotKeyBindings().get(key);
|
||||
if (keyBindingValue != null) {
|
||||
consumed = dispatchRunCommand(event, keyBindingValue);
|
||||
}
|
||||
|
||||
}
|
||||
return KeyHandler.resultOf(event, consumed, null);
|
||||
};
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a callback function which is invoked after a {@link View} has been
|
||||
* drawn.
|
||||
@@ -236,6 +256,24 @@ public abstract class AbstractView extends AbstractControl implements View {
|
||||
});
|
||||
}
|
||||
|
||||
protected void registerHotKeyBinding(Integer keyType, String keyCommand) {
|
||||
registerHotKeyBinding(keyType, keyCommand, null, null);
|
||||
}
|
||||
|
||||
protected void registerHotKeyBinding(Integer keyType, KeyBindingConsumer keyConsumer) {
|
||||
registerHotKeyBinding(keyType, null, keyConsumer, null);
|
||||
}
|
||||
|
||||
protected void registerHotKeyBinding(Integer keyType, Runnable keyRunnable) {
|
||||
registerHotKeyBinding(keyType, null, null, keyRunnable);
|
||||
}
|
||||
|
||||
private void registerHotKeyBinding(Integer keyType, String keyCommand, KeyBindingConsumer keyConsumer, Runnable keyRunnable) {
|
||||
hotKeyBindings.compute(keyType, (key, old) -> {
|
||||
return KeyBindingValue.of(old, keyCommand, keyConsumer, keyRunnable);
|
||||
});
|
||||
}
|
||||
|
||||
record KeyBindingValue(String keyCommand, KeyBindingConsumer keyConsumer, Runnable keyRunnable) {
|
||||
static KeyBindingValue of(KeyBindingValue old, String keyCommand, KeyBindingConsumer keyConsumer,
|
||||
Runnable keyRunnable) {
|
||||
@@ -257,6 +295,15 @@ public abstract class AbstractView extends AbstractControl implements View {
|
||||
return keyBindings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hotkey bindings.
|
||||
*
|
||||
* @return hotkey bindings
|
||||
*/
|
||||
protected Map<Integer, KeyBindingValue> getHotKeyBindings() {
|
||||
return hotKeyBindings;
|
||||
}
|
||||
|
||||
record MouseBindingValue(String mouseCommand, MouseBindingConsumer mouseConsumer, Runnable mouseRunnable,
|
||||
Predicate<MouseEvent> mousePredicate) {
|
||||
static MouseBindingValue of(MouseBindingValue old, String mouseCommand, MouseBindingConsumer mouseConsumer,
|
||||
|
||||
@@ -133,6 +133,11 @@ public class AppView extends BoxView {
|
||||
return otherHandler.thenIfNotConsumed(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyHandler getHotKeyHandler() {
|
||||
return menu != null ? menu.getHotKeyHandler() : super.getHotKeyHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFocus() {
|
||||
if (grid != null) {
|
||||
|
||||
@@ -35,8 +35,8 @@ import org.springframework.shell.component.view.geom.Dimension;
|
||||
import org.springframework.shell.component.view.geom.Rectangle;
|
||||
import org.springframework.shell.component.view.screen.Screen;
|
||||
import org.springframework.shell.component.view.screen.Screen.Writer;
|
||||
import org.springframework.shell.style.ThemeResolver;
|
||||
import org.springframework.shell.component.view.screen.ScreenItem;
|
||||
import org.springframework.shell.style.ThemeResolver;
|
||||
|
||||
/**
|
||||
* {@link MenuBarView} shows {@link MenuBarItem items} horizontally and is
|
||||
@@ -187,6 +187,17 @@ public class MenuBarView extends BoxView {
|
||||
return x;
|
||||
}
|
||||
|
||||
private int itemIndex(MenuBarItem item) {
|
||||
int index = 0;
|
||||
for (MenuBarItem i : items) {
|
||||
if (i == item) {
|
||||
return index;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void select(MouseEvent event) {
|
||||
int x = event.x();
|
||||
int y = event.y();
|
||||
@@ -256,6 +267,23 @@ public class MenuBarView extends BoxView {
|
||||
public void setItems(List<MenuBarItem> items) {
|
||||
this.items.clear();
|
||||
this.items.addAll(items);
|
||||
registerHotKeys();
|
||||
}
|
||||
|
||||
private void selectItem(MenuBarItem item) {
|
||||
int index = itemIndex(item);
|
||||
if (index > -1) {
|
||||
setSelected(index);
|
||||
checkMenuView();
|
||||
}
|
||||
}
|
||||
|
||||
private void registerHotKeys() {
|
||||
getItems().stream()
|
||||
.filter(item -> item.getHotKey() != null)
|
||||
.forEach(item -> {
|
||||
registerHotKeyBinding(item.getHotKey(), () -> selectItem(item));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -265,6 +293,7 @@ public class MenuBarView extends BoxView {
|
||||
|
||||
private String title;
|
||||
private List<MenuItem> items;
|
||||
private Integer hotKey;
|
||||
|
||||
public MenuBarItem(String title) {
|
||||
this(title, null);
|
||||
@@ -286,6 +315,15 @@ public class MenuBarView extends BoxView {
|
||||
public List<MenuItem> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
public Integer getHotKey() {
|
||||
return hotKey;
|
||||
}
|
||||
|
||||
public MenuBarItem setHotKey(Integer hotKey) {
|
||||
this.hotKey = hotKey;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -60,13 +60,23 @@ public interface View extends Control {
|
||||
MouseHandler getMouseHandler();
|
||||
|
||||
/**
|
||||
* Gets a {@link View} mouse {@link KeyHandler}. Can be {@code null} which
|
||||
* Gets a {@link View} key {@link KeyHandler}. Can be {@code null} which
|
||||
* indicates view will not handle any key events.
|
||||
*
|
||||
* @return a view mouse handler
|
||||
* @return a view key handler
|
||||
* @see KeyHandler
|
||||
*/
|
||||
@Nullable
|
||||
KeyHandler getKeyHandler();
|
||||
|
||||
/**
|
||||
* Gets a {@link View} hotkey {@link KeyHandler}. Can be {@code null} which
|
||||
* indicates view will not handle any key events.
|
||||
*
|
||||
* @return a view hotkey handler
|
||||
* @see KeyHandler
|
||||
*/
|
||||
@Nullable
|
||||
KeyHandler getHotKeyHandler();
|
||||
|
||||
}
|
||||
|
||||
@@ -45,6 +45,10 @@ public class KeyBinder {
|
||||
keyMap.bind(KeyEvent.Key.Char, Character.toString(i));
|
||||
}
|
||||
|
||||
for (char i = KeyEvent.Key.a; i <= KeyEvent.Key.z; i++) {
|
||||
keyMap.bind(i | KeyEvent.KeyMask.AltMask, alt(i));
|
||||
}
|
||||
|
||||
keyMap.bind(KeyEvent.Key.q | KeyEvent.KeyMask.CtrlMask, ctrl('q'));
|
||||
|
||||
keyMap.bind(KeyEvent.Key.Mouse, key(terminal, Capability.key_mouse));
|
||||
|
||||
@@ -44,6 +44,18 @@ class MenuBarViewTests extends AbstractViewTests {
|
||||
assertThat(view.getItems()).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hotkeys() {
|
||||
MenuBarItem item;
|
||||
|
||||
item = MenuBarItem.of("title");
|
||||
assertThat(item.getHotKey()).isNull();
|
||||
item.setHotKey(Key.f);
|
||||
assertThat(item.getHotKey()).isEqualTo(Key.f);
|
||||
|
||||
item = MenuBarItem.of("title").setHotKey(Key.f);
|
||||
assertThat(item.getHotKey()).isEqualTo(Key.f);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.springframework.shell.component.view.control.cell.ListCell;
|
||||
import org.springframework.shell.component.view.event.EventLoop;
|
||||
import org.springframework.shell.component.view.event.KeyEvent;
|
||||
import org.springframework.shell.component.view.event.KeyEvent.Key;
|
||||
import org.springframework.shell.component.view.event.KeyEvent.KeyMask;
|
||||
import org.springframework.shell.component.view.geom.Rectangle;
|
||||
import org.springframework.shell.component.view.message.ShellMessageBuilder;
|
||||
import org.springframework.shell.component.view.screen.Screen;
|
||||
@@ -281,11 +282,13 @@ public class Catalog {
|
||||
.toArray(MenuItem[]::new);
|
||||
MenuBarView menuBar = MenuBarView.of(
|
||||
MenuBarItem.of("File",
|
||||
MenuItem.of("Quit", MenuItemCheckStyle.NOCHECK, quitAction)),
|
||||
MenuItem.of("Quit", MenuItemCheckStyle.NOCHECK, quitAction))
|
||||
.setHotKey(Key.f | KeyMask.AltMask),
|
||||
MenuBarItem.of("Theme",
|
||||
themeItems),
|
||||
themeItems)
|
||||
.setHotKey(Key.t | KeyMask.AltMask),
|
||||
MenuBarItem.of("Help",
|
||||
MenuItem.of("About"))
|
||||
MenuItem.of("About"))
|
||||
);
|
||||
|
||||
menuBar.setThemeResolver(themeResolver);
|
||||
|
||||
Reference in New Issue
Block a user