X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2FTApplication.java;h=8cbce90008643cb622928322cab03ae2741fec35;hb=3fe82fa71d39d874691dc85e6a77250fd4953b17;hp=83dbd25e305e2a09eb43b1127f00323d7242e94f;hpb=e6469faa3f6895ec0ff9b7592a7348a321898b71;p=fanfix.git diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index 83dbd25..8cbce90 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. */ @@ -288,6 +294,11 @@ public class TApplication implements Runnable { */ private List invokeLaters = new LinkedList(); + /** + * The last time the screen was resized. + */ + private long screenResizeTime = 0; + /** * WidgetEventHandler is the main event consumer loop. There are at most * two such threads in existence: the primary for normal case and a @@ -450,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 ----------------------------------------------------------- // ------------------------------------------------------------------------ @@ -635,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(); @@ -711,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(); @@ -829,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; } @@ -886,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"); @@ -927,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; @@ -1323,6 +1432,19 @@ public class TApplication implements Runnable { } } + /** + * Wake the sleeping screen handler. + */ + private void wakeScreenHandler() { + if (!started) { + return; + } + + synchronized (screenHandler) { + screenHandler.notify(); + } + } + // ------------------------------------------------------------------------ // TApplication ----------------------------------------------------------- // ------------------------------------------------------------------------ @@ -1483,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), @@ -1534,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); @@ -1560,19 +1695,41 @@ public class TApplication implements Runnable { Cell cell = getScreen().getCharXY(x, y); if (cell.isImage()) { cell.invertImage(); + } + if (cell.getForeColorRGB() < 0) { + cell.setForeColor(cell.getForeColor().invert()); } else { - if (cell.getForeColorRGB() < 0) { - cell.setForeColor(cell.getForeColor().invert()); - } else { - cell.setForeColorRGB(cell.getForeColorRGB() ^ 0x00ffffff); + cell.setForeColorRGB(cell.getForeColorRGB() ^ 0x00ffffff); + } + if (cell.getBackColorRGB() < 0) { + cell.setBackColor(cell.getBackColor().invert()); + } else { + cell.setBackColorRGB(cell.getBackColorRGB() ^ 0x00ffffff); + } + 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.getBackColorRGB() < 0) { - cell.setBackColor(cell.getBackColor().invert()); - } else { - cell.setBackColorRGB(cell.getBackColorRGB() ^ 0x00ffffff); + } + 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); + } } } - getScreen().putCharXY(x, y, cell); } /** @@ -1639,7 +1796,7 @@ public class TApplication implements Runnable { oldDrawnMouseY = mouseY; } if (getScreen().isDirty()) { - backend.flushScreen(); + screenHandler.setDirty(); } return; } @@ -1694,11 +1851,11 @@ 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 - getScreen().putCharXY(x + 1 + menu.getMnemonic().getShortcutIdx(), + getScreen().putCharXY(x + 1 + menu.getMnemonic().getScreenShortcutIdx(), 0, menu.getMnemonic().getShortcut(), menuMnemonicColor); if (menu.isActive()) { @@ -1706,7 +1863,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) { @@ -1790,15 +1947,9 @@ public class TApplication implements Runnable { getScreen().hideCursor(); } - // Flush the screen contents if (getScreen().isDirty()) { - if (debugThreads) { - System.err.printf("%d %s backend.flushScreen()\n", - System.currentTimeMillis(), Thread.currentThread()); - } - backend.flushScreen(); + screenHandler.setDirty(); } - repaint = false; } @@ -2587,7 +2738,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; @@ -2615,7 +2766,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; @@ -2900,7 +3051,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(); @@ -2980,7 +3131,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"));