Skip to content

Commit bba7a82

Browse files
committed
Make StatusBarView work with hotkeys
- StatusItem can now define a hotkey which is then bound to its action. - In catalog app replace use of raw key event to item's hotkey for status bar visibility. - Various doc updates. - Relates #826
1 parent db3f677 commit bba7a82

File tree

9 files changed

+227
-22
lines changed

9 files changed

+227
-22
lines changed

spring-shell-core/src/main/java/org/springframework/shell/component/view/control/AppView.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,10 @@ else if (event.isKey(Key.CursorRight)) {
135135

136136
@Override
137137
public KeyHandler getHotKeyHandler() {
138-
return menu != null ? menu.getHotKeyHandler() : super.getHotKeyHandler();
138+
KeyHandler mainHandler = main != null ? main.getHotKeyHandler() : super.getHotKeyHandler();
139+
KeyHandler menuHandler = menu != null ? menu.getHotKeyHandler() : super.getHotKeyHandler();
140+
KeyHandler statusHandler = status != null ? status.getHotKeyHandler() : super.getHotKeyHandler();
141+
return mainHandler.thenIfNotConsumed(menuHandler).thenIfNotConsumed(statusHandler);
139142
}
140143

141144
@Override

spring-shell-core/src/main/java/org/springframework/shell/component/view/control/StatusBarView.java

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ private StatusItem itemAt(int x, int y) {
121121
public void setItems(List<StatusItem> items) {
122122
this.items.clear();
123123
this.items.addAll(items);
124+
registerHotKeys();
124125
}
125126

126127
/**
@@ -132,21 +133,50 @@ public List<StatusItem> getItems() {
132133
return items;
133134
}
134135

136+
private void registerHotKeys() {
137+
getItems().stream()
138+
.filter(item -> item.getHotKey() != null)
139+
.forEach(item -> {
140+
Runnable action = item.getAction();
141+
if (action != null) {
142+
registerHotKeyBinding(item.getHotKey(), action);
143+
}
144+
});
145+
}
146+
135147
/**
136148
* {@link StatusItem} represents an item in a {@link StatusBarView}.
137149
*/
138150
public static class StatusItem {
139151

140152
private String title;
141153
private Runnable action;
154+
private Integer hotKey;
142155

143156
public StatusItem(String title) {
144157
this(title, null);
145158
}
146159

147160
public StatusItem(String title, Runnable action) {
161+
this(title, action, null);
162+
}
163+
164+
public StatusItem(String title, Runnable action, Integer hotKey) {
148165
this.title = title;
149166
this.action = action;
167+
this.hotKey = hotKey;
168+
}
169+
170+
public static StatusItem of(String title) {
171+
return new StatusItem(title);
172+
}
173+
174+
public static StatusItem of(String title, Runnable action) {
175+
return new StatusItem(title, action);
176+
}
177+
178+
public static StatusItem of(String title, Runnable action, Integer hotKey) {
179+
return new StatusItem(title, action, hotKey);
150180
}
151181

152182
public String getTitle() {
@@ -157,8 +187,18 @@ public Runnable getAction() {
157187
return action;
158188
}
159189

160-
public void setAction(Runnable action) {
190+
public StatusItem setAction(Runnable action) {
161191
this.action = action;
192+
return this;
193+
}
194+
195+
public Integer getHotKey() {
196+
return hotKey;
197+
}
198+
199+
public StatusItem setHotKey(Integer hotKey) {
200+
this.hotKey = hotKey;
201+
return this;
162202
}
163203

164204
}

spring-shell-core/src/test/java/org/springframework/shell/component/view/control/StatusBarViewTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import org.springframework.shell.component.view.control.StatusBarView.StatusBarViewOpenSelectedItemEvent;
2828
import org.springframework.shell.component.view.control.StatusBarView.StatusItem;
29+
import org.springframework.shell.component.view.event.KeyEvent.Key;
2930
import org.springframework.shell.component.view.event.MouseEvent;
3031
import org.springframework.shell.component.view.event.MouseHandler.MouseHandlerResult;
3132
import org.springframework.test.util.ReflectionTestUtils;
@@ -51,8 +52,23 @@ void constructView() {
5152

5253
view = new StatusBarView(Arrays.asList(new StatusItem("item1")));
5354
assertThat(view.getItems()).hasSize(1);
55+
56+
view = new StatusBarView(Arrays.asList(StatusItem.of("item1")));
57+
assertThat(view.getItems()).hasSize(1);
5458
}
5559

60+
@Test
61+
void hotkeys() {
62+
StatusItem item;
63+
64+
item = StatusItem.of("title");
65+
assertThat(item.getHotKey()).isNull();
66+
item.setHotKey(Key.f);
67+
assertThat(item.getHotKey()).isEqualTo(Key.f);
68+
69+
item = StatusItem.of("title").setHotKey(Key.f);
70+
assertThat(item.getHotKey()).isEqualTo(Key.f);
71+
}
5672
}
5773

5874
@Nested

spring-shell-docs/modules/ROOT/nav.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,15 @@
5656
** xref:tui/intro/index.adoc[]
5757
*** xref:tui/intro/terminalui.adoc[]
5858
** xref:tui/views/index.adoc[]
59+
*** xref:tui/views/app.adoc[]
5960
*** xref:tui/views/box.adoc[]
6061
*** xref:tui/views/button.adoc[]
6162
*** xref:tui/views/dialog.adoc[]
6263
*** xref:tui/views/grid.adoc[]
6364
*** xref:tui/views/list.adoc[]
6465
*** xref:tui/views/menu.adoc[]
6566
*** xref:tui/views/menubar.adoc[]
67+
*** xref:tui/views/statusbar.adoc[]
6668
** xref:tui/events/index.adoc[]
6769
*** xref:tui/events/eventloop.adoc[]
6870
*** xref:tui/events/key.adoc[]
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
= AppView
2+
:page-section-summary-toc: 1
3+
4+
ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/shell/docs]
5+
6+
_AppView_ is a base implementation providing functionality to draw opinionated _application view_.
7+
Inherits xref:tui/views/box.adoc[].
8+
9+
Generic idea is to have menu and status views which typically are xref:tui/views/menubar.adoc[] and
10+
xref:tui/views/statusbar.adoc[] respectively. Main content view is then whatever user want to show
11+
in it.
12+
13+
[source, text]
14+
----
15+
┌──────────────────────────┐
16+
│ Menu │
17+
├──────────────────────────┤
18+
│ │
19+
│ Main │
20+
│ │
21+
├──────────────────────────┤
22+
│ Status │
23+
└──────────────────────────┘
24+
----
25+
26+
== Key Handling
27+
If menu has a focus key handling is processed there, then main is consulted for handling.
28+
Lastly cursor left/right are processed to dispatch _AppViewEvent_.
29+
30+
== HotKey Handling
31+
Hotkeys are processed in order of _main_, _menu_ and _status_.
32+
33+
== Events
34+
.AppView Events
35+
|===
36+
|Event |Description
37+
38+
|AppViewEvent
39+
|Direction for a next selection.
40+
41+
|===

spring-shell-docs/modules/ROOT/pages/tui/views/menubar.adoc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/she
77
_MenuBarView_ is a base implementation providing functionality to draw a menu bar.
88
Inherits xref:tui/views/box.adoc[].
99

10+
[source, text]
11+
----
12+
┌─────────────────────────────┐
13+
│ File Help │
14+
└─────────────────────────────┘
15+
----
16+
1017
== Default Bindings
1118
Default _key bindigs_ are:
1219

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
= StatusBarView
2+
:page-section-summary-toc: 1
3+
4+
ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/shell/docs]
5+
6+
_StatusBarView_ is a base implementation providing functionality to draw a status bar.
7+
Inherits xref:tui/views/box.adoc[].
8+
9+
[source, text]
10+
----
11+
┌─────────────────────────────┐
12+
│ Item1 | Item2 | Item3 │
13+
└─────────────────────────────┘
14+
----
15+
16+
You can create a simple status bar with an item:
17+
18+
[source, java, indent=0]
19+
----
20+
include::{snippets}/StatusBarViewSnippets.java[tag=simple]
21+
----
22+
23+
Constructor can take array form which allows to lay out simple
24+
item definitions in a _dsl_ style.
25+
26+
[source, java, indent=0]
27+
----
28+
include::{snippets}/StatusBarViewSnippets.java[tag=viaarray]
29+
----
30+
31+
Items support runnable actions which generally as executed when
32+
item is selected. It can also get attached to a hot key.
33+
34+
[source, java, indent=0]
35+
----
36+
include::{snippets}/StatusBarViewSnippets.java[tag=items]
37+
----
38+
39+
40+
== Events
41+
.StatusBarView Events
42+
|===
43+
|Event |Description
44+
45+
|StatusBarViewOpenSelectedItemEvent
46+
|StatusItem is selected.
47+
48+
|===
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.shell.docs;
17+
18+
import java.util.List;
19+
20+
import org.springframework.shell.component.view.control.StatusBarView;
21+
import org.springframework.shell.component.view.control.StatusBarView.StatusItem;
22+
import org.springframework.shell.component.view.event.KeyEvent;
23+
import org.springframework.shell.component.view.event.KeyEvent.Key;
24+
25+
class StatusBarViewSnippets {
26+
27+
@SuppressWarnings("unused")
28+
void simple() {
29+
// tag::simple[]
30+
StatusItem item1 = new StatusBarView.StatusItem("Item1");
31+
StatusBarView statusBar = new StatusBarView(List.of(item1));
32+
// end::simple[]
33+
}
34+
35+
void items() {
36+
// tag::items[]
37+
StatusItem item1 = StatusBarView.StatusItem.of("Item1");
38+
39+
Runnable action1 = () -> {};
40+
StatusItem item2 = StatusBarView.StatusItem.of("Item2", action1);
41+
42+
Runnable action2 = () -> {};
43+
StatusItem item3 = StatusBarView.StatusItem.of("Item3", action2, KeyEvent.Key.f10);
44+
45+
StatusBarView statusBar = new StatusBarView();
46+
statusBar.setItems(List.of(item1, item2, item3));
47+
// end::items[]
48+
}
49+
50+
void viaArray() {
51+
// tag::viaarray[]
52+
new StatusBarView(new StatusItem[] {
53+
StatusItem.of("Item1"),
54+
StatusItem.of("Item2")
55+
.setAction(() -> {}),
56+
StatusItem.of("Item3")
57+
.setAction(() -> {})
58+
.setHotKey(Key.f10)
59+
});
60+
// end::viaarray[]
61+
}
62+
63+
}

spring-shell-samples/spring-shell-sample-catalog/src/main/java/org/springframework/shell/samples/catalog/Catalog.java

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
package org.springframework.shell.samples.catalog;
1717

1818
import java.util.ArrayList;
19-
import java.util.Arrays;
2019
import java.util.List;
2120
import java.util.Map;
2221
import java.util.TreeMap;
@@ -203,21 +202,6 @@ private AppView buildScenarioBrowser(EventLoop eventLoop, TerminalUI component)
203202
}
204203
));
205204

206-
// we could potentially do keybinding somewhere else
207-
// but at least this shows how to do it in low level.
208-
// essentially we now just handle F10 to toggle
209-
// menubar visibility
210-
// TODO: when we get support for hotkeys we should do
211-
// binding there
212-
eventLoop.onDestroy(eventLoop.keyEvents()
213-
.subscribe(event -> {
214-
log.debug("Raw keyevent {}", event);
215-
if (event.isKey(KeyEvent.Key.f10)) {
216-
app.toggleStatusBarVisibility();
217-
}
218-
}
219-
));
220-
221205
return app;
222206
}
223207

@@ -322,11 +306,12 @@ private MenuBarView buildMenuBar(EventLoop eventLoop) {
322306

323307
private StatusBarView buildStatusBar(EventLoop eventLoop) {
324308
Runnable quitAction = () -> requestQuit();
325-
StatusBarView statusBar = new StatusBarView();
309+
Runnable visibilyAction = () -> app.toggleStatusBarVisibility();
310+
StatusBarView statusBar = new StatusBarView(new StatusItem[] {
311+
StatusItem.of("CTRL-Q Quit", quitAction),
312+
StatusItem.of("F10 Status Bar", visibilyAction, KeyEvent.Key.f10)
313+
});
326314
ui.configure(statusBar);
327-
StatusItem item1 = new StatusBarView.StatusItem("CTRL-Q Quit", quitAction);
328-
StatusItem item2 = new StatusBarView.StatusItem("F10 Status Bar");
329-
statusBar.setItems(Arrays.asList(item1, item2));
330315
return statusBar;
331316
}
332317

0 commit comments

Comments
 (0)