Improve UI, take "dirty" check into account, move launcher to Main.java
authorNiki Roo <roo.niki@gmail.com>
Thu, 25 Feb 2016 15:42:50 +0000 (16:42 +0100)
committerNiki Roo <roo.niki@gmail.com>
Thu, 25 Feb 2016 15:42:50 +0000 (16:42 +0100)
src/be/nikiroo/jvcard/Card.java
src/be/nikiroo/jvcard/Contact.java
src/be/nikiroo/jvcard/i18n/Trans.java
src/be/nikiroo/jvcard/tui/Main.java [new file with mode: 0644]
src/be/nikiroo/jvcard/tui/MainWindow.java
src/be/nikiroo/jvcard/tui/UiColors.java
src/be/nikiroo/jvcard/tui/panes/ContactDetails.java
src/be/nikiroo/jvcard/tui/panes/ContactList.java
src/be/nikiroo/jvcard/tui/panes/MainContent.java
src/be/nikiroo/jvcard/tui/panes/MainContentList.java
src/com/googlecode/lanterna/gui2/BorderLayout.java

index a9018ca066d1cc590fcb92b76ce884919150a1ea..65ab6fa5225b91fdacdd3430fdc52abdbb317395 100644 (file)
@@ -24,10 +24,15 @@ public class Card {
        private List<Contact> 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<String> lines = new LinkedList<String>();
                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<Contact> 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.
         */
index f2a8b01b5ac4be470383f2b29934fde412bbeca8..21ed69b86f66e532adee8bc06a5bf317d00f9021 100644 (file)
@@ -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:
+        * <ul>
+        * <li>@x: show only a present/not present info</li>
+        * <li>@n: limit the size to a fixed value 'n'</li>
+        * <li>@+: expand the size of this field as much as possible</li>
+        * </ul>
+        * 
+        * 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:
+        * <ul>
+        * <li>@x: show only a present/not present info</li>
+        * <li>@n: limit the size to a fixed value 'n'</li>
+        * <li>@+: expand the size of this field as much as possible</li>
+        * </ul>
+        * 
+        * 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<String> str = new LinkedList<String>();
+
+               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:
+        * <ul>
+        * <li>@x: show only a present/not present info</li>
+        * <li>@n: limit the size to a fixed value 'n'</li>
+        * <li>@+: expand the size of this field as much as possible</li>
+        * </ul>
+        * 
+        * 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();
        }
 
index 8c744d38f2123ccf08eeb309d6837d69319133d7..cea5df4c1ba0eb5f53bb7eb574611d61cb5a4172 100644 (file)
@@ -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 (file)
index 0000000..ab6849c
--- /dev/null
@@ -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<File> files = new LinkedList<File>();
+               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<String> table = new Table<String>("Column 1", "Column 2",
+                               "Column 3");
+               table.getTableModel().addRow("1", "2", "3");
+               table.setSelectAction(new Runnable() {
+                       @Override
+                       public void run() {
+                               List<String> 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();
+       }
+}
index aa996353c9b2b8d292dfa92761f763c94bd98a09..a09751ef26a81dbbfa3f00c1e869fd9757ca05d9 100644 (file)
@@ -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<KeyAction> defaultActions = new LinkedList<KeyAction>();
        private List<KeyAction> actions = new LinkedList<KeyAction>();
-       private List<MainContent> content = new LinkedList<MainContent>();
+       private List<MainContent> contentStack = new LinkedList<MainContent>();
        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<KeyAction> 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<KeyAction> 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<KeyAction> 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)
index 0525a53297f1789403bf3e471b2de6abc708082b..834641da514d1fcdf2ebf93342d3f4daa127822c 100644 (file)
@@ -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) {
index 2cd1403b1c5270772f2842f57eff64a60e7a1a9b..c1e86c3d1fca92fbda2798a7bc16aae2542b92e9 100644 (file)
@@ -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
index 7bb15dc6a048bd5277216f88b14d752f00c78de2..370aba7cd5adf2e98177ef2d8cc86701c250c15d 100644 (file)
@@ -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<KeyAction> actions = new LinkedList<KeyAction>();
 
                // 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<TextPart> getLabel(int index, int width, boolean selected,
                        boolean focused) {
-               List<TextPart> parts = new LinkedList<TextPart>();
+               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<TextPart> parts = new LinkedList<TextPart>();
+               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;
        }
index df8731366b37d788e2ab5ff86258e018221fb4da..0bc9b29fce7bc091d0f3696534776b640b340b34 100644 (file)
@@ -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<KeyAction> 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;
+       }
 }
index 79cfb2baa6cec9f9aa482ad504040b9499fc2cb0..83654198f08c127f75d6ab6f1f06de66ca7372d3 100644 (file)
@@ -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<TextPart> 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.
         * 
index 446774c834e3434a0e8d1ceccc57cdf283bf2b6b..6cc6e85e8d5f779225f0f8355e249c494063e35d 100644 (file)
@@ -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);