Fix --noutf, fix onAction being called to many times, lot of small fixes
authorNiki Roo <niki@nikiroo.be>
Mon, 29 Feb 2016 10:35:59 +0000 (11:35 +0100)
committerNiki Roo <niki@nikiroo.be>
Mon, 29 Feb 2016 10:35:59 +0000 (11:35 +0100)
14 files changed:
src/be/nikiroo/jvcard/Card.java
src/be/nikiroo/jvcard/Contact.java
src/be/nikiroo/jvcard/i18n/Trans.java
src/be/nikiroo/jvcard/tui/KeyAction.java
src/be/nikiroo/jvcard/tui/Main.java
src/be/nikiroo/jvcard/tui/MainWindow.java
src/be/nikiroo/jvcard/tui/StringUtils.java
src/be/nikiroo/jvcard/tui/TuiLauncher.java
src/be/nikiroo/jvcard/tui/UiColors.java
src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java [moved from src/be/nikiroo/jvcard/tui/panes/ContactDetails.java with 92% similarity]
src/be/nikiroo/jvcard/tui/panes/ContactList.java
src/be/nikiroo/jvcard/tui/panes/FileList.java
src/be/nikiroo/jvcard/tui/panes/MainContentList.java
src/com/googlecode/lanterna/gui2/AbstractWindow.java

index e82ce7cc933ebb864a2b7b405875d09865d12cf9..f3e07733528f50d4ce121b67eecdba8243205ac4 100644 (file)
@@ -3,9 +3,11 @@ package be.nikiroo.jvcard;
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.io.InputStreamReader;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedList;
@@ -36,7 +38,8 @@ public class Card {
                        name = file.getName();
                }
 
-               BufferedReader buffer = new BufferedReader(new FileReader(file));
+               BufferedReader buffer = new BufferedReader(new InputStreamReader(
+                               new FileInputStream(file), "UTF-8"));
                List<String> lines = new LinkedList<String>();
                for (String line = buffer.readLine(); line != null; line = buffer
                                .readLine()) {
index 643631314d0adbbdb06356991238e2195647fd8a..553ca7680f486f34b1a38e9753dff0b4f06edb99 100644 (file)
@@ -7,6 +7,7 @@ import java.util.Map;
 
 import be.nikiroo.jvcard.parsers.Format;
 import be.nikiroo.jvcard.parsers.Parser;
+import be.nikiroo.jvcard.tui.StringUtils;
 
 /**
  * A contact is the information that represent a contact person or organisation.
@@ -155,7 +156,7 @@ public class Contact {
         * @return the {@link String} representation
         */
        public String toString(String format) {
-               return toString(format, "|", null, -1);
+               return toString(format, "|", null, -1, true, false);
        }
 
        /**
@@ -181,13 +182,17 @@ public class Contact {
         * @param width
         *            a fixed width or -1 for "as long as needed"
         * 
+        * @param unicode
+        *            allow Uniode or only ASCII characters
+        * 
         * @return the {@link String} representation
         */
        public String toString(String format, String separator, String padding,
-                       int width) {
+                       int width, boolean unicode, boolean removeAccents) {
                StringBuilder builder = new StringBuilder();
 
-               for (String str : toStringArray(format, separator, padding, width)) {
+               for (String str : toStringArray(format, separator, padding, width,
+                               unicode)) {
                        builder.append(str);
                }
 
@@ -217,10 +222,13 @@ public class Contact {
         * @param width
         *            a fixed width or -1 for "as long as needed"
         * 
+        * @param unicode
+        *            allow Uniode or only ASCII characters
+        * 
         * @return the {@link String} representation
         */
        public String[] toStringArray(String format, String separator,
-                       String padding, int width) {
+                       String padding, int width, boolean unicode) {
                if (width > -1) {
                        int numOfFields = format.split("\\|").length;
                        if (separator != null)
@@ -235,7 +243,7 @@ public class Contact {
                List<String> str = new LinkedList<String>();
 
                boolean first = true;
-               for (String s : toStringArray(format, width)) {
+               for (String s : toStringArray(format, width, unicode)) {
                        if (!first) {
                                str.add(separator);
                        }
@@ -269,10 +277,12 @@ public class Contact {
         *            the format to use
         * @param width
         *            a fixed width or -1 for "as long as needed"
-        * 
+        * @param unicode
+        *            allow Uniode or only ASCII characters
+        *
         * @return the {@link String} representation
         */
-       public String[] toStringArray(String format, int width) {
+       public String[] toStringArray(String format, int width, boolean unicode) {
                List<String> str = new LinkedList<String>();
 
                String[] formatFields = format.split("\\|");
@@ -318,11 +328,14 @@ public class Contact {
                        }
 
                        String value = getPreferredDataValue(field);
-                       if (value == null)
+                       if (value == null) {
                                value = "";
+                       } else {
+                               value = StringUtils.sanitize(value, unicode);
+                       }
 
                        if (size > -1) {
-                               value = fixedString(value, size);
+                               value = StringUtils.padString(value, size);
                        }
 
                        expandedFields[i] = expand;
@@ -371,11 +384,13 @@ public class Contact {
                                for (int i = 0; i < values.length; i++) {
                                        if (expandedFields[i]) {
                                                if (remainder > 0) {
-                                                       values[i] = values[i] + fixedString("", remainder);
+                                                       values[i] = values[i]
+                                                                       + StringUtils.padString("", remainder);
                                                        remainder = 0;
                                                }
                                                if (padPerItem > 0) {
-                                                       values[i] = values[i] + fixedString("", padPerItem);
+                                                       values[i] = values[i]
+                                                                       + StringUtils.padString("", padPerItem);
                                                }
                                        }
                                }
@@ -392,30 +407,6 @@ public class Contact {
                return str.toArray(new String[] {});
        }
 
-       /**
-        * Fix the size of the given {@link String} either with space-padding or by
-        * shortening it.
-        * 
-        * @param string
-        *            the {@link String} to fix
-        * @param size
-        *            the size of the resulting {@link String}
-        * 
-        * @return the fixed {@link String} of size <i>size</i>
-        */
-       static private String fixedString(String string, int size) {
-               int length = string.length();
-
-               if (length > size) {
-                       string = string.substring(0, size);
-               } else if (length < size) {
-                       string = string
-                                       + new String(new char[size - length]).replace('\0', ' ');
-               }
-
-               return string;
-       }
-
        /**
         * Add a {@link String} to the given {@link List}, but make sure it does not
         * exceed the maximum size, and truncate it if needed to fit.
@@ -532,7 +523,7 @@ public class Contact {
                        data.setParent(this);
                }
        }
-       
+
        /**
         * Delete this {@link Contact} from its parent {@link Card} if any.
         * 
@@ -559,7 +550,7 @@ public class Contact {
         */
        void setPristine() {
                dirty = false;
-               for (Data data: datas) {
+               for (Data data : datas) {
                        data.setPristine();
                }
        }
index 603050489461412f11c9a9c6a1dc30fd8fc1d1e2..069f81ca90bf6bb6299870e04da915c52059717b 100644 (file)
@@ -3,6 +3,10 @@ package be.nikiroo.jvcard.i18n;
 import java.util.HashMap;
 import java.util.Map;
 
+import com.googlecode.lanterna.input.KeyStroke;
+
+import be.nikiroo.jvcard.tui.UiColors;
+
 /**
  * This class manages the translation of {@link Trans#StringId}s into
  * user-understandable text.
@@ -27,6 +31,7 @@ public class Trans {
                KEY_ACTION_BACK, KEY_ACTION_HELP, // MainWindow
                KEY_ACTION_VIEW_CARD, // FileList
                KEY_ACTION_VIEW_CONTACT, KEY_ACTION_EDIT_CONTACT, KEY_ACTION_SAVE_CARD, KEY_ACTION_DELETE_CONTACT, KEY_ACTION_SWITCH_FORMAT, // ContactList
+               DEAULT_FIELD_SEPARATOR, DEAULT_FIELD_SEPARATOR_NOUTF, // MainContentList
                NULL; // Special usage
 
                public String trans() {
@@ -48,12 +53,74 @@ public class Trans {
                return instance;
        }
 
+       /**
+        * Translate the given {@link StringId} into user text.
+        * 
+        * @param stringId
+        *            the ID to translate
+        * 
+        * @return the translated text
+        */
        public String trans(StringId stringId) {
-               if (map.containsKey(stringId)) {
-                       return map.get(stringId);
+               StringId id = stringId;
+               if (!UiColors.getInstance().isUnicode()) {
+                       try {
+                               id = StringId.valueOf(stringId.toString() + "_NOUTF");
+                       } catch (IllegalArgumentException iae) {
+                               // no special _NOUTF version found
+                       }
+               }
+
+               if (map.containsKey(id)) {
+                       return map.get(id);
+               }
+
+               return id.toString();
+       }
+
+       /**
+        * Translate the given {@link KeyStroke} into a user text {@link String} of
+        * size 3.
+        * 
+        * @param key
+        *            the key to translate
+        * 
+        * @return the translated text
+        */
+       public String trans(KeyStroke key) {
+               String keyTrans = "";
+
+               switch (key.getKeyType()) {
+               case Enter:
+                       if (UiColors.getInstance().isUnicode())
+                               keyTrans = " ⤶ ";
+                       else
+                               keyTrans = "ENT";
+                       break;
+               case Tab:
+                       if (UiColors.getInstance().isUnicode())
+                               keyTrans = " ↹ ";
+                       else
+                               keyTrans = "TAB";
+
+                       break;
+               case Character:
+                       keyTrans = " " + key.getCharacter() + " ";
+                       break;
+               default:
+                       keyTrans = "" + key.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;
                }
 
-               return stringId.toString();
+               return keyTrans;
        }
 
        private Trans() {
@@ -62,6 +129,9 @@ public class Trans {
                // TODO: get from a file instead?
                map.put(StringId.NULL, "");
                map.put(StringId.DUMMY, "[dummy]");
+               // we could use: " ", "┃", "│"...
+               map.put(StringId.DEAULT_FIELD_SEPARATOR, "┃");
+               map.put(StringId.DEAULT_FIELD_SEPARATOR_NOUTF, "|");
                map.put(StringId.KEY_ACTION_BACK, "Back");
                map.put(StringId.KEY_ACTION_HELP, "Help");
                map.put(StringId.KEY_ACTION_VIEW_CONTACT, "Open");
index 70282d5fbc46b6439883a648555f616e8faf0ee1..5686e2edaa7fea1d079c22776f37a1eab2b0deb9 100644 (file)
@@ -146,14 +146,14 @@ public class KeyAction {
                return null;
        }
 
-       // override this one if needed
+       // override this one if needed, DO NOT process here as it will be call a lot
        public Object getObject() {
                return null;
        }
 
        /**
         * The method which is called when the action is performed. You can subclass
-        * it if you want to customize the action (by default, it just accepts the
+        * it if you want to customise the action (by default, it just accepts the
         * mode change (see {@link KeyAction#getMode}).
         * 
         * @return false to cancel mode change
index a95e2e3b7f2481c4f187b3fb3345b787ac739486..8e34e00b1e51a430ae93e8a29f4da07a232be61c 100644 (file)
@@ -48,6 +48,8 @@ public class Main {
                                                                + "\t--help: this here thingy\n"
                                                                + "\t--tui: force pure text mode even if swing treminal is available\n"
                                                                + "\t--gui: force swing terminal mode\n"
+                                                               + "\t--noutf: force non-utf8 mode if you need it\n"
+                                                               + "\t--noutfa: force non-utf8 and no accents mode if you need it\n"
                                                                + "everyhing else is either a file to open or a directory to open\n"
                                                                + "(we will only open 1st level files in given directories)");
                                return;
@@ -55,6 +57,8 @@ public class Main {
                                textMode = true;
                        } else if (!noMoreParams && arg.equals("--gui")) {
                                textMode = false;
+                       } else if (!noMoreParams && arg.equals("--noutf")) {
+                               UiColors.getInstance().setUnicode(false);
                        } else {
                                filesTried = true;
                                files.addAll(open(arg));
@@ -70,8 +74,10 @@ public class Main {
                        files.addAll(open("."));
                }
 
+               Window win = new MainWindow(new FileList(files));
+
                try {
-                       TuiLauncher.start(textMode, new MainWindow(new FileList(files)));
+                       TuiLauncher.start(textMode, win);
                } catch (IOException ioe) {
                        ioe.printStackTrace();
                        System.exit(2);
index 459d1b1a4dabfea6a13dd6049c2afce3c1d95b5c..13c6b3e238a4e021873d2a4694a667b023137eb8 100644 (file)
@@ -9,10 +9,11 @@ import java.util.List;
 import be.nikiroo.jvcard.Card;
 import be.nikiroo.jvcard.Contact;
 import be.nikiroo.jvcard.Data;
+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;
 
@@ -25,7 +26,6 @@ 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;
@@ -42,8 +42,7 @@ public class MainWindow extends BasicWindow {
        private List<KeyAction> actions = new LinkedList<KeyAction>();
        private List<MainContent> contentStack = new LinkedList<MainContent>();
        private boolean waitForOneKeyAnswer;
-       private KeyStroke questionKey; // key that "asked" a question, and to replay
-       // later with an answer
+       private KeyAction questionAction;
        private String titleCache;
        private Panel titlePanel;
        private Panel mainPanel;
@@ -51,7 +50,6 @@ public class MainWindow extends BasicWindow {
        private Panel actionPanel;
        private Panel messagePanel;
        private TextBox text;
-       private int width;
 
        /**
         * Create a new, empty window.
@@ -69,8 +67,6 @@ public class MainWindow extends BasicWindow {
        public MainWindow(MainContent content) {
                super(content == null ? "" : content.getTitle());
 
-               width = -1;
-
                setHints(Arrays.asList(Window.Hint.FULL_SCREEN,
                                Window.Hint.NO_DECORATIONS, Window.Hint.FIT_TERMINAL_WINDOW));
 
@@ -201,13 +197,15 @@ public class MainWindow extends BasicWindow {
         * {@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(KeyStroke key, String question, String initial) {
-               setQuestion(key, question, initial, false);
+       public void setQuestion(KeyAction action, String question, String initial) {
+               setQuestion(action, question, initial, false);
        }
 
        /**
@@ -215,41 +213,21 @@ public class MainWindow extends BasicWindow {
         * {@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(KeyStroke key, String question) {
-               setQuestion(key, question, null, true);
-       }
-
-       @Override
-       public void draw(TextGUIGraphics graphics) {
-               int width = graphics.getSize().getColumns();
-
-               if (width != this.width) {
-                       this.width = width;
-
-                       setTitle();
-
-                       if (actions != null)
-                               setActions(new ArrayList<KeyAction>(actions), false);
-               }
-
-               super.draw(graphics);
-       }
-
-       @Override
-       public void invalidate() {
-               super.invalidate();
-               for (MainContent content : contentStack) {
-                       content.invalidate();
-               }
+       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 action
+        *            the related action
         * @param question
         *            the question to ask
         * @param initial
@@ -258,9 +236,9 @@ public class MainWindow extends BasicWindow {
         *            TRUE for a one-key answer, FALSE for a text answer validated
         *            by ENTER
         */
-       private void setQuestion(KeyStroke key, String question, String initial,
+       private void setQuestion(KeyAction action, String question, String initial,
                        boolean oneKey) {
-               questionKey = key;
+               questionAction = action;
                waitForOneKeyAnswer = oneKey;
 
                messagePanel.removeAllComponents();
@@ -272,8 +250,8 @@ public class MainWindow extends BasicWindow {
 
                Label lbl = UiColors.Element.LINE_MESSAGE_QUESTION.createLabel(" "
                                + question + " ");
-               text = new TextBox(new TerminalSize(width - lbl.getSize().getColumns(),
-                               1));
+               text = new TextBox(new TerminalSize(getSize().getColumns()
+                               - lbl.getSize().getColumns(), 1));
                if (initial != null)
                        text.setText(initial);
 
@@ -289,9 +267,61 @@ public class MainWindow extends BasicWindow {
                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<KeyAction>(actions), false);
+
+               invalidate();
+       }
+
+       @Override
+       public void invalidate() {
+               super.invalidate();
+               for (MainContent content : contentStack) {
+                       content.invalidate();
+               }
+       }
+
+       @Override
+       public boolean handleInput(KeyStroke key) {
+               boolean handled = false;
+
+               if (questionAction != null) {
+                       String answer = handleQuestion(key);
+                       if (answer != null) {
+                               handled = true;
+
+                               handleAction(questionAction, answer);
+                               questionAction = null;
+                       }
+               } else {
+                       handled = handleKey(key);
+               }
+
+               if (!handled)
+                       handled = super.handleInput(key);
+
+               return handled;
+       }
+
        /**
         * Actually set the title <b>inside</b> the window. Will also call
-        * {@link BasicWindow#setTitle} with the compuited parameters.
+        * {@link BasicWindow#setTitle} with the computed parameters.
         */
        private void setTitle() {
                String prefix = " " + Main.APPLICATION_TITLE + " (version "
@@ -311,6 +341,8 @@ public class MainWindow extends BasicWindow {
 
                if (title.length() > 0) {
                        prefix = prefix + ": ";
+                       title = StringUtils.sanitize(title, UiColors.getInstance()
+                                       .isUnicode());
                }
 
                String countStr = "";
@@ -318,6 +350,11 @@ public class MainWindow extends BasicWindow {
                        countStr = "[" + count + "]";
                }
 
+               int width = -1;
+               if (getSize() != null) {
+                       width = getSize().getColumns();
+               }
+
                if (width > 0) {
                        int padding = width - prefix.length() - title.length()
                                        - countStr.length();
@@ -398,29 +435,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);
@@ -435,10 +450,14 @@ public class MainWindow extends BasicWindow {
                }
 
                // 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)));
-
                }
        }
 
@@ -487,178 +506,166 @@ public class MainWindow extends BasicWindow {
         * 
         * @return if the window handled the input
         */
-       private boolean handleInput(KeyStroke key, String answer) {
+       private boolean handleKey(KeyStroke key) {
                boolean handled = false;
 
-               // reset the message pane if no answers are pending
-               if (answer == null) {
-                       if (setMessage(null, false))
-                               return true;
-               }
+               if (setMessage(null, false))
+                       return true;
 
                for (KeyAction action : actions) {
                        if (!action.match(key))
                                continue;
 
-                       MainContent content = getContent();
                        handled = true;
 
                        if (action.onAction()) {
-                               Card card = action.getCard();
-                               Contact contact = action.getContact();
-                               Data data = action.getData();
-
-                               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);
-                                       }
+                               handleAction(action, null);
+                       }
 
-                                       break;
-                               // mode with windows:
-                               case CONTACT_LIST:
-                                       if (card != null) {
-                                               pushContent(new ContactList(card));
-                                       }
-                                       break;
-                               case CONTACT_DETAILS:
-                                       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?", "[initial]");
-                                       } else {
-                                               setMessage("You answered: " + answer, false);
-                                       }
+                       break;
+               }
 
-                                       break;
-                               case BACK:
-                                       String warning = content.getExitWarning();
-                                       if (warning != null) {
-                                               if (answer == null) {
-                                                       setQuestion(key, warning);
-                                               } else {
-                                                       setMessage(null, false);
-                                                       if (answer.equalsIgnoreCase("y")) {
-                                                               popContent();
-                                                       }
-                                               }
-                                       } else {
+               return handled;
+       }
+
+       /**
+        * 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();
+
+               Card card = action.getCard();
+               Contact contact = action.getContact();
+               Data data = action.getData();
+
+               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:
+                       if (card != null) {
+                               pushContent(new ContactList(card));
+                       }
+                       break;
+               case CONTACT_DETAILS:
+                       if (contact != null) {
+                               pushContent(new ContactDetailsRaw(contact));
+                       }
+                       break;
+               // mode interpreted by MainWindow:
+               case HELP:
+                       // TODO
+                       // setMessage("Help! I need somebody! Help!", false);
+                       if (answer == null) {
+                               setQuestion(action, "Test question?", "[initial]");
+                       } else {
+                               setMessage("You answered: " + answer, false);
+                       }
+
+                       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();
+                       }
 
-                                       if (contentStack.size() == 0) {
-                                               close();
-                                       }
+                       if (contentStack.size() == 0) {
+                               close();
+                       }
 
-                                       break;
-                               // action modes:
-                               case EDIT_DETAIL:
-                                       if (answer == null) {
-                                               if (data != null) {
-                                                       String name = data.getName();
-                                                       String value = data.getValue();
-                                                       setQuestion(key, name, value);
-                                               }
+                       break;
+               // action modes:
+               case EDIT_DETAIL:
+                       if (answer == null) {
+                               if (data != null) {
+                                       String name = data.getName();
+                                       String value = data.getValue();
+                                       setQuestion(action, name, value);
+                               }
+                       } else {
+                               setMessage(null, false);
+                               data.setValue(answer);
+                       }
+                       break;
+               case DELETE_CONTACT:
+                       if (answer == null) {
+                               if (contact != null) {
+                                       setQuestion(action, "Delete contact? [Y/N]");
+                               }
+                       } else {
+                               setMessage(null, false);
+                               if (answer.equalsIgnoreCase("y")) {
+                                       if (contact.delete()) {
+                                               content.refreshData();
+                                               invalidate();
+                                               setTitle();
                                        } else {
-                                               setMessage(null, false);
-                                               data.setValue(answer);
+                                               setMessage("Cannot delete this contact", true);
                                        }
-                                       break;
-                               case DELETE_CONTACT:
-                                       if (answer == null) {
-                                               if (contact != null) {
-                                                       setQuestion(key, "Delete contact? [Y/N]");
-                                               }
-                                       } else {
-                                               setMessage(null, false);
-                                               if (answer.equalsIgnoreCase("y")) {
-                                                       if (contact.delete()) {
-                                                               content.refreshData();
-                                                               invalidate();
-                                                               setTitle();
-                                                       } else {
-                                                               setMessage("Cannot delete this contact", true);
-                                                       }
+                               }
+                       }
+                       break;
+               case SAVE_CARD:
+                       if (answer == null) {
+                               if (card != null) {
+                                       setQuestion(action, "Save changes? [Y/N]");
+                               }
+                       } else {
+                               setMessage(null, false);
+                               if (answer.equalsIgnoreCase("y")) {
+                                       boolean ok = false;
+                                       try {
+                                               if (card.save()) {
+                                                       ok = true;
+                                                       invalidate();
                                                }
+                                       } catch (IOException ioe) {
+                                               ioe.printStackTrace();
                                        }
-                                       break;
-                               case SAVE_CARD:
-                                       if (answer == null) {
-                                               if (card != null) {
-                                                       setQuestion(key, "Save changes? [Y/N]");
-                                               }
-                                       } else {
-                                               setMessage(null, false);
-                                               if (answer.equalsIgnoreCase("y")) {
-                                                       boolean ok = false;
-                                                       try {
-                                                               if (card.save()) {
-                                                                       ok = true;
-                                                                       invalidate();
-                                                               }
-                                                       } catch (IOException ioe) {
-                                                               ioe.printStackTrace();
-                                                       }
-
-                                                       if (!ok) {
-                                                               setMessage("Cannot save to file", true);
-                                                       }
-                                               }
+
+                                       if (!ok) {
+                                               setMessage("Cannot save to file", true);
                                        }
-                                       break;
-                               default:
-                               case NONE:
-                                       break;
                                }
                        }
-
+                       break;
+               default:
+               case NONE:
                        break;
                }
-
-               return handled;
-       }
-
-       @Override
-       public boolean handleInput(KeyStroke key) {
-               boolean handled = false;
-
-               if (questionKey != null) {
-                       String answer = handleQuestion(key);
-                       if (answer != null) {
-                               handled = true;
-
-                               key = questionKey;
-                               questionKey = null;
-
-                               handleInput(key, answer);
-                       }
-               } else {
-                       handled = handleInput(key, null);
-               }
-
-               if (!handled)
-                       handled = super.handleInput(key);
-
-               return handled;
        }
 }
index e9353c83ccde43c2c77128359fe329cfc11dec17..3f5d22b2eb283a6a6fa43253affe3b0410408ee0 100644 (file)
@@ -1,14 +1,49 @@
 package be.nikiroo.jvcard.tui;
 
+import java.text.Normalizer;
+import java.text.Normalizer.Form;
+import java.util.regex.Pattern;
+
 import com.googlecode.lanterna.gui2.LinearLayout.Alignment;
 
 public class StringUtils {
+       static private Pattern marks = Pattern
+                       .compile("[\\p{InCombiningDiacriticalMarks}\\p{IsLm}\\p{IsSk}]+");
+       static private Pattern notAscii = Pattern.compile("[^\\p{ASCII}]+");
 
+       /**
+        * Fix the size of the given {@link String} either with space-padding or by
+        * shortening it.
+        * 
+        * @param text
+        *            the {@link String} to fix
+        * @param width
+        *            the size of the resulting {@link String} if the text fits or
+        *            if cut is TRUE
+        * 
+        * @return the resulting {@link String} of size <i>size</i>
+        */
        static public String padString(String text, int width) {
                return padString(text, width, true, Alignment.Beginning);
        }
 
-       // TODO: doc it, width of -1 == no change to text
+       /**
+        * Fix the size of the given {@link String} either with space-padding or by
+        * optionally shortening it.
+        * 
+        * @param text
+        *            the {@link String} to fix
+        * @param width
+        *            the size of the resulting {@link String} if the text fits or
+        *            if cut is TRUE
+        * @param cut
+        *            cut the {@link String} shorter if needed
+        * @param align
+        *            align the {@link String} in this position if we have enough
+        *            space
+        * 
+        * @return the resulting {@link String} of size <i>size</i> minimum
+        */
        static public String padString(String text, int width, boolean cut,
                        Alignment align) {
 
@@ -47,4 +82,49 @@ public class StringUtils {
                return text;
        }
 
+       /**
+        * Sanitise the given input to make it more Terminal-friendly by removing
+        * combining characters.
+        * 
+        * @param input
+        *            the input to sanitise
+        * @param allowUnicode
+        *            allow Unicode or only allow ASCII Latin characters
+        * 
+        * @return the sanitised {@link String}
+        */
+       static public String sanitize(String input, boolean allowUnicode) {
+               return sanitize(input, allowUnicode, !allowUnicode);
+       }
+
+       /**
+        * Sanitise the given input to make it more Terminal-friendly by removing
+        * combining characters.
+        * 
+        * @param input
+        *            the input to sanitise
+        * @param allowUnicode
+        *            allow Unicode or only allow ASCII Latin characters
+        * @param removeAllAccents
+        *            TRUE to replace all accentuated characters by their non
+        *            accentuated counter-parts
+        * 
+        * @return the sanitised {@link String}
+        */
+       static public String sanitize(String input, boolean allowUnicode,
+                       boolean removeAllAccents) {
+
+               if (removeAllAccents) {
+                       input = Normalizer.normalize(input, Form.NFKD);
+                       input = marks.matcher(input).replaceAll("");
+               }
+
+               input = Normalizer.normalize(input, Form.NFKC);
+
+               if (!allowUnicode) {
+                       input = notAscii.matcher(input).replaceAll("");
+               }
+
+               return input;
+       }
 }
index efaa68939421e8f24e89a4d5aa02672a34fdf310..7bee3e7bc87eb89163de7aebd677990f2641e867 100644 (file)
@@ -2,6 +2,7 @@ package be.nikiroo.jvcard.tui;
 
 import java.io.IOException;
 
+import com.googlecode.lanterna.TerminalSize;
 import com.googlecode.lanterna.TextColor;
 import com.googlecode.lanterna.gui2.DefaultWindowManager;
 import com.googlecode.lanterna.gui2.EmptySpace;
@@ -10,11 +11,12 @@ import com.googlecode.lanterna.gui2.Window;
 import com.googlecode.lanterna.screen.Screen;
 import com.googlecode.lanterna.screen.TerminalScreen;
 import com.googlecode.lanterna.terminal.DefaultTerminalFactory;
+import com.googlecode.lanterna.terminal.ResizeListener;
 import com.googlecode.lanterna.terminal.Terminal;
 
 /*
  * 
- * Change in Lanterna3 (issue and fix reported to GitHub):
+ * Change in Lanterna 3.0.0-beta2 (issue and fix reported to GitHub):
  * 
  * java.lang.StringIndexOutOfBoundsException: String index out of range: 83
  * at java.lang.String.charAt(String.java:686)
@@ -24,8 +26,7 @@ import com.googlecode.lanterna.terminal.Terminal;
  */
 
 public class TuiLauncher {
-       public static void start(Boolean textMode, Window win)
-                       throws IOException {
+       public static void start(Boolean textMode, Window win) throws IOException {
                Terminal terminal = null;
 
                DefaultTerminalFactory factory = new DefaultTerminalFactory();
@@ -38,6 +39,17 @@ public class TuiLauncher {
                        terminal = factory.createTerminalEmulator();
                }
 
+               if (win instanceof MainWindow) {
+                       MainWindow mwin = (MainWindow) win;
+                       mwin.refresh(terminal.getTerminalSize());
+                       terminal.addResizeListener(new ResizeListener() {
+                               @Override
+                               public void onResized(Terminal terminal, TerminalSize newSize) {
+                                       mwin.refresh(newSize);
+                               }
+                       });
+               }
+
                Screen screen = new TerminalScreen(terminal);
                screen.startScreen();
 
index 834641da514d1fcdf2ebf93342d3f4daa127822c..589579a6281caebdcb61d04ea04d46ff251c8fcf 100644 (file)
@@ -18,6 +18,7 @@ public class UiColors {
 
        private Map<Element, TextColor> mapForegroundColor = null;
        private Map<Element, TextColor> mapBackgroundColor = null;
+       private boolean utf = true;
 
        /**
         * Get the (unique) instance of this class.
@@ -34,10 +35,10 @@ public class UiColors {
        }
 
        public enum Element {
-               DEFAULT, // 
+               DEFAULT, //
                TITLE_MAIN, TITLE_VARIABLE, TITLE_COUNT, //
                ACTION_KEY, ACTION_DESC, //
-               LINE_MESSAGE, LINE_MESSAGE_ERR, LINE_MESSAGE_QUESTION, LINE_MESSAGE_ANS, // 
+               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;
 
                /**
@@ -67,6 +68,25 @@ public class UiColors {
                }
        }
 
+       /**
+        * Check if unicode characters should be used.
+        * 
+        * @return TRUE to allow unicode
+        */
+       public boolean isUnicode() {
+               return utf;
+       }
+
+       /**
+        * Allow or disallow unicode characters in the program.
+        * 
+        * @param utf
+        *            TRUE to allow unuciode, FALSE to only allow ASCII characters
+        */
+       public void setUnicode(boolean utf) {
+               this.utf = utf;
+       }
+
        private Label createLabel(Element el, String text) {
                Label lbl = new Label(text);
                themeLabel(el, lbl);
@@ -117,8 +137,7 @@ public class UiColors {
                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_VARIABLE, TextColor.ANSI.GREEN, TextColor.ANSI.BLUE);
                addEl(Element.TITLE_COUNT, TextColor.ANSI.RED, TextColor.ANSI.BLUE);
        }
 
@@ -126,5 +145,4 @@ public class UiColors {
                mapForegroundColor.put(el, fore);
                mapBackgroundColor.put(el, back);
        }
-
 }
similarity index 92%
rename from src/be/nikiroo/jvcard/tui/panes/ContactDetails.java
rename to src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java
index 1fad960e46730006273c8ab47943fab1a85eb11d..506eeff96f28481b2c5a9c0303ccde3e40e6ad83 100644 (file)
@@ -10,16 +10,17 @@ import be.nikiroo.jvcard.Data;
 import be.nikiroo.jvcard.TypeInfo;
 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.StringUtils;
 import be.nikiroo.jvcard.tui.UiColors.Element;
 
-public class ContactDetails extends MainContentList {
+public class ContactDetailsRaw extends MainContentList {
        private Contact contact;
        private int mode;
 
-       public ContactDetails(Contact contact) {
+       public ContactDetailsRaw(Contact contact) {
                super(null, null);
 
                this.contact = contact;
@@ -79,6 +80,9 @@ public class ContactDetails extends MainContentList {
 
                value = valueBuilder.toString();
 
+               name = StringUtils.sanitize(name, UiColors.getInstance().isUnicode());
+               value = StringUtils.sanitize(value, UiColors.getInstance().isUnicode());
+
                name = StringUtils.padString(name, SIZE_COL_1);
                value = StringUtils.padString(value, width - SIZE_COL_1
                                - getSeparator().length() - 2);
index d5cf26b2be381f7db18134ef960a4d26df825711..7d1037006c13ea2b0998ca3c40976c8d8049df27 100644 (file)
@@ -73,7 +73,7 @@ public class ContactList extends MainContentList {
        public List<KeyAction> getKeyBindings() {
                List<KeyAction> actions = new LinkedList<KeyAction>();
 
-               // TODO add, del, save...
+               // TODO add
                actions.add(new KeyAction(Mode.CONTACT_DETAILS, 'e',
                                Trans.StringId.KEY_ACTION_EDIT_CONTACT) {
                        @Override
@@ -155,7 +155,7 @@ public class ContactList extends MainContentList {
                width -= 2; // dirty mark space
 
                String[] array = contact.toStringArray(format, getSeparator(), " ",
-                               width);
+                               width, UiColors.getInstance().isUnicode());
 
                if (contact.isDirty()) {
                        parts.add(new TextPart(" ", el));
index dfc3b73114cf3ffda2f2989d74841a1e602a9e69..66e35ce2e43d9985507a149f2f4b8f09498cd2ac 100644 (file)
@@ -72,6 +72,8 @@ public class FileList extends MainContentList {
 
                String name = files.get(index).getName();
 
+               name = StringUtils.sanitize(name, UiColors.getInstance().isUnicode());
+
                count = " " + StringUtils.padString(count, SIZE_COL_1) + " ";
                name = " "
                                + StringUtils.padString(name, width - SIZE_COL_1
index 38b775c2998af5986cf06e31440bd663b42d90d5..cac03e2426c615b9e61b2420d049408ed4d15f67 100644 (file)
@@ -3,15 +3,17 @@ package be.nikiroo.jvcard.tui.panes;
 import java.util.LinkedList;
 import java.util.List;
 
+import be.nikiroo.jvcard.i18n.Trans.StringId;
+import be.nikiroo.jvcard.tui.StringUtils;
 import be.nikiroo.jvcard.tui.UiColors;
 import be.nikiroo.jvcard.tui.UiColors.Element;
 
 import com.googlecode.lanterna.TextColor;
+import com.googlecode.lanterna.gui2.AbstractListBox.ListItemRenderer;
 import com.googlecode.lanterna.gui2.ActionListBox;
 import com.googlecode.lanterna.gui2.Direction;
 import com.googlecode.lanterna.gui2.LinearLayout;
 import com.googlecode.lanterna.gui2.TextGUIGraphics;
-import com.googlecode.lanterna.gui2.AbstractListBox.ListItemRenderer;
 
 abstract public class MainContentList extends MainContent implements Runnable {
        private ActionListBox lines;
@@ -59,62 +61,58 @@ abstract public class MainContentList extends MainContent implements Runnable {
 
                lines = new ActionListBox();
 
-               lines
-                               .setListItemRenderer(new ListItemRenderer<Runnable, ActionListBox>() {
-                                       /**
-                                        * This is the main drawing method for a single list box
-                                        * item, it applies the current theme to setup the colors
-                                        * and then calls {@code getLabel(..)} and draws the result
-                                        * using the supplied {@code TextGUIGraphics}. The graphics
-                                        * object is created just for this item and is restricted so
-                                        * that it can only draw on the area this item is occupying.
-                                        * The top-left corner (0x0) should be the starting point
-                                        * when drawing the item.
-                                        * 
-                                        * @param graphics
-                                        *            Graphics object to draw with
-                                        * @param listBox
-                                        *            List box we are drawing an item from
-                                        * @param index
-                                        *            Index of the item we are drawing
-                                        * @param item
-                                        *            The item we are drawing
-                                        * @param selected
-                                        *            Will be set to {@code true} if the item is
-                                        *            currently selected, otherwise {@code false},
-                                        *            but please notice what context 'selected'
-                                        *            refers to here (see {@code setSelectedIndex})
-                                        * @param focused
-                                        *            Will be set to {@code true} if the list box
-                                        *            currently has input focus, otherwise {@code
-                                        *            false}
-                                        */
-                                       public void drawItem(TextGUIGraphics graphics,
-                                                       ActionListBox listBox, int index, Runnable item,
-                                                       boolean selected, boolean focused) {
-
-                                               // width "-1" to reserve space for the optional vertical
-                                               // scroll bar
-                                               List<TextPart> parts = MainContentList.this.getLabel(
-                                                               index, lines.getSize().getColumns() - 1,
-                                                               selected, focused);
-
-                                               int position = 0;
-                                               for (TextPart part : parts) {
-                                                       graphics.setForegroundColor(part
-                                                                       .getForegroundColor());
-                                                       graphics.setBackgroundColor(part
-                                                                       .getBackgroundColor());
-                                                       String label = part.getText();
-
-                                                       graphics.putString(position, 0, label);
-                                                       position += label.length();
-                                               }
-                                       }
-                               });
-
-               addComponent(lines, LinearLayout
-                               .createLayoutData(LinearLayout.Alignment.Fill));
+               lines.setListItemRenderer(new ListItemRenderer<Runnable, ActionListBox>() {
+                       /**
+                        * This is the main drawing method for a single list box item, it
+                        * applies the current theme to setup the colors and then calls
+                        * {@code getLabel(..)} and draws the result using the supplied
+                        * {@code TextGUIGraphics}. The graphics object is created just for
+                        * this item and is restricted so that it can only draw on the area
+                        * this item is occupying. The top-left corner (0x0) should be the
+                        * starting point when drawing the item.
+                        * 
+                        * @param graphics
+                        *            Graphics object to draw with
+                        * @param listBox
+                        *            List box we are drawing an item from
+                        * @param index
+                        *            Index of the item we are drawing
+                        * @param item
+                        *            The item we are drawing
+                        * @param selected
+                        *            Will be set to {@code true} if the item is currently
+                        *            selected, otherwise {@code false}, but please notice
+                        *            what context 'selected' refers to here (see
+                        *            {@code setSelectedIndex})
+                        * @param focused
+                        *            Will be set to {@code true} if the list box currently
+                        *            has input focus, otherwise {@code false}
+                        */
+                       public void drawItem(TextGUIGraphics graphics,
+                                       ActionListBox listBox, int index, Runnable item,
+                                       boolean selected, boolean focused) {
+
+                               // width "-1" to reserve space for the optional vertical
+                               // scroll bar
+                               List<TextPart> parts = MainContentList.this.getLabel(index,
+                                               lines.getSize().getColumns() - 1, selected, focused);
+
+                               int position = 0;
+                               for (TextPart part : parts) {
+                                       graphics.setForegroundColor(part.getForegroundColor());
+                                       graphics.setBackgroundColor(part.getBackgroundColor());
+
+                                       String label = StringUtils.sanitize(part.getText(),
+                                                       UiColors.getInstance().isUnicode());
+
+                                       graphics.putString(position, 0, label);
+                                       position += label.length();
+                               }
+                       }
+               });
+
+               addComponent(lines,
+                               LinearLayout.createLayoutData(LinearLayout.Alignment.Fill));
        }
 
        /**
@@ -152,16 +150,14 @@ abstract public class MainContentList extends MainContent implements Runnable {
        public void setSelectedIndex(int index) {
                lines.setSelectedIndex(index);
        }
-       
-       
+
        /**
         * Return the default content separator for text fields.
         * 
         * @return the separator
         */
        public String getSeparator() {
-               // we could use: " ", "┃", "│"...
-               return "┃";
+               return StringId.DEAULT_FIELD_SEPARATOR.trans();
        }
 
        @Override
index 4e55f3d54d63d51556eeb2319592733de78ccf88..6c9c5fdfab05742d32a5e52fd5c7994ac744a859 100644 (file)
@@ -123,7 +123,7 @@ public abstract class AbstractWindow extends AbstractBasePane implements Window
             getComponent().invalidate();
         }
         setSize(graphics.getSize(), false);
-        super.draw(graphics);
+       super.draw(graphics);
     }
 
     @Override