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