X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2FTApplication.java;h=ec93629b0b17f9584336ff855f0d1010660121be;hb=bfa37f3b2ef87d39c15fad7d565c00cbabd92acf;hp=2537c3e4b5bbdfddb0d05837000bd6effea21a48;hpb=2b4274048c2f409b5eba8373ab3018aa75911c73;p=fanfix.git diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index 2537c3e..ec93629 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -48,6 +48,7 @@ import java.util.ResourceBundle; import jexer.bits.Cell; import jexer.bits.CellAttributes; import jexer.bits.ColorTheme; +import jexer.bits.StringUtils; import jexer.event.TCommandEvent; import jexer.event.TInputEvent; import jexer.event.TKeypressEvent; @@ -132,6 +133,11 @@ public class TApplication implements Runnable { */ private volatile WidgetEventHandler secondaryEventHandler; + /** + * The screen handler thread. + */ + private volatile ScreenHandler screenHandler; + /** * The widget receiving events from the secondary event handler thread. */ @@ -284,14 +290,14 @@ public class TApplication implements Runnable { private boolean focusFollowsMouse = false; /** - * The images that might be displayed. Note package private access. + * The list of commands to run before the next I/O check. */ - private List images; + private List invokeLaters = new LinkedList(); /** - * The list of commands to run before the next I/O check. + * The last time the screen was resized. */ - private List invokeLaters = new LinkedList(); + private long screenResizeTime = 0; /** * WidgetEventHandler is the main event consumer loop. There are at most @@ -455,6 +461,94 @@ public class TApplication implements Runnable { } } + /** + * ScreenHandler pushes screen updates to the physical device. + */ + private class ScreenHandler implements Runnable { + /** + * The main application. + */ + private TApplication application; + + /** + * The dirty flag. + */ + private boolean dirty = false; + + /** + * Public constructor. + * + * @param application the main application + */ + public ScreenHandler(final TApplication application) { + this.application = application; + } + + /** + * The screen update loop. + */ + public void run() { + // Wrap everything in a try, so that if we go belly up we can let + // the user have their terminal back. + try { + runImpl(); + } catch (Throwable t) { + this.application.restoreConsole(); + t.printStackTrace(); + this.application.exit(); + } + } + + /** + * The update loop. + */ + private void runImpl() { + + // Loop forever + while (!application.quit) { + + // Wait until application notifies me + while (!application.quit) { + try { + synchronized (this) { + if (dirty) { + dirty = false; + break; + } + + // Always check within 50 milliseconds. + this.wait(50); + } + } catch (InterruptedException e) { + // SQUASH + } + } // while (!application.quit) + + // Flush the screen contents + if (debugThreads) { + System.err.printf("%d %s backend.flushScreen()\n", + System.currentTimeMillis(), Thread.currentThread()); + } + synchronized (getScreen()) { + backend.flushScreen(); + } + } // while (true) (main runnable loop) + + // Shutdown the user I/O thread(s) + backend.shutdown(); + } + + /** + * Set the dirty flag. + */ + public void setDirty() { + synchronized (this) { + dirty = true; + } + } + + } + // ------------------------------------------------------------------------ // Constructors ----------------------------------------------------------- // ------------------------------------------------------------------------ @@ -606,7 +700,6 @@ public class TApplication implements Runnable { accelerators = new HashMap(); menuItems = new LinkedList(); desktop = new TDesktop(this); - images = new LinkedList(); // Special case: the Swing backend needs to have a timer to drive its // blink state. @@ -641,6 +734,10 @@ public class TApplication implements Runnable { public void run() { // System.err.println("*** TApplication.run() begins ***"); + // Start the screen updater thread + screenHandler = new ScreenHandler(this); + (new Thread(screenHandler)).start(); + // Start the main consumer thread primaryEventHandler = new WidgetEventHandler(this, true); (new Thread(primaryEventHandler)).start(); @@ -717,9 +814,6 @@ public class TApplication implements Runnable { } } - // Shutdown the user I/O thread(s) - backend.shutdown(); - // Close all the windows. This gives them an opportunity to release // resources. closeAllWindows(); @@ -835,7 +929,7 @@ public class TApplication implements Runnable { openImage(); return true; } - if (menu.getId() == TMenu.MID_CHANGE_FONT) { + if (menu.getId() == TMenu.MID_SCREEN_OPTIONS) { new TFontChooserWindow(this); return true; } @@ -892,6 +986,9 @@ public class TApplication implements Runnable { drawAll(); } + // Wake up the screen repainter + wakeScreenHandler(); + if (debugThreads) { System.err.printf(System.currentTimeMillis() + " " + Thread.currentThread() + " finishEventProcessing() END\n"); @@ -933,8 +1030,14 @@ public class TApplication implements Runnable { if (event instanceof TResizeEvent) { TResizeEvent resize = (TResizeEvent) event; synchronized (getScreen()) { - getScreen().setDimensions(resize.getWidth(), - resize.getHeight()); + if ((System.currentTimeMillis() - screenResizeTime >= 15) + || (resize.getWidth() < getScreen().getWidth()) + || (resize.getHeight() < getScreen().getHeight()) + ) { + getScreen().setDimensions(resize.getWidth(), + resize.getHeight()); + screenResizeTime = System.currentTimeMillis(); + } desktopBottom = getScreen().getHeight() - 1; mouseX = 0; mouseY = 0; @@ -1329,6 +1432,19 @@ public class TApplication implements Runnable { } } + /** + * Wake the sleeping screen handler. + */ + private void wakeScreenHandler() { + if (!started) { + return; + } + + synchronized (screenHandler) { + screenHandler.notify(); + } + } + // ------------------------------------------------------------------------ // TApplication ----------------------------------------------------------- // ------------------------------------------------------------------------ @@ -1489,7 +1605,7 @@ public class TApplication implements Runnable { String version = getClass().getPackage().getImplementationVersion(); if (version == null) { // This is Java 9+, use a hardcoded string here. - version = "0.3.1"; + version = "0.3.2"; } messageBox(i18n.getString("aboutDialogTitle"), MessageFormat.format(i18n.getString("aboutDialogText"), version), @@ -1540,6 +1656,19 @@ public class TApplication implements Runnable { * @param y row position */ private void invertCell(final int x, final int y) { + invertCell(x, y, false); + } + + /** + * Invert the cell color at a position. This is used to track the mouse. + * + * @param x column position + * @param y row position + * @param onlyThisCell if true, only invert this cell + */ + private void invertCell(final int x, final int y, + final boolean onlyThisCell) { + if (debugThreads) { System.err.printf("%d %s invertCell() %d %d\n", System.currentTimeMillis(), Thread.currentThread(), x, y); @@ -1579,6 +1708,29 @@ public class TApplication implements Runnable { } } getScreen().putCharXY(x, y, cell); + if ((onlyThisCell == true) || (cell.getWidth() == Cell.Width.SINGLE)) { + return; + } + + // This cell is one half of a fullwidth glyph. Invert the other + // half. + if (cell.getWidth() == Cell.Width.LEFT) { + if (x < getScreen().getWidth() - 1) { + Cell rightHalf = getScreen().getCharXY(x + 1, y); + if (rightHalf.getWidth() == Cell.Width.RIGHT) { + invertCell(x + 1, y, true); + return; + } + } + } + if (cell.getWidth() == Cell.Width.RIGHT) { + if (x > 0) { + Cell leftHalf = getScreen().getCharXY(x - 1, y); + if (leftHalf.getWidth() == Cell.Width.LEFT) { + invertCell(x - 1, y, true); + } + } + } } /** @@ -1617,7 +1769,7 @@ public class TApplication implements Runnable { getScreen().putCharXY(oldDrawnMouseX, oldDrawnMouseY, oldDrawnMouseCell); oldDrawnMouseCell = getScreen().getCharXY(mouseX, mouseY); - if ((images.size() > 0) && (backend instanceof ECMA48Backend)) { + if (backend instanceof ECMA48Backend) { // Special case: the entire row containing the mouse has // to be re-drawn if it has any image data, AND any rows // in between. @@ -1644,8 +1796,8 @@ public class TApplication implements Runnable { oldDrawnMouseX = mouseX; oldDrawnMouseY = mouseY; } - if ((images.size() > 0) || getScreen().isDirty()) { - backend.flushScreen(); + if (getScreen().isDirty()) { + screenHandler.setDirty(); } return; } @@ -1700,7 +1852,7 @@ public class TApplication implements Runnable { menuMnemonicColor = theme.getColor("tmenu.mnemonic"); } // Draw the menu title - getScreen().hLineXY(x, 0, menu.getTitle().length() + 2, ' ', + getScreen().hLineXY(x, 0, StringUtils.width(menu.getTitle()) + 2, ' ', menuColor); getScreen().putStringXY(x + 1, 0, menu.getTitle(), menuColor); // Draw the highlight character @@ -1712,7 +1864,7 @@ public class TApplication implements Runnable { // Reset the screen clipping so we can draw the next title. getScreen().resetClipping(); } - x += menu.getTitle().length() + 2; + x += StringUtils.width(menu.getTitle()) + 2; } for (TMenu menu: subMenus) { @@ -1746,7 +1898,7 @@ public class TApplication implements Runnable { oldDrawnMouseX, oldDrawnMouseY); } oldDrawnMouseCell = getScreen().getCharXY(mouseX, mouseY); - if ((images.size() > 0) && (backend instanceof ECMA48Backend)) { + if (backend instanceof ECMA48Backend) { // Special case: the entire row containing the mouse has to be // re-drawn if it has any image data, AND any rows in between. if (oldDrawnMouseY != mouseY) { @@ -1796,15 +1948,9 @@ public class TApplication implements Runnable { getScreen().hideCursor(); } - // Flush the screen contents - if ((images.size() > 0) || getScreen().isDirty()) { - if (debugThreads) { - System.err.printf("%d %s backend.flushScreen()\n", - System.currentTimeMillis(), Thread.currentThread()); - } - backend.flushScreen(); + if (getScreen().isDirty()) { + screenHandler.setDirty(); } - repaint = false; } @@ -2529,46 +2675,6 @@ public class TApplication implements Runnable { window.setY(windowY); } - // ------------------------------------------------------------------------ - // TImage management ------------------------------------------------------ - // ------------------------------------------------------------------------ - - /** - * Add an image to the list. Note package private access. - * - * @param image the image to add - * @throws IllegalArgumentException if the image is already used in - * another TApplication - */ - final void addImage(final TImage image) { - if ((image.getApplication() != null) - && (image.getApplication() != this) - ) { - throw new IllegalArgumentException("Image " + image + - " is already " + "part of application " + - image.getApplication()); - } - images.add(image); - } - - /** - * Remove an image from the list. Note package private access. - * - * @param image the image to remove - * @throws IllegalArgumentException if the image is already used in - * another TApplication - */ - final void removeImage(final TImage image) { - if ((image.getApplication() != null) - && (image.getApplication() != this) - ) { - throw new IllegalArgumentException("Image " + image + - " is already " + "part of application " + - image.getApplication()); - } - images.remove(image); - } - // ------------------------------------------------------------------------ // TMenu management ------------------------------------------------------- // ------------------------------------------------------------------------ @@ -2633,7 +2739,7 @@ public class TApplication implements Runnable { for (TMenu menu: menus) { if ((mouse.getAbsoluteX() >= menu.getTitleX()) && (mouse.getAbsoluteX() < menu.getTitleX() - + menu.getTitle().length() + 2) + + StringUtils.width(menu.getTitle()) + 2) ) { menu.setActive(true); activeMenu = menu; @@ -2661,7 +2767,7 @@ public class TApplication implements Runnable { for (TMenu menu: menus) { if ((mouse.getAbsoluteX() >= menu.getTitleX()) && (mouse.getAbsoluteX() < menu.getTitleX() - + menu.getTitle().length() + 2) + + StringUtils.width(menu.getTitle()) + 2) ) { menu.setActive(true); activeMenu = menu; @@ -2946,7 +3052,7 @@ public class TApplication implements Runnable { for (TMenu menu: menus) { menu.setX(x); menu.setTitleX(x); - x += menu.getTitle().length() + 2; + x += StringUtils.width(menu.getTitle()) + 2; // Don't let the menu window exceed the screen width int rightEdge = menu.getX() + menu.getWidth(); @@ -3026,7 +3132,7 @@ public class TApplication implements Runnable { TMenu toolMenu = addMenu(i18n.getString("toolMenuTitle")); toolMenu.addDefaultItem(TMenu.MID_REPAINT); toolMenu.addDefaultItem(TMenu.MID_VIEW_IMAGE); - toolMenu.addDefaultItem(TMenu.MID_CHANGE_FONT); + toolMenu.addDefaultItem(TMenu.MID_SCREEN_OPTIONS); TStatusBar toolStatusBar = toolMenu.newStatusBar(i18n. getString("toolMenuStatus")); toolStatusBar.addShortcutKeypress(kbF1, cmHelp, i18n.getString("Help")); @@ -3157,6 +3263,7 @@ public class TApplication implements Runnable { columnMenu.addDefaultItem(TMenu.MID_TABLE_COLUMN_WIDEN, false); TSubMenu fileMenu = tableMenu.addSubMenu(i18n. getString("tableSubMenuFile")); + fileMenu.addDefaultItem(TMenu.MID_TABLE_FILE_OPEN_CSV, false); fileMenu.addDefaultItem(TMenu.MID_TABLE_FILE_SAVE_CSV, false); fileMenu.addDefaultItem(TMenu.MID_TABLE_FILE_SAVE_TEXT, false);