From 10dd1e387d6a1834596ae70f48cf905d7b302131 Mon Sep 17 00:00:00 2001
From: Niki Roo
Date: Mon, 10 Jul 2017 21:34:36 +0200
Subject: [PATCH] Prepare a new TUI version with Jexer (needs TTable)
---
src/be/nikiroo/jvcard/launcher/Optional.java | 17 +-
src/be/nikiroo/jvcard/tui/TuiLauncher.java | 62 ++++---
.../nikiroo/jvcard/tui/TuiLauncherJexer.java | 74 +++++++++
.../jvcard/tui/windows/TuiBrowserWindow.java | 119 ++++++++++++++
.../tui/windows/TuiContactListWindow.java | 110 +++++++++++++
.../jvcard/tui/windows/TuiContactWindow.java | 56 +++++++
.../jvcard/tui/windows/TuiFileListWindow.java | 152 ++++++++++++++++++
.../tui/windows/TuiRawContactWindow.java | 41 +++++
8 files changed, 590 insertions(+), 41 deletions(-)
create mode 100644 src/be/nikiroo/jvcard/tui/TuiLauncherJexer.java
create mode 100644 src/be/nikiroo/jvcard/tui/windows/TuiBrowserWindow.java
create mode 100644 src/be/nikiroo/jvcard/tui/windows/TuiContactListWindow.java
create mode 100644 src/be/nikiroo/jvcard/tui/windows/TuiContactWindow.java
create mode 100644 src/be/nikiroo/jvcard/tui/windows/TuiFileListWindow.java
create mode 100644 src/be/nikiroo/jvcard/tui/windows/TuiRawContactWindow.java
diff --git a/src/be/nikiroo/jvcard/launcher/Optional.java b/src/be/nikiroo/jvcard/launcher/Optional.java
index 9ed726d..40592f3 100644
--- a/src/be/nikiroo/jvcard/launcher/Optional.java
+++ b/src/be/nikiroo/jvcard/launcher/Optional.java
@@ -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;
*
*
* @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);
}
}
diff --git a/src/be/nikiroo/jvcard/tui/TuiLauncher.java b/src/be/nikiroo/jvcard/tui/TuiLauncher.java
index 054fe4a..52d244d 100644
--- a/src/be/nikiroo/jvcard/tui/TuiLauncher.java
+++ b/src/be/nikiroo/jvcard/tui/TuiLauncher.java
@@ -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 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 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 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
index 0000000..445fc78
--- /dev/null
+++ b/src/be/nikiroo/jvcard/tui/TuiLauncherJexer.java
@@ -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 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
index 0000000..55cc4aa
--- /dev/null
+++ b/src/be/nikiroo/jvcard/tui/windows/TuiBrowserWindow.java
@@ -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 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();
+
+ // 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 headers, List> lines) {
+ int prevLine = table.getSelectedLine();
+ int prevColumn = table.getSelectedColumn();
+
+ table.clear();
+ table.setHeaders(headers, showHeader);
+ for (List 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
index 0000000..af6b08e
--- /dev/null
+++ b/src/be/nikiroo/jvcard/tui/windows/TuiContactListWindow.java
@@ -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 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();
+ 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 headers = new ArrayList();
+ for (String field : format.split("\\|")) {
+ headers.add(field);
+ }
+
+ List> dataLines = new ArrayList>();
+ 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
index 0000000..bc4cb58
--- /dev/null
+++ b/src/be/nikiroo/jvcard/tui/windows/TuiContactWindow.java
@@ -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 keyBindings;
+
+ public TuiContactWindow(final TApplication app, final Contact contact) {
+ super(app, "Contact view", 40, 20);
+
+ keyBindings = new HashMap();
+
+ 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
index 0000000..f1feea5
--- /dev/null
+++ b/src/be/nikiroo/jvcard/tui/windows/TuiFileListWindow.java
@@ -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 files;
+ private List cards;
+
+ public TuiFileListWindow(TApplication app, List files) {
+ super(app, "Contacts", false);
+
+ this.app = app;
+ this.files = files;
+
+ cards = new ArrayList();
+ for (int i = 0; i < files.size(); i++) {
+ cards.add(null);
+ }
+
+ List headers = new ArrayList();
+ headers.add("File");
+ List> dataLines = new ArrayList>();
+ for (String file : files) {
+ List listOfOneFile = new ArrayList(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 mfiles = new LinkedList();
+ 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
index 0000000..c784b80
--- /dev/null
+++ b/src/be/nikiroo/jvcard/tui/windows/TuiRawContactWindow.java
@@ -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 headers = new ArrayList();
+ headers.add("Name");
+ headers.add("Value");
+ List> dataLines = new ArrayList>();
+ for (Data data : contact) {
+ List dataLine = new ArrayList(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);
+ }
+}
--
2.27.0