Skip to content

Commit 8ffd1c9

Browse files
committed
add history capability
1 parent efaba6d commit 8ffd1c9

File tree

6 files changed

+173
-37
lines changed

6 files changed

+173
-37
lines changed

README.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
Example [Text-IO](https://github.com/beryx/text-io) application that registers handlers for the up and down arrow keys in order to
2-
display available choices.
1+
Example [Text-IO](https://github.com/beryx/text-io) application that registers:
2+
- handlers for the Up and Down arrow keys in order to display available choices.
3+
- handlers for the Ctrl-Shift-Left and Ctrl-Shift-Right arrow keys in order to display previously entered values
34

4-
See [issue #17](https://github.com/beryx/text-io/issues/17) for details.
5+
See [issue #17](https://github.com/beryx/text-io/issues/17)
6+
and [issue #20](https://github.com/beryx/text-io/issues/20) for details.
57

68

79
**Getting started**

src/main/java/org/beryx/swing/handler/Demo.java

+28-21
Original file line numberDiff line numberDiff line change
@@ -38,32 +38,39 @@ public static void main(String[] args) {
3838
MarsTerminal terminal = new MarsTerminal();
3939
terminal.init();
4040
TextIO textIO = new TextIO(terminal);
41+
terminal.setBookmark("start");
4142

42-
Product product = new Product();
43-
SwingHandler handler = new SwingHandler(textIO, product);
43+
while(true) {
44+
Product product = new Product();
45+
SwingHandler handler = new SwingHandler(textIO, "mars-demo", product);
4446

45-
terminal.println("----------------------------------------------------------------");
46-
terminal.println("| Use the up and down arrow keys to scroll through choices. |");
47-
terminal.println("| Press '" + handler.getBackKeyStroke() + "' to go back to the previous field. |");
48-
terminal.println("----------------------------------------------------------------\n");
47+
terminal.println("---------------------------------------------------------------------------------------------------------");
48+
terminal.println("| Use the Up and Down arrow keys to scroll through choices. |");
49+
terminal.println("| Use the Ctrl-Shift-Left and Ctrl-Shift-Right arrow keys to scroll through previous entered values. |");
50+
terminal.println("| Press '" + handler.getBackKeyStroke() + "' to go back to the previous field. |");
51+
terminal.println("---------------------------------------------------------------------------------------------------------\n");
4952

50-
handler.addStringTask("name", "Product name")
51-
.addChoices("air conditioner", "air ioniser", "air purifier", "appliance plug", "aroma lamp", "attic fan", "bachelor griller", "back boiler", "beverage opener", "blender", "box mangle", "can opener", "ceiling fan", "central vacuum cleaner", "clothes dryer", "clothes iron", "cold-pressed juicer", "combo washer dryer", "dish draining closet", "dishwasher", "domestic robot", "drawer dishwasher", "electric water boiler", "exhaust hood", "fan heater", "flame supervision device", "forced-air", "futon dryer", "garbage disposal unit", "gas appliance", "go-to-bed matchbox", "hair dryer", "hair iron", "hob (hearth)", "home server", "humidifier", "hvac", "icebox", "kimchi refrigerator", "light fixture", "light", "mangle (machine)", "micathermic heater", "microwave oven", "mobile charger", "mousetrap", "oil heater", "oven", "patio heater", "paper shredder", "radiator (heating)", "refrigerator", "sewing machine", "space heater", "steam mop", "stove", "sump pump", "television", "tie press", "toaster and toaster ovens", "trouser press", "vacuum cleaner", "washing machine", "water cooker", "water purifier", "water heater", "window fan", "waffle iron")
52-
.constrainInputToChoices();
53-
handler.addIntTask("quantity", "Quantity")
54-
.withInputReaderConfigurator(r -> r.withMinVal(1).withMaxVal(50))
55-
.addChoices(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
56-
handler.addDoubleTask("unitPrice", "Unit price")
57-
.withInputReaderConfigurator(r -> r.withMinVal(0.01).withMaxVal(99.99))
58-
.addChoices(0.59, 0.86, 0.99, 1.14, 1.55, 1.63, 1.74, 1.99, 2.55, 2.88, 2.99);
59-
handler.addStringTask("color", "Color")
60-
.withInputReaderConfigurator(r -> r.withPropertiesPrefix("highlight"))
61-
.addChoices("amaranth", "amber", "amethyst", "apricot", "aquamarine", "azure", "baby blue", "beige", "black", "blue", "blue-green", "blue-violet", "blush", "bronze", "brown", "burgundy", "byzantium", "carmine", "cerise", "cerulean", "champagne", "chartreuse", "chocolate", "cobalt blue", "coffee", "copper", "coral", "crimson", "cyan", "desert sand", "electric blue", "emerald", "erin", "gold", "gray", "green", "harlequin", "indigo", "ivory", "jade", "jungle green", "lavender", "lemon", "lilac", "lime", "magenta", "magenta rose", "maroon", "mauve", "navy blue", "ocher", "olive", "orange", "orange-red", "orchid", "peach", "pear", "periwinkle", "persian blue", "pink", "plum", "prussian blue", "puce", "purple", "raspberry", "red", "red-violet", "rose", "ruby", "salmon", "sangria", "sapphire", "scarlet", "silver", "slate gray", "spring bud", "spring green", "tan", "taupe", "teal", "turquoise", "violet", "viridian", "white", "yellow");
62-
handler.execute();
53+
handler.addStringTask("name", "Product name")
54+
.addChoices("air conditioner", "air ioniser", "air purifier", "appliance plug", "aroma lamp", "attic fan", "bachelor griller", "back boiler", "beverage opener", "blender", "box mangle", "can opener", "ceiling fan", "central vacuum cleaner", "clothes dryer", "clothes iron", "cold-pressed juicer", "combo washer dryer", "dish draining closet", "dishwasher", "domestic robot", "drawer dishwasher", "electric water boiler", "exhaust hood", "fan heater", "flame supervision device", "forced-air", "futon dryer", "garbage disposal unit", "gas appliance", "go-to-bed matchbox", "hair dryer", "hair iron", "hob (hearth)", "home server", "humidifier", "hvac", "icebox", "kimchi refrigerator", "light fixture", "light", "mangle (machine)", "micathermic heater", "microwave oven", "mobile charger", "mousetrap", "oil heater", "oven", "patio heater", "paper shredder", "radiator (heating)", "refrigerator", "sewing machine", "space heater", "steam mop", "stove", "sump pump", "television", "tie press", "toaster and toaster ovens", "trouser press", "vacuum cleaner", "washing machine", "water cooker", "water purifier", "water heater", "window fan", "waffle iron")
55+
.constrainInputToChoices();
56+
handler.addIntTask("quantity", "Quantity")
57+
.withInputReaderConfigurator(r -> r.withMinVal(1).withMaxVal(50))
58+
.addChoices(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
59+
handler.addDoubleTask("unitPrice", "Unit price")
60+
.withInputReaderConfigurator(r -> r.withMinVal(0.01).withMaxVal(99.99))
61+
.addChoices(0.59, 0.86, 0.99, 1.14, 1.55, 1.63, 1.74, 1.99, 2.55, 2.88, 2.99);
62+
handler.addStringTask("color", "Color")
63+
.withInputReaderConfigurator(r -> r.withPropertiesPrefix("highlight"))
64+
.addChoices("amaranth", "amber", "amethyst", "apricot", "aquamarine", "azure", "baby blue", "beige", "black", "blue", "blue-green", "blue-violet", "blush", "bronze", "brown", "burgundy", "byzantium", "carmine", "cerise", "cerulean", "champagne", "chartreuse", "chocolate", "cobalt blue", "coffee", "copper", "coral", "crimson", "cyan", "desert sand", "electric blue", "emerald", "erin", "gold", "gray", "green", "harlequin", "indigo", "ivory", "jade", "jungle green", "lavender", "lemon", "lilac", "lime", "magenta", "magenta rose", "maroon", "mauve", "navy blue", "ocher", "olive", "orange", "orange-red", "orchid", "peach", "pear", "periwinkle", "persian blue", "pink", "plum", "prussian blue", "puce", "purple", "raspberry", "red", "red-violet", "rose", "ruby", "salmon", "sangria", "sapphire", "scarlet", "silver", "slate gray", "spring bud", "spring green", "tan", "taupe", "teal", "turquoise", "violet", "viridian", "white", "yellow");
65+
handler.execute();
6366

64-
terminal.println("\nProduct info: " + product);
67+
terminal.println("\nProduct info: " + product);
68+
69+
if(!textIO.newBooleanInputReader().withDefaultValue(true).read("Run again?")) break;
70+
71+
terminal.resetToBookmark("start");
72+
}
6573

66-
textIO.newStringInputReader().withMinLength(0).read("\nPress enter to terminate...");
6774
textIO.dispose();
6875
}
6976
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2018 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+
* http://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.beryx.swing.handler;
17+
18+
import java.io.FileInputStream;
19+
import java.io.FileOutputStream;
20+
import java.io.IOException;
21+
import java.util.*;
22+
import java.util.logging.Logger;
23+
import java.util.stream.Collectors;
24+
25+
public class History {
26+
private static final Logger logger = Logger.getLogger(History.class.getName());
27+
28+
private final String appName;
29+
private final Map<String, List<String>> history = new HashMap<>();
30+
31+
public History(String appName) {
32+
this.appName = appName;
33+
Properties props = new Properties();
34+
try {
35+
props.load(new FileInputStream(getPropFilePath()));
36+
} catch (IOException e) {
37+
logger.fine("History file not found. Initializing empty history.");
38+
}
39+
props.entrySet().forEach(entry -> {
40+
String[] values = entry.getValue().toString().split("\\s*,\\s*");
41+
history.put(entry.getKey().toString(), new ArrayList<>(Arrays.asList(values)));
42+
});
43+
}
44+
45+
public List<String> getValues(String name) {
46+
return history.getOrDefault(name, new ArrayList<>());
47+
}
48+
49+
public void addValue(String name, String value){
50+
history.compute(name, (v,list) -> {
51+
List<String> l = (list == null) ? new ArrayList<>() : list;
52+
l.removeAll(Collections.singleton(value));
53+
l.add(0, value);
54+
return l;
55+
});
56+
}
57+
58+
public void save() {
59+
Properties props = new Properties();
60+
history.entrySet().forEach(entry -> {
61+
props.setProperty(entry.getKey(), entry.getValue().stream().collect(Collectors.joining(", ")));
62+
});
63+
try {
64+
props.store(new FileOutputStream(getPropFilePath()), "List of previous values");
65+
} catch (IOException e) {
66+
throw new RuntimeException(e);
67+
}
68+
}
69+
70+
private String getPropFilePath() {
71+
return System.getProperty("user.home") + "/textio-" + appName + ".properties";
72+
}
73+
}

src/main/java/org/beryx/swing/handler/MarsTerminal.java

+15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
/*
2+
* Copyright 2018 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+
* http://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+
*/
116
package org.beryx.swing.handler;
217

318
import org.beryx.textio.swing.SwingTextTerminal;

src/main/java/org/beryx/swing/handler/SwingHandler.java

+51-12
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,14 @@
3636
import static org.beryx.textio.ReadInterruptionStrategy.Action.ABORT;
3737

3838
public class SwingHandler {
39-
private static final String KEY_STROKE_UP = "pressed UP";
40-
private static final String KEY_STROKE_DOWN = "pressed DOWN";
39+
private static final String KEY_PREV_CHOICE = "pressed DOWN";
40+
private static final String KEY_NEXT_CHOICE = "pressed UP";
41+
private static final String KEY_PREV_HISTORY = "ctrl shift pressed LEFT";
42+
private static final String KEY_NEXT_HISTORY = "ctrl shift pressed RIGHT";
4143

4244
private final TextIO textIO;
4345
private final SwingTextTerminal terminal;
46+
private final History historyStore;
4447
private final Object dataObject;
4548

4649
private final String backKeyStroke;
@@ -50,16 +53,21 @@ public class SwingHandler {
5053
private List<String> choices = new ArrayList<>();
5154
private List<String> filteredChoices = new ArrayList<>();
5255

56+
private String historyInput = "";
57+
private int historyIndex = -1;
58+
private List<String> history= new ArrayList<>();
59+
5360
private final Supplier<StringInputReader> stringInputReaderSupplier;
5461
private final Supplier<IntInputReader> intInputReaderSupplier;
5562
private final Supplier<LongInputReader> longInputReaderSupplier;
5663
private final Supplier<DoubleInputReader> doubleInputReaderSupplier;
5764

5865
private final List<Task<?,?,?>> tasks = new ArrayList<>();
5966

60-
public SwingHandler(TextIO textIO, Object dataObject) {
67+
public SwingHandler(TextIO textIO, String appName, Object dataObject) {
6168
this.textIO = textIO;
6269
this.terminal = (SwingTextTerminal)textIO.getTextTerminal();
70+
this.historyStore = new History(appName);
6371
this.dataObject = dataObject;
6472

6573
this.stringInputReaderSupplier = () -> textIO.newStringInputReader();
@@ -75,7 +83,7 @@ public SwingHandler(TextIO textIO, Object dataObject) {
7583
@Override public void changedUpdate(DocumentEvent e) {choiceIndex = -1;}
7684
});
7785

78-
terminal.registerHandler(KEY_STROKE_UP, t -> {
86+
terminal.registerHandler(KEY_NEXT_CHOICE, t -> {
7987
if(choiceIndex < 0) {
8088
originalInput = terminal.getPartialInput();
8189
filteredChoices = choices.stream()
@@ -90,7 +98,7 @@ public SwingHandler(TextIO textIO, Object dataObject) {
9098
return new ReadHandlerData(ReadInterruptionStrategy.Action.CONTINUE);
9199
});
92100

93-
terminal.registerHandler(KEY_STROKE_DOWN, t -> {
101+
terminal.registerHandler(KEY_PREV_CHOICE, t -> {
94102
if(choiceIndex >= 0) {
95103
int savedChoiceIndex = --choiceIndex;
96104
String text = (choiceIndex < 0) ? originalInput : filteredChoices.get(choiceIndex);
@@ -100,6 +108,26 @@ public SwingHandler(TextIO textIO, Object dataObject) {
100108
return new ReadHandlerData(ReadInterruptionStrategy.Action.CONTINUE);
101109
});
102110

111+
history.addAll(Arrays.asList("Alice", "Bob", "Chloe", "Daisy", "Elaine", "Frank"));
112+
113+
terminal.registerHandler(KEY_NEXT_HISTORY, t -> {
114+
if (historyIndex < history.size() - 1) {
115+
historyIndex++;
116+
String text = history.get(historyIndex);
117+
t.replaceInput(text, false);
118+
}
119+
return new ReadHandlerData(ReadInterruptionStrategy.Action.CONTINUE);
120+
});
121+
122+
terminal.registerHandler(KEY_PREV_HISTORY, t -> {
123+
if (historyIndex >= 0) {
124+
historyIndex--;
125+
String text = (historyIndex < 0) ? historyInput : history.get(historyIndex);
126+
t.replaceInput(text, false);
127+
}
128+
return new ReadHandlerData(ReadInterruptionStrategy.Action.CONTINUE);
129+
});
130+
103131
terminal.registerHandler(backKeyStroke, t -> new ReadHandlerData(ABORT));
104132
}
105133

@@ -113,15 +141,17 @@ public String getBackKeyStroke() {
113141

114142
public class Task<T,B extends Task<T,B, R>, R extends InputReader<T, ?>> implements Runnable {
115143
protected final String prompt;
144+
protected final String key;
116145
protected final Supplier<R> inputReaderSupplier;
117146
protected final Supplier<T> defaultValueSupplier;
118147
protected final Consumer<T> valueSetter;
119148
protected final List<T> choices = new ArrayList<>();
120149
protected boolean constrainedInput;
121150
protected Consumer<R> inputReaderConfigurator;
122151

123-
public Task(String prompt, Supplier<R> inputReaderSupplier, Supplier<T> defaultValueSupplier, Consumer<T> valueSetter) {
152+
public Task(String key, String prompt, Supplier<R> inputReaderSupplier, Supplier<T> defaultValueSupplier, Consumer<T> valueSetter) {
124153
this.prompt = prompt;
154+
this.key = key;
125155
this.inputReaderSupplier = inputReaderSupplier;
126156
this.defaultValueSupplier = defaultValueSupplier;
127157
this.valueSetter = valueSetter;
@@ -130,6 +160,7 @@ public Task(String prompt, Supplier<R> inputReaderSupplier, Supplier<T> defaultV
130160
@Override
131161
public void run() {
132162
setChoices(choices.stream().map(Object::toString).collect(Collectors.toList()));
163+
setHistory(historyStore.getValues(key));
133164
try {
134165
R inputReader = inputReaderSupplier.get();
135166
inputReader.withDefaultValue(defaultValueSupplier.get());
@@ -141,9 +172,12 @@ public void run() {
141172
: Arrays.asList("'" + val + "' is not in the choice list."));
142173

143174
}
144-
valueSetter.accept(inputReader.read(prompt));
175+
T value = inputReader.read(prompt);
176+
historyStore.addValue(key, value.toString());
177+
valueSetter.accept(value);
145178
} finally {
146179
setChoices(Collections.emptyList());
180+
setHistory(Collections.emptyList());
147181
}
148182
}
149183

@@ -170,6 +204,11 @@ private void setChoices(List<String> choices) {
170204
this.choices = choices;
171205
}
172206

207+
private void setHistory(List<String> history) {
208+
this.historyIndex = -1;
209+
this.history = history;
210+
}
211+
173212
private final <T> Supplier<T> getDefaultValueSupplier(String fieldName) {
174213
return () -> getFieldValue(fieldName);
175214
}
@@ -180,12 +219,11 @@ private final <T> Consumer<T> getValueSetter(String fieldName) {
180219

181220
public class StringTask extends Task<String, StringTask, StringInputReader> {
182221
public StringTask(String fieldName, String prompt) {
183-
super(prompt,
222+
super(fieldName, prompt,
184223
stringInputReaderSupplier,
185224
getDefaultValueSupplier(fieldName),
186225
getValueSetter(fieldName));
187226
}
188-
189227
public StringTask addChoices(String... choices) {
190228
this.choices.addAll(Arrays.asList(choices));
191229
return this;
@@ -201,7 +239,7 @@ public StringTask addStringTask(String fieldName, String prompt) {
201239

202240
public class IntTask extends Task<Integer, IntTask, IntInputReader> {
203241
public IntTask(String fieldName, String prompt) {
204-
super(prompt,
242+
super(fieldName, prompt,
205243
intInputReaderSupplier,
206244
getDefaultValueSupplier(fieldName),
207245
getValueSetter(fieldName));
@@ -221,7 +259,7 @@ public IntTask addIntTask(String fieldName, String prompt) {
221259

222260
public class LongTask extends Task<Long, LongTask, LongInputReader> {
223261
public LongTask(String fieldName, String prompt) {
224-
super(prompt,
262+
super(fieldName, prompt,
225263
longInputReaderSupplier,
226264
getDefaultValueSupplier(fieldName),
227265
getValueSetter(fieldName));
@@ -241,7 +279,7 @@ public LongTask addLongTask(String fieldName, String prompt) {
241279

242280
public class DoubleTask extends Task<Double, DoubleTask, DoubleInputReader> {
243281
public DoubleTask(String fieldName, String prompt) {
244-
super(prompt,
282+
super(fieldName, prompt,
245283
doubleInputReaderSupplier,
246284
getDefaultValueSupplier(fieldName),
247285
getValueSetter(fieldName));
@@ -275,6 +313,7 @@ public void execute() {
275313
}
276314
step++;
277315
}
316+
historyStore.save();
278317
}
279318

280319
private Field getField(String fieldName) {

src/main/resources/textio.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ textio.highlight.prompt.bold = true
1212
textio.highlight.input.color = orange
1313
textio.highlight.input.bold = true
1414

15-
textio.pane.width = 760
15+
textio.pane.width = 1000

0 commit comments

Comments
 (0)