1 package be
.nikiroo
.jvcard
.tui
;
3 import java
.util
.Arrays
;
4 import java
.util
.LinkedList
;
7 import be
.nikiroo
.jvcard
.Card
;
8 import be
.nikiroo
.jvcard
.Contact
;
9 import be
.nikiroo
.jvcard
.i18n
.Trans
.StringId
;
10 import be
.nikiroo
.jvcard
.tui
.KeyAction
.Mode
;
11 import be
.nikiroo
.jvcard
.tui
.UiColors
.Element
;
12 import be
.nikiroo
.jvcard
.tui
.panes
.ContactDetails
;
13 import be
.nikiroo
.jvcard
.tui
.panes
.ContactList
;
14 import be
.nikiroo
.jvcard
.tui
.panes
.MainContent
;
16 import com
.googlecode
.lanterna
.TerminalSize
;
17 import com
.googlecode
.lanterna
.TextColor
;
18 import com
.googlecode
.lanterna
.gui2
.BasicWindow
;
19 import com
.googlecode
.lanterna
.gui2
.BorderLayout
;
20 import com
.googlecode
.lanterna
.gui2
.ComponentRenderer
;
21 import com
.googlecode
.lanterna
.gui2
.Direction
;
22 import com
.googlecode
.lanterna
.gui2
.Interactable
;
23 import com
.googlecode
.lanterna
.gui2
.Label
;
24 import com
.googlecode
.lanterna
.gui2
.LinearLayout
;
25 import com
.googlecode
.lanterna
.gui2
.Panel
;
26 import com
.googlecode
.lanterna
.gui2
.TextBox
;
27 import com
.googlecode
.lanterna
.gui2
.TextGUIGraphics
;
28 import com
.googlecode
.lanterna
.gui2
.Window
;
29 import com
.googlecode
.lanterna
.input
.KeyStroke
;
30 import com
.googlecode
.lanterna
.input
.KeyType
;
33 * This is the main "window" of the program. It can show up to one
34 * {@link MainContent} at any one time, but keeps a stack of contents.
39 public class MainWindow
extends BasicWindow
{
40 private List
<KeyAction
> defaultActions
= new LinkedList
<KeyAction
>();
41 private List
<KeyAction
> actions
= new LinkedList
<KeyAction
>();
42 private List
<MainContent
> contentStack
= new LinkedList
<MainContent
>();
43 private boolean actionsPadded
;
44 private boolean waitForOneKeyAnswer
;
45 private KeyStroke questionKey
; // key that "asked" a question, and to replay
46 // later with an answer
48 private Panel titlePanel
;
49 private Panel mainPanel
;
50 private Panel contentPanel
;
51 private Panel actionPanel
;
52 private Panel messagePanel
;
56 * Create a new, empty window.
63 * Create a new window hosting the given content.
68 public MainWindow(MainContent content
) {
69 super(content
== null ?
"" : content
.getTitle());
71 setHints(Arrays
.asList(Window
.Hint
.FULL_SCREEN
,
72 Window
.Hint
.NO_DECORATIONS
, Window
.Hint
.FIT_TERMINAL_WINDOW
));
74 defaultActions
.add(new KeyAction(Mode
.BACK
, 'q',
75 StringId
.KEY_ACTION_BACK
));
76 defaultActions
.add(new KeyAction(Mode
.BACK
, KeyType
.Escape
,
78 defaultActions
.add(new KeyAction(Mode
.HELP
, 'h',
79 StringId
.KEY_ACTION_HELP
));
80 defaultActions
.add(new KeyAction(Mode
.HELP
, KeyType
.F1
, StringId
.NULL
));
82 actionPanel
= new Panel();
83 contentPanel
= new Panel();
84 mainPanel
= new Panel();
85 messagePanel
= new Panel();
86 titlePanel
= new Panel();
88 Panel actionMessagePanel
= new Panel();
90 LinearLayout llayout
= new LinearLayout(Direction
.HORIZONTAL
);
91 llayout
.setSpacing(0);
92 actionPanel
.setLayoutManager(llayout
);
94 BorderLayout blayout
= new BorderLayout();
95 titlePanel
.setLayoutManager(blayout
);
97 llayout
= new LinearLayout(Direction
.VERTICAL
);
98 llayout
.setSpacing(0);
99 messagePanel
.setLayoutManager(llayout
);
101 blayout
= new BorderLayout();
102 mainPanel
.setLayoutManager(blayout
);
104 blayout
= new BorderLayout();
105 contentPanel
.setLayoutManager(blayout
);
107 blayout
= new BorderLayout();
108 actionMessagePanel
.setLayoutManager(blayout
);
111 .addComponent(messagePanel
, BorderLayout
.Location
.TOP
);
112 actionMessagePanel
.addComponent(actionPanel
,
113 BorderLayout
.Location
.CENTER
);
115 mainPanel
.addComponent(titlePanel
, BorderLayout
.Location
.TOP
);
116 mainPanel
.addComponent(contentPanel
, BorderLayout
.Location
.CENTER
);
118 .addComponent(actionMessagePanel
, BorderLayout
.Location
.BOTTOM
);
120 pushContent(content
);
122 setComponent(mainPanel
);
126 * "push" some content to the window stack.
129 * the new top-of-the-stack content
131 public void pushContent(MainContent content
) {
132 List
<KeyAction
> actions
= null;
135 contentPanel
.removeAllComponents();
136 if (content
!= null) {
137 title
= content
.getTitle();
138 actions
= content
.getKeyBindings();
139 contentPanel
.addComponent(content
, BorderLayout
.Location
.CENTER
);
140 this.contentStack
.add(content
);
142 Interactable focus
= content
.nextFocus(null);
148 setActions(actions
, true);
154 * "pop" the latest, top-of-the-stack content from the window stack.
156 * @return the removed content if any
158 public MainContent
popContent() {
159 MainContent removed
= null;
160 MainContent prev
= null;
162 MainContent content
= getContent();
164 removed
= contentStack
.remove(contentStack
.size() - 1);
166 if (contentStack
.size() > 0)
167 prev
= contentStack
.remove(contentStack
.size() - 1);
175 * Show the given message on screen. It will disappear at the next action.
178 * the message to display
180 * TRUE for an error message, FALSE for an information message
182 public void setMessage(String mess
, boolean error
) {
183 messagePanel
.removeAllComponents();
185 Element element
= (error ? UiColors
.Element
.LINE_MESSAGE_ERR
186 : UiColors
.Element
.LINE_MESSAGE
);
187 Label lbl
= element
.createLabel(" " + mess
+ " ");
188 messagePanel
.addComponent(lbl
, LinearLayout
189 .createLayoutData(LinearLayout
.Alignment
.Center
));
194 * Show a question to the user and switch to "ask for answer" mode see
195 * {@link MainWindow#handleQuestion}.
198 * the message to display
200 * TRUE for a one-key answer, FALSE for a text answer validated
203 public void setQuestion(KeyStroke key
, String mess
, boolean oneKey
) {
205 waitForOneKeyAnswer
= oneKey
;
207 messagePanel
.removeAllComponents();
209 Panel hpanel
= new Panel();
210 LinearLayout llayout
= new LinearLayout(Direction
.HORIZONTAL
);
211 llayout
.setSpacing(0);
212 hpanel
.setLayoutManager(llayout
);
214 Label lbl
= UiColors
.Element
.LINE_MESSAGE_QUESTION
.createLabel(" "
216 text
= new TextBox(new TerminalSize(getSize().getColumns()
217 - lbl
.getSize().getColumns(), 1));
219 hpanel
.addComponent(lbl
, LinearLayout
220 .createLayoutData(LinearLayout
.Alignment
.Beginning
));
221 hpanel
.addComponent(text
, LinearLayout
222 .createLayoutData(LinearLayout
.Alignment
.Fill
));
224 messagePanel
.addComponent(hpanel
, LinearLayout
225 .createLayoutData(LinearLayout
.Alignment
.Beginning
));
231 public void draw(TextGUIGraphics graphics
) {
232 if (!actionsPadded
) {
233 // fill with "desc" colour
234 actionPanel
.addComponent(UiColors
.Element
.ACTION_DESC
235 .createLabel(StringUtils
.padString("", graphics
.getSize()
237 actionsPadded
= true;
239 super.draw(graphics
);
243 public void setTitle(String title
) {
244 String prefix
= " " + Main
.APPLICATION_TITLE
+ " (version "
245 + Main
.APPLICATION_VERSION
+ ")";
248 MainContent content
= getContent();
250 count
= content
.getCount();
253 prefix
= prefix
+ ": ";
256 if (getSize() != null) {
258 title
= StringUtils
.padString(title
, getSize().getColumns());
260 // cause busy-loop freeze:
261 prefix
= StringUtils
.padString(prefix
, getSize().getColumns());
264 if (!(title
+ count
).equals(this.title
)) {
265 this.title
= title
+ count
;
267 super.setTitle(prefix
+ title
);
269 Label lblPrefix
= new Label(prefix
);
270 UiColors
.Element
.TITLE_MAIN
.themeLabel(lblPrefix
);
272 Label lblTitle
= null;
274 lblTitle
= new Label(title
);
275 UiColors
.Element
.TITLE_VARIABLE
.themeLabel(lblTitle
);
278 Label lblCount
= null;
280 lblCount
= new Label("[" + count
+ "]");
281 UiColors
.Element
.TITLE_COUNT
.themeLabel(lblCount
);
284 titlePanel
.removeAllComponents();
286 titlePanel
.addComponent(lblPrefix
, BorderLayout
.Location
.LEFT
);
287 if (lblTitle
!= null)
288 titlePanel
.addComponent(lblTitle
, BorderLayout
.Location
.CENTER
);
289 if (lblCount
!= null)
290 titlePanel
.addComponent(lblCount
, BorderLayout
.Location
.RIGHT
);
297 * Return the current {@link MainContent} from the stack if any.
299 * @return the current {@link MainContent}
301 private MainContent
getContent() {
302 if (contentStack
.size() > 0) {
303 return contentStack
.get(contentStack
.size() - 1);
310 * Update the list of actions and refresh the action panel.
313 * the list of actions to support
314 * @param enableDefaultactions
315 * TRUE to enable the default actions
317 private void setActions(List
<KeyAction
> actions
,
318 boolean enableDefaultactions
) {
319 this.actions
.clear();
320 actionsPadded
= false;
322 if (enableDefaultactions
)
323 this.actions
.addAll(defaultActions
);
326 this.actions
.addAll(actions
);
328 actionPanel
.removeAllComponents();
329 for (KeyAction action
: this.actions
) {
330 String trans
= " " + action
.getStringId().trans() + " ";
332 if (" ".equals(trans
))
335 String keyTrans
= "";
336 switch (action
.getKey().getKeyType()) {
344 keyTrans
= " " + action
.getKey().getCharacter() + " ";
347 keyTrans
= "" + action
.getKey().getKeyType();
349 if (keyTrans
.length() > width
) {
350 keyTrans
= keyTrans
.substring(0, width
);
351 } else if (keyTrans
.length() < width
) {
353 + new String(new char[width
- keyTrans
.length()])
359 Panel kPane
= new Panel();
360 LinearLayout layout
= new LinearLayout(Direction
.HORIZONTAL
);
361 layout
.setSpacing(0);
362 kPane
.setLayoutManager(layout
);
364 kPane
.addComponent(UiColors
.Element
.ACTION_KEY
365 .createLabel(keyTrans
));
366 kPane
.addComponent(UiColors
.Element
.ACTION_DESC
.createLabel(trans
));
368 actionPanel
.addComponent(kPane
);
373 * Handle user input when in "ask for question" mode (see
374 * {@link MainWindow#questionKey}).
377 * the key that has been pressed by the user
379 * @return the user's answer if done
381 private String
handleQuestion(KeyStroke key
) {
382 String answer
= null;
384 if (waitForOneKeyAnswer
) {
385 answer
= "" + key
.getCharacter();
387 if (key
.getKeyType() == KeyType
.Enter
) {
389 answer
= text
.getText();
395 if (answer
!= null) {
396 Interactable focus
= null;
397 MainContent content
= getContent();
399 focus
= content
.nextFocus(null);
408 * Handle the input in case of "normal" (not "ask for answer") mode.
411 * the key that was pressed
413 * the answer given for this key
415 * @return if the window handled the inout
417 private boolean handleInput(KeyStroke key
, String answer
) {
418 boolean handled
= false;
420 setMessage(null, false);
422 for (KeyAction action
: actions
) {
423 if (!action
.match(key
))
426 MainContent content
= getContent();
429 if (action
.onAction()) {
430 switch (action
.getMode()) {
435 if (action
.getKey().getKeyType() == KeyType
.ArrowUp
)
437 if (action
.getKey().getKeyType() == KeyType
.ArrowDown
)
439 if (action
.getKey().getKeyType() == KeyType
.ArrowLeft
)
441 if (action
.getKey().getKeyType() == KeyType
.ArrowRight
)
444 if (content
!= null) {
445 String err
= content
.move(x
, y
);
447 setMessage(err
, true);
451 // mode with windows:
453 Card card
= action
.getCard();
455 pushContent(new ContactList(card
));
458 case CONTACT_DETAILS
:
459 Contact contact
= action
.getContact();
460 if (contact
!= null) {
461 pushContent(new ContactDetails(contact
));
464 // mode interpreted by MainWindow:
467 // setMessage("Help! I need somebody! Help!", false);
468 if (answer
== null) {
469 setQuestion(key
, "Test question?", false);
471 setMessage("You answered: " + answer
, false);
477 if (content
!= null) {
478 String warning
= content
.getExitWarning();
479 if (warning
!= null) {
480 if (answer
== null) {
481 setQuestion(key
, warning
, true);
483 if (answer
.equalsIgnoreCase("y")) {
492 if (contentStack
.size() == 0)
508 public boolean handleInput(KeyStroke key
) {
509 boolean handled
= false;
511 if (questionKey
!= null) {
512 String answer
= handleQuestion(key
);
513 if (answer
!= null) {
518 handled
= handleInput(key
, answer
);
521 handled
= handleInput(key
, null);
525 handled
= super.handleInput(key
);