From 0b0b2b0ff1f5e21f7b0feb955b4b54855fb3d508 Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Thu, 25 Feb 2016 16:42:50 +0100 Subject: [PATCH] Improve UI, take "dirty" check into account, move launcher to Main.java --- src/be/nikiroo/jvcard/Card.java | 15 + src/be/nikiroo/jvcard/Contact.java | 142 +++++-- src/be/nikiroo/jvcard/i18n/Trans.java | 7 +- src/be/nikiroo/jvcard/tui/Main.java | 105 +++++ src/be/nikiroo/jvcard/tui/MainWindow.java | 395 ++++++++++++------ src/be/nikiroo/jvcard/tui/UiColors.java | 13 +- .../jvcard/tui/panes/ContactDetails.java | 11 +- .../nikiroo/jvcard/tui/panes/ContactList.java | 50 ++- .../nikiroo/jvcard/tui/panes/MainContent.java | 48 ++- .../jvcard/tui/panes/MainContentList.java | 13 +- .../lanterna/gui2/BorderLayout.java | 22 + 11 files changed, 607 insertions(+), 214 deletions(-) create mode 100644 src/be/nikiroo/jvcard/tui/Main.java diff --git a/src/be/nikiroo/jvcard/Card.java b/src/be/nikiroo/jvcard/Card.java index a9018ca..65ab6fa 100644 --- a/src/be/nikiroo/jvcard/Card.java +++ b/src/be/nikiroo/jvcard/Card.java @@ -24,10 +24,15 @@ public class Card { private List contacts; private File file; private boolean dirty; + private String name; public Card(File file, Format format) throws IOException { this.file = file; + if (file != null) { + name = file.getName(); + } + BufferedReader buffer = new BufferedReader(new FileReader(file)); List lines = new LinkedList(); for (String line = buffer.readLine(); line != null; line = buffer @@ -36,6 +41,7 @@ public class Card { } load(lines, format); + dirty = false; // initial load, so no change yet } public List getContacts() { @@ -88,6 +94,15 @@ public class Card { return dirty; } + /** + * Return the name of this card. + * + * @return the name + */ + public String getName() { + return name; + } + /** * Notify that this element has unsaved changes. */ diff --git a/src/be/nikiroo/jvcard/Contact.java b/src/be/nikiroo/jvcard/Contact.java index f2a8b01..21ed69b 100644 --- a/src/be/nikiroo/jvcard/Contact.java +++ b/src/be/nikiroo/jvcard/Contact.java @@ -135,6 +135,29 @@ public class Contact { return Parser.toString(this, format, startingBKey); } + /** + * Return a {@link String} representation of this contact formated + * accordingly to the given format. + * + * The format is basically a list of field names separated by a pipe and + * optionally parametrised. The parameters allows you to: + *
    + *
  • @x: show only a present/not present info
  • + *
  • @n: limit the size to a fixed value 'n'
  • + *
  • @+: expand the size of this field as much as possible
  • + *
+ * + * Example: "N@10|FN@20|NICK@+|PHOTO@x" + * + * @param format + * the format to use + * + * @return the {@link String} representation + */ + public String toString(String format) { + return toString(format, "|", null, -1); + } + /** * Return a {@link String} representation of this contact formated * accordingly to the given format. @@ -153,46 +176,79 @@ public class Contact { * the format to use * @param separator * the separator {@link String} to use between fields + * @param padding + * the {@link String} to use for left and right padding * @param width * a fixed width or -1 for "as long as needed" * * @return the {@link String} representation */ - public String toString(String format, String separator, int width) { + public String toString(String format, String separator, String padding, + int width) { StringBuilder builder = new StringBuilder(); - String[] formatFields = format.split("\\|"); - if (width > -1 && separator != null && separator.length() > 0 - && formatFields.length > 1) { - int swidth = (formatFields.length - 1) * separator.length(); - if (swidth >= width) { - int num = width / separator.length(); - int remainder = width % separator.length(); - - if (remainder > 0) - num++; - - while (builder.length() < width) { - if (builder.length() + separator.length() <= width) - builder.append(separator); - else - builder.append(separator - .substring(0, (builder.length() + separator - .length()) - - width)); - } + for (String str : toStringArray(format, separator, padding, width)) { + builder.append(str); + } - return builder.toString(); - } + return builder.toString(); + } - width -= swidth; + /** + * Return a {@link String} representation of this contact formated + * accordingly to the given format, part by part. + * + * The format is basically a list of field names separated by a pipe and + * optionally parametrised. The parameters allows you to: + *
    + *
  • @x: show only a present/not present info
  • + *
  • @n: limit the size to a fixed value 'n'
  • + *
  • @+: expand the size of this field as much as possible
  • + *
+ * + * Example: "N@10|FN@20|NICK@+|PHOTO@x" + * + * @param format + * the format to use + * @param separator + * the separator {@link String} to use between fields + * @param padding + * the {@link String} to use for left and right padding + * @param width + * a fixed width or -1 for "as long as needed" + * + * @return the {@link String} representation + */ + public String[] toStringArray(String format, String separator, + String padding, int width) { + if (width > -1) { + int numOfFields = format.split("\\|").length; + if (separator != null) + width -= (numOfFields - 1) * separator.length(); + if (padding != null) + width -= (numOfFields) * (2 * padding.length()); + + if (width < 0) + width = 0; } - for (String str : toStringArray(format, width)) { - builder.append(str); + List str = new LinkedList(); + + boolean first = true; + for (String s : toStringArray(format, width)) { + if (!first) { + str.add(separator); + } + + if (padding != null) + str.add(padding + s + padding); + else + str.add(s); + + first = false; } - return builder.toString(); + return str.toArray(new String[] {}); } /** @@ -200,8 +256,14 @@ public class Contact { * accordingly to the given format, part by part. * * The format is basically a list of field names separated by a pipe and - * optionally parametrised. See {@link Contact#toString} for more - * information about the format. + * optionally parametrised. The parameters allows you to: + *
    + *
  • @x: show only a present/not present info
  • + *
  • @n: limit the size to a fixed value 'n'
  • + *
  • @+: expand the size of this field as much as possible
  • + *
+ * + * Example: "N@10|FN@20|NICK@+|PHOTO@x" * * @param format * the format to use @@ -221,6 +283,10 @@ public class Contact { int totalSize = 0; if (width == 0) { + for (int i = 0; i < formatFields.length; i++) { + str.add(""); + } + return str.toArray(new String[] {}); } @@ -305,15 +371,11 @@ public class Contact { for (int i = 0; i < values.length; i++) { if (expandedFields[i]) { if (remainder > 0) { - values[i] = values[i] - + new String(new char[remainder]).replace( - '\0', ' '); + values[i] = values[i] + fixedString("", remainder); remainder = 0; } if (padPerItem > 0) { - values[i] = values[i] - + new String(new char[padPerItem]).replace( - '\0', ' '); + values[i] = values[i] + fixedString("", padPerItem); } } } @@ -344,11 +406,12 @@ public class Contact { static private String fixedString(String string, int size) { int length = string.length(); - if (length > size) + if (length > size) { string = string.substring(0, size); - else if (length < size) + } else if (length < size) { string = string + new String(new char[size - length]).replace('\0', ' '); + } return string; } @@ -381,10 +444,7 @@ public class Contact { } } - if (add.length() > 0) { - list.add(add); - } - + list.add(add); return add.length(); } diff --git a/src/be/nikiroo/jvcard/i18n/Trans.java b/src/be/nikiroo/jvcard/i18n/Trans.java index 8c744d3..cea5df4 100644 --- a/src/be/nikiroo/jvcard/i18n/Trans.java +++ b/src/be/nikiroo/jvcard/i18n/Trans.java @@ -23,7 +23,8 @@ public class Trans { * */ public enum StringId { - KEY_ACTION_BACK, KEY_ACTION_HELP, KEY_ACTION_VIEW_CONTACT, KEY_ACTION_VIEW_CARD, KEY_ACTION_EDIT_CONTACT, KEY_ACTION_SWITCH_FORMAT, TITLE, NULL; + DUMMY, // <-- TODO : remove + KEY_ACTION_BACK, KEY_ACTION_HELP, KEY_ACTION_VIEW_CONTACT, KEY_ACTION_VIEW_CARD, KEY_ACTION_EDIT_CONTACT, KEY_ACTION_SWITCH_FORMAT, NULL; public String trans() { return Trans.getInstance().trans(this); @@ -57,10 +58,10 @@ public class Trans { // TODO: get from a file instead? map.put(StringId.NULL, ""); + map.put(StringId.DUMMY, "[dummy]"); map.put(StringId.KEY_ACTION_BACK, "Back"); - map.put(StringId.TITLE, "[ jVcard: version 0.9 ]"); map.put(StringId.KEY_ACTION_VIEW_CONTACT, "view"); map.put(StringId.KEY_ACTION_EDIT_CONTACT, "edit"); - map.put(StringId.KEY_ACTION_SWITCH_FORMAT, "Change view"); + map.put(StringId.KEY_ACTION_SWITCH_FORMAT, "Change view"); } } diff --git a/src/be/nikiroo/jvcard/tui/Main.java b/src/be/nikiroo/jvcard/tui/Main.java new file mode 100644 index 0000000..ab6849c --- /dev/null +++ b/src/be/nikiroo/jvcard/tui/Main.java @@ -0,0 +1,105 @@ +package be.nikiroo.jvcard.tui; + +import java.io.File; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +import be.nikiroo.jvcard.Card; +import be.nikiroo.jvcard.parsers.Format; +import be.nikiroo.jvcard.tui.panes.ContactList; +import be.nikiroo.jvcard.tui.panes.FileList; + +import com.googlecode.lanterna.TerminalSize; +import com.googlecode.lanterna.TextColor; +import com.googlecode.lanterna.gui2.BasicWindow; +import com.googlecode.lanterna.gui2.Button; +import com.googlecode.lanterna.gui2.DefaultWindowManager; +import com.googlecode.lanterna.gui2.EmptySpace; +import com.googlecode.lanterna.gui2.GridLayout; +import com.googlecode.lanterna.gui2.Label; +import com.googlecode.lanterna.gui2.MultiWindowTextGUI; +import com.googlecode.lanterna.gui2.Panel; +import com.googlecode.lanterna.gui2.TextBox; +import com.googlecode.lanterna.gui2.Window; +import com.googlecode.lanterna.gui2.table.Table; +import com.googlecode.lanterna.screen.Screen; +import com.googlecode.lanterna.screen.TerminalScreen; +import com.googlecode.lanterna.terminal.DefaultTerminalFactory; +import com.googlecode.lanterna.terminal.Terminal; + +public class Main { + public static final String APPLICATION_TITLE = "jVcard"; + public static final String APPLICATION_VERSION = "0.9"; + + public static void main(String[] args) throws IOException { + Boolean textMode = null; + if (args.length > 0 && args[0].equals("--tui")) + textMode = true; + if (args.length > 0 && args[0].equals("--gui")) + textMode = false; + + Window win = null; + + // TODO: do not hardcode that: + Card card = new Card(new File("/home/niki/.addressbook"), Format.Abook); + win = new MainWindow(new ContactList(card)); + // + List files = new LinkedList(); + files.add(new File("/home/niki/vcf/coworkers.vcf")); + files.add(new File("/home/niki/vcf/oce.vcf")); + win = new MainWindow(new FileList(files)); + // + + TuiLauncher.start(textMode, win); + + /* + * String file = args.length > 0 ? args[0] : null; String file2 = + * args.length > 1 ? args[1] : null; + * + * if (file == null) file = + * "/home/niki/workspace/rcard/utils/CVcard/test.vcf"; if (file2 == + * null) file2 = "/home/niki/workspace/rcard/utils/CVcard/test.abook"; + * + * Card card = new Card(new File(file), Format.VCard21); + * System.out.println(card.toString()); + * + * System.out.println("\n -- PINE -- \n"); + * + * card = new Card(new File(file2), Format.Abook); + * System.out.println(card.toString(Format.Abook)); + */ + } + + static private void fullTestTable() throws IOException { + final Table table = new Table("Column 1", "Column 2", + "Column 3"); + table.getTableModel().addRow("1", "2", "3"); + table.setSelectAction(new Runnable() { + @Override + public void run() { + List data = table.getTableModel().getRow( + table.getSelectedRow()); + for (int i = 0; i < data.size(); i++) { + System.out.println(data.get(i)); + } + } + }); + + Window win = new BasicWindow(); + win.setComponent(table); + + DefaultTerminalFactory factory = new DefaultTerminalFactory(); + Terminal terminal = factory.createTerminal(); + + Screen screen = new TerminalScreen(terminal); + screen.startScreen(); + + // Create gui and start gui + MultiWindowTextGUI gui = new MultiWindowTextGUI(screen, + new DefaultWindowManager(), new EmptySpace(TextColor.ANSI.BLUE)); + gui.addWindowAndWait(win); + + screen.stopScreen(); + } +} diff --git a/src/be/nikiroo/jvcard/tui/MainWindow.java b/src/be/nikiroo/jvcard/tui/MainWindow.java index aa99635..a09751e 100644 --- a/src/be/nikiroo/jvcard/tui/MainWindow.java +++ b/src/be/nikiroo/jvcard/tui/MainWindow.java @@ -1,13 +1,11 @@ package be.nikiroo.jvcard.tui; -import java.io.File; 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; @@ -16,8 +14,10 @@ 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; @@ -30,8 +30,8 @@ import com.googlecode.lanterna.input.KeyStroke; import com.googlecode.lanterna.input.KeyType; /** - * This is the main "window" of the program. It will host one - * {@link MainContent} at any one time. + * This is the main "window" of the program. It can show up to one + * {@link MainContent} at any one time, but keeps a stack of contents. * * @author niki * @@ -39,10 +39,11 @@ import com.googlecode.lanterna.input.KeyType; public class MainWindow extends BasicWindow { private List defaultActions = new LinkedList(); private List actions = new LinkedList(); - private List content = new LinkedList(); + private List contentStack = new LinkedList(); private boolean actionsPadded; - private Boolean waitForOneKeyAnswer; // true, false, (null = do not wait for - // an answer) + private boolean waitForOneKeyAnswer; + private KeyStroke questionKey; // key that "asked" a question, and to replay + // later with an answer private String title; private Panel titlePanel; private Panel mainPanel; @@ -51,10 +52,19 @@ public class MainWindow extends BasicWindow { private Panel messagePanel; private TextBox text; + /** + * Create a new, empty window. + */ public MainWindow() { this(null); } + /** + * Create a new window hosting the given content. + * + * @param content + * the content to host + */ public MainWindow(MainContent content) { super(content == null ? "" : content.getTitle()); @@ -81,15 +91,14 @@ public class MainWindow extends BasicWindow { llayout.setSpacing(0); actionPanel.setLayoutManager(llayout); - llayout = new LinearLayout(Direction.VERTICAL); - llayout.setSpacing(0); - titlePanel.setLayoutManager(llayout); + BorderLayout blayout = new BorderLayout(); + titlePanel.setLayoutManager(blayout); llayout = new LinearLayout(Direction.VERTICAL); llayout.setSpacing(0); messagePanel.setLayoutManager(llayout); - BorderLayout blayout = new BorderLayout(); + blayout = new BorderLayout(); mainPanel.setLayoutManager(blayout); blayout = new BorderLayout(); @@ -113,6 +122,12 @@ public class MainWindow extends BasicWindow { setComponent(mainPanel); } + /** + * "push" some content to the window stack. + * + * @param content + * the new top-of-the-stack content + */ public void pushContent(MainContent content) { List actions = null; String title = null; @@ -122,7 +137,7 @@ public class MainWindow extends BasicWindow { title = content.getTitle(); actions = content.getKeyBindings(); contentPanel.addComponent(content, BorderLayout.Location.CENTER); - this.content.add(content); + this.contentStack.add(content); Interactable focus = content.nextFocus(null); if (focus != null) @@ -130,44 +145,30 @@ public class MainWindow extends BasicWindow { } setTitle(title); - setActions(actions, true, true); + setActions(actions, true); invalidate(); } + /** + * "pop" the latest, top-of-the-stack content from the window stack. + * + * @return the removed content if any + */ public MainContent popContent() { MainContent removed = null; MainContent prev = null; - if (content.size() > 0) - removed = content.remove(content.size() - 1); - if (content.size() > 0) - prev = content.remove(content.size() - 1); - pushContent(prev); - return removed; - } + MainContent content = getContent(); + if (content != null) + removed = contentStack.remove(contentStack.size() - 1); - /** - * Set the application title. - * - * @param title - * the new title or NULL for the default title - */ - public void setTitle(String title) { - if (title == null) { - title = Trans.StringId.TITLE.trans(); - } + if (contentStack.size() > 0) + prev = contentStack.remove(contentStack.size() - 1); - if (!title.equals(this.title)) { - super.setTitle(title); - this.title = title; - } - - Label lbl = new Label(title); - titlePanel.removeAllComponents(); + pushContent(prev); - titlePanel.addComponent(lbl, LinearLayout - .createLayoutData(LinearLayout.Alignment.Center)); + return removed; } /** @@ -189,36 +190,45 @@ public class MainWindow extends BasicWindow { } } - public void setQuestion(String mess, boolean oneKey) { + /** + * Show a question to the user and switch to "ask for answer" mode see + * {@link MainWindow#handleQuestion}. + * + * @param mess + * the message to display + * @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; + messagePanel.removeAllComponents(); - if (mess != null) { - waitForOneKeyAnswer = oneKey; - Panel hpanel = new Panel(); - LinearLayout llayout = new LinearLayout(Direction.HORIZONTAL); - llayout.setSpacing(0); - hpanel.setLayoutManager(llayout); + Panel hpanel = new Panel(); + LinearLayout llayout = new LinearLayout(Direction.HORIZONTAL); + llayout.setSpacing(0); + hpanel.setLayoutManager(llayout); - Label lbl = UiColors.Element.LINE_MESSAGE_QUESTION.createLabel(" " - + mess + " "); - text = new TextBox(new TerminalSize(getSize().getColumns() - - lbl.getSize().getColumns(), 1)); + Label lbl = UiColors.Element.LINE_MESSAGE_QUESTION.createLabel(" " + + mess + " "); + 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)); + 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(); - } + text.takeFocus(); } @Override public void draw(TextGUIGraphics graphics) { - setTitle(title); if (!actionsPadded) { // fill with "desc" colour actionPanel.addComponent(UiColors.Element.ACTION_DESC @@ -229,9 +239,83 @@ public class MainWindow extends BasicWindow { super.draw(graphics); } - private void setActions(List actions, boolean allowKeys, - boolean enableDefaultactions) { + @Override + public void setTitle(String title) { + String prefix = " " + Main.APPLICATION_TITLE + " (version " + + Main.APPLICATION_VERSION + ")"; + + int count = -1; + MainContent content = getContent(); + if (content != null) + count = content.getCount(); + + if (title != null) { + prefix = prefix + ": "; + } + + if (getSize() != null) { + if (title != null) + title = StringUtils.padString(title, getSize().getColumns()); + else + // cause busy-loop freeze: + prefix = StringUtils.padString(prefix, getSize().getColumns()); + } + + if (!(title + count).equals(this.title)) { + this.title = title + count; + + super.setTitle(prefix + title); + + Label lblPrefix = new Label(prefix); + UiColors.Element.TITLE_MAIN.themeLabel(lblPrefix); + + Label lblTitle = null; + if (title != null) { + lblTitle = new Label(title); + UiColors.Element.TITLE_VARIABLE.themeLabel(lblTitle); + } + + Label lblCount = null; + if (count > -1) { + lblCount = new Label("[" + count + "]"); + UiColors.Element.TITLE_COUNT.themeLabel(lblCount); + } + + titlePanel.removeAllComponents(); + titlePanel.addComponent(lblPrefix, BorderLayout.Location.LEFT); + if (lblTitle != null) + titlePanel.addComponent(lblTitle, BorderLayout.Location.CENTER); + if (lblCount != null) + titlePanel.addComponent(lblCount, BorderLayout.Location.RIGHT); + + invalidate(); + } + } + + /** + * Return the current {@link MainContent} from the stack if any. + * + * @return the current {@link MainContent} + */ + private MainContent getContent() { + if (contentStack.size() > 0) { + return contentStack.get(contentStack.size() - 1); + } + + return null; + } + + /** + * Update the list of actions and refresh the action panel. + * + * @param actions + * the list of actions to support + * @param enableDefaultactions + * TRUE to enable the default actions + */ + private void setActions(List actions, + boolean enableDefaultactions) { this.actions.clear(); actionsPadded = false; @@ -285,6 +369,15 @@ public class MainWindow extends BasicWindow { } } + /** + * Handle user input when in "ask for question" mode (see + * {@link MainWindow#questionKey}). + * + * @param key + * the key that has been pressed by the user + * + * @return the user's answer if done + */ private String handleQuestion(KeyStroke key) { String answer = null; @@ -301,9 +394,9 @@ public class MainWindow extends BasicWindow { if (answer != null) { Interactable focus = null; - if (this.content.size() > 0) - // focus = content.get(0).getDefaultFocusElement(); - focus = content.get(0).nextFocus(null); + MainContent content = getContent(); + if (content != null) + focus = content.nextFocus(null); focus.takeFocus(); } @@ -311,83 +404,121 @@ public class MainWindow extends BasicWindow { return answer; } - @Override - public boolean handleInput(KeyStroke key) { + /** + * 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 inout + */ + private boolean handleInput(KeyStroke key, String answer) { boolean handled = false; - if (waitForOneKeyAnswer != null) { - String answer = handleQuestion(key); - if (answer != null) { - waitForOneKeyAnswer = null; - setMessage("ANS: " + answer, false); + setMessage(null, false); - handled = true; - } - } else { - setMessage(null, false); - - for (KeyAction action : actions) { - if (!action.match(key)) - continue; - - 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.size() > 0) { - String err = content.get(content.size() - 1).move( - x, y); - if (err != null) - setMessage(err, true); - } + for (KeyAction action : actions) { + if (!action.match(key)) + continue; - 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)); + 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(); } - break; - // mode interpreted by MainWindow: - case HELP: - // TODO - // setMessage("Help! I need somebody! Help!", false); - setQuestion("Test question?", false); - handled = true; - break; - case BACK: - popContent(); - if (content.size() == 0) - close(); - break; - default: - case NONE: - break; } + + if (contentStack.size() == 0) + close(); + break; + default: + case NONE: + break; } + } - break; + break; + } + + return handled; + } + + @Override + public boolean handleInput(KeyStroke key) { + boolean handled = false; + + if (questionKey != null) { + String answer = handleQuestion(key); + if (answer != null) { + // TODO + key = questionKey; + questionKey = null; + + handled = handleInput(key, answer); } + } else { + handled = handleInput(key, null); } if (!handled) diff --git a/src/be/nikiroo/jvcard/tui/UiColors.java b/src/be/nikiroo/jvcard/tui/UiColors.java index 0525a53..834641d 100644 --- a/src/be/nikiroo/jvcard/tui/UiColors.java +++ b/src/be/nikiroo/jvcard/tui/UiColors.java @@ -34,7 +34,11 @@ public class UiColors { } public enum Element { - DEFAULT, ACTION_KEY, ACTION_DESC, LINE_MESSAGE, LINE_MESSAGE_ERR, LINE_MESSAGE_QUESTION, LINE_MESSAGE_ANS, CONTACT_LINE, CONTACT_LINE_SEPARATOR, CONTACT_LINE_SELECTED, CONTACT_LINE_SEPARATOR_SELECTED; + DEFAULT, // + TITLE_MAIN, TITLE_VARIABLE, TITLE_COUNT, // + ACTION_KEY, ACTION_DESC, // + LINE_MESSAGE, LINE_MESSAGE_ERR, LINE_MESSAGE_QUESTION, LINE_MESSAGE_ANS, // + CONTACT_LINE, CONTACT_LINE_SEPARATOR, CONTACT_LINE_SELECTED, CONTACT_LINE_SEPARATOR_SELECTED, CONTACT_LINE_DIRTY, CONTACT_LINE_DIRTY_SELECTED; /** * Get the foreground colour of this element. @@ -101,7 +105,8 @@ public class UiColors { addEl(Element.CONTACT_LINE, TextColor.ANSI.WHITE, TextColor.ANSI.BLACK); addEl(Element.CONTACT_LINE_SELECTED, TextColor.ANSI.WHITE, TextColor.ANSI.BLUE); - addEl(Element.CONTACT_LINE_SEPARATOR, TextColor.ANSI.RED, TextColor.ANSI.BLACK); + addEl(Element.CONTACT_LINE_SEPARATOR, TextColor.ANSI.RED, + TextColor.ANSI.BLACK); addEl(Element.CONTACT_LINE_SEPARATOR_SELECTED, TextColor.ANSI.RED, TextColor.ANSI.BLUE); addEl(Element.LINE_MESSAGE, TextColor.ANSI.BLUE, TextColor.ANSI.WHITE); @@ -111,6 +116,10 @@ public class UiColors { TextColor.ANSI.WHITE); addEl(Element.LINE_MESSAGE_ANS, TextColor.ANSI.BLUE, TextColor.ANSI.BLACK); + addEl(Element.TITLE_MAIN, TextColor.ANSI.WHITE, TextColor.ANSI.BLUE); + addEl(Element.TITLE_VARIABLE, TextColor.ANSI.GREEN, + TextColor.ANSI.BLUE); + addEl(Element.TITLE_COUNT, TextColor.ANSI.RED, TextColor.ANSI.BLUE); } private void addEl(Element el, TextColor fore, TextColor back) { diff --git a/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java b/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java index 2cd1403..c1e86c3 100644 --- a/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java +++ b/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java @@ -48,8 +48,15 @@ public class ContactDetails extends MainContent { @Override public String getTitle() { - // TODO Auto-generated method stub - return null; + String title = null; + + if (contact != null) { + title = contact.getPreferredDataValue("FN"); + if (title == null || title.length() == 0) + title = contact.getPreferredDataValue("N"); + } + + return title; } @Override diff --git a/src/be/nikiroo/jvcard/tui/panes/ContactList.java b/src/be/nikiroo/jvcard/tui/panes/ContactList.java index 7bb15dc..370aba7 100644 --- a/src/be/nikiroo/jvcard/tui/panes/ContactList.java +++ b/src/be/nikiroo/jvcard/tui/panes/ContactList.java @@ -4,13 +4,13 @@ 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.tui.KeyAction; import be.nikiroo.jvcard.tui.UiColors; import be.nikiroo.jvcard.tui.KeyAction.DataType; import be.nikiroo.jvcard.tui.KeyAction.Mode; import be.nikiroo.jvcard.tui.UiColors.Element; -import be.nikiroo.jvcard.tui.panes.MainContentList.TextPart; import com.googlecode.lanterna.input.KeyType; @@ -55,8 +55,10 @@ public class ContactList extends MainContentList { @Override public String getExitWarning() { if (card != null && card.isDirty()) { - return "Some of your contact information is not saved"; + //TODO: save? [y/n] instead + return "Some of your contact information is not saved; ignore? [Y/N]"; } + return null; } @@ -65,6 +67,17 @@ public class ContactList extends MainContentList { List actions = new LinkedList(); // TODO del, save... + // TODO: remove + actions.add(new KeyAction(Mode.NONE, 'd', Trans.StringId.DUMMY) { + @Override + public boolean onAction() { + //TODO dummy action + int index = getSelectedIndex(); + Contact c = card.getContacts().get(index); + c.updateFrom(c); + return false; + } + }); actions.add(new KeyAction(Mode.CONTACT_DETAILS, 'e', Trans.StringId.KEY_ACTION_EDIT_CONTACT) { @Override @@ -105,34 +118,43 @@ public class ContactList extends MainContentList { @Override public String getTitle() { - // TODO Auto-generated method stub + if (card != null) { + return card.getName(); + } + return null; } @Override protected List getLabel(int index, int width, boolean selected, boolean focused) { - List parts = new LinkedList(); + Contact c = card.getContacts().get(index); Element el = (focused && selected) ? Element.CONTACT_LINE_SELECTED : Element.CONTACT_LINE; Element elSep = (focused && selected) ? Element.CONTACT_LINE_SEPARATOR_SELECTED : Element.CONTACT_LINE_SEPARATOR; + Element elDirty = (focused && selected) ? Element.CONTACT_LINE_DIRTY_SELECTED + : Element.CONTACT_LINE_DIRTY; - // TODO: width/separator to check - String separator = " ┃ "; - width -= (format.split("\\|").length + 1) * separator.length(); - String[] array = card.getContacts().get(index).toStringArray(format, - width); + width -= 2; // dirty mark space // we could use: " ", "┃", "│"... - for (String str : array) { - parts.add(new TextPart(str, el)); - parts.add(new TextPart(separator, elSep)); + String[] array = c.toStringArray(format, "┃", " ", width); + + List parts = new LinkedList(); + if (c.isDirty()) { + parts.add(new TextPart(" ", el)); + parts.add(new TextPart("*", elDirty)); + } else { + parts.add(new TextPart(" ", elSep)); } - if (parts.size() > 0) - parts.remove(parts.get(parts.size() - 1)); + boolean separator = false; + for (String str : array) { + parts.add(new TextPart(str, (separator ? elSep : el))); + separator = !separator; + } return parts; } diff --git a/src/be/nikiroo/jvcard/tui/panes/MainContent.java b/src/be/nikiroo/jvcard/tui/panes/MainContent.java index df87313..0bc9b29 100644 --- a/src/be/nikiroo/jvcard/tui/panes/MainContent.java +++ b/src/be/nikiroo/jvcard/tui/panes/MainContent.java @@ -29,22 +29,6 @@ abstract public class MainContent extends Panel { setLayoutManager(layout); } - /** - * The title to display instead of the application name, or NULL for the - * default application name. - * - * @return the title or NULL - */ - abstract public String getTitle(); - - /** - * Returns an error message ready to be displayed if we should ask something - * to the user before exiting. - * - * @return an error message or NULL - */ - abstract public String getExitWarning(); - /** * The {@link KeyAction#Mode} that links to this {@link MainContent}. * @@ -66,6 +50,24 @@ abstract public class MainContent extends Panel { */ abstract public List getKeyBindings(); + /** + * The title to display instead of the application name, or NULL for the + * default application name. + * + * @return the title or NULL + */ + abstract public String getTitle(); + + /** + * Returns an error message ready to be displayed if we should ask something + * to the user before exiting. + * + * @return an error message or NULL + */ + public String getExitWarning() { + return null; + } + /** * Move the active cursor (not the text cursor, but the currently active * item). @@ -77,5 +79,17 @@ abstract public class MainContent extends Panel { * * @return the error message to display if any */ - abstract public String move(int x, int y); + public String move(int x, int y) { + return null; + } + + /** + * Return the number of items in this {@link MainContent}, or -1 if this + * {@link MainContent} is not countable. + * + * @return -1 or the number of present items + */ + public int getCount() { + return -1; + } } diff --git a/src/be/nikiroo/jvcard/tui/panes/MainContentList.java b/src/be/nikiroo/jvcard/tui/panes/MainContentList.java index 79cfb2b..8365419 100644 --- a/src/be/nikiroo/jvcard/tui/panes/MainContentList.java +++ b/src/be/nikiroo/jvcard/tui/panes/MainContentList.java @@ -23,7 +23,7 @@ abstract public class MainContentList extends MainContent implements Runnable { * @author niki * */ - protected class TextPart { + public class TextPart { private String text; private Element element; @@ -93,9 +93,10 @@ abstract public class MainContentList extends MainContent implements Runnable { ActionListBox listBox, int index, Runnable item, boolean selected, boolean focused) { - // TODO: why +5 ?? padding problem? + // width "-1" to reserve space for the optional vertical + // scroll bar List parts = MainContentList.this.getLabel( - index, lines.getSize().getColumns() + 5, + index, lines.getSize().getColumns() - 1, selected, focused); int position = 0; @@ -105,6 +106,7 @@ abstract public class MainContentList extends MainContent implements Runnable { graphics.setBackgroundColor(part .getBackgroundColor()); String label = part.getText(); + graphics.putString(position, 0, label); position += label.length(); } @@ -164,6 +166,11 @@ abstract public class MainContentList extends MainContent implements Runnable { return null; } + @Override + public int getCount() { + return lines.getItemCount(); + } + /** * Return the representation of the selected line, in {@link TextPart}s. * diff --git a/src/com/googlecode/lanterna/gui2/BorderLayout.java b/src/com/googlecode/lanterna/gui2/BorderLayout.java index 446774c..6cc6e85 100644 --- a/src/com/googlecode/lanterna/gui2/BorderLayout.java +++ b/src/com/googlecode/lanterna/gui2/BorderLayout.java @@ -133,16 +133,38 @@ public class BorderLayout implements LayoutManager { if(layout.containsKey(Location.LEFT)) { Component leftComponent = layout.get(Location.LEFT); leftComponentWidth = Math.min(leftComponent.getPreferredSize().getColumns(), availableHorizontalSpace); + + /* + if(leftComponentWidth == availableHorizontalSpace ){ + if(layout.containsKey(Location.RIGHT)) + leftComponentWidth--; + if(layout.containsKey(Location.CENTER)) + leftComponentWidth--; + }*/ + leftComponent.setPosition(new TerminalPosition(0, topComponentHeight)); leftComponent.setSize(new TerminalSize(leftComponentWidth, availableVerticalSpace)); availableHorizontalSpace -= leftComponentWidth; + + if(availableHorizontalSpace<=0) + availableHorizontalSpace=1; } if(layout.containsKey(Location.RIGHT)) { Component rightComponent = layout.get(Location.RIGHT); int rightComponentWidth = Math.min(rightComponent.getPreferredSize().getColumns(), availableHorizontalSpace); + + /* + if(rightComponentWidth == availableHorizontalSpace ){ + if(layout.containsKey(Location.CENTER)) + rightComponentWidth--; + }*/ + rightComponent.setPosition(new TerminalPosition(area.getColumns() - rightComponentWidth, topComponentHeight)); rightComponent.setSize(new TerminalSize(rightComponentWidth, availableVerticalSpace)); availableHorizontalSpace -= rightComponentWidth; + + if(availableHorizontalSpace<=0) + availableHorizontalSpace=1; } if(layout.containsKey(Location.CENTER)) { Component centerComponent = layout.get(Location.CENTER); -- 2.27.0