import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
+import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
name = file.getName();
}
- BufferedReader buffer = new BufferedReader(new FileReader(file));
+ BufferedReader buffer = new BufferedReader(new InputStreamReader(
+ new FileInputStream(file), "UTF-8"));
List<String> lines = new LinkedList<String>();
for (String line = buffer.readLine(); line != null; line = buffer
.readLine()) {
import be.nikiroo.jvcard.parsers.Format;
import be.nikiroo.jvcard.parsers.Parser;
+import be.nikiroo.jvcard.tui.StringUtils;
/**
* A contact is the information that represent a contact person or organisation.
* @return the {@link String} representation
*/
public String toString(String format) {
- return toString(format, "|", null, -1);
+ return toString(format, "|", null, -1, true, false);
}
/**
* @param width
* a fixed width or -1 for "as long as needed"
*
+ * @param unicode
+ * allow Uniode or only ASCII characters
+ *
* @return the {@link String} representation
*/
public String toString(String format, String separator, String padding,
- int width) {
+ int width, boolean unicode, boolean removeAccents) {
StringBuilder builder = new StringBuilder();
- for (String str : toStringArray(format, separator, padding, width)) {
+ for (String str : toStringArray(format, separator, padding, width,
+ unicode)) {
builder.append(str);
}
* @param width
* a fixed width or -1 for "as long as needed"
*
+ * @param unicode
+ * allow Uniode or only ASCII characters
+ *
* @return the {@link String} representation
*/
public String[] toStringArray(String format, String separator,
- String padding, int width) {
+ String padding, int width, boolean unicode) {
if (width > -1) {
int numOfFields = format.split("\\|").length;
if (separator != null)
List<String> str = new LinkedList<String>();
boolean first = true;
- for (String s : toStringArray(format, width)) {
+ for (String s : toStringArray(format, width, unicode)) {
if (!first) {
str.add(separator);
}
* the format to use
* @param width
* a fixed width or -1 for "as long as needed"
- *
+ * @param unicode
+ * allow Uniode or only ASCII characters
+ *
* @return the {@link String} representation
*/
- public String[] toStringArray(String format, int width) {
+ public String[] toStringArray(String format, int width, boolean unicode) {
List<String> str = new LinkedList<String>();
String[] formatFields = format.split("\\|");
}
String value = getPreferredDataValue(field);
- if (value == null)
+ if (value == null) {
value = "";
+ } else {
+ value = StringUtils.sanitize(value, unicode);
+ }
if (size > -1) {
- value = fixedString(value, size);
+ value = StringUtils.padString(value, size);
}
expandedFields[i] = expand;
for (int i = 0; i < values.length; i++) {
if (expandedFields[i]) {
if (remainder > 0) {
- values[i] = values[i] + fixedString("", remainder);
+ values[i] = values[i]
+ + StringUtils.padString("", remainder);
remainder = 0;
}
if (padPerItem > 0) {
- values[i] = values[i] + fixedString("", padPerItem);
+ values[i] = values[i]
+ + StringUtils.padString("", padPerItem);
}
}
}
return str.toArray(new String[] {});
}
- /**
- * Fix the size of the given {@link String} either with space-padding or by
- * shortening it.
- *
- * @param string
- * the {@link String} to fix
- * @param size
- * the size of the resulting {@link String}
- *
- * @return the fixed {@link String} of size <i>size</i>
- */
- static private String fixedString(String string, int size) {
- int length = string.length();
-
- if (length > size) {
- string = string.substring(0, size);
- } else if (length < size) {
- string = string
- + new String(new char[size - length]).replace('\0', ' ');
- }
-
- return string;
- }
-
/**
* Add a {@link String} to the given {@link List}, but make sure it does not
* exceed the maximum size, and truncate it if needed to fit.
data.setParent(this);
}
}
-
+
/**
* Delete this {@link Contact} from its parent {@link Card} if any.
*
*/
void setPristine() {
dirty = false;
- for (Data data: datas) {
+ for (Data data : datas) {
data.setPristine();
}
}
import java.util.HashMap;
import java.util.Map;
+import com.googlecode.lanterna.input.KeyStroke;
+
+import be.nikiroo.jvcard.tui.UiColors;
+
/**
* This class manages the translation of {@link Trans#StringId}s into
* user-understandable text.
KEY_ACTION_BACK, KEY_ACTION_HELP, // MainWindow
KEY_ACTION_VIEW_CARD, // FileList
KEY_ACTION_VIEW_CONTACT, KEY_ACTION_EDIT_CONTACT, KEY_ACTION_SAVE_CARD, KEY_ACTION_DELETE_CONTACT, KEY_ACTION_SWITCH_FORMAT, // ContactList
+ DEAULT_FIELD_SEPARATOR, DEAULT_FIELD_SEPARATOR_NOUTF, // MainContentList
NULL; // Special usage
public String trans() {
return instance;
}
+ /**
+ * Translate the given {@link StringId} into user text.
+ *
+ * @param stringId
+ * the ID to translate
+ *
+ * @return the translated text
+ */
public String trans(StringId stringId) {
- if (map.containsKey(stringId)) {
- return map.get(stringId);
+ StringId id = stringId;
+ if (!UiColors.getInstance().isUnicode()) {
+ try {
+ id = StringId.valueOf(stringId.toString() + "_NOUTF");
+ } catch (IllegalArgumentException iae) {
+ // no special _NOUTF version found
+ }
+ }
+
+ if (map.containsKey(id)) {
+ return map.get(id);
+ }
+
+ return id.toString();
+ }
+
+ /**
+ * Translate the given {@link KeyStroke} into a user text {@link String} of
+ * size 3.
+ *
+ * @param key
+ * the key to translate
+ *
+ * @return the translated text
+ */
+ public String trans(KeyStroke key) {
+ String keyTrans = "";
+
+ switch (key.getKeyType()) {
+ case Enter:
+ if (UiColors.getInstance().isUnicode())
+ keyTrans = " ⤶ ";
+ else
+ keyTrans = "ENT";
+ break;
+ case Tab:
+ if (UiColors.getInstance().isUnicode())
+ keyTrans = " ↹ ";
+ else
+ keyTrans = "TAB";
+
+ break;
+ case Character:
+ keyTrans = " " + key.getCharacter() + " ";
+ break;
+ default:
+ keyTrans = "" + key.getKeyType();
+ int width = 3;
+ if (keyTrans.length() > width) {
+ keyTrans = keyTrans.substring(0, width);
+ } else if (keyTrans.length() < width) {
+ keyTrans = keyTrans
+ + new String(new char[width - keyTrans.length()])
+ .replace('\0', ' ');
+ }
+ break;
}
- return stringId.toString();
+ return keyTrans;
}
private Trans() {
// TODO: get from a file instead?
map.put(StringId.NULL, "");
map.put(StringId.DUMMY, "[dummy]");
+ // we could use: " ", "┃", "│"...
+ map.put(StringId.DEAULT_FIELD_SEPARATOR, "┃");
+ map.put(StringId.DEAULT_FIELD_SEPARATOR_NOUTF, "|");
map.put(StringId.KEY_ACTION_BACK, "Back");
map.put(StringId.KEY_ACTION_HELP, "Help");
map.put(StringId.KEY_ACTION_VIEW_CONTACT, "Open");
return null;
}
- // override this one if needed
+ // override this one if needed, DO NOT process here as it will be call a lot
public Object getObject() {
return null;
}
/**
* The method which is called when the action is performed. You can subclass
- * it if you want to customize the action (by default, it just accepts the
+ * it if you want to customise the action (by default, it just accepts the
* mode change (see {@link KeyAction#getMode}).
*
* @return false to cancel mode change
+ "\t--help: this here thingy\n"
+ "\t--tui: force pure text mode even if swing treminal is available\n"
+ "\t--gui: force swing terminal mode\n"
+ + "\t--noutf: force non-utf8 mode if you need it\n"
+ + "\t--noutfa: force non-utf8 and no accents mode if you need it\n"
+ "everyhing else is either a file to open or a directory to open\n"
+ "(we will only open 1st level files in given directories)");
return;
textMode = true;
} else if (!noMoreParams && arg.equals("--gui")) {
textMode = false;
+ } else if (!noMoreParams && arg.equals("--noutf")) {
+ UiColors.getInstance().setUnicode(false);
} else {
filesTried = true;
files.addAll(open(arg));
files.addAll(open("."));
}
+ Window win = new MainWindow(new FileList(files));
+
try {
- TuiLauncher.start(textMode, new MainWindow(new FileList(files)));
+ TuiLauncher.start(textMode, win);
} catch (IOException ioe) {
ioe.printStackTrace();
System.exit(2);
import be.nikiroo.jvcard.Card;
import be.nikiroo.jvcard.Contact;
import be.nikiroo.jvcard.Data;
+import be.nikiroo.jvcard.i18n.Trans;
import be.nikiroo.jvcard.i18n.Trans.StringId;
import be.nikiroo.jvcard.tui.KeyAction.Mode;
import be.nikiroo.jvcard.tui.UiColors.Element;
-import be.nikiroo.jvcard.tui.panes.ContactDetails;
+import be.nikiroo.jvcard.tui.panes.ContactDetailsRaw;
import be.nikiroo.jvcard.tui.panes.ContactList;
import be.nikiroo.jvcard.tui.panes.MainContent;
import com.googlecode.lanterna.gui2.LinearLayout;
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.TextBox;
-import com.googlecode.lanterna.gui2.TextGUIGraphics;
import com.googlecode.lanterna.gui2.Window;
import com.googlecode.lanterna.input.KeyStroke;
import com.googlecode.lanterna.input.KeyType;
private List<KeyAction> actions = new LinkedList<KeyAction>();
private List<MainContent> contentStack = new LinkedList<MainContent>();
private boolean waitForOneKeyAnswer;
- private KeyStroke questionKey; // key that "asked" a question, and to replay
- // later with an answer
+ private KeyAction questionAction;
private String titleCache;
private Panel titlePanel;
private Panel mainPanel;
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));
* {@link MainWindow#handleQuestion}. The user will be asked to enter some
* answer and confirm with ENTER.
*
+ * @param action
+ * the related action
* @param question
* the question to ask
* @param initial
* the initial answer if any (to be edited by the user)
*/
- public void setQuestion(KeyStroke key, String question, String initial) {
- setQuestion(key, question, initial, false);
+ public void setQuestion(KeyAction action, String question, String initial) {
+ setQuestion(action, question, initial, false);
}
/**
* {@link MainWindow#handleQuestion}. The user will be asked to hit one key
* as an answer.
*
+ * @param action
+ * the related action
* @param question
* the question to ask
*/
- public void setQuestion(KeyStroke key, String question) {
- setQuestion(key, question, null, true);
- }
-
- @Override
- public void draw(TextGUIGraphics graphics) {
- int width = graphics.getSize().getColumns();
-
- if (width != this.width) {
- this.width = width;
-
- setTitle();
-
- if (actions != null)
- setActions(new ArrayList<KeyAction>(actions), false);
- }
-
- super.draw(graphics);
- }
-
- @Override
- public void invalidate() {
- super.invalidate();
- for (MainContent content : contentStack) {
- content.invalidate();
- }
+ public void setQuestion(KeyAction action, String question) {
+ setQuestion(action, question, null, true);
}
/**
* Show a question to the user and switch to "ask for answer" mode see
* {@link MainWindow#handleQuestion}.
*
+ * @param action
+ * the related action
* @param question
* the question to ask
* @param initial
* TRUE for a one-key answer, FALSE for a text answer validated
* by ENTER
*/
- private void setQuestion(KeyStroke key, String question, String initial,
+ private void setQuestion(KeyAction action, String question, String initial,
boolean oneKey) {
- questionKey = key;
+ questionAction = action;
waitForOneKeyAnswer = oneKey;
messagePanel.removeAllComponents();
Label lbl = UiColors.Element.LINE_MESSAGE_QUESTION.createLabel(" "
+ question + " ");
- text = new TextBox(new TerminalSize(width - lbl.getSize().getColumns(),
- 1));
+ text = new TextBox(new TerminalSize(getSize().getColumns()
+ - lbl.getSize().getColumns(), 1));
if (initial != null)
text.setText(initial);
text.takeFocus();
}
+ /**
+ * Refresh the window and the empty-space handling. You should call this
+ * method when the window size changed.
+ *
+ * @param size
+ * the new size of the window
+ */
+ public void refresh(TerminalSize size) {
+ if (size == null)
+ return;
+
+ if (getSize() == null || !getSize().equals(size))
+ setSize(size);
+
+ setTitle();
+
+ if (actions != null)
+ setActions(new ArrayList<KeyAction>(actions), false);
+
+ invalidate();
+ }
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+ for (MainContent content : contentStack) {
+ content.invalidate();
+ }
+ }
+
+ @Override
+ public boolean handleInput(KeyStroke key) {
+ boolean handled = false;
+
+ if (questionAction != null) {
+ String answer = handleQuestion(key);
+ if (answer != null) {
+ handled = true;
+
+ handleAction(questionAction, answer);
+ questionAction = null;
+ }
+ } else {
+ handled = handleKey(key);
+ }
+
+ if (!handled)
+ handled = super.handleInput(key);
+
+ return handled;
+ }
+
/**
* Actually set the title <b>inside</b> the window. Will also call
- * {@link BasicWindow#setTitle} with the compuited parameters.
+ * {@link BasicWindow#setTitle} with the computed parameters.
*/
private void setTitle() {
String prefix = " " + Main.APPLICATION_TITLE + " (version "
if (title.length() > 0) {
prefix = prefix + ": ";
+ title = StringUtils.sanitize(title, UiColors.getInstance()
+ .isUnicode());
}
String countStr = "";
countStr = "[" + count + "]";
}
+ int width = -1;
+ if (getSize() != null) {
+ width = getSize().getColumns();
+ }
+
if (width > 0) {
int padding = width - prefix.length() - title.length()
- countStr.length();
if (" ".equals(trans))
continue;
- String keyTrans = "";
- switch (action.getKey().getKeyType()) {
- case Enter:
- keyTrans = " ⤶ ";
- break;
- case Tab:
- keyTrans = " ↹ ";
- break;
- case Character:
- keyTrans = " " + action.getKey().getCharacter() + " ";
- break;
- default:
- keyTrans = "" + action.getKey().getKeyType();
- int width = 3;
- if (keyTrans.length() > width) {
- keyTrans = keyTrans.substring(0, width);
- } else if (keyTrans.length() < width) {
- keyTrans = keyTrans
- + new String(new char[width - keyTrans.length()])
- .replace('\0', ' ');
- }
- break;
- }
+ String keyTrans = Trans.getInstance().trans(action.getKey());
Panel kPane = new Panel();
LinearLayout layout = new LinearLayout(Direction.HORIZONTAL);
}
// fill with "desc" colour
+ int width = -1;
+ if (getSize() != null) {
+ width = getSize().getColumns();
+ }
+
if (width > 0) {
actionPanel.addComponent(UiColors.Element.ACTION_DESC
.createLabel(StringUtils.padString("", width)));
-
}
}
*
* @return if the window handled the input
*/
- private boolean handleInput(KeyStroke key, String answer) {
+ private boolean handleKey(KeyStroke key) {
boolean handled = false;
- // reset the message pane if no answers are pending
- if (answer == null) {
- if (setMessage(null, false))
- return true;
- }
+ if (setMessage(null, false))
+ return true;
for (KeyAction action : actions) {
if (!action.match(key))
continue;
- MainContent content = getContent();
handled = true;
if (action.onAction()) {
- Card card = action.getCard();
- Contact contact = action.getContact();
- Data data = action.getData();
-
- switch (action.getMode()) {
- case MOVE:
- int x = 0;
- int y = 0;
-
- if (action.getKey().getKeyType() == KeyType.ArrowUp)
- x = -1;
- if (action.getKey().getKeyType() == KeyType.ArrowDown)
- x = 1;
- if (action.getKey().getKeyType() == KeyType.ArrowLeft)
- y = -1;
- if (action.getKey().getKeyType() == KeyType.ArrowRight)
- y = 1;
-
- if (content != null) {
- String err = content.move(x, y);
- if (err != null)
- setMessage(err, true);
- }
+ handleAction(action, null);
+ }
- break;
- // mode with windows:
- case CONTACT_LIST:
- if (card != null) {
- pushContent(new ContactList(card));
- }
- break;
- case CONTACT_DETAILS:
- if (contact != null) {
- pushContent(new ContactDetails(contact));
- }
- break;
- // mode interpreted by MainWindow:
- case HELP:
- // TODO
- // setMessage("Help! I need somebody! Help!", false);
- if (answer == null) {
- setQuestion(key, "Test question?", "[initial]");
- } else {
- setMessage("You answered: " + answer, false);
- }
+ break;
+ }
- break;
- case BACK:
- String warning = content.getExitWarning();
- if (warning != null) {
- if (answer == null) {
- setQuestion(key, warning);
- } else {
- setMessage(null, false);
- if (answer.equalsIgnoreCase("y")) {
- popContent();
- }
- }
- } else {
+ return handled;
+ }
+
+ /**
+ * Handle the input in case of "normal" (not "ask for answer") mode.
+ *
+ * @param key
+ * the key that was pressed
+ * @param answer
+ * the answer given for this key
+ *
+ * @return if the window handled the input
+ */
+ private void handleAction(KeyAction action, String answer) {
+ MainContent content = getContent();
+
+ Card card = action.getCard();
+ Contact contact = action.getContact();
+ Data data = action.getData();
+
+ switch (action.getMode()) {
+ case MOVE:
+ int x = 0;
+ int y = 0;
+
+ if (action.getKey().getKeyType() == KeyType.ArrowUp)
+ x = -1;
+ if (action.getKey().getKeyType() == KeyType.ArrowDown)
+ x = 1;
+ if (action.getKey().getKeyType() == KeyType.ArrowLeft)
+ y = -1;
+ if (action.getKey().getKeyType() == KeyType.ArrowRight)
+ y = 1;
+
+ if (content != null) {
+ String err = content.move(x, y);
+ if (err != null)
+ setMessage(err, true);
+ }
+
+ break;
+ // mode with windows:
+ case CONTACT_LIST:
+ if (card != null) {
+ pushContent(new ContactList(card));
+ }
+ break;
+ case CONTACT_DETAILS:
+ if (contact != null) {
+ pushContent(new ContactDetailsRaw(contact));
+ }
+ break;
+ // mode interpreted by MainWindow:
+ case HELP:
+ // TODO
+ // setMessage("Help! I need somebody! Help!", false);
+ if (answer == null) {
+ setQuestion(action, "Test question?", "[initial]");
+ } else {
+ setMessage("You answered: " + answer, false);
+ }
+
+ break;
+ case BACK:
+ String warning = content.getExitWarning();
+ if (warning != null) {
+ if (answer == null) {
+ setQuestion(action, warning);
+ } else {
+ setMessage(null, false);
+ if (answer.equalsIgnoreCase("y")) {
popContent();
}
+ }
+ } else {
+ popContent();
+ }
- if (contentStack.size() == 0) {
- close();
- }
+ if (contentStack.size() == 0) {
+ close();
+ }
- break;
- // action modes:
- case EDIT_DETAIL:
- if (answer == null) {
- if (data != null) {
- String name = data.getName();
- String value = data.getValue();
- setQuestion(key, name, value);
- }
+ break;
+ // action modes:
+ case EDIT_DETAIL:
+ if (answer == null) {
+ if (data != null) {
+ String name = data.getName();
+ String value = data.getValue();
+ setQuestion(action, name, value);
+ }
+ } else {
+ setMessage(null, false);
+ data.setValue(answer);
+ }
+ break;
+ case DELETE_CONTACT:
+ if (answer == null) {
+ if (contact != null) {
+ setQuestion(action, "Delete contact? [Y/N]");
+ }
+ } else {
+ setMessage(null, false);
+ if (answer.equalsIgnoreCase("y")) {
+ if (contact.delete()) {
+ content.refreshData();
+ invalidate();
+ setTitle();
} else {
- setMessage(null, false);
- data.setValue(answer);
+ setMessage("Cannot delete this contact", true);
}
- break;
- case DELETE_CONTACT:
- if (answer == null) {
- if (contact != null) {
- setQuestion(key, "Delete contact? [Y/N]");
- }
- } else {
- setMessage(null, false);
- if (answer.equalsIgnoreCase("y")) {
- if (contact.delete()) {
- content.refreshData();
- invalidate();
- setTitle();
- } else {
- setMessage("Cannot delete this contact", true);
- }
+ }
+ }
+ break;
+ case SAVE_CARD:
+ if (answer == null) {
+ if (card != null) {
+ setQuestion(action, "Save changes? [Y/N]");
+ }
+ } else {
+ setMessage(null, false);
+ if (answer.equalsIgnoreCase("y")) {
+ boolean ok = false;
+ try {
+ if (card.save()) {
+ ok = true;
+ invalidate();
}
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
}
- break;
- case SAVE_CARD:
- if (answer == null) {
- if (card != null) {
- setQuestion(key, "Save changes? [Y/N]");
- }
- } else {
- setMessage(null, false);
- if (answer.equalsIgnoreCase("y")) {
- boolean ok = false;
- try {
- if (card.save()) {
- ok = true;
- invalidate();
- }
- } catch (IOException ioe) {
- ioe.printStackTrace();
- }
-
- if (!ok) {
- setMessage("Cannot save to file", true);
- }
- }
+
+ if (!ok) {
+ setMessage("Cannot save to file", true);
}
- break;
- default:
- case NONE:
- break;
}
}
-
+ break;
+ default:
+ case NONE:
break;
}
-
- return handled;
- }
-
- @Override
- public boolean handleInput(KeyStroke key) {
- boolean handled = false;
-
- if (questionKey != null) {
- String answer = handleQuestion(key);
- if (answer != null) {
- handled = true;
-
- key = questionKey;
- questionKey = null;
-
- handleInput(key, answer);
- }
- } else {
- handled = handleInput(key, null);
- }
-
- if (!handled)
- handled = super.handleInput(key);
-
- return handled;
}
}
package be.nikiroo.jvcard.tui;
+import java.text.Normalizer;
+import java.text.Normalizer.Form;
+import java.util.regex.Pattern;
+
import com.googlecode.lanterna.gui2.LinearLayout.Alignment;
public class StringUtils {
+ static private Pattern marks = Pattern
+ .compile("[\\p{InCombiningDiacriticalMarks}\\p{IsLm}\\p{IsSk}]+");
+ static private Pattern notAscii = Pattern.compile("[^\\p{ASCII}]+");
+ /**
+ * Fix the size of the given {@link String} either with space-padding or by
+ * shortening it.
+ *
+ * @param text
+ * the {@link String} to fix
+ * @param width
+ * the size of the resulting {@link String} if the text fits or
+ * if cut is TRUE
+ *
+ * @return the resulting {@link String} of size <i>size</i>
+ */
static public String padString(String text, int width) {
return padString(text, width, true, Alignment.Beginning);
}
- // TODO: doc it, width of -1 == no change to text
+ /**
+ * Fix the size of the given {@link String} either with space-padding or by
+ * optionally shortening it.
+ *
+ * @param text
+ * the {@link String} to fix
+ * @param width
+ * the size of the resulting {@link String} if the text fits or
+ * if cut is TRUE
+ * @param cut
+ * cut the {@link String} shorter if needed
+ * @param align
+ * align the {@link String} in this position if we have enough
+ * space
+ *
+ * @return the resulting {@link String} of size <i>size</i> minimum
+ */
static public String padString(String text, int width, boolean cut,
Alignment align) {
return text;
}
+ /**
+ * Sanitise the given input to make it more Terminal-friendly by removing
+ * combining characters.
+ *
+ * @param input
+ * the input to sanitise
+ * @param allowUnicode
+ * allow Unicode or only allow ASCII Latin characters
+ *
+ * @return the sanitised {@link String}
+ */
+ static public String sanitize(String input, boolean allowUnicode) {
+ return sanitize(input, allowUnicode, !allowUnicode);
+ }
+
+ /**
+ * Sanitise the given input to make it more Terminal-friendly by removing
+ * combining characters.
+ *
+ * @param input
+ * the input to sanitise
+ * @param allowUnicode
+ * allow Unicode or only allow ASCII Latin characters
+ * @param removeAllAccents
+ * TRUE to replace all accentuated characters by their non
+ * accentuated counter-parts
+ *
+ * @return the sanitised {@link String}
+ */
+ static public String sanitize(String input, boolean allowUnicode,
+ boolean removeAllAccents) {
+
+ if (removeAllAccents) {
+ input = Normalizer.normalize(input, Form.NFKD);
+ input = marks.matcher(input).replaceAll("");
+ }
+
+ input = Normalizer.normalize(input, Form.NFKC);
+
+ if (!allowUnicode) {
+ input = notAscii.matcher(input).replaceAll("");
+ }
+
+ return input;
+ }
}
import java.io.IOException;
+import com.googlecode.lanterna.TerminalSize;
import com.googlecode.lanterna.TextColor;
import com.googlecode.lanterna.gui2.DefaultWindowManager;
import com.googlecode.lanterna.gui2.EmptySpace;
import com.googlecode.lanterna.screen.Screen;
import com.googlecode.lanterna.screen.TerminalScreen;
import com.googlecode.lanterna.terminal.DefaultTerminalFactory;
+import com.googlecode.lanterna.terminal.ResizeListener;
import com.googlecode.lanterna.terminal.Terminal;
/*
*
- * Change in Lanterna3 (issue and fix reported to GitHub):
+ * Change in Lanterna 3.0.0-beta2 (issue and fix reported to GitHub):
*
* java.lang.StringIndexOutOfBoundsException: String index out of range: 83
* at java.lang.String.charAt(String.java:686)
*/
public class TuiLauncher {
- public static void start(Boolean textMode, Window win)
- throws IOException {
+ public static void start(Boolean textMode, Window win) throws IOException {
Terminal terminal = null;
DefaultTerminalFactory factory = new DefaultTerminalFactory();
terminal = factory.createTerminalEmulator();
}
+ if (win instanceof MainWindow) {
+ MainWindow mwin = (MainWindow) win;
+ mwin.refresh(terminal.getTerminalSize());
+ terminal.addResizeListener(new ResizeListener() {
+ @Override
+ public void onResized(Terminal terminal, TerminalSize newSize) {
+ mwin.refresh(newSize);
+ }
+ });
+ }
+
Screen screen = new TerminalScreen(terminal);
screen.startScreen();
private Map<Element, TextColor> mapForegroundColor = null;
private Map<Element, TextColor> mapBackgroundColor = null;
+ private boolean utf = true;
/**
* Get the (unique) instance of this class.
}
public enum Element {
- DEFAULT, //
+ DEFAULT, //
TITLE_MAIN, TITLE_VARIABLE, TITLE_COUNT, //
ACTION_KEY, ACTION_DESC, //
- LINE_MESSAGE, LINE_MESSAGE_ERR, LINE_MESSAGE_QUESTION, LINE_MESSAGE_ANS, //
+ LINE_MESSAGE, LINE_MESSAGE_ERR, LINE_MESSAGE_QUESTION, LINE_MESSAGE_ANS, //
CONTACT_LINE, CONTACT_LINE_SEPARATOR, CONTACT_LINE_SELECTED, CONTACT_LINE_SEPARATOR_SELECTED, CONTACT_LINE_DIRTY, CONTACT_LINE_DIRTY_SELECTED;
/**
}
}
+ /**
+ * Check if unicode characters should be used.
+ *
+ * @return TRUE to allow unicode
+ */
+ public boolean isUnicode() {
+ return utf;
+ }
+
+ /**
+ * Allow or disallow unicode characters in the program.
+ *
+ * @param utf
+ * TRUE to allow unuciode, FALSE to only allow ASCII characters
+ */
+ public void setUnicode(boolean utf) {
+ this.utf = utf;
+ }
+
private Label createLabel(Element el, String text) {
Label lbl = new Label(text);
themeLabel(el, lbl);
addEl(Element.LINE_MESSAGE_ANS, TextColor.ANSI.BLUE,
TextColor.ANSI.BLACK);
addEl(Element.TITLE_MAIN, TextColor.ANSI.WHITE, TextColor.ANSI.BLUE);
- addEl(Element.TITLE_VARIABLE, TextColor.ANSI.GREEN,
- TextColor.ANSI.BLUE);
+ addEl(Element.TITLE_VARIABLE, TextColor.ANSI.GREEN, TextColor.ANSI.BLUE);
addEl(Element.TITLE_COUNT, TextColor.ANSI.RED, TextColor.ANSI.BLUE);
}
mapForegroundColor.put(el, fore);
mapBackgroundColor.put(el, back);
}
-
}
import be.nikiroo.jvcard.TypeInfo;
import be.nikiroo.jvcard.i18n.Trans;
import be.nikiroo.jvcard.tui.KeyAction;
+import be.nikiroo.jvcard.tui.UiColors;
import be.nikiroo.jvcard.tui.KeyAction.DataType;
import be.nikiroo.jvcard.tui.KeyAction.Mode;
import be.nikiroo.jvcard.tui.StringUtils;
import be.nikiroo.jvcard.tui.UiColors.Element;
-public class ContactDetails extends MainContentList {
+public class ContactDetailsRaw extends MainContentList {
private Contact contact;
private int mode;
- public ContactDetails(Contact contact) {
+ public ContactDetailsRaw(Contact contact) {
super(null, null);
this.contact = contact;
value = valueBuilder.toString();
+ name = StringUtils.sanitize(name, UiColors.getInstance().isUnicode());
+ value = StringUtils.sanitize(value, UiColors.getInstance().isUnicode());
+
name = StringUtils.padString(name, SIZE_COL_1);
value = StringUtils.padString(value, width - SIZE_COL_1
- getSeparator().length() - 2);
public List<KeyAction> getKeyBindings() {
List<KeyAction> actions = new LinkedList<KeyAction>();
- // TODO add, del, save...
+ // TODO add
actions.add(new KeyAction(Mode.CONTACT_DETAILS, 'e',
Trans.StringId.KEY_ACTION_EDIT_CONTACT) {
@Override
width -= 2; // dirty mark space
String[] array = contact.toStringArray(format, getSeparator(), " ",
- width);
+ width, UiColors.getInstance().isUnicode());
if (contact.isDirty()) {
parts.add(new TextPart(" ", el));
String name = files.get(index).getName();
+ name = StringUtils.sanitize(name, UiColors.getInstance().isUnicode());
+
count = " " + StringUtils.padString(count, SIZE_COL_1) + " ";
name = " "
+ StringUtils.padString(name, width - SIZE_COL_1
import java.util.LinkedList;
import java.util.List;
+import be.nikiroo.jvcard.i18n.Trans.StringId;
+import be.nikiroo.jvcard.tui.StringUtils;
import be.nikiroo.jvcard.tui.UiColors;
import be.nikiroo.jvcard.tui.UiColors.Element;
import com.googlecode.lanterna.TextColor;
+import com.googlecode.lanterna.gui2.AbstractListBox.ListItemRenderer;
import com.googlecode.lanterna.gui2.ActionListBox;
import com.googlecode.lanterna.gui2.Direction;
import com.googlecode.lanterna.gui2.LinearLayout;
import com.googlecode.lanterna.gui2.TextGUIGraphics;
-import com.googlecode.lanterna.gui2.AbstractListBox.ListItemRenderer;
abstract public class MainContentList extends MainContent implements Runnable {
private ActionListBox lines;
lines = new ActionListBox();
- lines
- .setListItemRenderer(new ListItemRenderer<Runnable, ActionListBox>() {
- /**
- * This is the main drawing method for a single list box
- * item, it applies the current theme to setup the colors
- * and then calls {@code getLabel(..)} and draws the result
- * using the supplied {@code TextGUIGraphics}. The graphics
- * object is created just for this item and is restricted so
- * that it can only draw on the area this item is occupying.
- * The top-left corner (0x0) should be the starting point
- * when drawing the item.
- *
- * @param graphics
- * Graphics object to draw with
- * @param listBox
- * List box we are drawing an item from
- * @param index
- * Index of the item we are drawing
- * @param item
- * The item we are drawing
- * @param selected
- * Will be set to {@code true} if the item is
- * currently selected, otherwise {@code false},
- * but please notice what context 'selected'
- * refers to here (see {@code setSelectedIndex})
- * @param focused
- * Will be set to {@code true} if the list box
- * currently has input focus, otherwise {@code
- * false}
- */
- public void drawItem(TextGUIGraphics graphics,
- ActionListBox listBox, int index, Runnable item,
- boolean selected, boolean focused) {
-
- // width "-1" to reserve space for the optional vertical
- // scroll bar
- List<TextPart> parts = MainContentList.this.getLabel(
- index, lines.getSize().getColumns() - 1,
- selected, focused);
-
- int position = 0;
- for (TextPart part : parts) {
- graphics.setForegroundColor(part
- .getForegroundColor());
- graphics.setBackgroundColor(part
- .getBackgroundColor());
- String label = part.getText();
-
- graphics.putString(position, 0, label);
- position += label.length();
- }
- }
- });
-
- addComponent(lines, LinearLayout
- .createLayoutData(LinearLayout.Alignment.Fill));
+ lines.setListItemRenderer(new ListItemRenderer<Runnable, ActionListBox>() {
+ /**
+ * This is the main drawing method for a single list box item, it
+ * applies the current theme to setup the colors and then calls
+ * {@code getLabel(..)} and draws the result using the supplied
+ * {@code TextGUIGraphics}. The graphics object is created just for
+ * this item and is restricted so that it can only draw on the area
+ * this item is occupying. The top-left corner (0x0) should be the
+ * starting point when drawing the item.
+ *
+ * @param graphics
+ * Graphics object to draw with
+ * @param listBox
+ * List box we are drawing an item from
+ * @param index
+ * Index of the item we are drawing
+ * @param item
+ * The item we are drawing
+ * @param selected
+ * Will be set to {@code true} if the item is currently
+ * selected, otherwise {@code false}, but please notice
+ * what context 'selected' refers to here (see
+ * {@code setSelectedIndex})
+ * @param focused
+ * Will be set to {@code true} if the list box currently
+ * has input focus, otherwise {@code false}
+ */
+ public void drawItem(TextGUIGraphics graphics,
+ ActionListBox listBox, int index, Runnable item,
+ boolean selected, boolean focused) {
+
+ // width "-1" to reserve space for the optional vertical
+ // scroll bar
+ List<TextPart> parts = MainContentList.this.getLabel(index,
+ lines.getSize().getColumns() - 1, selected, focused);
+
+ int position = 0;
+ for (TextPart part : parts) {
+ graphics.setForegroundColor(part.getForegroundColor());
+ graphics.setBackgroundColor(part.getBackgroundColor());
+
+ String label = StringUtils.sanitize(part.getText(),
+ UiColors.getInstance().isUnicode());
+
+ graphics.putString(position, 0, label);
+ position += label.length();
+ }
+ }
+ });
+
+ addComponent(lines,
+ LinearLayout.createLayoutData(LinearLayout.Alignment.Fill));
}
/**
public void setSelectedIndex(int index) {
lines.setSelectedIndex(index);
}
-
-
+
/**
* Return the default content separator for text fields.
*
* @return the separator
*/
public String getSeparator() {
- // we could use: " ", "┃", "│"...
- return "┃";
+ return StringId.DEAULT_FIELD_SEPARATOR.trans();
}
@Override
getComponent().invalidate();
}
setSize(graphics.getSize(), false);
- super.draw(graphics);
+ super.draw(graphics);
}
@Override