#38 multiple backend disconencts
[nikiroo-utils.git] / src / jexer / backend / TWindowBackend.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 Kevin Lamonte
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29 package jexer.backend;
30
31 import java.util.ArrayList;
32 import java.util.List;
33
34 import jexer.TApplication;
35 import jexer.TWindow;
36 import jexer.event.TCommandEvent;
37 import jexer.event.TInputEvent;
38 import jexer.event.TKeypressEvent;
39 import jexer.event.TMouseEvent;
40 import jexer.event.TResizeEvent;
41 import static jexer.TCommand.*;
42
43 /**
44 * TWindowBackend uses a window in one TApplication to provide a backend for
45 * another TApplication.
46 *
47 * Note that TWindow has its own getScreen() and setTitle() functions.
48 * Clients in TWindowBackend's application won't be able to use it to get at
49 * the other application's screen. getOtherScreen() has been provided.
50 */
51 public class TWindowBackend extends TWindow implements Backend {
52
53 // ------------------------------------------------------------------------
54 // Variables --------------------------------------------------------------
55 // ------------------------------------------------------------------------
56
57 /**
58 * The listening object that run() wakes up on new input.
59 */
60 private Object listener;
61
62 /**
63 * The object to sync on in draw(). This is normally otherScreen, but it
64 * could also be a MultiScreen.
65 */
66 private Object drawLock;
67
68 /**
69 * The event queue, filled up by a thread reading on input.
70 */
71 private List<TInputEvent> eventQueue;
72
73 /**
74 * The screen this window is monitoring.
75 */
76 private Screen otherScreen;
77
78 /**
79 * The application associated with otherScreen.
80 */
81 private TApplication otherApplication;
82
83 /**
84 * The session information.
85 */
86 private SessionInfo sessionInfo;
87
88 /**
89 * OtherScreen provides a hook to notify TWindowBackend of screen size
90 * changes.
91 */
92 private class OtherScreen extends LogicalScreen {
93
94 /**
95 * The TWindowBackend to notify.
96 */
97 private TWindowBackend window;
98
99 /**
100 * Public constructor.
101 */
102 public OtherScreen(final TWindowBackend window) {
103 this.window = window;
104 }
105
106 /**
107 * Resize the physical screen to match the logical screen dimensions.
108 */
109 @Override
110 public void resizeToScreen() {
111 window.setWidth(getWidth() + 2);
112 window.setHeight(getHeight() + 2);
113 }
114
115 }
116
117
118 // ------------------------------------------------------------------------
119 // Constructors -----------------------------------------------------------
120 // ------------------------------------------------------------------------
121
122 /**
123 * Public constructor. Window will be located at (0, 0).
124 *
125 * @param listener the object this backend needs to wake up when new
126 * input comes in
127 * @param application TApplication that manages this window
128 * @param title window title, will be centered along the top border
129 * @param width width of window
130 * @param height height of window
131 */
132 public TWindowBackend(final Object listener,
133 final TApplication application, final String title,
134 final int width, final int height) {
135
136 super(application, title, width, height);
137
138 this.listener = listener;
139 eventQueue = new ArrayList<TInputEvent>();
140 sessionInfo = new TSessionInfo(width, height);
141 otherScreen = new OtherScreen(this);
142 otherScreen.setDimensions(width - 2, height - 2);
143 drawLock = otherScreen;
144 setHiddenMouse(true);
145 }
146
147 /**
148 * Public constructor. Window will be located at (0, 0).
149 *
150 * @param listener the object this backend needs to wake up when new
151 * input comes in
152 * @param application TApplication that manages this window
153 * @param title window title, will be centered along the top border
154 * @param width width of window
155 * @param height height of window
156 * @param flags bitmask of RESIZABLE, CENTERED, or MODAL
157 */
158 public TWindowBackend(final Object listener,
159 final TApplication application, final String title,
160 final int width, final int height, final int flags) {
161
162 super(application, title, width, height, flags);
163
164 this.listener = listener;
165 eventQueue = new ArrayList<TInputEvent>();
166 sessionInfo = new TSessionInfo(width, height);
167 otherScreen = new OtherScreen(this);
168 otherScreen.setDimensions(width - 2, height - 2);
169 drawLock = otherScreen;
170 setHiddenMouse(true);
171 }
172
173 /**
174 * Public constructor.
175 *
176 * @param listener the object this backend needs to wake up when new
177 * input comes in
178 * @param application TApplication that manages this window
179 * @param title window title, will be centered along the top border
180 * @param x column relative to parent
181 * @param y row relative to parent
182 * @param width width of window
183 * @param height height of window
184 */
185 public TWindowBackend(final Object listener,
186 final TApplication application, final String title,
187 final int x, final int y, final int width, final int height) {
188
189 super(application, title, x, y, width, height);
190
191 this.listener = listener;
192 eventQueue = new ArrayList<TInputEvent>();
193 sessionInfo = new TSessionInfo(width, height);
194 otherScreen = new OtherScreen(this);
195 otherScreen.setDimensions(width - 2, height - 2);
196 drawLock = otherScreen;
197 setHiddenMouse(true);
198 }
199
200 /**
201 * Public constructor.
202 *
203 * @param listener the object this backend needs to wake up when new
204 * input comes in
205 * @param application TApplication that manages this window
206 * @param title window title, will be centered along the top border
207 * @param x column relative to parent
208 * @param y row relative to parent
209 * @param width width of window
210 * @param height height of window
211 * @param flags mask of RESIZABLE, CENTERED, or MODAL
212 */
213 public TWindowBackend(final Object listener,
214 final TApplication application, final String title,
215 final int x, final int y, final int width, final int height,
216 final int flags) {
217
218 super(application, title, x, y, width, height, flags);
219
220 this.listener = listener;
221 eventQueue = new ArrayList<TInputEvent>();
222 sessionInfo = new TSessionInfo(width, height);
223 otherScreen = new OtherScreen(this);
224 otherScreen.setDimensions(width - 2, height - 2);
225 drawLock = otherScreen;
226 setHiddenMouse(true);
227 }
228
229 // ------------------------------------------------------------------------
230 // Event handlers ---------------------------------------------------------
231 // ------------------------------------------------------------------------
232
233 /**
234 * Handle window/screen resize events.
235 *
236 * @param event resize event
237 */
238 @Override
239 public void onResize(final TResizeEvent event) {
240 if (event.getType() == TResizeEvent.Type.WIDGET) {
241 int newWidth = event.getWidth() - 2;
242 int newHeight = event.getHeight() - 2;
243 if ((newWidth != otherScreen.getWidth())
244 || (newHeight != otherScreen.getHeight())
245 ) {
246 // I was resized, notify the screen I am watching to match my
247 // new size.
248 synchronized (eventQueue) {
249 eventQueue.add(new TResizeEvent(TResizeEvent.Type.SCREEN,
250 newWidth, newHeight));
251 }
252 synchronized (listener) {
253 listener.notifyAll();
254 }
255 }
256 return;
257 } else {
258 super.onResize(event);
259 }
260 }
261
262 /**
263 * Returns true if the mouse is currently in the otherScreen window.
264 *
265 * @param mouse mouse event
266 * @return true if mouse is currently in the otherScreen window.
267 */
268 protected boolean mouseOnOtherScreen(final TMouseEvent mouse) {
269 if ((mouse.getY() >= 1)
270 && (mouse.getY() <= otherScreen.getHeight())
271 && (mouse.getX() >= 1)
272 && (mouse.getX() <= otherScreen.getWidth())
273 ) {
274 return true;
275 }
276 return false;
277 }
278
279 /**
280 * Handle mouse button presses.
281 *
282 * @param mouse mouse button event
283 */
284 @Override
285 public void onMouseDown(final TMouseEvent mouse) {
286 if (mouseOnOtherScreen(mouse)) {
287 TMouseEvent event = mouse.dup();
288 event.setX(mouse.getX() - 1);
289 event.setY(mouse.getY() - 1);
290 event.setAbsoluteX(event.getX());
291 event.setAbsoluteY(event.getY());
292 synchronized (eventQueue) {
293 eventQueue.add(event);
294 }
295 synchronized (listener) {
296 listener.notifyAll();
297 }
298 }
299 super.onMouseDown(mouse);
300 }
301
302 /**
303 * Handle mouse button releases.
304 *
305 * @param mouse mouse button release event
306 */
307 @Override
308 public void onMouseUp(final TMouseEvent mouse) {
309 if (mouseOnOtherScreen(mouse)) {
310 TMouseEvent event = mouse.dup();
311 event.setX(mouse.getX() - 1);
312 event.setY(mouse.getY() - 1);
313 event.setAbsoluteX(event.getX());
314 event.setAbsoluteY(event.getY());
315 synchronized (eventQueue) {
316 eventQueue.add(event);
317 }
318 synchronized (listener) {
319 listener.notifyAll();
320 }
321 }
322 super.onMouseUp(mouse);
323 }
324
325 /**
326 * Handle mouse movements.
327 *
328 * @param mouse mouse motion event
329 */
330 @Override
331 public void onMouseMotion(final TMouseEvent mouse) {
332 if (mouseOnOtherScreen(mouse)) {
333 TMouseEvent event = mouse.dup();
334 event.setX(mouse.getX() - 1);
335 event.setY(mouse.getY() - 1);
336 event.setAbsoluteX(event.getX());
337 event.setAbsoluteY(event.getY());
338 synchronized (eventQueue) {
339 eventQueue.add(event);
340 }
341 synchronized (listener) {
342 listener.notifyAll();
343 }
344 }
345 super.onMouseMotion(mouse);
346 }
347
348 /**
349 * Handle keystrokes.
350 *
351 * @param keypress keystroke event
352 */
353 @Override
354 public void onKeypress(final TKeypressEvent keypress) {
355 TKeypressEvent event = keypress.dup();
356 synchronized (eventQueue) {
357 eventQueue.add(event);
358 }
359 synchronized (listener) {
360 listener.notifyAll();
361 }
362 }
363
364 // ------------------------------------------------------------------------
365 // TWindow ----------------------------------------------------------------
366 // ------------------------------------------------------------------------
367
368 /**
369 * Draw the foreground colors grid.
370 */
371 @Override
372 public void draw() {
373
374 // Sync on other screen, so that we do not draw in the middle of
375 // their screen update.
376 synchronized (drawLock) {
377 // Draw the box
378 super.draw();
379
380 // Draw every cell of the other screen
381 for (int y = 0; y < otherScreen.getHeight(); y++) {
382 for (int x = 0; x < otherScreen.getWidth(); x++) {
383 putCharXY(x + 1, y + 1, otherScreen.getCharXY(x, y));
384 }
385 }
386
387 // If their cursor is visible, draw that here too.
388 if (otherScreen.isCursorVisible()) {
389 setCursorX(otherScreen.getCursorX() + 1);
390 setCursorY(otherScreen.getCursorY() + 1);
391 setCursorVisible(true);
392 } else {
393 setCursorVisible(false);
394 }
395 }
396
397 // Check if the other application has died. If so, unset hidden
398 // mouse.
399 if (otherApplication != null) {
400 if (otherApplication.isRunning() == false) {
401 setHiddenMouse(false);
402 }
403 }
404
405 }
406
407 /**
408 * Subclasses should override this method to cleanup resources. This is
409 * called by application.closeWindow().
410 */
411 @Override
412 public void onClose() {
413 synchronized (eventQueue) {
414 eventQueue.add(new TCommandEvent(cmBackendDisconnect));
415 }
416 }
417
418 // ------------------------------------------------------------------------
419 // Backend ----------------------------------------------------------------
420 // ------------------------------------------------------------------------
421
422 /**
423 * Getter for sessionInfo.
424 *
425 * @return the SessionInfo
426 */
427 public final SessionInfo getSessionInfo() {
428 return sessionInfo;
429 }
430
431 /**
432 * Subclasses must provide an implementation that syncs the logical
433 * screen to the physical device.
434 */
435 public void flushScreen() {
436 getApplication().doRepaint();
437 }
438
439 /**
440 * Check if there are events in the queue.
441 *
442 * @return if true, getEvents() has something to return to the application
443 */
444 public boolean hasEvents() {
445 synchronized (eventQueue) {
446 return (eventQueue.size() > 0);
447 }
448 }
449
450 /**
451 * Subclasses must provide an implementation to get keyboard, mouse, and
452 * screen resize events.
453 *
454 * @param queue list to append new events to
455 */
456 public void getEvents(List<TInputEvent> queue) {
457 synchronized (eventQueue) {
458 if (eventQueue.size() > 0) {
459 synchronized (queue) {
460 queue.addAll(eventQueue);
461 }
462 eventQueue.clear();
463 }
464 }
465 }
466
467 /**
468 * Subclasses must provide an implementation that closes sockets,
469 * restores console, etc.
470 */
471 public void shutdown() {
472 // NOP
473 }
474
475 /**
476 * Set listener to a different Object.
477 *
478 * @param listener the new listening object that run() wakes up on new
479 * input
480 */
481 public void setListener(final Object listener) {
482 this.listener = listener;
483 }
484
485 /**
486 * Reload backend options from System properties.
487 */
488 public void reloadOptions() {
489 // NOP
490 }
491
492 // ------------------------------------------------------------------------
493 // TWindowBackend ---------------------------------------------------------
494 // ------------------------------------------------------------------------
495
496 /**
497 * Set the object to sync to in draw().
498 *
499 * @param drawLock the object to synchronize on
500 */
501 public void setDrawLock(final Object drawLock) {
502 this.drawLock = drawLock;
503 }
504
505 /**
506 * Getter for the other application's screen.
507 *
508 * @return the Screen
509 */
510 public Screen getOtherScreen() {
511 return otherScreen;
512 }
513
514 /**
515 * Set the other screen's application.
516 *
517 * @param application the application driving the other screen
518 */
519 public void setOtherApplication(final TApplication application) {
520 this.otherApplication = application;
521 }
522
523 }