Commit | Line | Data |
---|---|---|
a3b510ab NR |
1 | package be.nikiroo.jvcard.tui; |
2 | ||
bcb54330 | 3 | import java.util.ArrayList; |
a3b510ab NR |
4 | import java.util.Arrays; |
5 | import java.util.LinkedList; | |
6 | import java.util.List; | |
7 | ||
296a0b75 | 8 | import be.nikiroo.jvcard.i18n.Trans; |
a3b510ab NR |
9 | import be.nikiroo.jvcard.i18n.Trans.StringId; |
10 | import be.nikiroo.jvcard.tui.KeyAction.Mode; | |
11 | import be.nikiroo.jvcard.tui.UiColors.Element; | |
f04d8b1c | 12 | import be.nikiroo.jvcard.tui.panes.ContactDetails; |
296a0b75 | 13 | import be.nikiroo.jvcard.tui.panes.ContactDetailsRaw; |
fae07ea7 NR |
14 | import be.nikiroo.jvcard.tui.panes.ContactList; |
15 | import be.nikiroo.jvcard.tui.panes.MainContent; | |
a3b510ab NR |
16 | |
17 | import com.googlecode.lanterna.TerminalSize; | |
18 | import com.googlecode.lanterna.gui2.BasicWindow; | |
19 | import com.googlecode.lanterna.gui2.BorderLayout; | |
20 | import com.googlecode.lanterna.gui2.Direction; | |
21 | import com.googlecode.lanterna.gui2.Interactable; | |
22 | import com.googlecode.lanterna.gui2.Label; | |
23 | import com.googlecode.lanterna.gui2.LinearLayout; | |
24 | import com.googlecode.lanterna.gui2.Panel; | |
25 | import com.googlecode.lanterna.gui2.TextBox; | |
a3b510ab NR |
26 | import com.googlecode.lanterna.gui2.Window; |
27 | import com.googlecode.lanterna.input.KeyStroke; | |
28 | import com.googlecode.lanterna.input.KeyType; | |
29 | ||
30 | /** | |
0b0b2b0f NR |
31 | * This is the main "window" of the program. It can show up to one |
32 | * {@link MainContent} at any one time, but keeps a stack of contents. | |
a3b510ab NR |
33 | * |
34 | * @author niki | |
35 | * | |
36 | */ | |
f720df72 | 37 | |
a3b510ab NR |
38 | public class MainWindow extends BasicWindow { |
39 | private List<KeyAction> defaultActions = new LinkedList<KeyAction>(); | |
40 | private List<KeyAction> actions = new LinkedList<KeyAction>(); | |
0b0b2b0f | 41 | private List<MainContent> contentStack = new LinkedList<MainContent>(); |
f720df72 | 42 | private UserQuestion userQuestion; |
bcb54330 | 43 | private String titleCache; |
a3b510ab NR |
44 | private Panel titlePanel; |
45 | private Panel mainPanel; | |
46 | private Panel contentPanel; | |
47 | private Panel actionPanel; | |
48 | private Panel messagePanel; | |
49 | private TextBox text; | |
50 | ||
f720df72 NR |
51 | /** |
52 | * Information about a question to ask the user and its answer. | |
53 | * | |
54 | * @author niki | |
55 | * | |
56 | */ | |
57 | private class UserQuestion { | |
58 | private boolean oneKeyAnswer; | |
59 | private KeyAction action; | |
60 | private String answer; | |
61 | ||
62 | /** | |
63 | * Create a new {@link UserQuestion}. | |
64 | * | |
65 | * @param action | |
66 | * the action that triggered the question | |
67 | * @param oneKeyAnswer | |
68 | * TRUE if we expect a one-key answer | |
69 | */ | |
70 | public UserQuestion(KeyAction action, boolean oneKeyAnswer) { | |
71 | this.action = action; | |
72 | this.oneKeyAnswer = oneKeyAnswer; | |
73 | } | |
74 | ||
75 | /** | |
76 | * Return the {@link KeyAction} that triggered the question. | |
77 | * | |
78 | * @return the {@link KeyAction} | |
79 | */ | |
80 | public KeyAction getAction() { | |
81 | return action; | |
82 | } | |
83 | ||
84 | /** | |
85 | * Check if a one-key answer is expected. | |
86 | * | |
87 | * @return TRUE if a one-key answer is expected | |
88 | */ | |
89 | public boolean isOneKeyAnswer() { | |
90 | return oneKeyAnswer; | |
91 | } | |
92 | ||
93 | /** | |
94 | * Return the user answer. | |
95 | * | |
96 | * @return the user answer | |
97 | */ | |
98 | public String getAnswer() { | |
99 | return answer; | |
100 | } | |
101 | ||
102 | /** | |
103 | * Set the user answer. | |
104 | * | |
105 | * @param answer | |
106 | * the new answer | |
107 | */ | |
108 | public void setAnswer(String answer) { | |
109 | this.answer = answer; | |
110 | } | |
111 | } | |
112 | ||
0b0b2b0f NR |
113 | /** |
114 | * Create a new, empty window. | |
115 | */ | |
a3b510ab NR |
116 | public MainWindow() { |
117 | this(null); | |
118 | } | |
119 | ||
0b0b2b0f NR |
120 | /** |
121 | * Create a new window hosting the given content. | |
122 | * | |
123 | * @param content | |
124 | * the content to host | |
125 | */ | |
a3b510ab NR |
126 | public MainWindow(MainContent content) { |
127 | super(content == null ? "" : content.getTitle()); | |
128 | ||
129 | setHints(Arrays.asList(Window.Hint.FULL_SCREEN, | |
130 | Window.Hint.NO_DECORATIONS, Window.Hint.FIT_TERMINAL_WINDOW)); | |
131 | ||
132 | defaultActions.add(new KeyAction(Mode.BACK, 'q', | |
133 | StringId.KEY_ACTION_BACK)); | |
134 | defaultActions.add(new KeyAction(Mode.BACK, KeyType.Escape, | |
135 | StringId.NULL)); | |
136 | defaultActions.add(new KeyAction(Mode.HELP, 'h', | |
137 | StringId.KEY_ACTION_HELP)); | |
138 | defaultActions.add(new KeyAction(Mode.HELP, KeyType.F1, StringId.NULL)); | |
139 | ||
140 | actionPanel = new Panel(); | |
141 | contentPanel = new Panel(); | |
142 | mainPanel = new Panel(); | |
143 | messagePanel = new Panel(); | |
144 | titlePanel = new Panel(); | |
145 | ||
146 | Panel actionMessagePanel = new Panel(); | |
147 | ||
148 | LinearLayout llayout = new LinearLayout(Direction.HORIZONTAL); | |
149 | llayout.setSpacing(0); | |
150 | actionPanel.setLayoutManager(llayout); | |
151 | ||
0b0b2b0f NR |
152 | BorderLayout blayout = new BorderLayout(); |
153 | titlePanel.setLayoutManager(blayout); | |
a3b510ab NR |
154 | |
155 | llayout = new LinearLayout(Direction.VERTICAL); | |
156 | llayout.setSpacing(0); | |
157 | messagePanel.setLayoutManager(llayout); | |
158 | ||
0b0b2b0f | 159 | blayout = new BorderLayout(); |
a3b510ab NR |
160 | mainPanel.setLayoutManager(blayout); |
161 | ||
162 | blayout = new BorderLayout(); | |
163 | contentPanel.setLayoutManager(blayout); | |
164 | ||
165 | blayout = new BorderLayout(); | |
166 | actionMessagePanel.setLayoutManager(blayout); | |
167 | ||
168 | actionMessagePanel | |
169 | .addComponent(messagePanel, BorderLayout.Location.TOP); | |
170 | actionMessagePanel.addComponent(actionPanel, | |
171 | BorderLayout.Location.CENTER); | |
172 | ||
173 | mainPanel.addComponent(titlePanel, BorderLayout.Location.TOP); | |
174 | mainPanel.addComponent(contentPanel, BorderLayout.Location.CENTER); | |
175 | mainPanel | |
176 | .addComponent(actionMessagePanel, BorderLayout.Location.BOTTOM); | |
177 | ||
178 | pushContent(content); | |
179 | ||
180 | setComponent(mainPanel); | |
181 | } | |
182 | ||
0b0b2b0f NR |
183 | /** |
184 | * "push" some content to the window stack. | |
185 | * | |
186 | * @param content | |
187 | * the new top-of-the-stack content | |
188 | */ | |
a3b510ab NR |
189 | public void pushContent(MainContent content) { |
190 | List<KeyAction> actions = null; | |
a3b510ab NR |
191 | |
192 | contentPanel.removeAllComponents(); | |
193 | if (content != null) { | |
a3b510ab NR |
194 | actions = content.getKeyBindings(); |
195 | contentPanel.addComponent(content, BorderLayout.Location.CENTER); | |
0b0b2b0f | 196 | this.contentStack.add(content); |
9c8baf0c NR |
197 | |
198 | Interactable focus = content.nextFocus(null); | |
199 | if (focus != null) | |
200 | focus.takeFocus(); | |
a3b510ab NR |
201 | } |
202 | ||
bcb54330 | 203 | setTitle(); |
0b0b2b0f | 204 | setActions(actions, true); |
a3b510ab NR |
205 | } |
206 | ||
0b0b2b0f NR |
207 | /** |
208 | * "pop" the latest, top-of-the-stack content from the window stack. | |
209 | * | |
210 | * @return the removed content if any | |
211 | */ | |
9c8baf0c NR |
212 | public MainContent popContent() { |
213 | MainContent removed = null; | |
214 | MainContent prev = null; | |
9c8baf0c | 215 | |
0b0b2b0f NR |
216 | MainContent content = getContent(); |
217 | if (content != null) | |
218 | removed = contentStack.remove(contentStack.size() - 1); | |
9c8baf0c | 219 | |
0b0b2b0f NR |
220 | if (contentStack.size() > 0) |
221 | prev = contentStack.remove(contentStack.size() - 1); | |
a3b510ab | 222 | |
0b0b2b0f | 223 | pushContent(prev); |
a3b510ab | 224 | |
0b0b2b0f | 225 | return removed; |
a3b510ab NR |
226 | } |
227 | ||
9c8baf0c NR |
228 | /** |
229 | * Show the given message on screen. It will disappear at the next action. | |
230 | * | |
231 | * @param mess | |
232 | * the message to display | |
233 | * @param error | |
234 | * TRUE for an error message, FALSE for an information message | |
bcb54330 NR |
235 | * |
236 | * @return TRUE if changes were performed | |
9c8baf0c | 237 | */ |
bcb54330 NR |
238 | public boolean setMessage(String mess, boolean error) { |
239 | if (mess != null || messagePanel.getChildCount() > 0) { | |
240 | messagePanel.removeAllComponents(); | |
241 | if (mess != null) { | |
242 | Element element = (error ? UiColors.Element.LINE_MESSAGE_ERR | |
243 | : UiColors.Element.LINE_MESSAGE); | |
244 | Label lbl = element.createLabel(" " + mess + " "); | |
245 | messagePanel.addComponent(lbl, LinearLayout | |
246 | .createLayoutData(LinearLayout.Alignment.Center)); | |
247 | } | |
248 | return true; | |
249 | } | |
250 | ||
251 | return false; | |
252 | } | |
253 | ||
254 | /** | |
255 | * Show a question to the user and switch to "ask for answer" mode see | |
256 | * {@link MainWindow#handleQuestion}. The user will be asked to enter some | |
257 | * answer and confirm with ENTER. | |
258 | * | |
296a0b75 NR |
259 | * @param action |
260 | * the related action | |
bcb54330 NR |
261 | * @param question |
262 | * the question to ask | |
263 | * @param initial | |
264 | * the initial answer if any (to be edited by the user) | |
265 | */ | |
296a0b75 NR |
266 | public void setQuestion(KeyAction action, String question, String initial) { |
267 | setQuestion(action, question, initial, false); | |
bcb54330 NR |
268 | } |
269 | ||
270 | /** | |
271 | * Show a question to the user and switch to "ask for answer" mode see | |
272 | * {@link MainWindow#handleQuestion}. The user will be asked to hit one key | |
273 | * as an answer. | |
274 | * | |
296a0b75 NR |
275 | * @param action |
276 | * the related action | |
bcb54330 NR |
277 | * @param question |
278 | * the question to ask | |
279 | */ | |
296a0b75 NR |
280 | public void setQuestion(KeyAction action, String question) { |
281 | setQuestion(action, question, null, true); | |
9c8baf0c NR |
282 | } |
283 | ||
0b0b2b0f NR |
284 | /** |
285 | * Show a question to the user and switch to "ask for answer" mode see | |
286 | * {@link MainWindow#handleQuestion}. | |
287 | * | |
296a0b75 NR |
288 | * @param action |
289 | * the related action | |
bcb54330 NR |
290 | * @param question |
291 | * the question to ask | |
292 | * @param initial | |
293 | * the initial answer if any (to be edited by the user) | |
0b0b2b0f NR |
294 | * @param oneKey |
295 | * TRUE for a one-key answer, FALSE for a text answer validated | |
296 | * by ENTER | |
297 | */ | |
296a0b75 | 298 | private void setQuestion(KeyAction action, String question, String initial, |
bcb54330 | 299 | boolean oneKey) { |
f720df72 | 300 | userQuestion = new UserQuestion(action, oneKey); |
0b0b2b0f | 301 | |
9c8baf0c | 302 | messagePanel.removeAllComponents(); |
9c8baf0c | 303 | |
0b0b2b0f NR |
304 | Panel hpanel = new Panel(); |
305 | LinearLayout llayout = new LinearLayout(Direction.HORIZONTAL); | |
306 | llayout.setSpacing(0); | |
307 | hpanel.setLayoutManager(llayout); | |
9c8baf0c | 308 | |
0b0b2b0f | 309 | Label lbl = UiColors.Element.LINE_MESSAGE_QUESTION.createLabel(" " |
bcb54330 | 310 | + question + " "); |
296a0b75 NR |
311 | text = new TextBox(new TerminalSize(getSize().getColumns() |
312 | - lbl.getSize().getColumns(), 1)); | |
f720df72 NR |
313 | |
314 | if (initial != null) { | |
315 | // add all chars one by one so the caret is at the end | |
316 | for (int index = 0; index < initial.length(); index++) { | |
317 | text.handleInput(new KeyStroke(initial.charAt(index), false, | |
318 | false)); | |
319 | } | |
320 | } | |
9c8baf0c | 321 | |
bcb54330 NR |
322 | hpanel.addComponent(lbl, |
323 | LinearLayout.createLayoutData(LinearLayout.Alignment.Beginning)); | |
324 | hpanel.addComponent(text, | |
325 | LinearLayout.createLayoutData(LinearLayout.Alignment.Fill)); | |
9c8baf0c | 326 | |
bcb54330 NR |
327 | messagePanel |
328 | .addComponent(hpanel, LinearLayout | |
329 | .createLayoutData(LinearLayout.Alignment.Beginning)); | |
9c8baf0c | 330 | |
0b0b2b0f | 331 | text.takeFocus(); |
9c8baf0c NR |
332 | } |
333 | ||
296a0b75 NR |
334 | /** |
335 | * Refresh the window and the empty-space handling. You should call this | |
336 | * method when the window size changed. | |
337 | * | |
338 | * @param size | |
339 | * the new size of the window | |
340 | */ | |
341 | public void refresh(TerminalSize size) { | |
342 | if (size == null) | |
343 | return; | |
344 | ||
345 | if (getSize() == null || !getSize().equals(size)) | |
346 | setSize(size); | |
347 | ||
348 | setTitle(); | |
349 | ||
350 | if (actions != null) | |
351 | setActions(new ArrayList<KeyAction>(actions), false); | |
352 | ||
353 | invalidate(); | |
354 | } | |
355 | ||
356 | @Override | |
357 | public void invalidate() { | |
358 | super.invalidate(); | |
359 | for (MainContent content : contentStack) { | |
360 | content.invalidate(); | |
361 | } | |
362 | } | |
363 | ||
364 | @Override | |
365 | public boolean handleInput(KeyStroke key) { | |
366 | boolean handled = false; | |
367 | ||
f720df72 NR |
368 | if (userQuestion != null) { |
369 | handled = handleQuestion(userQuestion, key); | |
370 | if (handled) { | |
371 | if (userQuestion.getAnswer() != null) { | |
372 | handleAction(userQuestion.getAction(), | |
373 | userQuestion.getAnswer()); | |
296a0b75 | 374 | |
f720df72 NR |
375 | userQuestion = null; |
376 | } | |
296a0b75 NR |
377 | } |
378 | } else { | |
379 | handled = handleKey(key); | |
380 | } | |
381 | ||
f720df72 | 382 | if (!handled) { |
296a0b75 | 383 | handled = super.handleInput(key); |
f720df72 | 384 | } |
296a0b75 NR |
385 | |
386 | return handled; | |
387 | } | |
388 | ||
bcb54330 NR |
389 | /** |
390 | * Actually set the title <b>inside</b> the window. Will also call | |
296a0b75 | 391 | * {@link BasicWindow#setTitle} with the computed parameters. |
bcb54330 NR |
392 | */ |
393 | private void setTitle() { | |
0b0b2b0f NR |
394 | String prefix = " " + Main.APPLICATION_TITLE + " (version " |
395 | + Main.APPLICATION_VERSION + ")"; | |
396 | ||
bcb54330 | 397 | String title = null; |
0b0b2b0f | 398 | int count = -1; |
bcb54330 | 399 | |
0b0b2b0f | 400 | MainContent content = getContent(); |
bcb54330 NR |
401 | if (content != null) { |
402 | title = content.getTitle(); | |
0b0b2b0f | 403 | count = content.getCount(); |
bcb54330 NR |
404 | } |
405 | ||
406 | if (title == null) | |
407 | title = ""; | |
0b0b2b0f | 408 | |
bcb54330 | 409 | if (title.length() > 0) { |
0b0b2b0f | 410 | prefix = prefix + ": "; |
296a0b75 NR |
411 | title = StringUtils.sanitize(title, UiColors.getInstance() |
412 | .isUnicode()); | |
0b0b2b0f NR |
413 | } |
414 | ||
bcb54330 NR |
415 | String countStr = ""; |
416 | if (count > -1) { | |
417 | countStr = "[" + count + "]"; | |
418 | } | |
419 | ||
296a0b75 NR |
420 | int width = -1; |
421 | if (getSize() != null) { | |
422 | width = getSize().getColumns(); | |
423 | } | |
424 | ||
bcb54330 NR |
425 | if (width > 0) { |
426 | int padding = width - prefix.length() - title.length() | |
427 | - countStr.length(); | |
428 | if (padding > 0) { | |
429 | if (title.length() > 0) | |
430 | title = StringUtils.padString(title, title.length() | |
431 | + padding); | |
432 | else | |
433 | prefix = StringUtils.padString(prefix, prefix.length() | |
434 | + padding); | |
435 | } | |
0b0b2b0f | 436 | } |
0b0b2b0f | 437 | |
bcb54330 NR |
438 | String titleCache = prefix + title + count; |
439 | if (!titleCache.equals(this.titleCache)) { | |
440 | super.setTitle(prefix); | |
0b0b2b0f NR |
441 | |
442 | Label lblPrefix = new Label(prefix); | |
443 | UiColors.Element.TITLE_MAIN.themeLabel(lblPrefix); | |
444 | ||
445 | Label lblTitle = null; | |
bcb54330 | 446 | if (title.length() > 0) { |
0b0b2b0f NR |
447 | lblTitle = new Label(title); |
448 | UiColors.Element.TITLE_VARIABLE.themeLabel(lblTitle); | |
449 | } | |
450 | ||
451 | Label lblCount = null; | |
bcb54330 NR |
452 | if (countStr != null) { |
453 | lblCount = new Label(countStr); | |
0b0b2b0f NR |
454 | UiColors.Element.TITLE_COUNT.themeLabel(lblCount); |
455 | } | |
456 | ||
457 | titlePanel.removeAllComponents(); | |
a3b510ab | 458 | |
0b0b2b0f NR |
459 | titlePanel.addComponent(lblPrefix, BorderLayout.Location.LEFT); |
460 | if (lblTitle != null) | |
461 | titlePanel.addComponent(lblTitle, BorderLayout.Location.CENTER); | |
462 | if (lblCount != null) | |
463 | titlePanel.addComponent(lblCount, BorderLayout.Location.RIGHT); | |
0b0b2b0f NR |
464 | } |
465 | } | |
466 | ||
467 | /** | |
468 | * Return the current {@link MainContent} from the stack if any. | |
469 | * | |
470 | * @return the current {@link MainContent} | |
471 | */ | |
472 | private MainContent getContent() { | |
473 | if (contentStack.size() > 0) { | |
474 | return contentStack.get(contentStack.size() - 1); | |
475 | } | |
476 | ||
477 | return null; | |
478 | } | |
479 | ||
480 | /** | |
481 | * Update the list of actions and refresh the action panel. | |
482 | * | |
483 | * @param actions | |
484 | * the list of actions to support | |
485 | * @param enableDefaultactions | |
486 | * TRUE to enable the default actions | |
487 | */ | |
488 | private void setActions(List<KeyAction> actions, | |
489 | boolean enableDefaultactions) { | |
a3b510ab | 490 | this.actions.clear(); |
9c8baf0c | 491 | |
a3b510ab NR |
492 | if (enableDefaultactions) |
493 | this.actions.addAll(defaultActions); | |
494 | ||
495 | if (actions != null) | |
496 | this.actions.addAll(actions); | |
497 | ||
498 | actionPanel.removeAllComponents(); | |
499 | for (KeyAction action : this.actions) { | |
500 | String trans = " " + action.getStringId().trans() + " "; | |
501 | ||
502 | if (" ".equals(trans)) | |
503 | continue; | |
504 | ||
296a0b75 | 505 | String keyTrans = Trans.getInstance().trans(action.getKey()); |
a3b510ab NR |
506 | |
507 | Panel kPane = new Panel(); | |
508 | LinearLayout layout = new LinearLayout(Direction.HORIZONTAL); | |
509 | layout.setSpacing(0); | |
510 | kPane.setLayoutManager(layout); | |
511 | ||
512 | kPane.addComponent(UiColors.Element.ACTION_KEY | |
513 | .createLabel(keyTrans)); | |
514 | kPane.addComponent(UiColors.Element.ACTION_DESC.createLabel(trans)); | |
515 | ||
516 | actionPanel.addComponent(kPane); | |
517 | } | |
bcb54330 NR |
518 | |
519 | // fill with "desc" colour | |
296a0b75 NR |
520 | int width = -1; |
521 | if (getSize() != null) { | |
522 | width = getSize().getColumns(); | |
523 | } | |
524 | ||
bcb54330 NR |
525 | if (width > 0) { |
526 | actionPanel.addComponent(UiColors.Element.ACTION_DESC | |
527 | .createLabel(StringUtils.padString("", width))); | |
bcb54330 | 528 | } |
a3b510ab NR |
529 | } |
530 | ||
0b0b2b0f NR |
531 | /** |
532 | * Handle user input when in "ask for question" mode (see | |
f720df72 | 533 | * {@link MainWindow#userQuestion}). |
0b0b2b0f | 534 | * |
f720df72 NR |
535 | * @param userQuestion |
536 | * the question data | |
0b0b2b0f NR |
537 | * @param key |
538 | * the key that has been pressed by the user | |
539 | * | |
f720df72 | 540 | * @return TRUE if the {@link KeyStroke} was handled |
0b0b2b0f | 541 | */ |
f720df72 NR |
542 | private boolean handleQuestion(UserQuestion userQuestion, KeyStroke key) { |
543 | userQuestion.setAnswer(null); | |
ae22c247 | 544 | |
f720df72 NR |
545 | if (userQuestion.isOneKeyAnswer()) { |
546 | userQuestion.setAnswer("" + key.getCharacter()); | |
a3b510ab | 547 | } else { |
f720df72 NR |
548 | // ^h == Backspace |
549 | if (key.isCtrlDown() && key.getCharacter() == 'h') { | |
550 | key = new KeyStroke(KeyType.Backspace); | |
551 | } | |
552 | ||
553 | switch (key.getKeyType()) { | |
554 | case Enter: | |
a3b510ab | 555 | if (text != null) |
f720df72 | 556 | userQuestion.setAnswer(text.getText()); |
a3b510ab | 557 | else |
f720df72 NR |
558 | userQuestion.setAnswer(""); |
559 | break; | |
560 | case Backspace: | |
561 | int pos = text.getCaretPosition().getColumn(); | |
562 | if (pos > 0) { | |
563 | String current = text.getText(); | |
564 | // force caret one space before: | |
565 | text.setText(current.substring(0, pos - 1)); | |
566 | // re-add full text: | |
567 | text.setText(current.substring(0, pos - 1) | |
568 | + current.substring(pos)); | |
569 | } | |
570 | return true; | |
571 | default: | |
572 | // Do nothing (continue entering text) | |
573 | break; | |
a3b510ab NR |
574 | } |
575 | } | |
576 | ||
f720df72 | 577 | if (userQuestion.getAnswer() != null) { |
a3b510ab | 578 | Interactable focus = null; |
0b0b2b0f NR |
579 | MainContent content = getContent(); |
580 | if (content != null) | |
581 | focus = content.nextFocus(null); | |
a3b510ab | 582 | |
fae07ea7 | 583 | focus.takeFocus(); |
f720df72 NR |
584 | |
585 | return true; | |
a3b510ab NR |
586 | } |
587 | ||
f720df72 | 588 | return false; |
a3b510ab NR |
589 | } |
590 | ||
0b0b2b0f NR |
591 | /** |
592 | * Handle the input in case of "normal" (not "ask for answer") mode. | |
593 | * | |
594 | * @param key | |
595 | * the key that was pressed | |
596 | * @param answer | |
597 | * the answer given for this key | |
598 | * | |
bcb54330 | 599 | * @return if the window handled the input |
0b0b2b0f | 600 | */ |
296a0b75 | 601 | private boolean handleKey(KeyStroke key) { |
a3b510ab NR |
602 | boolean handled = false; |
603 | ||
296a0b75 NR |
604 | if (setMessage(null, false)) |
605 | return true; | |
a3b510ab | 606 | |
0b0b2b0f NR |
607 | for (KeyAction action : actions) { |
608 | if (!action.match(key)) | |
609 | continue; | |
a3b510ab | 610 | |
0b0b2b0f NR |
611 | handled = true; |
612 | ||
613 | if (action.onAction()) { | |
296a0b75 NR |
614 | handleAction(action, null); |
615 | } | |
0b0b2b0f | 616 | |
296a0b75 NR |
617 | break; |
618 | } | |
0b0b2b0f | 619 | |
296a0b75 NR |
620 | return handled; |
621 | } | |
622 | ||
623 | /** | |
624 | * Handle the input in case of "normal" (not "ask for answer") mode. | |
625 | * | |
626 | * @param key | |
627 | * the key that was pressed | |
628 | * @param answer | |
629 | * the answer given for this key | |
630 | * | |
631 | * @return if the window handled the input | |
632 | */ | |
633 | private void handleAction(KeyAction action, String answer) { | |
634 | MainContent content = getContent(); | |
635 | ||
296a0b75 NR |
636 | switch (action.getMode()) { |
637 | case MOVE: | |
638 | int x = 0; | |
639 | int y = 0; | |
640 | ||
641 | if (action.getKey().getKeyType() == KeyType.ArrowUp) | |
642 | x = -1; | |
643 | if (action.getKey().getKeyType() == KeyType.ArrowDown) | |
644 | x = 1; | |
645 | if (action.getKey().getKeyType() == KeyType.ArrowLeft) | |
646 | y = -1; | |
647 | if (action.getKey().getKeyType() == KeyType.ArrowRight) | |
648 | y = 1; | |
649 | ||
650 | if (content != null) { | |
651 | String err = content.move(x, y); | |
652 | if (err != null) | |
653 | setMessage(err, true); | |
654 | } | |
655 | ||
656 | break; | |
657 | // mode with windows: | |
658 | case CONTACT_LIST: | |
ae22c247 NR |
659 | if (action.getCard() != null) { |
660 | pushContent(new ContactList(action.getCard())); | |
296a0b75 NR |
661 | } |
662 | break; | |
663 | case CONTACT_DETAILS: | |
ae22c247 NR |
664 | if (action.getContact() != null) { |
665 | pushContent(new ContactDetails(action.getContact())); | |
f04d8b1c NR |
666 | } |
667 | break; | |
668 | case CONTACT_DETAILS_RAW: | |
ae22c247 NR |
669 | if (action.getContact() != null) { |
670 | pushContent(new ContactDetailsRaw(action.getContact())); | |
296a0b75 NR |
671 | } |
672 | break; | |
673 | // mode interpreted by MainWindow: | |
674 | case HELP: | |
675 | // TODO | |
f720df72 | 676 | setMessage("Help! I need somebody! Help!", false); |
296a0b75 NR |
677 | |
678 | break; | |
679 | case BACK: | |
680 | String warning = content.getExitWarning(); | |
681 | if (warning != null) { | |
682 | if (answer == null) { | |
683 | setQuestion(action, warning); | |
684 | } else { | |
685 | setMessage(null, false); | |
686 | if (answer.equalsIgnoreCase("y")) { | |
bcb54330 | 687 | popContent(); |
a3b510ab | 688 | } |
296a0b75 NR |
689 | } |
690 | } else { | |
691 | popContent(); | |
692 | } | |
0b0b2b0f | 693 | |
296a0b75 NR |
694 | if (contentStack.size() == 0) { |
695 | close(); | |
696 | } | |
bcb54330 | 697 | |
296a0b75 NR |
698 | break; |
699 | // action modes: | |
ae22c247 | 700 | case ASK_USER: |
296a0b75 | 701 | if (answer == null) { |
ae22c247 NR |
702 | setQuestion(action, action.getQuestion(), |
703 | action.getDefaultAnswer()); | |
296a0b75 | 704 | } else { |
ae22c247 NR |
705 | setMessage(action.callback(answer), true); |
706 | content.refreshData(); | |
707 | invalidate(); | |
708 | setTitle(); | |
296a0b75 NR |
709 | } |
710 | break; | |
ae22c247 | 711 | case ASK_USER_KEY: |
296a0b75 | 712 | if (answer == null) { |
ae22c247 | 713 | setQuestion(action, action.getQuestion()); |
296a0b75 | 714 | } else { |
ae22c247 NR |
715 | setMessage(action.callback(answer), true); |
716 | content.refreshData(); | |
717 | invalidate(); | |
718 | setTitle(); | |
0b0b2b0f | 719 | } |
296a0b75 NR |
720 | break; |
721 | default: | |
722 | case NONE: | |
0b0b2b0f NR |
723 | break; |
724 | } | |
a3b510ab NR |
725 | } |
726 | } |