#103 Push screens from ECMA48 to TTerminalWidget

This commit is contained in:
Autumn Lamonte 2022-01-31 06:01:49 -06:00
parent 3bb1294e04
commit ea4d7bdd08
4 changed files with 129 additions and 86 deletions

View file

@ -135,12 +135,7 @@ public class TTerminalWidget extends TScrollableWidget
/**
* If true, the display has changed and needs updating.
*/
private List<String> dirtyQueue = new ArrayList<String>();
/**
* Time that the display was last updated.
*/
private long lastUpdateTime = 0;
private List<List<DisplayLine>> dirtyQueue = new ArrayList<List<DisplayLine>>();
/**
* If true, hide the mouse after typing a keystroke.
@ -642,50 +637,21 @@ public class TTerminalWidget extends TScrollableWidget
int width = getDisplayWidth();
boolean syncEmulator = false;
// Get the very first display.
if (display == null) {
width = readEmulatorDisplay();
}
// If the emulator notified of an update, sync.
synchronized (dirtyQueue) {
if (dirtyQueue.size() > 0) {
dirtyQueue.remove(dirtyQueue.size() - 1);
syncEmulator = true;
// We will be dropping frames to keep up.
display = dirtyQueue.remove(dirtyQueue.size() - 1);
dirtyQueue.clear();
}
}
// If it has been longer than 250 milliseconds, sync.
long now = System.currentTimeMillis();
if ((now - lastUpdateTime) > 250) {
syncEmulator = true;
}
lastUpdateTime = now;
if ((syncEmulator == true)
|| (display == null)
) {
// We want to minimize the amount of time we have the emulator
// locked. Grab a copy of its display.
synchronized (emulator) {
// Update the scroll bars
reflowData();
if (!isDrawable()) {
// We lost the connection, onShellExit() called an action
// that ultimately removed this widget from the UI
// hierarchy, so no one cares if we update the display.
// Bail out.
return;
}
if ((display == null) || emulator.isReading()) {
display = emulator.getVisibleDisplay(getHeight(),
-getVerticalValue());
assert (display.size() == getHeight());
}
width = emulator.getWidth();
}
}
// Now draw the emulator screen
// Draw the emulator screen.
int row = 0;
for (DisplayLine line: display) {
int widthMax = width;
@ -840,14 +806,10 @@ public class TTerminalWidget extends TScrollableWidget
// ------------------------------------------------------------------------
/**
* Set the dirty flag.
* Update the display to account for a change in scrollback.
*/
public void setDirty() {
synchronized (dirtyQueue) {
if (dirtyQueue.size() == 0) {
dirtyQueue.add("dirty");
}
}
private void setDirty() {
readEmulatorDisplay();
}
/**
@ -914,6 +876,9 @@ public class TTerminalWidget extends TScrollableWidget
*/
public boolean hasHiddenMouse() {
if (emulator != null) {
if (!emulator.isReading()) {
typingHidMouse = false;
}
boolean hiddenMouse = (emulator.hasHiddenMousePointer()
|| typingHidMouse);
if (hiddenMouse) {
@ -1429,30 +1394,42 @@ public class TTerminalWidget extends TScrollableWidget
}
/**
* Get the scrollback buffer from the emulator.
* Get the visible display buffer from the emulator.
*
* @return the scrollback buffer, all the lines that have scrolled off
* screen
* @return the width of the display, or -1 if the display was not read
*/
public final List<DisplayLine> getScrollbackBuffer() {
ArrayList<DisplayLine> buffer = new ArrayList<DisplayLine>();
for (DisplayLine line: emulator.getScrollbackBuffer()) {
buffer.add(new DisplayLine(line));
}
return buffer;
private int readEmulatorDisplay() {
List<DisplayLine> currentDisplay = null;
int width = 80;
// Synchronize against the emulator so we don't stomp on its reader
// thread.
synchronized (emulator) {
// Update the scroll bars
reflowData();
if (!isDrawable()) {
// We lost the connection, onShellExit() called an action
// that ultimately removed this widget from the UI hierarchy,
// so no one cares if we update the display. Bail out.
return -1;
}
/**
* Get the display buffer from the emulator.
*
* @return the display buffer, the lines that are on the visible screen
*/
public final List<DisplayLine> getDisplayBuffer() {
ArrayList<DisplayLine> buffer = new ArrayList<DisplayLine>();
for (DisplayLine line: emulator.getDisplayBuffer()) {
buffer.add(new DisplayLine(line));
if (emulator.isReading()) {
currentDisplay = emulator.getVisibleDisplay(getHeight(),
-getVerticalValue());
assert (display.size() == getHeight());
}
return buffer;
width = emulator.getWidth();
}
if (currentDisplay != null) {
synchronized (dirtyQueue) {
dirtyQueue.add(currentDisplay);
}
}
return width;
}
/**
@ -1506,6 +1483,18 @@ public class TTerminalWidget extends TScrollableWidget
// DisplayListener --------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Called by emulator when fresh data has come in (push).
*
* @param display the updated display
*/
public void updateDisplay(final List<DisplayLine> display) {
synchronized (dirtyQueue) {
dirtyQueue.add(display);
}
getApplication().doRepaint();
}
/**
* Called by emulator when fresh data has come in.
*
@ -1514,22 +1503,30 @@ public class TTerminalWidget extends TScrollableWidget
*/
public void displayChanged(final boolean cursorOnly) {
if (cursorOnly) {
TApplication app = getApplication();
if (app != null) {
app.postEvent(new TMenuEvent(null, TMenu.MID_REPAINT));
}
getApplication().doRepaint();
return;
}
boolean readDisplay = false;
synchronized (dirtyQueue) {
if (dirtyQueue.size() == 0) {
dirtyQueue.add("dirty");
TApplication app = getApplication();
if (app != null) {
app.postEvent(new TMenuEvent(null, TMenu.MID_REPAINT));
if (dirtyQueue.size() > 0) {
display = dirtyQueue.remove(0);
}
}
if (readDisplay) {
readEmulatorDisplay();
}
getApplication().doRepaint();
}
/**
* Function to call to obtain the number of rows from the bottom to
* scroll back when sending updates via updateDisplay().
*
* @return the number of rows from the bottom to scroll back
*/
public int getScrollBottom() {
return -getVerticalValue();
}
/**

View file

@ -278,7 +278,7 @@ public class TTextPicture extends TScrollableWidget
// ------------------------------------------------------------------------
/**
* Called by emulator when fresh data has come in.
* Called by emulator when fresh data has come in (request poll).
*
* @param cursorOnly if true, the screen has not changed but the cursor
* may be on a different location.
@ -287,6 +287,25 @@ public class TTextPicture extends TScrollableWidget
// Do nothing
}
/**
* Called by emulator when fresh data has come in (push).
*
* @param display the updated display
*/
public void updateDisplay(final List<DisplayLine> display) {
// Do nothing
}
/**
* Function to call to obtain the number of rows from the bottom to
* scroll back when sending updates via updateDisplay().
*
* @return the number of rows from the bottom to scroll back
*/
public int getScrollBottom() {
return -getVerticalValue();
}
/**
* Function to call to obtain the display width.
*

View file

@ -28,6 +28,8 @@
*/
package jexer.tterminal;
import java.util.List;
/**
* DisplayListener is used to callback into external UI when data has come in
* from the remote side.
@ -35,13 +37,28 @@ package jexer.tterminal;
public interface DisplayListener {
/**
* Function to call when the display needs to be updated.
* Function to call when the display needs to be updated (request poll).
*
* @param cursorOnly if true, the screen has not changed but the cursor
* may be on a different location.
*/
public void displayChanged(final boolean cursorOnly);
/**
* Function to call when the display has updated (push).
*
* @param display the updated display
*/
public void updateDisplay(final List<DisplayLine> display);
/**
* Function to call to obtain the number of rows from the bottom to
* scroll back when sending updates via updateDisplay().
*
* @return the number of rows from the bottom to scroll back
*/
public int getScrollBottom();
/**
* Function to call to obtain the display width.
*

View file

@ -266,6 +266,12 @@ public class ECMA48 implements Runnable {
*/
private boolean screenIsDirty = true;
/**
* When true, synchronized update has already pushed a screen to the
* display, so run() should not do it again.
*/
private boolean doNotUpdateDisplay = false;
/**
* When true, the reader thread is expected to exit.
*/
@ -860,15 +866,17 @@ public class ECMA48 implements Runnable {
}
}
// Permit my enclosing UI to know that I updated.
if (displayListener != null) {
if ((displayListener != null) && !doNotUpdateDisplay) {
if (screenIsDirty) {
displayListener.displayChanged(false);
displayListener.updateDisplay(getVisibleDisplay(
height, displayListener.getScrollBottom()));
screenIsDirty = false;
} else {
displayListener.displayChanged(true);
screenIsDirty = false;
}
}
doNotUpdateDisplay = false;
}
// System.err.println("end while loop"); System.err.flush();
} catch (IOException e) {
@ -2288,7 +2296,8 @@ public class ECMA48 implements Runnable {
printCharacter(keypress.getChar());
}
if (displayListener != null) {
displayListener.displayChanged(false);
displayListener.updateDisplay(getVisibleDisplay(
height, displayListener.getScrollBottom()));
screenIsDirty = false;
}
}
@ -3804,7 +3813,8 @@ public class ECMA48 implements Runnable {
withinSynchronizedUpdate = false;
// Permit my enclosing UI to know that I updated.
if (displayListener != null) {
displayListener.displayChanged(false);
displayListener.updateDisplay(lastVisibleDisplay);
doNotUpdateDisplay = true;
}
}
}