update roadmap
[fanfix.git] / src / jexer / TApplication.java
CommitLineData
7d4115a5
KL
1/**
2 * Jexer - Java Text User Interface - demonstration program
3 *
4 * Version: $Id$
5 *
6 * Author: Kevin Lamonte, <a href="mailto:kevin.lamonte@gmail.com">kevin.lamonte@gmail.com</a>
7 *
8 * License: LGPLv3 or later
9 *
10 * Copyright: This module is licensed under the GNU Lesser General
11 * Public License Version 3. Please see the file "COPYING" in this
12 * directory for more information about the GNU Lesser General Public
13 * License Version 3.
14 *
15 * Copyright (C) 2015 Kevin Lamonte
16 *
17 * This program is free software; you can redistribute it and/or
18 * modify it under the terms of the GNU Lesser General Public License
19 * as published by the Free Software Foundation; either version 3 of
20 * the License, or (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful, but
23 * WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
25 * General Public License for more details.
26 *
27 * You should have received a copy of the GNU Lesser General Public
28 * License along with this program; if not, see
29 * http://www.gnu.org/licenses/, or write to the Free Software
30 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
31 * 02110-1301 USA
32 */
33package jexer;
34
4328bb42
KL
35import java.io.InputStream;
36import java.io.OutputStream;
37import java.io.UnsupportedEncodingException;
38import java.util.LinkedList;
39import java.util.List;
40
41import jexer.bits.CellAttributes;
42import jexer.bits.ColorTheme;
43import jexer.bits.GraphicsChars;
44import jexer.event.TCommandEvent;
45import jexer.event.TInputEvent;
46import jexer.event.TKeypressEvent;
47import jexer.event.TMouseEvent;
48import jexer.event.TResizeEvent;
49import jexer.backend.Backend;
50import jexer.backend.ECMA48Backend;
51import static jexer.TCommand.*;
52import static jexer.TKeypress.*;
53
7d4115a5
KL
54/**
55 * TApplication sets up a full Text User Interface application.
56 */
57public class TApplication {
58
59 /**
4328bb42
KL
60 * Access to the physical screen, keyboard, and mouse.
61 */
62 public Backend backend;
63
64 /**
65 * Actual mouse coordinate X
66 */
67 private int mouseX;
68
69 /**
70 * Actual mouse coordinate Y
71 */
72 private int mouseY;
73
74 /**
75 * Event queue that will be drained by either primary or secondary Fiber
76 */
77 private List<TInputEvent> eventQueue;
78
79 /**
80 * Windows and widgets pull colors from this ColorTheme.
81 */
82 public ColorTheme theme;
83
84 /**
85 * When true, exit the application.
86 */
87 public boolean quit = false;
88
89 /**
90 * When true, repaint the entire screen.
91 */
92 public boolean repaint = true;
93
94 /**
95 * When true, just flush updates from the screen.
7d4115a5 96 */
4328bb42
KL
97 public boolean flush = false;
98
99 /**
100 * Y coordinate of the top edge of the desktop.
101 */
102 static public final int desktopTop = 1;
103
104 /**
105 * Y coordinate of the bottom edge of the desktop.
106 */
107 public int desktopBottom;
108
109 /**
110 * Public constructor.
111 *
112 * @param input an InputStream connected to the remote user, or null for
113 * System.in. If System.in is used, then on non-Windows systems it will
114 * be put in raw mode; shutdown() will (blindly!) put System.in in cooked
115 * mode. input is always converted to a Reader with UTF-8 encoding.
116 * @param output an OutputStream connected to the remote user, or null
117 * for System.out. output is always converted to a Writer with UTF-8
118 * encoding.
119 */
120 public TApplication(InputStream input, OutputStream output) throws UnsupportedEncodingException {
121
122 backend = new ECMA48Backend(input, output);
123 theme = new ColorTheme();
124 desktopBottom = backend.screen.getHeight() - 1;
125 eventQueue = new LinkedList<TInputEvent>();
126 }
127
128 /**
129 * Invert the cell at the mouse pointer position.
130 */
131 private void drawMouse() {
132 CellAttributes attr = backend.screen.getAttrXY(mouseX, mouseY);
133 attr.foreColor = attr.foreColor.invert();
134 attr.backColor = attr.backColor.invert();
135 backend.screen.putAttrXY(mouseX, mouseY, attr, false);
136 flush = true;
7d4115a5
KL
137
138 /*
4328bb42
KL
139 if (windows.length == 0) {
140 repaint = true;
141 }
142 */
143 // TODO: remove this repaint after the above if (windows.length == 0)
144 // can be used again.
145 repaint = true;
146 }
147
148 /**
149 * Draw everything.
150 */
151 final public void drawAll() {
152 if ((flush) && (!repaint)) {
153 backend.flushScreen();
154 flush = false;
155 return;
156 }
157
158 if (!repaint) {
159 return;
160 }
161
162 // If true, the cursor is not visible
163 boolean cursor = false;
164
165 // Start with a clean screen
166 backend.screen.clear();
167
168 // Draw the background
169 CellAttributes background = theme.getColor("tapplication.background");
170 backend.screen.putAll(GraphicsChars.HATCH, background);
171
172 /*
173 // Draw each window in reverse Z order
174 TWindow [] sorted = windows.dup;
175 sorted.sort.reverse;
176 foreach (w; sorted) {
177 w.drawChildren();
178 }
179
180 // Draw the blank menubar line - reset the screen clipping first so
181 // it won't trim it out.
182 backend.screen.resetClipping();
183 backend.screen.hLineXY(0, 0, backend.screen.getWidth(), ' ',
184 theme.getColor("tmenu"));
185 // Now draw the menus.
186 int x = 1;
187 foreach (m; menus) {
188 CellAttributes menuColor;
189 CellAttributes menuMnemonicColor;
190 if (m.active) {
191 menuColor = theme.getColor("tmenu.highlighted");
192 menuMnemonicColor = theme.getColor("tmenu.mnemonic.highlighted");
193 } else {
194 menuColor = theme.getColor("tmenu");
195 menuMnemonicColor = theme.getColor("tmenu.mnemonic");
196 }
197 // Draw the menu title
198 backend.screen.hLineXY(x, 0, cast(int)m.title.length + 2, ' ',
199 menuColor);
200 backend.screen.putStrXY(x + 1, 0, m.title, menuColor);
201 // Draw the highlight character
202 backend.screen.putCharXY(x + 1 + m.mnemonic.shortcutIdx, 0,
203 m.mnemonic.shortcut, menuMnemonicColor);
204
205 if (m.active) {
206 m.drawChildren();
207 // Reset the screen clipping so we can draw the next title.
208 backend.screen.resetClipping();
209 }
210 x += m.title.length + 2;
211 }
212
213 foreach (m; subMenus) {
214 // Reset the screen clipping so we can draw the next sub-menu.
215 backend.screen.resetClipping();
216 m.drawChildren();
217 }
218 */
219
220 // Draw the mouse pointer
221 drawMouse();
7d4115a5 222
4328bb42
KL
223 /*
224 // Place the cursor if it is visible
225 TWidget activeWidget = null;
226 if (sorted.length > 0) {
227 activeWidget = sorted[$ - 1].getActiveChild();
228 if (activeWidget.hasCursor) {
229 backend.screen.putCursor(true, activeWidget.getCursorAbsoluteX(),
230 activeWidget.getCursorAbsoluteY());
231 cursor = true;
232 }
233 }
234
235 // Kill the cursor
236 if (cursor == false) {
237 backend.screen.hideCursor();
238 }
239 */
240
241 // Flush the screen contents
242 backend.flushScreen();
243
244 repaint = false;
245 flush = false;
246 }
247
248 /**
249 * Run this application until it exits, using stdin and stdout
250 */
251 public final void run() {
623a1bd1
KL
252 List<TInputEvent> events = new LinkedList<TInputEvent>();
253
4328bb42
KL
254 while (quit == false) {
255 // Timeout is in milliseconds, so default timeout after 1 second
256 // of inactivity.
257 int timeout = getSleepTime(1000);
7d4115a5 258
4328bb42 259 if (eventQueue.size() > 0) {
7d4115a5
KL
260 // Do not wait if there are definitely events waiting to be
261 // processed or a screen redraw to do.
262 timeout = 0;
263 }
264
265 // Pull any pending input events
623a1bd1 266 backend.getEvents(events, timeout);
7d4115a5 267 metaHandleEvents(events);
623a1bd1 268 events.clear();
7d4115a5
KL
269
270 // Process timers and call doIdle()'s
271 doIdle();
272
273 // Update the screen
274 drawAll();
275 }
276
4328bb42
KL
277 /*
278
7d4115a5
KL
279 // Shutdown the fibers
280 eventQueue.length = 0;
281 if (secondaryEventFiber !is null) {
282 assert(secondaryEventReceiver !is null);
283 secondaryEventReceiver = null;
284 if (secondaryEventFiber.state == Fiber.State.HOLD) {
285 // Wake up the secondary handler so that it can exit.
286 secondaryEventFiber.call();
287 }
288 }
289
290 if (primaryEventFiber.state == Fiber.State.HOLD) {
291 // Wake up the primary handler so that it can exit.
292 primaryEventFiber.call();
293 }
4328bb42 294 */
7d4115a5
KL
295
296 backend.shutdown();
4328bb42
KL
297 }
298
299 /**
300 * Peek at certain application-level events, add to eventQueue, and wake
301 * up the consuming Fiber.
302 *
303 * @param events the input events to consume
304 */
305 private void metaHandleEvents(List<TInputEvent> events) {
306
307 for (TInputEvent event: events) {
308
309 /*
310 System.err.printf(String.format("metaHandleEvents event: %s\n",
311 event)); System.err.flush();
312 */
313
314 if (quit == true) {
315 // Do no more processing if the application is already trying
316 // to exit.
317 return;
318 }
319
320 // DEBUG
321 if (event instanceof TKeypressEvent) {
322 TKeypressEvent keypress = (TKeypressEvent)event;
323 if (keypress.key.equals(kbAltX)) {
324 quit = true;
325 return;
326 }
327 }
328 // DEBUG
329
330 // Special application-wide events -------------------------------
331
332 // Abort everything
333 if (event instanceof TCommandEvent) {
334 TCommandEvent command = (TCommandEvent)event;
335 if (command.cmd.equals(cmAbort)) {
336 quit = true;
337 return;
338 }
339 }
340
341 // Screen resize
342 if (event instanceof TResizeEvent) {
343 TResizeEvent resize = (TResizeEvent)event;
344 backend.screen.setDimensions(resize.width, resize.height);
345 desktopBottom = backend.screen.getHeight() - 1;
346 repaint = true;
347 mouseX = 0;
348 mouseY = 0;
349 continue;
350 }
351
352 // Peek at the mouse position
353 if (event instanceof TMouseEvent) {
354 TMouseEvent mouse = (TMouseEvent)event;
355 if ((mouseX != mouse.x) || (mouseY != mouse.y)) {
356 mouseX = mouse.x;
357 mouseY = mouse.y;
358 drawMouse();
359 }
360 }
361
362 /*
363
364 // Put into the main queue
365 addEvent(event);
366
367 // Have one of the two consumer Fibers peel the events off
368 // the queue.
369 if (secondaryEventFiber !is null) {
370 assert(secondaryEventFiber.state == Fiber.State.HOLD);
371
372 // Wake up the secondary handler for these events
373 secondaryEventFiber.call();
374 } else {
375 assert(primaryEventFiber.state == Fiber.State.HOLD);
376
377 // Wake up the primary handler for these events
378 primaryEventFiber.call();
379 }
380 */
381
382 } // for (TInputEvent event: events)
383
384 }
385
386 /**
387 * Do stuff when there is no user input.
388 */
389 private void doIdle() {
390 /*
391 // Now run any timers that have timed out
392 auto now = Clock.currTime;
393 TTimer [] keepTimers;
394 foreach (t; timers) {
395 if (t.nextTick < now) {
396 t.tick();
397 if (t.recurring == true) {
398 keepTimers ~= t;
399 }
400 } else {
401 keepTimers ~= t;
402 }
403 }
404 timers = keepTimers;
405
406 // Call onIdle's
407 foreach (w; windows) {
408 w.onIdle();
409 }
7d4115a5 410 */
4328bb42 411 }
7d4115a5 412
4328bb42
KL
413 /**
414 * Get the amount of time I can sleep before missing a Timer tick.
415 *
416 * @param timeout = initial (maximum) timeout
417 * @return number of milliseconds between now and the next timer event
418 */
419 protected int getSleepTime(int timeout) {
420 /*
421 auto now = Clock.currTime;
422 auto sleepTime = dur!("msecs")(timeout);
423 foreach (t; timers) {
424 if (t.nextTick < now) {
425 return 0;
426 }
427 if ((t.nextTick > now) &&
428 ((t.nextTick - now) < sleepTime)
429 ) {
430 sleepTime = t.nextTick - now;
431 }
432 }
433 assert(sleepTime.total!("msecs")() >= 0);
434 return cast(uint)sleepTime.total!("msecs")();
435 */
623a1bd1
KL
436 // TODO: fix timers. Until then, come back after 250 millis.
437 return 250;
7d4115a5 438 }
4328bb42 439
7d4115a5 440}