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
.StringId
;
13 import be
.nikiroo
.jvcard
.tui
.KeyAction
.Mode
;
14 import be
.nikiroo
.jvcard
.tui
.UiColors
.Element
;
15 import be
.nikiroo
.jvcard
.tui
.panes
.ContactDetails
;
16 import be
.nikiroo
.jvcard
.tui
.panes
.ContactList
;
17 import be
.nikiroo
.jvcard
.tui
.panes
.MainContent
;
19 import com
.googlecode
.lanterna
.TerminalSize
;
20 import com
.googlecode
.lanterna
.gui2
.BasicWindow
;
21 import com
.googlecode
.lanterna
.gui2
.BorderLayout
;
22 import com
.googlecode
.lanterna
.gui2
.Direction
;
23 import com
.googlecode
.lanterna
.gui2
.Interactable
;
24 import com
.googlecode
.lanterna
.gui2
.Label
;
25 import com
.googlecode
.lanterna
.gui2
.LinearLayout
;
26 import com
.googlecode
.lanterna
.gui2
.Panel
;
27 import com
.googlecode
.lanterna
.gui2
.TextBox
;
28 import com
.googlecode
.lanterna
.gui2
.TextGUIGraphics
;
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 KeyStroke questionKey
; // key that "asked" a question, and to replay
46 // later with an answer
47 private String titleCache
;
48 private Panel titlePanel
;
49 private Panel mainPanel
;
50 private Panel contentPanel
;
51 private Panel actionPanel
;
52 private Panel messagePanel
;
57 * Create a new, empty window.
64 * Create a new window hosting the given content.
69 public MainWindow(MainContent content
) {
70 super(content
== null ?
"" : content
.getTitle());
74 setHints(Arrays
.asList(Window
.Hint
.FULL_SCREEN
,
75 Window
.Hint
.NO_DECORATIONS
, Window
.Hint
.FIT_TERMINAL_WINDOW
));
77 defaultActions
.add(new KeyAction(Mode
.BACK
, 'q',
78 StringId
.KEY_ACTION_BACK
));
79 defaultActions
.add(new KeyAction(Mode
.BACK
, KeyType
.Escape
,
81 defaultActions
.add(new KeyAction(Mode
.HELP
, 'h',
82 StringId
.KEY_ACTION_HELP
));
83 defaultActions
.add(new KeyAction(Mode
.HELP
, KeyType
.F1
, StringId
.NULL
));
85 actionPanel
= new Panel();
86 contentPanel
= new Panel();
87 mainPanel
= new Panel();
88 messagePanel
= new Panel();
89 titlePanel
= new Panel();
91 Panel actionMessagePanel
= new Panel();
93 LinearLayout llayout
= new LinearLayout(Direction
.HORIZONTAL
);
94 llayout
.setSpacing(0);
95 actionPanel
.setLayoutManager(llayout
);
97 BorderLayout blayout
= new BorderLayout();
98 titlePanel
.setLayoutManager(blayout
);
100 llayout
= new LinearLayout(Direction
.VERTICAL
);
101 llayout
.setSpacing(0);
102 messagePanel
.setLayoutManager(llayout
);
104 blayout
= new BorderLayout();
105 mainPanel
.setLayoutManager(blayout
);
107 blayout
= new BorderLayout();
108 contentPanel
.setLayoutManager(blayout
);
110 blayout
= new BorderLayout();
111 actionMessagePanel
.setLayoutManager(blayout
);
114 .addComponent(messagePanel
, BorderLayout
.Location
.TOP
);
115 actionMessagePanel
.addComponent(actionPanel
,
116 BorderLayout
.Location
.CENTER
);
118 mainPanel
.addComponent(titlePanel
, BorderLayout
.Location
.TOP
);
119 mainPanel
.addComponent(contentPanel
, BorderLayout
.Location
.CENTER
);
121 .addComponent(actionMessagePanel
, BorderLayout
.Location
.BOTTOM
);
123 pushContent(content
);
125 setComponent(mainPanel
);
129 * "push" some content to the window stack.
132 * the new top-of-the-stack content
134 public void pushContent(MainContent content
) {
135 List
<KeyAction
> actions
= null;
137 contentPanel
.removeAllComponents();
138 if (content
!= null) {
139 actions
= content
.getKeyBindings();
140 contentPanel
.addComponent(content
, BorderLayout
.Location
.CENTER
);
141 this.contentStack
.add(content
);
143 Interactable focus
= content
.nextFocus(null);
149 setActions(actions
, true);
153 * "pop" the latest, top-of-the-stack content from the window stack.
155 * @return the removed content if any
157 public MainContent
popContent() {
158 MainContent removed
= null;
159 MainContent prev
= null;
161 MainContent content
= getContent();
163 removed
= contentStack
.remove(contentStack
.size() - 1);
165 if (contentStack
.size() > 0)
166 prev
= contentStack
.remove(contentStack
.size() - 1);
174 * Show the given message on screen. It will disappear at the next action.
177 * the message to display
179 * TRUE for an error message, FALSE for an information message
181 * @return TRUE if changes were performed
183 public boolean setMessage(String mess
, boolean error
) {
184 if (mess
!= null || messagePanel
.getChildCount() > 0) {
185 messagePanel
.removeAllComponents();
187 Element element
= (error ? UiColors
.Element
.LINE_MESSAGE_ERR
188 : UiColors
.Element
.LINE_MESSAGE
);
189 Label lbl
= element
.createLabel(" " + mess
+ " ");
190 messagePanel
.addComponent(lbl
, LinearLayout
191 .createLayoutData(LinearLayout
.Alignment
.Center
));
200 * Show a question to the user and switch to "ask for answer" mode see
201 * {@link MainWindow#handleQuestion}. The user will be asked to enter some
202 * answer and confirm with ENTER.
205 * the question to ask
207 * the initial answer if any (to be edited by the user)
209 public void setQuestion(KeyStroke key
, String question
, String initial
) {
210 setQuestion(key
, question
, initial
, false);
214 * Show a question to the user and switch to "ask for answer" mode see
215 * {@link MainWindow#handleQuestion}. The user will be asked to hit one key
219 * the question to ask
221 public void setQuestion(KeyStroke key
, String question
) {
222 setQuestion(key
, question
, null, true);
226 public void draw(TextGUIGraphics graphics
) {
227 int width
= graphics
.getSize().getColumns();
229 if (width
!= this.width
) {
235 setActions(new ArrayList
<KeyAction
>(actions
), false);
238 super.draw(graphics
);
242 public void invalidate() {
244 for (MainContent content
: contentStack
) {
245 content
.invalidate();
250 * Show a question to the user and switch to "ask for answer" mode see
251 * {@link MainWindow#handleQuestion}.
254 * the question to ask
256 * the initial answer if any (to be edited by the user)
258 * TRUE for a one-key answer, FALSE for a text answer validated
261 private void setQuestion(KeyStroke key
, String question
, String initial
,
264 waitForOneKeyAnswer
= oneKey
;
266 messagePanel
.removeAllComponents();
268 Panel hpanel
= new Panel();
269 LinearLayout llayout
= new LinearLayout(Direction
.HORIZONTAL
);
270 llayout
.setSpacing(0);
271 hpanel
.setLayoutManager(llayout
);
273 Label lbl
= UiColors
.Element
.LINE_MESSAGE_QUESTION
.createLabel(" "
275 text
= new TextBox(new TerminalSize(width
- lbl
.getSize().getColumns(),
278 text
.setText(initial
);
280 hpanel
.addComponent(lbl
,
281 LinearLayout
.createLayoutData(LinearLayout
.Alignment
.Beginning
));
282 hpanel
.addComponent(text
,
283 LinearLayout
.createLayoutData(LinearLayout
.Alignment
.Fill
));
286 .addComponent(hpanel
, LinearLayout
287 .createLayoutData(LinearLayout
.Alignment
.Beginning
));
293 * Actually set the title <b>inside</b> the window. Will also call
294 * {@link BasicWindow#setTitle} with the compuited parameters.
296 private void setTitle() {
297 String prefix
= " " + Main
.APPLICATION_TITLE
+ " (version "
298 + Main
.APPLICATION_VERSION
+ ")";
303 MainContent content
= getContent();
304 if (content
!= null) {
305 title
= content
.getTitle();
306 count
= content
.getCount();
312 if (title
.length() > 0) {
313 prefix
= prefix
+ ": ";
316 String countStr
= "";
318 countStr
= "[" + count
+ "]";
322 int padding
= width
- prefix
.length() - title
.length()
325 if (title
.length() > 0)
326 title
= StringUtils
.padString(title
, title
.length()
329 prefix
= StringUtils
.padString(prefix
, prefix
.length()
334 String titleCache
= prefix
+ title
+ count
;
335 if (!titleCache
.equals(this.titleCache
)) {
336 super.setTitle(prefix
);
338 Label lblPrefix
= new Label(prefix
);
339 UiColors
.Element
.TITLE_MAIN
.themeLabel(lblPrefix
);
341 Label lblTitle
= null;
342 if (title
.length() > 0) {
343 lblTitle
= new Label(title
);
344 UiColors
.Element
.TITLE_VARIABLE
.themeLabel(lblTitle
);
347 Label lblCount
= null;
348 if (countStr
!= null) {
349 lblCount
= new Label(countStr
);
350 UiColors
.Element
.TITLE_COUNT
.themeLabel(lblCount
);
353 titlePanel
.removeAllComponents();
355 titlePanel
.addComponent(lblPrefix
, BorderLayout
.Location
.LEFT
);
356 if (lblTitle
!= null)
357 titlePanel
.addComponent(lblTitle
, BorderLayout
.Location
.CENTER
);
358 if (lblCount
!= null)
359 titlePanel
.addComponent(lblCount
, BorderLayout
.Location
.RIGHT
);
364 * Return the current {@link MainContent} from the stack if any.
366 * @return the current {@link MainContent}
368 private MainContent
getContent() {
369 if (contentStack
.size() > 0) {
370 return contentStack
.get(contentStack
.size() - 1);
377 * Update the list of actions and refresh the action panel.
380 * the list of actions to support
381 * @param enableDefaultactions
382 * TRUE to enable the default actions
384 private void setActions(List
<KeyAction
> actions
,
385 boolean enableDefaultactions
) {
386 this.actions
.clear();
388 if (enableDefaultactions
)
389 this.actions
.addAll(defaultActions
);
392 this.actions
.addAll(actions
);
394 actionPanel
.removeAllComponents();
395 for (KeyAction action
: this.actions
) {
396 String trans
= " " + action
.getStringId().trans() + " ";
398 if (" ".equals(trans
))
401 String keyTrans
= "";
402 switch (action
.getKey().getKeyType()) {
410 keyTrans
= " " + action
.getKey().getCharacter() + " ";
413 keyTrans
= "" + action
.getKey().getKeyType();
415 if (keyTrans
.length() > width
) {
416 keyTrans
= keyTrans
.substring(0, width
);
417 } else if (keyTrans
.length() < width
) {
419 + new String(new char[width
- keyTrans
.length()])
425 Panel kPane
= new Panel();
426 LinearLayout layout
= new LinearLayout(Direction
.HORIZONTAL
);
427 layout
.setSpacing(0);
428 kPane
.setLayoutManager(layout
);
430 kPane
.addComponent(UiColors
.Element
.ACTION_KEY
431 .createLabel(keyTrans
));
432 kPane
.addComponent(UiColors
.Element
.ACTION_DESC
.createLabel(trans
));
434 actionPanel
.addComponent(kPane
);
437 // fill with "desc" colour
439 actionPanel
.addComponent(UiColors
.Element
.ACTION_DESC
440 .createLabel(StringUtils
.padString("", width
)));
446 * Handle user input when in "ask for question" mode (see
447 * {@link MainWindow#questionKey}).
450 * the key that has been pressed by the user
452 * @return the user's answer if done
454 private String
handleQuestion(KeyStroke key
) {
455 String answer
= null;
457 if (waitForOneKeyAnswer
) {
458 answer
= "" + key
.getCharacter();
460 if (key
.getKeyType() == KeyType
.Enter
) {
462 answer
= text
.getText();
468 if (answer
!= null) {
469 Interactable focus
= null;
470 MainContent content
= getContent();
472 focus
= content
.nextFocus(null);
481 * Handle the input in case of "normal" (not "ask for answer") mode.
484 * the key that was pressed
486 * the answer given for this key
488 * @return if the window handled the input
490 private boolean handleInput(KeyStroke key
, String answer
) {
491 boolean handled
= false;
493 // reset the message pane if no answers are pending
494 if (answer
== null) {
495 if (setMessage(null, false))
499 for (KeyAction action
: actions
) {
500 if (!action
.match(key
))
503 MainContent content
= getContent();
506 if (action
.onAction()) {
507 Card card
= action
.getCard();
508 Contact contact
= action
.getContact();
509 Data data
= action
.getData();
511 switch (action
.getMode()) {
516 if (action
.getKey().getKeyType() == KeyType
.ArrowUp
)
518 if (action
.getKey().getKeyType() == KeyType
.ArrowDown
)
520 if (action
.getKey().getKeyType() == KeyType
.ArrowLeft
)
522 if (action
.getKey().getKeyType() == KeyType
.ArrowRight
)
525 if (content
!= null) {
526 String err
= content
.move(x
, y
);
528 setMessage(err
, true);
532 // mode with windows:
535 pushContent(new ContactList(card
));
538 case CONTACT_DETAILS
:
539 if (contact
!= null) {
540 pushContent(new ContactDetails(contact
));
543 // mode interpreted by MainWindow:
546 // setMessage("Help! I need somebody! Help!", false);
547 if (answer
== null) {
548 setQuestion(key
, "Test question?", "[initial]");
550 setMessage("You answered: " + answer
, false);
555 String warning
= content
.getExitWarning();
556 if (warning
!= null) {
557 if (answer
== null) {
558 setQuestion(key
, warning
);
560 setMessage(null, false);
561 if (answer
.equalsIgnoreCase("y")) {
569 if (contentStack
.size() == 0) {
576 if (answer
== null) {
578 String name
= data
.getName();
579 String value
= data
.getValue();
580 setQuestion(key
, name
, value
);
583 setMessage(null, false);
584 data
.setValue(answer
);
588 if (answer
== null) {
589 if (contact
!= null) {
590 setQuestion(key
, "Delete contact? [Y/N]");
593 setMessage(null, false);
594 if (answer
.equalsIgnoreCase("y")) {
595 if (contact
.delete()) {
596 content
.refreshData();
600 setMessage("Cannot delete this contact", true);
606 if (answer
== null) {
608 setQuestion(key
, "Save changes? [Y/N]");
611 setMessage(null, false);
612 if (answer
.equalsIgnoreCase("y")) {
619 } catch (IOException ioe
) {
620 ioe
.printStackTrace();
624 setMessage("Cannot save to file", true);
642 public boolean handleInput(KeyStroke key
) {
643 boolean handled
= false;
645 if (questionKey
!= null) {
646 String answer
= handleQuestion(key
);
647 if (answer
!= null) {
653 handleInput(key
, answer
);
656 handled
= handleInput(key
, null);
660 handled
= super.handleInput(key
);