X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Fjvcard%2Ftui%2FMainWindow.java;h=8dc860dcb3f75212de68187a778cc183e94bc888;hb=f720df72028ac616d569d4584e3684cd7134105d;hp=a09751ef26a81dbbfa3f00c1e869fd9757ca05d9;hpb=0b0b2b0ff1f5e21f7b0feb955b4b54855fb3d508;p=jvcard.git diff --git a/src/be/nikiroo/jvcard/tui/MainWindow.java b/src/be/nikiroo/jvcard/tui/MainWindow.java index a09751e..8dc860d 100644 --- a/src/be/nikiroo/jvcard/tui/MainWindow.java +++ b/src/be/nikiroo/jvcard/tui/MainWindow.java @@ -1,30 +1,28 @@ package be.nikiroo.jvcard.tui; +import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; -import be.nikiroo.jvcard.Card; -import be.nikiroo.jvcard.Contact; +import be.nikiroo.jvcard.i18n.Trans; import be.nikiroo.jvcard.i18n.Trans.StringId; import be.nikiroo.jvcard.tui.KeyAction.Mode; import be.nikiroo.jvcard.tui.UiColors.Element; import be.nikiroo.jvcard.tui.panes.ContactDetails; +import be.nikiroo.jvcard.tui.panes.ContactDetailsRaw; import be.nikiroo.jvcard.tui.panes.ContactList; import be.nikiroo.jvcard.tui.panes.MainContent; import com.googlecode.lanterna.TerminalSize; -import com.googlecode.lanterna.TextColor; import com.googlecode.lanterna.gui2.BasicWindow; import com.googlecode.lanterna.gui2.BorderLayout; -import com.googlecode.lanterna.gui2.ComponentRenderer; import com.googlecode.lanterna.gui2.Direction; import com.googlecode.lanterna.gui2.Interactable; import com.googlecode.lanterna.gui2.Label; import com.googlecode.lanterna.gui2.LinearLayout; import com.googlecode.lanterna.gui2.Panel; import com.googlecode.lanterna.gui2.TextBox; -import com.googlecode.lanterna.gui2.TextGUIGraphics; import com.googlecode.lanterna.gui2.Window; import com.googlecode.lanterna.input.KeyStroke; import com.googlecode.lanterna.input.KeyType; @@ -36,15 +34,13 @@ import com.googlecode.lanterna.input.KeyType; * @author niki * */ + public class MainWindow extends BasicWindow { private List defaultActions = new LinkedList(); private List actions = new LinkedList(); private List contentStack = new LinkedList(); - private boolean actionsPadded; - private boolean waitForOneKeyAnswer; - private KeyStroke questionKey; // key that "asked" a question, and to replay - // later with an answer - private String title; + private UserQuestion userQuestion; + private String titleCache; private Panel titlePanel; private Panel mainPanel; private Panel contentPanel; @@ -52,6 +48,68 @@ public class MainWindow extends BasicWindow { private Panel messagePanel; private TextBox text; + /** + * Information about a question to ask the user and its answer. + * + * @author niki + * + */ + private class UserQuestion { + private boolean oneKeyAnswer; + private KeyAction action; + private String answer; + + /** + * Create a new {@link UserQuestion}. + * + * @param action + * the action that triggered the question + * @param oneKeyAnswer + * TRUE if we expect a one-key answer + */ + public UserQuestion(KeyAction action, boolean oneKeyAnswer) { + this.action = action; + this.oneKeyAnswer = oneKeyAnswer; + } + + /** + * Return the {@link KeyAction} that triggered the question. + * + * @return the {@link KeyAction} + */ + public KeyAction getAction() { + return action; + } + + /** + * Check if a one-key answer is expected. + * + * @return TRUE if a one-key answer is expected + */ + public boolean isOneKeyAnswer() { + return oneKeyAnswer; + } + + /** + * Return the user answer. + * + * @return the user answer + */ + public String getAnswer() { + return answer; + } + + /** + * Set the user answer. + * + * @param answer + * the new answer + */ + public void setAnswer(String answer) { + this.answer = answer; + } + } + /** * Create a new, empty window. */ @@ -130,11 +188,9 @@ public class MainWindow extends BasicWindow { */ public void pushContent(MainContent content) { List actions = null; - String title = null; contentPanel.removeAllComponents(); if (content != null) { - title = content.getTitle(); actions = content.getKeyBindings(); contentPanel.addComponent(content, BorderLayout.Location.CENTER); this.contentStack.add(content); @@ -144,10 +200,8 @@ public class MainWindow extends BasicWindow { focus.takeFocus(); } - setTitle(title); + setTitle(); setActions(actions, true); - - invalidate(); } /** @@ -178,31 +232,72 @@ public class MainWindow extends BasicWindow { * the message to display * @param error * TRUE for an error message, FALSE for an information message + * + * @return TRUE if changes were performed */ - public void setMessage(String mess, boolean error) { - messagePanel.removeAllComponents(); - if (mess != null) { - Element element = (error ? UiColors.Element.LINE_MESSAGE_ERR - : UiColors.Element.LINE_MESSAGE); - Label lbl = element.createLabel(" " + mess + " "); - messagePanel.addComponent(lbl, LinearLayout - .createLayoutData(LinearLayout.Alignment.Center)); + public boolean setMessage(String mess, boolean error) { + if (mess != null || messagePanel.getChildCount() > 0) { + messagePanel.removeAllComponents(); + if (mess != null) { + Element element = (error ? UiColors.Element.LINE_MESSAGE_ERR + : UiColors.Element.LINE_MESSAGE); + Label lbl = element.createLabel(" " + mess + " "); + messagePanel.addComponent(lbl, LinearLayout + .createLayoutData(LinearLayout.Alignment.Center)); + } + return true; } + + return false; + } + + /** + * Show a question to the user and switch to "ask for answer" mode see + * {@link MainWindow#handleQuestion}. The user will be asked to enter some + * answer and confirm with ENTER. + * + * @param action + * the related action + * @param question + * the question to ask + * @param initial + * the initial answer if any (to be edited by the user) + */ + public void setQuestion(KeyAction action, String question, String initial) { + setQuestion(action, question, initial, false); + } + + /** + * Show a question to the user and switch to "ask for answer" mode see + * {@link MainWindow#handleQuestion}. The user will be asked to hit one key + * as an answer. + * + * @param action + * the related action + * @param question + * the question to ask + */ + public void setQuestion(KeyAction action, String question) { + setQuestion(action, question, null, true); } /** * Show a question to the user and switch to "ask for answer" mode see * {@link MainWindow#handleQuestion}. * - * @param mess - * the message to display + * @param action + * the related action + * @param question + * the question to ask + * @param initial + * the initial answer if any (to be edited by the user) * @param oneKey * TRUE for a one-key answer, FALSE for a text answer validated * by ENTER */ - public void setQuestion(KeyStroke key, String mess, boolean oneKey) { - questionKey = key; - waitForOneKeyAnswer = oneKey; + private void setQuestion(KeyAction action, String question, String initial, + boolean oneKey) { + userQuestion = new UserQuestion(action, oneKey); messagePanel.removeAllComponents(); @@ -212,72 +307,150 @@ public class MainWindow extends BasicWindow { hpanel.setLayoutManager(llayout); Label lbl = UiColors.Element.LINE_MESSAGE_QUESTION.createLabel(" " - + mess + " "); + + question + " "); text = new TextBox(new TerminalSize(getSize().getColumns() - lbl.getSize().getColumns(), 1)); - hpanel.addComponent(lbl, LinearLayout - .createLayoutData(LinearLayout.Alignment.Beginning)); - hpanel.addComponent(text, LinearLayout - .createLayoutData(LinearLayout.Alignment.Fill)); + if (initial != null) { + // add all chars one by one so the caret is at the end + for (int index = 0; index < initial.length(); index++) { + text.handleInput(new KeyStroke(initial.charAt(index), false, + false)); + } + } + + hpanel.addComponent(lbl, + LinearLayout.createLayoutData(LinearLayout.Alignment.Beginning)); + hpanel.addComponent(text, + LinearLayout.createLayoutData(LinearLayout.Alignment.Fill)); - messagePanel.addComponent(hpanel, LinearLayout - .createLayoutData(LinearLayout.Alignment.Beginning)); + messagePanel + .addComponent(hpanel, LinearLayout + .createLayoutData(LinearLayout.Alignment.Beginning)); text.takeFocus(); } + /** + * Refresh the window and the empty-space handling. You should call this + * method when the window size changed. + * + * @param size + * the new size of the window + */ + public void refresh(TerminalSize size) { + if (size == null) + return; + + if (getSize() == null || !getSize().equals(size)) + setSize(size); + + setTitle(); + + if (actions != null) + setActions(new ArrayList(actions), false); + + invalidate(); + } + @Override - public void draw(TextGUIGraphics graphics) { - if (!actionsPadded) { - // fill with "desc" colour - actionPanel.addComponent(UiColors.Element.ACTION_DESC - .createLabel(StringUtils.padString("", graphics.getSize() - .getColumns()))); - actionsPadded = true; + public void invalidate() { + super.invalidate(); + for (MainContent content : contentStack) { + content.invalidate(); } - super.draw(graphics); } @Override - public void setTitle(String title) { + public boolean handleInput(KeyStroke key) { + boolean handled = false; + + if (userQuestion != null) { + handled = handleQuestion(userQuestion, key); + if (handled) { + if (userQuestion.getAnswer() != null) { + handleAction(userQuestion.getAction(), + userQuestion.getAnswer()); + + userQuestion = null; + } + } + } else { + handled = handleKey(key); + } + + if (!handled) { + handled = super.handleInput(key); + } + + return handled; + } + + /** + * Actually set the title inside the window. Will also call + * {@link BasicWindow#setTitle} with the computed parameters. + */ + private void setTitle() { String prefix = " " + Main.APPLICATION_TITLE + " (version " + Main.APPLICATION_VERSION + ")"; + String title = null; int count = -1; + MainContent content = getContent(); - if (content != null) + if (content != null) { + title = content.getTitle(); count = content.getCount(); + } + + if (title == null) + title = ""; - if (title != null) { + if (title.length() > 0) { prefix = prefix + ": "; + title = StringUtils.sanitize(title, UiColors.getInstance() + .isUnicode()); } + String countStr = ""; + if (count > -1) { + countStr = "[" + count + "]"; + } + + int width = -1; if (getSize() != null) { - if (title != null) - title = StringUtils.padString(title, getSize().getColumns()); - else - // cause busy-loop freeze: - prefix = StringUtils.padString(prefix, getSize().getColumns()); + width = getSize().getColumns(); + } + + if (width > 0) { + int padding = width - prefix.length() - title.length() + - countStr.length(); + if (padding > 0) { + if (title.length() > 0) + title = StringUtils.padString(title, title.length() + + padding); + else + prefix = StringUtils.padString(prefix, prefix.length() + + padding); + } } - - if (!(title + count).equals(this.title)) { - this.title = title + count; - super.setTitle(prefix + title); + String titleCache = prefix + title + count; + if (!titleCache.equals(this.titleCache)) { + super.setTitle(prefix); Label lblPrefix = new Label(prefix); UiColors.Element.TITLE_MAIN.themeLabel(lblPrefix); Label lblTitle = null; - if (title != null) { + if (title.length() > 0) { lblTitle = new Label(title); UiColors.Element.TITLE_VARIABLE.themeLabel(lblTitle); } Label lblCount = null; - if (count > -1) { - lblCount = new Label("[" + count + "]"); + if (countStr != null) { + lblCount = new Label(countStr); UiColors.Element.TITLE_COUNT.themeLabel(lblCount); } @@ -288,8 +461,6 @@ public class MainWindow extends BasicWindow { titlePanel.addComponent(lblTitle, BorderLayout.Location.CENTER); if (lblCount != null) titlePanel.addComponent(lblCount, BorderLayout.Location.RIGHT); - - invalidate(); } } @@ -317,7 +488,6 @@ public class MainWindow extends BasicWindow { private void setActions(List actions, boolean enableDefaultactions) { this.actions.clear(); - actionsPadded = false; if (enableDefaultactions) this.actions.addAll(defaultActions); @@ -332,29 +502,7 @@ public class MainWindow extends BasicWindow { if (" ".equals(trans)) continue; - String keyTrans = ""; - switch (action.getKey().getKeyType()) { - case Enter: - keyTrans = " ⤶ "; - break; - case Tab: - keyTrans = " ↹ "; - break; - case Character: - keyTrans = " " + action.getKey().getCharacter() + " "; - break; - default: - keyTrans = "" + action.getKey().getKeyType(); - int width = 3; - if (keyTrans.length() > width) { - keyTrans = keyTrans.substring(0, width); - } else if (keyTrans.length() < width) { - keyTrans = keyTrans - + new String(new char[width - keyTrans.length()]) - .replace('\0', ' '); - } - break; - } + String keyTrans = Trans.getInstance().trans(action.getKey()); Panel kPane = new Panel(); LinearLayout layout = new LinearLayout(Direction.HORIZONTAL); @@ -367,41 +515,77 @@ public class MainWindow extends BasicWindow { actionPanel.addComponent(kPane); } + + // fill with "desc" colour + int width = -1; + if (getSize() != null) { + width = getSize().getColumns(); + } + + if (width > 0) { + actionPanel.addComponent(UiColors.Element.ACTION_DESC + .createLabel(StringUtils.padString("", width))); + } } /** * Handle user input when in "ask for question" mode (see - * {@link MainWindow#questionKey}). + * {@link MainWindow#userQuestion}). * + * @param userQuestion + * the question data * @param key * the key that has been pressed by the user * - * @return the user's answer if done + * @return TRUE if the {@link KeyStroke} was handled */ - private String handleQuestion(KeyStroke key) { - String answer = null; + private boolean handleQuestion(UserQuestion userQuestion, KeyStroke key) { + userQuestion.setAnswer(null); - if (waitForOneKeyAnswer) { - answer = "" + key.getCharacter(); + if (userQuestion.isOneKeyAnswer()) { + userQuestion.setAnswer("" + key.getCharacter()); } else { - if (key.getKeyType() == KeyType.Enter) { + // ^h == Backspace + if (key.isCtrlDown() && key.getCharacter() == 'h') { + key = new KeyStroke(KeyType.Backspace); + } + + switch (key.getKeyType()) { + case Enter: if (text != null) - answer = text.getText(); + userQuestion.setAnswer(text.getText()); else - answer = ""; + userQuestion.setAnswer(""); + break; + case Backspace: + int pos = text.getCaretPosition().getColumn(); + if (pos > 0) { + String current = text.getText(); + // force caret one space before: + text.setText(current.substring(0, pos - 1)); + // re-add full text: + text.setText(current.substring(0, pos - 1) + + current.substring(pos)); + } + return true; + default: + // Do nothing (continue entering text) + break; } } - if (answer != null) { + if (userQuestion.getAnswer() != null) { Interactable focus = null; MainContent content = getContent(); if (content != null) focus = content.nextFocus(null); focus.takeFocus(); + + return true; } - return answer; + return false; } /** @@ -412,90 +596,22 @@ public class MainWindow extends BasicWindow { * @param answer * the answer given for this key * - * @return if the window handled the inout + * @return if the window handled the input */ - private boolean handleInput(KeyStroke key, String answer) { + private boolean handleKey(KeyStroke key) { boolean handled = false; - setMessage(null, false); + if (setMessage(null, false)) + return true; for (KeyAction action : actions) { if (!action.match(key)) continue; - MainContent content = getContent(); handled = true; if (action.onAction()) { - switch (action.getMode()) { - case MOVE: - int x = 0; - int y = 0; - - if (action.getKey().getKeyType() == KeyType.ArrowUp) - x = -1; - if (action.getKey().getKeyType() == KeyType.ArrowDown) - x = 1; - if (action.getKey().getKeyType() == KeyType.ArrowLeft) - y = -1; - if (action.getKey().getKeyType() == KeyType.ArrowRight) - y = 1; - - if (content != null) { - String err = content.move(x, y); - if (err != null) - setMessage(err, true); - } - - break; - // mode with windows: - case CONTACT_LIST: - Card card = action.getCard(); - if (card != null) { - pushContent(new ContactList(card)); - } - break; - case CONTACT_DETAILS: - Contact contact = action.getContact(); - if (contact != null) { - pushContent(new ContactDetails(contact)); - } - break; - // mode interpreted by MainWindow: - case HELP: - // TODO - // setMessage("Help! I need somebody! Help!", false); - if (answer == null) { - setQuestion(key, "Test question?", false); - } else { - setMessage("You answered: " + answer, false); - } - - handled = true; - break; - case BACK: - if (content != null) { - String warning = content.getExitWarning(); - if (warning != null) { - if (answer == null) { - setQuestion(key, warning, true); - } else { - if (answer.equalsIgnoreCase("y")) { - popContent(); - } - } - } else { - popContent(); - } - } - - if (contentStack.size() == 0) - close(); - break; - default: - case NONE: - break; - } + handleAction(action, null); } break; @@ -504,26 +620,107 @@ public class MainWindow extends BasicWindow { return handled; } - @Override - public boolean handleInput(KeyStroke key) { - boolean handled = false; + /** + * Handle the input in case of "normal" (not "ask for answer") mode. + * + * @param key + * the key that was pressed + * @param answer + * the answer given for this key + * + * @return if the window handled the input + */ + private void handleAction(KeyAction action, String answer) { + MainContent content = getContent(); - if (questionKey != null) { - String answer = handleQuestion(key); - if (answer != null) { - // TODO - key = questionKey; - questionKey = null; + switch (action.getMode()) { + case MOVE: + int x = 0; + int y = 0; + + if (action.getKey().getKeyType() == KeyType.ArrowUp) + x = -1; + if (action.getKey().getKeyType() == KeyType.ArrowDown) + x = 1; + if (action.getKey().getKeyType() == KeyType.ArrowLeft) + y = -1; + if (action.getKey().getKeyType() == KeyType.ArrowRight) + y = 1; + + if (content != null) { + String err = content.move(x, y); + if (err != null) + setMessage(err, true); + } - handled = handleInput(key, answer); + break; + // mode with windows: + case CONTACT_LIST: + if (action.getCard() != null) { + pushContent(new ContactList(action.getCard())); } - } else { - handled = handleInput(key, null); - } + break; + case CONTACT_DETAILS: + if (action.getContact() != null) { + pushContent(new ContactDetails(action.getContact())); + } + break; + case CONTACT_DETAILS_RAW: + if (action.getContact() != null) { + pushContent(new ContactDetailsRaw(action.getContact())); + } + break; + // mode interpreted by MainWindow: + case HELP: + // TODO + setMessage("Help! I need somebody! Help!", false); - if (!handled) - handled = super.handleInput(key); + break; + case BACK: + String warning = content.getExitWarning(); + if (warning != null) { + if (answer == null) { + setQuestion(action, warning); + } else { + setMessage(null, false); + if (answer.equalsIgnoreCase("y")) { + popContent(); + } + } + } else { + popContent(); + } - return handled; + if (contentStack.size() == 0) { + close(); + } + + break; + // action modes: + case ASK_USER: + if (answer == null) { + setQuestion(action, action.getQuestion(), + action.getDefaultAnswer()); + } else { + setMessage(action.callback(answer), true); + content.refreshData(); + invalidate(); + setTitle(); + } + break; + case ASK_USER_KEY: + if (answer == null) { + setQuestion(action, action.getQuestion()); + } else { + setMessage(action.callback(answer), true); + content.refreshData(); + invalidate(); + setTitle(); + } + break; + default: + case NONE: + break; + } } }