13c6b3e238a4e021873d2a4694a667b023137eb8
1 package be
.nikiroo
.jvcard
.tui
;
3 import java
.io
.IOException
;
4 import java
.util
.ArrayList
;
5 import java
.util
.Arrays
;
6 import java
.util
.LinkedList
;
9 import be
.nikiroo
.jvcard
.Card
;
10 import be
.nikiroo
.jvcard
.Contact
;
11 import be
.nikiroo
.jvcard
.Data
;
12 import be
.nikiroo
.jvcard
.i18n
.Trans
;
13 import be
.nikiroo
.jvcard
.i18n
.Trans
.StringId
;
14 import be
.nikiroo
.jvcard
.tui
.KeyAction
.Mode
;
15 import be
.nikiroo
.jvcard
.tui
.UiColors
.Element
;
16 import be
.nikiroo
.jvcard
.tui
.panes
.ContactDetailsRaw
;
17 import be
.nikiroo
.jvcard
.tui
.panes
.ContactList
;
18 import be
.nikiroo
.jvcard
.tui
.panes
.MainContent
;
20 import com
.googlecode
.lanterna
.TerminalSize
;
21 import com
.googlecode
.lanterna
.gui2
.BasicWindow
;
22 import com
.googlecode
.lanterna
.gui2
.BorderLayout
;
23 import com
.googlecode
.lanterna
.gui2
.Direction
;
24 import com
.googlecode
.lanterna
.gui2
.Interactable
;
25 import com
.googlecode
.lanterna
.gui2
.Label
;
26 import com
.googlecode
.lanterna
.gui2
.LinearLayout
;
27 import com
.googlecode
.lanterna
.gui2
.Panel
;
28 import com
.googlecode
.lanterna
.gui2
.TextBox
;
29 import com
.googlecode
.lanterna
.gui2
.Window
;
30 import com
.googlecode
.lanterna
.input
.KeyStroke
;
31 import com
.googlecode
.lanterna
.input
.KeyType
;
34 * This is the main "window" of the program. It can show up to one
35 * {@link MainContent} at any one time, but keeps a stack of contents.
40 public class MainWindow
extends BasicWindow
{
41 private List
<KeyAction
> defaultActions
= new LinkedList
<KeyAction
>();
42 private List
<KeyAction
> actions
= new LinkedList
<KeyAction
>();
43 private List
<MainContent
> contentStack
= new LinkedList
<MainContent
>();
44 private boolean waitForOneKeyAnswer
;
45 private KeyAction questionAction
;
46 private String titleCache
;
47 private Panel titlePanel
;
48 private Panel mainPanel
;
49 private Panel contentPanel
;
50 private Panel actionPanel
;
51 private Panel messagePanel
;
55 * Create a new, empty window.
62 * Create a new window hosting the given content.
67 public MainWindow(MainContent content
) {
68 super(content
== null ?
"" : content
.getTitle());
70 setHints(Arrays
.asList(Window
.Hint
.FULL_SCREEN
,
71 Window
.Hint
.NO_DECORATIONS
, Window
.Hint
.FIT_TERMINAL_WINDOW
));
73 defaultActions
.add(new KeyAction(Mode
.BACK
, 'q',
74 StringId
.KEY_ACTION_BACK
));
75 defaultActions
.add(new KeyAction(Mode
.BACK
, KeyType
.Escape
,
77 defaultActions
.add(new KeyAction(Mode
.HELP
, 'h',
78 StringId
.KEY_ACTION_HELP
));
79 defaultActions
.add(new KeyAction(Mode
.HELP
, KeyType
.F1
, StringId
.NULL
));
81 actionPanel
= new Panel();
82 contentPanel
= new Panel();
83 mainPanel
= new Panel();
84 messagePanel
= new Panel();
85 titlePanel
= new Panel();
87 Panel actionMessagePanel
= new Panel();
89 LinearLayout llayout
= new LinearLayout(Direction
.HORIZONTAL
);
90 llayout
.setSpacing(0);
91 actionPanel
.setLayoutManager(llayout
);
93 BorderLayout blayout
= new BorderLayout();
94 titlePanel
.setLayoutManager(blayout
);
96 llayout
= new LinearLayout(Direction
.VERTICAL
);
97 llayout
.setSpacing(0);
98 messagePanel
.setLayoutManager(llayout
);
100 blayout
= new BorderLayout();
101 mainPanel
.setLayoutManager(blayout
);
103 blayout
= new BorderLayout();
104 contentPanel
.setLayoutManager(blayout
);
106 blayout
= new BorderLayout();
107 actionMessagePanel
.setLayoutManager(blayout
);
110 .addComponent(messagePanel
, BorderLayout
.Location
.TOP
);
111 actionMessagePanel
.addComponent(actionPanel
,
112 BorderLayout
.Location
.CENTER
);
114 mainPanel
.addComponent(titlePanel
, BorderLayout
.Location
.TOP
);
115 mainPanel
.addComponent(contentPanel
, BorderLayout
.Location
.CENTER
);
117 .addComponent(actionMessagePanel
, BorderLayout
.Location
.BOTTOM
);
119 pushContent(content
);
121 setComponent(mainPanel
);
125 * "push" some content to the window stack.
128 * the new top-of-the-stack content
130 public void pushContent(MainContent content
) {
131 List
<KeyAction
> actions
= null;
133 contentPanel
.removeAllComponents();
134 if (content
!= null) {
135 actions
= content
.getKeyBindings();
136 contentPanel
.addComponent(content
, BorderLayout
.Location
.CENTER
);
137 this.contentStack
.add(content
);
139 Interactable focus
= content
.nextFocus(null);
145 setActions(actions
, true);
149 * "pop" the latest, top-of-the-stack content from the window stack.
151 * @return the removed content if any
153 public MainContent
popContent() {
154 MainContent removed
= null;
155 MainContent prev
= null;
157 MainContent content
= getContent();
159 removed
= contentStack
.remove(contentStack
.size() - 1);
161 if (contentStack
.size() > 0)
162 prev
= contentStack
.remove(contentStack
.size() - 1);
170 * Show the given message on screen. It will disappear at the next action.
173 * the message to display
175 * TRUE for an error message, FALSE for an information message
177 * @return TRUE if changes were performed
179 public boolean setMessage(String mess
, boolean error
) {
180 if (mess
!= null || messagePanel
.getChildCount() > 0) {
181 messagePanel
.removeAllComponents();
183 Element element
= (error ? UiColors
.Element
.LINE_MESSAGE_ERR
184 : UiColors
.Element
.LINE_MESSAGE
);
185 Label lbl
= element
.createLabel(" " + mess
+ " ");
186 messagePanel
.addComponent(lbl
, LinearLayout
187 .createLayoutData(LinearLayout
.Alignment
.Center
));
196 * Show a question to the user and switch to "ask for answer" mode see
197 * {@link MainWindow#handleQuestion}. The user will be asked to enter some
198 * answer and confirm with ENTER.
203 * the question to ask
205 * the initial answer if any (to be edited by the user)
207 public void setQuestion(KeyAction action
, String question
, String initial
) {
208 setQuestion(action
, question
, initial
, false);
212 * Show a question to the user and switch to "ask for answer" mode see
213 * {@link MainWindow#handleQuestion}. The user will be asked to hit one key
219 * the question to ask
221 public void setQuestion(KeyAction action
, String question
) {
222 setQuestion(action
, question
, null, true);
226 * Show a question to the user and switch to "ask for answer" mode see
227 * {@link MainWindow#handleQuestion}.
232 * the question to ask
234 * the initial answer if any (to be edited by the user)
236 * TRUE for a one-key answer, FALSE for a text answer validated
239 private void setQuestion(KeyAction action
, String question
, String initial
,
241 questionAction
= action
;
242 waitForOneKeyAnswer
= oneKey
;
244 messagePanel
.removeAllComponents();
246 Panel hpanel
= new Panel();
247 LinearLayout llayout
= new LinearLayout(Direction
.HORIZONTAL
);
248 llayout
.setSpacing(0);
249 hpanel
.setLayoutManager(llayout
);
251 Label lbl
= UiColors
.Element
.LINE_MESSAGE_QUESTION
.createLabel(" "
253 text
= new TextBox(new TerminalSize(getSize().getColumns()
254 - lbl
.getSize().getColumns(), 1));
256 text
.setText(initial
);
258 hpanel
.addComponent(lbl
,
259 LinearLayout
.createLayoutData(LinearLayout
.Alignment
.Beginning
));
260 hpanel
.addComponent(text
,
261 LinearLayout
.createLayoutData(LinearLayout
.Alignment
.Fill
));
264 .addComponent(hpanel
, LinearLayout
265 .createLayoutData(LinearLayout
.Alignment
.Beginning
));
271 * Refresh the window and the empty-space handling. You should call this
272 * method when the window size changed.
275 * the new size of the window
277 public void refresh(TerminalSize size
) {
281 if (getSize() == null || !getSize().equals(size
))
287 setActions(new ArrayList
<KeyAction
>(actions
), false);
293 public void invalidate() {
295 for (MainContent content
: contentStack
) {
296 content
.invalidate();
301 public boolean handleInput(KeyStroke key
) {
302 boolean handled
= false;
304 if (questionAction
!= null) {
305 String answer
= handleQuestion(key
);
306 if (answer
!= null) {
309 handleAction(questionAction
, answer
);
310 questionAction
= null;
313 handled
= handleKey(key
);
317 handled
= super.handleInput(key
);
323 * Actually set the title <b>inside</b> the window. Will also call
324 * {@link BasicWindow#setTitle} with the computed parameters.
326 private void setTitle() {
327 String prefix
= " " + Main
.APPLICATION_TITLE
+ " (version "
328 + Main
.APPLICATION_VERSION
+ ")";
333 MainContent content
= getContent();
334 if (content
!= null) {
335 title
= content
.getTitle();
336 count
= content
.getCount();
342 if (title
.length() > 0) {
343 prefix
= prefix
+ ": ";
344 title
= StringUtils
.sanitize(title
, UiColors
.getInstance()
348 String countStr
= "";
350 countStr
= "[" + count
+ "]";
354 if (getSize() != null) {
355 width
= getSize().getColumns();
359 int padding
= width
- prefix
.length() - title
.length()
362 if (title
.length() > 0)
363 title
= StringUtils
.padString(title
, title
.length()
366 prefix
= StringUtils
.padString(prefix
, prefix
.length()
371 String titleCache
= prefix
+ title
+ count
;
372 if (!titleCache
.equals(this.titleCache
)) {
373 super.setTitle(prefix
);
375 Label lblPrefix
= new Label(prefix
);
376 UiColors
.Element
.TITLE_MAIN
.themeLabel(lblPrefix
);
378 Label lblTitle
= null;
379 if (title
.length() > 0) {
380 lblTitle
= new Label(title
);
381 UiColors
.Element
.TITLE_VARIABLE
.themeLabel(lblTitle
);
384 Label lblCount
= null;
385 if (countStr
!= null) {
386 lblCount
= new Label(countStr
);
387 UiColors
.Element
.TITLE_COUNT
.themeLabel(lblCount
);
390 titlePanel
.removeAllComponents();
392 titlePanel
.addComponent(lblPrefix
, BorderLayout
.Location
.LEFT
);
393 if (lblTitle
!= null)
394 titlePanel
.addComponent(lblTitle
, BorderLayout
.Location
.CENTER
);
395 if (lblCount
!= null)
396 titlePanel
.addComponent(lblCount
, BorderLayout
.Location
.RIGHT
);
401 * Return the current {@link MainContent} from the stack if any.
403 * @return the current {@link MainContent}
405 private MainContent
getContent() {
406 if (contentStack
.size() > 0) {
407 return contentStack
.get(contentStack
.size() - 1);
414 * Update the list of actions and refresh the action panel.
417 * the list of actions to support
418 * @param enableDefaultactions
419 * TRUE to enable the default actions
421 private void setActions(List
<KeyAction
> actions
,
422 boolean enableDefaultactions
) {
423 this.actions
.clear();
425 if (enableDefaultactions
)
426 this.actions
.addAll(defaultActions
);
429 this.actions
.addAll(actions
);
431 actionPanel
.removeAllComponents();
432 for (KeyAction action
: this.actions
) {
433 String trans
= " " + action
.getStringId().trans() + " ";
435 if (" ".equals(trans
))
438 String keyTrans
= Trans
.getInstance().trans(action
.getKey());
440 Panel kPane
= new Panel();
441 LinearLayout layout
= new LinearLayout(Direction
.HORIZONTAL
);
442 layout
.setSpacing(0);
443 kPane
.setLayoutManager(layout
);
445 kPane
.addComponent(UiColors
.Element
.ACTION_KEY
446 .createLabel(keyTrans
));
447 kPane
.addComponent(UiColors
.Element
.ACTION_DESC
.createLabel(trans
));
449 actionPanel
.addComponent(kPane
);
452 // fill with "desc" colour
454 if (getSize() != null) {
455 width
= getSize().getColumns();
459 actionPanel
.addComponent(UiColors
.Element
.ACTION_DESC
460 .createLabel(StringUtils
.padString("", width
)));
465 * Handle user input when in "ask for question" mode (see
466 * {@link MainWindow#questionKey}).
469 * the key that has been pressed by the user
471 * @return the user's answer if done
473 private String
handleQuestion(KeyStroke key
) {
474 String answer
= null;
476 if (waitForOneKeyAnswer
) {
477 answer
= "" + key
.getCharacter();
479 if (key
.getKeyType() == KeyType
.Enter
) {
481 answer
= text
.getText();
487 if (answer
!= null) {
488 Interactable focus
= null;
489 MainContent content
= getContent();
491 focus
= content
.nextFocus(null);
500 * Handle the input in case of "normal" (not "ask for answer") mode.
503 * the key that was pressed
505 * the answer given for this key
507 * @return if the window handled the input
509 private boolean handleKey(KeyStroke key
) {
510 boolean handled
= false;
512 if (setMessage(null, false))
515 for (KeyAction action
: actions
) {
516 if (!action
.match(key
))
521 if (action
.onAction()) {
522 handleAction(action
, null);
532 * Handle the input in case of "normal" (not "ask for answer") mode.
535 * the key that was pressed
537 * the answer given for this key
539 * @return if the window handled the input
541 private void handleAction(KeyAction action
, String answer
) {
542 MainContent content
= getContent();
544 Card card
= action
.getCard();
545 Contact contact
= action
.getContact();
546 Data data
= action
.getData();
548 switch (action
.getMode()) {
553 if (action
.getKey().getKeyType() == KeyType
.ArrowUp
)
555 if (action
.getKey().getKeyType() == KeyType
.ArrowDown
)
557 if (action
.getKey().getKeyType() == KeyType
.ArrowLeft
)
559 if (action
.getKey().getKeyType() == KeyType
.ArrowRight
)
562 if (content
!= null) {
563 String err
= content
.move(x
, y
);
565 setMessage(err
, true);
569 // mode with windows:
572 pushContent(new ContactList(card
));
575 case CONTACT_DETAILS
:
576 if (contact
!= null) {
577 pushContent(new ContactDetailsRaw(contact
));
580 // mode interpreted by MainWindow:
583 // setMessage("Help! I need somebody! Help!", false);
584 if (answer
== null) {
585 setQuestion(action
, "Test question?", "[initial]");
587 setMessage("You answered: " + answer
, false);
592 String warning
= content
.getExitWarning();
593 if (warning
!= null) {
594 if (answer
== null) {
595 setQuestion(action
, warning
);
597 setMessage(null, false);
598 if (answer
.equalsIgnoreCase("y")) {
606 if (contentStack
.size() == 0) {
613 if (answer
== null) {
615 String name
= data
.getName();
616 String value
= data
.getValue();
617 setQuestion(action
, name
, value
);
620 setMessage(null, false);
621 data
.setValue(answer
);
625 if (answer
== null) {
626 if (contact
!= null) {
627 setQuestion(action
, "Delete contact? [Y/N]");
630 setMessage(null, false);
631 if (answer
.equalsIgnoreCase("y")) {
632 if (contact
.delete()) {
633 content
.refreshData();
637 setMessage("Cannot delete this contact", true);
643 if (answer
== null) {
645 setQuestion(action
, "Save changes? [Y/N]");
648 setMessage(null, false);
649 if (answer
.equalsIgnoreCase("y")) {
656 } catch (IOException ioe
) {
657 ioe
.printStackTrace();
661 setMessage("Cannot save to file", true);