From: Niki Roo Date: Fri, 26 Feb 2016 15:50:27 +0000 (+0100) Subject: Update lanterna, fix bugs, implement save... X-Git-Tag: v1.0-beta1~7^2~6 X-Git-Url: http://git.nikiroo.be/?p=jvcard.git;a=commitdiff_plain;h=bcb54330afff6a443ab43ee3d38cc7f863c701b7 Update lanterna, fix bugs, implement save... --- 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 index 0000000..bb45a36 Binary files /dev/null and b/lanterna/2016-02-26 - 10:25 lanterna-master.zip differ diff --git a/src/be/nikiroo/jvcard/Card.java b/src/be/nikiroo/jvcard/Card.java index 65ab6fa..e82ce7c 100644 --- a/src/be/nikiroo/jvcard/Card.java +++ b/src/be/nikiroo/jvcard/Card.java @@ -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 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 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 getContacts() { + ArrayList list = new ArrayList(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 and all its descendants that it is in pristine + * state (as opposed to dirty). + */ + void setPristine() { + dirty = false; + for (Contact contact : contacts) { + contact.setPristine(); + } + } } diff --git a/src/be/nikiroo/jvcard/Contact.java b/src/be/nikiroo/jvcard/Contact.java index 21ed69b..6436313 100644 --- a/src/be/nikiroo/jvcard/Contact.java +++ b/src/be/nikiroo/jvcard/Contact.java @@ -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 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 and all its descendants that it is in pristine + * state (as opposed to dirty). + */ + void setPristine() { + dirty = false; + for (Data data: datas) { + data.setPristine(); + } + } } diff --git a/src/be/nikiroo/jvcard/Data.java b/src/be/nikiroo/jvcard/Data.java index 9935332..3f62b7e 100644 --- a/src/be/nikiroo/jvcard/Data.java +++ b/src/be/nikiroo/jvcard/Data.java @@ -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 and all its descendants 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; } + } diff --git a/src/be/nikiroo/jvcard/i18n/Trans.java b/src/be/nikiroo/jvcard/i18n/Trans.java index cea5df4..6030504 100644 --- a/src/be/nikiroo/jvcard/i18n/Trans.java +++ b/src/be/nikiroo/jvcard/i18n/Trans.java @@ -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"); } } diff --git a/src/be/nikiroo/jvcard/parsers/AbookParser.java b/src/be/nikiroo/jvcard/parsers/AbookParser.java index 92bef01..2fd0ca2 100644 --- a/src/be/nikiroo/jvcard/parsers/AbookParser.java +++ b/src/be/nikiroo/jvcard/parsers/AbookParser.java @@ -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)); } diff --git a/src/be/nikiroo/jvcard/parsers/Vcard21Parser.java b/src/be/nikiroo/jvcard/parsers/Vcard21Parser.java index 6cb4635..cc74216 100644 --- a/src/be/nikiroo/jvcard/parsers/Vcard21Parser.java +++ b/src/be/nikiroo/jvcard/parsers/Vcard21Parser.java @@ -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 parse(List lines) { + public static List parse(Iterable textData) { + Iterator lines = textData.iterator(); List contacts = new LinkedList(); List 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(); } 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 index 6cda3e6..0000000 --- a/src/be/nikiroo/jvcard/test/TestCli.java +++ /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 files = new LinkedList(); - files.add(new File("/home/niki/vcf/coworkers.vcf")); - files.add(new File("/home/niki/vcf/oce.vcf")); - win = new MainWindow(new FileList(files)); - // - - TuiLauncher.start(textMode, win); - - /* - * String file = args.length > 0 ? args[0] : null; String file2 = - * args.length > 1 ? args[1] : null; - * - * if (file == null) file = - * "/home/niki/workspace/rcard/utils/CVcard/test.vcf"; if (file2 == - * null) file2 = "/home/niki/workspace/rcard/utils/CVcard/test.abook"; - * - * Card card = new Card(new File(file), Format.VCard21); - * System.out.println(card.toString()); - * - * System.out.println("\n -- PINE -- \n"); - * - * card = new Card(new File(file2), Format.Abook); - * System.out.println(card.toString(Format.Abook)); - */ - } - - static private Table test2() throws IOException { - final Table table = new Table("Column 1", "Column 2", - "Column 3"); - table.getTableModel().addRow("1", "2", "3"); - table.setSelectAction(new Runnable() { - @Override - public void run() { - List data = table.getTableModel().getRow( - table.getSelectedRow()); - for (int i = 0; i < data.size(); i++) { - System.out.println(data.get(i)); - } - } - }); - - 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 index 5107fa0..0000000 --- a/src/be/nikiroo/jvcard/tui/ContactDetails.java +++ /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 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 index 67e5952..0000000 --- a/src/be/nikiroo/jvcard/tui/ContactList.java +++ /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 formats = new LinkedList(); - 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() { - /** - * 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 getKeyBindings() { - List actions = new LinkedList(); - - // 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; - } -} diff --git a/src/be/nikiroo/jvcard/tui/KeyAction.java b/src/be/nikiroo/jvcard/tui/KeyAction.java index ac1b093..70282d5 100644 --- a/src/be/nikiroo/jvcard/tui/KeyAction.java +++ b/src/be/nikiroo/jvcard/tui/KeyAction.java @@ -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 { diff --git a/src/be/nikiroo/jvcard/tui/Main.java b/src/be/nikiroo/jvcard/tui/Main.java index ab6849c..a95e2e3 100644 --- a/src/be/nikiroo/jvcard/tui/Main.java +++ b/src/be/nikiroo/jvcard/tui/Main.java @@ -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 files = new LinkedList(); - files.add(new File("/home/niki/vcf/coworkers.vcf")); - files.add(new File("/home/niki/vcf/oce.vcf")); - win = new MainWindow(new FileList(files)); - // + 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 open(String path) { + List files = new LinkedList(); + + 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 table = new Table("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 index 1977358..0000000 --- a/src/be/nikiroo/jvcard/tui/MainContent.java +++ /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 getKeyBindings(); - - /** - * Move the active cursor (not the text cursor, but the currently active - * item). - * - * @param x - * the horizontal move (< 0 for left, > 0 for right) - * @param y - * the vertical move (< 0 for up, > 0 for down) - * - * @return the error message to display if any - */ - abstract public String move(int x, int y); -} diff --git a/src/be/nikiroo/jvcard/tui/MainWindow.java b/src/be/nikiroo/jvcard/tui/MainWindow.java index a09751e..459d1b1 100644 --- a/src/be/nikiroo/jvcard/tui/MainWindow.java +++ b/src/be/nikiroo/jvcard/tui/MainWindow.java @@ -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 defaultActions = new LinkedList(); private List actions = new LinkedList(); private List contentStack = new LinkedList(); - private boolean actionsPadded; private boolean waitForOneKeyAnswer; private KeyStroke questionKey; // key that "asked" a question, and to replay // later with an answer - private String title; + private 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 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(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 inside 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 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); diff --git a/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java b/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java index c1e86c3..1fad960 100644 --- a/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java +++ b/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java @@ -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 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 parts = new LinkedList(); + 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 getKeyBindings() { // TODO Auto-generated method stub - return null; + List actions = new LinkedList(); + + // 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 diff --git a/src/be/nikiroo/jvcard/tui/panes/ContactList.java b/src/be/nikiroo/jvcard/tui/panes/ContactList.java index 370aba7..d5cf26b 100644 --- a/src/be/nikiroo/jvcard/tui/panes/ContactList.java +++ b/src/be/nikiroo/jvcard/tui/panes/ContactList.java @@ -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 getKeyBindings() { List actions = new LinkedList(); - // 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 getLabel(int index, int width, boolean selected, boolean focused) { - Contact c = card.getContacts().get(index); + List parts = new LinkedList(); + + 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 parts = new LinkedList(); - 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; diff --git a/src/be/nikiroo/jvcard/tui/panes/FileList.java b/src/be/nikiroo/jvcard/tui/panes/FileList.java index 79f530e..dfc3b73 100644 --- a/src/be/nikiroo/jvcard/tui/panes/FileList.java +++ b/src/be/nikiroo/jvcard/tui/panes/FileList.java @@ -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 files; + private List cards; public FileList(List files) { super(UiColors.Element.CONTACT_LINE, @@ -34,10 +38,11 @@ public class FileList extends MainContentList { public void setFiles(List files) { clearItems(); this.files = files; + cards = new ArrayList(); - // 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 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 parts = new LinkedList(); + + 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 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; - } - } diff --git a/src/be/nikiroo/jvcard/tui/panes/MainContent.java b/src/be/nikiroo/jvcard/tui/panes/MainContent.java index 0bc9b29..c33bbd3 100644 --- a/src/be/nikiroo/jvcard/tui/panes/MainContent.java +++ b/src/be/nikiroo/jvcard/tui/panes/MainContent.java @@ -51,12 +51,14 @@ abstract public class MainContent extends Panel { abstract public List 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(); + } } diff --git a/src/be/nikiroo/jvcard/tui/panes/MainContentList.java b/src/be/nikiroo/jvcard/tui/panes/MainContentList.java index 8365419..38b775c 100644 --- a/src/be/nikiroo/jvcard/tui/panes/MainContentList.java +++ b/src/be/nikiroo/jvcard/tui/panes/MainContentList.java @@ -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() { diff --git a/src/com/googlecode/lanterna/TerminalTextUtils.java b/src/com/googlecode/lanterna/TerminalTextUtils.java index f4ce6ab..53fe735 100644 --- a/src/com/googlecode/lanterna/TerminalTextUtils.java +++ b/src/com/googlecode/lanterna/TerminalTextUtils.java @@ -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 StackOverflow 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 + * StackOverflow + * 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 availableColumnSpace - * columns. This method does not handle special cases like tab or new-line. - *

- * 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 + * availableColumnSpace columns. This method does not handle special cases like tab or new-line. + *

+ * 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 availableColumnSpace - * columns. This method does not handle special cases like tab or new-line. - *

- * 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 + * availableColumnSpace columns. This method does not handle special cases like tab or new-line. + *

+ * 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 getWordWrappedText(int maxWidth, String... lines) { - List result = new ArrayList(); - LinkedList linesToBeWrapped = new LinkedList(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 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 result = new ArrayList(); + LinkedList linesToBeWrapped = new LinkedList(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; + } } diff --git a/src/com/googlecode/lanterna/gui2/AbstractTextGUIThread.java b/src/com/googlecode/lanterna/gui2/AbstractTextGUIThread.java index df542d0..f4a0016 100644 --- a/src/com/googlecode/lanterna/gui2/AbstractTextGUIThread.java +++ b/src/com/googlecode/lanterna/gui2/AbstractTextGUIThread.java @@ -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(); + } } } diff --git a/src/com/googlecode/lanterna/gui2/BorderLayout.java b/src/com/googlecode/lanterna/gui2/BorderLayout.java index 6cc6e85..446774c 100644 --- a/src/com/googlecode/lanterna/gui2/BorderLayout.java +++ b/src/com/googlecode/lanterna/gui2/BorderLayout.java @@ -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); diff --git a/src/com/googlecode/lanterna/gui2/TextGUIThread.java b/src/com/googlecode/lanterna/gui2/TextGUIThread.java index ba005f8..f76fccd 100644 --- a/src/com/googlecode/lanterna/gui2/TextGUIThread.java +++ b/src/com/googlecode/lanterna/gui2/TextGUIThread.java @@ -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 is 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 */ diff --git a/src/com/googlecode/lanterna/terminal/swing/AWTTerminal.java b/src/com/googlecode/lanterna/terminal/swing/AWTTerminal.java index 90efcbc..97e20e6 100644 --- a/src/com/googlecode/lanterna/terminal/swing/AWTTerminal.java +++ b/src/com/googlecode/lanterna/terminal/swing/AWTTerminal.java @@ -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! diff --git a/src/com/googlecode/lanterna/terminal/swing/GraphicalTerminalImplementation.java b/src/com/googlecode/lanterna/terminal/swing/GraphicalTerminalImplementation.java index 8c444a8..085f38a 100644 --- a/src/com/googlecode/lanterna/terminal/swing/GraphicalTerminalImplementation.java +++ b/src/com/googlecode/lanterna/terminal/swing/GraphicalTerminalImplementation.java @@ -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; diff --git a/src/com/googlecode/lanterna/terminal/swing/SwingTerminal.java b/src/com/googlecode/lanterna/terminal/swing/SwingTerminal.java index 4597222..3470b64 100644 --- a/src/com/googlecode/lanterna/terminal/swing/SwingTerminal.java +++ b/src/com/googlecode/lanterna/terminal/swing/SwingTerminal.java @@ -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 diff --git a/src/com/googlecode/lanterna/terminal/swing/TerminalScrollController.java b/src/com/googlecode/lanterna/terminal/swing/TerminalScrollController.java index 5bae5b0..6810da3 100644 --- a/src/com/googlecode/lanterna/terminal/swing/TerminalScrollController.java +++ b/src/com/googlecode/lanterna/terminal/swing/TerminalScrollController.java @@ -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 index 0000000..9e89475 --- /dev/null +++ b/src/resources/default-theme.properties @@ -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 index 0000000..51fe286 --- /dev/null +++ b/src/resources/multilang/lanterna-ui.properties @@ -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 index 0000000..94e8467 --- /dev/null +++ b/src/resources/multilang/lanterna-ui_da.properties @@ -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 index 0000000..932993f --- /dev/null +++ b/src/resources/multilang/lanterna-ui_de.properties @@ -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 index 0000000..2b27367 --- /dev/null +++ b/src/resources/multilang/lanterna-ui_fi.properties @@ -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 index 0000000..192c016 --- /dev/null +++ b/src/resources/multilang/lanterna-ui_fr.properties @@ -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 index 0000000..3d0d7ca --- /dev/null +++ b/src/resources/multilang/lanterna-ui_ja.properties @@ -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 index 0000000..f7d6536 --- /dev/null +++ b/src/resources/multilang/lanterna-ui_nb.properties @@ -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 index 0000000..f7d6536 --- /dev/null +++ b/src/resources/multilang/lanterna-ui_nn.properties @@ -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 index 0000000..f7d6536 --- /dev/null +++ b/src/resources/multilang/lanterna-ui_no.properties @@ -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 index 0000000..2660a78 --- /dev/null +++ b/src/resources/multilang/lanterna-ui_sv.properties @@ -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