Add 'src/jexer/' from commit 'cf01c92f5809a0732409e280fb0f32f27393618d'
[fanfix.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 * Get the width of a character cell in pixels.
117 *
118 * @return the width in pixels of a character cell
119 */
120 @Override
121 public int getTextWidth() {
122 return window.getScreen().getTextWidth();
123 }
124
125 /**
126 * Get the height of a character cell in pixels.
127 *
128 * @return the height in pixels of a character cell
129 */
130 @Override
131 public int getTextHeight() {
132 return window.getScreen().getTextHeight();
133 }
134
135 }
136
137
138 // ------------------------------------------------------------------------
139 // Constructors -----------------------------------------------------------
140 // ------------------------------------------------------------------------
141
142 /**
143 * Public constructor. Window will be located at (0, 0).
144 *
145 * @param listener the object this backend needs to wake up when new
146 * input comes in
147 * @param application TApplication that manages this window
148 * @param title window title, will be centered along the top border
149 * @param width width of window
150 * @param height height of window
151 */
152 public TWindowBackend(final Object listener,
153 final TApplication application, final String title,
154 final int width, final int height) {
155
156 super(application, title, width, height);
157
158 this.listener = listener;
159 eventQueue = new ArrayList<TInputEvent>();
160 sessionInfo = new TSessionInfo(width, height);
161 otherScreen = new OtherScreen(this);
162 otherScreen.setDimensions(width - 2, height - 2);
163 drawLock = otherScreen;
164 setHiddenMouse(true);
165 }
166
167 /**
168 * Public constructor. Window will be located at (0, 0).
169 *
170 * @param listener the object this backend needs to wake up when new
171 * input comes in
172 * @param application TApplication that manages this window
173 * @param title window title, will be centered along the top border
174 * @param width width of window
175 * @param height height of window
176 * @param flags bitmask of RESIZABLE, CENTERED, or MODAL
177 */
178 public TWindowBackend(final Object listener,
179 final TApplication application, final String title,
180 final int width, final int height, final int flags) {
181
182 super(application, title, width, height, flags);
183
184 this.listener = listener;
185 eventQueue = new ArrayList<TInputEvent>();
186 sessionInfo = new TSessionInfo(width, height);
187 otherScreen = new OtherScreen(this);
188 otherScreen.setDimensions(width - 2, height - 2);
189 drawLock = otherScreen;
190 setHiddenMouse(true);
191 }
192
193 /**
194 * Public constructor.
195 *
196 * @param listener the object this backend needs to wake up when new
197 * input comes in
198 * @param application TApplication that manages this window
199 * @param title window title, will be centered along the top border
200 * @param x column relative to parent
201 * @param y row relative to parent
202 * @param width width of window
203 * @param height height of window
204 */
205 public TWindowBackend(final Object listener,
206 final TApplication application, final String title,
207 final int x, final int y, final int width, final int height) {
208
209 super(application, title, x, y, width, height);
210
211 this.listener = listener;
212 eventQueue = new ArrayList<TInputEvent>();
213 sessionInfo = new TSessionInfo(width, height);
214 otherScreen = new OtherScreen(this);
215 otherScreen.setDimensions(width - 2, height - 2);
216 drawLock = otherScreen;
217 setHiddenMouse(true);
218 }
219
220 /**
221 * Public constructor.
222 *
223 * @param listener the object this backend needs to wake up when new
224 * input comes in
225 * @param application TApplication that manages this window
226 * @param title window title, will be centered along the top border
227 * @param x column relative to parent
228 * @param y row relative to parent
229 * @param width width of window
230 * @param height height of window
231 * @param flags mask of RESIZABLE, CENTERED, or MODAL
232 */
233 public TWindowBackend(final Object listener,
234 final TApplication application, final String title,
235 final int x, final int y, final int width, final int height,
236 final int flags) {
237
238 super(application, title, x, y, width, height, flags);
239
240 this.listener = listener;
241 eventQueue = new ArrayList<TInputEvent>();
242 sessionInfo = new TSessionInfo(width, height);
243 otherScreen = new OtherScreen(this);
244 otherScreen.setDimensions(width - 2, height - 2);
245 drawLock = otherScreen;
246 setHiddenMouse(true);
247 }
248
249 // ------------------------------------------------------------------------
250 // Event handlers ---------------------------------------------------------
251 // ------------------------------------------------------------------------
252
253 /**
254 * Handle window/screen resize events.
255 *
256 * @param event resize event
257 */
258 @Override
259 public void onResize(final TResizeEvent event) {
260 if (event.getType() == TResizeEvent.Type.WIDGET) {
261 int newWidth = event.getWidth() - 2;
262 int newHeight = event.getHeight() - 2;
263 if ((newWidth != otherScreen.getWidth())
264 || (newHeight != otherScreen.getHeight())
265 ) {
266 // I was resized, notify the screen I am watching to match my
267 // new size.
268 synchronized (eventQueue) {
269 eventQueue.add(new TResizeEvent(TResizeEvent.Type.SCREEN,
270 newWidth, newHeight));
271 }
272 synchronized (listener) {
273 listener.notifyAll();
274 }
275 }
276 return;
277 } else {
278 super.onResize(event);
279 }
280 }
281
282 /**
283 * Returns true if the mouse is currently in the otherScreen window.
284 *
285 * @param mouse mouse event
286 * @return true if mouse is currently in the otherScreen window.
287 */
288 protected boolean mouseOnOtherScreen(final TMouseEvent mouse) {
289 if ((mouse.getY() >= 1)
290 && (mouse.getY() <= otherScreen.getHeight())
291 && (mouse.getX() >= 1)
292 && (mouse.getX() <= otherScreen.getWidth())
293 ) {
294 return true;
295 }
296 return false;
297 }
298
299 /**
300 * Handle mouse button presses.
301 *
302 * @param mouse mouse button event
303 */
304 @Override
305 public void onMouseDown(final TMouseEvent mouse) {
306 if (mouseOnOtherScreen(mouse)) {
307 TMouseEvent event = mouse.dup();
308 event.setX(mouse.getX() - 1);
309 event.setY(mouse.getY() - 1);
310 event.setAbsoluteX(event.getX());
311 event.setAbsoluteY(event.getY());
312 synchronized (eventQueue) {
313 eventQueue.add(event);
314 }
315 synchronized (listener) {
316 listener.notifyAll();
317 }
318 }
319 super.onMouseDown(mouse);
320 }
321
322 /**
323 * Handle mouse button releases.
324 *
325 * @param mouse mouse button release event
326 */
327 @Override
328 public void onMouseUp(final TMouseEvent mouse) {
329 if (mouseOnOtherScreen(mouse)) {
330 TMouseEvent event = mouse.dup();
331 event.setX(mouse.getX() - 1);
332 event.setY(mouse.getY() - 1);
333 event.setAbsoluteX(event.getX());
334 event.setAbsoluteY(event.getY());
335 synchronized (eventQueue) {
336 eventQueue.add(event);
337 }
338 synchronized (listener) {
339 listener.notifyAll();
340 }
341 }
342 super.onMouseUp(mouse);
343 }
344
345 /**
346 * Handle mouse movements.
347 *
348 * @param mouse mouse motion event
349 */
350 @Override
351 public void onMouseMotion(final TMouseEvent mouse) {
352 if (mouseOnOtherScreen(mouse)) {
353 TMouseEvent event = mouse.dup();
354 event.setX(mouse.getX() - 1);
355 event.setY(mouse.getY() - 1);
356 event.setAbsoluteX(event.getX());
357 event.setAbsoluteY(event.getY());
358 synchronized (eventQueue) {
359 eventQueue.add(event);
360 }
361 synchronized (listener) {
362 listener.notifyAll();
363 }
364 }
365 super.onMouseMotion(mouse);
366 }
367
368 /**
369 * Handle keystrokes.
370 *
371 * @param keypress keystroke event
372 */
373 @Override
374 public void onKeypress(final TKeypressEvent keypress) {
375 TKeypressEvent event = keypress.dup();
376 synchronized (eventQueue) {
377 eventQueue.add(event);
378 }
379 synchronized (listener) {
380 listener.notifyAll();
381 }
382 }
383
384 // ------------------------------------------------------------------------
385 // TWindow ----------------------------------------------------------------
386 // ------------------------------------------------------------------------
387
388 /**
389 * Draw the foreground colors grid.
390 */
391 @Override
392 public void draw() {
393
394 // Sync on other screen, so that we do not draw in the middle of
395 // their screen update.
396 synchronized (drawLock) {
397 // Draw the box
398 super.draw();
399
400 // Draw every cell of the other screen
401 for (int y = 0; y < otherScreen.getHeight(); y++) {
402 for (int x = 0; x < otherScreen.getWidth(); x++) {
403 putCharXY(x + 1, y + 1, otherScreen.getCharXY(x, y));
404 }
405 }
406
407 // If their cursor is visible, draw that here too.
408 if (otherScreen.isCursorVisible()) {
409 setCursorX(otherScreen.getCursorX() + 1);
410 setCursorY(otherScreen.getCursorY() + 1);
411 setCursorVisible(true);
412 } else {
413 setCursorVisible(false);
414 }
415 }
416
417 // Check if the other application has died. If so, unset hidden
418 // mouse.
419 if (otherApplication != null) {
420 if (otherApplication.isRunning() == false) {
421 setHiddenMouse(false);
422 }
423 }
424
425 }
426
427 /**
428 * Subclasses should override this method to cleanup resources. This is
429 * called by application.closeWindow().
430 */
431 @Override
432 public void onClose() {
433 synchronized (eventQueue) {
434 eventQueue.add(new TCommandEvent(cmBackendDisconnect));
435 }
436 }
437
438 // ------------------------------------------------------------------------
439 // Backend ----------------------------------------------------------------
440 // ------------------------------------------------------------------------
441
442 /**
443 * Getter for sessionInfo.
444 *
445 * @return the SessionInfo
446 */
447 public final SessionInfo getSessionInfo() {
448 return sessionInfo;
449 }
450
451 /**
452 * Subclasses must provide an implementation that syncs the logical
453 * screen to the physical device.
454 */
455 public void flushScreen() {
456 getApplication().doRepaint();
457 }
458
459 /**
460 * Check if there are events in the queue.
461 *
462 * @return if true, getEvents() has something to return to the application
463 */
464 public boolean hasEvents() {
465 synchronized (eventQueue) {
466 return (eventQueue.size() > 0);
467 }
468 }
469
470 /**
471 * Subclasses must provide an implementation to get keyboard, mouse, and
472 * screen resize events.
473 *
474 * @param queue list to append new events to
475 */
476 public void getEvents(List<TInputEvent> queue) {
477 synchronized (eventQueue) {
478 if (eventQueue.size() > 0) {
479 synchronized (queue) {
480 queue.addAll(eventQueue);
481 }
482 eventQueue.clear();
483 }
484 }
485 }
486
487 /**
488 * Subclasses must provide an implementation that closes sockets,
489 * restores console, etc.
490 */
491 public void shutdown() {
492 // NOP
493 }
494
495 /**
496 * Set listener to a different Object.
497 *
498 * @param listener the new listening object that run() wakes up on new
499 * input
500 */
501 public void setListener(final Object listener) {
502 this.listener = listener;
503 }
504
505 /**
506 * Reload backend options from System properties.
507 */
508 public void reloadOptions() {
509 // NOP
510 }
511
512 // ------------------------------------------------------------------------
513 // TWindowBackend ---------------------------------------------------------
514 // ------------------------------------------------------------------------
515
516 /**
517 * Set the object to sync to in draw().
518 *
519 * @param drawLock the object to synchronize on
520 */
521 public void setDrawLock(final Object drawLock) {
522 this.drawLock = drawLock;
523 }
524
525 /**
526 * Getter for the other application's screen.
527 *
528 * @return the Screen
529 */
530 public Screen getOtherScreen() {
531 return otherScreen;
532 }
533
534 /**
535 * Set the other screen's application.
536 *
537 * @param application the application driving the other screen
538 */
539 public void setOtherApplication(final TApplication application) {
540 this.otherApplication = application;
541 }
542
543 }