Expose width/height in TApplication constructor, attempt on ECMA48
[fanfix.git] / src / jexer / backend / TWindowBackend.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2017 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.TApplication;
39 import jexer.TWindow;
40
41 /**
42 * TWindowBackend uses a window in one TApplication to provide a backend for
43 * another TApplication.
44 *
45 * Note that TWindow has its own getScreen() and setTitle() functions.
46 * Clients in TWindowBackend's application won't be able to use it to get at
47 * the other application's screen. getOtherScreen() has been provided.
48 */
49 public class TWindowBackend extends TWindow implements Backend {
50
51 /**
52 * The listening object that run() wakes up on new input.
53 */
54 private Object listener;
55
56 /**
57 * The object to sync on in draw(). This is normally otherScreen, but it
58 * could also be a MultiScreen.
59 */
60 private Object drawLock;
61
62 /**
63 * The event queue, filled up by a thread reading on input.
64 */
65 private List<TInputEvent> eventQueue;
66
67 /**
68 * The screen to use.
69 */
70 private Screen otherScreen;
71
72 /**
73 * The mouse X position as seen on the other screen.
74 */
75 private int otherMouseX = -1;
76
77 /**
78 * The mouse Y position as seen on the other screen.
79 */
80 private int otherMouseY = -1;
81
82 /**
83 * The session information.
84 */
85 private SessionInfo sessionInfo;
86
87 /**
88 * Set the object to sync to in draw().
89 *
90 * @param drawLock the object to synchronize on
91 */
92 public void setDrawLock(final Object drawLock) {
93 this.drawLock = drawLock;
94 }
95
96 /**
97 * Getter for the other application's screen.
98 *
99 * @return the Screen
100 */
101 public Screen getOtherScreen() {
102 return otherScreen;
103 }
104
105 /**
106 * Getter for sessionInfo.
107 *
108 * @return the SessionInfo
109 */
110 public final SessionInfo getSessionInfo() {
111 return sessionInfo;
112 }
113
114 /**
115 * Public constructor. Window will be located at (0, 0).
116 *
117 * @param listener the object this backend needs to wake up when new
118 * input comes in
119 * @param application TApplication that manages this window
120 * @param title window title, will be centered along the top border
121 * @param width width of window
122 * @param height height of window
123 */
124 public TWindowBackend(final Object listener,
125 final TApplication application, final String title,
126 final int width, final int height) {
127
128 super(application, title, width, height);
129
130 this.listener = listener;
131 eventQueue = new LinkedList<TInputEvent>();
132 sessionInfo = new TSessionInfo(width, height);
133 otherScreen = new LogicalScreen();
134 otherScreen.setDimensions(width - 2, height - 2);
135 drawLock = otherScreen;
136 }
137
138 /**
139 * Public constructor. Window will be located at (0, 0).
140 *
141 * @param listener the object this backend needs to wake up when new
142 * input comes in
143 * @param application TApplication that manages this window
144 * @param title window title, will be centered along the top border
145 * @param width width of window
146 * @param height height of window
147 * @param flags bitmask of RESIZABLE, CENTERED, or MODAL
148 */
149 public TWindowBackend(final Object listener,
150 final TApplication application, final String title,
151 final int width, final int height, final int flags) {
152
153 super(application, title, width, height, flags);
154
155 this.listener = listener;
156 eventQueue = new LinkedList<TInputEvent>();
157 sessionInfo = new TSessionInfo(width, height);
158 otherScreen = new LogicalScreen();
159 otherScreen.setDimensions(width - 2, height - 2);
160 drawLock = otherScreen;
161 }
162
163 /**
164 * Public constructor.
165 *
166 * @param listener the object this backend needs to wake up when new
167 * input comes in
168 * @param application TApplication that manages this window
169 * @param title window title, will be centered along the top border
170 * @param x column relative to parent
171 * @param y row relative to parent
172 * @param width width of window
173 * @param height height of window
174 */
175 public TWindowBackend(final Object listener,
176 final TApplication application, final String title,
177 final int x, final int y, final int width, final int height) {
178
179 super(application, title, x, y, width, height);
180
181 this.listener = listener;
182 eventQueue = new LinkedList<TInputEvent>();
183 sessionInfo = new TSessionInfo(width, height);
184 otherScreen = new LogicalScreen();
185 otherScreen.setDimensions(width - 2, height - 2);
186 drawLock = otherScreen;
187 }
188
189 /**
190 * Public constructor.
191 *
192 * @param listener the object this backend needs to wake up when new
193 * input comes in
194 * @param application TApplication that manages this window
195 * @param title window title, will be centered along the top border
196 * @param x column relative to parent
197 * @param y row relative to parent
198 * @param width width of window
199 * @param height height of window
200 * @param flags mask of RESIZABLE, CENTERED, or MODAL
201 */
202 public TWindowBackend(final Object listener,
203 final TApplication application, final String title,
204 final int x, final int y, final int width, final int height,
205 final int flags) {
206
207 super(application, title, x, y, width, height, flags);
208
209 this.listener = listener;
210 eventQueue = new LinkedList<TInputEvent>();
211 sessionInfo = new TSessionInfo(width, height);
212 otherScreen = new LogicalScreen();
213 otherScreen.setDimensions(width - 2, height - 2);
214 drawLock = otherScreen;
215 }
216
217 /**
218 * Subclasses must provide an implementation that syncs the logical
219 * screen to the physical device.
220 */
221 public void flushScreen() {
222 getApplication().doRepaint();
223 }
224
225 /**
226 * Subclasses must provide an implementation to get keyboard, mouse, and
227 * screen resize events.
228 *
229 * @param queue list to append new events to
230 */
231 public void getEvents(List<TInputEvent> queue) {
232 synchronized (eventQueue) {
233 if (eventQueue.size() > 0) {
234 synchronized (queue) {
235 queue.addAll(eventQueue);
236 }
237 eventQueue.clear();
238 }
239 }
240 }
241
242 /**
243 * Subclasses must provide an implementation that closes sockets,
244 * restores console, etc.
245 */
246 public void shutdown() {
247 // NOP
248 }
249
250 /**
251 * Set listener to a different Object.
252 *
253 * @param listener the new listening object that run() wakes up on new
254 * input
255 */
256 public void setListener(final Object listener) {
257 this.listener = listener;
258 }
259
260 /**
261 * Draw the foreground colors grid.
262 */
263 @Override
264 public void draw() {
265
266 // Sync on other screen, so that we do not draw in the middle of
267 // their screen update.
268 synchronized (drawLock) {
269 // Draw the box
270 super.draw();
271
272 // Draw every cell of the other screen
273 for (int y = 0; y < otherScreen.getHeight(); y++) {
274 for (int x = 0; x < otherScreen.getWidth(); x++) {
275 putCharXY(x + 1, y + 1, otherScreen.getCharXY(x, y));
276 }
277 }
278
279 // If the mouse pointer is over the other window, draw its
280 // pointer again here. (Their TApplication drew it, then our
281 // TApplication drew it again (undo-ing it), so now we draw it a
282 // third time so that it is visible.)
283 if ((otherMouseX != -1) && (otherMouseY != -1)) {
284 CellAttributes attr = getAttrXY(otherMouseX, otherMouseY);
285 attr.setForeColor(attr.getForeColor().invert());
286 attr.setBackColor(attr.getBackColor().invert());
287 putAttrXY(otherMouseX, otherMouseY, attr, false);
288 }
289
290 // If their cursor is visible, draw that here too.
291 if (otherScreen.isCursorVisible()) {
292 setCursorX(otherScreen.getCursorX() + 1);
293 setCursorY(otherScreen.getCursorY() + 1);
294 setCursorVisible(true);
295 } else {
296 setCursorVisible(false);
297 }
298 }
299 }
300
301 /**
302 * Subclasses should override this method to cleanup resources. This is
303 * called by application.closeWindow().
304 */
305 public void onClose() {
306 // TODO: send a screen disconnect
307 }
308
309 /**
310 * Returns true if the mouse is currently in the otherScreen window.
311 *
312 * @param mouse mouse event
313 * @return true if mouse is currently in the otherScreen window.
314 */
315 protected boolean mouseOnOtherScreen(final TMouseEvent mouse) {
316 if ((mouse.getY() >= 1)
317 && (mouse.getY() <= otherScreen.getHeight())
318 && (mouse.getX() >= 1)
319 && (mouse.getX() <= otherScreen.getWidth())
320 ) {
321 return true;
322 }
323 return false;
324 }
325
326 /**
327 * Handle mouse button presses.
328 *
329 * @param mouse mouse button event
330 */
331 @Override
332 public void onMouseDown(final TMouseEvent mouse) {
333 if (mouseOnOtherScreen(mouse)) {
334 TMouseEvent event = mouse.dup();
335 event.setX(mouse.getX() - 1);
336 event.setY(mouse.getY() - 1);
337 event.setAbsoluteX(event.getX());
338 event.setAbsoluteY(event.getY());
339 synchronized (eventQueue) {
340 eventQueue.add(event);
341 }
342 synchronized (listener) {
343 listener.notifyAll();
344 }
345 }
346 super.onMouseDown(mouse);
347 }
348
349 /**
350 * Handle mouse button releases.
351 *
352 * @param mouse mouse button release event
353 */
354 @Override
355 public void onMouseUp(final TMouseEvent mouse) {
356 if (mouseOnOtherScreen(mouse)) {
357 TMouseEvent event = mouse.dup();
358 event.setX(mouse.getX() - 1);
359 event.setY(mouse.getY() - 1);
360 event.setAbsoluteX(event.getX());
361 event.setAbsoluteY(event.getY());
362 synchronized (eventQueue) {
363 eventQueue.add(event);
364 }
365 synchronized (listener) {
366 listener.notifyAll();
367 }
368 }
369 super.onMouseUp(mouse);
370 }
371
372 /**
373 * Handle mouse movements.
374 *
375 * @param mouse mouse motion event
376 */
377 @Override
378 public void onMouseMotion(final TMouseEvent mouse) {
379 if (mouseOnOtherScreen(mouse)) {
380 TMouseEvent event = mouse.dup();
381 event.setX(mouse.getX() - 1);
382 event.setY(mouse.getY() - 1);
383 event.setAbsoluteX(event.getX());
384 event.setAbsoluteY(event.getY());
385 otherMouseX = event.getX() + getX() + 1;
386 otherMouseY = event.getY() + getY() + 1;
387 synchronized (eventQueue) {
388 eventQueue.add(event);
389 }
390 synchronized (listener) {
391 listener.notifyAll();
392 }
393 } else {
394 otherMouseX = -1;
395 otherMouseY = -1;
396 }
397 super.onMouseMotion(mouse);
398 }
399
400 /**
401 * Handle keystrokes.
402 *
403 * @param keypress keystroke event
404 */
405 @Override
406 public void onKeypress(final TKeypressEvent keypress) {
407 TKeypressEvent event = keypress.dup();
408 synchronized (eventQueue) {
409 eventQueue.add(event);
410 }
411 synchronized (listener) {
412 listener.notifyAll();
413 }
414 }
415
416 }