From: Niki Roo Date: Mon, 10 Jul 2017 19:34:36 +0000 (+0200) Subject: Prepare a new TUI version with Jexer (needs TTable) X-Git-Url: http://git.nikiroo.be/?p=jvcard.git;a=commitdiff_plain;h=10dd1e387d6a1834596ae70f48cf905d7b302131 Prepare a new TUI version with Jexer (needs TTable) --- 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); + } +}