From 7da41ecd30228908bf2afcd07ff7943ab59d4c01 Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Tue, 15 Mar 2016 21:14:00 +0100 Subject: [PATCH] New launcher class to start all 3 modes: - jvcard TUI - jvcard remote server - i18n files generation Also, the 2 packages be.nikiroo.jvcard.remote and be.nikiroo.jvcard.tui (and tui.panes) are now independantly optional --- only tui requires lanterna --- src/be/nikiroo/jvcard/BaseClass.java | 2 +- src/be/nikiroo/jvcard/Contact.java | 2 +- src/be/nikiroo/jvcard/launcher/Main.java | 496 ++++++++++++++++++ src/be/nikiroo/jvcard/remote/Server.java | 9 +- src/be/nikiroo/jvcard/remote/Sync.java | 3 +- .../nikiroo/jvcard/remote/package-info.java | 6 + src/be/nikiroo/jvcard/resources/Bundles.java | 5 +- .../jvcard/{i18n => resources}/Meta.java | 2 +- .../{tui => resources}/StringUtils.java | 2 +- .../jvcard/{i18n => resources}/Trans.java | 167 +++--- .../nikiroo/jvcard/tui/ImageTextControl.java | 5 +- src/be/nikiroo/jvcard/tui/KeyAction.java | 48 +- src/be/nikiroo/jvcard/tui/Main.java | 243 --------- src/be/nikiroo/jvcard/tui/MainWindow.java | 9 +- src/be/nikiroo/jvcard/tui/TuiLauncher.java | 32 ++ src/be/nikiroo/jvcard/tui/UiColors.java | 20 - .../jvcard/tui/panes/ContactDetails.java | 2 +- .../jvcard/tui/panes/ContactDetailsRaw.java | 10 +- .../nikiroo/jvcard/tui/panes/ContactList.java | 6 +- src/be/nikiroo/jvcard/tui/panes/FileList.java | 47 +- .../jvcard/tui/panes/MainContentList.java | 9 +- 21 files changed, 687 insertions(+), 438 deletions(-) create mode 100644 src/be/nikiroo/jvcard/launcher/Main.java create mode 100644 src/be/nikiroo/jvcard/remote/package-info.java rename src/be/nikiroo/jvcard/{i18n => resources}/Meta.java (96%) rename src/be/nikiroo/jvcard/{tui => resources}/StringUtils.java (99%) rename src/be/nikiroo/jvcard/{i18n => resources}/Trans.java (66%) delete mode 100644 src/be/nikiroo/jvcard/tui/Main.java diff --git a/src/be/nikiroo/jvcard/BaseClass.java b/src/be/nikiroo/jvcard/BaseClass.java index 2686390..ed9a693 100644 --- a/src/be/nikiroo/jvcard/BaseClass.java +++ b/src/be/nikiroo/jvcard/BaseClass.java @@ -9,7 +9,7 @@ import java.util.LinkedList; import java.util.List; import java.util.ListIterator; -import be.nikiroo.jvcard.tui.StringUtils; +import be.nikiroo.jvcard.resources.StringUtils; /** * This class is basically a List with a parent and a "dirty" state check. It diff --git a/src/be/nikiroo/jvcard/Contact.java b/src/be/nikiroo/jvcard/Contact.java index 87c1286..ce701a2 100644 --- a/src/be/nikiroo/jvcard/Contact.java +++ b/src/be/nikiroo/jvcard/Contact.java @@ -11,7 +11,7 @@ import java.util.UUID; import be.nikiroo.jvcard.parsers.Format; import be.nikiroo.jvcard.parsers.Parser; -import be.nikiroo.jvcard.tui.StringUtils; +import be.nikiroo.jvcard.resources.StringUtils; /** * A contact is the information that represent a contact person or organisation. diff --git a/src/be/nikiroo/jvcard/launcher/Main.java b/src/be/nikiroo/jvcard/launcher/Main.java new file mode 100644 index 0000000..b0e3bd8 --- /dev/null +++ b/src/be/nikiroo/jvcard/launcher/Main.java @@ -0,0 +1,496 @@ +package be.nikiroo.jvcard.launcher; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.Socket; +import java.nio.charset.Charset; +import java.util.LinkedList; +import java.util.List; + +import be.nikiroo.jvcard.Card; +import be.nikiroo.jvcard.parsers.Format; +import be.nikiroo.jvcard.remote.Command.Verb; +import be.nikiroo.jvcard.remote.SimpleSocket; +import be.nikiroo.jvcard.resources.Bundles; +import be.nikiroo.jvcard.resources.StringUtils; +import be.nikiroo.jvcard.resources.Trans; +import be.nikiroo.jvcard.resources.Trans.StringId; + +/** + * 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 { + static public final String APPLICATION_TITLE = "jVcard"; + static public final String APPLICATION_VERSION = "1.0-beta2-dev"; + + static private final int ERR_NO_FILE = 1; + static private final int ERR_SYNTAX = 2; + static private final int ERR_INTERNAL = 3; + static private Trans transService; + + /** + * Translate the given {@link StringId}. + * + * @param id + * the ID to translate + * + * @return the translation + */ + static public String trans(StringId id) { + return transService.trans(id); + } + + /** + * Check if unicode characters should be used. + * + * @return TRUE to allow unicode + */ + static public boolean isUnicode() { + return transService.isUnicode(); + } + + /** + * Start the application. + * + *

+ * The returned exit codes are: + *

+ *

+ * + * @param args + * the parameters (see --help to know which are + * supported) + */ + public static void main(String[] args) { + Boolean textMode = null; + boolean noMoreParams = false; + boolean filesTried = false; + + // get the "system default" language to help translate the --help + // message if needed + String language = null; + transService = new Trans(language); + + boolean unicode = transService.isUnicode(); + String i18nDir = null; + List files = new LinkedList(); + Integer port = null; + for (int index = 0; index < args.length; index++) { + String arg = args[index]; + 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--lang LANGUAGE: choose the language, for instance en_GB\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--config DIRECTORY: force the given directory as a CONFIG_DIR\n" + + "\t--server PORT: start a remoting server instead of a client\n" + + "\t--i18n DIR: generate the translation file for the given language (can be \"\") to/from the .properties given dir\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)\n" + + "('jvcard://hostname:8888/file' links -- or without 'file' -- are also ok)\n"); + return; + } else if (!noMoreParams && arg.equals("--tui")) { + textMode = true; + } else if (!noMoreParams && arg.equals("--gui")) { + textMode = false; + } else if (!noMoreParams && arg.equals("--noutf")) { + unicode = false; + transService.setUnicode(unicode); + } else if (!noMoreParams && arg.equals("--lang")) { + index++; + if (index >= args.length) { + System.err.println("Syntax error: no language given"); + System.exit(ERR_SYNTAX); + return; + } + + language = args[index]; + transService = new Trans(language); + transService.setUnicode(unicode); + } else if (!noMoreParams && arg.equals("--config")) { + index++; + if (index >= args.length) { + System.err + .println("Syntax error: no config directory given"); + System.exit(ERR_SYNTAX); + return; + } + + Bundles.setDirectory(args[index]); + transService = new Trans(language); + transService.setUnicode(unicode); + } else if (!noMoreParams && arg.equals("--server")) { + index++; + if (index >= args.length) { + System.err.println("Syntax error: no port given"); + System.exit(ERR_SYNTAX); + return; + } + + try { + port = Integer.parseInt(args[index]); + } catch (NumberFormatException e) { + System.err.println("Invalid port number: " + args[index]); + System.exit(ERR_SYNTAX); + return; + } + } else if (!noMoreParams && arg.equals("--i18n")) { + index++; + if (index >= args.length) { + System.err + .println("Syntax error: no .properties directory given"); + System.exit(ERR_SYNTAX); + return; + } + + i18nDir = args[index]; + } else { + filesTried = true; + files.addAll(open(arg)); + } + } + + if (unicode) { + utf8(); + } + + // Error management: + if (port != null && files.size() > 0) { + System.err + .println("Invalid syntax: you cannot both use --server and provide card files"); + System.exit(ERR_SYNTAX); + } else if (i18nDir != null && files.size() > 0) { + System.err + .println("Invalid syntax: you cannot both use --i18n and provide card files"); + System.exit(ERR_SYNTAX); + } else if (port != null && i18nDir != null) { + System.err + .println("Invalid syntax: you cannot both use --server and --i18n"); + System.exit(ERR_SYNTAX); + } else if (i18nDir != null && language == null) { + System.err + .println("Invalid syntax: you cannot use --i18n without --lang"); + System.exit(ERR_SYNTAX); + } else if (port == null && i18nDir == null && files.size() == 0) { + if (files.size() == 0 && !filesTried) { + files.addAll(open(".")); + } + + if (files.size() == 0) { + System.err.println("No files to open"); + System.exit(ERR_NO_FILE); + return; + } + } + // + + if (port != null) { + try { + runServer(port); + } catch (Exception e) { + if (e instanceof IOException) { + System.err + .println("I/O Exception: Cannot start the server"); + } else { + System.err.println("FATAL ERROR"); + e.printStackTrace(); + System.exit(ERR_INTERNAL); + } + } + } else if (i18nDir != null) { + try { + Trans.generateTranslationFile(i18nDir, language); + } catch (IOException e) { + System.err + .println("I/O Exception: Cannot create/update a language in directory: " + + i18nDir); + } + } else { + try { + startTui(textMode, files); + } catch (Exception e) { + if (e instanceof IOException) { + System.err + .println("I/O Exception: Cannot start the program with the given cards"); + } else { + System.err.println("FATAL ERROR"); + e.printStackTrace(); + System.exit(ERR_INTERNAL); + } + } + } + } + + /** + * Return the {@link Card} corresponding to the given resource name -- a + * file or a remote jvcard URL + * + * @param input + * a filename or a remote jvcard url with named resource (e.g.: + * jvcard://localhost:4444/coworkers.vcf) + * + * @return the {@link Card} + * + * @throws IOException + * in case of IO error or remoting not available + */ + static public Card getCard(String input) throws IOException { + boolean remote = false; + Format format = Format.Abook; + String ext = input; + if (ext.contains(".")) { + String tab[] = ext.split("\\."); + if (tab.length > 1 && tab[tab.length - 1].equalsIgnoreCase("vcf")) { + format = Format.VCard21; + } + } + + if (input.contains("://")) { + format = Format.VCard21; + remote = true; + } + + Card card = null; + try { + if (remote) { + card = syncCard(input); + } else { + card = new Card(new File(input), format); + } + } catch (IOException ioe) { + throw ioe; + } catch (Exception e) { + throw new IOException("Remoting not available", e); + } + + return card; + } + + /** + * Create a new jVCard server on the given port, then run it. + * + * @param port + * the port to run on + * + * @throws SecurityException + * in case of internal error + * @throws NoSuchMethodException + * in case of internal error + * @throws ClassNotFoundException + * in case of internal error + * @throws IllegalAccessException + * in case of internal error + * @throws InstantiationException + * in case of internal error + * @throws InvocationTargetException + * in case of internal error + * @throws IllegalArgumentException + * in case of internal error + * @throws IOException + * in case of IO error + */ + @SuppressWarnings("unchecked") + static private void runServer(int port) throws NoSuchMethodException, + SecurityException, ClassNotFoundException, InstantiationException, + IllegalAccessException, IllegalArgumentException, + InvocationTargetException { + @SuppressWarnings("rawtypes") + Class serverClass = Class.forName("be.nikiroo.jvcard.remote.Server"); + Method run = serverClass.getDeclaredMethod("run", new Class[] {}); + run.invoke(serverClass.getConstructor(int.class).newInstance(port)); + } + + /** + * Start the TUI program. + * + * @param textMode + * TRUE to force text mode, FALSE to force the Swing terminal + * emulator, null to automatically determine the best choice + * @param files + * the files to show at startup + * + * @throws SecurityException + * in case of internal error + * @throws NoSuchMethodException + * in case of internal error + * @throws ClassNotFoundException + * in case of internal error + * @throws IllegalAccessException + * in case of internal error + * @throws InstantiationException + * in case of internal error + * @throws InvocationTargetException + * in case of internal error + * @throws IllegalArgumentException + * in case of internal error + * @throws IOException + * in case of IO error + */ + @SuppressWarnings("unchecked") + static private void startTui(Boolean textMode, List files) + throws NoSuchMethodException, SecurityException, + ClassNotFoundException, InstantiationException, + IllegalAccessException, IllegalArgumentException, + InvocationTargetException { + @SuppressWarnings("rawtypes") + Class launcherClass = Class + .forName("be.nikiroo.jvcard.tui.TuiLauncher"); + Method start = launcherClass.getDeclaredMethod("start", new Class[] { + Boolean.class, List.class }); + start.invoke(launcherClass.newInstance(), textMode, files); + } + + /** + * Return the {@link Card} corresponding to the given URL, synchronised if + * necessary. + * + * @param input + * the jvcard:// with resource name URL (e.g.: + * jvcard://localhost:4444/coworkers) + * + * @throws SecurityException + * in case of internal error + * @throws NoSuchMethodException + * in case of internal error + * @throws ClassNotFoundException + * in case of internal error + * @throws IllegalAccessException + * in case of internal error + * @throws InstantiationException + * in case of internal error + * @throws InvocationTargetException + * in case of internal error + * @throws IllegalArgumentException + * in case of internal error + * @throws IOException + * in case of IO error + */ + @SuppressWarnings("unchecked") + static private Card syncCard(String input) throws ClassNotFoundException, + NoSuchMethodException, SecurityException, InstantiationException, + IllegalAccessException, IllegalArgumentException, + InvocationTargetException, IOException { + @SuppressWarnings("rawtypes") + Class syncClass = Class.forName("be.nikiroo.jvcard.remote.Sync"); + Method getCache = syncClass.getDeclaredMethod("getCache", + new Class[] {}); + Method sync = syncClass.getDeclaredMethod("sync", new Class[] { + Card.class, boolean.class }); + + Object o = syncClass.getConstructor(String.class).newInstance(input); + + File file = (File) getCache.invoke(o); + Card card = new Card(file, Format.VCard21); + card.setRemote(true); + sync.invoke(o, card, false); + + return card; + } + + /** + * Open the given path and add all its files if it is a directory or just + * this one if not to the returned list. + * + * @param path + * the path to open + * + * @return the list of opened files + */ + static private List open(String path) { + List files = new LinkedList(); + + if (path != null && path.startsWith("jvcard://")) { + if (path.endsWith("/")) { + files.addAll(list(path)); + } else { + files.add(path); + } + } else { + File file = new File(path); + if (file.exists()) { + if (file.isDirectory()) { + for (File subfile : file.listFiles()) { + if (!subfile.isDirectory()) + files.add(subfile.getAbsolutePath()); + } + } else { + files.add(file.getAbsolutePath()); + } + } else { + System.err.println("File or directory not found: \"" + path + + "\""); + } + } + + return files; + } + + /** + * List all the available {@link Card}s on the given network location (which + * is expected to be a jVCard remote server, obviously). + * + * @param path + * the jVCard remote server path (e.g.: + * jvcard://localhost:4444/) + * + * @return the list of {@link Card}s + */ + static private List list(String path) { + List files = new LinkedList(); + + try { + String host = path.split("\\:")[1].substring(2); + int port = Integer.parseInt(path.split("\\:")[2].replaceAll("/$", + "")); + SimpleSocket s = new SimpleSocket(new Socket(host, port), + "sync client"); + s.open(true); + + s.sendCommand(Verb.LIST); + for (String p : s.receiveBlock()) { + files.add(path + + p.substring(StringUtils.fromTime(0).length() + 1)); + } + s.close(); + } catch (Exception e) { + e.printStackTrace(); + } + + return files; + } + + /** + * Really, really ask for UTF-8 encoding. + */ + static private void utf8() { + try { + System.setProperty("file.encoding", "UTF-8"); + Field charset = Charset.class.getDeclaredField("defaultCharset"); + charset.setAccessible(true); + charset.set(null, null); + } catch (SecurityException e) { + } catch (NoSuchFieldException e) { + } catch (IllegalArgumentException e) { + } catch (IllegalAccessException e) { + } + } +} diff --git a/src/be/nikiroo/jvcard/remote/Server.java b/src/be/nikiroo/jvcard/remote/Server.java index 4b99365..2ad5619 100644 --- a/src/be/nikiroo/jvcard/remote/Server.java +++ b/src/be/nikiroo/jvcard/remote/Server.java @@ -18,7 +18,7 @@ import be.nikiroo.jvcard.parsers.Format; import be.nikiroo.jvcard.parsers.Vcard21Parser; import be.nikiroo.jvcard.remote.Command.Verb; import be.nikiroo.jvcard.resources.Bundles; -import be.nikiroo.jvcard.tui.StringUtils; +import be.nikiroo.jvcard.resources.StringUtils; /** * This class implements a small server that can listen for requests to @@ -45,13 +45,8 @@ public class Server implements Runnable { private Object updateLock = new Object(); - public static void main(String[] args) throws IOException { - Server server = new Server(4444); - server.run(); - } - /** - * Create a new jVCard sercer on the given port. + * Create a new jVCard server on the given port. * * @param port * the port to run on diff --git a/src/be/nikiroo/jvcard/remote/Sync.java b/src/be/nikiroo/jvcard/remote/Sync.java index d382a2f..70bf93c 100644 --- a/src/be/nikiroo/jvcard/remote/Sync.java +++ b/src/be/nikiroo/jvcard/remote/Sync.java @@ -24,7 +24,7 @@ import be.nikiroo.jvcard.parsers.Format; import be.nikiroo.jvcard.parsers.Vcard21Parser; import be.nikiroo.jvcard.remote.Command.Verb; import be.nikiroo.jvcard.resources.Bundles; -import be.nikiroo.jvcard.tui.StringUtils; +import be.nikiroo.jvcard.resources.StringUtils; /** * This class will synchronise {@link Card}s between a local instance an a @@ -141,6 +141,7 @@ public class Sync { } // return: synced or not + //TODO jDoc public boolean sync(Card card, boolean force) throws UnknownHostException, IOException { diff --git a/src/be/nikiroo/jvcard/remote/package-info.java b/src/be/nikiroo/jvcard/remote/package-info.java new file mode 100644 index 0000000..5cb9de1 --- /dev/null +++ b/src/be/nikiroo/jvcard/remote/package-info.java @@ -0,0 +1,6 @@ +/** + * TODO: describe protocol here. + * + * @author niki + */ +package be.nikiroo.jvcard.remote; \ No newline at end of file diff --git a/src/be/nikiroo/jvcard/resources/Bundles.java b/src/be/nikiroo/jvcard/resources/Bundles.java index e105b71..3b18084 100644 --- a/src/be/nikiroo/jvcard/resources/Bundles.java +++ b/src/be/nikiroo/jvcard/resources/Bundles.java @@ -12,8 +12,9 @@ import java.util.ResourceBundle; public class Bundles { static private String confDir = getConfDir(); - // TODO: rename to bundle, use it as base for i18n.Trans, create one for - // each resource + // TODO: create "Trans" like classes for all .properties file, always get it + // them from here, including Trans (create a new one each time like + // currently) + update Main to call trans again when chaning dir private int TODO; /** diff --git a/src/be/nikiroo/jvcard/i18n/Meta.java b/src/be/nikiroo/jvcard/resources/Meta.java similarity index 96% rename from src/be/nikiroo/jvcard/i18n/Meta.java rename to src/be/nikiroo/jvcard/resources/Meta.java index 6ebe7be..769d132 100644 --- a/src/be/nikiroo/jvcard/i18n/Meta.java +++ b/src/be/nikiroo/jvcard/resources/Meta.java @@ -1,4 +1,4 @@ -package be.nikiroo.jvcard.i18n; +package be.nikiroo.jvcard.resources; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/src/be/nikiroo/jvcard/tui/StringUtils.java b/src/be/nikiroo/jvcard/resources/StringUtils.java similarity index 99% rename from src/be/nikiroo/jvcard/tui/StringUtils.java rename to src/be/nikiroo/jvcard/resources/StringUtils.java index efbce6a..bfe0d3d 100644 --- a/src/be/nikiroo/jvcard/tui/StringUtils.java +++ b/src/be/nikiroo/jvcard/resources/StringUtils.java @@ -1,4 +1,4 @@ -package be.nikiroo.jvcard.tui; +package be.nikiroo.jvcard.resources; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; diff --git a/src/be/nikiroo/jvcard/i18n/Trans.java b/src/be/nikiroo/jvcard/resources/Trans.java similarity index 66% rename from src/be/nikiroo/jvcard/i18n/Trans.java rename to src/be/nikiroo/jvcard/resources/Trans.java index 2a44383..537b3a3 100644 --- a/src/be/nikiroo/jvcard/i18n/Trans.java +++ b/src/be/nikiroo/jvcard/resources/Trans.java @@ -1,4 +1,4 @@ -package be.nikiroo.jvcard.i18n; +package be.nikiroo.jvcard.resources; import java.io.BufferedWriter; import java.io.File; @@ -9,11 +9,6 @@ import java.lang.reflect.Field; import java.util.Locale; import java.util.ResourceBundle; -import be.nikiroo.jvcard.resources.Bundles; -import be.nikiroo.jvcard.tui.UiColors; - -import com.googlecode.lanterna.input.KeyStroke; - /** * This class manages the translation of {@link Trans.StringId}s into * user-understandable text. @@ -23,6 +18,7 @@ import com.googlecode.lanterna.input.KeyStroke; */ public class Trans { private ResourceBundle map; + private boolean utf = true; /** * Create a translation service with the default language. @@ -52,7 +48,7 @@ public class Trans { */ public String trans(StringId stringId) { StringId id = stringId; - if (!UiColors.getInstance().isUnicode()) { + if (!isUnicode()) { try { id = StringId.valueOf(stringId.name() + "_NOUTF"); } catch (IllegalArgumentException iae) { @@ -76,48 +72,22 @@ public class Trans { } /** - * Translate the given {@link KeyStroke} into a user text {@link String} of - * size 3. + * Check if unicode characters should be used. * - * @param key - * the key to translate - * - * @return the translated text + * @return TRUE to allow unicode */ - public String trans(KeyStroke key) { - String keyTrans = ""; - - switch (key.getKeyType()) { - case Enter: - if (UiColors.getInstance().isUnicode()) - keyTrans = " ⤶ "; - else - keyTrans = trans(StringId.KEY_ENTER); - break; - case Tab: - if (UiColors.getInstance().isUnicode()) - keyTrans = " ↹ "; - else - keyTrans = trans(StringId.KEY_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; - } + public boolean isUnicode() { + return utf; + } - return keyTrans; + /** + * 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; } /** @@ -136,70 +106,71 @@ public class Trans { * candidate as base if the file does not already exists (for instance, * "en_US" will use "en" as a base). * - * @param args - * the path where the .properties files are, then the languages - * to create/update + * @param path + * the path where the .properties files are + * + * @param language + * the language code to create/update (e.g.: fr-BE) * * @throws IOException * in case of IO errors */ - static public void main(String[] args) throws IOException { - String path = args[0]; - for (int i = 1; i < args.length; i++) { - Locale locale = getLocaleFor(args[i]); - String code = locale.toString(); - Trans trans = new Trans(code); - - File file = null; - if (code.length() > 0) { - file = new File(path + "resources_" + code + ".properties"); - } else { - // Default properties file: - file = new File(path + "resources.properties"); - } + static public void generateTranslationFile(String path, String language) + throws IOException { - BufferedWriter writer = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream(file), "UTF-8")); + Locale locale = getLocaleFor(language); + String code = locale.toString(); + Trans trans = new Trans(code); - String name = locale.getDisplayCountry(locale); - if (name.length() == 0) - name = locale.getDisplayLanguage(locale); - if (name.length() == 0) - name = "default"; + File file = null; + if (code.length() > 0) { + file = new File(path + "resources_" + code + ".properties"); + } else { + // Default properties file: + file = new File(path + "resources.properties"); + } - if (code.length() > 0) { - name = name + " (" + code + ")"; - } + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter( + new FileOutputStream(file), "UTF-8")); - writer.append("# " + name + " translation file (UTF-8)\n"); - writer.append("# \n"); - writer.append("# Note that any key can be doubled with a _NOUTF suffix\n"); - writer.append("# to use when the flag --noutf is passed\n"); - writer.append("# \n"); - writer.append("# Also, the comments always refer to the key below them.\n"); - writer.append("# \n"); - writer.append("\n"); - - for (Field field : StringId.class.getDeclaredFields()) { - Meta meta = field.getAnnotation(Meta.class); - if (meta != null) { - StringId id = StringId.valueOf(field.getName()); - String info = getMetaInfo(meta); - if (info != null) { - writer.append(info); - writer.append("\n"); - } - - writer.append(id.name()); - writer.append(" = "); - if (!trans.trans(id).equals(id.name())) - writer.append(trans.trans(id)); + String name = locale.getDisplayCountry(locale); + if (name.length() == 0) + name = locale.getDisplayLanguage(locale); + if (name.length() == 0) + name = "default"; + + if (code.length() > 0) { + name = name + " (" + code + ")"; + } + + writer.append("# " + name + " translation file (UTF-8)\n"); + writer.append("# \n"); + writer.append("# Note that any key can be doubled with a _NOUTF suffix\n"); + writer.append("# to use when the flag --noutf is passed\n"); + writer.append("# \n"); + writer.append("# Also, the comments always refer to the key below them.\n"); + writer.append("# \n"); + writer.append("\n"); + + for (Field field : StringId.class.getDeclaredFields()) { + Meta meta = field.getAnnotation(Meta.class); + if (meta != null) { + StringId id = StringId.valueOf(field.getName()); + String info = getMetaInfo(meta); + if (info != null) { + writer.append(info); writer.append("\n"); } - } - writer.close(); + writer.append(id.name()); + writer.append(" = "); + if (!trans.trans(id).equals(id.name())) + writer.append(trans.trans(id)); + writer.append("\n"); + } } + + writer.close(); } /** diff --git a/src/be/nikiroo/jvcard/tui/ImageTextControl.java b/src/be/nikiroo/jvcard/tui/ImageTextControl.java index 33773b2..dc05482 100644 --- a/src/be/nikiroo/jvcard/tui/ImageTextControl.java +++ b/src/be/nikiroo/jvcard/tui/ImageTextControl.java @@ -2,6 +2,7 @@ package be.nikiroo.jvcard.tui; import java.awt.Image; +import be.nikiroo.jvcard.launcher.Main; import be.nikiroo.jvcard.tui.ImageText.Mode; import com.googlecode.lanterna.TerminalSize; @@ -31,7 +32,7 @@ public class ImageTextControl extends Panel { */ public ImageTextControl(Image image, TerminalSize size) { Mode mode = Mode.DOUBLE_DITHERING; - if (!UiColors.getInstance().isUnicode()) { + if (!Main.isUnicode()) { mode = Mode.ASCII; } @@ -52,7 +53,7 @@ public class ImageTextControl extends Panel { * @return TRUE if it was possible to switch modes */ public boolean switchMode() { - if (image == null || !UiColors.getInstance().isUnicode()) + if (image == null || !Main.isUnicode()) return false; Mode[] modes = Mode.values(); diff --git a/src/be/nikiroo/jvcard/tui/KeyAction.java b/src/be/nikiroo/jvcard/tui/KeyAction.java index b60664b..f39b103 100644 --- a/src/be/nikiroo/jvcard/tui/KeyAction.java +++ b/src/be/nikiroo/jvcard/tui/KeyAction.java @@ -5,7 +5,8 @@ import java.io.File; 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.launcher.Main; +import be.nikiroo.jvcard.resources.Trans.StringId; import com.googlecode.lanterna.input.KeyStroke; import com.googlecode.lanterna.input.KeyType; @@ -193,4 +194,49 @@ public class KeyAction { public String getDefaultAnswer() { return null; } + + /** + * Translate the given {@link KeyStroke} into a user text {@link String} of + * size 3. + * + * @param key + * the key to translate + * + * @return the translated text + */ + static public String trans(KeyStroke key) { + String keyTrans = ""; + + switch (key.getKeyType()) { + case Enter: + if (Main.isUnicode()) + keyTrans = " ⤶ "; + else + keyTrans = Main.trans(StringId.KEY_ENTER); + break; + case Tab: + if (Main.isUnicode()) + keyTrans = " ↹ "; + else + keyTrans = Main.trans(StringId.KEY_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 keyTrans; + } } diff --git a/src/be/nikiroo/jvcard/tui/Main.java b/src/be/nikiroo/jvcard/tui/Main.java deleted file mode 100644 index bf6ab4b..0000000 --- a/src/be/nikiroo/jvcard/tui/Main.java +++ /dev/null @@ -1,243 +0,0 @@ -package be.nikiroo.jvcard.tui; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.net.Socket; -import java.nio.charset.Charset; -import java.util.LinkedList; -import java.util.List; - -import be.nikiroo.jvcard.Card; -import be.nikiroo.jvcard.i18n.Trans; -import be.nikiroo.jvcard.i18n.Trans.StringId; -import be.nikiroo.jvcard.remote.Command.Verb; -import be.nikiroo.jvcard.remote.SimpleSocket; -import be.nikiroo.jvcard.resources.Bundles; -import be.nikiroo.jvcard.tui.panes.FileList; - -import com.googlecode.lanterna.gui2.Window; -import com.googlecode.lanterna.input.KeyStroke; - -/** - * 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 { - // TODO: move Main to be.nikiroo.jvcard, use introspection to load the other - // main classes, allow the 3 programs to run from this new Main - // also requires StringUtils/... in a new package - private int TODO; - - public static final String APPLICATION_TITLE = "jVcard"; - public static final String APPLICATION_VERSION = "1.0-beta2-dev"; - - static private Trans transService; - - /** - * Translate the given {@link StringId}. - * - * @param id - * the ID to translate - * - * @return the translation - */ - static public String trans(StringId id) { - if (transService == null) - return ""; - - return transService.trans(id); - } - - /** - * Translate the given {@link KeyStroke}. - * - * @param key - * the key to translate - * - * @return the translation - */ - static public String trans(KeyStroke key) { - if (transService == null) - return ""; - - return transService.trans(key); - } - - /** - * Start the application. - * - * @param args - * the parameters (see --help to know which are - * supported) - */ - public static void main(String[] args) { - Boolean textMode = null; - boolean noMoreParams = false; - boolean filesTried = false; - - // get the "system default" language to help translate the --help - // message if needed - String language = null; - transService = new Trans(language); - - List files = new LinkedList(); - for (int index = 0; index < args.length; index++) { - String arg = args[index]; - 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--lang LANGUAGE: choose the language, for instance en_GB\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--config DIRECTORY: force the given directory as a CONFIG_DIR\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)\n" - + "('jvcard://hostname:8888/file' links -- or without 'file' -- are also ok)\n"); - return; - } else if (!noMoreParams && arg.equals("--tui")) { - textMode = true; - } else if (!noMoreParams && arg.equals("--gui")) { - textMode = false; - } else if (!noMoreParams && arg.equals("--noutf")) { - UiColors.getInstance().setUnicode(false); - } else if (!noMoreParams && arg.equals("--lang")) { - index++; - if (index < args.length) - language = args[index]; - transService = new Trans(language); - } else if (!noMoreParams && arg.equals("--config")) { - index++; - if (index < args.length) { - Bundles.setDirectory(args[index]); - transService = new Trans(language); - } - } else { - filesTried = true; - files.addAll(open(arg)); - } - } - - if (UiColors.getInstance().isUnicode()) { - utf8(); - } - - if (files.size() == 0) { - if (filesTried) { - System.exit(1); - return; - } - - files.addAll(open(".")); - } - - // TODO error case when no file - - Window win = new MainWindow(new FileList(files)); - - try { - TuiLauncher.start(textMode, win); - } catch (IOException ioe) { - ioe.printStackTrace(); - System.exit(2); - } - } - - /** - * Open the given path and add all its files if it is a directory or just - * this one if not to the returned list. - * - * @param path - * the path to open - * - * @return the list of opened files - */ - static private List open(String path) { - List files = new LinkedList(); - - if (path != null && path.startsWith("jvcard://")) { - if (path.endsWith("/")) { - files.addAll(list(path)); - } else { - files.add(path); - } - } else { - File file = new File(path); - if (file.exists()) { - if (file.isDirectory()) { - for (File subfile : file.listFiles()) { - if (!subfile.isDirectory()) - files.add(subfile.getAbsolutePath()); - } - } else { - files.add(file.getAbsolutePath()); - } - } else { - System.err.println("File or directory not found: \"" + path - + "\""); - } - } - - return files; - } - - /** - * List all the available {@link Card}s on the given network location (which - * is expected to be a jVCard remote server, obviously). - * - * @param path - * the jVCard remote server path (e.g.: - * jvcard://localhost:4444/) - * - * @return the list of {@link Card}s - */ - static private List list(String path) { - List files = new LinkedList(); - - try { - String host = path.split("\\:")[1].substring(2); - int port = Integer.parseInt(path.split("\\:")[2].replaceAll("/$", - "")); - SimpleSocket s = new SimpleSocket(new Socket(host, port), - "sync client"); - s.open(true); - - s.sendCommand(Verb.LIST); - for (String p : s.receiveBlock()) { - files.add(path - + p.substring(StringUtils.fromTime(0).length() + 1)); - } - s.close(); - } catch (Exception e) { - e.printStackTrace(); - } - - return files; - } - - /** - * Really, really ask for UTF-8 encoding. - */ - static private void utf8() { - try { - System.setProperty("file.encoding", "UTF-8"); - Field charset = Charset.class.getDeclaredField("defaultCharset"); - charset.setAccessible(true); - charset.set(null, null); - } catch (SecurityException e) { - } catch (NoSuchFieldException e) { - } catch (IllegalArgumentException e) { - } catch (IllegalAccessException e) { - } - } -} diff --git a/src/be/nikiroo/jvcard/tui/MainWindow.java b/src/be/nikiroo/jvcard/tui/MainWindow.java index c3277e3..2362899 100644 --- a/src/be/nikiroo/jvcard/tui/MainWindow.java +++ b/src/be/nikiroo/jvcard/tui/MainWindow.java @@ -5,7 +5,9 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; -import be.nikiroo.jvcard.i18n.Trans.StringId; +import be.nikiroo.jvcard.launcher.Main; +import be.nikiroo.jvcard.resources.StringUtils; +import be.nikiroo.jvcard.resources.Trans.StringId; import be.nikiroo.jvcard.tui.KeyAction.Mode; import be.nikiroo.jvcard.tui.UiColors.Element; import be.nikiroo.jvcard.tui.panes.ContactDetails; @@ -407,8 +409,7 @@ public class MainWindow extends BasicWindow { if (title.length() > 0) { prefix = prefix + ": "; - title = StringUtils.sanitize(title, UiColors.getInstance() - .isUnicode()); + title = StringUtils.sanitize(title, Main.isUnicode()); } String countStr = ""; @@ -501,7 +502,7 @@ public class MainWindow extends BasicWindow { if (" ".equals(trans)) continue; - String keyTrans = Main.trans(action.getKey()); + String keyTrans = KeyAction.trans(action.getKey()); Panel kPane = new Panel(); LinearLayout layout = new LinearLayout(Direction.HORIZONTAL); diff --git a/src/be/nikiroo/jvcard/tui/TuiLauncher.java b/src/be/nikiroo/jvcard/tui/TuiLauncher.java index deb09d4..066ba31 100644 --- a/src/be/nikiroo/jvcard/tui/TuiLauncher.java +++ b/src/be/nikiroo/jvcard/tui/TuiLauncher.java @@ -1,6 +1,9 @@ package be.nikiroo.jvcard.tui; import java.io.IOException; +import java.util.List; + +import be.nikiroo.jvcard.tui.panes.FileList; import com.googlecode.lanterna.TerminalSize; import com.googlecode.lanterna.TextColor; @@ -21,7 +24,36 @@ import com.googlecode.lanterna.terminal.Terminal; * */ public class TuiLauncher { + /** + * Start the TUI program. + * + * @param textMode + * TRUE to force text mode, FALSE to force the Swing terminal + * emulator, null to automatically determine the best choice + * @param files + * the files to show at startup + * + * @throws IOException + * in case of IO error + */ + static public void start(Boolean textMode, List files) + throws IOException { + Window win = new MainWindow(new FileList(files)); + TuiLauncher.start(textMode, win); + } + /** + * Start the TUI program. + * + * @param textMode + * TRUE to force text mode, FALSE to force the Swing terminal + * emulator, null to automatically determine the best choice + * @param win + * the window to show at start + * + * @throws IOException + * in case of IO error + */ static public void start(Boolean textMode, Window win) throws IOException { Terminal terminal = null; diff --git a/src/be/nikiroo/jvcard/tui/UiColors.java b/src/be/nikiroo/jvcard/tui/UiColors.java index 50c2bf4..3fa83c7 100644 --- a/src/be/nikiroo/jvcard/tui/UiColors.java +++ b/src/be/nikiroo/jvcard/tui/UiColors.java @@ -22,7 +22,6 @@ public class UiColors { private ResourceBundle bundle = null; private Map colorMap = null; - private boolean utf = true; private UiColors() { colorMap = new HashMap(); @@ -86,25 +85,6 @@ public class UiColors { } } - /** - * Check if unicode characters should be used. - * - * @return TRUE to allow unicode - */ - public boolean isUnicode() { - return utf; - } - - /** - * Allow or disallow unicode characters in the program. - * - * @param utf - * TRUE to allow unuciode, FALSE to only allow ASCII characters - */ - public void setUnicode(boolean utf) { - this.utf = utf; - } - /** * Create a new {@link Label} with the colours of the given {@link Element}. * diff --git a/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java b/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java index 090cf14..fc6a198 100644 --- a/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java +++ b/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java @@ -11,7 +11,7 @@ import javax.xml.bind.DatatypeConverter; 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.resources.Trans; import be.nikiroo.jvcard.tui.ImageTextControl; import be.nikiroo.jvcard.tui.KeyAction; import be.nikiroo.jvcard.tui.KeyAction.DataType; diff --git a/src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java b/src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java index be74d1c..d0cc8a8 100644 --- a/src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java +++ b/src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java @@ -6,12 +6,12 @@ import java.util.List; 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.launcher.Main; +import be.nikiroo.jvcard.resources.StringUtils; +import be.nikiroo.jvcard.resources.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; import be.nikiroo.jvcard.tui.UiColors.Element; import com.googlecode.lanterna.input.KeyType; @@ -314,8 +314,8 @@ public class ContactDetailsRaw extends MainContentList { value = valueBuilder.toString(); - name = StringUtils.sanitize(name, UiColors.getInstance().isUnicode()); - value = StringUtils.sanitize(value, UiColors.getInstance().isUnicode()); + name = StringUtils.sanitize(name, Main.isUnicode()); + value = StringUtils.sanitize(value, Main.isUnicode()); name = StringUtils.padString(name, SIZE_COL_1); group = StringUtils.padString(group, SIZE_COL_2_OPT); diff --git a/src/be/nikiroo/jvcard/tui/panes/ContactList.java b/src/be/nikiroo/jvcard/tui/panes/ContactList.java index 2e6db52..73c6a84 100644 --- a/src/be/nikiroo/jvcard/tui/panes/ContactList.java +++ b/src/be/nikiroo/jvcard/tui/panes/ContactList.java @@ -7,12 +7,12 @@ import java.util.List; import be.nikiroo.jvcard.Card; import be.nikiroo.jvcard.Contact; import be.nikiroo.jvcard.Data; -import be.nikiroo.jvcard.i18n.Trans; +import be.nikiroo.jvcard.launcher.Main; import be.nikiroo.jvcard.resources.Bundles; +import be.nikiroo.jvcard.resources.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.UiColors; import be.nikiroo.jvcard.tui.UiColors.Element; import com.googlecode.lanterna.input.KeyType; @@ -254,7 +254,7 @@ public class ContactList extends MainContentList { width -= 2; // dirty mark space String[] array = contact.toStringArray(format, getSeparator(), " ", - width, UiColors.getInstance().isUnicode()); + width, Main.isUnicode()); if (contact.isDirty()) { parts.add(new TextPart(" ", el)); diff --git a/src/be/nikiroo/jvcard/tui/panes/FileList.java b/src/be/nikiroo/jvcard/tui/panes/FileList.java index bfc7f8b..e7632e0 100644 --- a/src/be/nikiroo/jvcard/tui/panes/FileList.java +++ b/src/be/nikiroo/jvcard/tui/panes/FileList.java @@ -1,20 +1,17 @@ package be.nikiroo.jvcard.tui.panes; -import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import be.nikiroo.jvcard.Card; -import be.nikiroo.jvcard.i18n.Trans; -import be.nikiroo.jvcard.parsers.Format; -import be.nikiroo.jvcard.remote.Sync; +import be.nikiroo.jvcard.launcher.Main; +import be.nikiroo.jvcard.resources.StringUtils; +import be.nikiroo.jvcard.resources.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; import be.nikiroo.jvcard.tui.UiColors.Element; import com.googlecode.lanterna.input.KeyType; @@ -74,7 +71,7 @@ public class FileList extends MainContentList { name = name.substring(indexSl + 1); } - name = StringUtils.sanitize(name, UiColors.getInstance().isUnicode()); + name = StringUtils.sanitize(name, Main.isUnicode()); count = " " + StringUtils.padString(count, SIZE_COL_1) + " "; name = " " @@ -108,7 +105,7 @@ public class FileList extends MainContentList { String file = files.get(index); try { - Card card = FileList.getCard(file); + Card card = Main.getCard(file); cards.set(index, card); invalidate(); @@ -123,38 +120,4 @@ public class FileList extends MainContentList { return actions; } - - static private Card getCard(String input) throws IOException { - boolean remote = false; - Format format = Format.Abook; - String ext = input; - if (ext.contains(".")) { - String tab[] = ext.split("\\."); - if (tab.length > 1 && tab[tab.length - 1].equalsIgnoreCase("vcf")) { - format = Format.VCard21; - } - } - - if (input.contains("://")) { - format = Format.VCard21; - remote = true; - } - - Card card = null; - try { - if (remote) { - Sync sync = new Sync(input); - card = new Card(sync.getCache(), format); - card.setRemote(true); - sync.sync(card, false); - } else { - card = new Card(new File(input), format); - } - } catch (IOException ioe) { - ioe.printStackTrace(); - throw ioe; - } - - return card; - } } diff --git a/src/be/nikiroo/jvcard/tui/panes/MainContentList.java b/src/be/nikiroo/jvcard/tui/panes/MainContentList.java index 4b6d8ad..e4c40ba 100644 --- a/src/be/nikiroo/jvcard/tui/panes/MainContentList.java +++ b/src/be/nikiroo/jvcard/tui/panes/MainContentList.java @@ -3,10 +3,9 @@ package be.nikiroo.jvcard.tui.panes; import java.util.LinkedList; import java.util.List; -import be.nikiroo.jvcard.i18n.Trans.StringId; -import be.nikiroo.jvcard.tui.Main; -import be.nikiroo.jvcard.tui.StringUtils; -import be.nikiroo.jvcard.tui.UiColors; +import be.nikiroo.jvcard.launcher.Main; +import be.nikiroo.jvcard.resources.StringUtils; +import be.nikiroo.jvcard.resources.Trans.StringId; import be.nikiroo.jvcard.tui.UiColors.Element; import com.googlecode.lanterna.TextColor; @@ -103,7 +102,7 @@ abstract public class MainContentList extends MainContent implements Runnable { graphics.setBackgroundColor(part.getBackgroundColor()); String label = StringUtils.sanitize(part.getText(), - UiColors.getInstance().isUnicode()); + Main.isUnicode()); graphics.putString(position, 0, label); position += label.length(); -- 2.27.0