Update lanterna, fix bugs, implement save...
authorNiki Roo <roo.niki@gmail.com>
Fri, 26 Feb 2016 15:50:27 +0000 (16:50 +0100)
committerNiki Roo <roo.niki@gmail.com>
Fri, 26 Feb 2016 15:50:27 +0000 (16:50 +0100)
38 files changed:
lanterna/2016-02-26 - 10:25 lanterna-master.zip [new file with mode: 0644]
src/be/nikiroo/jvcard/Card.java
src/be/nikiroo/jvcard/Contact.java
src/be/nikiroo/jvcard/Data.java
src/be/nikiroo/jvcard/i18n/Trans.java
src/be/nikiroo/jvcard/parsers/AbookParser.java
src/be/nikiroo/jvcard/parsers/Vcard21Parser.java
src/be/nikiroo/jvcard/test/TestCli.java [deleted file]
src/be/nikiroo/jvcard/tui/ContactDetails.java [deleted file]
src/be/nikiroo/jvcard/tui/ContactList.java [deleted file]
src/be/nikiroo/jvcard/tui/KeyAction.java
src/be/nikiroo/jvcard/tui/Main.java
src/be/nikiroo/jvcard/tui/MainContent.java [deleted file]
src/be/nikiroo/jvcard/tui/MainWindow.java
src/be/nikiroo/jvcard/tui/panes/ContactDetails.java
src/be/nikiroo/jvcard/tui/panes/ContactList.java
src/be/nikiroo/jvcard/tui/panes/FileList.java
src/be/nikiroo/jvcard/tui/panes/MainContent.java
src/be/nikiroo/jvcard/tui/panes/MainContentList.java
src/com/googlecode/lanterna/TerminalTextUtils.java
src/com/googlecode/lanterna/gui2/AbstractTextGUIThread.java
src/com/googlecode/lanterna/gui2/BorderLayout.java
src/com/googlecode/lanterna/gui2/TextGUIThread.java
src/com/googlecode/lanterna/terminal/swing/AWTTerminal.java
src/com/googlecode/lanterna/terminal/swing/GraphicalTerminalImplementation.java
src/com/googlecode/lanterna/terminal/swing/SwingTerminal.java
src/com/googlecode/lanterna/terminal/swing/TerminalScrollController.java
src/resources/default-theme.properties [new file with mode: 0644]
src/resources/multilang/lanterna-ui.properties [new file with mode: 0644]
src/resources/multilang/lanterna-ui_da.properties [new file with mode: 0644]
src/resources/multilang/lanterna-ui_de.properties [new file with mode: 0644]
src/resources/multilang/lanterna-ui_fi.properties [new file with mode: 0644]
src/resources/multilang/lanterna-ui_fr.properties [new file with mode: 0644]
src/resources/multilang/lanterna-ui_ja.properties [new file with mode: 0644]
src/resources/multilang/lanterna-ui_nb.properties [new file with mode: 0644]
src/resources/multilang/lanterna-ui_nn.properties [new file with mode: 0644]
src/resources/multilang/lanterna-ui_no.properties [new file with mode: 0644]
src/resources/multilang/lanterna-ui_sv.properties [new file with mode: 0644]

diff --git a/lanterna/2016-02-26 - 10:25 lanterna-master.zip b/lanterna/2016-02-26 - 10:25 lanterna-master.zip
new file mode 100644 (file)
index 0000000..bb45a36
Binary files /dev/null and b/lanterna/2016-02-26 - 10:25 lanterna-master.zip differ
index 65ab6fa5225b91fdacdd3430fdc52abdbb317395..e82ce7cc933ebb864a2b7b405875d09865d12cf9 100644 (file)
@@ -6,6 +6,7 @@ import java.io.File;
 import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
@@ -25,9 +26,11 @@ public class Card {
        private File file;
        private boolean dirty;
        private String name;
+       private Format format;
 
        public Card(File file, Format format) throws IOException {
                this.file = file;
+               this.format = format;
 
                if (file != null) {
                        name = file.getName();
@@ -39,15 +42,42 @@ public class Card {
                                .readLine()) {
                        lines.add(line);
                }
+               buffer.close();
 
                load(lines, format);
-               dirty = false; // initial load, so no change yet
+               dirty = false; // initial load, so no change yet, so no need to call
+                                               // setPristine()
        }
 
-       public List<Contact> getContacts() {
+       /**
+        * Return the full list of {@link Contact}s. Please use responsibly (this is
+        * the original list, do not modify the list itself).
+        * 
+        * @return the list of {@link Contact}s
+        */
+       public List<Contact> getContactsList() {
                return contacts;
        }
 
+       /**
+        * Return the list of {@link Contact}s. Note that this list is a copy.
+        * 
+        * @return the list of {@link Contact}s
+        */
+       public List<Contact> getContacts() {
+               ArrayList<Contact> list = new ArrayList<Contact>(size());
+               list.addAll(contacts);
+               return list;
+       }
+
+       public int size() {
+               return contacts.size();
+       }
+
+       public Contact get(int index) {
+               return contacts.get(index);
+       }
+
        public boolean saveAs(File file, Format format) throws IOException {
                if (file == null)
                        return false;
@@ -57,13 +87,13 @@ public class Card {
                writer.close();
 
                if (file.equals(this.file)) {
-                       dirty = false;
+                       setPristine();
                }
 
                return true;
        }
 
-       public boolean save(Format format, boolean bKeys) throws IOException {
+       public boolean save() throws IOException {
                return saveAs(file, format);
        }
 
@@ -109,4 +139,15 @@ public class Card {
        void setDirty() {
                dirty = true;
        }
+
+       /**
+        * Notify this element <i>and all its descendants</i> that it is in pristine
+        * state (as opposed to dirty).
+        */
+       void setPristine() {
+               dirty = false;
+               for (Contact contact : contacts) {
+                       contact.setPristine();
+               }
+       }
 }
index 21ed69b86f66e532adee8bc06a5bf317d00f9021..643631314d0adbbdb06356991238e2195647fd8a 100644 (file)
@@ -286,7 +286,7 @@ public class Contact {
                        for (int i = 0; i < formatFields.length; i++) {
                                str.add("");
                        }
-                       
+
                        return str.toArray(new String[] {});
                }
 
@@ -526,10 +526,41 @@ public class Contact {
                        this.parent.setDirty();
        }
 
-       public void setParent(Card parent) {
+       void setParent(Card parent) {
                this.parent = parent;
                for (Data data : datas) {
                        data.setParent(this);
                }
        }
+       
+       /**
+        * Delete this {@link Contact} from its parent {@link Card} if any.
+        * 
+        * @return TRUE in case of success
+        */
+       public boolean delete() {
+               if (parent != null) {
+                       List<Contact> list = parent.getContactsList();
+                       for (int i = 0; i < list.size(); i++) {
+                               if (list.get(i) == this) {
+                                       list.remove(i);
+                                       parent.setDirty();
+                                       return true;
+                               }
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * Notify this element <i>and all its descendants</i> that it is in pristine
+        * state (as opposed to dirty).
+        */
+       void setPristine() {
+               dirty = false;
+               for (Data data: datas) {
+                       data.setPristine();
+               }
+       }
 }
index 993533263eaf6d6f2ab09a2d05339b82f0a4af99..3f62b7e8f0f6158aff806a4baa06ff2b59ef8dc2 100644 (file)
@@ -45,6 +45,14 @@ public class Data {
                return value;
        }
 
+       public void setValue(String value) {
+               if ((value == null && this.value != null)
+                               || (value != null && !value.equals(this.value))) {
+                       this.value = value;
+                       setDirty();
+               }
+       }
+
        public String getGroup() {
                return group;
        }
@@ -72,7 +80,29 @@ public class Data {
                return dirty;
        }
 
-       public void setParent(Contact parent) {
+       /**
+        * Notify that this element has unsaved changes, and notify its parent of
+        * the same if any.
+        */
+       protected void setDirty() {
+               this.dirty = true;
+               if (this.parent != null)
+                       this.parent.setDirty();
+       }
+
+       /**
+        * Notify this element <i>and all its descendants</i> that it is in pristine
+        * state (as opposed to dirty).
+        */
+       void setPristine() {
+               dirty = false;
+               for (TypeInfo type : types) {
+                       // TODO ?
+               }
+       }
+
+       void setParent(Contact parent) {
                this.parent = parent;
        }
+
 }
index cea5df4c1ba0eb5f53bb7eb574611d61cb5a4172..603050489461412f11c9a9c6a1dc30fd8fc1d1e2 100644 (file)
@@ -23,8 +23,11 @@ public class Trans {
         * 
         */
        public enum StringId {
-               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;
+               DUMMY, // <-- TODO : remove
+               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
+               NULL; // Special usage
 
                public String trans() {
                        return Trans.getInstance().trans(this);
@@ -60,8 +63,11 @@ public class Trans {
                map.put(StringId.NULL, "");
                map.put(StringId.DUMMY, "[dummy]");
                map.put(StringId.KEY_ACTION_BACK, "Back");
-               map.put(StringId.KEY_ACTION_VIEW_CONTACT, "view");
-               map.put(StringId.KEY_ACTION_EDIT_CONTACT, "edit");
+               map.put(StringId.KEY_ACTION_HELP, "Help");
+               map.put(StringId.KEY_ACTION_VIEW_CONTACT, "Open");
+               map.put(StringId.KEY_ACTION_VIEW_CARD, "Open");
+               map.put(StringId.KEY_ACTION_EDIT_CONTACT, "Edit");
+               map.put(StringId.KEY_ACTION_DELETE_CONTACT, "Delete");
                map.put(StringId.KEY_ACTION_SWITCH_FORMAT, "Change view");
        }
 }
index 92bef0140ee92a67b84b41472f78a0debace72c9..2fd0ca2f15a61e1e92833d4a373364b6c4ccde05 100644 (file)
@@ -90,7 +90,7 @@ public class AbookParser {
        public static String toString(Card card) {
                StringBuilder builder = new StringBuilder();
 
-               for (Contact contact : card.getContacts()) {
+               for (Contact contact : card.getContactsList()) {
                        builder.append(toString(contact, -1));
                }
 
index 6cb4635b27fe1aba3394618d1b493170dfd637b0..cc7421672d62ec3b74d3f74ad92ece2f65478a1a 100644 (file)
@@ -1,5 +1,6 @@
 package be.nikiroo.jvcard.parsers;
 
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -9,12 +10,38 @@ import be.nikiroo.jvcard.Data;
 import be.nikiroo.jvcard.TypeInfo;
 
 public class Vcard21Parser {
-       public static List<Contact> parse(List<String> lines) {
+       public static List<Contact> parse(Iterable<String> textData) {
+               Iterator<String> lines = textData.iterator();
                List<Contact> contacts = new LinkedList<Contact>();
                List<Data> datas = null;
 
-               for (String l : lines) {
-                       String line = l.trim();
+               String nextRawLine = null;
+               if (lines.hasNext()) {
+                       nextRawLine = lines.next();
+                       while (lines.hasNext() && isContinuation(nextRawLine)) {
+                               // BAD INPUT FILE. IGNORE.
+                               System.err
+                                               .println("VCARD Parser warning: CONTINUATION line seen before any data line");
+                               nextRawLine = lines.next();
+                       }
+               }
+
+               while (nextRawLine != null) {
+                       StringBuilder rawLine = new StringBuilder(nextRawLine.trim());
+                       if (lines.hasNext())
+                               nextRawLine = lines.next();
+                       else
+                               nextRawLine = null;
+
+                       while (isContinuation(nextRawLine)) {
+                               rawLine.append(nextRawLine.trim());
+                               if (lines.hasNext())
+                                       nextRawLine = lines.next();
+                               else
+                                       nextRawLine = null;
+                       }
+
+                       String line = rawLine.toString();
                        if (line.equals("BEGIN:VCARD")) {
                                datas = new LinkedList<Data>();
                        } else if (line.equals("END:VCARD")) {
@@ -98,8 +125,8 @@ public class Vcard21Parser {
                                }
                        }
                        builder.append(':');
-                       
-                       //TODO: bkey!
+
+                       // TODO: bkey!
                        builder.append(data.getValue());
                        builder.append("\r\n");
                }
@@ -112,10 +139,26 @@ public class Vcard21Parser {
        public static String toString(Card card) {
                StringBuilder builder = new StringBuilder();
 
-               for (Contact contact : card.getContacts()) {
+               for (Contact contact : card.getContactsList()) {
                        builder.append(toString(contact, -1));
                }
+               
+               builder.append("\r\n");
 
                return builder.toString();
        }
+
+       /**
+        * Check if the given line is a continuation line or not.
+        * 
+        * @param line
+        *            the line to check
+        * 
+        * @return TRUE if the line is a continuation line
+        */
+       private static boolean isContinuation(String line) {
+               if (line != null && line.length() > 0)
+                       return (line.charAt(0) == ' ' || line.charAt(0) == '\t');
+               return false;
+       }
 }
diff --git a/src/be/nikiroo/jvcard/test/TestCli.java b/src/be/nikiroo/jvcard/test/TestCli.java
deleted file mode 100644 (file)
index 6cda3e6..0000000
+++ /dev/null
@@ -1,122 +0,0 @@
-package be.nikiroo.jvcard.test;
-
-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.MainWindow;
-import be.nikiroo.jvcard.tui.TuiLauncher;
-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 TestCli {
-       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 Table test2() 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));
-                               }
-                       }
-               });
-
-               return table;
-       }
-
-       static private void test() throws IOException {
-               // Setup terminal and screen layers
-               Terminal terminal = new DefaultTerminalFactory().createTerminal();
-               Screen screen = new TerminalScreen(terminal);
-               screen.startScreen();
-
-               // Create panel to hold components
-               Panel panel = new Panel();
-               panel.setLayoutManager(new GridLayout(2));
-
-               panel.addComponent(new Label("Forename"));
-               panel.addComponent(new TextBox());
-
-               panel.addComponent(new Label("Surname"));
-               panel.addComponent(new TextBox());
-
-               panel.addComponent(new EmptySpace(new TerminalSize(0, 0))); // Empty
-               // space
-               // underneath
-               // labels
-               panel.addComponent(new Button("Submit"));
-
-               // Create window to hold the panel
-               BasicWindow window = new BasicWindow();
-               window.setComponent(panel);
-
-               // Create gui and start gui
-               MultiWindowTextGUI gui = new MultiWindowTextGUI(screen,
-                               new DefaultWindowManager(), new EmptySpace(TextColor.ANSI.BLUE));
-               gui.addWindowAndWait(window);
-       }
-}
diff --git a/src/be/nikiroo/jvcard/tui/ContactDetails.java b/src/be/nikiroo/jvcard/tui/ContactDetails.java
deleted file mode 100644 (file)
index 5107fa0..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-package be.nikiroo.jvcard.tui;
-
-import java.util.List;
-
-import be.nikiroo.jvcard.Contact;
-import be.nikiroo.jvcard.Data;
-import be.nikiroo.jvcard.tui.KeyAction.DataType;
-import be.nikiroo.jvcard.tui.KeyAction.Mode;
-
-import com.googlecode.lanterna.gui2.Direction;
-import com.googlecode.lanterna.gui2.Interactable;
-import com.googlecode.lanterna.gui2.Label;
-
-public class ContactDetails extends MainContent {
-       private Contact contact;
-
-       public ContactDetails(Contact contact) {
-               super(Direction.VERTICAL);
-
-               this.contact = contact;
-
-               for (Data data : contact.getContent()) {
-                       addComponent(new Label(data.getName() + ": " + data.getValue()));
-               }
-       }
-
-       @Override
-       public DataType getDataType() {
-               return DataType.CONTACT;
-       }
-
-       @Override
-       public String getExitWarning() {
-               // TODO Auto-generated method stub
-               return null;
-       }
-
-       @Override
-       public List<KeyAction> getKeyBindings() {
-               // TODO Auto-generated method stub
-               return null;
-       }
-
-       @Override
-       public Mode getMode() {
-               return Mode.CONTACT_DETAILS;
-       }
-
-       @Override
-       public String getTitle() {
-               // TODO Auto-generated method stub
-               return null;
-       }
-
-       @Override
-       public String move(int x, int y) {
-               // TODO Auto-generated method stub
-               return null;
-       }
-}
diff --git a/src/be/nikiroo/jvcard/tui/ContactList.java b/src/be/nikiroo/jvcard/tui/ContactList.java
deleted file mode 100644 (file)
index 67e5952..0000000
+++ /dev/null
@@ -1,204 +0,0 @@
-package be.nikiroo.jvcard.tui;
-
-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.DataType;
-import be.nikiroo.jvcard.tui.KeyAction.Mode;
-
-import com.googlecode.lanterna.gui2.ActionListBox;
-import com.googlecode.lanterna.gui2.Direction;
-import com.googlecode.lanterna.gui2.Interactable;
-import com.googlecode.lanterna.gui2.LinearLayout;
-import com.googlecode.lanterna.gui2.TextGUIGraphics;
-import com.googlecode.lanterna.gui2.AbstractListBox.ListItemRenderer;
-import com.googlecode.lanterna.input.KeyType;
-
-public class ContactList extends MainContent implements Runnable {
-       private Card card;
-       private ActionListBox lines;
-
-       private List<String> formats = new LinkedList<String>();
-       private int selectedFormat = -1;
-       private String format = "";
-
-       public ContactList(Card card) {
-               super(Direction.VERTICAL);
-
-               // TODO: should get that in an INI file
-               formats.add("NICKNAME@3|FN@+|EMAIL@30");
-               formats.add("FN@+|EMAIL@40");
-               switchFormat();
-
-               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) {
-
-                                               if (selected && focused) {
-                                                       graphics
-                                                                       .setForegroundColor(UiColors.Element.CONTACT_LINE_SELECTED
-                                                                                       .getForegroundColor());
-                                                       graphics
-                                                                       .setBackgroundColor(UiColors.Element.CONTACT_LINE_SELECTED
-                                                                                       .getBackgroundColor());
-                                               } else {
-                                                       graphics
-                                                                       .setForegroundColor(UiColors.Element.CONTACT_LINE
-                                                                                       .getForegroundColor());
-                                                       graphics
-                                                                       .setBackgroundColor(UiColors.Element.CONTACT_LINE
-                                                                                       .getBackgroundColor());
-                                               }
-
-                                               String label = getLabel(listBox, index, item);
-                                               // label = TerminalTextUtils.fitString(label,
-                                               // graphics.getSize().getColumns());
-
-                                               Contact c = ContactList.this.card.getContacts().get(
-                                                               index);
-
-                                               // we could use: " ", "┃", "│"...
-                                               //TODO: why +5 ?? padding problem?
-                                               label = c.toString(format, " ┃ ", lines.getSize().getColumns() + 5);
-
-                                               graphics.putString(0, 0, label);
-                                       }
-                               });
-
-               addComponent(lines, LinearLayout
-                               .createLayoutData(LinearLayout.Alignment.Fill));
-
-               setCard(card);
-       }
-
-       private void switchFormat() {
-               if (formats.size() == 0)
-                       return;
-
-               selectedFormat++;
-               if (selectedFormat >= formats.size()) {
-                       selectedFormat = 0;
-               }
-
-               format = formats.get(selectedFormat);
-
-               if (lines != null)
-                       lines.invalidate();
-       }
-
-       public void setCard(Card card) {
-               lines.clearItems();
-               this.card = card;
-
-               if (card != null) {
-                       for (int i = 0; i < card.getContacts().size(); i++) {
-                               lines.addItem("[contact line]", this);
-                       }
-               }
-
-               lines.setSelectedIndex(0);
-       }
-
-       @Override
-       public void run() {
-               // TODO: item selected.
-               // should we do something?
-       }
-
-       @Override
-       public String getExitWarning() {
-               if (card != null && card.isDirty()) {
-                       return "Some of your contact information is not saved";
-               }
-               return null;
-       }
-
-       @Override
-       public List<KeyAction> getKeyBindings() {
-               List<KeyAction> actions = new LinkedList<KeyAction>();
-
-               // TODO del, save...
-               actions.add(new KeyAction(Mode.CONTACT_DETAILS, 'e',
-                               Trans.StringId.KEY_ACTION_EDIT_CONTACT) {
-                       @Override
-                       public Object getObject() {
-                               int index = lines.getSelectedIndex();
-                               return card.getContacts().get(index);
-                       }
-               });
-               actions.add(new KeyAction(Mode.CONTACT_DETAILS, KeyType.Enter,
-                               Trans.StringId.KEY_ACTION_VIEW_CONTACT) {
-                       @Override
-                       public Object getObject() {
-                               int index = lines.getSelectedIndex();
-                               return card.getContacts().get(index);
-                       }
-               });
-               actions.add(new KeyAction(Mode.SWICTH_FORMAT, KeyType.Tab,
-                               Trans.StringId.KEY_ACTION_SWITCH_FORMAT) {
-                       @Override
-                       public boolean onAction() {
-                               switchFormat();
-                               return false;
-                       }
-               });
-
-               return actions;
-       }
-
-       public DataType getDataType() {
-               return DataType.CARD;
-       }
-
-       public Mode getMode() {
-               return Mode.CONTACT_LIST;
-       }
-
-       @Override
-       public String move(int x, int y) {
-               lines.setSelectedIndex(lines.getSelectedIndex() + x);
-               // TODO: y?
-               return null;
-       }
-
-       @Override
-       public String getTitle() {
-               // TODO Auto-generated method stub
-               return null;
-       }
-}
index ac1b093849a279ddd26d09fb3a748582c22466d9..70282d5fbc46b6439883a648555f616e8faf0ee1 100644 (file)
@@ -27,7 +27,7 @@ public class KeyAction {
         * 
         */
        public enum Mode {
-               NONE, MOVE, BACK, HELP, FILE_LIST, CONTACT_LIST, CONTACT_DETAILS, SWICTH_FORMAT,
+               NONE, MOVE, BACK, HELP, FILE_LIST, CONTACT_LIST, CONTACT_DETAILS, EDIT_DETAIL, DELETE_CONTACT, SAVE_CARD,
        }
 
        public enum DataType {
index ab6849c552de2dcf3085e9b0bbebfbb573ed9789..a95e2e3b7f2481c4f187b3fb3345b787ac739486 100644 (file)
@@ -5,22 +5,13 @@ 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;
@@ -28,30 +19,63 @@ import com.googlecode.lanterna.screen.TerminalScreen;
 import com.googlecode.lanterna.terminal.DefaultTerminalFactory;
 import com.googlecode.lanterna.terminal.Terminal;
 
+/**
+ * This class contains the runnable Main method. It will parse the user supplied
+ * parameters and take action based upon those. Most of the time, it will start
+ * a MainWindow.
+ * 
+ * @author niki
+ *
+ */
 public class Main {
        public static final String APPLICATION_TITLE = "jVcard";
-       public static final String APPLICATION_VERSION = "0.9";
+       public static final String APPLICATION_VERSION = "1.0-beta1-dev";
 
-       public static void main(String[] args) throws IOException {
+       public static void main(String[] args) {
                Boolean textMode = null;
-               if (args.length > 0 && args[0].equals("--tui"))
-                       textMode = true;
-               if (args.length > 0 && args[0].equals("--gui"))
-                       textMode = false;
+               boolean noMoreParams = false;
+               boolean filesTried = 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));
-               //
+               for (String arg : args) {
+                       if (!noMoreParams && arg.equals("--")) {
+                               noMoreParams = true;
+                       } else if (!noMoreParams && arg.equals("--help")) {
+                               System.out
+                                               .println("TODO: implement some help text.\n"
+                                                               + "Usable switches:\n"
+                                                               + "\t--: stop looking for switches\n"
+                                                               + "\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"
+                                                               + "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;
+                       } else if (!noMoreParams && arg.equals("--tui")) {
+                               textMode = true;
+                       } else if (!noMoreParams && arg.equals("--gui")) {
+                               textMode = false;
+                       } else {
+                               filesTried = true;
+                               files.addAll(open(arg));
+                       }
+               }
+
+               if (files.size() == 0) {
+                       if (filesTried) {
+                               System.exit(1);
+                               return;
+                       }
 
-               TuiLauncher.start(textMode, win);
+                       files.addAll(open("."));
+               }
+
+               try {
+                       TuiLauncher.start(textMode, new MainWindow(new FileList(files)));
+               } catch (IOException ioe) {
+                       ioe.printStackTrace();
+                       System.exit(2);
+               }
 
                /*
                 * String file = args.length > 0 ? args[0] : null; String file2 =
@@ -71,6 +95,35 @@ public class Main {
                 */
        }
 
+       /**
+        * Open the given path and add all its files if it is a directory or just
+        * this one if not to the returned list.
+        * 
+        * @param path
+        *            the path to open
+        * 
+        * @return the list of opened files
+        */
+       static private List<File> open(String path) {
+               List<File> files = new LinkedList<File>();
+
+               File file = new File(path);
+               if (file.exists()) {
+                       if (file.isDirectory()) {
+                               for (File subfile : file.listFiles()) {
+                                       if (!subfile.isDirectory())
+                                               files.add(subfile);
+                               }
+                       } else {
+                               files.add(file);
+                       }
+               } else {
+                       System.err.println("File or directory not found: \"" + path + "\"");
+               }
+
+               return files;
+       }
+
        static private void fullTestTable() throws IOException {
                final Table<String> table = new Table<String>("Column 1", "Column 2",
                                "Column 3");
@@ -88,9 +141,9 @@ public class Main {
 
                Window win = new BasicWindow();
                win.setComponent(table);
-               
+
                DefaultTerminalFactory factory = new DefaultTerminalFactory();
-                       Terminal terminal = factory.createTerminal();
+               Terminal terminal = factory.createTerminal();
 
                Screen screen = new TerminalScreen(terminal);
                screen.startScreen();
diff --git a/src/be/nikiroo/jvcard/tui/MainContent.java b/src/be/nikiroo/jvcard/tui/MainContent.java
deleted file mode 100644 (file)
index 1977358..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-package be.nikiroo.jvcard.tui;
-
-import java.util.List;
-
-import com.googlecode.lanterna.gui2.Direction;
-import com.googlecode.lanterna.gui2.Interactable;
-import com.googlecode.lanterna.gui2.LinearLayout;
-import com.googlecode.lanterna.gui2.Panel;
-
-/**
- * This class represents the main content that you can see in this application
- * (i.e., everything but the title and the actions keys is a {@link Panel}
- * extended from this class).
- * 
- * @author niki
- * 
- */
-abstract public class MainContent extends Panel {
-
-       public MainContent() {
-               super();
-       }
-
-       public MainContent(Direction dir) {
-               super();
-               LinearLayout layout = new LinearLayout(dir);
-               layout.setSpacing(0);
-               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}.
-        * 
-        * @return the linked mode
-        */
-       abstract public KeyAction.Mode getMode();
-
-       /**
-        * The kind of data displayed by this {@link MainContent}.
-        * 
-        * @return the kind of data displayed
-        */
-       abstract public KeyAction.DataType getDataType();
-
-       /**
-        * Returns the list of actions and the keys that are bound to it.
-        * 
-        * @return the list of actions
-        */
-       abstract public List<KeyAction> getKeyBindings();
-
-       /**
-        * Move the active cursor (not the text cursor, but the currently active
-        * item).
-        * 
-        * @param x
-        *            the horizontal move (&lt; 0 for left, &gt; 0 for right)
-        * @param y
-        *            the vertical move (&lt; 0 for up, &gt; 0 for down)
-        * 
-        * @return the error message to display if any
-        */
-       abstract public String move(int x, int y);
-}
index a09751ef26a81dbbfa3f00c1e869fd9757ca05d9..459d1b1a4dabfea6a13dd6049c2afce3c1d95b5c 100644 (file)
@@ -1,11 +1,14 @@
 package be.nikiroo.jvcard.tui;
 
+import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
 
 import be.nikiroo.jvcard.Card;
 import be.nikiroo.jvcard.Contact;
+import be.nikiroo.jvcard.Data;
 import be.nikiroo.jvcard.i18n.Trans.StringId;
 import be.nikiroo.jvcard.tui.KeyAction.Mode;
 import be.nikiroo.jvcard.tui.UiColors.Element;
@@ -14,10 +17,8 @@ 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;
@@ -40,17 +41,17 @@ public class MainWindow extends BasicWindow {
        private List<KeyAction> defaultActions = new LinkedList<KeyAction>();
        private List<KeyAction> actions = new LinkedList<KeyAction>();
        private List<MainContent> contentStack = new LinkedList<MainContent>();
-       private boolean actionsPadded;
        private boolean waitForOneKeyAnswer;
        private KeyStroke questionKey; // key that "asked" a question, and to replay
        // later with an answer
-       private String title;
+       private String titleCache;
        private Panel titlePanel;
        private Panel mainPanel;
        private Panel contentPanel;
        private Panel actionPanel;
        private Panel messagePanel;
        private TextBox text;
+       private int width;
 
        /**
         * Create a new, empty window.
@@ -68,6 +69,8 @@ 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));
 
@@ -130,11 +133,9 @@ public class MainWindow extends BasicWindow {
         */
        public void pushContent(MainContent content) {
                List<KeyAction> actions = null;
-               String title = null;
 
                contentPanel.removeAllComponents();
                if (content != null) {
-                       title = content.getTitle();
                        actions = content.getKeyBindings();
                        contentPanel.addComponent(content, BorderLayout.Location.CENTER);
                        this.contentStack.add(content);
@@ -144,10 +145,8 @@ public class MainWindow extends BasicWindow {
                                focus.takeFocus();
                }
 
-               setTitle(title);
+               setTitle();
                setActions(actions, true);
-
-               invalidate();
        }
 
        /**
@@ -178,15 +177,72 @@ public class MainWindow extends BasicWindow {
         *            the message to display
         * @param error
         *            TRUE for an error message, FALSE for an information message
+        * 
+        * @return TRUE if changes were performed
         */
-       public void setMessage(String mess, boolean error) {
-               messagePanel.removeAllComponents();
-               if (mess != null) {
-                       Element element = (error ? UiColors.Element.LINE_MESSAGE_ERR
-                                       : UiColors.Element.LINE_MESSAGE);
-                       Label lbl = element.createLabel(" " + mess + " ");
-                       messagePanel.addComponent(lbl, LinearLayout
-                                       .createLayoutData(LinearLayout.Alignment.Center));
+       public boolean setMessage(String mess, boolean error) {
+               if (mess != null || messagePanel.getChildCount() > 0) {
+                       messagePanel.removeAllComponents();
+                       if (mess != null) {
+                               Element element = (error ? UiColors.Element.LINE_MESSAGE_ERR
+                                               : UiColors.Element.LINE_MESSAGE);
+                               Label lbl = element.createLabel(" " + mess + " ");
+                               messagePanel.addComponent(lbl, LinearLayout
+                                               .createLayoutData(LinearLayout.Alignment.Center));
+                       }
+                       return true;
+               }
+
+               return false;
+       }
+
+       /**
+        * Show a question to the user and switch to "ask for answer" mode see
+        * {@link MainWindow#handleQuestion}. The user will be asked to enter some
+        * answer and confirm with ENTER.
+        * 
+        * @param 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);
+       }
+
+       /**
+        * Show a question to the user and switch to "ask for answer" mode see
+        * {@link MainWindow#handleQuestion}. The user will be asked to hit one key
+        * as an answer.
+        * 
+        * @param 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();
                }
        }
 
@@ -194,13 +250,16 @@ public class MainWindow extends BasicWindow {
         * Show a question to the user and switch to "ask for answer" mode see
         * {@link MainWindow#handleQuestion}.
         * 
-        * @param mess
-        *            the message to display
+        * @param question
+        *            the question to ask
+        * @param initial
+        *            the initial answer if any (to be edited by the user)
         * @param oneKey
         *            TRUE for a one-key answer, FALSE for a text answer validated
         *            by ENTER
         */
-       public void setQuestion(KeyStroke key, String mess, boolean oneKey) {
+       private void setQuestion(KeyStroke key, String question, String initial,
+                       boolean oneKey) {
                questionKey = key;
                waitForOneKeyAnswer = oneKey;
 
@@ -212,72 +271,82 @@ public class MainWindow extends BasicWindow {
                hpanel.setLayoutManager(llayout);
 
                Label lbl = UiColors.Element.LINE_MESSAGE_QUESTION.createLabel(" "
-                               + mess + " ");
-               text = new TextBox(new TerminalSize(getSize().getColumns()
-                               - lbl.getSize().getColumns(), 1));
+                               + question + " ");
+               text = new TextBox(new TerminalSize(width - lbl.getSize().getColumns(),
+                               1));
+               if (initial != null)
+                       text.setText(initial);
 
-               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();
        }
 
-       @Override
-       public void draw(TextGUIGraphics graphics) {
-               if (!actionsPadded) {
-                       // fill with "desc" colour
-                       actionPanel.addComponent(UiColors.Element.ACTION_DESC
-                                       .createLabel(StringUtils.padString("", graphics.getSize()
-                                                       .getColumns())));
-                       actionsPadded = true;
-               }
-               super.draw(graphics);
-       }
-
-       @Override
-       public void setTitle(String title) {
+       /**
+        * Actually set the title <b>inside</b> the window. Will also call
+        * {@link BasicWindow#setTitle} with the compuited parameters.
+        */
+       private void setTitle() {
                String prefix = " " + Main.APPLICATION_TITLE + " (version "
                                + Main.APPLICATION_VERSION + ")";
 
+               String title = null;
                int count = -1;
+
                MainContent content = getContent();
-               if (content != null)
+               if (content != null) {
+                       title = content.getTitle();
                        count = content.getCount();
+               }
+
+               if (title == null)
+                       title = "";
 
-               if (title != null) {
+               if (title.length() > 0) {
                        prefix = prefix + ": ";
                }
 
-               if (getSize() != null) {
-                       if (title != null)
-                               title = StringUtils.padString(title, getSize().getColumns());
-                       else
-                               // cause busy-loop freeze:
-                               prefix = StringUtils.padString(prefix, getSize().getColumns());
+               String countStr = "";
+               if (count > -1) {
+                       countStr = "[" + count + "]";
+               }
+
+               if (width > 0) {
+                       int padding = width - prefix.length() - title.length()
+                                       - countStr.length();
+                       if (padding > 0) {
+                               if (title.length() > 0)
+                                       title = StringUtils.padString(title, title.length()
+                                                       + padding);
+                               else
+                                       prefix = StringUtils.padString(prefix, prefix.length()
+                                                       + padding);
+                       }
                }
-               
-               if (!(title + count).equals(this.title)) {
-                       this.title = title + count;
 
-                       super.setTitle(prefix + title);
+               String titleCache = prefix + title + count;
+               if (!titleCache.equals(this.titleCache)) {
+                       super.setTitle(prefix);
 
                        Label lblPrefix = new Label(prefix);
                        UiColors.Element.TITLE_MAIN.themeLabel(lblPrefix);
 
                        Label lblTitle = null;
-                       if (title != null) {
+                       if (title.length() > 0) {
                                lblTitle = new Label(title);
                                UiColors.Element.TITLE_VARIABLE.themeLabel(lblTitle);
                        }
 
                        Label lblCount = null;
-                       if (count > -1) {
-                               lblCount = new Label("[" + count + "]");
+                       if (countStr != null) {
+                               lblCount = new Label(countStr);
                                UiColors.Element.TITLE_COUNT.themeLabel(lblCount);
                        }
 
@@ -288,8 +357,6 @@ public class MainWindow extends BasicWindow {
                                titlePanel.addComponent(lblTitle, BorderLayout.Location.CENTER);
                        if (lblCount != null)
                                titlePanel.addComponent(lblCount, BorderLayout.Location.RIGHT);
-
-                       invalidate();
                }
        }
 
@@ -317,7 +384,6 @@ public class MainWindow extends BasicWindow {
        private void setActions(List<KeyAction> actions,
                        boolean enableDefaultactions) {
                this.actions.clear();
-               actionsPadded = false;
 
                if (enableDefaultactions)
                        this.actions.addAll(defaultActions);
@@ -367,6 +433,13 @@ public class MainWindow extends BasicWindow {
 
                        actionPanel.addComponent(kPane);
                }
+
+               // fill with "desc" colour
+               if (width > 0) {
+                       actionPanel.addComponent(UiColors.Element.ACTION_DESC
+                                       .createLabel(StringUtils.padString("", width)));
+
+               }
        }
 
        /**
@@ -412,12 +485,16 @@ public class MainWindow extends BasicWindow {
         * @param answer
         *            the answer given for this key
         * 
-        * @return if the window handled the inout
+        * @return if the window handled the input
         */
        private boolean handleInput(KeyStroke key, String answer) {
                boolean handled = false;
 
-               setMessage(null, false);
+               // reset the message pane if no answers are pending
+               if (answer == null) {
+                       if (setMessage(null, false))
+                               return true;
+               }
 
                for (KeyAction action : actions) {
                        if (!action.match(key))
@@ -427,6 +504,10 @@ public class MainWindow extends BasicWindow {
                        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;
@@ -450,13 +531,11 @@ public class MainWindow extends BasicWindow {
                                        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));
                                        }
@@ -466,31 +545,86 @@ public class MainWindow extends BasicWindow {
                                        // TODO
                                        // setMessage("Help! I need somebody! Help!", false);
                                        if (answer == null) {
-                                               setQuestion(key, "Test question?", false);
+                                               setQuestion(key, "Test question?", "[initial]");
                                        } 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();
-                                                               }
-                                                       }
+                                       String warning = content.getExitWarning();
+                                       if (warning != null) {
+                                               if (answer == null) {
+                                                       setQuestion(key, warning);
                                                } else {
-                                                       popContent();
+                                                       setMessage(null, false);
+                                                       if (answer.equalsIgnoreCase("y")) {
+                                                               popContent();
+                                                       }
                                                }
+                                       } else {
+                                               popContent();
                                        }
 
-                                       if (contentStack.size() == 0)
+                                       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);
+                                               }
+                                       } else {
+                                               setMessage(null, false);
+                                               data.setValue(answer);
+                                       }
+                                       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(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);
+                                                       }
+                                               }
+                                       }
                                        break;
                                default:
                                case NONE:
@@ -511,11 +645,12 @@ public class MainWindow extends BasicWindow {
                if (questionKey != null) {
                        String answer = handleQuestion(key);
                        if (answer != null) {
-                               // TODO
+                               handled = true;
+
                                key = questionKey;
                                questionKey = null;
 
-                               handled = handleInput(key, answer);
+                               handleInput(key, answer);
                        }
                } else {
                        handled = handleInput(key, null);
index c1e86c3d1fca92fbda2798a7bc16aae2542b92e9..1fad960e46730006273c8ab47943fab1a85eb11d 100644 (file)
@@ -1,32 +1,98 @@
 package be.nikiroo.jvcard.tui.panes;
 
+import java.util.LinkedList;
 import java.util.List;
 
+import com.googlecode.lanterna.input.KeyType;
+
 import be.nikiroo.jvcard.Contact;
 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.KeyAction.DataType;
 import be.nikiroo.jvcard.tui.KeyAction.Mode;
+import be.nikiroo.jvcard.tui.StringUtils;
+import be.nikiroo.jvcard.tui.UiColors.Element;
 
-import com.googlecode.lanterna.gui2.Direction;
-import com.googlecode.lanterna.gui2.Label;
-
-public class ContactDetails extends MainContent {
+public class ContactDetails extends MainContentList {
        private Contact contact;
+       private int mode;
 
        public ContactDetails(Contact contact) {
-               super(Direction.VERTICAL);
+               super(null, null);
 
                this.contact = contact;
+               this.mode = 0;
 
-               for (Data data : contact.getContent()) {
-                       addComponent(new Label(data.getName() + ": " + data.getValue()));
+               for (int i = 0; i < contact.getContent().size(); i++) {
+                       addItem("[detail line]");
                }
        }
 
+       @Override
+       protected List<TextPart> getLabel(int index, int width, boolean selected,
+                       boolean focused) {
+               // TODO: from ini file?
+               int SIZE_COL_1 = 15;
+
+               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;
+
+               Data data = contact.getContent().get(index);
+
+               List<TextPart> parts = new LinkedList<TextPart>();
+               if (data.isDirty()) {
+                       parts.add(new TextPart(" ", el));
+                       parts.add(new TextPart("*", elDirty));
+               } else {
+                       parts.add(new TextPart("  ", elSep));
+               }
+               String name = " " + data.getName() + " ";
+               String value = null;
+
+               StringBuilder valueBuilder = new StringBuilder(" ");
+               switch (mode) {
+               case 0:
+                       valueBuilder.append(data.getValue());
+                       if (data.getGroup() != null && data.getGroup().length() > 0) {
+                               valueBuilder.append("(");
+                               valueBuilder.append(data.getGroup());
+                               valueBuilder.append(")");
+                       }
+                       break;
+               case 1:
+                       for (TypeInfo type : data.getTypes()) {
+                               if (valueBuilder.length() > 1)
+                                       valueBuilder.append(", ");
+                               valueBuilder.append(type.getName());
+                               valueBuilder.append(": ");
+                               valueBuilder.append(type.getValue());
+                       }
+                       break;
+               }
+               valueBuilder.append(" ");
+
+               value = valueBuilder.toString();
+
+               name = StringUtils.padString(name, SIZE_COL_1);
+               value = StringUtils.padString(value, width - SIZE_COL_1
+                               - getSeparator().length() - 2);
+
+               parts.add(new TextPart(name, el));
+               parts.add(new TextPart(getSeparator(), elSep));
+               parts.add(new TextPart(value, el));
+
+               return parts;
+       };
+
        @Override
        public DataType getDataType() {
-               return DataType.CONTACT;
+               return DataType.DATA;
        }
 
        @Override
@@ -38,7 +104,28 @@ public class ContactDetails extends MainContent {
        @Override
        public List<KeyAction> getKeyBindings() {
                // TODO Auto-generated method stub
-               return null;
+               List<KeyAction> actions = new LinkedList<KeyAction>();
+
+               // TODO: add, remove
+               actions.add(new KeyAction(Mode.EDIT_DETAIL, 'd', Trans.StringId.DUMMY) {
+                       @Override
+                       public Object getObject() {
+                               return contact.getContent().get(getSelectedIndex());
+                       }
+               });
+               actions.add(new KeyAction(Mode.NONE, KeyType.Tab,
+                               Trans.StringId.KEY_ACTION_SWITCH_FORMAT) {
+                       @Override
+                       public boolean onAction() {
+                               mode++;
+                               if (mode > 1)
+                                       mode = 0;
+
+                               return false;
+                       }
+               });
+
+               return actions;
        }
 
        @Override
index 370aba7cd5adf2e98177ef2d8cc86701c250c15d..d5cf26b2be381f7db18134ef960a4d26df825711 100644 (file)
@@ -52,13 +52,20 @@ public class ContactList extends MainContentList {
                setSelectedIndex(0);
        }
 
+       @Override
+       public void refreshData() {
+               int index = getSelectedIndex();
+               setCard(card);
+               setSelectedIndex(index);
+               super.refreshData();
+       }
+
        @Override
        public String getExitWarning() {
                if (card != null && card.isDirty()) {
-                       //TODO: save? [y/n] instead
-                       return "Some of your contact information is not saved; ignore? [Y/N]";
+                       return "Ignore unsaved changes? [Y/N]";
                }
-               
+
                return null;
        }
 
@@ -66,35 +73,36 @@ public class ContactList extends MainContentList {
        public List<KeyAction> getKeyBindings() {
                List<KeyAction> actions = new LinkedList<KeyAction>();
 
-               // TODO del, save...
-               // TODO: remove
-               actions.add(new KeyAction(Mode.NONE, 'd', Trans.StringId.DUMMY) {
+               // TODO add, del, save...
+               actions.add(new KeyAction(Mode.CONTACT_DETAILS, 'e',
+                               Trans.StringId.KEY_ACTION_EDIT_CONTACT) {
                        @Override
-                       public boolean onAction() {
-                               //TODO dummy action
-                               int index = getSelectedIndex();
-                               Contact c = card.getContacts().get(index);
-                               c.updateFrom(c);
-                               return false;
+                       public Object getObject() {
+                               return getSelectedContact();
                        }
                });
-               actions.add(new KeyAction(Mode.CONTACT_DETAILS, 'e',
-                               Trans.StringId.KEY_ACTION_EDIT_CONTACT) {
+               actions.add(new KeyAction(Mode.DELETE_CONTACT, 'd',
+                               Trans.StringId.KEY_ACTION_DELETE_CONTACT) {
+                       @Override
+                       public Object getObject() {
+                               return getSelectedContact();
+                       }
+               });
+               actions.add(new KeyAction(Mode.SAVE_CARD, 's',
+                               Trans.StringId.KEY_ACTION_SAVE_CARD) {
                        @Override
                        public Object getObject() {
-                               int index = getSelectedIndex();
-                               return card.getContacts().get(index);
+                               return card;
                        }
                });
                actions.add(new KeyAction(Mode.CONTACT_DETAILS, KeyType.Enter,
                                Trans.StringId.KEY_ACTION_VIEW_CONTACT) {
                        @Override
                        public Object getObject() {
-                               int index = getSelectedIndex();
-                               return card.getContacts().get(index);
+                               return getSelectedContact();
                        }
                });
-               actions.add(new KeyAction(Mode.SWICTH_FORMAT, KeyType.Tab,
+               actions.add(new KeyAction(Mode.NONE, KeyType.Tab,
                                Trans.StringId.KEY_ACTION_SWITCH_FORMAT) {
                        @Override
                        public boolean onAction() {
@@ -128,7 +136,14 @@ public class ContactList extends MainContentList {
        @Override
        protected List<TextPart> getLabel(int index, int width, boolean selected,
                        boolean focused) {
-               Contact c = card.getContacts().get(index);
+               List<TextPart> parts = new LinkedList<TextPart>();
+
+               Contact contact = null;
+               if (index > -1 && index < card.size())
+                       contact = card.get(index);
+
+               if (contact == null)
+                       return parts;
 
                Element el = (focused && selected) ? Element.CONTACT_LINE_SELECTED
                                : Element.CONTACT_LINE;
@@ -139,11 +154,10 @@ public class ContactList extends MainContentList {
 
                width -= 2; // dirty mark space
 
-               // we could use: " ", "┃", "│"...
-               String[] array = c.toStringArray(format, "┃", " ", width);
+               String[] array = contact.toStringArray(format, getSeparator(), " ",
+                               width);
 
-               List<TextPart> parts = new LinkedList<TextPart>();
-               if (c.isDirty()) {
+               if (contact.isDirty()) {
                        parts.add(new TextPart(" ", el));
                        parts.add(new TextPart("*", elDirty));
                } else {
@@ -159,6 +173,18 @@ public class ContactList extends MainContentList {
                return parts;
        }
 
+       /**
+        * Return the currently selected {@link Contact}.
+        * 
+        * @return the currently selected {@link Contact}
+        */
+       private Contact getSelectedContact() {
+               int index = getSelectedIndex();
+               if (index > -1 && index < card.size())
+                       return card.get(index);
+               return null;
+       }
+
        private void switchFormat() {
                if (formats.size() == 0)
                        return;
index 79f530e0604f38318d6b3283ce1dc75ce9530f88..dfc3b73114cf3ffda2f2989d74841a1e602a9e69 100644 (file)
@@ -2,6 +2,7 @@ package be.nikiroo.jvcard.tui.panes;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -9,14 +10,17 @@ import be.nikiroo.jvcard.Card;
 import be.nikiroo.jvcard.i18n.Trans;
 import be.nikiroo.jvcard.parsers.Format;
 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;
+import be.nikiroo.jvcard.tui.UiColors.Element;
 
 import com.googlecode.lanterna.input.KeyType;
 
 public class FileList extends MainContentList {
        private List<File> files;
+       private List<Card> cards;
 
        public FileList(List<File> files) {
                super(UiColors.Element.CONTACT_LINE,
@@ -34,10 +38,11 @@ public class FileList extends MainContentList {
        public void setFiles(List<File> files) {
                clearItems();
                this.files = files;
+               cards = new ArrayList<Card>();
 
-               // TODO
                for (File file : files) {
                        addItem(file.getName());
+                       cards.add(null);
                }
 
                setSelectedIndex(0);
@@ -49,10 +54,35 @@ public class FileList extends MainContentList {
        }
 
        @Override
-       public String getExitWarning() {
-               // TODO Auto-generated method stub
-               return null;
-       }
+       protected List<TextPart> getLabel(int index, int width, boolean selected,
+                       boolean focused) {
+               // TODO: from ini file?
+               int SIZE_COL_1 = 3;
+
+               Element el = (focused && selected) ? Element.CONTACT_LINE_SELECTED
+                               : Element.CONTACT_LINE;
+               Element elSep = (focused && selected) ? Element.CONTACT_LINE_SEPARATOR_SELECTED
+                               : Element.CONTACT_LINE_SEPARATOR;
+
+               List<TextPart> parts = new LinkedList<TextPart>();
+
+               String count = "";
+               if (cards.get(index) != null)
+                       count += cards.get(index).size();
+
+               String name = files.get(index).getName();
+
+               count = " " + StringUtils.padString(count, SIZE_COL_1) + " ";
+               name = " "
+                               + StringUtils.padString(name, width - SIZE_COL_1
+                                               - getSeparator().length()) + " ";
+
+               parts.add(new TextPart(count, el));
+               parts.add(new TextPart(getSeparator(), elSep));
+               parts.add(new TextPart(name, el));
+
+               return parts;
+       };
 
        @Override
        public List<KeyAction> getKeyBindings() {
@@ -63,7 +93,15 @@ public class FileList extends MainContentList {
                                Trans.StringId.KEY_ACTION_VIEW_CARD) {
                        @Override
                        public Object getObject() {
-                               File file = files.get(getSelectedIndex());
+                               int index = getSelectedIndex();
+
+                               if (index < 0 || index >= cards.size())
+                                       return null;
+
+                               if (cards.get(index) != null)
+                                       return cards.get(index);
+
+                               File file = files.get(index);
                                Format format = Format.Abook;
                                String ext = file.getName();
                                if (ext.contains(".")) {
@@ -74,7 +112,12 @@ public class FileList extends MainContentList {
                                        }
                                }
                                try {
-                                       return new Card(file, format);
+                                       Card card = new Card(file, format);
+                                       cards.set(index, card);
+
+                                       invalidate();
+
+                                       return card;
                                } catch (IOException ioe) {
                                        ioe.printStackTrace();
                                        return null;
@@ -89,11 +132,4 @@ public class FileList extends MainContentList {
        public Mode getMode() {
                return Mode.FILE_LIST;
        }
-
-       @Override
-       public String getTitle() {
-               // TODO Auto-generated method stub
-               return null;
-       }
-
 }
index 0bc9b29fce7bc091d0f3696534776b640b340b34..c33bbd3fdb93a868b84177382add38f49d6712bb 100644 (file)
@@ -51,12 +51,14 @@ abstract public class MainContent extends Panel {
        abstract public List<KeyAction> getKeyBindings();
 
        /**
-        * The title to display instead of the application name, or NULL for the
+        * The title to display in addition to the application name, or NULL for the
         * default application name.
         * 
         * @return the title or NULL
         */
-       abstract public String getTitle();
+       public String getTitle() {
+               return null;
+       }
 
        /**
         * Returns an error message ready to be displayed if we should ask something
@@ -92,4 +94,12 @@ abstract public class MainContent extends Panel {
        public int getCount() {
                return -1;
        }
+
+       /**
+        * Refresh the display according to the actual data (this method should be
+        * called when the data changed).
+        */
+       public void refreshData() {
+               invalidate();
+       }
 }
index 83654198f08c127f75d6ab6f1f06de66ca7372d3..38b775c2998af5986cf06e31440bd663b42d90d5 100644 (file)
@@ -152,6 +152,17 @@ 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 "┃";
+       }
 
        @Override
        public void run() {
index f4ce6ab074065c9a0675a4c29a8881131fb6af47..53fe735f08e1b8933458ece9ff2ea8f2a9f91362 100644 (file)
@@ -24,286 +24,244 @@ import java.util.LinkedList;
 import java.util.List;
 
 /**
- * This class contains a number of utility methods for analyzing characters and
- * strings in a terminal context. The main purpose is to make it easier to work
- * with text that may or may not contain double-width text characters, such as
- * CJK (Chinese, Japanese, Korean) and other special symbols. This class assumes
- * those are all double-width and in case the terminal (-emulator) chooses to
- * draw them (somehow) as single-column then all the calculations in this class
- * will be wrong. It seems safe to assume what this class considers double-width
- * really is taking up two columns though.
+ * This class contains a number of utility methods for analyzing characters and strings in a terminal context. The main
+ * purpose is to make it easier to work with text that may or may not contain double-width text characters, such as CJK
+ * (Chinese, Japanese, Korean) and other special symbols. This class assumes those are all double-width and in case the
+ * terminal (-emulator) chooses to draw them (somehow) as single-column then all the calculations in this class will be
+ * wrong. It seems safe to assume what this class considers double-width really is taking up two columns though.
  * 
  * @author Martin
  */
 public class TerminalTextUtils {
-       private TerminalTextUtils() {
-       }
+    private TerminalTextUtils() {
+    }
 
-       /**
-        * Given a character, is this character considered to be a CJK character?
-        * Shamelessly stolen from <a href="http://stackoverflow.com/questions/1499804/how-can-i-detect-japanese-text-in-a-java-string"
-        * >StackOverflow</a> where it was contributed by user Rakesh N
-        * 
-        * @param c
-        *            Character to test
-        * @return {@code true} if the character is a CJK character
-        * 
-        */
-       public static boolean isCharCJK(final char c) {
-               Character.UnicodeBlock unicodeBlock = Character.UnicodeBlock.of(c);
-               return (unicodeBlock == Character.UnicodeBlock.HIRAGANA)
-                               || (unicodeBlock == Character.UnicodeBlock.KATAKANA)
-                               || (unicodeBlock == Character.UnicodeBlock.KATAKANA_PHONETIC_EXTENSIONS)
-                               || (unicodeBlock == Character.UnicodeBlock.HANGUL_COMPATIBILITY_JAMO)
-                               || (unicodeBlock == Character.UnicodeBlock.HANGUL_JAMO)
-                               || (unicodeBlock == Character.UnicodeBlock.HANGUL_SYLLABLES)
-                               || (unicodeBlock == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS)
-                               || (unicodeBlock == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A)
-                               || (unicodeBlock == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B)
-                               || (unicodeBlock == Character.UnicodeBlock.CJK_COMPATIBILITY_FORMS)
-                               || (unicodeBlock == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS)
-                               || (unicodeBlock == Character.UnicodeBlock.CJK_RADICALS_SUPPLEMENT)
-                               || (unicodeBlock == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION)
-                               || (unicodeBlock == Character.UnicodeBlock.ENCLOSED_CJK_LETTERS_AND_MONTHS)
-                               || (unicodeBlock == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS && c < 0xFF61); // The
-                                                                                                                                                                                                                       // magic
-                                                                                                                                                                                                                       // number
-                                                                                                                                                                                                                       // here
-                                                                                                                                                                                                                       // is
-                                                                                                                                                                                                                       // the
-                                                                                                                                                                                                                       // separating
-                                                                                                                                                                                                                       // index
-                                                                                                                                                                                                                       // between
-                                                                                                                                                                                                                       // full-width
-                                                                                                                                                                                                                       // and
-                                                                                                                                                                                                                       // half-width
-       }
+    /**
+     * Given a character, is this character considered to be a CJK character?
+     * Shamelessly stolen from
+     * <a href="http://stackoverflow.com/questions/1499804/how-can-i-detect-japanese-text-in-a-java-string">StackOverflow</a>
+     * where it was contributed by user Rakesh N
+     * @param c Character to test
+     * @return {@code true} if the character is a CJK character
+     *
+     */
+    public static boolean isCharCJK(final char c) {
+        Character.UnicodeBlock unicodeBlock = Character.UnicodeBlock.of(c);
+        return (unicodeBlock == Character.UnicodeBlock.HIRAGANA)
+                || (unicodeBlock == Character.UnicodeBlock.KATAKANA)
+                || (unicodeBlock == Character.UnicodeBlock.KATAKANA_PHONETIC_EXTENSIONS)
+                || (unicodeBlock == Character.UnicodeBlock.HANGUL_COMPATIBILITY_JAMO)
+                || (unicodeBlock == Character.UnicodeBlock.HANGUL_JAMO)
+                || (unicodeBlock == Character.UnicodeBlock.HANGUL_SYLLABLES)
+                || (unicodeBlock == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS)
+                || (unicodeBlock == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A)
+                || (unicodeBlock == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B)
+                || (unicodeBlock == Character.UnicodeBlock.CJK_COMPATIBILITY_FORMS)
+                || (unicodeBlock == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS)
+                || (unicodeBlock == Character.UnicodeBlock.CJK_RADICALS_SUPPLEMENT)
+                || (unicodeBlock == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION)
+                || (unicodeBlock == Character.UnicodeBlock.ENCLOSED_CJK_LETTERS_AND_MONTHS)
+                || (unicodeBlock == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS && c < 0xFF61);    //The magic number here is the separating index between full-width and half-width
+    }
 
-       /**
-        * Checks if a character is expected to be taking up two columns if printed
-        * to a terminal. This will generally be {@code true} for CJK (Chinese,
-        * Japanese and Korean) characters.
-        * 
-        * @param c
-        *            Character to test if it's double-width when printed to a
-        *            terminal
-        * @return {@code true} if this character is expected to be taking up two
-        *         columns when printed to the terminal, otherwise {@code false}
-        */
-       public static boolean isCharDoubleWidth(final char c) {
-               return isCharCJK(c);
-       }
+    /**
+     * Checks if a character is expected to be taking up two columns if printed to a terminal. This will generally be
+     * {@code true} for CJK (Chinese, Japanese and Korean) characters.
+     * @param c Character to test if it's double-width when printed to a terminal
+     * @return {@code true} if this character is expected to be taking up two columns when printed to the terminal,
+     * otherwise {@code false}
+     */
+    public static boolean isCharDoubleWidth(final char c) {
+        return isCharCJK(c);
+    }
 
-       /**
-        * @deprecated Call {@code getColumnWidth(s)} instead
-        */
-       @Deprecated
-       public static int getTrueWidth(String s) {
-               return getColumnWidth(s);
-       }
+    /**
+     * @deprecated Call {@code getColumnWidth(s)} instead
+     */
+    @Deprecated
+    public static int getTrueWidth(String s) {
+        return getColumnWidth(s);
+    }
 
-       /**
-        * Given a string, returns how many columns this string would need to occupy
-        * in a terminal, taking into account that CJK characters takes up two
-        * columns.
-        * 
-        * @param s
-        *            String to check length
-        * @return Number of actual terminal columns the string would occupy
-        */
-       public static int getColumnWidth(String s) {
-               return getColumnIndex(s, s.length());
-       }
+    /**
+     * Given a string, returns how many columns this string would need to occupy in a terminal, taking into account that
+     * CJK characters takes up two columns.
+     * @param s String to check length
+     * @return Number of actual terminal columns the string would occupy
+     */
+    public static int getColumnWidth(String s) {
+        return getColumnIndex(s, s.length());
+    }
 
-       /**
-        * Given a string and a character index inside that string, find out what
-        * the column index of that character would be if printed in a terminal. If
-        * the string only contains non-CJK characters then the returned value will
-        * be same as {@code stringCharacterIndex}, but if there are CJK characters
-        * the value will be different due to CJK characters taking up two columns
-        * in width. If the character at the index in the string is a CJK character
-        * itself, the returned value will be the index of the left-side of
-        * character.
-        * 
-        * @param s
-        *            String to translate the index from
-        * @param stringCharacterIndex
-        *            Index within the string to get the terminal column index of
-        * @return Index of the character inside the String at {@code
-        *         stringCharacterIndex} when it has been writted to a terminal
-        * @throws StringIndexOutOfBoundsException
-        *             if the index given is outside the String length or negative
-        */
-       public static int getColumnIndex(String s, int stringCharacterIndex)
-                       throws StringIndexOutOfBoundsException {
-               int index = 0;
-               for (int i = 0; i < stringCharacterIndex; i++) {
-                       if (isCharCJK(s.charAt(i))) {
-                               index++;
-                       }
-                       index++;
-               }
-               return index;
-       }
+    /**
+     * Given a string and a character index inside that string, find out what the column index of that character would
+     * be if printed in a terminal. If the string only contains non-CJK characters then the returned value will be same
+     * as {@code stringCharacterIndex}, but if there are CJK characters the value will be different due to CJK
+     * characters taking up two columns in width. If the character at the index in the string is a CJK character itself,
+     * the returned value will be the index of the left-side of character.
+     * @param s String to translate the index from
+     * @param stringCharacterIndex Index within the string to get the terminal column index of
+     * @return Index of the character inside the String at {@code stringCharacterIndex} when it has been writted to a
+     * terminal
+     * @throws StringIndexOutOfBoundsException if the index given is outside the String length or negative
+     */
+    public static int getColumnIndex(String s, int stringCharacterIndex) throws StringIndexOutOfBoundsException {
+        int index = 0;
+        for(int i = 0; i < stringCharacterIndex; i++) {
+            if(isCharCJK(s.charAt(i))) {
+                index++;
+            }
+            index++;
+        }
+        return index;
+    }
 
-       /**
-        * This method does the reverse of getColumnIndex, given a String and
-        * imagining it has been printed out to the top-left corner of a terminal,
-        * in the column specified by {@code columnIndex}, what is the index of that
-        * character in the string. If the string contains no CJK characters, this
-        * will always be the same as {@code columnIndex}. If the index specified is
-        * the right column of a CJK character, the index is the same as if the
-        * column was the left column. So calling {@code
-        * getStringCharacterIndex("英", 0)} and {@code getStringCharacterIndex("英",
-        * 1)} will both return 0.
-        * 
-        * @param s
-        *            String to translate the index to
-        * @param columnIndex
-        *            Column index of the string written to a terminal
-        * @return The index in the string of the character in terminal column
-        *         {@code columnIndex}
-        */
-       public static int getStringCharacterIndex(String s, int columnIndex) {
-               int index = 0;
-               int counter = 0;
-               while (counter < columnIndex) {
-                       if (isCharCJK(s.charAt(index++))) {
-                               counter++;
-                               if (counter == columnIndex) {
-                                       return index - 1;
-                               }
-                       }
-                       counter++;
-               }
-               return index;
-       }
+    /**
+     * This method does the reverse of getColumnIndex, given a String and imagining it has been printed out to the
+     * top-left corner of a terminal, in the column specified by {@code columnIndex}, what is the index of that
+     * character in the string. If the string contains no CJK characters, this will always be the same as
+     * {@code columnIndex}. If the index specified is the right column of a CJK character, the index is the same as if
+     * the column was the left column. So calling {@code getStringCharacterIndex("英", 0)} and
+     * {@code getStringCharacterIndex("英", 1)} will both return 0.
+     * @param s String to translate the index to
+     * @param columnIndex Column index of the string written to a terminal
+     * @return The index in the string of the character in terminal column {@code columnIndex}
+     */
+    public static int getStringCharacterIndex(String s, int columnIndex) {
+        int index = 0;
+        int counter = 0;
+        while(counter < columnIndex) {
+            if(isCharCJK(s.charAt(index++))) {
+                counter++;
+                if(counter == columnIndex) {
+                    return index - 1;
+                }
+            }
+            counter++;
+        }
+        return index;
+    }
 
-       /**
-        * Given a string that may or may not contain CJK characters, returns the
-        * substring which will fit inside <code>availableColumnSpace</code>
-        * columns. This method does not handle special cases like tab or new-line.
-        * <p>
-        * Calling this method is the same as calling {@code fitString(string, 0,
-        * availableColumnSpace)}.
-        * 
-        * @param string
-        *            The string to fit inside the availableColumnSpace
-        * @param availableColumnSpace
-        *            Number of columns to fit the string inside
-        * @return The whole or part of the input string which will fit inside the
-        *         supplied availableColumnSpace
-        */
-       public static String fitString(String string, int availableColumnSpace) {
-               return fitString(string, 0, availableColumnSpace);
-       }
+    /**
+     * Given a string that may or may not contain CJK characters, returns the substring which will fit inside
+     * <code>availableColumnSpace</code> columns. This method does not handle special cases like tab or new-line.
+     * <p>
+     * Calling this method is the same as calling {@code fitString(string, 0, availableColumnSpace)}.
+     * @param string The string to fit inside the availableColumnSpace
+     * @param availableColumnSpace Number of columns to fit the string inside
+     * @return The whole or part of the input string which will fit inside the supplied availableColumnSpace
+     */
+    public static String fitString(String string, int availableColumnSpace) {
+        return fitString(string, 0, availableColumnSpace);
+    }
 
-       /**
-        * Given a string that may or may not contain CJK characters, returns the
-        * substring which will fit inside <code>availableColumnSpace</code>
-        * columns. This method does not handle special cases like tab or new-line.
-        * <p>
-        * This overload has a {@code fromColumn} parameter that specified where
-        * inside the string to start fitting. Please notice that {@code fromColumn}
-        * is not a character index inside the string, but a column index as if the
-        * string has been printed from the left-most side of the terminal. So if
-        * the string is "日本語", fromColumn set to 1 will not starting counting from
-        * the second character ("本") in the string but from the CJK filler
-        * character belonging to "日". If you want to count from a particular
-        * character index inside the string, please pass in a substring and use
-        * fromColumn set to 0.
-        * 
-        * @param string
-        *            The string to fit inside the availableColumnSpace
-        * @param fromColumn
-        *            From what column of the input string to start fitting (see
-        *            description above!)
-        * @param availableColumnSpace
-        *            Number of columns to fit the string inside
-        * @return The whole or part of the input string which will fit inside the
-        *         supplied availableColumnSpace
-        */
-       public static String fitString(String string, int fromColumn,
-                       int availableColumnSpace) {
-               if (availableColumnSpace <= 0) {
-                       return "";
-               }
+    /**
+     * Given a string that may or may not contain CJK characters, returns the substring which will fit inside
+     * <code>availableColumnSpace</code> columns. This method does not handle special cases like tab or new-line.
+     * <p>
+     * This overload has a {@code fromColumn} parameter that specified where inside the string to start fitting. Please
+     * notice that {@code fromColumn} is not a character index inside the string, but a column index as if the string
+     * has been printed from the left-most side of the terminal. So if the string is "日本語", fromColumn set to 1 will
+     * not starting counting from the second character ("本") in the string but from the CJK filler character belonging
+     * to "日". If you want to count from a particular character index inside the string, please pass in a substring
+     * and use fromColumn set to 0.
+     * @param string The string to fit inside the availableColumnSpace
+     * @param fromColumn From what column of the input string to start fitting (see description above!)
+     * @param availableColumnSpace Number of columns to fit the string inside
+     * @return The whole or part of the input string which will fit inside the supplied availableColumnSpace
+     */
+    public static String fitString(String string, int fromColumn, int availableColumnSpace) {
+        if(availableColumnSpace <= 0) {
+            return "";
+        }
 
-               StringBuilder bob = new StringBuilder();
-               int column = 0;
-               int index = 0;
-               while (index < string.length() && column < fromColumn) {
-                       char c = string.charAt(index++);
-                       column += TerminalTextUtils.isCharCJK(c) ? 2 : 1;
-               }
-               if (column > fromColumn) {
-                       bob.append(" ");
-                       availableColumnSpace--;
-               }
+        StringBuilder bob = new StringBuilder();
+        int column = 0;
+        int index = 0;
+        while(index < string.length() && column < fromColumn) {
+            char c = string.charAt(index++);
+            column += TerminalTextUtils.isCharCJK(c) ? 2 : 1;
+        }
+        if(column > fromColumn) {
+            bob.append(" ");
+            availableColumnSpace--;
+        }
 
-               while (availableColumnSpace > 0 && index < string.length()) {
-                       char c = string.charAt(index++);
-                       availableColumnSpace -= TerminalTextUtils.isCharCJK(c) ? 2 : 1;
-                       if (availableColumnSpace < 0) {
-                               bob.append(' ');
-                       } else {
-                               bob.append(c);
-                       }
-               }
-               return bob.toString();
-       }
+        while(availableColumnSpace > 0 && index < string.length()) {
+            char c = string.charAt(index++);
+            availableColumnSpace -= TerminalTextUtils.isCharCJK(c) ? 2 : 1;
+            if(availableColumnSpace < 0) {
+                bob.append(' ');
+            }
+            else {
+                bob.append(c);
+            }
+        }
+        return bob.toString();
+    }
 
-       /**
-        * This method will calculate word wrappings given a number of lines of text
-        * and how wide the text can be printed. The result is a list of new rows
-        * where word-wrapping was applied.
-        * 
-        * @param maxWidth
-        *            Maximum number of columns that can be used before
-        *            word-wrapping is applied
-        * @param lines
-        *            Input text
-        * @return The input text word-wrapped at {@code maxWidth}; this may contain
-        *         more rows than the input text
-        */
-       public static List<String> getWordWrappedText(int maxWidth, String... lines) {
-               List<String> result = new ArrayList<String>();
-               LinkedList<String> linesToBeWrapped = new LinkedList<String>(Arrays
-                               .asList(lines));
-               while (!linesToBeWrapped.isEmpty()) {
-                       String row = linesToBeWrapped.removeFirst();
-                       int rowWidth = getColumnWidth(row);
-                       if (rowWidth <= maxWidth) {
-                               result.add(row);
-                       } else {
-                               // Now search in reverse and find the first possible line-break
-                               int characterIndex = getStringCharacterIndex(row, maxWidth);
-                               while (!Character.isSpaceChar(row.charAt(characterIndex))
-                                               && !isCharCJK(row.charAt(characterIndex))
-                                               && characterIndex > 0) {
-                                       characterIndex--;
-                               }
+    /**
+     * This method will calculate word wrappings given a number of lines of text and how wide the text can be printed.
+     * The result is a list of new rows where word-wrapping was applied.
+     * @param maxWidth Maximum number of columns that can be used before word-wrapping is applied, if <= 0 then the
+     *                 lines will be returned unchanged
+     * @param lines Input text
+     * @return The input text word-wrapped at {@code maxWidth}; this may contain more rows than the input text
+     */
+    public static List<String> getWordWrappedText(int maxWidth, String... lines) {
+        //Bounds checking
+        if(maxWidth <= 0) {
+            return Arrays.asList(lines);
+        }
 
-                               if (characterIndex == 0) {
-                                       // Failed! There was no 'nice' place to cut so just cut it
-                                       // at maxWidth
-                                       result.add(row.substring(0, maxWidth));
-                                       linesToBeWrapped.addFirst(row.substring(maxWidth));
-                               } else {
-                                       // Ok, split the row, add it to the result and continue
-                                       // processing the second half on a new line
-                                       result.add(row.substring(0, characterIndex));
-                                       int spaceCharsToSkip = 0;
-                                       while (characterIndex < row.length()
-                                                       && Character
-                                                                       .isSpaceChar(row.charAt(characterIndex))) {
-                                               characterIndex++;
-                                       }
-                                       ;
-                                       linesToBeWrapped.addFirst(row.substring(characterIndex));
-                               }
-                       }
-               }
-               return result;
-       }
+        List<String> result = new ArrayList<String>();
+        LinkedList<String> linesToBeWrapped = new LinkedList<String>(Arrays.asList(lines));
+        while(!linesToBeWrapped.isEmpty()) {
+            String row = linesToBeWrapped.removeFirst();
+            int rowWidth = getColumnWidth(row);
+            if(rowWidth <= maxWidth) {
+                result.add(row);
+            }
+            else {
+                //Now search in reverse and find the first possible line-break
+                final int characterIndexMax = getStringCharacterIndex(row, maxWidth);
+                int characterIndex = characterIndexMax;
+                while(characterIndex >= 0 &&
+                        !Character.isSpaceChar(row.charAt(characterIndex)) &&
+                        !isCharCJK(row.charAt(characterIndex))) {
+                    characterIndex--;
+                }
+                // right *after* a CJK is also a "nice" spot to break the line!
+                if (characterIndex >= 0 && characterIndex < characterIndexMax &&
+                      isCharCJK(row.charAt(characterIndex))) {
+                    characterIndex++; // with these conditions it fits!
+                }
+
+                if(characterIndex < 0) {
+                    //Failed! There was no 'nice' place to cut so just cut it at maxWidth
+                    characterIndex = Math.max(characterIndexMax, 1); // at least 1 char
+                    result.add(row.substring(0, characterIndex));
+                    linesToBeWrapped.addFirst(row.substring(characterIndex));
+                }
+                else {
+                    // characterIndex == 0 only happens, if either
+                    //   - first char is CJK and maxWidth==1   or
+                    //   - first char is whitespace
+                    // either way: put it in row before break to prevent infinite loop.
+                    characterIndex = Math.max( characterIndex, 1); // at least 1 char
+                    
+                    //Ok, split the row, add it to the result and continue processing the second half on a new line
+                    result.add(row.substring(0, characterIndex));
+                    while(characterIndex < row.length() &&
+                          Character.isSpaceChar(row.charAt(characterIndex))) {
+                        characterIndex++;
+                    };
+                    if (characterIndex < row.length()) { // only if rest contains non-whitespace
+                        linesToBeWrapped.addFirst(row.substring(characterIndex));
+                    }
+                }
+            }
+        }
+        return result;
+    }
 }
index df542d0fa6825621d7a8c7987118b8e892f376d8..f4a00163ded8a3524f703a77320219e715bf37cf 100644 (file)
@@ -34,12 +34,7 @@ public abstract class AbstractTextGUIThread implements TextGUIThread {
 
     @Override
     public void invokeLater(Runnable runnable) throws IllegalStateException {
-        if(Thread.currentThread() == getThread()) {
-            runnable.run();
-        }
-        else {
-            customTasks.add(runnable);
-        }
+        customTasks.add(runnable);
     }
 
     @Override
@@ -71,14 +66,23 @@ public abstract class AbstractTextGUIThread implements TextGUIThread {
 
     @Override
     public void invokeAndWait(final Runnable runnable) throws IllegalStateException, InterruptedException {
-        final CountDownLatch countDownLatch = new CountDownLatch(1);
-        invokeLater(new Runnable() {
-            @Override
-            public void run() {
-                runnable.run();
-                countDownLatch.countDown();
-            }
-        });
-        countDownLatch.await();
+        if(Thread.currentThread() == getThread()) {
+            runnable.run();
+        }
+        else {
+            final CountDownLatch countDownLatch = new CountDownLatch(1);
+            invokeLater(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        runnable.run();
+                    }
+                    finally {
+                        countDownLatch.countDown();
+                    }
+                }
+            });
+            countDownLatch.await();
+        }
     }
 }
index 6cc6e85e8d5f779225f0f8355e249c494063e35d..446774c834e3434a0e8d1ceccc57cdf283bf2b6b 100644 (file)
@@ -133,38 +133,16 @@ 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);
index ba005f86b28a589703f34d04b741fb8a7c560221..f76fccde01e9a6940fb9d47aabc89b048951533f 100644 (file)
@@ -31,8 +31,10 @@ import java.io.IOException;
  */
 public interface TextGUIThread {
     /**
-     * Invokes custom code on the GUI thread. If the caller is already on the GUI thread, the code is executed immediately
-     * @param runnable Code to run
+     * Invokes custom code on the GUI thread. Even if the current thread <b>is</b> the GUI thread, the code will be
+     * executed at a later time when the event processing is done.
+     *
+     * @param runnable Code to run asynchronously
      * @throws java.lang.IllegalStateException If the GUI thread is not running
      */
     void invokeLater(Runnable runnable) throws IllegalStateException;
@@ -51,8 +53,9 @@ public interface TextGUIThread {
 
     /**
      * Schedules custom code to be executed on the GUI thread and waits until the code has been executed before
-     * returning.
-     * @param runnable Code to run
+     * returning. If this is run on the GUI thread, it will immediately run the {@code Runnable} and then return.
+     *
+     * @param runnable Code to be run and waited for completion before this method returns
      * @throws IllegalStateException If the GUI thread is not running
      * @throws InterruptedException If the caller thread was interrupted while waiting for the task to be executed
      */
index 90efcbc2beec2d0a79fde93207142c66c909e7ff..97e20e62a3ad98a83400fdc05b6344ea1dcfb60f 100644 (file)
@@ -162,11 +162,20 @@ public class AWTTerminal extends Panel implements IOSafeTerminal {
                 scrollController);
     }
 
+    /**
+     * Overridden method from AWT's {@code Component} class that returns the preferred size of the terminal (in pixels)
+     * @return The terminal's preferred size in pixels
+     */
     @Override
     public synchronized Dimension getPreferredSize() {
         return terminalImplementation.getPreferredSize();
     }
 
+    /**
+     * Overridden method from AWT's {@code Component} class that is called by OS window system when the component needs
+     * to be redrawn
+     * @param {@code Graphics} object to use when drawing the component
+     */
     @Override
     public synchronized void paint(Graphics componentGraphics) {
         // Flicker-free AWT!
@@ -174,6 +183,11 @@ public class AWTTerminal extends Panel implements IOSafeTerminal {
         terminalImplementation.paintComponent(componentGraphics);
     }
 
+    /**
+     * Overridden method from AWT's {@code Component} class that is called by OS window system when the component needs
+     * to be updated (the size has changed) and redrawn
+     * @param {@code Graphics} object to use when drawing the component
+     */
     @Override
     public synchronized void update(Graphics componentGraphics) {
         // Flicker-free AWT!
index 8c444a86ff8a167b344a0e4bd8a2efceef2f0145..085f38a24013cd262ae4d63544722cde22febf30 100644 (file)
@@ -249,10 +249,10 @@ abstract class GraphicalTerminalImplementation implements IOSafeTerminal {
         // At this point, if the user hasn't asked for an explicit flush, just paint the backbuffer. It's prone to
         // problems if the user isn't flushing properly but it reduces flickering when resizing the window and the code
         // is asynchronously responding to the resize
-        //if(flushed) {
+        if(flushed) {
             updateBackBuffer(fontWidth, fontHeight, terminalResized, terminalSize);
             flushed = false;
-        //}
+        }
 
         componentGraphics.drawImage(backbuffer, 0, 0, getWidth(), getHeight(), 0, 0, getWidth(), getHeight(), null);
 
@@ -269,8 +269,6 @@ abstract class GraphicalTerminalImplementation implements IOSafeTerminal {
 
         //Setup the graphics object
         Graphics2D backbufferGraphics = backbuffer.createGraphics();
-        backbufferGraphics.setColor(colorConfiguration.toAWTColor(TextColor.ANSI.DEFAULT, false, false));
-        backbufferGraphics.fillRect(0, 0, getWidth(), getHeight());
 
         if(isTextAntiAliased()) {
             backbufferGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
@@ -309,7 +307,7 @@ abstract class GraphicalTerminalImplementation implements IOSafeTerminal {
                                 (deviceConfiguration.isCursorBlinking() && blinkOn));    //If the cursor is blinking, only draw when blinkOn is true
 
                 CharacterState characterState = new CharacterState(character, foregroundColor, backgroundColor, drawCursor);
-                //if(!characterState.equals(visualState[rowIndex][columnIndex]) || terminalResized) {
+                if(!characterState.equals(visualState[rowIndex][columnIndex]) || terminalResized) {
                     drawCharacter(backbufferGraphics,
                             character,
                             columnIndex,
@@ -324,7 +322,7 @@ abstract class GraphicalTerminalImplementation implements IOSafeTerminal {
                     if(TerminalTextUtils.isCharCJK(character.getCharacter())) {
                         visualState[rowIndex][columnIndex+1] = characterState;
                     }
-                //}
+                }
 
                 if(character.getModifiers().contains(SGR.BLINK)) {
                     foundBlinkingCharacters = true;
index 459722275b77c334e8ff53bb4908dc33070ffb6b..3470b643c6c22a6ab6e028a69ac46ed3da378c3b 100644 (file)
@@ -145,16 +145,27 @@ public class SwingTerminal extends JComponent implements IOSafeTerminal {
                 scrollController);
     }
 
+    /**
+     * Overridden method from Swing's {@code JComponent} class that returns the preferred size of the terminal (in
+     * pixels)
+     * @return The terminal's preferred size in pixels
+     */
     @Override
     public synchronized Dimension getPreferredSize() {
         return terminalImplementation.getPreferredSize();
     }
 
+    /**
+     * Overridden method from Swing's {@code JComponent} class that is called by OS window system when the component
+     * needs to be redrawn
+     * @param {@code Graphics} object to use when drawing the component
+     */
     @Override
     protected synchronized void paintComponent(Graphics componentGraphics) {
         terminalImplementation.paintComponent(componentGraphics);
     }
 
+    ////////////////////////////////////////////////////////////////////////////////
     // Terminal methods below here, just forward to the implementation
 
     @Override
index 5bae5b085852f91a9be8ea8a669ec657edb563f3..6810da3bea88ee60cc8f8030ad867dd880ac3d83 100644 (file)
@@ -39,6 +39,9 @@ public interface TerminalScrollController {
      */
     int getScrollingOffset();
 
+    /**
+     * Implementation of {@link TerminalScrollController} that does nothing
+     */
     final class Null implements TerminalScrollController {
         @Override
         public void updateModel(int totalSize, int screenSize) {
diff --git a/src/resources/default-theme.properties b/src/resources/default-theme.properties
new file mode 100644 (file)
index 0000000..9e89475
--- /dev/null
@@ -0,0 +1,135 @@
+# This is the default properties
+
+foreground = white
+background = black
+sgr        =
+foreground[SELECTED] = yellow
+background[SELECTED] = blue
+sgr[SELECTED]        = bold
+
+com.googlecode.lanterna.foreground = black
+com.googlecode.lanterna.background = white
+com.googlecode.lanterna.sgr        =
+com.googlecode.lanterna.foreground[SELECTED] = yellow
+com.googlecode.lanterna.background[SELECTED] = blue
+com.googlecode.lanterna.sgr[SELECTED]        = bold
+
+# Default color and style for the window decoration renderer
+com.googlecode.lanterna.gui2.DefaultWindowDecorationRenderer.foreground = black
+com.googlecode.lanterna.gui2.DefaultWindowDecorationRenderer.background = white
+com.googlecode.lanterna.gui2.DefaultWindowDecorationRenderer.foreground[PRELIGHT] = white
+com.googlecode.lanterna.gui2.DefaultWindowDecorationRenderer.sgr[PRELIGHT] = bold
+com.googlecode.lanterna.gui2.DefaultWindowDecorationRenderer.char[HORIZONTAL_LINE] = \u2500
+com.googlecode.lanterna.gui2.DefaultWindowDecorationRenderer.char[VERTICAL_LINE] = \u2502
+com.googlecode.lanterna.gui2.DefaultWindowDecorationRenderer.char[BOTTOM_LEFT_CORNER] = \u2514
+com.googlecode.lanterna.gui2.DefaultWindowDecorationRenderer.char[TOP_LEFT_CORNER] = \u250c
+com.googlecode.lanterna.gui2.DefaultWindowDecorationRenderer.char[BOTTOM_RIGHT_CORNER] = \u2518
+com.googlecode.lanterna.gui2.DefaultWindowDecorationRenderer.char[TOP_RIGHT_CORNER] = \u2510
+
+#Borders
+com.googlecode.lanterna.gui2.Borders$StandardBorder.foreground = black
+com.googlecode.lanterna.gui2.Borders$StandardBorder.background = white
+com.googlecode.lanterna.gui2.Borders$StandardBorder.sgr =
+com.googlecode.lanterna.gui2.Borders$StandardBorder.foreground[PRELIGHT] = white
+com.googlecode.lanterna.gui2.Borders$StandardBorder.sgr[PRELIGHT] = bold
+com.googlecode.lanterna.gui2.Borders$SingleLine.char[HORIZONTAL_LINE] = \u2500
+com.googlecode.lanterna.gui2.Borders$SingleLine.char[VERTICAL_LINE] = \u2502
+com.googlecode.lanterna.gui2.Borders$SingleLine.char[BOTTOM_LEFT_CORNER] = \u2514
+com.googlecode.lanterna.gui2.Borders$SingleLine.char[TOP_LEFT_CORNER] = \u250c
+com.googlecode.lanterna.gui2.Borders$SingleLine.char[BOTTOM_RIGHT_CORNER] = \u2518
+com.googlecode.lanterna.gui2.Borders$SingleLine.char[TOP_RIGHT_CORNER] = \u2510
+com.googlecode.lanterna.gui2.Borders$DoubleLine.char[HORIZONTAL_LINE] = \u2550
+com.googlecode.lanterna.gui2.Borders$DoubleLine.char[VERTICAL_LINE] = \u2551
+com.googlecode.lanterna.gui2.Borders$DoubleLine.char[BOTTOM_LEFT_CORNER] = \u255a
+com.googlecode.lanterna.gui2.Borders$DoubleLine.char[TOP_LEFT_CORNER] = \u2554
+com.googlecode.lanterna.gui2.Borders$DoubleLine.char[BOTTOM_RIGHT_CORNER] = \u255d
+com.googlecode.lanterna.gui2.Borders$DoubleLine.char[TOP_RIGHT_CORNER] = \u2557
+
+#Button
+com.googlecode.lanterna.gui2.Button.renderer = com.googlecode.lanterna.gui2.Button$DefaultButtonRenderer
+com.googlecode.lanterna.gui2.Button.foreground = black
+com.googlecode.lanterna.gui2.Button.background = white
+com.googlecode.lanterna.gui2.Button.sgr = bold
+com.googlecode.lanterna.gui2.Button.foreground[SELECTED] = yellow
+com.googlecode.lanterna.gui2.Button.background[SELECTED] = blue
+com.googlecode.lanterna.gui2.Button.sgr[SELECTED] = bold
+com.googlecode.lanterna.gui2.Button.foreground[ACTIVE] = white
+com.googlecode.lanterna.gui2.Button.background[ACTIVE] = blue
+com.googlecode.lanterna.gui2.Button.sgr[ACTIVE] = bold
+com.googlecode.lanterna.gui2.Button.foreground[PRELIGHT] = red
+com.googlecode.lanterna.gui2.Button.background[PRELIGHT] = white
+com.googlecode.lanterna.gui2.Button.sgr[PRELIGHT] =
+com.googlecode.lanterna.gui2.Button.foreground[INSENSITIVE] = black
+com.googlecode.lanterna.gui2.Button.background[INSENSITIVE] = white
+com.googlecode.lanterna.gui2.Button.sgr[INSENSITIVE] =
+com.googlecode.lanterna.gui2.Button.char[LEFT_BORDER] = <
+com.googlecode.lanterna.gui2.Button.char[RIGHT_BORDER] = >
+
+# List boxes default
+com.googlecode.lanterna.gui2.AbstractListBox.foreground = black
+com.googlecode.lanterna.gui2.AbstractListBox.background = white
+com.googlecode.lanterna.gui2.AbstractListBox.foreground[SELECTED] = white
+com.googlecode.lanterna.gui2.AbstractListBox.background[SELECTED] = blue
+com.googlecode.lanterna.gui2.AbstractListBox.foreground[INSENSITIVE] = white
+com.googlecode.lanterna.gui2.AbstractListBox.background[INSENSITIVE] = black
+
+# TextBox
+com.googlecode.lanterna.gui2.TextBox.foreground = white
+com.googlecode.lanterna.gui2.TextBox.background = blue
+com.googlecode.lanterna.gui2.TextBox.foreground[ACTIVE] = yellow
+com.googlecode.lanterna.gui2.TextBox.background[ACTIVE] = blue
+com.googlecode.lanterna.gui2.TextBox.sgr[ACTIVE] = bold
+
+# CheckBox
+com.googlecode.lanterna.gui2.CheckBox.foreground = black
+com.googlecode.lanterna.gui2.CheckBox.background = white
+com.googlecode.lanterna.gui2.CheckBox.foreground[PRELIGHT] = white
+com.googlecode.lanterna.gui2.CheckBox.background[PRELIGHT] = blue
+com.googlecode.lanterna.gui2.CheckBox.sgr[PRELIGHT] = bold
+com.googlecode.lanterna.gui2.CheckBox.foreground[ACTIVE] = yellow
+com.googlecode.lanterna.gui2.CheckBox.background[ACTIVE] = blue
+com.googlecode.lanterna.gui2.CheckBox.sgr[ACTIVE] = bold
+com.googlecode.lanterna.gui2.CheckBox.char[MARKER] = x
+
+# Separator
+com.googlecode.lanterna.gui2.Separator.foreground = black
+com.googlecode.lanterna.gui2.Separator.background = white
+com.googlecode.lanterna.gui2.Separator.sgr = bold
+
+# ScrollBar
+com.googlecode.lanterna.gui2.ScrollBar.char[UP_ARROW]=\u2191
+com.googlecode.lanterna.gui2.ScrollBar.char[DOWN_ARROW]=\u2193
+com.googlecode.lanterna.gui2.ScrollBar.char[LEFT_ARROW]=\u2190
+com.googlecode.lanterna.gui2.ScrollBar.char[RIGHT_ARROW]=\u2192
+
+com.googlecode.lanterna.gui2.ScrollBar.char[VERTICAL_BACKGROUND]=\u2502
+com.googlecode.lanterna.gui2.ScrollBar.char[VERTICAL_SMALL_TRACKER]=\u2503
+com.googlecode.lanterna.gui2.ScrollBar.char[VERTICAL_TRACKER_BACKGROUND]=\u2503
+com.googlecode.lanterna.gui2.ScrollBar.char[VERTICAL_TRACKER_TOP]=\u257d
+com.googlecode.lanterna.gui2.ScrollBar.char[VERTICAL_TRACKER_BOTTOM]=\u257f
+
+com.googlecode.lanterna.gui2.ScrollBar.char[HORIZONTAL_BACKGROUND]=\u2500
+com.googlecode.lanterna.gui2.ScrollBar.char[HORIZONTAL_SMALL_TRACKER]=\u2501
+com.googlecode.lanterna.gui2.ScrollBar.char[HORIZONTAL_TRACKER_BACKGROUND]=\u2501
+com.googlecode.lanterna.gui2.ScrollBar.char[HORIZONTAL_TRACKER_LEFT]=\u257c
+com.googlecode.lanterna.gui2.ScrollBar.char[HORIZONTAL_TRACKER_RIGHT]=\u257e
+
+# com.googlecode.lanterna.gui2.ScrollBar.char[VERTICAL_BACKGROUND]=\u2592
+# com.googlecode.lanterna.gui2.ScrollBar.char[HORIZONTAL_BACKGROUND]=\u2592
+# com.googlecode.lanterna.gui2.ScrollBar.char[SMALL_TRACKER]=\u25aa
+# com.googlecode.lanterna.gui2.ScrollBar.char[TRACKER_MIDDLE]=\u25aa
+# com.googlecode.lanterna.gui2.ScrollBar.char[TRACKER_BACKGROUND]=\u0020
+# com.googlecode.lanterna.gui2.ScrollBar.char[TRACKER_TOP]=\u028c
+# com.googlecode.lanterna.gui2.ScrollBar.char[TRACKER_BOTTOM]=\u0076
+# com.googlecode.lanterna.gui2.ScrollBar.char[TRACKER_LEFT]=\u003c
+# com.googlecode.lanterna.gui2.ScrollBar.char[TRACKER_RIGHT]=\u003e
+
+# Table
+com.googlecode.lanterna.gui2.table.Table.foreground = black
+com.googlecode.lanterna.gui2.table.Table.background = white
+com.googlecode.lanterna.gui2.table.Table.sgr[HEADER] = underline,bold
+com.googlecode.lanterna.gui2.table.Table.foreground[SELECTED] = white
+com.googlecode.lanterna.gui2.table.Table.background[SELECTED] = blue
+com.googlecode.lanterna.gui2.table.Table.foreground[ACTIVE] = yellow
+com.googlecode.lanterna.gui2.table.Table.background[ACTIVE] = blue
+com.googlecode.lanterna.gui2.table.Table.sgr[ACTIVE] = bold
\ No newline at end of file
diff --git a/src/resources/multilang/lanterna-ui.properties b/src/resources/multilang/lanterna-ui.properties
new file mode 100644 (file)
index 0000000..51fe286
--- /dev/null
@@ -0,0 +1,11 @@
+short.label.ok=OK
+short.label.cancel=Cancel
+short.label.yes=Yes
+short.label.no=No
+short.label.close=Close
+short.label.abort=Abort
+short.label.ignore=Ignore
+short.label.retry=Retry
+short.label.continue=Continue
+short.label.open=Open
+short.label.save=Save
\ No newline at end of file
diff --git a/src/resources/multilang/lanterna-ui_da.properties b/src/resources/multilang/lanterna-ui_da.properties
new file mode 100644 (file)
index 0000000..94e8467
--- /dev/null
@@ -0,0 +1,11 @@
+short.label.ok=OK
+short.label.cancel=Annullér
+short.label.yes=Ja
+short.label.no=Nej
+short.label.close=Luk
+short.label.abort=Afbryd
+short.label.ignore=Ignorér
+short.label.retry=Prøv igen
+short.label.continue=Fortsæt
+short.label.open=Åbn
+short.label.save=Gem
\ No newline at end of file
diff --git a/src/resources/multilang/lanterna-ui_de.properties b/src/resources/multilang/lanterna-ui_de.properties
new file mode 100644 (file)
index 0000000..932993f
--- /dev/null
@@ -0,0 +1,11 @@
+short.label.ok=Ok
+short.label.cancel=Abbrechen
+short.label.yes=Ja
+short.label.no=Nein
+short.label.close=Schließen
+short.label.abort=Beenden
+short.label.ignore=Ignorieren
+short.label.retry=Wiederholen
+short.label.continue=Weiter
+short.label.open=Öffnen
+short.label.save=Speichern
\ No newline at end of file
diff --git a/src/resources/multilang/lanterna-ui_fi.properties b/src/resources/multilang/lanterna-ui_fi.properties
new file mode 100644 (file)
index 0000000..2b27367
--- /dev/null
@@ -0,0 +1,11 @@
+short.label.ok=OK
+short.label.cancel=Peru
+short.label.yes=Kyllä
+short.label.no=Ei
+short.label.close=Sulje
+short.label.abort=Keskeytä
+short.label.ignore=Ohita
+short.label.retry=Yritä uudelleen
+short.label.continue=Jatka
+short.label.open=Avaa
+short.label.save=Tallenna
\ No newline at end of file
diff --git a/src/resources/multilang/lanterna-ui_fr.properties b/src/resources/multilang/lanterna-ui_fr.properties
new file mode 100644 (file)
index 0000000..192c016
--- /dev/null
@@ -0,0 +1,11 @@
+short.label.ok=Ok
+short.label.cancel=Annuler
+short.label.yes=Oui
+short.label.no=Non
+short.label.close=Fermer
+short.label.abort=Abandonner
+short.label.ignore=Ignorer
+short.label.retry=Réessayer
+short.label.continue=Continuer
+short.label.open=Ouvrir
+short.label.save=Enregistrer
\ No newline at end of file
diff --git a/src/resources/multilang/lanterna-ui_ja.properties b/src/resources/multilang/lanterna-ui_ja.properties
new file mode 100644 (file)
index 0000000..3d0d7ca
--- /dev/null
@@ -0,0 +1,11 @@
+short.label.ok=OK
+short.label.cancel=キャンセル
+short.label.yes=はい
+short.label.no=いいえ
+short.label.close=閉じる
+short.label.abort=中断
+short.label.ignore=無視
+short.label.retry=再試行
+short.label.continue=続ける
+short.label.open=オープン
+short.label.save=保存
\ No newline at end of file
diff --git a/src/resources/multilang/lanterna-ui_nb.properties b/src/resources/multilang/lanterna-ui_nb.properties
new file mode 100644 (file)
index 0000000..f7d6536
--- /dev/null
@@ -0,0 +1,11 @@
+short.label.ok=OK
+short.label.cancel=Avbryt
+short.label.yes=Ja
+short.label.no=Nei
+short.label.close=Lukk
+short.label.abort=Avbryt
+short.label.ignore=Ignorer
+short.label.retry=Prøv igjen
+short.label.continue=Fortsett
+short.label.open=Åpne
+short.label.save=Lagre
\ No newline at end of file
diff --git a/src/resources/multilang/lanterna-ui_nn.properties b/src/resources/multilang/lanterna-ui_nn.properties
new file mode 100644 (file)
index 0000000..f7d6536
--- /dev/null
@@ -0,0 +1,11 @@
+short.label.ok=OK
+short.label.cancel=Avbryt
+short.label.yes=Ja
+short.label.no=Nei
+short.label.close=Lukk
+short.label.abort=Avbryt
+short.label.ignore=Ignorer
+short.label.retry=Prøv igjen
+short.label.continue=Fortsett
+short.label.open=Åpne
+short.label.save=Lagre
\ No newline at end of file
diff --git a/src/resources/multilang/lanterna-ui_no.properties b/src/resources/multilang/lanterna-ui_no.properties
new file mode 100644 (file)
index 0000000..f7d6536
--- /dev/null
@@ -0,0 +1,11 @@
+short.label.ok=OK
+short.label.cancel=Avbryt
+short.label.yes=Ja
+short.label.no=Nei
+short.label.close=Lukk
+short.label.abort=Avbryt
+short.label.ignore=Ignorer
+short.label.retry=Prøv igjen
+short.label.continue=Fortsett
+short.label.open=Åpne
+short.label.save=Lagre
\ No newline at end of file
diff --git a/src/resources/multilang/lanterna-ui_sv.properties b/src/resources/multilang/lanterna-ui_sv.properties
new file mode 100644 (file)
index 0000000..2660a78
--- /dev/null
@@ -0,0 +1,11 @@
+short.label.ok=OK
+short.label.cancel=Avbryt
+short.label.yes=Ja
+short.label.no=Nej
+short.label.close=Stäng
+short.label.abort=Avbryt
+short.label.ignore=Ignorera
+short.label.retry=Försök igen
+short.label.continue=Fortsätt
+short.label.open=Öppna
+short.label.save=Spara
\ No newline at end of file