From e3dfbd233442a877d5efa1bc177c3d357771e5cb Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Sun, 22 Mar 2015 18:25:14 -0400 Subject: [PATCH] double buffer swing --- README.md | 27 +- src/jexer/TApplication.java | 8 +- src/jexer/backend/SwingBackend.java | 2 +- src/jexer/demos/Demo1.java | 453 +----------------------- src/jexer/demos/DemoApplication.java | 81 +++++ src/jexer/demos/DemoCheckboxWindow.java | 87 +++++ src/jexer/demos/DemoMainWindow.java | 197 +++++++++++ src/jexer/demos/DemoMsgBoxWindow.java | 163 +++++++++ src/jexer/demos/DemoTextWindow.java | 98 +++++ src/jexer/io/SwingScreen.java | 71 +++- src/jexer/io/SwingTerminal.java | 8 - 11 files changed, 700 insertions(+), 495 deletions(-) create mode 100644 src/jexer/demos/DemoApplication.java create mode 100644 src/jexer/demos/DemoCheckboxWindow.java create mode 100644 src/jexer/demos/DemoMainWindow.java create mode 100644 src/jexer/demos/DemoMsgBoxWindow.java create mode 100644 src/jexer/demos/DemoTextWindow.java diff --git a/README.md b/README.md index 1e1bb54..d014852 100644 --- a/README.md +++ b/README.md @@ -93,9 +93,6 @@ Some arbitrary design decisions had to be made when either the obviously expected behavior did not happen or when a specification was ambiguous. This section describes such issues. - TTerminalWindow - --------------- - - TTerminalWindow will hang on input from the remote if the TApplication is exited before the TTerminalWindow's process has closed on its own. This is due to a Java limitation/interaction @@ -115,20 +112,15 @@ ambiguous. This section describes such issues. checking for a tty: script launches $SHELL in a pseudo-tty. This works on Linux but might not on other Posix-y platforms. - ECMA48 Backend - -------------- - - - Java's InputStreamReader requires a valid UTF-8 stream. The - default X10 encoding for mouse coordinates outside (160,94) can - corrupt that stream, at best putting garbage keyboard events in - the input queue but at worst causing the backend reader thread to - throw an Exception and exit and make the entire UI unusable. - Mouse support therefore requires a terminal that can deliver - either UTF-8 coordinates (1005 mode) or SGR coordinates (1006 - mode). Most modern terminals can do this. - - Use of 'stty' - ------------- + - Java's InputStreamReader as used by the ECMA48 backend requires a + valid UTF-8 stream. The default X10 encoding for mouse + coordinates outside (160,94) can corrupt that stream, at best + putting garbage keyboard events in the input queue but at worst + causing the backend reader thread to throw an Exception and exit + and make the entire UI unusable. Mouse support therefore requires + a terminal that can deliver either UTF-8 coordinates (1005 mode) + or SGR coordinates (1006 mode). Most modern terminals can do + this. - jexer.session.TTYSession calls 'stty size' once every second to check the current window size, performing the same function as @@ -187,6 +179,7 @@ Many tasks remain before calling this version 1.0: 0.0.5: BUG HUNT - TSubMenu keyboard mnemonic not working +- Swing performance. Even with double buffering it isn't great. 0.1.0: BETA RELEASE diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index 8be6af5..72f8078 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -691,9 +691,11 @@ public class TApplication implements Runnable { if (!repaint && ((mouseX == oldMouseX) && (mouseY == oldMouseY)) ) { - // Never sleep longer than 100 millis, to get windows with - // background tasks an opportunity to update the display. - timeout = getSleepTime(100); + // Never sleep longer than 50 millis. We need time for + // windows with background tasks to update the display, and + // still flip buffers reasonably quickly in + // backend.flushPhysical(). + timeout = getSleepTime(50); // See if there are any definitely events waiting to be // processed. If so, do not wait -- either there is I/O diff --git a/src/jexer/backend/SwingBackend.java b/src/jexer/backend/SwingBackend.java index 3f95866..0f5b5bc 100644 --- a/src/jexer/backend/SwingBackend.java +++ b/src/jexer/backend/SwingBackend.java @@ -90,7 +90,7 @@ public final class SwingBackend extends Backend { */ @Override public void shutdown() { - terminal.shutdown(); + ((SwingScreen) screen).shutdown(); } } diff --git a/src/jexer/demos/Demo1.java b/src/jexer/demos/Demo1.java index f8ede85..53c205f 100644 --- a/src/jexer/demos/Demo1.java +++ b/src/jexer/demos/Demo1.java @@ -35,457 +35,8 @@ import jexer.event.*; import jexer.menu.*; /** - * This window demonstates the TText, THScroller, and TVScroller widgets. - */ -class DemoTextWindow extends TWindow { - - /** - * Hang onto my TText so I can resize it with the window. - */ - private TText textField; - - /** - * Public constructor. - * - * @param parent the main application - */ - public DemoTextWindow(final TApplication parent) { - super(parent, "Text Areas", 0, 0, 44, 20, RESIZABLE); - - textField = addText( -"This is an example of a reflowable text field. Some example text follows.\n" + -"\n" + -"This library implements a text-based windowing system loosely\n" + -"reminiscient of Borland's [Turbo\n" + -"Vision](http://en.wikipedia.org/wiki/Turbo_Vision) library. For those\n" + -"wishing to use the actual C++ Turbo Vision library, see [Sergio\n" + -"Sigala's updated version](http://tvision.sourceforge.net/) that runs\n" + -"on many more platforms.\n" + -"\n" + -"Currently the only console platform supported is Posix (tested on\n" + -"Linux). Input/output is handled through terminal escape sequences\n" + -"generated by the library itself: ncurses is not required or linked to. \n" + -"xterm mouse tracking using UTF8 coordinates is supported.\n" + -"\n" + -"This library is licensed LGPL (\"GNU Lesser General Public License\")\n" + -"version 3 or greater. See the file COPYING for the full license text,\n" + -"which includes both the GPL v3 and the LGPL supplemental terms.\n" + -"\n", - 1, 1, 40, 16); - } - - /** - * Handle window/screen resize events. - * - * @param event resize event - */ - @Override - public void onResize(final TResizeEvent event) { - if (event.getType() == TResizeEvent.Type.WIDGET) { - // Resize the text field - textField.setWidth(event.getWidth() - 4); - textField.setHeight(event.getHeight() - 4); - textField.reflow(); - return; - } - - // Pass to children instead - for (TWidget widget: getChildren()) { - widget.onResize(event); - } - } -} - -/** - * This window demonstates the TRadioGroup, TRadioButton, and TCheckbox - * widgets. - */ -class DemoCheckboxWindow extends TWindow { - - /** - * Constructor. - * - * @param parent the main application - */ - DemoCheckboxWindow(final TApplication parent) { - this(parent, CENTERED | RESIZABLE); - } - - /** - * Constructor. - * - * @param parent the main application - * @param flags bitmask of MODAL, CENTERED, or RESIZABLE - */ - DemoCheckboxWindow(final TApplication parent, final int flags) { - // Construct a demo window. X and Y don't matter because it will be - // centered on screen. - super(parent, "Radiobuttons and Checkboxes", 0, 0, 60, 15, flags); - - int row = 1; - - // Add some widgets - addLabel("Check box example 1", 1, row); - addCheckbox(35, row++, "Checkbox 1", false); - addLabel("Check box example 2", 1, row); - addCheckbox(35, row++, "Checkbox 2", true); - row += 2; - - TRadioGroup group = addRadioGroup(1, row, "Group 1"); - group.addRadioButton("Radio option 1"); - group.addRadioButton("Radio option 2"); - group.addRadioButton("Radio option 3"); - - addButton("&Close Window", (getWidth() - 14) / 2, getHeight() - 4, - new TAction() { - public void DO() { - DemoCheckboxWindow.this.getApplication() - .closeWindow(DemoCheckboxWindow.this); - } - } - ); - } - -} - - -/** - * This window demonstates the TMessageBox and TInputBox widgets. - */ -class DemoMsgBoxWindow extends TWindow { - - /** - * Constructor. - * - * @param parent the main application - */ - DemoMsgBoxWindow(final TApplication parent) { - this(parent, TWindow.CENTERED | TWindow.RESIZABLE); - } - - /** - * Constructor. - * - * @param parent the main application - * @param flags bitmask of MODAL, CENTERED, or RESIZABLE - */ - DemoMsgBoxWindow(final TApplication parent, final int flags) { - // Construct a demo window. X and Y don't matter because it - // will be centered on screen. - super(parent, "Message Boxes", 0, 0, 60, 15, flags); - - int row = 1; - - // Add some widgets - addLabel("Default OK message box", 1, row); - addButton("Open O&K MB", 35, row, - new TAction() { - public void DO() { - getApplication().messageBox("OK MessageBox", -"This is an example of a OK MessageBox. This is the\n" + -"default MessageBox.\n" + -"\n" + -"Note that the MessageBox text can span multiple\n" + -"lines.\n" + -"\n" + -"The default result (if someone hits the top-left\n" + -"close button) is OK.\n", - TMessageBox.Type.OK); - } - } - ); - row += 2; - - addLabel("OK/Cancel message box", 1, row); - addButton("O&pen OKC MB", 35, row, - new TAction() { - public void DO() { - getApplication().messageBox("OK/Cancel MessageBox", -"This is an example of a OK/Cancel MessageBox.\n" + -"\n" + -"Note that the MessageBox text can span multiple\n" + -"lines.\n" + -"\n" + -"The default result (if someone hits the top-left\n" + -"close button) is CANCEL.\n", - TMessageBox.Type.OKCANCEL); - } - } - ); - row += 2; - - addLabel("Yes/No message box", 1, row); - addButton("Open &YN MB", 35, row, - new TAction() { - public void DO() { - getApplication().messageBox("Yes/No MessageBox", -"This is an example of a Yes/No MessageBox.\n" + -"\n" + -"Note that the MessageBox text can span multiple\n" + -"lines.\n" + -"\n" + -"The default result (if someone hits the top-left\n" + -"close button) is NO.\n", - TMessageBox.Type.YESNO); - } - } - ); - row += 2; - - addLabel("Yes/No/Cancel message box", 1, row); - addButton("Ope&n YNC MB", 35, row, - new TAction() { - public void DO() { - getApplication().messageBox("Yes/No/Cancel MessageBox", -"This is an example of a Yes/No/Cancel MessageBox.\n" + -"\n" + -"Note that the MessageBox text can span multiple\n" + -"lines.\n" + -"\n" + -"The default result (if someone hits the top-left\n" + -"close button) is CANCEL.\n", - TMessageBox.Type.YESNOCANCEL); - } - } - ); - row += 2; - - addLabel("Input box", 1, row); - addButton("Open &input box", 35, row, - new TAction() { - public void DO() { - TInputBox in = getApplication().inputBox("Input Box", -"This is an example of an InputBox.\n" + -"\n" + -"Note that the InputBox text can span multiple\n" + -"lines.\n", - "some input text"); - getApplication().messageBox("Your InputBox Answer", - "You entered: " + in.getText()); - } - } - ); - - addButton("&Close Window", (getWidth() - 14) / 2, getHeight() - 4, - new TAction() { - public void DO() { - getApplication().closeWindow(DemoMsgBoxWindow.this); - } - } - ); - } -} - -/** - * This is the main "demo" application window. It makes use of the TTimer, - * TProgressBox, TLabel, TButton, and TField widgets. - */ -class DemoMainWindow extends TWindow { - - // Timer that increments a number. - private TTimer timer; - - // Timer label is updated with timer ticks. - TLabel timerLabel; - - /** - * We need to override onClose so that the timer will no longer be called - * after we close the window. TTimers currently are completely unaware - * of the rest of the UI classes. - */ - @Override - public void onClose() { - getApplication().removeTimer(timer); - } - - /** - * Construct demo window. It will be centered on screen. - * - * @param parent the main application - */ - public DemoMainWindow(final TApplication parent) { - this(parent, CENTERED | RESIZABLE); - } - - // These are used by the timer loop. They have to be at class scope so - // that they can be accessed by the anonymous TAction class. - int timerI = 0; - TProgressBar progressBar; - - /** - * Constructor. - * - * @param parent the main application - * @param flags bitmask of MODAL, CENTERED, or RESIZABLE - */ - private DemoMainWindow(final TApplication parent, final int flags) { - // Construct a demo window. X and Y don't matter because it will be - // centered on screen. - super(parent, "Demo Window", 0, 0, 60, 23, flags); - - int row = 1; - - // Add some widgets - if (!isModal()) { - addLabel("Message Boxes", 1, row); - addButton("&MessageBoxes", 35, row, - new TAction() { - public void DO() { - new DemoMsgBoxWindow(getApplication()); - } - } - ); - } - row += 2; - - addLabel("Open me as modal", 1, row); - addButton("W&indow", 35, row, - new TAction() { - public void DO() { - new DemoMainWindow(getApplication(), MODAL); - } - } - ); - - row += 2; - - addLabel("Variable-width text field:", 1, row); - addField(35, row++, 15, false, "Field text"); - addLabel("Fixed-width text field:", 1, row); - addField(35, row++, 15, true); - addLabel("Variable-width password:", 1, row); - addPasswordField(35, row++, 15, false); - addLabel("Fixed-width password:", 1, row); - addPasswordField(35, row++, 15, true, "hunter2"); - row += 2; - - if (!isModal()) { - addLabel("Radio buttons and checkboxes", 1, row); - addButton("&Checkboxes", 35, row, - new TAction() { - public void DO() { - new DemoCheckboxWindow(getApplication()); - } - } - ); - } - row += 2; - - /* - if (!isModal()) { - addLabel("Editor window", 1, row); - addButton("Edito&r", 35, row, - { - new TEditor(application, 0, 0, 60, 15); - } - ); - } - row += 2; - */ - - if (!isModal()) { - addLabel("Text areas", 1, row); - addButton("&Text", 35, row, - new TAction() { - public void DO() { - new DemoTextWindow(getApplication()); - } - } - ); - } - row += 2; - - /* - if (!isModal()) { - addLabel("Tree views", 1, row); - addButton("Tree&View", 35, row, - { - new DemoTreeViewWindow(application); - } - ); - } - row += 2; - */ - - if (!isModal()) { - addLabel("Terminal", 1, row); - addButton("Termi&nal", 35, row, - new TAction() { - public void DO() { - getApplication().openTerminal(0, 0); - } - } - ); - } - row += 2; - - progressBar = addProgressBar(1, row, 22, 0); - row++; - timerLabel = addLabel("Timer", 1, row); - timer = getApplication().addTimer(250, true, - new TAction() { - - public void DO() { - timerLabel.setLabel(String.format("Timer: %d", timerI)); - timerLabel.setWidth(timerLabel.getLabel().length()); - if (timerI < 100) { - timerI++; - } - progressBar.setValue(timerI); - } - } - ); - } -} - -/** - * The demo application itself. - */ -class DemoApplication extends TApplication { - - /** - * Public constructor. - * - * @param backendType one of the TApplication.BackendType values - * @throws Exception if TApplication can't instantiate the Backend. - */ - public DemoApplication(BackendType backendType) throws Exception { - super(backendType); - new DemoMainWindow(this); - - // Add the menus - addFileMenu(); - addEditMenu(); - - TMenu demoMenu = addMenu("&Demo"); - TMenuItem item = demoMenu.addItem(2000, "&Checkable"); - item.setCheckable(true); - item = demoMenu.addItem(2001, "Disabled"); - item.setEnabled(false); - item = demoMenu.addItem(2002, "&Normal"); - TSubMenu subMenu = demoMenu.addSubMenu("Sub-&Menu"); - item = demoMenu.addItem(2010, "N&ormal A&&D"); - - item = subMenu.addItem(2000, "&Checkable (sub)"); - item.setCheckable(true); - item = subMenu.addItem(2001, "Disabled (sub)"); - item.setEnabled(false); - item = subMenu.addItem(2002, "&Normal (sub)"); - - subMenu = subMenu.addSubMenu("Sub-&Menu"); - item = subMenu.addItem(2000, "&Checkable (sub)"); - item.setCheckable(true); - item = subMenu.addItem(2001, "Disabled (sub)"); - item.setEnabled(false); - item = subMenu.addItem(2002, "&Normal (sub)"); - - addWindowMenu(); - - } -} - -/** - * This class provides a simple demonstration of Jexer's capabilities. + * This class is the main driver for a simple demonstration of Jexer's + * capabilities. */ public class Demo1 { /** diff --git a/src/jexer/demos/DemoApplication.java b/src/jexer/demos/DemoApplication.java new file mode 100644 index 0000000..4c5ba31 --- /dev/null +++ b/src/jexer/demos/DemoApplication.java @@ -0,0 +1,81 @@ +/* + * Jexer - Java Text User Interface + * + * License: LGPLv3 or later + * + * This module is licensed under the GNU Lesser General Public License + * Version 3. Please see the file "COPYING" in this directory for more + * information about the GNU Lesser General Public License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer.demos; + +import jexer.*; +import jexer.event.*; +import jexer.menu.*; + +/** + * The demo application itself. + */ +class DemoApplication extends TApplication { + + /** + * Public constructor. + * + * @param backendType one of the TApplication.BackendType values + * @throws Exception if TApplication can't instantiate the Backend. + */ + public DemoApplication(BackendType backendType) throws Exception { + super(backendType); + new DemoMainWindow(this); + + // Add the menus + addFileMenu(); + addEditMenu(); + + TMenu demoMenu = addMenu("&Demo"); + TMenuItem item = demoMenu.addItem(2000, "&Checkable"); + item.setCheckable(true); + item = demoMenu.addItem(2001, "Disabled"); + item.setEnabled(false); + item = demoMenu.addItem(2002, "&Normal"); + TSubMenu subMenu = demoMenu.addSubMenu("Sub-&Menu"); + item = demoMenu.addItem(2010, "N&ormal A&&D"); + + item = subMenu.addItem(2000, "&Checkable (sub)"); + item.setCheckable(true); + item = subMenu.addItem(2001, "Disabled (sub)"); + item.setEnabled(false); + item = subMenu.addItem(2002, "&Normal (sub)"); + + subMenu = subMenu.addSubMenu("Sub-&Menu"); + item = subMenu.addItem(2000, "&Checkable (sub)"); + item.setCheckable(true); + item = subMenu.addItem(2001, "Disabled (sub)"); + item.setEnabled(false); + item = subMenu.addItem(2002, "&Normal (sub)"); + + addWindowMenu(); + + } +} diff --git a/src/jexer/demos/DemoCheckboxWindow.java b/src/jexer/demos/DemoCheckboxWindow.java new file mode 100644 index 0000000..8f92bff --- /dev/null +++ b/src/jexer/demos/DemoCheckboxWindow.java @@ -0,0 +1,87 @@ +/* + * Jexer - Java Text User Interface + * + * License: LGPLv3 or later + * + * This module is licensed under the GNU Lesser General Public License + * Version 3. Please see the file "COPYING" in this directory for more + * information about the GNU Lesser General Public License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer.demos; + +import jexer.*; +import jexer.event.*; +import jexer.menu.*; + +/** + * This window demonstates the TRadioGroup, TRadioButton, and TCheckbox + * widgets. + */ +class DemoCheckboxWindow extends TWindow { + + /** + * Constructor. + * + * @param parent the main application + */ + DemoCheckboxWindow(final TApplication parent) { + this(parent, CENTERED | RESIZABLE); + } + + /** + * Constructor. + * + * @param parent the main application + * @param flags bitmask of MODAL, CENTERED, or RESIZABLE + */ + DemoCheckboxWindow(final TApplication parent, final int flags) { + // Construct a demo window. X and Y don't matter because it will be + // centered on screen. + super(parent, "Radiobuttons and Checkboxes", 0, 0, 60, 15, flags); + + int row = 1; + + // Add some widgets + addLabel("Check box example 1", 1, row); + addCheckbox(35, row++, "Checkbox 1", false); + addLabel("Check box example 2", 1, row); + addCheckbox(35, row++, "Checkbox 2", true); + row += 2; + + TRadioGroup group = addRadioGroup(1, row, "Group 1"); + group.addRadioButton("Radio option 1"); + group.addRadioButton("Radio option 2"); + group.addRadioButton("Radio option 3"); + + addButton("&Close Window", (getWidth() - 14) / 2, getHeight() - 4, + new TAction() { + public void DO() { + DemoCheckboxWindow.this.getApplication() + .closeWindow(DemoCheckboxWindow.this); + } + } + ); + } + +} diff --git a/src/jexer/demos/DemoMainWindow.java b/src/jexer/demos/DemoMainWindow.java new file mode 100644 index 0000000..cff9bbd --- /dev/null +++ b/src/jexer/demos/DemoMainWindow.java @@ -0,0 +1,197 @@ +/* + * Jexer - Java Text User Interface + * + * License: LGPLv3 or later + * + * This module is licensed under the GNU Lesser General Public License + * Version 3. Please see the file "COPYING" in this directory for more + * information about the GNU Lesser General Public License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer.demos; + +import jexer.*; +import jexer.event.*; +import jexer.menu.*; + +/** + * This is the main "demo" application window. It makes use of the TTimer, + * TProgressBox, TLabel, TButton, and TField widgets. + */ +class DemoMainWindow extends TWindow { + + // Timer that increments a number. + private TTimer timer; + + // Timer label is updated with timer ticks. + TLabel timerLabel; + + /** + * We need to override onClose so that the timer will no longer be called + * after we close the window. TTimers currently are completely unaware + * of the rest of the UI classes. + */ + @Override + public void onClose() { + getApplication().removeTimer(timer); + } + + /** + * Construct demo window. It will be centered on screen. + * + * @param parent the main application + */ + public DemoMainWindow(final TApplication parent) { + this(parent, CENTERED | RESIZABLE); + } + + // These are used by the timer loop. They have to be at class scope so + // that they can be accessed by the anonymous TAction class. + int timerI = 0; + TProgressBar progressBar; + + /** + * Constructor. + * + * @param parent the main application + * @param flags bitmask of MODAL, CENTERED, or RESIZABLE + */ + private DemoMainWindow(final TApplication parent, final int flags) { + // Construct a demo window. X and Y don't matter because it will be + // centered on screen. + super(parent, "Demo Window", 0, 0, 60, 23, flags); + + int row = 1; + + // Add some widgets + if (!isModal()) { + addLabel("Message Boxes", 1, row); + addButton("&MessageBoxes", 35, row, + new TAction() { + public void DO() { + new DemoMsgBoxWindow(getApplication()); + } + } + ); + } + row += 2; + + addLabel("Open me as modal", 1, row); + addButton("W&indow", 35, row, + new TAction() { + public void DO() { + new DemoMainWindow(getApplication(), MODAL); + } + } + ); + + row += 2; + + addLabel("Variable-width text field:", 1, row); + addField(35, row++, 15, false, "Field text"); + addLabel("Fixed-width text field:", 1, row); + addField(35, row++, 15, true); + addLabel("Variable-width password:", 1, row); + addPasswordField(35, row++, 15, false); + addLabel("Fixed-width password:", 1, row); + addPasswordField(35, row++, 15, true, "hunter2"); + row += 2; + + if (!isModal()) { + addLabel("Radio buttons and checkboxes", 1, row); + addButton("&Checkboxes", 35, row, + new TAction() { + public void DO() { + new DemoCheckboxWindow(getApplication()); + } + } + ); + } + row += 2; + + /* + if (!isModal()) { + addLabel("Editor window", 1, row); + addButton("Edito&r", 35, row, + { + new TEditor(application, 0, 0, 60, 15); + } + ); + } + row += 2; + */ + + if (!isModal()) { + addLabel("Text areas", 1, row); + addButton("&Text", 35, row, + new TAction() { + public void DO() { + new DemoTextWindow(getApplication()); + } + } + ); + } + row += 2; + + /* + if (!isModal()) { + addLabel("Tree views", 1, row); + addButton("Tree&View", 35, row, + { + new DemoTreeViewWindow(application); + } + ); + } + row += 2; + */ + + if (!isModal()) { + addLabel("Terminal", 1, row); + addButton("Termi&nal", 35, row, + new TAction() { + public void DO() { + getApplication().openTerminal(0, 0); + } + } + ); + } + row += 2; + + progressBar = addProgressBar(1, row, 22, 0); + row++; + timerLabel = addLabel("Timer", 1, row); + timer = getApplication().addTimer(250, true, + new TAction() { + + public void DO() { + timerLabel.setLabel(String.format("Timer: %d", timerI)); + timerLabel.setWidth(timerLabel.getLabel().length()); + if (timerI < 100) { + timerI++; + } + progressBar.setValue(timerI); + } + } + ); + } +} diff --git a/src/jexer/demos/DemoMsgBoxWindow.java b/src/jexer/demos/DemoMsgBoxWindow.java new file mode 100644 index 0000000..25115ea --- /dev/null +++ b/src/jexer/demos/DemoMsgBoxWindow.java @@ -0,0 +1,163 @@ +/* + * Jexer - Java Text User Interface + * + * License: LGPLv3 or later + * + * This module is licensed under the GNU Lesser General Public License + * Version 3. Please see the file "COPYING" in this directory for more + * information about the GNU Lesser General Public License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer.demos; + +import jexer.*; +import jexer.event.*; +import jexer.menu.*; + +/** + * This window demonstates the TMessageBox and TInputBox widgets. + */ +class DemoMsgBoxWindow extends TWindow { + + /** + * Constructor. + * + * @param parent the main application + */ + DemoMsgBoxWindow(final TApplication parent) { + this(parent, TWindow.CENTERED | TWindow.RESIZABLE); + } + + /** + * Constructor. + * + * @param parent the main application + * @param flags bitmask of MODAL, CENTERED, or RESIZABLE + */ + DemoMsgBoxWindow(final TApplication parent, final int flags) { + // Construct a demo window. X and Y don't matter because it + // will be centered on screen. + super(parent, "Message Boxes", 0, 0, 60, 15, flags); + + int row = 1; + + // Add some widgets + addLabel("Default OK message box", 1, row); + addButton("Open O&K MB", 35, row, + new TAction() { + public void DO() { + getApplication().messageBox("OK MessageBox", +"This is an example of a OK MessageBox. This is the\n" + +"default MessageBox.\n" + +"\n" + +"Note that the MessageBox text can span multiple\n" + +"lines.\n" + +"\n" + +"The default result (if someone hits the top-left\n" + +"close button) is OK.\n", + TMessageBox.Type.OK); + } + } + ); + row += 2; + + addLabel("OK/Cancel message box", 1, row); + addButton("O&pen OKC MB", 35, row, + new TAction() { + public void DO() { + getApplication().messageBox("OK/Cancel MessageBox", +"This is an example of a OK/Cancel MessageBox.\n" + +"\n" + +"Note that the MessageBox text can span multiple\n" + +"lines.\n" + +"\n" + +"The default result (if someone hits the top-left\n" + +"close button) is CANCEL.\n", + TMessageBox.Type.OKCANCEL); + } + } + ); + row += 2; + + addLabel("Yes/No message box", 1, row); + addButton("Open &YN MB", 35, row, + new TAction() { + public void DO() { + getApplication().messageBox("Yes/No MessageBox", +"This is an example of a Yes/No MessageBox.\n" + +"\n" + +"Note that the MessageBox text can span multiple\n" + +"lines.\n" + +"\n" + +"The default result (if someone hits the top-left\n" + +"close button) is NO.\n", + TMessageBox.Type.YESNO); + } + } + ); + row += 2; + + addLabel("Yes/No/Cancel message box", 1, row); + addButton("Ope&n YNC MB", 35, row, + new TAction() { + public void DO() { + getApplication().messageBox("Yes/No/Cancel MessageBox", +"This is an example of a Yes/No/Cancel MessageBox.\n" + +"\n" + +"Note that the MessageBox text can span multiple\n" + +"lines.\n" + +"\n" + +"The default result (if someone hits the top-left\n" + +"close button) is CANCEL.\n", + TMessageBox.Type.YESNOCANCEL); + } + } + ); + row += 2; + + addLabel("Input box", 1, row); + addButton("Open &input box", 35, row, + new TAction() { + public void DO() { + TInputBox in = getApplication().inputBox("Input Box", +"This is an example of an InputBox.\n" + +"\n" + +"Note that the InputBox text can span multiple\n" + +"lines.\n", + "some input text"); + getApplication().messageBox("Your InputBox Answer", + "You entered: " + in.getText()); + } + } + ); + + addButton("&Close Window", (getWidth() - 14) / 2, getHeight() - 4, + new TAction() { + public void DO() { + getApplication().closeWindow(DemoMsgBoxWindow.this); + } + } + ); + } +} + diff --git a/src/jexer/demos/DemoTextWindow.java b/src/jexer/demos/DemoTextWindow.java new file mode 100644 index 0000000..aa68f89 --- /dev/null +++ b/src/jexer/demos/DemoTextWindow.java @@ -0,0 +1,98 @@ +/* + * Jexer - Java Text User Interface + * + * License: LGPLv3 or later + * + * This module is licensed under the GNU Lesser General Public License + * Version 3. Please see the file "COPYING" in this directory for more + * information about the GNU Lesser General Public License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer.demos; + +import jexer.*; +import jexer.event.*; +import jexer.menu.*; + +/** + * This window demonstates the TText, THScroller, and TVScroller widgets. + */ +class DemoTextWindow extends TWindow { + + /** + * Hang onto my TText so I can resize it with the window. + */ + private TText textField; + + /** + * Public constructor. + * + * @param parent the main application + */ + public DemoTextWindow(final TApplication parent) { + super(parent, "Text Areas", 0, 0, 44, 20, RESIZABLE); + + textField = addText( +"This is an example of a reflowable text field. Some example text follows.\n" + +"\n" + +"This library implements a text-based windowing system loosely\n" + +"reminiscient of Borland's [Turbo\n" + +"Vision](http://en.wikipedia.org/wiki/Turbo_Vision) library. For those\n" + +"wishing to use the actual C++ Turbo Vision library, see [Sergio\n" + +"Sigala's updated version](http://tvision.sourceforge.net/) that runs\n" + +"on many more platforms.\n" + +"\n" + +"Currently the only console platform supported is Posix (tested on\n" + +"Linux). Input/output is handled through terminal escape sequences\n" + +"generated by the library itself: ncurses is not required or linked to. \n" + +"xterm mouse tracking using UTF8 coordinates is supported.\n" + +"\n" + +"This library is licensed LGPL (\"GNU Lesser General Public License\")\n" + +"version 3 or greater. See the file COPYING for the full license text,\n" + +"which includes both the GPL v3 and the LGPL supplemental terms.\n" + +"\n", + 1, 1, 40, 16); + } + + /** + * Handle window/screen resize events. + * + * @param event resize event + */ + @Override + public void onResize(final TResizeEvent event) { + if (event.getType() == TResizeEvent.Type.WIDGET) { + // Resize the text field + textField.setWidth(event.getWidth() - 4); + textField.setHeight(event.getHeight() - 4); + textField.reflow(); + return; + } + + // Pass to children instead + for (TWidget widget: getChildren()) { + widget.onResize(event); + } + } +} + diff --git a/src/jexer/io/SwingScreen.java b/src/jexer/io/SwingScreen.java index 2a96122..8677fdf 100644 --- a/src/jexer/io/SwingScreen.java +++ b/src/jexer/io/SwingScreen.java @@ -41,6 +41,7 @@ import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; +import java.awt.image.BufferStrategy; import java.io.InputStream; import java.util.Date; import javax.swing.JFrame; @@ -55,6 +56,11 @@ import jexer.session.SwingSessionInfo; */ public final class SwingScreen extends Screen { + /** + * If true, use double buffering thread. + */ + private static final boolean doubleBuffer = true; + /** * Cursor style to draw. */ @@ -137,6 +143,11 @@ public final class SwingScreen extends Screen { */ private static final String FONTFILE = "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf"; + /** + * The BufferStrategy object needed for double-buffering. + */ + private BufferStrategy bufferStrategy; + /** * The TUI Screen data. */ @@ -197,12 +208,6 @@ public final class SwingScreen extends Screen { * @return the Swing Color */ private Color attrToForegroundColor(final CellAttributes attr) { - /* - * TODO: - * reverse - * blink - * underline - */ if (attr.isBold()) { if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) { return MYBOLD_BLACK; @@ -250,12 +255,6 @@ public final class SwingScreen extends Screen { * @return the Swing Color */ private Color attrToBackgroundColor(final CellAttributes attr) { - /* - * TODO: - * reverse - * blink - * underline - */ if (attr.getBackColor().equals(jexer.bits.Color.BLACK)) { return MYBLACK; } else if (attr.getBackColor().equals(jexer.bits.Color.RED)) { @@ -328,6 +327,13 @@ public final class SwingScreen extends Screen { // Save the text cell width/height getFontDimensions(); + + // Setup double-buffering + if (screen.doubleBuffer) { + setIgnoreRepaint(true); + createBufferStrategy(2); + bufferStrategy = getBufferStrategy(); + } } /** @@ -447,6 +453,14 @@ public final class SwingScreen extends Screen { Cell pCell = screen.physical[x][y]; if (!lCell.equals(pCell) || reallyCleared) { + + /* + * TODO: + * reverse + * blink + * underline + */ + // Draw the background rectangle, then the // foreground character. gr.setColor(attrToBackgroundColor(lCell)); @@ -500,6 +514,13 @@ public final class SwingScreen extends Screen { */ SwingFrame frame; + /** + * Restore terminal to normal state. + */ + public void shutdown() { + frame.dispose(); + } + /** * Public constructor. */ @@ -547,7 +568,15 @@ public final class SwingScreen extends Screen { if (reallyCleared) { // Really refreshed, do it all - frame.repaint(); + if (doubleBuffer) { + Graphics gr = frame.bufferStrategy.getDrawGraphics(); + frame.paint(gr); + gr.dispose(); + frame.bufferStrategy.show(); + Toolkit.getDefaultToolkit().sync(); + } else { + frame.repaint(); + } return; } @@ -603,8 +632,20 @@ public final class SwingScreen extends Screen { } // Repaint the desired area - frame.repaint(xMin, yMin, xMax - xMin, yMax - yMin); - // System.err.printf("REPAINT X %d %d Y %d %d\n", xMin, xMax, yMin, yMax); + // System.err.printf("REPAINT X %d %d Y %d %d\n", xMin, xMax, + // yMin, yMax); + if (doubleBuffer) { + Graphics gr = frame.bufferStrategy.getDrawGraphics(); + Rectangle bounds = new Rectangle(xMin, yMin, xMax - xMin, + yMax - yMin); + gr.setClip(bounds); + frame.paint(gr); + gr.dispose(); + frame.bufferStrategy.show(); + Toolkit.getDefaultToolkit().sync(); + } else { + frame.repaint(xMin, yMin, xMax - xMin, yMax - yMin); + } } /** diff --git a/src/jexer/io/SwingTerminal.java b/src/jexer/io/SwingTerminal.java index 3f3d8e3..a7a4c33 100644 --- a/src/jexer/io/SwingTerminal.java +++ b/src/jexer/io/SwingTerminal.java @@ -151,14 +151,6 @@ public final class SwingTerminal implements ComponentListener, KeyListener, screen.frame.addMouseWheelListener(this); } - /** - * Restore terminal to normal state. - */ - public void shutdown() { - // System.err.println("=== shutdown() ==="); System.err.flush(); - screen.frame.dispose(); - } - /** * Return any events in the IO queue. * -- 2.27.0