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;
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();
.readLine()) {
lines.add(line);
}
+ buffer.close();
load(lines, format);
- dirty = false; // initial load, so no change yet
+ dirty = false; // initial load, so no change yet, so no need to call
+ // setPristine()
}
- public List<Contact> getContacts() {
+ /**
+ * Return the full list of {@link Contact}s. Please use responsibly (this is
+ * the original list, do not modify the list itself).
+ *
+ * @return the list of {@link Contact}s
+ */
+ public List<Contact> getContactsList() {
return contacts;
}
+ /**
+ * Return the list of {@link Contact}s. Note that this list is a copy.
+ *
+ * @return the list of {@link Contact}s
+ */
+ public List<Contact> getContacts() {
+ ArrayList<Contact> list = new ArrayList<Contact>(size());
+ list.addAll(contacts);
+ return list;
+ }
+
+ public int size() {
+ return contacts.size();
+ }
+
+ public Contact get(int index) {
+ return contacts.get(index);
+ }
+
public boolean saveAs(File file, Format format) throws IOException {
if (file == null)
return false;
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);
}
void setDirty() {
dirty = true;
}
+
+ /**
+ * Notify this element <i>and all its descendants</i> that it is in pristine
+ * state (as opposed to dirty).
+ */
+ void setPristine() {
+ dirty = false;
+ for (Contact contact : contacts) {
+ contact.setPristine();
+ }
+ }
}
for (int i = 0; i < formatFields.length; i++) {
str.add("");
}
-
+
return str.toArray(new String[] {});
}
this.parent.setDirty();
}
- public void setParent(Card parent) {
+ void setParent(Card parent) {
this.parent = parent;
for (Data data : datas) {
data.setParent(this);
}
}
+
+ /**
+ * Delete this {@link Contact} from its parent {@link Card} if any.
+ *
+ * @return TRUE in case of success
+ */
+ public boolean delete() {
+ if (parent != null) {
+ List<Contact> list = parent.getContactsList();
+ for (int i = 0; i < list.size(); i++) {
+ if (list.get(i) == this) {
+ list.remove(i);
+ parent.setDirty();
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Notify this element <i>and all its descendants</i> that it is in pristine
+ * state (as opposed to dirty).
+ */
+ void setPristine() {
+ dirty = false;
+ for (Data data: datas) {
+ data.setPristine();
+ }
+ }
}
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;
}
return dirty;
}
- public void setParent(Contact parent) {
+ /**
+ * Notify that this element has unsaved changes, and notify its parent of
+ * the same if any.
+ */
+ protected void setDirty() {
+ this.dirty = true;
+ if (this.parent != null)
+ this.parent.setDirty();
+ }
+
+ /**
+ * Notify this element <i>and all its descendants</i> that it is in pristine
+ * state (as opposed to dirty).
+ */
+ void setPristine() {
+ dirty = false;
+ for (TypeInfo type : types) {
+ // TODO ?
+ }
+ }
+
+ void setParent(Contact parent) {
this.parent = parent;
}
+
}
*
*/
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);
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");
}
}
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));
}
package be.nikiroo.jvcard.parsers;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import be.nikiroo.jvcard.TypeInfo;
public class Vcard21Parser {
- public static List<Contact> parse(List<String> lines) {
+ public static List<Contact> parse(Iterable<String> textData) {
+ Iterator<String> lines = textData.iterator();
List<Contact> contacts = new LinkedList<Contact>();
List<Data> datas = null;
- for (String l : lines) {
- String line = l.trim();
+ String nextRawLine = null;
+ if (lines.hasNext()) {
+ nextRawLine = lines.next();
+ while (lines.hasNext() && isContinuation(nextRawLine)) {
+ // BAD INPUT FILE. IGNORE.
+ System.err
+ .println("VCARD Parser warning: CONTINUATION line seen before any data line");
+ nextRawLine = lines.next();
+ }
+ }
+
+ while (nextRawLine != null) {
+ StringBuilder rawLine = new StringBuilder(nextRawLine.trim());
+ if (lines.hasNext())
+ nextRawLine = lines.next();
+ else
+ nextRawLine = null;
+
+ while (isContinuation(nextRawLine)) {
+ rawLine.append(nextRawLine.trim());
+ if (lines.hasNext())
+ nextRawLine = lines.next();
+ else
+ nextRawLine = null;
+ }
+
+ String line = rawLine.toString();
if (line.equals("BEGIN:VCARD")) {
datas = new LinkedList<Data>();
} else if (line.equals("END:VCARD")) {
}
}
builder.append(':');
-
- //TODO: bkey!
+
+ // TODO: bkey!
builder.append(data.getValue());
builder.append("\r\n");
}
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;
+ }
}
+++ /dev/null
-package be.nikiroo.jvcard.test;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.LinkedList;
-import java.util.List;
-
-import be.nikiroo.jvcard.Card;
-import be.nikiroo.jvcard.parsers.Format;
-import be.nikiroo.jvcard.tui.MainWindow;
-import be.nikiroo.jvcard.tui.TuiLauncher;
-import be.nikiroo.jvcard.tui.panes.ContactList;
-import be.nikiroo.jvcard.tui.panes.FileList;
-
-import com.googlecode.lanterna.TerminalSize;
-import com.googlecode.lanterna.TextColor;
-import com.googlecode.lanterna.gui2.BasicWindow;
-import com.googlecode.lanterna.gui2.Button;
-import com.googlecode.lanterna.gui2.DefaultWindowManager;
-import com.googlecode.lanterna.gui2.EmptySpace;
-import com.googlecode.lanterna.gui2.GridLayout;
-import com.googlecode.lanterna.gui2.Label;
-import com.googlecode.lanterna.gui2.MultiWindowTextGUI;
-import com.googlecode.lanterna.gui2.Panel;
-import com.googlecode.lanterna.gui2.TextBox;
-import com.googlecode.lanterna.gui2.Window;
-import com.googlecode.lanterna.gui2.table.Table;
-import com.googlecode.lanterna.screen.Screen;
-import com.googlecode.lanterna.screen.TerminalScreen;
-import com.googlecode.lanterna.terminal.DefaultTerminalFactory;
-import com.googlecode.lanterna.terminal.Terminal;
-
-public class TestCli {
- public static void main(String[] args) throws IOException {
- Boolean textMode = null;
- if (args.length > 0 && args[0].equals("--tui"))
- textMode = true;
- if (args.length > 0 && args[0].equals("--gui"))
- textMode = false;
-
- Window win = null;
-
- // TODO: do not hardcode that:
- Card card = new Card(new File("/home/niki/.addressbook"), Format.Abook);
- win = new MainWindow(new ContactList(card));
- //
- List<File> files = new LinkedList<File>();
- files.add(new File("/home/niki/vcf/coworkers.vcf"));
- files.add(new File("/home/niki/vcf/oce.vcf"));
- win = new MainWindow(new FileList(files));
- //
-
- TuiLauncher.start(textMode, win);
-
- /*
- * String file = args.length > 0 ? args[0] : null; String file2 =
- * args.length > 1 ? args[1] : null;
- *
- * if (file == null) file =
- * "/home/niki/workspace/rcard/utils/CVcard/test.vcf"; if (file2 ==
- * null) file2 = "/home/niki/workspace/rcard/utils/CVcard/test.abook";
- *
- * Card card = new Card(new File(file), Format.VCard21);
- * System.out.println(card.toString());
- *
- * System.out.println("\n -- PINE -- \n");
- *
- * card = new Card(new File(file2), Format.Abook);
- * System.out.println(card.toString(Format.Abook));
- */
- }
-
- static private Table test2() throws IOException {
- final Table<String> table = new Table<String>("Column 1", "Column 2",
- "Column 3");
- table.getTableModel().addRow("1", "2", "3");
- table.setSelectAction(new Runnable() {
- @Override
- public void run() {
- List<String> data = table.getTableModel().getRow(
- table.getSelectedRow());
- for (int i = 0; i < data.size(); i++) {
- System.out.println(data.get(i));
- }
- }
- });
-
- return table;
- }
-
- static private void test() throws IOException {
- // Setup terminal and screen layers
- Terminal terminal = new DefaultTerminalFactory().createTerminal();
- Screen screen = new TerminalScreen(terminal);
- screen.startScreen();
-
- // Create panel to hold components
- Panel panel = new Panel();
- panel.setLayoutManager(new GridLayout(2));
-
- panel.addComponent(new Label("Forename"));
- panel.addComponent(new TextBox());
-
- panel.addComponent(new Label("Surname"));
- panel.addComponent(new TextBox());
-
- panel.addComponent(new EmptySpace(new TerminalSize(0, 0))); // Empty
- // space
- // underneath
- // labels
- panel.addComponent(new Button("Submit"));
-
- // Create window to hold the panel
- BasicWindow window = new BasicWindow();
- window.setComponent(panel);
-
- // Create gui and start gui
- MultiWindowTextGUI gui = new MultiWindowTextGUI(screen,
- new DefaultWindowManager(), new EmptySpace(TextColor.ANSI.BLUE));
- gui.addWindowAndWait(window);
- }
-}
+++ /dev/null
-package be.nikiroo.jvcard.tui;
-
-import java.util.List;
-
-import be.nikiroo.jvcard.Contact;
-import be.nikiroo.jvcard.Data;
-import be.nikiroo.jvcard.tui.KeyAction.DataType;
-import be.nikiroo.jvcard.tui.KeyAction.Mode;
-
-import com.googlecode.lanterna.gui2.Direction;
-import com.googlecode.lanterna.gui2.Interactable;
-import com.googlecode.lanterna.gui2.Label;
-
-public class ContactDetails extends MainContent {
- private Contact contact;
-
- public ContactDetails(Contact contact) {
- super(Direction.VERTICAL);
-
- this.contact = contact;
-
- for (Data data : contact.getContent()) {
- addComponent(new Label(data.getName() + ": " + data.getValue()));
- }
- }
-
- @Override
- public DataType getDataType() {
- return DataType.CONTACT;
- }
-
- @Override
- public String getExitWarning() {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public List<KeyAction> getKeyBindings() {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public Mode getMode() {
- return Mode.CONTACT_DETAILS;
- }
-
- @Override
- public String getTitle() {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public String move(int x, int y) {
- // TODO Auto-generated method stub
- return null;
- }
-}
+++ /dev/null
-package be.nikiroo.jvcard.tui;
-
-import java.util.LinkedList;
-import java.util.List;
-
-import be.nikiroo.jvcard.Card;
-import be.nikiroo.jvcard.Contact;
-import be.nikiroo.jvcard.i18n.Trans;
-import be.nikiroo.jvcard.tui.KeyAction.DataType;
-import be.nikiroo.jvcard.tui.KeyAction.Mode;
-
-import com.googlecode.lanterna.gui2.ActionListBox;
-import com.googlecode.lanterna.gui2.Direction;
-import com.googlecode.lanterna.gui2.Interactable;
-import com.googlecode.lanterna.gui2.LinearLayout;
-import com.googlecode.lanterna.gui2.TextGUIGraphics;
-import com.googlecode.lanterna.gui2.AbstractListBox.ListItemRenderer;
-import com.googlecode.lanterna.input.KeyType;
-
-public class ContactList extends MainContent implements Runnable {
- private Card card;
- private ActionListBox lines;
-
- private List<String> formats = new LinkedList<String>();
- private int selectedFormat = -1;
- private String format = "";
-
- public ContactList(Card card) {
- super(Direction.VERTICAL);
-
- // TODO: should get that in an INI file
- formats.add("NICKNAME@3|FN@+|EMAIL@30");
- formats.add("FN@+|EMAIL@40");
- switchFormat();
-
- lines = new ActionListBox();
-
- lines
- .setListItemRenderer(new ListItemRenderer<Runnable, ActionListBox>() {
- /**
- * This is the main drawing method for a single list box
- * item, it applies the current theme to setup the colors
- * and then calls {@code getLabel(..)} and draws the result
- * using the supplied {@code TextGUIGraphics}. The graphics
- * object is created just for this item and is restricted so
- * that it can only draw on the area this item is occupying.
- * The top-left corner (0x0) should be the starting point
- * when drawing the item.
- *
- * @param graphics
- * Graphics object to draw with
- * @param listBox
- * List box we are drawing an item from
- * @param index
- * Index of the item we are drawing
- * @param item
- * The item we are drawing
- * @param selected
- * Will be set to {@code true} if the item is
- * currently selected, otherwise {@code false},
- * but please notice what context 'selected'
- * refers to here (see {@code setSelectedIndex})
- * @param focused
- * Will be set to {@code true} if the list box
- * currently has input focus, otherwise {@code
- * false}
- */
- public void drawItem(TextGUIGraphics graphics,
- ActionListBox listBox, int index, Runnable item,
- boolean selected, boolean focused) {
-
- if (selected && focused) {
- graphics
- .setForegroundColor(UiColors.Element.CONTACT_LINE_SELECTED
- .getForegroundColor());
- graphics
- .setBackgroundColor(UiColors.Element.CONTACT_LINE_SELECTED
- .getBackgroundColor());
- } else {
- graphics
- .setForegroundColor(UiColors.Element.CONTACT_LINE
- .getForegroundColor());
- graphics
- .setBackgroundColor(UiColors.Element.CONTACT_LINE
- .getBackgroundColor());
- }
-
- String label = getLabel(listBox, index, item);
- // label = TerminalTextUtils.fitString(label,
- // graphics.getSize().getColumns());
-
- Contact c = ContactList.this.card.getContacts().get(
- index);
-
- // we could use: " ", "┃", "│"...
- //TODO: why +5 ?? padding problem?
- label = c.toString(format, " ┃ ", lines.getSize().getColumns() + 5);
-
- graphics.putString(0, 0, label);
- }
- });
-
- addComponent(lines, LinearLayout
- .createLayoutData(LinearLayout.Alignment.Fill));
-
- setCard(card);
- }
-
- private void switchFormat() {
- if (formats.size() == 0)
- return;
-
- selectedFormat++;
- if (selectedFormat >= formats.size()) {
- selectedFormat = 0;
- }
-
- format = formats.get(selectedFormat);
-
- if (lines != null)
- lines.invalidate();
- }
-
- public void setCard(Card card) {
- lines.clearItems();
- this.card = card;
-
- if (card != null) {
- for (int i = 0; i < card.getContacts().size(); i++) {
- lines.addItem("[contact line]", this);
- }
- }
-
- lines.setSelectedIndex(0);
- }
-
- @Override
- public void run() {
- // TODO: item selected.
- // should we do something?
- }
-
- @Override
- public String getExitWarning() {
- if (card != null && card.isDirty()) {
- return "Some of your contact information is not saved";
- }
- return null;
- }
-
- @Override
- public List<KeyAction> getKeyBindings() {
- List<KeyAction> actions = new LinkedList<KeyAction>();
-
- // TODO del, save...
- actions.add(new KeyAction(Mode.CONTACT_DETAILS, 'e',
- Trans.StringId.KEY_ACTION_EDIT_CONTACT) {
- @Override
- public Object getObject() {
- int index = lines.getSelectedIndex();
- return card.getContacts().get(index);
- }
- });
- actions.add(new KeyAction(Mode.CONTACT_DETAILS, KeyType.Enter,
- Trans.StringId.KEY_ACTION_VIEW_CONTACT) {
- @Override
- public Object getObject() {
- int index = lines.getSelectedIndex();
- return card.getContacts().get(index);
- }
- });
- actions.add(new KeyAction(Mode.SWICTH_FORMAT, KeyType.Tab,
- Trans.StringId.KEY_ACTION_SWITCH_FORMAT) {
- @Override
- public boolean onAction() {
- switchFormat();
- return false;
- }
- });
-
- return actions;
- }
-
- public DataType getDataType() {
- return DataType.CARD;
- }
-
- public Mode getMode() {
- return Mode.CONTACT_LIST;
- }
-
- @Override
- public String move(int x, int y) {
- lines.setSelectedIndex(lines.getSelectedIndex() + x);
- // TODO: y?
- return null;
- }
-
- @Override
- public String getTitle() {
- // TODO Auto-generated method stub
- return null;
- }
-}
*
*/
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 {
import java.util.LinkedList;
import java.util.List;
-import be.nikiroo.jvcard.Card;
-import be.nikiroo.jvcard.parsers.Format;
-import be.nikiroo.jvcard.tui.panes.ContactList;
import be.nikiroo.jvcard.tui.panes.FileList;
-import com.googlecode.lanterna.TerminalSize;
import com.googlecode.lanterna.TextColor;
import com.googlecode.lanterna.gui2.BasicWindow;
-import com.googlecode.lanterna.gui2.Button;
import com.googlecode.lanterna.gui2.DefaultWindowManager;
import com.googlecode.lanterna.gui2.EmptySpace;
-import com.googlecode.lanterna.gui2.GridLayout;
-import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.MultiWindowTextGUI;
-import com.googlecode.lanterna.gui2.Panel;
-import com.googlecode.lanterna.gui2.TextBox;
import com.googlecode.lanterna.gui2.Window;
import com.googlecode.lanterna.gui2.table.Table;
import com.googlecode.lanterna.screen.Screen;
import com.googlecode.lanterna.terminal.DefaultTerminalFactory;
import com.googlecode.lanterna.terminal.Terminal;
+/**
+ * This class contains the runnable Main method. It will parse the user supplied
+ * parameters and take action based upon those. Most of the time, it will start
+ * a MainWindow.
+ *
+ * @author niki
+ *
+ */
public class Main {
public static final String APPLICATION_TITLE = "jVcard";
- public static final String APPLICATION_VERSION = "0.9";
+ public static final String APPLICATION_VERSION = "1.0-beta1-dev";
- public static void main(String[] args) throws IOException {
+ public static void main(String[] args) {
Boolean textMode = null;
- if (args.length > 0 && args[0].equals("--tui"))
- textMode = true;
- if (args.length > 0 && args[0].equals("--gui"))
- textMode = false;
+ boolean noMoreParams = false;
+ boolean filesTried = false;
- Window win = null;
-
- // TODO: do not hardcode that:
- Card card = new Card(new File("/home/niki/.addressbook"), Format.Abook);
- win = new MainWindow(new ContactList(card));
- //
List<File> files = new LinkedList<File>();
- files.add(new File("/home/niki/vcf/coworkers.vcf"));
- files.add(new File("/home/niki/vcf/oce.vcf"));
- win = new MainWindow(new FileList(files));
- //
+ for (String arg : args) {
+ if (!noMoreParams && arg.equals("--")) {
+ noMoreParams = true;
+ } else if (!noMoreParams && arg.equals("--help")) {
+ System.out
+ .println("TODO: implement some help text.\n"
+ + "Usable switches:\n"
+ + "\t--: stop looking for switches\n"
+ + "\t--help: this here thingy\n"
+ + "\t--tui: force pure text mode even if swing treminal is available\n"
+ + "\t--gui: force swing terminal mode\n"
+ + "everyhing else is either a file to open or a directory to open\n"
+ + "(we will only open 1st level files in given directories)");
+ return;
+ } else if (!noMoreParams && arg.equals("--tui")) {
+ textMode = true;
+ } else if (!noMoreParams && arg.equals("--gui")) {
+ textMode = false;
+ } else {
+ filesTried = true;
+ files.addAll(open(arg));
+ }
+ }
+
+ if (files.size() == 0) {
+ if (filesTried) {
+ System.exit(1);
+ return;
+ }
- TuiLauncher.start(textMode, win);
+ files.addAll(open("."));
+ }
+
+ try {
+ TuiLauncher.start(textMode, new MainWindow(new FileList(files)));
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ System.exit(2);
+ }
/*
* String file = args.length > 0 ? args[0] : null; String file2 =
*/
}
+ /**
+ * Open the given path and add all its files if it is a directory or just
+ * this one if not to the returned list.
+ *
+ * @param path
+ * the path to open
+ *
+ * @return the list of opened files
+ */
+ static private List<File> open(String path) {
+ List<File> files = new LinkedList<File>();
+
+ File file = new File(path);
+ if (file.exists()) {
+ if (file.isDirectory()) {
+ for (File subfile : file.listFiles()) {
+ if (!subfile.isDirectory())
+ files.add(subfile);
+ }
+ } else {
+ files.add(file);
+ }
+ } else {
+ System.err.println("File or directory not found: \"" + path + "\"");
+ }
+
+ return files;
+ }
+
static private void fullTestTable() throws IOException {
final Table<String> table = new Table<String>("Column 1", "Column 2",
"Column 3");
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();
+++ /dev/null
-package be.nikiroo.jvcard.tui;
-
-import java.util.List;
-
-import com.googlecode.lanterna.gui2.Direction;
-import com.googlecode.lanterna.gui2.Interactable;
-import com.googlecode.lanterna.gui2.LinearLayout;
-import com.googlecode.lanterna.gui2.Panel;
-
-/**
- * This class represents the main content that you can see in this application
- * (i.e., everything but the title and the actions keys is a {@link Panel}
- * extended from this class).
- *
- * @author niki
- *
- */
-abstract public class MainContent extends Panel {
-
- public MainContent() {
- super();
- }
-
- public MainContent(Direction dir) {
- super();
- LinearLayout layout = new LinearLayout(dir);
- layout.setSpacing(0);
- setLayoutManager(layout);
- }
-
- /**
- * The title to display instead of the application name, or NULL for the
- * default application name.
- *
- * @return the title or NULL
- */
- abstract public String getTitle();
-
- /**
- * Returns an error message ready to be displayed if we should ask something
- * to the user before exiting.
- *
- * @return an error message or NULL
- */
- abstract public String getExitWarning();
-
- /**
- * The {@link KeyAction#Mode} that links to this {@link MainContent}.
- *
- * @return the linked mode
- */
- abstract public KeyAction.Mode getMode();
-
- /**
- * The kind of data displayed by this {@link MainContent}.
- *
- * @return the kind of data displayed
- */
- abstract public KeyAction.DataType getDataType();
-
- /**
- * Returns the list of actions and the keys that are bound to it.
- *
- * @return the list of actions
- */
- abstract public List<KeyAction> getKeyBindings();
-
- /**
- * Move the active cursor (not the text cursor, but the currently active
- * item).
- *
- * @param x
- * the horizontal move (< 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);
-}
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;
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;
private List<KeyAction> defaultActions = new LinkedList<KeyAction>();
private List<KeyAction> actions = new LinkedList<KeyAction>();
private List<MainContent> contentStack = new LinkedList<MainContent>();
- private boolean actionsPadded;
private boolean waitForOneKeyAnswer;
private KeyStroke questionKey; // key that "asked" a question, and to replay
// later with an answer
- private String title;
+ private String titleCache;
private Panel titlePanel;
private Panel mainPanel;
private Panel contentPanel;
private Panel actionPanel;
private Panel messagePanel;
private TextBox text;
+ private int width;
/**
* Create a new, empty window.
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));
*/
public void pushContent(MainContent content) {
List<KeyAction> actions = null;
- String title = null;
contentPanel.removeAllComponents();
if (content != null) {
- title = content.getTitle();
actions = content.getKeyBindings();
contentPanel.addComponent(content, BorderLayout.Location.CENTER);
this.contentStack.add(content);
focus.takeFocus();
}
- setTitle(title);
+ setTitle();
setActions(actions, true);
-
- invalidate();
}
/**
* the message to display
* @param error
* TRUE for an error message, FALSE for an information message
+ *
+ * @return TRUE if changes were performed
*/
- public void setMessage(String mess, boolean error) {
- messagePanel.removeAllComponents();
- if (mess != null) {
- Element element = (error ? UiColors.Element.LINE_MESSAGE_ERR
- : UiColors.Element.LINE_MESSAGE);
- Label lbl = element.createLabel(" " + mess + " ");
- messagePanel.addComponent(lbl, LinearLayout
- .createLayoutData(LinearLayout.Alignment.Center));
+ public boolean setMessage(String mess, boolean error) {
+ if (mess != null || messagePanel.getChildCount() > 0) {
+ messagePanel.removeAllComponents();
+ if (mess != null) {
+ Element element = (error ? UiColors.Element.LINE_MESSAGE_ERR
+ : UiColors.Element.LINE_MESSAGE);
+ Label lbl = element.createLabel(" " + mess + " ");
+ messagePanel.addComponent(lbl, LinearLayout
+ .createLayoutData(LinearLayout.Alignment.Center));
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Show a question to the user and switch to "ask for answer" mode see
+ * {@link MainWindow#handleQuestion}. The user will be asked to enter some
+ * answer and confirm with ENTER.
+ *
+ * @param question
+ * the question to ask
+ * @param initial
+ * the initial answer if any (to be edited by the user)
+ */
+ public void setQuestion(KeyStroke key, String question, String initial) {
+ setQuestion(key, question, initial, false);
+ }
+
+ /**
+ * Show a question to the user and switch to "ask for answer" mode see
+ * {@link MainWindow#handleQuestion}. The user will be asked to hit one key
+ * as an answer.
+ *
+ * @param question
+ * the question to ask
+ */
+ public void setQuestion(KeyStroke key, String question) {
+ setQuestion(key, question, null, true);
+ }
+
+ @Override
+ public void draw(TextGUIGraphics graphics) {
+ int width = graphics.getSize().getColumns();
+
+ if (width != this.width) {
+ this.width = width;
+
+ setTitle();
+
+ if (actions != null)
+ setActions(new ArrayList<KeyAction>(actions), false);
+ }
+
+ super.draw(graphics);
+ }
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+ for (MainContent content : contentStack) {
+ content.invalidate();
}
}
* 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;
hpanel.setLayoutManager(llayout);
Label lbl = UiColors.Element.LINE_MESSAGE_QUESTION.createLabel(" "
- + mess + " ");
- text = new TextBox(new TerminalSize(getSize().getColumns()
- - lbl.getSize().getColumns(), 1));
+ + question + " ");
+ text = new TextBox(new TerminalSize(width - lbl.getSize().getColumns(),
+ 1));
+ if (initial != null)
+ text.setText(initial);
- hpanel.addComponent(lbl, LinearLayout
- .createLayoutData(LinearLayout.Alignment.Beginning));
- hpanel.addComponent(text, LinearLayout
- .createLayoutData(LinearLayout.Alignment.Fill));
+ hpanel.addComponent(lbl,
+ LinearLayout.createLayoutData(LinearLayout.Alignment.Beginning));
+ hpanel.addComponent(text,
+ LinearLayout.createLayoutData(LinearLayout.Alignment.Fill));
- messagePanel.addComponent(hpanel, LinearLayout
- .createLayoutData(LinearLayout.Alignment.Beginning));
+ messagePanel
+ .addComponent(hpanel, LinearLayout
+ .createLayoutData(LinearLayout.Alignment.Beginning));
text.takeFocus();
}
- @Override
- public void draw(TextGUIGraphics graphics) {
- if (!actionsPadded) {
- // fill with "desc" colour
- actionPanel.addComponent(UiColors.Element.ACTION_DESC
- .createLabel(StringUtils.padString("", graphics.getSize()
- .getColumns())));
- actionsPadded = true;
- }
- super.draw(graphics);
- }
-
- @Override
- public void setTitle(String title) {
+ /**
+ * Actually set the title <b>inside</b> the window. Will also call
+ * {@link BasicWindow#setTitle} with the compuited parameters.
+ */
+ private void setTitle() {
String prefix = " " + Main.APPLICATION_TITLE + " (version "
+ Main.APPLICATION_VERSION + ")";
+ String title = null;
int count = -1;
+
MainContent content = getContent();
- if (content != null)
+ if (content != null) {
+ title = content.getTitle();
count = content.getCount();
+ }
+
+ if (title == null)
+ title = "";
- if (title != null) {
+ if (title.length() > 0) {
prefix = prefix + ": ";
}
- if (getSize() != null) {
- if (title != null)
- title = StringUtils.padString(title, getSize().getColumns());
- else
- // cause busy-loop freeze:
- prefix = StringUtils.padString(prefix, getSize().getColumns());
+ String countStr = "";
+ if (count > -1) {
+ countStr = "[" + count + "]";
+ }
+
+ if (width > 0) {
+ int padding = width - prefix.length() - title.length()
+ - countStr.length();
+ if (padding > 0) {
+ if (title.length() > 0)
+ title = StringUtils.padString(title, title.length()
+ + padding);
+ else
+ prefix = StringUtils.padString(prefix, prefix.length()
+ + padding);
+ }
}
-
- if (!(title + count).equals(this.title)) {
- this.title = title + count;
- super.setTitle(prefix + title);
+ String titleCache = prefix + title + count;
+ if (!titleCache.equals(this.titleCache)) {
+ super.setTitle(prefix);
Label lblPrefix = new Label(prefix);
UiColors.Element.TITLE_MAIN.themeLabel(lblPrefix);
Label lblTitle = null;
- if (title != null) {
+ if (title.length() > 0) {
lblTitle = new Label(title);
UiColors.Element.TITLE_VARIABLE.themeLabel(lblTitle);
}
Label lblCount = null;
- if (count > -1) {
- lblCount = new Label("[" + count + "]");
+ if (countStr != null) {
+ lblCount = new Label(countStr);
UiColors.Element.TITLE_COUNT.themeLabel(lblCount);
}
titlePanel.addComponent(lblTitle, BorderLayout.Location.CENTER);
if (lblCount != null)
titlePanel.addComponent(lblCount, BorderLayout.Location.RIGHT);
-
- invalidate();
}
}
private void setActions(List<KeyAction> actions,
boolean enableDefaultactions) {
this.actions.clear();
- actionsPadded = false;
if (enableDefaultactions)
this.actions.addAll(defaultActions);
actionPanel.addComponent(kPane);
}
+
+ // fill with "desc" colour
+ if (width > 0) {
+ actionPanel.addComponent(UiColors.Element.ACTION_DESC
+ .createLabel(StringUtils.padString("", width)));
+
+ }
}
/**
* @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))
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;
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));
}
// 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:
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);
package be.nikiroo.jvcard.tui.panes;
+import java.util.LinkedList;
import java.util.List;
+import com.googlecode.lanterna.input.KeyType;
+
import be.nikiroo.jvcard.Contact;
import be.nikiroo.jvcard.Data;
+import be.nikiroo.jvcard.TypeInfo;
+import be.nikiroo.jvcard.i18n.Trans;
import be.nikiroo.jvcard.tui.KeyAction;
import be.nikiroo.jvcard.tui.KeyAction.DataType;
import be.nikiroo.jvcard.tui.KeyAction.Mode;
+import be.nikiroo.jvcard.tui.StringUtils;
+import be.nikiroo.jvcard.tui.UiColors.Element;
-import com.googlecode.lanterna.gui2.Direction;
-import com.googlecode.lanterna.gui2.Label;
-
-public class ContactDetails extends MainContent {
+public class ContactDetails extends MainContentList {
private Contact contact;
+ private int mode;
public ContactDetails(Contact contact) {
- super(Direction.VERTICAL);
+ super(null, null);
this.contact = contact;
+ this.mode = 0;
- for (Data data : contact.getContent()) {
- addComponent(new Label(data.getName() + ": " + data.getValue()));
+ for (int i = 0; i < contact.getContent().size(); i++) {
+ addItem("[detail line]");
}
}
+ @Override
+ protected List<TextPart> getLabel(int index, int width, boolean selected,
+ boolean focused) {
+ // TODO: from ini file?
+ int SIZE_COL_1 = 15;
+
+ Element el = (focused && selected) ? Element.CONTACT_LINE_SELECTED
+ : Element.CONTACT_LINE;
+ Element elSep = (focused && selected) ? Element.CONTACT_LINE_SEPARATOR_SELECTED
+ : Element.CONTACT_LINE_SEPARATOR;
+ Element elDirty = (focused && selected) ? Element.CONTACT_LINE_DIRTY_SELECTED
+ : Element.CONTACT_LINE_DIRTY;
+
+ Data data = contact.getContent().get(index);
+
+ List<TextPart> parts = new LinkedList<TextPart>();
+ if (data.isDirty()) {
+ parts.add(new TextPart(" ", el));
+ parts.add(new TextPart("*", elDirty));
+ } else {
+ parts.add(new TextPart(" ", elSep));
+ }
+ String name = " " + data.getName() + " ";
+ String value = null;
+
+ StringBuilder valueBuilder = new StringBuilder(" ");
+ switch (mode) {
+ case 0:
+ valueBuilder.append(data.getValue());
+ if (data.getGroup() != null && data.getGroup().length() > 0) {
+ valueBuilder.append("(");
+ valueBuilder.append(data.getGroup());
+ valueBuilder.append(")");
+ }
+ break;
+ case 1:
+ for (TypeInfo type : data.getTypes()) {
+ if (valueBuilder.length() > 1)
+ valueBuilder.append(", ");
+ valueBuilder.append(type.getName());
+ valueBuilder.append(": ");
+ valueBuilder.append(type.getValue());
+ }
+ break;
+ }
+ valueBuilder.append(" ");
+
+ value = valueBuilder.toString();
+
+ name = StringUtils.padString(name, SIZE_COL_1);
+ value = StringUtils.padString(value, width - SIZE_COL_1
+ - getSeparator().length() - 2);
+
+ parts.add(new TextPart(name, el));
+ parts.add(new TextPart(getSeparator(), elSep));
+ parts.add(new TextPart(value, el));
+
+ return parts;
+ };
+
@Override
public DataType getDataType() {
- return DataType.CONTACT;
+ return DataType.DATA;
}
@Override
@Override
public List<KeyAction> getKeyBindings() {
// TODO Auto-generated method stub
- return null;
+ List<KeyAction> actions = new LinkedList<KeyAction>();
+
+ // TODO: add, remove
+ actions.add(new KeyAction(Mode.EDIT_DETAIL, 'd', Trans.StringId.DUMMY) {
+ @Override
+ public Object getObject() {
+ return contact.getContent().get(getSelectedIndex());
+ }
+ });
+ actions.add(new KeyAction(Mode.NONE, KeyType.Tab,
+ Trans.StringId.KEY_ACTION_SWITCH_FORMAT) {
+ @Override
+ public boolean onAction() {
+ mode++;
+ if (mode > 1)
+ mode = 0;
+
+ return false;
+ }
+ });
+
+ return actions;
}
@Override
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;
}
public List<KeyAction> getKeyBindings() {
List<KeyAction> actions = new LinkedList<KeyAction>();
- // TODO del, save...
- // TODO: remove
- actions.add(new KeyAction(Mode.NONE, 'd', Trans.StringId.DUMMY) {
+ // TODO add, del, save...
+ actions.add(new KeyAction(Mode.CONTACT_DETAILS, 'e',
+ Trans.StringId.KEY_ACTION_EDIT_CONTACT) {
@Override
- public boolean onAction() {
- //TODO dummy action
- int index = getSelectedIndex();
- Contact c = card.getContacts().get(index);
- c.updateFrom(c);
- return false;
+ public Object getObject() {
+ return getSelectedContact();
}
});
- actions.add(new KeyAction(Mode.CONTACT_DETAILS, 'e',
- Trans.StringId.KEY_ACTION_EDIT_CONTACT) {
+ actions.add(new KeyAction(Mode.DELETE_CONTACT, 'd',
+ Trans.StringId.KEY_ACTION_DELETE_CONTACT) {
+ @Override
+ public Object getObject() {
+ return getSelectedContact();
+ }
+ });
+ actions.add(new KeyAction(Mode.SAVE_CARD, 's',
+ Trans.StringId.KEY_ACTION_SAVE_CARD) {
@Override
public Object getObject() {
- int index = getSelectedIndex();
- return card.getContacts().get(index);
+ return card;
}
});
actions.add(new KeyAction(Mode.CONTACT_DETAILS, KeyType.Enter,
Trans.StringId.KEY_ACTION_VIEW_CONTACT) {
@Override
public Object getObject() {
- int index = getSelectedIndex();
- return card.getContacts().get(index);
+ return getSelectedContact();
}
});
- actions.add(new KeyAction(Mode.SWICTH_FORMAT, KeyType.Tab,
+ actions.add(new KeyAction(Mode.NONE, KeyType.Tab,
Trans.StringId.KEY_ACTION_SWITCH_FORMAT) {
@Override
public boolean onAction() {
@Override
protected List<TextPart> getLabel(int index, int width, boolean selected,
boolean focused) {
- Contact c = card.getContacts().get(index);
+ List<TextPart> parts = new LinkedList<TextPart>();
+
+ Contact contact = null;
+ if (index > -1 && index < card.size())
+ contact = card.get(index);
+
+ if (contact == null)
+ return parts;
Element el = (focused && selected) ? Element.CONTACT_LINE_SELECTED
: Element.CONTACT_LINE;
width -= 2; // dirty mark space
- // we could use: " ", "┃", "│"...
- String[] array = c.toStringArray(format, "┃", " ", width);
+ String[] array = contact.toStringArray(format, getSeparator(), " ",
+ width);
- List<TextPart> parts = new LinkedList<TextPart>();
- if (c.isDirty()) {
+ if (contact.isDirty()) {
parts.add(new TextPart(" ", el));
parts.add(new TextPart("*", elDirty));
} else {
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;
import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import be.nikiroo.jvcard.i18n.Trans;
import be.nikiroo.jvcard.parsers.Format;
import be.nikiroo.jvcard.tui.KeyAction;
-import be.nikiroo.jvcard.tui.UiColors;
import be.nikiroo.jvcard.tui.KeyAction.DataType;
import be.nikiroo.jvcard.tui.KeyAction.Mode;
+import be.nikiroo.jvcard.tui.StringUtils;
+import be.nikiroo.jvcard.tui.UiColors;
+import be.nikiroo.jvcard.tui.UiColors.Element;
import com.googlecode.lanterna.input.KeyType;
public class FileList extends MainContentList {
private List<File> files;
+ private List<Card> cards;
public FileList(List<File> files) {
super(UiColors.Element.CONTACT_LINE,
public void setFiles(List<File> files) {
clearItems();
this.files = files;
+ cards = new ArrayList<Card>();
- // TODO
for (File file : files) {
addItem(file.getName());
+ cards.add(null);
}
setSelectedIndex(0);
}
@Override
- public String getExitWarning() {
- // TODO Auto-generated method stub
- return null;
- }
+ protected List<TextPart> getLabel(int index, int width, boolean selected,
+ boolean focused) {
+ // TODO: from ini file?
+ int SIZE_COL_1 = 3;
+
+ Element el = (focused && selected) ? Element.CONTACT_LINE_SELECTED
+ : Element.CONTACT_LINE;
+ Element elSep = (focused && selected) ? Element.CONTACT_LINE_SEPARATOR_SELECTED
+ : Element.CONTACT_LINE_SEPARATOR;
+
+ List<TextPart> parts = new LinkedList<TextPart>();
+
+ String count = "";
+ if (cards.get(index) != null)
+ count += cards.get(index).size();
+
+ String name = files.get(index).getName();
+
+ count = " " + StringUtils.padString(count, SIZE_COL_1) + " ";
+ name = " "
+ + StringUtils.padString(name, width - SIZE_COL_1
+ - getSeparator().length()) + " ";
+
+ parts.add(new TextPart(count, el));
+ parts.add(new TextPart(getSeparator(), elSep));
+ parts.add(new TextPart(name, el));
+
+ return parts;
+ };
@Override
public List<KeyAction> getKeyBindings() {
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(".")) {
}
}
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;
public Mode getMode() {
return Mode.FILE_LIST;
}
-
- @Override
- public String getTitle() {
- // TODO Auto-generated method stub
- return null;
- }
-
}
abstract public List<KeyAction> getKeyBindings();
/**
- * The title to display instead of the application name, or NULL for the
+ * The title to display in addition to the application name, or NULL for the
* default application name.
*
* @return the title or NULL
*/
- abstract public String getTitle();
+ public String getTitle() {
+ return null;
+ }
/**
* Returns an error message ready to be displayed if we should ask something
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();
+ }
}
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() {
import java.util.List;
/**
- * This class contains a number of utility methods for analyzing characters and
- * strings in a terminal context. The main purpose is to make it easier to work
- * with text that may or may not contain double-width text characters, such as
- * CJK (Chinese, Japanese, Korean) and other special symbols. This class assumes
- * those are all double-width and in case the terminal (-emulator) chooses to
- * draw them (somehow) as single-column then all the calculations in this class
- * will be wrong. It seems safe to assume what this class considers double-width
- * really is taking up two columns though.
+ * This class contains a number of utility methods for analyzing characters and strings in a terminal context. The main
+ * purpose is to make it easier to work with text that may or may not contain double-width text characters, such as CJK
+ * (Chinese, Japanese, Korean) and other special symbols. This class assumes those are all double-width and in case the
+ * terminal (-emulator) chooses to draw them (somehow) as single-column then all the calculations in this class will be
+ * wrong. It seems safe to assume what this class considers double-width really is taking up two columns though.
*
* @author Martin
*/
public class TerminalTextUtils {
- private TerminalTextUtils() {
- }
+ private TerminalTextUtils() {
+ }
- /**
- * Given a character, is this character considered to be a CJK character?
- * Shamelessly stolen from <a href="http://stackoverflow.com/questions/1499804/how-can-i-detect-japanese-text-in-a-java-string"
- * >StackOverflow</a> where it was contributed by user Rakesh N
- *
- * @param c
- * Character to test
- * @return {@code true} if the character is a CJK character
- *
- */
- public static boolean isCharCJK(final char c) {
- Character.UnicodeBlock unicodeBlock = Character.UnicodeBlock.of(c);
- return (unicodeBlock == Character.UnicodeBlock.HIRAGANA)
- || (unicodeBlock == Character.UnicodeBlock.KATAKANA)
- || (unicodeBlock == Character.UnicodeBlock.KATAKANA_PHONETIC_EXTENSIONS)
- || (unicodeBlock == Character.UnicodeBlock.HANGUL_COMPATIBILITY_JAMO)
- || (unicodeBlock == Character.UnicodeBlock.HANGUL_JAMO)
- || (unicodeBlock == Character.UnicodeBlock.HANGUL_SYLLABLES)
- || (unicodeBlock == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS)
- || (unicodeBlock == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A)
- || (unicodeBlock == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B)
- || (unicodeBlock == Character.UnicodeBlock.CJK_COMPATIBILITY_FORMS)
- || (unicodeBlock == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS)
- || (unicodeBlock == Character.UnicodeBlock.CJK_RADICALS_SUPPLEMENT)
- || (unicodeBlock == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION)
- || (unicodeBlock == Character.UnicodeBlock.ENCLOSED_CJK_LETTERS_AND_MONTHS)
- || (unicodeBlock == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS && c < 0xFF61); // The
- // magic
- // number
- // here
- // is
- // the
- // separating
- // index
- // between
- // full-width
- // and
- // half-width
- }
+ /**
+ * Given a character, is this character considered to be a CJK character?
+ * Shamelessly stolen from
+ * <a href="http://stackoverflow.com/questions/1499804/how-can-i-detect-japanese-text-in-a-java-string">StackOverflow</a>
+ * where it was contributed by user Rakesh N
+ * @param c Character to test
+ * @return {@code true} if the character is a CJK character
+ *
+ */
+ public static boolean isCharCJK(final char c) {
+ Character.UnicodeBlock unicodeBlock = Character.UnicodeBlock.of(c);
+ return (unicodeBlock == Character.UnicodeBlock.HIRAGANA)
+ || (unicodeBlock == Character.UnicodeBlock.KATAKANA)
+ || (unicodeBlock == Character.UnicodeBlock.KATAKANA_PHONETIC_EXTENSIONS)
+ || (unicodeBlock == Character.UnicodeBlock.HANGUL_COMPATIBILITY_JAMO)
+ || (unicodeBlock == Character.UnicodeBlock.HANGUL_JAMO)
+ || (unicodeBlock == Character.UnicodeBlock.HANGUL_SYLLABLES)
+ || (unicodeBlock == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS)
+ || (unicodeBlock == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A)
+ || (unicodeBlock == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B)
+ || (unicodeBlock == Character.UnicodeBlock.CJK_COMPATIBILITY_FORMS)
+ || (unicodeBlock == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS)
+ || (unicodeBlock == Character.UnicodeBlock.CJK_RADICALS_SUPPLEMENT)
+ || (unicodeBlock == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION)
+ || (unicodeBlock == Character.UnicodeBlock.ENCLOSED_CJK_LETTERS_AND_MONTHS)
+ || (unicodeBlock == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS && c < 0xFF61); //The magic number here is the separating index between full-width and half-width
+ }
- /**
- * Checks if a character is expected to be taking up two columns if printed
- * to a terminal. This will generally be {@code true} for CJK (Chinese,
- * Japanese and Korean) characters.
- *
- * @param c
- * Character to test if it's double-width when printed to a
- * terminal
- * @return {@code true} if this character is expected to be taking up two
- * columns when printed to the terminal, otherwise {@code false}
- */
- public static boolean isCharDoubleWidth(final char c) {
- return isCharCJK(c);
- }
+ /**
+ * Checks if a character is expected to be taking up two columns if printed to a terminal. This will generally be
+ * {@code true} for CJK (Chinese, Japanese and Korean) characters.
+ * @param c Character to test if it's double-width when printed to a terminal
+ * @return {@code true} if this character is expected to be taking up two columns when printed to the terminal,
+ * otherwise {@code false}
+ */
+ public static boolean isCharDoubleWidth(final char c) {
+ return isCharCJK(c);
+ }
- /**
- * @deprecated Call {@code getColumnWidth(s)} instead
- */
- @Deprecated
- public static int getTrueWidth(String s) {
- return getColumnWidth(s);
- }
+ /**
+ * @deprecated Call {@code getColumnWidth(s)} instead
+ */
+ @Deprecated
+ public static int getTrueWidth(String s) {
+ return getColumnWidth(s);
+ }
- /**
- * Given a string, returns how many columns this string would need to occupy
- * in a terminal, taking into account that CJK characters takes up two
- * columns.
- *
- * @param s
- * String to check length
- * @return Number of actual terminal columns the string would occupy
- */
- public static int getColumnWidth(String s) {
- return getColumnIndex(s, s.length());
- }
+ /**
+ * Given a string, returns how many columns this string would need to occupy in a terminal, taking into account that
+ * CJK characters takes up two columns.
+ * @param s String to check length
+ * @return Number of actual terminal columns the string would occupy
+ */
+ public static int getColumnWidth(String s) {
+ return getColumnIndex(s, s.length());
+ }
- /**
- * Given a string and a character index inside that string, find out what
- * the column index of that character would be if printed in a terminal. If
- * the string only contains non-CJK characters then the returned value will
- * be same as {@code stringCharacterIndex}, but if there are CJK characters
- * the value will be different due to CJK characters taking up two columns
- * in width. If the character at the index in the string is a CJK character
- * itself, the returned value will be the index of the left-side of
- * character.
- *
- * @param s
- * String to translate the index from
- * @param stringCharacterIndex
- * Index within the string to get the terminal column index of
- * @return Index of the character inside the String at {@code
- * stringCharacterIndex} when it has been writted to a terminal
- * @throws StringIndexOutOfBoundsException
- * if the index given is outside the String length or negative
- */
- public static int getColumnIndex(String s, int stringCharacterIndex)
- throws StringIndexOutOfBoundsException {
- int index = 0;
- for (int i = 0; i < stringCharacterIndex; i++) {
- if (isCharCJK(s.charAt(i))) {
- index++;
- }
- index++;
- }
- return index;
- }
+ /**
+ * Given a string and a character index inside that string, find out what the column index of that character would
+ * be if printed in a terminal. If the string only contains non-CJK characters then the returned value will be same
+ * as {@code stringCharacterIndex}, but if there are CJK characters the value will be different due to CJK
+ * characters taking up two columns in width. If the character at the index in the string is a CJK character itself,
+ * the returned value will be the index of the left-side of character.
+ * @param s String to translate the index from
+ * @param stringCharacterIndex Index within the string to get the terminal column index of
+ * @return Index of the character inside the String at {@code stringCharacterIndex} when it has been writted to a
+ * terminal
+ * @throws StringIndexOutOfBoundsException if the index given is outside the String length or negative
+ */
+ public static int getColumnIndex(String s, int stringCharacterIndex) throws StringIndexOutOfBoundsException {
+ int index = 0;
+ for(int i = 0; i < stringCharacterIndex; i++) {
+ if(isCharCJK(s.charAt(i))) {
+ index++;
+ }
+ index++;
+ }
+ return index;
+ }
- /**
- * This method does the reverse of getColumnIndex, given a String and
- * imagining it has been printed out to the top-left corner of a terminal,
- * in the column specified by {@code columnIndex}, what is the index of that
- * character in the string. If the string contains no CJK characters, this
- * will always be the same as {@code columnIndex}. If the index specified is
- * the right column of a CJK character, the index is the same as if the
- * column was the left column. So calling {@code
- * getStringCharacterIndex("英", 0)} and {@code getStringCharacterIndex("英",
- * 1)} will both return 0.
- *
- * @param s
- * String to translate the index to
- * @param columnIndex
- * Column index of the string written to a terminal
- * @return The index in the string of the character in terminal column
- * {@code columnIndex}
- */
- public static int getStringCharacterIndex(String s, int columnIndex) {
- int index = 0;
- int counter = 0;
- while (counter < columnIndex) {
- if (isCharCJK(s.charAt(index++))) {
- counter++;
- if (counter == columnIndex) {
- return index - 1;
- }
- }
- counter++;
- }
- return index;
- }
+ /**
+ * This method does the reverse of getColumnIndex, given a String and imagining it has been printed out to the
+ * top-left corner of a terminal, in the column specified by {@code columnIndex}, what is the index of that
+ * character in the string. If the string contains no CJK characters, this will always be the same as
+ * {@code columnIndex}. If the index specified is the right column of a CJK character, the index is the same as if
+ * the column was the left column. So calling {@code getStringCharacterIndex("英", 0)} and
+ * {@code getStringCharacterIndex("英", 1)} will both return 0.
+ * @param s String to translate the index to
+ * @param columnIndex Column index of the string written to a terminal
+ * @return The index in the string of the character in terminal column {@code columnIndex}
+ */
+ public static int getStringCharacterIndex(String s, int columnIndex) {
+ int index = 0;
+ int counter = 0;
+ while(counter < columnIndex) {
+ if(isCharCJK(s.charAt(index++))) {
+ counter++;
+ if(counter == columnIndex) {
+ return index - 1;
+ }
+ }
+ counter++;
+ }
+ return index;
+ }
- /**
- * Given a string that may or may not contain CJK characters, returns the
- * substring which will fit inside <code>availableColumnSpace</code>
- * columns. This method does not handle special cases like tab or new-line.
- * <p>
- * Calling this method is the same as calling {@code fitString(string, 0,
- * availableColumnSpace)}.
- *
- * @param string
- * The string to fit inside the availableColumnSpace
- * @param availableColumnSpace
- * Number of columns to fit the string inside
- * @return The whole or part of the input string which will fit inside the
- * supplied availableColumnSpace
- */
- public static String fitString(String string, int availableColumnSpace) {
- return fitString(string, 0, availableColumnSpace);
- }
+ /**
+ * Given a string that may or may not contain CJK characters, returns the substring which will fit inside
+ * <code>availableColumnSpace</code> columns. This method does not handle special cases like tab or new-line.
+ * <p>
+ * Calling this method is the same as calling {@code fitString(string, 0, availableColumnSpace)}.
+ * @param string The string to fit inside the availableColumnSpace
+ * @param availableColumnSpace Number of columns to fit the string inside
+ * @return The whole or part of the input string which will fit inside the supplied availableColumnSpace
+ */
+ public static String fitString(String string, int availableColumnSpace) {
+ return fitString(string, 0, availableColumnSpace);
+ }
- /**
- * Given a string that may or may not contain CJK characters, returns the
- * substring which will fit inside <code>availableColumnSpace</code>
- * columns. This method does not handle special cases like tab or new-line.
- * <p>
- * This overload has a {@code fromColumn} parameter that specified where
- * inside the string to start fitting. Please notice that {@code fromColumn}
- * is not a character index inside the string, but a column index as if the
- * string has been printed from the left-most side of the terminal. So if
- * the string is "日本語", fromColumn set to 1 will not starting counting from
- * the second character ("本") in the string but from the CJK filler
- * character belonging to "日". If you want to count from a particular
- * character index inside the string, please pass in a substring and use
- * fromColumn set to 0.
- *
- * @param string
- * The string to fit inside the availableColumnSpace
- * @param fromColumn
- * From what column of the input string to start fitting (see
- * description above!)
- * @param availableColumnSpace
- * Number of columns to fit the string inside
- * @return The whole or part of the input string which will fit inside the
- * supplied availableColumnSpace
- */
- public static String fitString(String string, int fromColumn,
- int availableColumnSpace) {
- if (availableColumnSpace <= 0) {
- return "";
- }
+ /**
+ * Given a string that may or may not contain CJK characters, returns the substring which will fit inside
+ * <code>availableColumnSpace</code> columns. This method does not handle special cases like tab or new-line.
+ * <p>
+ * This overload has a {@code fromColumn} parameter that specified where inside the string to start fitting. Please
+ * notice that {@code fromColumn} is not a character index inside the string, but a column index as if the string
+ * has been printed from the left-most side of the terminal. So if the string is "日本語", fromColumn set to 1 will
+ * not starting counting from the second character ("本") in the string but from the CJK filler character belonging
+ * to "日". If you want to count from a particular character index inside the string, please pass in a substring
+ * and use fromColumn set to 0.
+ * @param string The string to fit inside the availableColumnSpace
+ * @param fromColumn From what column of the input string to start fitting (see description above!)
+ * @param availableColumnSpace Number of columns to fit the string inside
+ * @return The whole or part of the input string which will fit inside the supplied availableColumnSpace
+ */
+ public static String fitString(String string, int fromColumn, int availableColumnSpace) {
+ if(availableColumnSpace <= 0) {
+ return "";
+ }
- StringBuilder bob = new StringBuilder();
- int column = 0;
- int index = 0;
- while (index < string.length() && column < fromColumn) {
- char c = string.charAt(index++);
- column += TerminalTextUtils.isCharCJK(c) ? 2 : 1;
- }
- if (column > fromColumn) {
- bob.append(" ");
- availableColumnSpace--;
- }
+ StringBuilder bob = new StringBuilder();
+ int column = 0;
+ int index = 0;
+ while(index < string.length() && column < fromColumn) {
+ char c = string.charAt(index++);
+ column += TerminalTextUtils.isCharCJK(c) ? 2 : 1;
+ }
+ if(column > fromColumn) {
+ bob.append(" ");
+ availableColumnSpace--;
+ }
- while (availableColumnSpace > 0 && index < string.length()) {
- char c = string.charAt(index++);
- availableColumnSpace -= TerminalTextUtils.isCharCJK(c) ? 2 : 1;
- if (availableColumnSpace < 0) {
- bob.append(' ');
- } else {
- bob.append(c);
- }
- }
- return bob.toString();
- }
+ while(availableColumnSpace > 0 && index < string.length()) {
+ char c = string.charAt(index++);
+ availableColumnSpace -= TerminalTextUtils.isCharCJK(c) ? 2 : 1;
+ if(availableColumnSpace < 0) {
+ bob.append(' ');
+ }
+ else {
+ bob.append(c);
+ }
+ }
+ return bob.toString();
+ }
- /**
- * This method will calculate word wrappings given a number of lines of text
- * and how wide the text can be printed. The result is a list of new rows
- * where word-wrapping was applied.
- *
- * @param maxWidth
- * Maximum number of columns that can be used before
- * word-wrapping is applied
- * @param lines
- * Input text
- * @return The input text word-wrapped at {@code maxWidth}; this may contain
- * more rows than the input text
- */
- public static List<String> getWordWrappedText(int maxWidth, String... lines) {
- List<String> result = new ArrayList<String>();
- LinkedList<String> linesToBeWrapped = new LinkedList<String>(Arrays
- .asList(lines));
- while (!linesToBeWrapped.isEmpty()) {
- String row = linesToBeWrapped.removeFirst();
- int rowWidth = getColumnWidth(row);
- if (rowWidth <= maxWidth) {
- result.add(row);
- } else {
- // Now search in reverse and find the first possible line-break
- int characterIndex = getStringCharacterIndex(row, maxWidth);
- while (!Character.isSpaceChar(row.charAt(characterIndex))
- && !isCharCJK(row.charAt(characterIndex))
- && characterIndex > 0) {
- characterIndex--;
- }
+ /**
+ * This method will calculate word wrappings given a number of lines of text and how wide the text can be printed.
+ * The result is a list of new rows where word-wrapping was applied.
+ * @param maxWidth Maximum number of columns that can be used before word-wrapping is applied, if <= 0 then the
+ * lines will be returned unchanged
+ * @param lines Input text
+ * @return The input text word-wrapped at {@code maxWidth}; this may contain more rows than the input text
+ */
+ public static List<String> getWordWrappedText(int maxWidth, String... lines) {
+ //Bounds checking
+ if(maxWidth <= 0) {
+ return Arrays.asList(lines);
+ }
- if (characterIndex == 0) {
- // Failed! There was no 'nice' place to cut so just cut it
- // at maxWidth
- result.add(row.substring(0, maxWidth));
- linesToBeWrapped.addFirst(row.substring(maxWidth));
- } else {
- // Ok, split the row, add it to the result and continue
- // processing the second half on a new line
- result.add(row.substring(0, characterIndex));
- int spaceCharsToSkip = 0;
- while (characterIndex < row.length()
- && Character
- .isSpaceChar(row.charAt(characterIndex))) {
- characterIndex++;
- }
- ;
- linesToBeWrapped.addFirst(row.substring(characterIndex));
- }
- }
- }
- return result;
- }
+ List<String> result = new ArrayList<String>();
+ LinkedList<String> linesToBeWrapped = new LinkedList<String>(Arrays.asList(lines));
+ while(!linesToBeWrapped.isEmpty()) {
+ String row = linesToBeWrapped.removeFirst();
+ int rowWidth = getColumnWidth(row);
+ if(rowWidth <= maxWidth) {
+ result.add(row);
+ }
+ else {
+ //Now search in reverse and find the first possible line-break
+ final int characterIndexMax = getStringCharacterIndex(row, maxWidth);
+ int characterIndex = characterIndexMax;
+ while(characterIndex >= 0 &&
+ !Character.isSpaceChar(row.charAt(characterIndex)) &&
+ !isCharCJK(row.charAt(characterIndex))) {
+ characterIndex--;
+ }
+ // right *after* a CJK is also a "nice" spot to break the line!
+ if (characterIndex >= 0 && characterIndex < characterIndexMax &&
+ isCharCJK(row.charAt(characterIndex))) {
+ characterIndex++; // with these conditions it fits!
+ }
+
+ if(characterIndex < 0) {
+ //Failed! There was no 'nice' place to cut so just cut it at maxWidth
+ characterIndex = Math.max(characterIndexMax, 1); // at least 1 char
+ result.add(row.substring(0, characterIndex));
+ linesToBeWrapped.addFirst(row.substring(characterIndex));
+ }
+ else {
+ // characterIndex == 0 only happens, if either
+ // - first char is CJK and maxWidth==1 or
+ // - first char is whitespace
+ // either way: put it in row before break to prevent infinite loop.
+ characterIndex = Math.max( characterIndex, 1); // at least 1 char
+
+ //Ok, split the row, add it to the result and continue processing the second half on a new line
+ result.add(row.substring(0, characterIndex));
+ while(characterIndex < row.length() &&
+ Character.isSpaceChar(row.charAt(characterIndex))) {
+ characterIndex++;
+ };
+ if (characterIndex < row.length()) { // only if rest contains non-whitespace
+ linesToBeWrapped.addFirst(row.substring(characterIndex));
+ }
+ }
+ }
+ }
+ return result;
+ }
}
@Override
public void invokeLater(Runnable runnable) throws IllegalStateException {
- if(Thread.currentThread() == getThread()) {
- runnable.run();
- }
- else {
- customTasks.add(runnable);
- }
+ customTasks.add(runnable);
}
@Override
@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();
+ }
}
}
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);
*/
public interface TextGUIThread {
/**
- * Invokes custom code on the GUI thread. If the caller is already on the GUI thread, the code is executed immediately
- * @param runnable Code to run
+ * Invokes custom code on the GUI thread. Even if the current thread <b>is</b> the GUI thread, the code will be
+ * executed at a later time when the event processing is done.
+ *
+ * @param runnable Code to run asynchronously
* @throws java.lang.IllegalStateException If the GUI thread is not running
*/
void invokeLater(Runnable runnable) throws IllegalStateException;
/**
* 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
*/
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!
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!
// 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);
//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);
(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,
if(TerminalTextUtils.isCharCJK(character.getCharacter())) {
visualState[rowIndex][columnIndex+1] = characterState;
}
- //}
+ }
if(character.getModifiers().contains(SGR.BLINK)) {
foundBlinkingCharacters = true;
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
*/
int getScrollingOffset();
+ /**
+ * Implementation of {@link TerminalScrollController} that does nothing
+ */
final class Null implements TerminalScrollController {
@Override
public void updateModel(int totalSize, int screenSize) {
--- /dev/null
+# 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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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