mirror of
https://gitlab.com/AutumnMeowMeow/jexer
synced 2024-09-19 11:50:19 -06:00
#18 move to event-driven main loop
This commit is contained in:
parent
f6d9020703
commit
be72cb5ccb
16 changed files with 483 additions and 387 deletions
|
@ -205,11 +205,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.
|
||||
|
||||
- The JVM needs some warmup time to exhibit the true performance
|
||||
behavior. Drag a window around for a bit to see this: the initial
|
||||
performance is slow, then the JIT compiler kicks in and Jexer can
|
||||
be visually competitive with C/C++ curses applications.
|
||||
|
||||
- See jexer.tterminal.ECMA48 for more specifics of terminal
|
||||
emulation limitations.
|
||||
|
||||
|
|
|
@ -1,6 +1,46 @@
|
|||
Jexer Work Log
|
||||
==============
|
||||
|
||||
August 16, 2017
|
||||
|
||||
Holy balls this has gotten so much faster! It is FINALLY visibly
|
||||
identical in speed to the original d-tui: on xterm it is glass
|
||||
smooth. CPU load is about +/- 10%, idling around 5%.
|
||||
|
||||
I had to dramatically rework the event processing order, but now it
|
||||
makes much more sense. TApplication.run()'s sole job is to listen for
|
||||
backend I/O, push it into drainEventQueue, and wake up the consumer
|
||||
thread. The consumer thread's run() has the job of dealing with the
|
||||
event, AND THEN calling doIdles and updating the screen. That was the
|
||||
big breakthrough: why bother having main thread do screen updates? It
|
||||
just leads to contention everywhere as it tries to tell the consumer
|
||||
thread to lay off its data structures, when in reality the consumer
|
||||
thread should have been the real owner of those structures in the
|
||||
first place! This was mainly an artifact of the d-tui fiber threading
|
||||
design.
|
||||
|
||||
So now we have nice flow of events:
|
||||
|
||||
* I/O enters the backend, backend wakes up main thread.
|
||||
|
||||
* Main thread grabs events, wakes up consumer thread.
|
||||
|
||||
* Consumer thread does work, updates screen.
|
||||
|
||||
* Anyone can call doRepaint() to get a screen update shortly
|
||||
thereafter.
|
||||
|
||||
* Same flow for TTerminalWindow: ECMA48 gets remote I/O, calls back
|
||||
into TTerminalWindow, which then calls doRepaint(). So in this case
|
||||
we have a completely external thread asking for a screen update, and
|
||||
it is working.
|
||||
|
||||
Along the way I also eliminated the Screen.dirty flag and cut out
|
||||
calls to CellAttribute checks. Overall we now have about 80% less CPU
|
||||
being burned and way less latency. Both HPROF samples and times puts
|
||||
my code at roughly 5% of the total, all the rest is the
|
||||
sleeping/locking infrastructure.
|
||||
|
||||
August 15, 2017
|
||||
|
||||
I cut 0.0.5 just now, and also applied for a Sonatype repository.
|
||||
|
|
|
@ -52,6 +52,7 @@ import jexer.event.TMouseEvent;
|
|||
import jexer.event.TResizeEvent;
|
||||
import jexer.backend.Backend;
|
||||
import jexer.backend.Screen;
|
||||
import jexer.backend.MultiBackend;
|
||||
import jexer.backend.SwingBackend;
|
||||
import jexer.backend.ECMA48Backend;
|
||||
import jexer.backend.TWindowBackend;
|
||||
|
@ -145,6 +146,7 @@ public class TApplication implements Runnable {
|
|||
* The consumer loop.
|
||||
*/
|
||||
public void run() {
|
||||
boolean first = true;
|
||||
|
||||
// Loop forever
|
||||
while (!application.quit) {
|
||||
|
@ -158,44 +160,52 @@ public class TApplication implements Runnable {
|
|||
}
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
if (debugThreads) {
|
||||
System.err.printf("%s %s sleep\n", this,
|
||||
primary ? "primary" : "secondary");
|
||||
}
|
||||
long timeout = 0;
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
timeout = application.getSleepTime(1000);
|
||||
}
|
||||
|
||||
this.wait();
|
||||
|
||||
if (debugThreads) {
|
||||
System.err.printf("%s %s AWAKE\n", this,
|
||||
primary ? "primary" : "secondary");
|
||||
}
|
||||
|
||||
if ((!primary)
|
||||
&& (application.secondaryEventReceiver == null)
|
||||
) {
|
||||
// Secondary thread, emergency exit. If we
|
||||
// got here then something went wrong with
|
||||
// the handoff between yield() and
|
||||
// closeWindow().
|
||||
synchronized (application.primaryEventHandler) {
|
||||
application.primaryEventHandler.notify();
|
||||
}
|
||||
application.secondaryEventHandler = null;
|
||||
throw new RuntimeException(
|
||||
"secondary exited at wrong time");
|
||||
}
|
||||
if (timeout == 0) {
|
||||
// A timer needs to fire, break out.
|
||||
break;
|
||||
}
|
||||
|
||||
if (debugThreads) {
|
||||
System.err.printf("%d %s %s sleep %d millis\n",
|
||||
System.currentTimeMillis(), this,
|
||||
primary ? "primary" : "secondary", timeout);
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
this.wait(timeout);
|
||||
}
|
||||
|
||||
if (debugThreads) {
|
||||
System.err.printf("%d %s %s AWAKE\n",
|
||||
System.currentTimeMillis(), this,
|
||||
primary ? "primary" : "secondary");
|
||||
}
|
||||
|
||||
if ((!primary)
|
||||
&& (application.secondaryEventReceiver == null)
|
||||
) {
|
||||
// Secondary thread, emergency exit. If we got
|
||||
// here then something went wrong with the
|
||||
// handoff between yield() and closeWindow().
|
||||
synchronized (application.primaryEventHandler) {
|
||||
application.primaryEventHandler.notify();
|
||||
}
|
||||
application.secondaryEventHandler = null;
|
||||
throw new RuntimeException("secondary exited " +
|
||||
"at wrong time");
|
||||
}
|
||||
break;
|
||||
} catch (InterruptedException e) {
|
||||
// SQUASH
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for drawAll() or doIdle() to be done, then handle the
|
||||
// events.
|
||||
boolean oldLock = lockHandleEvent();
|
||||
assert (oldLock == false);
|
||||
} // while (!application.quit)
|
||||
|
||||
// Pull all events off the queue
|
||||
for (;;) {
|
||||
|
@ -206,7 +216,11 @@ public class TApplication implements Runnable {
|
|||
}
|
||||
event = application.drainEventQueue.remove(0);
|
||||
}
|
||||
|
||||
// We will have an event to process, so repaint the
|
||||
// screen at the end.
|
||||
application.repaint = true;
|
||||
|
||||
if (primary) {
|
||||
primaryHandleEvent(event);
|
||||
} else {
|
||||
|
@ -230,17 +244,12 @@ public class TApplication implements Runnable {
|
|||
// All done!
|
||||
return;
|
||||
}
|
||||
|
||||
} // for (;;)
|
||||
|
||||
// Unlock. Either I am primary thread, or I am secondary
|
||||
// thread and still running.
|
||||
oldLock = unlockHandleEvent();
|
||||
assert (oldLock == true);
|
||||
|
||||
// I have done some work of some kind. Tell the main run()
|
||||
// loop to wake up now.
|
||||
synchronized (application) {
|
||||
application.notify();
|
||||
// Fire timers, update screen.
|
||||
if (!quit) {
|
||||
application.finishEventProcessing();
|
||||
}
|
||||
|
||||
} // while (true) (main runnable loop)
|
||||
|
@ -262,12 +271,6 @@ public class TApplication implements Runnable {
|
|||
*/
|
||||
private volatile TWidget secondaryEventReceiver;
|
||||
|
||||
/**
|
||||
* Spinlock for the primary and secondary event handlers.
|
||||
* WidgetEventHandler.run() is responsible for setting this value.
|
||||
*/
|
||||
private volatile boolean insideHandleEvent = false;
|
||||
|
||||
/**
|
||||
* Wake the sleeping active event handler.
|
||||
*/
|
||||
|
@ -284,104 +287,6 @@ public class TApplication implements Runnable {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the insideHandleEvent flag to true. lockoutEventHandlers() will
|
||||
* spin indefinitely until unlockHandleEvent() is called.
|
||||
*
|
||||
* @return the old value of insideHandleEvent
|
||||
*/
|
||||
private boolean lockHandleEvent() {
|
||||
if (debugThreads) {
|
||||
System.err.printf(" >> lockHandleEvent(): oldValue %s",
|
||||
insideHandleEvent);
|
||||
}
|
||||
boolean oldValue = true;
|
||||
|
||||
synchronized (this) {
|
||||
// Wait for TApplication.run() to finish using the global state
|
||||
// before allowing further event processing.
|
||||
while (lockoutHandleEvent == true) {
|
||||
try {
|
||||
// Backoff so that the backend can finish its work.
|
||||
Thread.sleep(5);
|
||||
} catch (InterruptedException e) {
|
||||
// SQUASH
|
||||
}
|
||||
}
|
||||
|
||||
oldValue = insideHandleEvent;
|
||||
insideHandleEvent = true;
|
||||
}
|
||||
|
||||
if (debugThreads) {
|
||||
System.err.printf(" ***\n");
|
||||
}
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the insideHandleEvent flag to false. lockoutEventHandlers() will
|
||||
* spin indefinitely until unlockHandleEvent() is called.
|
||||
*
|
||||
* @return the old value of insideHandleEvent
|
||||
*/
|
||||
private boolean unlockHandleEvent() {
|
||||
if (debugThreads) {
|
||||
System.err.printf(" << unlockHandleEvent(): oldValue %s\n",
|
||||
insideHandleEvent);
|
||||
}
|
||||
synchronized (this) {
|
||||
boolean oldValue = insideHandleEvent;
|
||||
insideHandleEvent = false;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spinlock for the primary and secondary event handlers. When true, the
|
||||
* event handlers will spinlock wait before calling handleEvent().
|
||||
*/
|
||||
private volatile boolean lockoutHandleEvent = false;
|
||||
|
||||
/**
|
||||
* TApplication.run() needs to be able rely on the global data structures
|
||||
* being intact when calling doIdle() and drawAll(). Tell the event
|
||||
* handlers to wait for an unlock before handling their events.
|
||||
*/
|
||||
private void stopEventHandlers() {
|
||||
if (debugThreads) {
|
||||
System.err.printf(">> stopEventHandlers()");
|
||||
}
|
||||
|
||||
lockoutHandleEvent = true;
|
||||
// Wait for the last event to finish processing before returning
|
||||
// control to TApplication.run().
|
||||
while (insideHandleEvent == true) {
|
||||
try {
|
||||
// Backoff so that the event handler can finish its work.
|
||||
Thread.sleep(1);
|
||||
} catch (InterruptedException e) {
|
||||
// SQUASH
|
||||
}
|
||||
}
|
||||
|
||||
if (debugThreads) {
|
||||
System.err.printf(" XXX\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TApplication.run() needs to be able rely on the global data structures
|
||||
* being intact when calling doIdle() and drawAll(). Tell the event
|
||||
* handlers that it is now OK to handle their events.
|
||||
*/
|
||||
private void startEventHandlers() {
|
||||
if (debugThreads) {
|
||||
System.err.printf("<< startEventHandlers()\n");
|
||||
}
|
||||
lockoutHandleEvent = false;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// TApplication attributes ------------------------------------------------
|
||||
// ------------------------------------------------------------------------
|
||||
|
@ -512,6 +417,14 @@ public class TApplication implements Runnable {
|
|||
*/
|
||||
private volatile boolean repaint = true;
|
||||
|
||||
/**
|
||||
* Repaint the screen on the next update.
|
||||
*/
|
||||
public void doRepaint() {
|
||||
repaint = true;
|
||||
wakeEventHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Y coordinate of the top edge of the desktop. For now this is a
|
||||
* constant. Someday it would be nice to have a multi-line menu or
|
||||
|
@ -748,15 +661,56 @@ public class TApplication implements Runnable {
|
|||
menuItems = new ArrayList<TMenuItem>();
|
||||
desktop = new TDesktop(this);
|
||||
|
||||
// Setup the main consumer thread
|
||||
primaryEventHandler = new WidgetEventHandler(this, true);
|
||||
(new Thread(primaryEventHandler)).start();
|
||||
// Special case: the Swing backend needs to have a timer to drive its
|
||||
// blink state.
|
||||
if ((backend instanceof SwingBackend)
|
||||
|| (backend instanceof MultiBackend)
|
||||
) {
|
||||
// Default to 500 millis, unless a SwingBackend has its own
|
||||
// value.
|
||||
long millis = 500;
|
||||
if (backend instanceof SwingBackend) {
|
||||
millis = ((SwingBackend) backend).getBlinkMillis();
|
||||
}
|
||||
if (millis > 0) {
|
||||
addTimer(millis, true,
|
||||
new TAction() {
|
||||
public void DO() {
|
||||
TApplication.this.doRepaint();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Screen refresh loop ----------------------------------------------------
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Process background events, and update the screen.
|
||||
*/
|
||||
private void finishEventProcessing() {
|
||||
if (debugThreads) {
|
||||
System.err.printf(System.currentTimeMillis() + " " +
|
||||
Thread.currentThread() + " finishEventProcessing()\n");
|
||||
}
|
||||
|
||||
// Process timers and call doIdle()'s
|
||||
doIdle();
|
||||
|
||||
// Update the screen
|
||||
synchronized (getScreen()) {
|
||||
drawAll();
|
||||
}
|
||||
|
||||
if (debugThreads) {
|
||||
System.err.printf(System.currentTimeMillis() + " " +
|
||||
Thread.currentThread() + " finishEventProcessing() END\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invert the cell color at a position. This is used to track the mouse.
|
||||
*
|
||||
|
@ -765,7 +719,8 @@ public class TApplication implements Runnable {
|
|||
*/
|
||||
private void invertCell(final int x, final int y) {
|
||||
if (debugThreads) {
|
||||
System.err.printf("invertCell() %d %d\n", x, y);
|
||||
System.err.printf("%d %s invertCell() %d %d\n",
|
||||
System.currentTimeMillis(), Thread.currentThread(), x, y);
|
||||
}
|
||||
CellAttributes attr = getScreen().getAttrXY(x, y);
|
||||
attr.setForeColor(attr.getForeColor().invert());
|
||||
|
@ -778,12 +733,14 @@ public class TApplication implements Runnable {
|
|||
*/
|
||||
private void drawAll() {
|
||||
if (debugThreads) {
|
||||
System.err.printf("drawAll() enter\n");
|
||||
System.err.printf("%d %s drawAll() enter\n",
|
||||
System.currentTimeMillis(), Thread.currentThread());
|
||||
}
|
||||
|
||||
if (!repaint) {
|
||||
if (debugThreads) {
|
||||
System.err.printf("drawAll() !repaint\n");
|
||||
System.err.printf("%d %s drawAll() !repaint\n",
|
||||
System.currentTimeMillis(), Thread.currentThread());
|
||||
}
|
||||
synchronized (getScreen()) {
|
||||
if ((oldMouseX != mouseX) || (oldMouseY != mouseY)) {
|
||||
|
@ -802,7 +759,8 @@ public class TApplication implements Runnable {
|
|||
}
|
||||
|
||||
if (debugThreads) {
|
||||
System.err.printf("drawAll() REDRAW\n");
|
||||
System.err.printf("%d %s drawAll() REDRAW\n",
|
||||
System.currentTimeMillis(), Thread.currentThread());
|
||||
}
|
||||
|
||||
// If true, the cursor is not visible
|
||||
|
@ -897,9 +855,19 @@ public class TApplication implements Runnable {
|
|||
if (sorted.size() > 0) {
|
||||
activeWidget = sorted.get(sorted.size() - 1).getActiveChild();
|
||||
if (activeWidget.isCursorVisible()) {
|
||||
getScreen().putCursor(true, activeWidget.getCursorAbsoluteX(),
|
||||
activeWidget.getCursorAbsoluteY());
|
||||
cursor = true;
|
||||
if ((activeWidget.getCursorAbsoluteY() < desktopBottom)
|
||||
&& (activeWidget.getCursorAbsoluteY() > desktopTop)
|
||||
) {
|
||||
getScreen().putCursor(true,
|
||||
activeWidget.getCursorAbsoluteX(),
|
||||
activeWidget.getCursorAbsoluteY());
|
||||
cursor = true;
|
||||
} else {
|
||||
getScreen().putCursor(false,
|
||||
activeWidget.getCursorAbsoluteX(),
|
||||
activeWidget.getCursorAbsoluteY());
|
||||
cursor = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -925,68 +893,66 @@ public class TApplication implements Runnable {
|
|||
*/
|
||||
public void exit() {
|
||||
quit = true;
|
||||
synchronized (this) {
|
||||
this.notify();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run this application until it exits.
|
||||
*/
|
||||
public void run() {
|
||||
boolean first = true;
|
||||
// Start the main consumer thread
|
||||
primaryEventHandler = new WidgetEventHandler(this, true);
|
||||
(new Thread(primaryEventHandler)).start();
|
||||
|
||||
while (!quit) {
|
||||
// Timeout is in milliseconds, so default timeout after 1 second
|
||||
// of inactivity.
|
||||
long timeout = 1000;
|
||||
if (first) {
|
||||
first = false;
|
||||
timeout = 0;
|
||||
}
|
||||
synchronized (this) {
|
||||
boolean doWait = false;
|
||||
|
||||
// If I've got no updates to render, wait for something from the
|
||||
// backend or a timer.
|
||||
if (!repaint
|
||||
&& ((mouseX == oldMouseX) && (mouseY == oldMouseY))
|
||||
) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
if (timeout > 0) {
|
||||
// As of now, I've got nothing to do: no I/O, nothing from
|
||||
// the consumer threads, no timers that need to run ASAP. So
|
||||
// wait until either the backend or the consumer threads have
|
||||
// something to do.
|
||||
try {
|
||||
if (debugThreads) {
|
||||
System.err.println("sleep " + timeout + " millis");
|
||||
synchronized (fillEventQueue) {
|
||||
if (fillEventQueue.size() == 0) {
|
||||
doWait = true;
|
||||
}
|
||||
synchronized (this) {
|
||||
this.wait(timeout);
|
||||
}
|
||||
|
||||
if (doWait) {
|
||||
// No I/O to dispatch, so wait until the backend
|
||||
// provides new I/O.
|
||||
try {
|
||||
if (debugThreads) {
|
||||
System.err.println(System.currentTimeMillis() +
|
||||
" MAIN sleep");
|
||||
}
|
||||
|
||||
this.wait();
|
||||
|
||||
if (debugThreads) {
|
||||
System.err.println(System.currentTimeMillis() +
|
||||
" MAIN AWAKE");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// I'm awake and don't care why, let's see what's
|
||||
// going on out there.
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// I'm awake and don't care why, let's see what's going
|
||||
// on out there.
|
||||
}
|
||||
repaint = true;
|
||||
}
|
||||
|
||||
// Prevent stepping on the primary or secondary event handler.
|
||||
stopEventHandlers();
|
||||
} // synchronized (this)
|
||||
|
||||
// Pull any pending I/O events
|
||||
backend.getEvents(fillEventQueue);
|
||||
synchronized (fillEventQueue) {
|
||||
// Pull any pending I/O events
|
||||
backend.getEvents(fillEventQueue);
|
||||
|
||||
// Dispatch each event to the appropriate handler, one at a time.
|
||||
for (;;) {
|
||||
TInputEvent event = null;
|
||||
if (fillEventQueue.size() == 0) {
|
||||
break;
|
||||
// Dispatch each event to the appropriate handler, one at a
|
||||
// time.
|
||||
for (;;) {
|
||||
TInputEvent event = null;
|
||||
if (fillEventQueue.size() == 0) {
|
||||
break;
|
||||
}
|
||||
event = fillEventQueue.remove(0);
|
||||
metaHandleEvent(event);
|
||||
}
|
||||
event = fillEventQueue.remove(0);
|
||||
metaHandleEvent(event);
|
||||
}
|
||||
|
||||
// Wake a consumer thread if we have any pending events.
|
||||
|
@ -994,17 +960,6 @@ public class TApplication implements Runnable {
|
|||
wakeEventHandler();
|
||||
}
|
||||
|
||||
// Process timers and call doIdle()'s
|
||||
doIdle();
|
||||
|
||||
// Update the screen
|
||||
synchronized (getScreen()) {
|
||||
drawAll();
|
||||
}
|
||||
|
||||
// Let the event handlers run again.
|
||||
startEventHandlers();
|
||||
|
||||
} // while (!quit)
|
||||
|
||||
// Shutdown the event consumer threads
|
||||
|
@ -1053,32 +1008,34 @@ public class TApplication implements Runnable {
|
|||
if (event instanceof TCommandEvent) {
|
||||
TCommandEvent command = (TCommandEvent) event;
|
||||
if (command.getCmd().equals(cmAbort)) {
|
||||
quit = true;
|
||||
exit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Screen resize
|
||||
if (event instanceof TResizeEvent) {
|
||||
TResizeEvent resize = (TResizeEvent) event;
|
||||
synchronized (getScreen()) {
|
||||
getScreen().setDimensions(resize.getWidth(),
|
||||
resize.getHeight());
|
||||
desktopBottom = getScreen().getHeight() - 1;
|
||||
mouseX = 0;
|
||||
mouseY = 0;
|
||||
oldMouseX = 0;
|
||||
oldMouseY = 0;
|
||||
synchronized (drainEventQueue) {
|
||||
// Screen resize
|
||||
if (event instanceof TResizeEvent) {
|
||||
TResizeEvent resize = (TResizeEvent) event;
|
||||
synchronized (getScreen()) {
|
||||
getScreen().setDimensions(resize.getWidth(),
|
||||
resize.getHeight());
|
||||
desktopBottom = getScreen().getHeight() - 1;
|
||||
mouseX = 0;
|
||||
mouseY = 0;
|
||||
oldMouseX = 0;
|
||||
oldMouseY = 0;
|
||||
}
|
||||
if (desktop != null) {
|
||||
desktop.setDimensions(0, 0, resize.getWidth(),
|
||||
resize.getHeight() - 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (desktop != null) {
|
||||
desktop.setDimensions(0, 0, resize.getWidth(),
|
||||
resize.getHeight() - 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Put into the main queue
|
||||
drainEventQueue.add(event);
|
||||
// Put into the main queue
|
||||
drainEventQueue.add(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1262,12 +1219,18 @@ public class TApplication implements Runnable {
|
|||
* @param widget widget that will receive events
|
||||
*/
|
||||
public final void enableSecondaryEventReceiver(final TWidget widget) {
|
||||
if (debugThreads) {
|
||||
System.err.println(System.currentTimeMillis() +
|
||||
" enableSecondaryEventReceiver()");
|
||||
}
|
||||
|
||||
assert (secondaryEventReceiver == null);
|
||||
assert (secondaryEventHandler == null);
|
||||
assert ((widget instanceof TMessageBox)
|
||||
|| (widget instanceof TFileOpenBox));
|
||||
secondaryEventReceiver = widget;
|
||||
secondaryEventHandler = new WidgetEventHandler(this, false);
|
||||
|
||||
(new Thread(secondaryEventHandler)).start();
|
||||
}
|
||||
|
||||
|
@ -1276,12 +1239,6 @@ public class TApplication implements Runnable {
|
|||
*/
|
||||
public final void yield() {
|
||||
assert (secondaryEventReceiver != null);
|
||||
// This is where we handoff the event handler lock from the primary
|
||||
// to secondary thread. We unlock here, and in a future loop the
|
||||
// secondary thread locks again. When it gives up, we have the
|
||||
// single lock back.
|
||||
boolean oldLock = unlockHandleEvent();
|
||||
assert (oldLock);
|
||||
|
||||
while (secondaryEventReceiver != null) {
|
||||
synchronized (primaryEventHandler) {
|
||||
|
@ -1299,23 +1256,34 @@ public class TApplication implements Runnable {
|
|||
*/
|
||||
private void doIdle() {
|
||||
if (debugThreads) {
|
||||
System.err.printf("doIdle()\n");
|
||||
System.err.printf(System.currentTimeMillis() + " " +
|
||||
Thread.currentThread() + " doIdle()\n");
|
||||
}
|
||||
|
||||
// Now run any timers that have timed out
|
||||
Date now = new Date();
|
||||
List<TTimer> keepTimers = new LinkedList<TTimer>();
|
||||
for (TTimer timer: timers) {
|
||||
if (timer.getNextTick().getTime() <= now.getTime()) {
|
||||
timer.tick();
|
||||
if (timer.recurring) {
|
||||
synchronized (timers) {
|
||||
|
||||
if (debugThreads) {
|
||||
System.err.printf(System.currentTimeMillis() + " " +
|
||||
Thread.currentThread() + " doIdle() 2\n");
|
||||
}
|
||||
|
||||
// Run any timers that have timed out
|
||||
Date now = new Date();
|
||||
List<TTimer> keepTimers = new LinkedList<TTimer>();
|
||||
for (TTimer timer: timers) {
|
||||
if (timer.getNextTick().getTime() <= now.getTime()) {
|
||||
// Something might change, so repaint the screen.
|
||||
repaint = true;
|
||||
timer.tick();
|
||||
if (timer.recurring) {
|
||||
keepTimers.add(timer);
|
||||
}
|
||||
} else {
|
||||
keepTimers.add(timer);
|
||||
}
|
||||
} else {
|
||||
keepTimers.add(timer);
|
||||
}
|
||||
timers = keepTimers;
|
||||
}
|
||||
timers = keepTimers;
|
||||
|
||||
// Call onIdle's
|
||||
for (TWindow window: windows) {
|
||||
|
@ -2322,10 +2290,17 @@ public class TApplication implements Runnable {
|
|||
* @param event new event to add to the queue
|
||||
*/
|
||||
public final void postMenuEvent(final TInputEvent event) {
|
||||
synchronized (fillEventQueue) {
|
||||
fillEventQueue.add(event);
|
||||
synchronized (this) {
|
||||
synchronized (fillEventQueue) {
|
||||
fillEventQueue.add(event);
|
||||
}
|
||||
if (debugThreads) {
|
||||
System.err.println(System.currentTimeMillis() + " " +
|
||||
Thread.currentThread() + " postMenuEvent() wake up main");
|
||||
}
|
||||
closeMenu();
|
||||
this.notify();
|
||||
}
|
||||
closeMenu();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2444,7 +2419,7 @@ public class TApplication implements Runnable {
|
|||
if (command.equals(cmExit)) {
|
||||
if (messageBox("Confirmation", "Exit application?",
|
||||
TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
|
||||
quit = true;
|
||||
exit();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -2493,7 +2468,7 @@ public class TApplication implements Runnable {
|
|||
if (menu.getId() == TMenu.MID_EXIT) {
|
||||
if (messageBox("Confirmation", "Exit application?",
|
||||
TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
|
||||
quit = true;
|
||||
exit();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -2569,17 +2544,21 @@ public class TApplication implements Runnable {
|
|||
Date now = new Date();
|
||||
long nowTime = now.getTime();
|
||||
long sleepTime = timeout;
|
||||
for (TTimer timer: timers) {
|
||||
long nextTickTime = timer.getNextTick().getTime();
|
||||
if (nextTickTime < nowTime) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
long timeDifference = nextTickTime - nowTime;
|
||||
if (timeDifference < sleepTime) {
|
||||
sleepTime = timeDifference;
|
||||
synchronized (timers) {
|
||||
for (TTimer timer: timers) {
|
||||
long nextTickTime = timer.getNextTick().getTime();
|
||||
if (nextTickTime < nowTime) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
long timeDifference = nextTickTime - nowTime;
|
||||
if (timeDifference < sleepTime) {
|
||||
sleepTime = timeDifference;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert (sleepTime >= 0);
|
||||
assert (sleepTime <= timeout);
|
||||
return sleepTime;
|
||||
|
|
|
@ -40,13 +40,15 @@ import jexer.event.TKeypressEvent;
|
|||
import jexer.event.TMouseEvent;
|
||||
import jexer.event.TResizeEvent;
|
||||
import jexer.tterminal.DisplayLine;
|
||||
import jexer.tterminal.DisplayListener;
|
||||
import jexer.tterminal.ECMA48;
|
||||
import static jexer.TKeypress.*;
|
||||
|
||||
/**
|
||||
* TTerminalWindow exposes a ECMA-48 / ANSI X3.64 style terminal in a window.
|
||||
*/
|
||||
public class TTerminalWindow extends TScrollableWindow {
|
||||
public class TTerminalWindow extends TScrollableWindow
|
||||
implements DisplayListener {
|
||||
|
||||
/**
|
||||
* The emulator.
|
||||
|
@ -185,6 +187,7 @@ public class TTerminalWindow extends TScrollableWindow {
|
|||
shell = pb.start();
|
||||
emulator = new ECMA48(deviceType, shell.getInputStream(),
|
||||
shell.getOutputStream());
|
||||
emulator.setListener(this);
|
||||
} catch (IOException e) {
|
||||
messageBox("Error", "Error launching shell: " + e.getMessage());
|
||||
}
|
||||
|
@ -323,6 +326,13 @@ public class TTerminalWindow extends TScrollableWindow {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by emulator when fresh data has come in.
|
||||
*/
|
||||
public void displayChanged() {
|
||||
doRepaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle window close.
|
||||
*/
|
||||
|
|
|
@ -60,6 +60,15 @@ public final class TTimer {
|
|||
return nextTick;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the recurring flag.
|
||||
*
|
||||
* @param recurring if true, re-schedule this timer after every tick
|
||||
*/
|
||||
public void setRecurring(final boolean recurring) {
|
||||
this.recurring = recurring;
|
||||
}
|
||||
|
||||
/**
|
||||
* The action to perfom on a tick.
|
||||
*/
|
||||
|
|
|
@ -550,6 +550,13 @@ public abstract class TWidget implements Comparable<TWidget> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Repaint the screen on the next update.
|
||||
*/
|
||||
public void doRepaint() {
|
||||
window.getApplication().doRepaint();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Constructors -----------------------------------------------------------
|
||||
// ------------------------------------------------------------------------
|
||||
|
@ -990,7 +997,8 @@ public abstract class TWidget implements Comparable<TWidget> {
|
|||
|
||||
/**
|
||||
* Method that subclasses can override to do processing when the UI is
|
||||
* idle.
|
||||
* idle. Note that repainting is NOT assumed. To get a refresh after
|
||||
* onIdle, call doRepaint().
|
||||
*/
|
||||
public void onIdle() {
|
||||
// Default: do nothing, pass to children instead
|
||||
|
|
|
@ -40,7 +40,6 @@ import java.io.PrintWriter;
|
|||
import java.io.Reader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
|
||||
|
@ -712,11 +711,6 @@ public final class ECMA48Terminal extends LogicalScreen
|
|||
* physical screen
|
||||
*/
|
||||
private String flushString() {
|
||||
if (!dirty) {
|
||||
assert (!reallyCleared);
|
||||
return "";
|
||||
}
|
||||
|
||||
CellAttributes attr = null;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
@ -729,7 +723,6 @@ public final class ECMA48Terminal extends LogicalScreen
|
|||
flushLine(y, sb, attr);
|
||||
}
|
||||
|
||||
dirty = false;
|
||||
reallyCleared = false;
|
||||
|
||||
String result = sb.toString();
|
||||
|
@ -1106,10 +1099,10 @@ public final class ECMA48Terminal extends LogicalScreen
|
|||
* @param queue list to append new events to
|
||||
*/
|
||||
private void getIdleEvents(final List<TInputEvent> queue) {
|
||||
Date now = new Date();
|
||||
long nowTime = System.currentTimeMillis();
|
||||
|
||||
// Check for new window size
|
||||
long windowSizeDelay = now.getTime() - windowSizeTime;
|
||||
long windowSizeDelay = nowTime - windowSizeTime;
|
||||
if (windowSizeDelay > 1000) {
|
||||
sessionInfo.queryWindowSize();
|
||||
int newWidth = sessionInfo.getWindowWidth();
|
||||
|
@ -1123,12 +1116,12 @@ public final class ECMA48Terminal extends LogicalScreen
|
|||
newWidth, newHeight);
|
||||
queue.add(event);
|
||||
}
|
||||
windowSizeTime = now.getTime();
|
||||
windowSizeTime = nowTime;
|
||||
}
|
||||
|
||||
// ESCDELAY type timeout
|
||||
if (state == ParseState.ESCAPE) {
|
||||
long escDelay = now.getTime() - escapeTime;
|
||||
long escDelay = nowTime - escapeTime;
|
||||
if (escDelay > 100) {
|
||||
// After 0.1 seconds, assume a true escape character
|
||||
queue.add(controlChar((char)0x1B, false));
|
||||
|
@ -1192,9 +1185,9 @@ public final class ECMA48Terminal extends LogicalScreen
|
|||
private void processChar(final List<TInputEvent> events, final char ch) {
|
||||
|
||||
// ESCDELAY type timeout
|
||||
Date now = new Date();
|
||||
long nowTime = System.currentTimeMillis();
|
||||
if (state == ParseState.ESCAPE) {
|
||||
long escDelay = now.getTime() - escapeTime;
|
||||
long escDelay = nowTime - escapeTime;
|
||||
if (escDelay > 250) {
|
||||
// After 0.25 seconds, assume a true escape character
|
||||
events.add(controlChar((char)0x1B, false));
|
||||
|
@ -1214,7 +1207,7 @@ public final class ECMA48Terminal extends LogicalScreen
|
|||
|
||||
if (ch == 0x1B) {
|
||||
state = ParseState.ESCAPE;
|
||||
escapeTime = now.getTime();
|
||||
escapeTime = nowTime;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -177,11 +177,6 @@ public class LogicalScreen implements Screen {
|
|||
*/
|
||||
protected Cell [][] logical;
|
||||
|
||||
/**
|
||||
* When true, logical != physical.
|
||||
*/
|
||||
protected volatile boolean dirty;
|
||||
|
||||
/**
|
||||
* Get dirty flag.
|
||||
*
|
||||
|
@ -189,7 +184,20 @@ public class LogicalScreen implements Screen {
|
|||
* screen
|
||||
*/
|
||||
public final boolean isDirty() {
|
||||
return dirty;
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
if (!logical[x][y].equals(physical[x][y])) {
|
||||
return true;
|
||||
}
|
||||
if (logical[x][y].isBlink()) {
|
||||
// Blinking screens are always dirty. There is
|
||||
// opportunity for a Netscape blink tag joke here...
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -284,14 +292,7 @@ public class LogicalScreen implements Screen {
|
|||
}
|
||||
|
||||
if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
|
||||
dirty = true;
|
||||
logical[X][Y].setForeColor(attr.getForeColor());
|
||||
logical[X][Y].setBackColor(attr.getBackColor());
|
||||
logical[X][Y].setBold(attr.isBold());
|
||||
logical[X][Y].setBlink(attr.isBlink());
|
||||
logical[X][Y].setReverse(attr.isReverse());
|
||||
logical[X][Y].setUnderline(attr.isUnderline());
|
||||
logical[X][Y].setProtect(attr.isProtect());
|
||||
logical[X][Y].setTo(attr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -346,20 +347,13 @@ public class LogicalScreen implements Screen {
|
|||
// System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
|
||||
|
||||
if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
|
||||
dirty = true;
|
||||
|
||||
// Do not put control characters on the display
|
||||
assert (ch >= 0x20);
|
||||
assert (ch != 0x7F);
|
||||
|
||||
logical[X][Y].setTo(attr);
|
||||
logical[X][Y].setChar(ch);
|
||||
logical[X][Y].setForeColor(attr.getForeColor());
|
||||
logical[X][Y].setBackColor(attr.getBackColor());
|
||||
logical[X][Y].setBold(attr.isBold());
|
||||
logical[X][Y].setBlink(attr.isBlink());
|
||||
logical[X][Y].setReverse(attr.isReverse());
|
||||
logical[X][Y].setUnderline(attr.isUnderline());
|
||||
logical[X][Y].setProtect(attr.isProtect());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -386,7 +380,6 @@ public class LogicalScreen implements Screen {
|
|||
// System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
|
||||
|
||||
if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
|
||||
dirty = true;
|
||||
logical[X][Y].setChar(ch);
|
||||
}
|
||||
}
|
||||
|
@ -510,7 +503,6 @@ public class LogicalScreen implements Screen {
|
|||
clipBottom = height;
|
||||
|
||||
reallyCleared = true;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -580,7 +572,6 @@ public class LogicalScreen implements Screen {
|
|||
* clip variables.
|
||||
*/
|
||||
public final synchronized void reset() {
|
||||
dirty = true;
|
||||
for (int row = 0; row < height; row++) {
|
||||
for (int col = 0; col < width; col++) {
|
||||
logical[col][row].reset();
|
||||
|
@ -612,7 +603,6 @@ public class LogicalScreen implements Screen {
|
|||
* Clear the physical screen.
|
||||
*/
|
||||
public final void clearPhysical() {
|
||||
dirty = true;
|
||||
for (int row = 0; row < height; row++) {
|
||||
for (int col = 0; col < width; col++) {
|
||||
physical[col][row].reset();
|
||||
|
@ -773,6 +763,18 @@ public class LogicalScreen implements Screen {
|
|||
* @param y row coordinate to put the cursor on
|
||||
*/
|
||||
public void putCursor(final boolean visible, final int x, final int y) {
|
||||
if ((cursorY >= 0)
|
||||
&& (cursorX >= 0)
|
||||
&& (cursorY <= height - 1)
|
||||
&& (cursorX <= width - 1)
|
||||
) {
|
||||
// Make the current cursor position dirty
|
||||
if (physical[cursorX][cursorY].getChar() == 'Q') {
|
||||
physical[cursorX][cursorY].setChar('X');
|
||||
} else {
|
||||
physical[cursorX][cursorY].setChar('Q');
|
||||
}
|
||||
}
|
||||
|
||||
cursorVisible = visible;
|
||||
cursorX = x;
|
||||
|
|
|
@ -182,7 +182,12 @@ public class MultiScreen implements Screen {
|
|||
* screen
|
||||
*/
|
||||
public boolean isDirty() {
|
||||
return screens.get(0).isDirty();
|
||||
for (Screen screen: screens) {
|
||||
if (screen.isDirty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -136,4 +136,15 @@ public final class SwingBackend extends GenericBackend {
|
|||
((SwingTerminal) terminal).setFont(font);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of millis to wait before switching the blink from
|
||||
* visible to invisible.
|
||||
*
|
||||
* @return the number of milli to wait before switching the blink from
|
||||
* visible to invisible
|
||||
*/
|
||||
public long getBlinkMillis() {
|
||||
return ((SwingTerminal) terminal).getBlinkMillis();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import java.awt.Graphics2D;
|
|||
import java.awt.Graphics;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.ComponentListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
@ -50,7 +51,6 @@ import java.awt.event.WindowListener;
|
|||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.InputStream;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
@ -241,11 +241,22 @@ public final class SwingTerminal extends LogicalScreen
|
|||
private CursorStyle cursorStyle = CursorStyle.UNDERLINE;
|
||||
|
||||
/**
|
||||
* The number of millis to wait before switching the blink from
|
||||
* visible to invisible.
|
||||
* The number of millis to wait before switching the blink from visible
|
||||
* to invisible. Set to 0 or negative to disable blinking.
|
||||
*/
|
||||
private long blinkMillis = 500;
|
||||
|
||||
/**
|
||||
* Get the number of millis to wait before switching the blink from
|
||||
* visible to invisible.
|
||||
*
|
||||
* @return the number of milli to wait before switching the blink from
|
||||
* visible to invisible
|
||||
*/
|
||||
public long getBlinkMillis() {
|
||||
return blinkMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, the cursor should be visible right now based on the blink
|
||||
* time.
|
||||
|
@ -663,9 +674,7 @@ public final class SwingTerminal extends LogicalScreen
|
|||
* Reset the blink timer.
|
||||
*/
|
||||
private void resetBlinkTimer() {
|
||||
// See if it is time to flip the blink time.
|
||||
long nowTime = (new Date()).getTime();
|
||||
lastBlinkTime = nowTime;
|
||||
lastBlinkTime = System.currentTimeMillis();
|
||||
cursorBlinkVisible = true;
|
||||
}
|
||||
|
||||
|
@ -678,21 +687,12 @@ public final class SwingTerminal extends LogicalScreen
|
|||
|
||||
if (gotFontDimensions == false) {
|
||||
// Lazy-load the text width/height
|
||||
// System.err.println("calling getFontDimensions...");
|
||||
getFontDimensions(gr);
|
||||
/*
|
||||
System.err.println("textWidth " + textWidth +
|
||||
" textHeight " + textHeight);
|
||||
System.err.println("FONT: " + swing.getFont() + " font " + font);
|
||||
*/
|
||||
// resizeToScreen();
|
||||
}
|
||||
|
||||
// See if it is time to flip the blink time.
|
||||
long nowTime = (new Date()).getTime();
|
||||
if (nowTime > blinkMillis + lastBlinkTime) {
|
||||
lastBlinkTime = nowTime;
|
||||
cursorBlinkVisible = !cursorBlinkVisible;
|
||||
}
|
||||
|
||||
int xCellMin = 0;
|
||||
|
@ -762,7 +762,6 @@ public final class SwingTerminal extends LogicalScreen
|
|||
}
|
||||
drawCursor(gr);
|
||||
|
||||
dirty = false;
|
||||
reallyCleared = false;
|
||||
} // synchronized (this)
|
||||
}
|
||||
|
@ -779,9 +778,39 @@ public final class SwingTerminal extends LogicalScreen
|
|||
*/
|
||||
@Override
|
||||
public void flushPhysical() {
|
||||
// See if it is time to flip the blink time.
|
||||
long nowTime = System.currentTimeMillis();
|
||||
if (nowTime >= blinkMillis + lastBlinkTime) {
|
||||
lastBlinkTime = nowTime;
|
||||
cursorBlinkVisible = !cursorBlinkVisible;
|
||||
// System.err.println("New lastBlinkTime: " + lastBlinkTime);
|
||||
}
|
||||
|
||||
if ((swing.getFrame() != null)
|
||||
&& (swing.getBufferStrategy() != null)
|
||||
) {
|
||||
do {
|
||||
do {
|
||||
drawToSwing();
|
||||
} while (swing.getBufferStrategy().contentsRestored());
|
||||
|
||||
swing.getBufferStrategy().show();
|
||||
Toolkit.getDefaultToolkit().sync();
|
||||
} while (swing.getBufferStrategy().contentsLost());
|
||||
|
||||
} else {
|
||||
// Non-triple-buffered, call drawToSwing() once
|
||||
drawToSwing();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Push the logical screen to the physical device.
|
||||
*/
|
||||
private void drawToSwing() {
|
||||
|
||||
/*
|
||||
System.err.printf("flushPhysical(): reallyCleared %s dirty %s\n",
|
||||
System.err.printf("drawToSwing(): reallyCleared %s dirty %s\n",
|
||||
reallyCleared, dirty);
|
||||
*/
|
||||
|
||||
|
@ -795,8 +824,7 @@ public final class SwingTerminal extends LogicalScreen
|
|||
swing.paint(gr);
|
||||
gr.dispose();
|
||||
swing.getBufferStrategy().show();
|
||||
// sync() doesn't seem to help the tearing for me.
|
||||
// Toolkit.getDefaultToolkit().sync();
|
||||
Toolkit.getDefaultToolkit().sync();
|
||||
return;
|
||||
} else if (((swing.getFrame() != null)
|
||||
&& (swing.getBufferStrategy() == null))
|
||||
|
@ -808,19 +836,7 @@ public final class SwingTerminal extends LogicalScreen
|
|||
return;
|
||||
}
|
||||
|
||||
// Do nothing if nothing happened.
|
||||
if (!dirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((swing.getFrame() != null) && (swing.getBufferStrategy() != null)) {
|
||||
// See if it is time to flip the blink time.
|
||||
long nowTime = (new Date()).getTime();
|
||||
if (nowTime > blinkMillis + lastBlinkTime) {
|
||||
lastBlinkTime = nowTime;
|
||||
cursorBlinkVisible = !cursorBlinkVisible;
|
||||
}
|
||||
|
||||
Graphics gr = swing.getBufferStrategy().getDrawGraphics();
|
||||
|
||||
synchronized (this) {
|
||||
|
@ -848,8 +864,7 @@ public final class SwingTerminal extends LogicalScreen
|
|||
|
||||
gr.dispose();
|
||||
swing.getBufferStrategy().show();
|
||||
// sync() doesn't seem to help the tearing for me.
|
||||
// Toolkit.getDefaultToolkit().sync();
|
||||
Toolkit.getDefaultToolkit().sync();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -916,50 +931,13 @@ public final class SwingTerminal extends LogicalScreen
|
|||
swing.paint(gr);
|
||||
gr.dispose();
|
||||
swing.getBufferStrategy().show();
|
||||
// sync() doesn't seem to help the tearing for me.
|
||||
// Toolkit.getDefaultToolkit().sync();
|
||||
Toolkit.getDefaultToolkit().sync();
|
||||
} else {
|
||||
// Repaint on the Swing thread.
|
||||
swing.repaint(xMin, yMin, xMax - xMin, yMax - yMin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put the cursor at (x,y).
|
||||
*
|
||||
* @param visible if true, the cursor should be visible
|
||||
* @param x column coordinate to put the cursor on
|
||||
* @param y row coordinate to put the cursor on
|
||||
*/
|
||||
@Override
|
||||
public void putCursor(final boolean visible, final int x, final int y) {
|
||||
|
||||
if ((visible == cursorVisible) && ((x == cursorX) && (y == cursorY))) {
|
||||
// See if it is time to flip the blink time.
|
||||
long nowTime = (new Date()).getTime();
|
||||
if (nowTime < blinkMillis + lastBlinkTime) {
|
||||
// Nothing has changed, so don't do anything.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (cursorVisible
|
||||
&& (cursorY >= 0)
|
||||
&& (cursorX >= 0)
|
||||
&& (cursorY <= height - 1)
|
||||
&& (cursorX <= width - 1)
|
||||
) {
|
||||
// Make the current cursor position dirty
|
||||
if (physical[cursorX][cursorY].getChar() == 'Q') {
|
||||
physical[cursorX][cursorY].setChar('X');
|
||||
} else {
|
||||
physical[cursorX][cursorY].setChar('Q');
|
||||
}
|
||||
}
|
||||
|
||||
super.putCursor(visible, x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert pixel column position to text cell column position.
|
||||
*
|
||||
|
@ -1265,6 +1243,9 @@ public final class SwingTerminal extends LogicalScreen
|
|||
component.setLayout(new BorderLayout());
|
||||
component.add(newComponent);
|
||||
|
||||
// Allow key events to be received
|
||||
component.setFocusable(true);
|
||||
|
||||
// Get the Swing component
|
||||
SwingTerminal.this.swing = new SwingComponent(component);
|
||||
|
||||
|
|
|
@ -31,7 +31,6 @@ package jexer.backend;
|
|||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
|
@ -64,7 +63,7 @@ public final class TTYSessionInfo implements SessionInfo {
|
|||
/**
|
||||
* Time at which the window size was refreshed.
|
||||
*/
|
||||
private Date lastQueryWindowTime;
|
||||
private long lastQueryWindowTime;
|
||||
|
||||
/**
|
||||
* Username getter.
|
||||
|
@ -180,11 +179,11 @@ public final class TTYSessionInfo implements SessionInfo {
|
|||
* Re-query the text window size.
|
||||
*/
|
||||
public void queryWindowSize() {
|
||||
if (lastQueryWindowTime == null) {
|
||||
lastQueryWindowTime = new Date();
|
||||
if (lastQueryWindowTime == 0) {
|
||||
lastQueryWindowTime = System.currentTimeMillis();
|
||||
} else {
|
||||
Date now = new Date();
|
||||
if (now.getTime() - lastQueryWindowTime.getTime() < 3000) {
|
||||
long nowTime = System.currentTimeMillis();
|
||||
if (nowTime - lastQueryWindowTime < 3000) {
|
||||
// Don't re-spawn stty, it's been too soon.
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -84,6 +84,7 @@ public class Demo6 {
|
|||
* one demo application spanning two physical screens.
|
||||
*/
|
||||
multiBackend.addBackend(swingBackend);
|
||||
multiBackend.setListener(demoApp);
|
||||
|
||||
/*
|
||||
* Time for the second application. This one will have a single
|
||||
|
|
|
@ -197,6 +197,8 @@ public class DemoMainWindow extends TWindow {
|
|||
timerLabel.setWidth(timerLabel.getLabel().length());
|
||||
if (timerI < 100) {
|
||||
timerI++;
|
||||
} else {
|
||||
timer.setRecurring(false);
|
||||
}
|
||||
progressBar.setValue(timerI);
|
||||
}
|
||||
|
|
42
src/jexer/tterminal/DisplayListener.java
Normal file
42
src/jexer/tterminal/DisplayListener.java
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Jexer - Java Text User Interface
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (C) 2017 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.tterminal;
|
||||
|
||||
/**
|
||||
* DisplayListener is used to callback into external UI when data has come in
|
||||
* from the remote side.
|
||||
*/
|
||||
public interface DisplayListener {
|
||||
|
||||
/**
|
||||
* Function to call when the display needs to be updated.
|
||||
*/
|
||||
public void displayChanged();
|
||||
|
||||
}
|
|
@ -302,6 +302,21 @@ public class ECMA48 implements Runnable {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The enclosing listening object.
|
||||
*/
|
||||
private DisplayListener listener;
|
||||
|
||||
/**
|
||||
* Set a listening object.
|
||||
*
|
||||
* @param listener the object that will have displayChanged() called
|
||||
* after bytes are received from the remote terminal
|
||||
*/
|
||||
public void setListener(final DisplayListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* When true, the reader thread is expected to exit.
|
||||
*/
|
||||
|
@ -6024,6 +6039,10 @@ public class ECMA48 implements Runnable {
|
|||
consume((char)ch);
|
||||
}
|
||||
}
|
||||
// Permit my enclosing UI to know that I updated.
|
||||
if (listener != null) {
|
||||
listener.displayChanged();
|
||||
}
|
||||
}
|
||||
// System.err.println("end while loop"); System.err.flush();
|
||||
} catch (IOException e) {
|
||||
|
|
Loading…
Reference in a new issue