Prepare a new TUI version with Jexer (needs TTable)
authorNiki Roo <niki@nikiroo.be>
Mon, 10 Jul 2017 19:34:36 +0000 (21:34 +0200)
committerNiki Roo <niki@nikiroo.be>
Mon, 10 Jul 2017 19:34:36 +0000 (21:34 +0200)
src/be/nikiroo/jvcard/launcher/Optional.java
src/be/nikiroo/jvcard/tui/TuiLauncher.java
src/be/nikiroo/jvcard/tui/TuiLauncherJexer.java [new file with mode: 0644]
src/be/nikiroo/jvcard/tui/windows/TuiBrowserWindow.java [new file with mode: 0644]
src/be/nikiroo/jvcard/tui/windows/TuiContactListWindow.java [new file with mode: 0644]
src/be/nikiroo/jvcard/tui/windows/TuiContactWindow.java [new file with mode: 0644]
src/be/nikiroo/jvcard/tui/windows/TuiFileListWindow.java [new file with mode: 0644]
src/be/nikiroo/jvcard/tui/windows/TuiRawContactWindow.java [new file with mode: 0644]

index 9ed726dfe857201285e9dc61078b632abb5c2d63..40592f36ed6ff9622e0ce9eccbc8f24600434d50 100644 (file)
@@ -1,6 +1,7 @@
 package be.nikiroo.jvcard.launcher;
 
 import java.io.IOException;
+import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.List;
@@ -21,7 +22,7 @@ import be.nikiroo.jvcard.launcher.CardResult.MergeCallback;
  * </p>
  * 
  * @author niki
- *
+ * 
  */
 class Optional {
        /***
@@ -29,7 +30,7 @@ class Optional {
         * that has not been compiled into the code.
         * 
         * @author niki
-        *
+        * 
         */
        public class NotSupportedException extends Exception {
                private static final long serialVersionUID = 1L;
@@ -70,7 +71,7 @@ class Optional {
         * 
         * @param port
         *            the port to run on
-        *
+        * 
         * @throws NotSupportedException
         *             in case the option is not supported
         * @throws IOException
@@ -124,9 +125,10 @@ class Optional {
                        @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);
+                       Constructor<?> cons = launcherClass.getConstructors()[0];
+                       Object instance = cons.newInstance(textMode, files);
+                       Method start = launcherClass.getDeclaredMethod("start");
+                       start.invoke(instance);
                } catch (NoSuchMethodException e) {
                        throw new Optional().new NotSupportedException(e, "TUI", true);
                } catch (ClassNotFoundException e) {
@@ -140,6 +142,9 @@ class Optional {
                } catch (IllegalArgumentException e) {
                        throw new Optional().new NotSupportedException(e, "TUI", false);
                } catch (InvocationTargetException e) {
+                       e.printStackTrace();
+                       throw new Optional().new NotSupportedException(e, "TUI", false);
+               } catch (IndexOutOfBoundsException e) {
                        throw new Optional().new NotSupportedException(e, "TUI", false);
                }
        }
index 054fe4a88eb1436314e4f56d21bdb5a526ad09d3..52d244dafe9af65a4596aafb7b68307d02bdbde9 100644 (file)
@@ -8,7 +8,6 @@ import be.nikiroo.jvcard.tui.panes.FileList;
 import com.googlecode.lanterna.TerminalSize;
 import com.googlecode.lanterna.TextColor;
 import com.googlecode.lanterna.gui2.MultiWindowTextGUI;
-import com.googlecode.lanterna.gui2.Window;
 import com.googlecode.lanterna.screen.Screen;
 import com.googlecode.lanterna.screen.TerminalScreen;
 import com.googlecode.lanterna.terminal.DefaultTerminalFactory;
@@ -19,13 +18,15 @@ import com.googlecode.lanterna.terminal.Terminal;
  * Starting the TUI.
  * 
  * @author niki
- *
+ * 
  */
 public class TuiLauncher {
-       static private Screen screen = null;
+       static private Screen screen;
+
+       private Boolean textMode;
+       private List<String> files;
 
        /**
-        * Start the TUI program.
         * 
         * @param textMode
         *            TRUE to force text mode, FALSE to force the Swing terminal
@@ -33,37 +34,21 @@ public class TuiLauncher {
         * @param files
         *            the files to show at startup
         * 
-        * @throws IOException
-        *             in case of IO error
-        */
-       static public void start(Boolean textMode, List<String> files)
-                       throws IOException {
-               Window win = new MainWindow(new FileList(files));
-               TuiLauncher.start(textMode, win);
-       }
-
-       /**
-        * Return the used {@link Screen}.
-        * 
-        * @return the {@link Screen}
         */
-       static public Screen getScreen() {
-               return screen;
+       public TuiLauncher(Boolean textMode, List<String> files) {
+               this.textMode = textMode;
+               this.files = files;
        }
 
        /**
         * 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 {
+       public void start() throws IOException {
                Terminal terminal = null;
 
                DefaultTerminalFactory factory = new DefaultTerminalFactory();
@@ -76,16 +61,14 @@ public class TuiLauncher {
                        terminal = factory.createTerminalEmulator();
                }
 
-               if (win instanceof MainWindow) {
-                       final MainWindow mwin = (MainWindow) win;
-                       mwin.refresh(terminal.getTerminalSize());
-                       terminal.addResizeListener(new ResizeListener() {
-                               @Override
-                               public void onResized(Terminal terminal, TerminalSize newSize) {
-                                       mwin.refresh(newSize);
-                               }
-                       });
-               }
+               final MainWindow win = new MainWindow(new FileList(files));
+               win.refresh(terminal.getTerminalSize());
+               terminal.addResizeListener(new ResizeListener() {
+                       @Override
+                       public void onResized(Terminal terminal, TerminalSize newSize) {
+                               win.refresh(newSize);
+                       }
+               });
 
                screen = new TerminalScreen(terminal);
                screen.startScreen();
@@ -99,4 +82,13 @@ public class TuiLauncher {
                gui.addWindowAndWait(win);
                screen.stopScreen();
        }
+
+       /**
+        * Return the used {@link Screen}.
+        * 
+        * @return the {@link Screen}
+        */
+       static public Screen getScreen() {
+               return screen;
+       }
 }
diff --git a/src/be/nikiroo/jvcard/tui/TuiLauncherJexer.java b/src/be/nikiroo/jvcard/tui/TuiLauncherJexer.java
new file mode 100644 (file)
index 0000000..445fc78
--- /dev/null
@@ -0,0 +1,74 @@
+package be.nikiroo.jvcard.tui;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.List;
+
+import jexer.TApplication;
+import jexer.TWindow;
+import be.nikiroo.jvcard.tui.windows.TuiFileListWindow;
+
+/**
+ * Starting the TUI.
+ * 
+ * @author niki
+ */
+public class TuiLauncherJexer extends TApplication {
+
+       /**
+        * @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 UnsupportedEncodingException
+        */
+       public TuiLauncherJexer(final Boolean textMode, final List<String> files)
+                       throws UnsupportedEncodingException {
+               super(backend(textMode));
+
+               addFileMenu();
+               addWindowMenu();
+
+               @SuppressWarnings("unused")
+               TWindow w = new TuiFileListWindow(this, files);
+       }
+
+       /**
+        * Start the TUI program.
+        * 
+        * @throws IOException
+        *             in case of IO error
+        */
+       public void start() throws IOException {
+               (new Thread(this)).start();
+       }
+
+       /**
+        * Select the most appropriate backend.
+        * 
+        * @param textMode
+        *            NULL for auto-dection
+        * @return the backend type to use
+        */
+       private static BackendType backend(Boolean textMode) {
+               if (textMode == null) {
+                       boolean isMsWindows = System.getProperty("os.name", "")
+                                       .toLowerCase().startsWith("windows");
+                       boolean forceSwing = System.getProperty("jexer.Swing", "false")
+                                       .equals("true");
+                       boolean noConsole = System.console() == null;
+                       if (isMsWindows || forceSwing || noConsole) {
+                               return BackendType.SWING;
+                       }
+
+                       return BackendType.XTERM;
+               }
+
+               if (textMode) {
+                       return BackendType.XTERM;
+               }
+
+               return BackendType.SWING;
+       }
+}
diff --git a/src/be/nikiroo/jvcard/tui/windows/TuiBrowserWindow.java b/src/be/nikiroo/jvcard/tui/windows/TuiBrowserWindow.java
new file mode 100644 (file)
index 0000000..55cc4aa
--- /dev/null
@@ -0,0 +1,119 @@
+package be.nikiroo.jvcard.tui.windows;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import jexer.TAction;
+import jexer.TApplication;
+import jexer.TKeypress;
+import jexer.TTable;
+import jexer.TWindow;
+import jexer.event.TKeypressEvent;
+import jexer.event.TResizeEvent;
+import be.nikiroo.jvcard.tui.panes.MainContent;
+
+public abstract class TuiBrowserWindow extends TWindow {
+       private TApplication app;
+       private TTable table;
+       private boolean showHeader;
+       private Map<TKeypress, TAction> keyBindings;
+
+       public TuiBrowserWindow(TApplication app, String title, boolean showHeaders) {
+               super(app, title, 10, 10);
+
+               this.app = app;
+               this.showHeader = showHeaders;
+
+               table = new TTable(this, 0, 0, getWidth(), getHeight(), new TAction() {
+                       @Override
+                       public void DO() {
+                               onAction(table.getSelectedLine(), table.getSelectedColumn());
+                       }
+               }, null);
+
+               keyBindings = new HashMap<TKeypress, TAction>();
+
+               // TODO: fullscreen selection?
+
+               // TODO: auto-maximize on FS, auto-resize on maximize
+               // setFullscreen(true);
+               maximize();
+               onResize(null);
+       }
+
+       /**
+        * Change the currently displayed data.
+        * 
+        * @param headers
+        *            the table headers (mandatory)
+        * @param lines
+        *            the data to display
+        */
+       public void setData(List<String> headers, List<List<String>> lines) {
+               int prevLine = table.getSelectedLine();
+               int prevColumn = table.getSelectedColumn();
+
+               table.clear();
+               table.setHeaders(headers, showHeader);
+               for (List<String> line : lines) {
+                       table.addLine(line);
+               }
+
+               table.reflow();
+
+               table.setSelectedLine(Math.min(prevLine, table.getNumberOfLines() - 1));
+               table.setSelectedColumn(Math.min(prevColumn,
+                               table.getNumberOfColumns() - 1));
+       }
+
+       public void addKeyBinding(TKeypress key, TAction action) {
+               keyBindings.put(key, action);
+       }
+
+       /**
+        * Return the number of items in this {@link MainContent}, or -1 if this
+        * {@link MainContent} is not countable.
+        * 
+        * @return -1 or the number of present items
+        */
+       public int size() {
+               return table.getNumberOfLines();
+       }
+
+       /**
+        * Close the window.
+        */
+       public void close() {
+               app.closeWindow(this);
+       }
+
+       /**
+        * An item has been selected.
+        * 
+        * @param selectedLine
+        *            the currently selected line
+        * @param selectedColumn
+        *            the currently selected column
+        */
+       @SuppressWarnings("unused")
+       public void onAction(int selectedLine, int selectedColumn) {
+       }
+
+       @Override
+       public void onResize(TResizeEvent resize) {
+               super.onResize(resize);
+               table.setWidth(getWidth());
+               table.setHeight(getHeight());
+               table.reflow();
+       }
+
+       @Override
+       public void onKeypress(TKeypressEvent keypress) {
+               if (keyBindings.containsKey(keypress.getKey())) {
+                       keyBindings.get(keypress.getKey()).DO();
+               } else {
+                       super.onKeypress(keypress);
+               }
+       }
+}
diff --git a/src/be/nikiroo/jvcard/tui/windows/TuiContactListWindow.java b/src/be/nikiroo/jvcard/tui/windows/TuiContactListWindow.java
new file mode 100644 (file)
index 0000000..af6b08e
--- /dev/null
@@ -0,0 +1,110 @@
+package be.nikiroo.jvcard.tui.windows;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+import jexer.TAction;
+import jexer.TApplication;
+import jexer.TKeypress;
+import jexer.TWindow;
+import be.nikiroo.jvcard.Card;
+import be.nikiroo.jvcard.Contact;
+import be.nikiroo.jvcard.resources.DisplayBundle;
+import be.nikiroo.jvcard.resources.DisplayOption;
+
+public class TuiContactListWindow extends TuiBrowserWindow {
+       private TApplication app;
+       private Card card;
+       private String filter;
+       private List<String> formats;
+       private int selectedFormat;
+       private String format;
+
+       public TuiContactListWindow(TApplication app, Card card) {
+               super(app, "Contacts", false);
+
+               this.app = app;
+               this.card = card;
+               this.selectedFormat = -1;
+
+               DisplayBundle map = new DisplayBundle();
+               formats = new LinkedList<String>();
+               for (String format : map.getString(DisplayOption.CONTACT_LIST_FORMAT)
+                               .split(",")) {
+                       formats.add(format);
+               }
+
+               addKeyBinding(TKeypress.kbTab, new TAction() {
+                       @Override
+                       public void DO() {
+                               switchFormat();
+                       }
+               });
+
+               addKeyBinding(TKeypress.kbQ, new TAction() {
+                       @Override
+                       public void DO() {
+                               close();
+                       }
+               });
+
+               switchFormat();
+               setCard(card);
+       }
+
+       @Override
+       public void onAction(int selectedLine, int selectedColumn) {
+               try {
+                       @SuppressWarnings("unused")
+                       TWindow w = new TuiContactWindow(app, card.get(selectedLine));
+               } catch (IndexOutOfBoundsException e) {
+                       setMessage("Fail to get contact", true);
+               }
+       }
+
+       private void setCard(Card card) {
+               List<String> headers = new ArrayList<String>();
+               for (String field : format.split("\\|")) {
+                       headers.add(field);
+               }
+
+               List<List<String>> dataLines = new ArrayList<List<String>>();
+               if (card != null) {
+                       for (Contact c : card) {
+                               if (filter == null
+                                               || c.toString(format, "|").toLowerCase()
+                                                               .contains(filter.toLowerCase())) {
+                                       dataLines.add(Arrays.asList(c.toStringArray(format,
+                                                       getWidth(), true)));
+                               }
+                       }
+               }
+
+               setData(headers, dataLines);
+       }
+
+       private void switchFormat() {
+               if (formats.size() == 0)
+                       return;
+
+               selectedFormat++;
+               if (selectedFormat >= formats.size()) {
+                       selectedFormat = 0;
+               }
+
+               format = formats.get(selectedFormat);
+
+               setCard(card);
+       }
+
+       // TODO
+       private void setMessage(String message, boolean error) {
+               if (error) {
+                       System.err.println(message);
+               } else {
+                       System.out.println(message);
+               }
+       }
+}
diff --git a/src/be/nikiroo/jvcard/tui/windows/TuiContactWindow.java b/src/be/nikiroo/jvcard/tui/windows/TuiContactWindow.java
new file mode 100644 (file)
index 0000000..bc4cb58
--- /dev/null
@@ -0,0 +1,56 @@
+package be.nikiroo.jvcard.tui.windows;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import jexer.TAction;
+import jexer.TApplication;
+import jexer.TKeypress;
+import jexer.TLabel;
+import jexer.TWindow;
+import jexer.event.TKeypressEvent;
+import be.nikiroo.jvcard.Contact;
+
+public class TuiContactWindow extends TWindow {
+       private Map<TKeypress, TAction> keyBindings;
+
+       public TuiContactWindow(final TApplication app, final Contact contact) {
+               super(app, "Contact view", 40, 20);
+
+               keyBindings = new HashMap<TKeypress, TAction>();
+
+               keyBindings.put(TKeypress.kbQ, new TAction() {
+                       @Override
+                       public void DO() {
+                               app.closeWindow(TuiContactWindow.this);
+                       }
+               });
+
+               keyBindings.put(TKeypress.kbR, new TAction() {
+                       @Override
+                       public void DO() {
+                               @SuppressWarnings("unused")
+                               TWindow w = new TuiRawContactWindow(app, contact);
+                       }
+               });
+
+               @SuppressWarnings("unused")
+               TLabel l = new TLabel(this, "'r' to see raw view", 0, 0);
+
+               // TODO: fullscreen selection?
+
+               // TODO: auto-maximize on FS, auto-resize on maximize
+               // setFullscreen(true);
+               maximize();
+               onResize(null);
+       }
+
+       @Override
+       public void onKeypress(TKeypressEvent keypress) {
+               if (keyBindings.containsKey(keypress.getKey())) {
+                       keyBindings.get(keypress.getKey()).DO();
+               } else {
+                       super.onKeypress(keypress);
+               }
+       }
+}
diff --git a/src/be/nikiroo/jvcard/tui/windows/TuiFileListWindow.java b/src/be/nikiroo/jvcard/tui/windows/TuiFileListWindow.java
new file mode 100644 (file)
index 0000000..f1feea5
--- /dev/null
@@ -0,0 +1,152 @@
+package be.nikiroo.jvcard.tui.windows;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import jexer.TAction;
+import jexer.TApplication;
+import jexer.TKeypress;
+import jexer.TWindow;
+import be.nikiroo.jvcard.Card;
+import be.nikiroo.jvcard.launcher.CardResult;
+import be.nikiroo.jvcard.launcher.CardResult.MergeCallback;
+import be.nikiroo.jvcard.launcher.Main;
+import be.nikiroo.jvcard.parsers.Format;
+
+public class TuiFileListWindow extends TuiBrowserWindow {
+       private TApplication app;
+       private List<String> files;
+       private List<CardResult> cards;
+
+       public TuiFileListWindow(TApplication app, List<String> files) {
+               super(app, "Contacts", false);
+
+               this.app = app;
+               this.files = files;
+
+               cards = new ArrayList<CardResult>();
+               for (int i = 0; i < files.size(); i++) {
+                       cards.add(null);
+               }
+
+               List<String> headers = new ArrayList<String>();
+               headers.add("File");
+               List<List<String>> dataLines = new ArrayList<List<String>>();
+               for (String file : files) {
+                       List<String> listOfOneFile = new ArrayList<String>(1);
+                       listOfOneFile.add(new File(file).getName());
+                       dataLines.add(listOfOneFile);
+               }
+
+               addKeyBinding(TKeypress.kbQ, new TAction() {
+                       @Override
+                       public void DO() {
+                               close();
+                       }
+               });
+
+               setData(headers, dataLines);
+       }
+
+       @Override
+       public void onAction(int selectedLine, int selectedColumn) {
+               try {
+                       @SuppressWarnings("unused")
+                       TWindow w = new TuiContactListWindow(app, getCard(selectedLine));
+               } catch (IOException e) {
+                       setMessage("Fail to get file: " + e.getMessage(), true);
+               }
+       }
+
+       private Card getCard(int index) throws IOException {
+               // TODO: check index?
+               if (cards.get(index) == null) {
+                       String file = files.get(index);
+                       CardResult cardResult = retrieveCardResult(file);
+                       cards.set(index, cardResult);
+               }
+
+               return cards.get(index).getCard();
+       }
+
+       private CardResult retrieveCardResult(String file) throws IOException {
+               CardResult cardResult = null;
+               final Card arr[] = new Card[4];
+               try {
+                       cardResult = Main.getCard(file, new MergeCallback() {
+                               @Override
+                               public Card merge(Card previous, Card local, Card server,
+                                               Card autoMerged) {
+                                       arr[0] = previous;
+                                       arr[1] = local;
+                                       arr[2] = server;
+                                       arr[3] = autoMerged;
+
+                                       return null;
+                               }
+                       });
+
+                       cardResult.getCard(); // throw IOE if sync issues
+               } catch (IOException e) {
+                       // Check if merge issue or something else I/O related
+                       if (arr[0] == null)
+                               throw e; // other I/O problems
+
+                       // merge management: set all merge vars in
+                       // merger,
+                       // make sure it has cards but mergeTargetFile
+                       // does not exist
+                       // (create then delete if needed)
+                       // TODO: i18n
+                       setMessage("Merge error, please check/fix the merged contact", true);
+
+                       // TODO: i18n + filename with numbers in it to
+                       // fix
+                       File a = File.createTempFile("Merge result ", ".vcf");
+                       File p = File.createTempFile("Previous common version ", ".vcf");
+                       File l = File.createTempFile("Local ", ".vcf");
+                       File s = File.createTempFile("Remote ", ".vcf");
+                       arr[3].saveAs(a, Format.VCard21);
+                       arr[0].saveAs(p, Format.VCard21);
+                       arr[1].saveAs(l, Format.VCard21);
+                       arr[2].saveAs(s, Format.VCard21);
+                       List<String> mfiles = new LinkedList<String>();
+                       mfiles.add(a.getAbsolutePath());
+                       mfiles.add(p.getAbsolutePath());
+                       mfiles.add(l.getAbsolutePath());
+                       mfiles.add(s.getAbsolutePath());
+                       /*
+                        * merger = new FileList(mfiles); merger.mergeRemoteState =
+                        * arr[2].getContentState(false); merger.mergeSourceFile =
+                        * files.get(index); merger.mergeTargetFile = a;
+                        * 
+                        * obj = merger;
+                        */
+               }
+
+               // TODO:
+               // invalidate();
+
+               if (cardResult.isSynchronised()) {
+                       // TODO i18n
+                       if (cardResult.isChanged())
+                               setMessage("card synchronised: changes from server", false);
+                       else
+                               setMessage("card synchronised: no changes", false);
+               }
+
+               return cardResult;
+       }
+
+       // TODO
+       private void setMessage(String message, boolean error) {
+               if (error) {
+                       System.err.println(message);
+               } else {
+                       System.out.println(message);
+               }
+       }
+}
diff --git a/src/be/nikiroo/jvcard/tui/windows/TuiRawContactWindow.java b/src/be/nikiroo/jvcard/tui/windows/TuiRawContactWindow.java
new file mode 100644 (file)
index 0000000..c784b80
--- /dev/null
@@ -0,0 +1,41 @@
+package be.nikiroo.jvcard.tui.windows;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import jexer.TAction;
+import jexer.TApplication;
+import jexer.TKeypress;
+import be.nikiroo.jvcard.Contact;
+import be.nikiroo.jvcard.Data;
+
+public class TuiRawContactWindow extends TuiBrowserWindow {
+
+       public TuiRawContactWindow(TApplication app, Contact contact) {
+               super(app, "Contact RAW mode", false);
+
+               List<String> headers = new ArrayList<String>();
+               headers.add("Name");
+               headers.add("Value");
+               List<List<String>> dataLines = new ArrayList<List<String>>();
+               for (Data data : contact) {
+                       List<String> dataLine = new ArrayList<String>(1);
+                       dataLine.add(data.getName());
+                       if (data.isBinary()) {
+                               dataLine.add("[BINARY]");
+                       } else {
+                               dataLine.add(data.getValue());
+                       }
+                       dataLines.add(dataLine);
+               }
+
+               addKeyBinding(TKeypress.kbQ, new TAction() {
+                       @Override
+                       public void DO() {
+                               close();
+                       }
+               });
+
+               setData(headers, dataLines);
+       }
+}