From e23ea53820244957b17a7000c6d3e1ff586f1ef0 Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Thu, 22 Nov 2018 17:06:48 -0600 Subject: [PATCH] misc fixes --- src/jexer/TApplication.java | 110 ++++- src/jexer/TApplication.properties | 6 + src/jexer/TButton.java | 26 +- src/jexer/TComboBox.java | 2 +- src/jexer/TEditColorThemeWindow.java | 2 +- src/jexer/TExceptionDialog.java | 207 +++++++++ src/jexer/TExceptionDialog.properties | 15 + src/jexer/TFileOpenBox.java | 3 +- src/jexer/TFontChooserWindow.java | 577 ++++++++++++++++++++++++ src/jexer/TFontChooserWindow.properties | 15 + src/jexer/TSpinner.java | 4 +- src/jexer/TWidget.java | 43 +- src/jexer/backend/Backend.java | 5 + src/jexer/backend/ECMA48Terminal.java | 67 ++- src/jexer/backend/GenericBackend.java | 7 + src/jexer/backend/MultiBackend.java | 9 + src/jexer/backend/SwingTerminal.java | 326 ++++++++----- src/jexer/backend/TWindowBackend.java | 7 + src/jexer/backend/TerminalReader.java | 5 + src/jexer/demos/DemoApplication.java | 1 + src/jexer/menu/TMenu.java | 81 ++-- src/jexer/menu/TMenu.properties | 3 + 22 files changed, 1312 insertions(+), 209 deletions(-) create mode 100644 src/jexer/TExceptionDialog.java create mode 100644 src/jexer/TExceptionDialog.properties create mode 100644 src/jexer/TFontChooserWindow.java create mode 100644 src/jexer/TFontChooserWindow.properties diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index 96f91f2..41981da 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -28,12 +28,14 @@ */ package jexer; +import java.io.File; import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.io.Reader; import java.io.UnsupportedEncodingException; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -812,11 +814,23 @@ public class TApplication implements Runnable { closeAllWindows(); return true; } + if (menu.getId() == TMenu.MID_ABOUT) { + showAboutDialog(); + return true; + } if (menu.getId() == TMenu.MID_REPAINT) { getScreen().clearPhysical(); doRepaint(); return true; } + if (menu.getId() == TMenu.MID_VIEW_IMAGE) { + openImage(); + return true; + } + if (menu.getId() == TMenu.MID_CHANGE_FONT) { + new TFontChooserWindow(this); + return true; + } return false; } @@ -969,7 +983,10 @@ public class TApplication implements Runnable { mouseX = mouse.getX(); mouseY = mouse.getY(); } else { - if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) { + if ((mouse.getType() == TMouseEvent.Type.MOUSE_DOWN) + && (!mouse.isMouseWheelUp()) + && (!mouse.isMouseWheelDown()) + ) { if ((mouse.getTime().getTime() - lastMouseUpTime) < doubleClickTime) { @@ -1151,7 +1168,10 @@ public class TApplication implements Runnable { mouseX = mouse.getX(); mouseY = mouse.getY(); } else { - if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) { + if ((mouse.getType() == TMouseEvent.Type.MOUSE_DOWN) + && (!mouse.isMouseWheelUp()) + && (!mouse.isMouseWheelDown()) + ) { if ((mouse.getTime().getTime() - lastMouseUpTime) < doubleClickTime) { @@ -1314,6 +1334,7 @@ public class TApplication implements Runnable { synchronized (invokeLaters) { invokeLaters.add(command); } + doRepaint(); } /** @@ -1452,6 +1473,41 @@ public class TApplication implements Runnable { this.focusFollowsMouse = focusFollowsMouse; } + /** + * Display the about dialog. + */ + protected void showAboutDialog() { + String version = getClass().getPackage().getImplementationVersion(); + if (version == null) { + // This is Java 9+, use a hardcoded string here. + version = "0.3.0"; + } + messageBox(i18n.getString("aboutDialogTitle"), + MessageFormat.format(i18n.getString("aboutDialogText"), version), + TMessageBox.Type.OK); + } + + /** + * Handle the Tool | Open image menu item. + */ + private void openImage() { + try { + List filters = new ArrayList(); + filters.add("^.*\\.[Jj][Pp][Gg]$"); + filters.add("^.*\\.[Jj][Pp][Ee][Gg]$"); + filters.add("^.*\\.[Pp][Nn][Gg]$"); + filters.add("^.*\\.[Gg][Ii][Ff]$"); + filters.add("^.*\\.[Bb][Mm][Pp]$"); + String filename = fileOpenBox(".", TFileOpenBox.Type.OPEN, filters); + if (filename != null) { + new TImageWindow(this, new File(filename)); + } + } catch (IOException e) { + // Show this exception to the user. + new TExceptionDialog(this, e); + } + } + // ------------------------------------------------------------------------ // Screen refresh loop ---------------------------------------------------- // ------------------------------------------------------------------------ @@ -1959,29 +2015,33 @@ public class TApplication implements Runnable { int z = window.getZ(); window.setZ(-1); window.onUnfocus(); + windows.remove(window); Collections.sort(windows); - windows.remove(0); activeWindow = null; + int newZ = 0; + boolean foundNextWindow = false; + for (TWindow w: windows) { + w.setZ(newZ); + newZ++; // Do not activate a hidden window. if (w.isHidden()) { continue; } - if (w.getZ() > z) { - w.setZ(w.getZ() - 1); - if (w.getZ() == 0) { - w.setActive(true); - w.onFocus(); - assert (activeWindow == null); - activeWindow = w; - } else { - if (w.isActive()) { - w.setActive(false); - w.onUnfocus(); - } - } + if (foundNextWindow == false) { + foundNextWindow = true; + w.setActive(true); + w.onFocus(); + assert (activeWindow == null); + activeWindow = w; + continue; + } + + if (w.isActive()) { + w.setActive(false); + w.onUnfocus(); } } } @@ -2557,7 +2617,7 @@ public class TApplication implements Runnable { if (((focusFollowsMouse == true) && (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION)) - || (mouse.getType() == TMouseEvent.Type.MOUSE_UP) + || (mouse.getType() == TMouseEvent.Type.MOUSE_DOWN) ) { synchronized (windows) { Collections.sort(windows); @@ -2873,6 +2933,22 @@ public class TApplication implements Runnable { return menu; } + /** + * Convenience function to add a default tools (hamburger) menu. + * + * @return the new menu + */ + public final TMenu addToolMenu() { + TMenu toolMenu = addMenu(i18n.getString("toolMenuTitle")); + toolMenu.addDefaultItem(TMenu.MID_REPAINT); + toolMenu.addDefaultItem(TMenu.MID_VIEW_IMAGE); + toolMenu.addDefaultItem(TMenu.MID_CHANGE_FONT); + TStatusBar toolStatusBar = toolMenu.newStatusBar(i18n. + getString("toolMenuStatus")); + toolStatusBar.addShortcutKeypress(kbF1, cmHelp, i18n.getString("Help")); + return toolMenu; + } + /** * Convenience function to add a default "File" menu. * diff --git a/src/jexer/TApplication.properties b/src/jexer/TApplication.properties index bf9bcbe..c4d183e 100644 --- a/src/jexer/TApplication.properties +++ b/src/jexer/TApplication.properties @@ -1,4 +1,7 @@ Help=Help + +toolMenuTitle=&\u2261 +toolMenuStatus=Additional tools fileMenuTitle=&File fileMenuStatus=File-management commands (Open, Save, Print, etc.) editMenuTitle=&Edit @@ -10,3 +13,6 @@ helpMenuStatus=Access online help exitDialogTitle=Confirmation exitDialogText=Exit application? + +aboutDialogTitle=About +aboutDialogText=Jexer Version {0} diff --git a/src/jexer/TButton.java b/src/jexer/TButton.java index 29f3743..9c0e98b 100644 --- a/src/jexer/TButton.java +++ b/src/jexer/TButton.java @@ -68,6 +68,11 @@ public class TButton extends TWidget { */ private TAction action; + /** + * The background color used for the button "shadow". + */ + private CellAttributes shadowColor; + // ------------------------------------------------------------------------ // Constructors ----------------------------------------------------------- // ------------------------------------------------------------------------ @@ -92,6 +97,11 @@ public class TButton extends TWidget { setY(y); setHeight(2); setWidth(mnemonic.getRawLabel().length() + 3); + + shadowColor = new CellAttributes(); + shadowColor.setTo(getWindow().getBackground()); + shadowColor.setForeColor(Color.BLACK); + shadowColor.setBold(false); } /** @@ -209,10 +219,6 @@ public class TButton extends TWidget { public void draw() { CellAttributes buttonColor; CellAttributes menuMnemonicColor; - CellAttributes shadowColor = new CellAttributes(); - shadowColor.setTo(getWindow().getBackground()); - shadowColor.setForeColor(Color.BLACK); - shadowColor.setBold(false); if (!isEnabled()) { buttonColor = getTheme().getColor("tbutton.disabled"); @@ -275,4 +281,16 @@ public class TButton extends TWidget { } } + /** + * Set the background color used for the button "shadow". + * + * @param color the new background color + */ + public void setShadowColor(final CellAttributes color) { + shadowColor = new CellAttributes(); + shadowColor.setTo(color); + shadowColor.setForeColor(Color.BLACK); + shadowColor.setBold(false); + } + } diff --git a/src/jexer/TComboBox.java b/src/jexer/TComboBox.java index bb223c3..5748c36 100644 --- a/src/jexer/TComboBox.java +++ b/src/jexer/TComboBox.java @@ -249,7 +249,7 @@ public class TComboBox extends TWidget { } } - if (isAbsoluteActive() && (limitToListValue == false)) { + if (isAbsoluteActive()) { comboBoxColor = getTheme().getColor("tcombobox.active"); } else { comboBoxColor = getTheme().getColor("tcombobox.inactive"); diff --git a/src/jexer/TEditColorThemeWindow.java b/src/jexer/TEditColorThemeWindow.java index 55be8aa..5d1c041 100644 --- a/src/jexer/TEditColorThemeWindow.java +++ b/src/jexer/TEditColorThemeWindow.java @@ -627,7 +627,7 @@ public class TEditColorThemeWindow extends TWindow { // ------------------------------------------------------------------------ /** - * Public constructor. The file open box will be centered on screen. + * Public constructor. The window will be centered on screen. * * @param application the TApplication that manages this window */ diff --git a/src/jexer/TExceptionDialog.java b/src/jexer/TExceptionDialog.java new file mode 100644 index 0000000..0e06756 --- /dev/null +++ b/src/jexer/TExceptionDialog.java @@ -0,0 +1,207 @@ +/* + * Jexer - Java Text User Interface + * + * The MIT License (MIT) + * + * Copyright (C) 2019 Kevin Lamonte + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.ResourceBundle; + +import jexer.bits.CellAttributes; + +/** + * TExceptionDialog displays an exception and its stack trace to the user, + * and provides a means to save a troubleshooting report for support. + */ +public class TExceptionDialog extends TWindow { + + /** + * Translated strings. + */ + private static ResourceBundle i18n = ResourceBundle.getBundle(TExceptionDialog.class.getName()); + + // ------------------------------------------------------------------------ + // Constants -------------------------------------------------------------- + // ------------------------------------------------------------------------ + + // ------------------------------------------------------------------------ + // Variables -------------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * The exception. We will actually make it Throwable, for the unlikely + * event we catch an Error rather than an Exception. + */ + private Throwable exception; + + /** + * The exception's stack trace. + */ + private TList stackTrace; + + // ------------------------------------------------------------------------ + // Constructors ----------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Public constructor. + * + * @param application TApplication that manages this window + * @param exception the exception to display + */ + public TExceptionDialog(final TApplication application, + final Throwable exception) { + + super(application, i18n.getString("windowTitle"), + 1, 1, 70, 20, CENTERED | MODAL); + + this.exception = exception; + + addLabel(i18n.getString("captionLine1"), 1, 1, + "twindow.background.modal"); + addLabel(i18n.getString("captionLine2"), 1, 2, + "twindow.background.modal"); + addLabel(i18n.getString("captionLine3"), 1, 3, + "twindow.background.modal"); + addLabel(i18n.getString("captionLine4"), 1, 4, + "twindow.background.modal"); + + addLabel(MessageFormat.format(i18n.getString("exceptionString"), + exception.getClass().getName(), exception.getMessage()), + 2, 6, "ttext", false); + + ArrayList stackTraceStrings = new ArrayList(); + StackTraceElement [] stack = exception.getStackTrace(); + for (int i = 0; i < stack.length; i++) { + stackTraceStrings.add(stack[i].toString()); + } + stackTrace = addList(stackTraceStrings, 2, 7, getWidth() - 6, 8); + + // Buttons + addButton(i18n.getString("saveButton"), 19, getHeight() - 4, + new TAction() { + public void DO() { + saveToFile(); + } + }); + + TButton closeButton = addButton(i18n.getString("closeButton"), + 35, getHeight() - 4, + new TAction() { + public void DO() { + // Don't do anything, just close the window. + TExceptionDialog.this.close(); + } + }); + + // Save this for last: make the close button default action. + activate(closeButton); + } + + // ------------------------------------------------------------------------ + // TWindow ---------------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Draw the exception message background. + */ + @Override + public void draw() { + // Draw window and border. + super.draw(); + + CellAttributes boxColor = getTheme().getColor("ttext"); + hLineXY(3, 7, getWidth() - 6, ' ', boxColor); + } + + // ------------------------------------------------------------------------ + // TExceptionDialog ------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Save a troubleshooting report to file. Note that we do NOT translate + * the strings within the error report. + */ + private void saveToFile() { + // Prompt for filename. + PrintWriter writer = null; + try { + String filename = fileSaveBox("."); + if (filename == null) { + // User cancelled, bail out. + return; + } + writer = new PrintWriter(new FileWriter(filename)); + writer.write("Date: " + new Date(System.currentTimeMillis()) + + "\n"); + + // System properties + writer.write("System properties:\n"); + writer.write("-----------------------------------\n"); + System.getProperties().store(writer, null); + writer.write("-----------------------------------\n"); + writer.write("\n"); + + // The exception we caught + writer.write("Caught exception:\n"); + writer.write("-----------------------------------\n"); + exception.printStackTrace(writer); + writer.write("-----------------------------------\n"); + writer.write("\n"); + // The exception's cause, if it was set + if (exception.getCause() != null) { + writer.write("Caught exception's cause:\n"); + writer.write("-----------------------------------\n"); + exception.getCause().printStackTrace(writer); + writer.write("-----------------------------------\n"); + } + writer.write("\n"); + + // The UI stack trace + writer.write("UI stack trace:\n"); + writer.write("-----------------------------------\n"); + (new Throwable("UI Thread")).printStackTrace(writer); + writer.write("-----------------------------------\n"); + writer.write("\n"); + writer.close(); + } catch (IOException e) { + messageBox(i18n.getString("errorDialogTitle"), + MessageFormat.format(i18n. + getString("errorSavingFile"), e.getMessage())); + } finally { + if (writer != null) { + writer.close(); + writer = null; + } + } + } +} diff --git a/src/jexer/TExceptionDialog.properties b/src/jexer/TExceptionDialog.properties new file mode 100644 index 0000000..d07998c --- /dev/null +++ b/src/jexer/TExceptionDialog.properties @@ -0,0 +1,15 @@ +windowTitle=Java Exception Caught +statusBar=Exception + +captionLine1=An error has occurred. This may be due to a programming bug, but +captionLine2=could also be a correctable or temporary issue. The stack trace +captionLine3=is reported below. If you wish to submit a bug report, please +captionLine4=use the Save button to create a more detailed error log. + +exceptionString={0}: {1} + +saveButton=&Save Report +closeButton=\ \ \ &Close\ \ \ + +errorDialogTitle=Error +errorSavingFile=Error saving file: {0} diff --git a/src/jexer/TFileOpenBox.java b/src/jexer/TFileOpenBox.java index ac23cfd..eaa38ce 100644 --- a/src/jexer/TFileOpenBox.java +++ b/src/jexer/TFileOpenBox.java @@ -185,6 +185,7 @@ public class TFileOpenBox extends TWindow { File selectedDir = ((TDirectoryTreeItem) item).getFile(); try { directoryList.setPath(selectedDir.getCanonicalPath()); + entryField.setText(selectedDir.getCanonicalPath()); if (type == Type.OPEN) { openButton.setEnabled(false); } @@ -391,7 +392,7 @@ public class TFileOpenBox extends TWindow { private void checkFilename(final String newFilename) throws IOException { File newFile = new File(newFilename); if (newFile.exists()) { - if (newFile.isFile()) { + if (newFile.isFile() || (type == Type.SELECT)) { filename = newFilename; getApplication().closeWindow(this); return; diff --git a/src/jexer/TFontChooserWindow.java b/src/jexer/TFontChooserWindow.java new file mode 100644 index 0000000..5878b59 --- /dev/null +++ b/src/jexer/TFontChooserWindow.java @@ -0,0 +1,577 @@ +/* + * Jexer - Java Text User Interface + * + * The MIT License (MIT) + * + * Copyright (C) 2019 Kevin Lamonte + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer; + +import java.awt.Font; +import java.awt.GraphicsEnvironment; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.ResourceBundle; + +import jexer.backend.SwingTerminal; +import jexer.bits.CellAttributes; +import jexer.bits.GraphicsChars; +import jexer.event.TKeypressEvent; +import static jexer.TKeypress.*; + +/** + * TFontChooserWindow provides an easy UI for users to alter the running + * font. + * + */ +public class TFontChooserWindow extends TWindow { + + /** + * Translated strings. + */ + private static final ResourceBundle i18n = ResourceBundle.getBundle(TFontChooserWindow.class.getName()); + + // ------------------------------------------------------------------------ + // Variables -------------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * The Swing screen. + */ + private SwingTerminal terminal = null; + + /** + * The font name. + */ + private TComboBox fontName; + + /** + * The font size. + */ + private TField fontSize; + + /** + * The X text adjustment. + */ + private TField textAdjustX; + + /** + * The Y text adjustment. + */ + private TField textAdjustY; + + /** + * The height text adjustment. + */ + private TField textAdjustHeight; + + /** + * The width text adjustment. + */ + private TField textAdjustWidth; + + /** + * The original font size. + */ + private int oldFontSize = 20; + + /** + * The original font. + */ + private Font oldFont = null; + + /** + * The original text adjust X value. + */ + private int oldTextAdjustX = 0; + + /** + * The original text adjust Y value. + */ + private int oldTextAdjustY = 0; + + /** + * The original text adjust height value. + */ + private int oldTextAdjustHeight = 0; + + /** + * The original text adjust width value. + */ + private int oldTextAdjustWidth = 0; + + // ------------------------------------------------------------------------ + // Constructors ----------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Public constructor. The window will be centered on screen. + * + * @param application the TApplication that manages this window + */ + public TFontChooserWindow(final TApplication application) { + + // Register with the TApplication + super(application, i18n.getString("windowTitle"), 0, 0, 60, 18, MODAL); + + // Add shortcut text + newStatusBar(i18n.getString("statusBar")); + + if (getScreen() instanceof SwingTerminal) { + terminal = (SwingTerminal) getScreen(); + } + + addLabel(i18n.getString("fontName"), 1, 1, "ttext", false); + addLabel(i18n.getString("fontSize"), 1, 2, "ttext", false); + addLabel(i18n.getString("textAdjustX"), 1, 4, "ttext", false); + addLabel(i18n.getString("textAdjustY"), 1, 5, "ttext", false); + addLabel(i18n.getString("textAdjustHeight"), 1, 6, "ttext", false); + addLabel(i18n.getString("textAdjustWidth"), 1, 7, "ttext", false); + + int col = 18; + if (terminal == null) { + // Non-Swing case: we can't change anything + addLabel(i18n.getString("unavailable"), col, 1); + addLabel(i18n.getString("unavailable"), col, 2); + addLabel(i18n.getString("unavailable"), col, 4); + addLabel(i18n.getString("unavailable"), col, 5); + addLabel(i18n.getString("unavailable"), col, 6); + addLabel(i18n.getString("unavailable"), col, 7); + } else { + oldFont = terminal.getFont(); + oldFontSize = terminal.getFontSize(); + oldTextAdjustX = terminal.getTextAdjustX(); + oldTextAdjustY = terminal.getTextAdjustY(); + oldTextAdjustHeight = terminal.getTextAdjustHeight(); + oldTextAdjustWidth = terminal.getTextAdjustWidth(); + + String [] fontNames = GraphicsEnvironment. + getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); + List fonts = new ArrayList(); + fonts.add(0, i18n.getString("builtInTerminus")); + fonts.addAll(Arrays.asList(fontNames)); + fontName = addComboBox(col, 1, 25, fonts, 0, 10, + new TAction() { + public void DO() { + if (fontName.getText().equals(i18n. + getString("builtInTerminus"))) { + + terminal.setDefaultFont(); + } else { + terminal.setFont(new Font(fontName.getText(), + Font.PLAIN, terminal.getFontSize())); + fontSize.setText(Integer.toString( + terminal.getFontSize())); + textAdjustX.setText(Integer.toString( + terminal.getTextAdjustX())); + textAdjustY.setText(Integer.toString( + terminal.getTextAdjustY())); + textAdjustHeight.setText(Integer.toString( + terminal.getTextAdjustHeight())); + textAdjustWidth.setText(Integer.toString( + terminal.getTextAdjustWidth())); + } + } + } + ); + + // Font size + fontSize = addField(col, 2, 3, true, + Integer.toString(terminal.getFontSize()), + new TAction() { + public void DO() { + int currentSize = terminal.getFontSize(); + int newSize = currentSize; + try { + newSize = Integer.parseInt(fontSize.getText()); + } catch (NumberFormatException e) { + fontSize.setText(Integer.toString(currentSize)); + } + if (newSize != currentSize) { + terminal.setFontSize(newSize); + textAdjustX.setText(Integer.toString( + terminal.getTextAdjustX())); + textAdjustY.setText(Integer.toString( + terminal.getTextAdjustY())); + textAdjustHeight.setText(Integer.toString( + terminal.getTextAdjustHeight())); + textAdjustWidth.setText(Integer.toString( + terminal.getTextAdjustWidth())); + } + } + }, + null); + + addSpinner(col + 3, 2, + new TAction() { + public void DO() { + int currentSize = terminal.getFontSize(); + int newSize = currentSize; + try { + newSize = Integer.parseInt(fontSize.getText()); + newSize++; + } catch (NumberFormatException e) { + fontSize.setText(Integer.toString(currentSize)); + } + fontSize.setText(Integer.toString(newSize)); + if (newSize != currentSize) { + terminal.setFontSize(newSize); + textAdjustX.setText(Integer.toString( + terminal.getTextAdjustX())); + textAdjustY.setText(Integer.toString( + terminal.getTextAdjustY())); + textAdjustHeight.setText(Integer.toString( + terminal.getTextAdjustHeight())); + textAdjustWidth.setText(Integer.toString( + terminal.getTextAdjustWidth())); + } + } + }, + new TAction() { + public void DO() { + int currentSize = terminal.getFontSize(); + int newSize = currentSize; + try { + newSize = Integer.parseInt(fontSize.getText()); + newSize--; + } catch (NumberFormatException e) { + fontSize.setText(Integer.toString(currentSize)); + } + fontSize.setText(Integer.toString(newSize)); + if (newSize != currentSize) { + terminal.setFontSize(newSize); + textAdjustX.setText(Integer.toString( + terminal.getTextAdjustX())); + textAdjustY.setText(Integer.toString( + terminal.getTextAdjustY())); + textAdjustHeight.setText(Integer.toString( + terminal.getTextAdjustHeight())); + textAdjustWidth.setText(Integer.toString( + terminal.getTextAdjustWidth())); + } + } + } + ); + + // textAdjustX + textAdjustX = addField(col, 4, 3, true, + Integer.toString(terminal.getTextAdjustX()), + new TAction() { + public void DO() { + int currentAdjust = terminal.getTextAdjustX(); + int newAdjust = currentAdjust; + try { + newAdjust = Integer.parseInt(textAdjustX.getText()); + } catch (NumberFormatException e) { + textAdjustX.setText(Integer.toString(currentAdjust)); + } + if (newAdjust != currentAdjust) { + terminal.setTextAdjustX(newAdjust); + } + } + }, + null); + + addSpinner(col + 3, 4, + new TAction() { + public void DO() { + int currentAdjust = terminal.getTextAdjustX(); + int newAdjust = currentAdjust; + try { + newAdjust = Integer.parseInt(textAdjustX.getText()); + newAdjust++; + } catch (NumberFormatException e) { + textAdjustX.setText(Integer.toString(currentAdjust)); + } + textAdjustX.setText(Integer.toString(newAdjust)); + if (newAdjust != currentAdjust) { + terminal.setTextAdjustX(newAdjust); + } + } + }, + new TAction() { + public void DO() { + int currentAdjust = terminal.getTextAdjustX(); + int newAdjust = currentAdjust; + try { + newAdjust = Integer.parseInt(textAdjustX.getText()); + newAdjust--; + } catch (NumberFormatException e) { + textAdjustX.setText(Integer.toString(currentAdjust)); + } + textAdjustX.setText(Integer.toString(newAdjust)); + if (newAdjust != currentAdjust) { + terminal.setTextAdjustX(newAdjust); + } + } + } + ); + + // textAdjustY + textAdjustY = addField(col, 5, 3, true, + Integer.toString(terminal.getTextAdjustY()), + new TAction() { + public void DO() { + int currentAdjust = terminal.getTextAdjustY(); + int newAdjust = currentAdjust; + try { + newAdjust = Integer.parseInt(textAdjustY.getText()); + } catch (NumberFormatException e) { + textAdjustY.setText(Integer.toString(currentAdjust)); + } + if (newAdjust != currentAdjust) { + terminal.setTextAdjustY(newAdjust); + } + } + }, + null); + + addSpinner(col + 3, 5, + new TAction() { + public void DO() { + int currentAdjust = terminal.getTextAdjustY(); + int newAdjust = currentAdjust; + try { + newAdjust = Integer.parseInt(textAdjustY.getText()); + newAdjust++; + } catch (NumberFormatException e) { + textAdjustY.setText(Integer.toString(currentAdjust)); + } + textAdjustY.setText(Integer.toString(newAdjust)); + if (newAdjust != currentAdjust) { + terminal.setTextAdjustY(newAdjust); + } + } + }, + new TAction() { + public void DO() { + int currentAdjust = terminal.getTextAdjustY(); + int newAdjust = currentAdjust; + try { + newAdjust = Integer.parseInt(textAdjustY.getText()); + newAdjust--; + } catch (NumberFormatException e) { + textAdjustY.setText(Integer.toString(currentAdjust)); + } + textAdjustY.setText(Integer.toString(newAdjust)); + if (newAdjust != currentAdjust) { + terminal.setTextAdjustY(newAdjust); + } + } + } + ); + + // textAdjustHeight + textAdjustHeight = addField(col, 6, 3, true, + Integer.toString(terminal.getTextAdjustHeight()), + new TAction() { + public void DO() { + int currentAdjust = terminal.getTextAdjustHeight(); + int newAdjust = currentAdjust; + try { + newAdjust = Integer.parseInt(textAdjustHeight.getText()); + } catch (NumberFormatException e) { + textAdjustHeight.setText(Integer.toString(currentAdjust)); + } + if (newAdjust != currentAdjust) { + terminal.setTextAdjustHeight(newAdjust); + } + } + }, + null); + + addSpinner(col + 3, 6, + new TAction() { + public void DO() { + int currentAdjust = terminal.getTextAdjustHeight(); + int newAdjust = currentAdjust; + try { + newAdjust = Integer.parseInt(textAdjustHeight.getText()); + newAdjust++; + } catch (NumberFormatException e) { + textAdjustHeight.setText(Integer.toString(currentAdjust)); + } + textAdjustHeight.setText(Integer.toString(newAdjust)); + if (newAdjust != currentAdjust) { + terminal.setTextAdjustHeight(newAdjust); + } + } + }, + new TAction() { + public void DO() { + int currentAdjust = terminal.getTextAdjustHeight(); + int newAdjust = currentAdjust; + try { + newAdjust = Integer.parseInt(textAdjustHeight.getText()); + newAdjust--; + } catch (NumberFormatException e) { + textAdjustHeight.setText(Integer.toString(currentAdjust)); + } + textAdjustHeight.setText(Integer.toString(newAdjust)); + if (newAdjust != currentAdjust) { + terminal.setTextAdjustHeight(newAdjust); + } + } + } + ); + + // textAdjustWidth + textAdjustWidth = addField(col, 7, 3, true, + Integer.toString(terminal.getTextAdjustWidth()), + new TAction() { + public void DO() { + int currentAdjust = terminal.getTextAdjustWidth(); + int newAdjust = currentAdjust; + try { + newAdjust = Integer.parseInt(textAdjustWidth.getText()); + } catch (NumberFormatException e) { + textAdjustWidth.setText(Integer.toString(currentAdjust)); + } + if (newAdjust != currentAdjust) { + terminal.setTextAdjustWidth(newAdjust); + } + } + }, + null); + + addSpinner(col + 3, 7, + new TAction() { + public void DO() { + int currentAdjust = terminal.getTextAdjustWidth(); + int newAdjust = currentAdjust; + try { + newAdjust = Integer.parseInt(textAdjustWidth.getText()); + newAdjust++; + } catch (NumberFormatException e) { + textAdjustWidth.setText(Integer.toString(currentAdjust)); + } + textAdjustWidth.setText(Integer.toString(newAdjust)); + if (newAdjust != currentAdjust) { + terminal.setTextAdjustWidth(newAdjust); + } + } + }, + new TAction() { + public void DO() { + int currentAdjust = terminal.getTextAdjustWidth(); + int newAdjust = currentAdjust; + try { + newAdjust = Integer.parseInt(textAdjustWidth.getText()); + newAdjust--; + } catch (NumberFormatException e) { + textAdjustWidth.setText(Integer.toString(currentAdjust)); + } + textAdjustWidth.setText(Integer.toString(newAdjust)); + if (newAdjust != currentAdjust) { + terminal.setTextAdjustWidth(newAdjust); + } + } + } + ); + + } + + addButton(i18n.getString("okButton"), 18, getHeight() - 4, + new TAction() { + public void DO() { + // Close window. + TFontChooserWindow.this.close(); + } + }); + + TButton cancelButton = addButton(i18n.getString("cancelButton"), + 30, getHeight() - 4, + new TAction() { + public void DO() { + // Restore old values, then close the window. + if (terminal != null) { + terminal.setFont(oldFont); + terminal.setFontSize(oldFontSize); + terminal.setTextAdjustX(oldTextAdjustX); + terminal.setTextAdjustY(oldTextAdjustY); + terminal.setTextAdjustHeight(oldTextAdjustHeight); + terminal.setTextAdjustWidth(oldTextAdjustWidth); + } + TFontChooserWindow.this.close(); + } + }); + + // Save this for last: make the cancel button default action. + activate(cancelButton); + + } + + // ------------------------------------------------------------------------ + // Event handlers --------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Handle keystrokes. + * + * @param keypress keystroke event + */ + @Override + public void onKeypress(final TKeypressEvent keypress) { + // Escape - behave like cancel + if (keypress.equals(kbEsc)) { + // Restore old values, then close the window. + if (terminal != null) { + terminal.setFont(oldFont); + terminal.setFontSize(oldFontSize); + } + getApplication().closeWindow(this); + return; + } + + // Pass to my parent + super.onKeypress(keypress); + } + + // ------------------------------------------------------------------------ + // TWindow ---------------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Draw me on screen. + */ + @Override + public void draw() { + super.draw(); + + int left = 34; + CellAttributes color = getTheme().getColor("ttext"); + drawBox(left, 6, left + 24, 14, color, color, 3, false); + putStringXY(left + 2, 6, i18n.getString("sample"), color); + for (int i = 7; i < 13; i++) { + hLineXY(left + 1, i, 22, GraphicsChars.HATCH, color); + } + + } + + // ------------------------------------------------------------------------ + // TFontChooserWindow ----------------------------------------------------- + // ------------------------------------------------------------------------ + +} diff --git a/src/jexer/TFontChooserWindow.properties b/src/jexer/TFontChooserWindow.properties new file mode 100644 index 0000000..de30c1a --- /dev/null +++ b/src/jexer/TFontChooserWindow.properties @@ -0,0 +1,15 @@ +windowTitle=Font +okButton=\ \ &OK\ \ +cancelButton=&Cancel +statusBar=Select Font Options + +fontName=Font name: +fontSize=Font size: +textAdjustX=X adjust: +textAdjustY=Y adjust: +textAdjustHeight=Height adjust: +textAdjustWidth=Width adjust: + +unavailable=Unavailable +builtInTerminus=Built-In Terminus +sample=\ Sample Window\ diff --git a/src/jexer/TSpinner.java b/src/jexer/TSpinner.java index 881e1a7..ba45f6a 100644 --- a/src/jexer/TSpinner.java +++ b/src/jexer/TSpinner.java @@ -89,7 +89,7 @@ public class TSpinner extends TWidget { */ private boolean mouseOnUpArrow(final TMouseEvent mouse) { if ((mouse.getY() == 0) - && (mouse.getX() == getWidth() - 1) + && (mouse.getX() == getWidth() - 2) ) { return true; } @@ -104,7 +104,7 @@ public class TSpinner extends TWidget { */ private boolean mouseOnDownArrow(final TMouseEvent mouse) { if ((mouse.getY() == 0) - && (mouse.getX() == getWidth() - 2) + && (mouse.getX() == getWidth() - 1) ) { return true; } diff --git a/src/jexer/TWidget.java b/src/jexer/TWidget.java index 4a4ba2c..e8cd1a0 100644 --- a/src/jexer/TWidget.java +++ b/src/jexer/TWidget.java @@ -1098,12 +1098,19 @@ public abstract class TWidget implements Comparable { return; } - if (child != activeChild) { - if (activeChild != null) { - activeChild.active = false; + if (children.size() == 1) { + if (children.get(0).enabled == true) { + child.active = true; + activeChild = child; + } + } else { + if (child != activeChild) { + if (activeChild != null) { + activeChild.active = false; + } + child.active = true; + activeChild = child; } - child.active = true; - activeChild = child; } } @@ -1114,6 +1121,14 @@ public abstract class TWidget implements Comparable { * isn't enabled, then the next enabled child will be activated. */ public final void activate(final int tabOrder) { + if (children.size() == 1) { + if (children.get(0).enabled == true) { + children.get(0).active = true; + activeChild = children.get(0); + } + return; + } + if (activeChild == null) { return; } @@ -1144,11 +1159,25 @@ public abstract class TWidget implements Comparable { */ public final void switchWidget(final boolean forward) { - // Only switch if there are multiple enabled widgets - if ((children.size() < 2) || (activeChild == null)) { + // No children: do nothing. + if (children.size() == 0) { + return; + } + + // If there is only one child, make it active if it is enabled. + if (children.size() == 1) { + if (children.get(0).enabled == true) { + activeChild = children.get(0); + activeChild.active = true; + } else { + children.get(0).active = false; + activeChild = null; + } return; } + // Two or more children: go forward or backward to the next enabled + // child. int tabOrder = activeChild.tabOrder; do { if (forward) { diff --git a/src/jexer/backend/Backend.java b/src/jexer/backend/Backend.java index 8bd1816..eaed7e6 100644 --- a/src/jexer/backend/Backend.java +++ b/src/jexer/backend/Backend.java @@ -96,4 +96,9 @@ public interface Backend { */ public void setListener(final Object listener); + /** + * Reload backend options from System properties. + */ + public void reloadOptions(); + } diff --git a/src/jexer/backend/ECMA48Terminal.java b/src/jexer/backend/ECMA48Terminal.java index 1dc3957..2afa81a 100644 --- a/src/jexer/backend/ECMA48Terminal.java +++ b/src/jexer/backend/ECMA48Terminal.java @@ -1085,23 +1085,7 @@ public class ECMA48Terminal extends LogicalScreen windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN, sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight()); - // Permit RGB colors only if externally requested. - if (System.getProperty("jexer.ECMA48.rgbColor") != null) { - if (System.getProperty("jexer.ECMA48.rgbColor").equals("true")) { - doRgbColor = true; - } else { - doRgbColor = false; - } - } - - // Pull the system properties for sixel output. - if (System.getProperty("jexer.ECMA48.sixel") != null) { - if (System.getProperty("jexer.ECMA48.sixel").equals("true")) { - sixel = true; - } else { - sixel = false; - } - } + reloadOptions(); // Spin up the input reader eventQueue = new LinkedList(); @@ -1187,23 +1171,7 @@ public class ECMA48Terminal extends LogicalScreen windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN, sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight()); - // Permit RGB colors only if externally requested - if (System.getProperty("jexer.ECMA48.rgbColor") != null) { - if (System.getProperty("jexer.ECMA48.rgbColor").equals("true")) { - doRgbColor = true; - } else { - doRgbColor = false; - } - } - - // Pull the system properties for sixel output. - if (System.getProperty("jexer.ECMA48.sixel") != null) { - if (System.getProperty("jexer.ECMA48.sixel").equals("true")) { - sixel = true; - } else { - sixel = false; - } - } + reloadOptions(); // Spin up the input reader eventQueue = new LinkedList(); @@ -1357,6 +1325,27 @@ public class ECMA48Terminal extends LogicalScreen this.listener = listener; } + /** + * Reload options from System properties. + */ + public void reloadOptions() { + // Permit RGB colors only if externally requested. + if (System.getProperty("jexer.ECMA48.rgbColor", + "false").equals("true") + ) { + doRgbColor = true; + } else { + doRgbColor = false; + } + + // Pull the system properties for sixel output. + if (System.getProperty("jexer.ECMA48.sixel", "true").equals("true")) { + sixel = true; + } else { + sixel = false; + } + } + // ------------------------------------------------------------------------ // Runnable --------------------------------------------------------------- // ------------------------------------------------------------------------ @@ -2745,11 +2734,19 @@ public class ECMA48Terminal extends LogicalScreen StringBuilder sb = new StringBuilder(); - assert (sixel == true); assert (cells != null); assert (cells.size() > 0); assert (cells.get(0).getImage() != null); + if (sixel == false) { + sb.append(normal()); + sb.append(gotoXY(x, y)); + for (int i = 0; i < cells.size(); i++) { + sb.append(' '); + } + return sb.toString(); + } + if (sixelCache == null) { sixelCache = new SixelCache(height * 10); } diff --git a/src/jexer/backend/GenericBackend.java b/src/jexer/backend/GenericBackend.java index 908be1e..fa72956 100644 --- a/src/jexer/backend/GenericBackend.java +++ b/src/jexer/backend/GenericBackend.java @@ -137,4 +137,11 @@ public abstract class GenericBackend implements Backend { terminal.setListener(listener); } + /** + * Reload backend options from System properties. + */ + public void reloadOptions() { + terminal.reloadOptions(); + } + } diff --git a/src/jexer/backend/MultiBackend.java b/src/jexer/backend/MultiBackend.java index 5e4d3ca..08591ed 100644 --- a/src/jexer/backend/MultiBackend.java +++ b/src/jexer/backend/MultiBackend.java @@ -162,6 +162,15 @@ public class MultiBackend implements Backend { } } + /** + * Reload backend options from System properties. + */ + public void reloadOptions() { + for (Backend backend: backends) { + backend.reloadOptions(); + } + } + // ------------------------------------------------------------------------ // MultiBackend ----------------------------------------------------------- // ------------------------------------------------------------------------ diff --git a/src/jexer/backend/SwingTerminal.java b/src/jexer/backend/SwingTerminal.java index bab8f82..6a3b203 100644 --- a/src/jexer/backend/SwingTerminal.java +++ b/src/jexer/backend/SwingTerminal.java @@ -164,11 +164,6 @@ public class SwingTerminal extends LogicalScreen */ private Map glyphCache; - /** - * If true, we were successful getting Terminus. - */ - private boolean gotTerminus = false; - /** * If true, we were successful at getting the font dimensions. */ @@ -194,6 +189,16 @@ public class SwingTerminal extends LogicalScreen */ private int textHeight = 1; + /** + * Width of a character cell in pixels, as reported by font. + */ + private int fontTextWidth = 1; + + /** + * Height of a character cell in pixels, as reported by font. + */ + private int fontTextHeight = 1; + /** * Descent of a character cell in pixels. */ @@ -209,6 +214,16 @@ public class SwingTerminal extends LogicalScreen */ private int textAdjustX = 0; + /** + * System-dependent height adjustment for text in the character cell. + */ + private int textAdjustHeight = 0; + + /** + * System-dependent width adjustment for text in the character cell. + */ + private int textAdjustWidth = 0; + /** * Top pixel absolute location. */ @@ -302,26 +317,7 @@ public class SwingTerminal extends LogicalScreen this.fontSize = fontSize; setDOSColors(); - - // Figure out my cursor style. - String cursorStyleString = System.getProperty( - "jexer.Swing.cursorStyle", "underline").toLowerCase(); - if (cursorStyleString.equals("underline")) { - cursorStyle = CursorStyle.UNDERLINE; - } else if (cursorStyleString.equals("outline")) { - cursorStyle = CursorStyle.OUTLINE; - } else if (cursorStyleString.equals("block")) { - cursorStyle = CursorStyle.BLOCK; - } - - // Pull the system property for triple buffering. - if (System.getProperty("jexer.Swing.tripleBuffer") != null) { - if (System.getProperty("jexer.Swing.tripleBuffer").equals("true")) { - SwingComponent.tripleBuffer = true; - } else { - SwingComponent.tripleBuffer = false; - } - } + reloadOptions(); try { SwingUtilities.invokeAndWait(new Runnable() { @@ -388,7 +384,7 @@ public class SwingTerminal extends LogicalScreen SwingTerminal.this.top = insets.top; // Load the font so that we can set sessionInfo. - getDefaultFont(); + setDefaultFont(); // Get the default cols x rows and set component size // accordingly. @@ -443,17 +439,7 @@ public class SwingTerminal extends LogicalScreen this.fontSize = fontSize; setDOSColors(); - - // Figure out my cursor style. - String cursorStyleString = System.getProperty( - "jexer.Swing.cursorStyle", "underline").toLowerCase(); - if (cursorStyleString.equals("underline")) { - cursorStyle = CursorStyle.UNDERLINE; - } else if (cursorStyleString.equals("outline")) { - cursorStyle = CursorStyle.OUTLINE; - } else if (cursorStyleString.equals("block")) { - cursorStyle = CursorStyle.BLOCK; - } + reloadOptions(); try { SwingUtilities.invokeAndWait(new Runnable() { @@ -519,7 +505,7 @@ public class SwingTerminal extends LogicalScreen SwingTerminal.this.top = insets.top; // Load the font so that we can set sessionInfo. - getDefaultFont(); + setDefaultFont(); // Get the default cols x rows and set component size // accordingly. @@ -643,6 +629,31 @@ public class SwingTerminal extends LogicalScreen this.listener = listener; } + /** + * Reload options from System properties. + */ + public void reloadOptions() { + // Figure out my cursor style. + String cursorStyleString = System.getProperty( + "jexer.Swing.cursorStyle", "underline").toLowerCase(); + if (cursorStyleString.equals("underline")) { + cursorStyle = CursorStyle.UNDERLINE; + } else if (cursorStyleString.equals("outline")) { + cursorStyle = CursorStyle.OUTLINE; + } else if (cursorStyleString.equals("block")) { + cursorStyle = CursorStyle.BLOCK; + } + + // Pull the system property for triple buffering. + if (System.getProperty("jexer.Swing.tripleBuffer", + "true").equals("true") + ) { + SwingComponent.tripleBuffer = true; + } else { + SwingComponent.tripleBuffer = false; + } + } + // ------------------------------------------------------------------------ // SwingTerminal ---------------------------------------------------------- // ------------------------------------------------------------------------ @@ -729,25 +740,35 @@ public class SwingTerminal extends LogicalScreen * @param font the new font */ public void setFont(final Font font) { - this.font = font; - getFontDimensions(); - swing.setFont(font); - glyphCacheBlink = new HashMap(); - glyphCache = new HashMap(); - resizeToScreen(); + synchronized (this) { + this.font = font; + getFontDimensions(); + swing.setFont(font); + glyphCacheBlink = new HashMap(); + glyphCache = new HashMap(); + resizeToScreen(); + } + } + + /** + * Get the font this screen was last set to. + * + * @return the font + */ + public Font getFont() { + return font; } /** * Set the font to Terminus, the best all-around font for both CP437 and * ISO8859-1. */ - public void getDefaultFont() { + public void setDefaultFont() { try { ClassLoader loader = Thread.currentThread().getContextClassLoader(); InputStream in = loader.getResourceAsStream(FONTFILE); Font terminusRoot = Font.createFont(Font.TRUETYPE_FONT, in); Font terminus = terminusRoot.deriveFont(Font.PLAIN, fontSize); - gotTerminus = true; font = terminus; } catch (java.awt.FontFormatException e) { e.printStackTrace(); @@ -760,6 +781,100 @@ public class SwingTerminal extends LogicalScreen setFont(font); } + /** + * Get the X text adjustment. + * + * @return X text adjustment + */ + public int getTextAdjustX() { + return textAdjustX; + } + + /** + * Set the X text adjustment. + * + * @param textAdjustX the X text adjustment + */ + public void setTextAdjustX(final int textAdjustX) { + synchronized (this) { + this.textAdjustX = textAdjustX; + glyphCacheBlink = new HashMap(); + glyphCache = new HashMap(); + clearPhysical(); + } + } + + /** + * Get the Y text adjustment. + * + * @return Y text adjustment + */ + public int getTextAdjustY() { + return textAdjustY; + } + + /** + * Set the Y text adjustment. + * + * @param textAdjustY the Y text adjustment + */ + public void setTextAdjustY(final int textAdjustY) { + synchronized (this) { + this.textAdjustY = textAdjustY; + glyphCacheBlink = new HashMap(); + glyphCache = new HashMap(); + clearPhysical(); + } + } + + /** + * Get the height text adjustment. + * + * @return height text adjustment + */ + public int getTextAdjustHeight() { + return textAdjustHeight; + } + + /** + * Set the height text adjustment. + * + * @param textAdjustHeight the height text adjustment + */ + public void setTextAdjustHeight(final int textAdjustHeight) { + synchronized (this) { + this.textAdjustHeight = textAdjustHeight; + textHeight = fontTextHeight + textAdjustHeight; + glyphCacheBlink = new HashMap(); + glyphCache = new HashMap(); + clearPhysical(); + } + } + + /** + * Get the width text adjustment. + * + * @return width text adjustment + */ + public int getTextAdjustWidth() { + return textAdjustWidth; + } + + /** + * Set the width text adjustment. + * + * @param textAdjustWidth the width text adjustment + */ + public void setTextAdjustWidth(final int textAdjustWidth) { + synchronized (this) { + this.textAdjustWidth = textAdjustWidth; + textWidth = fontTextWidth + textAdjustWidth; + glyphCacheBlink = new HashMap(); + glyphCache = new HashMap(); + clearPhysical(); + } + } + /** * Convert a CellAttributes foreground color to an Swing Color. * @@ -855,13 +970,11 @@ public class SwingTerminal extends LogicalScreen } /** - * Figure out what textAdjustX and textAdjustY should be, based on the - * location of a vertical bar (to find textAdjustY) and a horizontal bar - * (to find textAdjustX). - * - * @return true if textAdjustX and textAdjustY were guessed at correctly + * Figure out what textAdjustX, textAdjustY, textAdjustHeight, and + * textAdjustWidth should be, based on the location of a vertical bar and + * a horizontal bar. */ - private boolean getFontAdjustments() { + private void getFontAdjustments() { BufferedImage image = null; // What SHOULD happen is that the topmost/leftmost white pixel is at @@ -871,66 +984,73 @@ public class SwingTerminal extends LogicalScreen Graphics2D gr2 = null; int gr2x = 3; int gr2y = 3; - image = new BufferedImage(textWidth * 2, textHeight * 2, + image = new BufferedImage(fontTextWidth * 2, fontTextHeight * 2, BufferedImage.TYPE_INT_ARGB); gr2 = image.createGraphics(); gr2.setFont(swing.getFont()); gr2.setColor(java.awt.Color.BLACK); - gr2.fillRect(0, 0, textWidth * 2, textHeight * 2); + gr2.fillRect(0, 0, fontTextWidth * 2, fontTextHeight * 2); gr2.setColor(java.awt.Color.WHITE); char [] chars = new char[1]; + chars[0] = jexer.bits.GraphicsChars.SINGLE_BAR; + gr2.drawChars(chars, 0, 1, gr2x, gr2y + fontTextHeight - maxDescent); chars[0] = jexer.bits.GraphicsChars.VERTICAL_BAR; - gr2.drawChars(chars, 0, 1, gr2x, gr2y + textHeight - maxDescent); + gr2.drawChars(chars, 0, 1, gr2x, gr2y + fontTextHeight - maxDescent); gr2.dispose(); - for (int x = 0; x < textWidth; x++) { - for (int y = 0; y < textHeight; y++) { + int top = fontTextHeight * 2; + int bottom = -1; + int left = fontTextWidth * 2; + int right = -1; + textAdjustX = 0; + textAdjustY = 0; + textAdjustHeight = 0; + textAdjustWidth = 0; - /* - System.err.println("X: " + x + " Y: " + y + " " + - image.getRGB(x, y)); - */ - - if ((image.getRGB(x, y) & 0xFFFFFF) != 0) { - textAdjustY = (gr2y - y); - - // System.err.println("textAdjustY: " + textAdjustY); - x = textWidth; - break; - } - } - } - - gr2 = image.createGraphics(); - gr2.setFont(swing.getFont()); - gr2.setColor(java.awt.Color.BLACK); - gr2.fillRect(0, 0, textWidth * 2, textHeight * 2); - gr2.setColor(java.awt.Color.WHITE); - chars[0] = jexer.bits.GraphicsChars.SINGLE_BAR; - gr2.drawChars(chars, 0, 1, gr2x, gr2y + textHeight - maxDescent); - gr2.dispose(); - - for (int x = 0; x < textWidth; x++) { - for (int y = 0; y < textHeight; y++) { + for (int x = 0; x < fontTextWidth * 2; x++) { + for (int y = 0; y < fontTextHeight * 2; y++) { /* - System.err.println("X: " + x + " Y: " + y + " " + + System.err.println("H X: " + x + " Y: " + y + " " + image.getRGB(x, y)); - */ + */ if ((image.getRGB(x, y) & 0xFFFFFF) != 0) { - textAdjustX = (gr2x - x); - - // System.err.println("textAdjustX: " + textAdjustX); - return true; + // Pixel is present. + if (y < top) { + top = y; + } + if (y > bottom) { + bottom = y; + } + if (x < left) { + left = x; + } + if (x > right) { + right = x; + } } } } + if (left < right) { + textAdjustX = (gr2x - left); + textAdjustWidth = fontTextWidth - (right - left + 1); + } + if (top < bottom) { + textAdjustY = (gr2y - top); + textAdjustHeight = fontTextHeight - (bottom - top + 1); + } + // System.err.println("top " + top + " bottom " + bottom); + // System.err.println("left " + left + " right " + right); - // Something weird happened, don't rely on this function. - // System.err.println("getFontAdjustments: false"); - return false; + // Special case: do not believe fonts that claim to be wider than + // they are tall. + if (fontTextWidth >= fontTextHeight) { + textAdjustX = 0; + textAdjustWidth = 0; + fontTextWidth = fontTextHeight / 2; + } } /** @@ -958,26 +1078,16 @@ public class SwingTerminal extends LogicalScreen maxDescent = fm.getMaxDescent(); Rectangle2D bounds = fm.getMaxCharBounds(gr); int leading = fm.getLeading(); - textWidth = (int)Math.round(bounds.getWidth()); - // textHeight = (int)Math.round(bounds.getHeight()) - maxDescent; + fontTextWidth = (int)Math.round(bounds.getWidth()); + // fontTextHeight = (int)Math.round(bounds.getHeight()) - maxDescent; // This produces the same number, but works better for ugly // monospace. - textHeight = fm.getMaxAscent() + maxDescent - leading; + fontTextHeight = fm.getMaxAscent() + maxDescent - leading; - if (gotTerminus == true) { - textHeight++; - } - - if (getFontAdjustments() == false) { - // We were unable to programmatically determine textAdjustX and - // textAdjustY, so try some guesses based on VM vendor. - String runtime = System.getProperty("java.runtime.name"); - if ((runtime != null) && (runtime.contains("Java(TM)"))) { - textAdjustY = -1; - textAdjustX = 0; - } - } + getFontAdjustments(); + textHeight = fontTextHeight + textAdjustHeight; + textWidth = fontTextWidth + textAdjustWidth; if (sessionInfo != null) { sessionInfo.setTextCellDimensions(textWidth, textHeight); diff --git a/src/jexer/backend/TWindowBackend.java b/src/jexer/backend/TWindowBackend.java index 20b9d7d..0a04233 100644 --- a/src/jexer/backend/TWindowBackend.java +++ b/src/jexer/backend/TWindowBackend.java @@ -427,6 +427,13 @@ public class TWindowBackend extends TWindow implements Backend { this.listener = listener; } + /** + * Reload backend options from System properties. + */ + public void reloadOptions() { + // NOP + } + // ------------------------------------------------------------------------ // TWindowBackend --------------------------------------------------------- // ------------------------------------------------------------------------ diff --git a/src/jexer/backend/TerminalReader.java b/src/jexer/backend/TerminalReader.java index 8edadbf..32033e0 100644 --- a/src/jexer/backend/TerminalReader.java +++ b/src/jexer/backend/TerminalReader.java @@ -66,4 +66,9 @@ public interface TerminalReader { */ public void setListener(final Object listener); + /** + * Reload options from System properties. + */ + public void reloadOptions(); + } diff --git a/src/jexer/demos/DemoApplication.java b/src/jexer/demos/DemoApplication.java index 18d47f3..5eb89ae 100644 --- a/src/jexer/demos/DemoApplication.java +++ b/src/jexer/demos/DemoApplication.java @@ -203,6 +203,7 @@ public class DemoApplication extends TApplication { new DemoMainWindow(this); // Add the menus + addToolMenu(); addFileMenu(); addEditMenu(); diff --git a/src/jexer/menu/TMenu.java b/src/jexer/menu/TMenu.java index bfda602..dbbea82 100644 --- a/src/jexer/menu/TMenu.java +++ b/src/jexer/menu/TMenu.java @@ -58,45 +58,48 @@ public class TMenu extends TWindow { // Reserved menu item IDs public static final int MID_UNUSED = -1; + // Tools menu + public static final int MID_REPAINT = 1; + public static final int MID_VIEW_IMAGE = 2; + public static final int MID_CHANGE_FONT = 3; + // File menu - public static final int MID_EXIT = 1; + public static final int MID_NEW = 10; + public static final int MID_EXIT = 11; public static final int MID_QUIT = MID_EXIT; - public static final int MID_OPEN_FILE = 2; - public static final int MID_SHELL = 3; + public static final int MID_OPEN_FILE = 12; + public static final int MID_SHELL = 13; // Edit menu - public static final int MID_CUT = 10; - public static final int MID_COPY = 11; - public static final int MID_PASTE = 12; - public static final int MID_CLEAR = 13; + public static final int MID_CUT = 20; + public static final int MID_COPY = 21; + public static final int MID_PASTE = 22; + public static final int MID_CLEAR = 23; // Search menu - public static final int MID_FIND = 20; - public static final int MID_REPLACE = 21; - public static final int MID_SEARCH_AGAIN = 22; - public static final int MID_GOTO_LINE = 23; + public static final int MID_FIND = 30; + public static final int MID_REPLACE = 31; + public static final int MID_SEARCH_AGAIN = 32; + public static final int MID_GOTO_LINE = 33; // Window menu - public static final int MID_TILE = 30; - public static final int MID_CASCADE = 31; - public static final int MID_CLOSE_ALL = 32; - public static final int MID_WINDOW_MOVE = 33; - public static final int MID_WINDOW_ZOOM = 34; - public static final int MID_WINDOW_NEXT = 35; - public static final int MID_WINDOW_PREVIOUS = 36; - public static final int MID_WINDOW_CLOSE = 37; + public static final int MID_TILE = 40; + public static final int MID_CASCADE = 41; + public static final int MID_CLOSE_ALL = 42; + public static final int MID_WINDOW_MOVE = 43; + public static final int MID_WINDOW_ZOOM = 44; + public static final int MID_WINDOW_NEXT = 45; + public static final int MID_WINDOW_PREVIOUS = 46; + public static final int MID_WINDOW_CLOSE = 47; // Help menu - public static final int MID_HELP_CONTENTS = 40; - public static final int MID_HELP_INDEX = 41; - public static final int MID_HELP_SEARCH = 42; - public static final int MID_HELP_PREVIOUS = 43; - public static final int MID_HELP_HELP = 44; - public static final int MID_HELP_ACTIVE_FILE = 45; - public static final int MID_ABOUT = 46; - - // Other - public static final int MID_REPAINT = 50; + public static final int MID_HELP_CONTENTS = 50; + public static final int MID_HELP_INDEX = 51; + public static final int MID_HELP_SEARCH = 52; + public static final int MID_HELP_PREVIOUS = 53; + public static final int MID_HELP_HELP = 54; + public static final int MID_HELP_ACTIVE_FILE = 55; + public static final int MID_ABOUT = 56; // ------------------------------------------------------------------------ // Variables -------------------------------------------------------------- @@ -508,6 +511,22 @@ public class TMenu extends TWindow { switch (id) { + case MID_REPAINT: + label = i18n.getString("menuRepaintDesktop"); + break; + + case MID_VIEW_IMAGE: + label = i18n.getString("menuViewImage"); + break; + + case MID_CHANGE_FONT: + label = i18n.getString("menuChangeFont"); + break; + + case MID_NEW: + label = i18n.getString("menuNew"); + break; + case MID_EXIT: label = i18n.getString("menuExit"); key = kbAltX; @@ -608,10 +627,6 @@ public class TMenu extends TWindow { label = i18n.getString("menuHelpAbout"); break; - case MID_REPAINT: - label = i18n.getString("menuRepaintDesktop"); - break; - default: throw new IllegalArgumentException("Invalid menu ID: " + id); } diff --git a/src/jexer/menu/TMenu.properties b/src/jexer/menu/TMenu.properties index d42157c..0ce4cde 100644 --- a/src/jexer/menu/TMenu.properties +++ b/src/jexer/menu/TMenu.properties @@ -1,3 +1,4 @@ +menuNew=&New menuExit=E&xit menuShell=O&S Shell menuOpen=&Open @@ -26,3 +27,5 @@ menuHelpActive=Active &file... menuHelpAbout=&About... menuRepaintDesktop=&Repaint desktop +menuViewImage=&Open image... +menuChangeFont=Change &font... -- 2.27.0