Commit | Line | Data |
---|---|---|
7d4115a5 | 1 | /** |
7b5261bc | 2 | * Jexer - Java Text User Interface |
7d4115a5 KL |
3 | * |
4 | * License: LGPLv3 or later | |
5 | * | |
7b5261bc KL |
6 | * This module is licensed under the GNU Lesser General Public License |
7 | * Version 3. Please see the file "COPYING" in this directory for more | |
8 | * information about the GNU Lesser General Public License Version 3. | |
7d4115a5 KL |
9 | * |
10 | * Copyright (C) 2015 Kevin Lamonte | |
11 | * | |
12 | * This program is free software; you can redistribute it and/or | |
13 | * modify it under the terms of the GNU Lesser General Public License | |
14 | * as published by the Free Software Foundation; either version 3 of | |
15 | * the License, or (at your option) any later version. | |
16 | * | |
17 | * This program is distributed in the hope that it will be useful, but | |
18 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
20 | * General Public License for more details. | |
21 | * | |
22 | * You should have received a copy of the GNU Lesser General Public | |
23 | * License along with this program; if not, see | |
24 | * http://www.gnu.org/licenses/, or write to the Free Software | |
25 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | |
26 | * 02110-1301 USA | |
7b5261bc KL |
27 | * |
28 | * @author Kevin Lamonte [kevin.lamonte@gmail.com] | |
29 | * @version 1 | |
7d4115a5 KL |
30 | */ |
31 | package jexer; | |
32 | ||
4328bb42 KL |
33 | import java.io.InputStream; |
34 | import java.io.OutputStream; | |
35 | import java.io.UnsupportedEncodingException; | |
a06459bd | 36 | import java.util.Collections; |
4328bb42 KL |
37 | import java.util.LinkedList; |
38 | import java.util.List; | |
39 | ||
40 | import jexer.bits.CellAttributes; | |
41 | import jexer.bits.ColorTheme; | |
42 | import jexer.bits.GraphicsChars; | |
43 | import jexer.event.TCommandEvent; | |
44 | import jexer.event.TInputEvent; | |
45 | import jexer.event.TKeypressEvent; | |
46 | import jexer.event.TMouseEvent; | |
47 | import jexer.event.TResizeEvent; | |
48 | import jexer.backend.Backend; | |
49 | import jexer.backend.ECMA48Backend; | |
48e27807 | 50 | import jexer.io.Screen; |
4328bb42 KL |
51 | import static jexer.TCommand.*; |
52 | import static jexer.TKeypress.*; | |
53 | ||
7d4115a5 KL |
54 | /** |
55 | * TApplication sets up a full Text User Interface application. | |
56 | */ | |
57 | public class TApplication { | |
58 | ||
59 | /** | |
4328bb42 KL |
60 | * Access to the physical screen, keyboard, and mouse. |
61 | */ | |
7b5261bc | 62 | private Backend backend; |
4328bb42 | 63 | |
48e27807 KL |
64 | /** |
65 | * Get the Screen. | |
66 | * | |
67 | * @return the Screen | |
68 | */ | |
69 | public final Screen getScreen() { | |
70 | return backend.getScreen(); | |
71 | } | |
72 | ||
4328bb42 | 73 | /** |
7b5261bc | 74 | * Actual mouse coordinate X. |
4328bb42 KL |
75 | */ |
76 | private int mouseX; | |
77 | ||
78 | /** | |
7b5261bc | 79 | * Actual mouse coordinate Y. |
4328bb42 KL |
80 | */ |
81 | private int mouseY; | |
82 | ||
83 | /** | |
7b5261bc | 84 | * Event queue that will be drained by either primary or secondary Fiber. |
4328bb42 KL |
85 | */ |
86 | private List<TInputEvent> eventQueue; | |
87 | ||
88 | /** | |
89 | * Windows and widgets pull colors from this ColorTheme. | |
90 | */ | |
7b5261bc KL |
91 | private ColorTheme theme; |
92 | ||
93 | /** | |
94 | * Get the color theme. | |
95 | * | |
96 | * @return the theme | |
97 | */ | |
98 | public final ColorTheme getTheme() { | |
99 | return theme; | |
100 | } | |
4328bb42 | 101 | |
a06459bd KL |
102 | /** |
103 | * The top-level windows (but not menus). | |
104 | */ | |
105 | List<TWindow> windows; | |
106 | ||
4328bb42 KL |
107 | /** |
108 | * When true, exit the application. | |
109 | */ | |
48e27807 | 110 | private boolean quit = false; |
4328bb42 KL |
111 | |
112 | /** | |
113 | * When true, repaint the entire screen. | |
114 | */ | |
48e27807 KL |
115 | private boolean repaint = true; |
116 | ||
117 | /** | |
118 | * Request full repaint on next screen refresh. | |
119 | */ | |
120 | public void setRepaint() { | |
121 | repaint = true; | |
122 | } | |
4328bb42 KL |
123 | |
124 | /** | |
125 | * When true, just flush updates from the screen. | |
7d4115a5 | 126 | */ |
48e27807 | 127 | private boolean flush = false; |
4328bb42 KL |
128 | |
129 | /** | |
7b5261bc KL |
130 | * Y coordinate of the top edge of the desktop. For now this is a |
131 | * constant. Someday it would be nice to have a multi-line menu or | |
132 | * toolbars. | |
4328bb42 | 133 | */ |
48e27807 KL |
134 | private static final int desktopTop = 1; |
135 | ||
136 | /** | |
137 | * Get Y coordinate of the top edge of the desktop. | |
138 | * | |
139 | * @return Y coordinate of the top edge of the desktop | |
140 | */ | |
141 | public final int getDesktopTop() { | |
142 | return desktopTop; | |
143 | } | |
4328bb42 KL |
144 | |
145 | /** | |
146 | * Y coordinate of the bottom edge of the desktop. | |
147 | */ | |
48e27807 KL |
148 | private int desktopBottom; |
149 | ||
150 | /** | |
151 | * Get Y coordinate of the bottom edge of the desktop. | |
152 | * | |
153 | * @return Y coordinate of the bottom edge of the desktop | |
154 | */ | |
155 | public final int getDesktopBottom() { | |
156 | return desktopBottom; | |
157 | } | |
4328bb42 KL |
158 | |
159 | /** | |
160 | * Public constructor. | |
161 | * | |
162 | * @param input an InputStream connected to the remote user, or null for | |
163 | * System.in. If System.in is used, then on non-Windows systems it will | |
164 | * be put in raw mode; shutdown() will (blindly!) put System.in in cooked | |
165 | * mode. input is always converted to a Reader with UTF-8 encoding. | |
166 | * @param output an OutputStream connected to the remote user, or null | |
167 | * for System.out. output is always converted to a Writer with UTF-8 | |
168 | * encoding. | |
7b5261bc KL |
169 | * @throws UnsupportedEncodingException if an exception is thrown when |
170 | * creating the InputStreamReader | |
4328bb42 | 171 | */ |
7b5261bc KL |
172 | public TApplication(final InputStream input, |
173 | final OutputStream output) throws UnsupportedEncodingException { | |
4328bb42 | 174 | |
7b5261bc KL |
175 | backend = new ECMA48Backend(input, output); |
176 | theme = new ColorTheme(); | |
a06459bd | 177 | desktopBottom = getScreen().getHeight() - 1; |
7b5261bc | 178 | eventQueue = new LinkedList<TInputEvent>(); |
a06459bd | 179 | windows = new LinkedList<TWindow>(); |
4328bb42 KL |
180 | } |
181 | ||
182 | /** | |
183 | * Invert the cell at the mouse pointer position. | |
184 | */ | |
185 | private void drawMouse() { | |
a06459bd | 186 | CellAttributes attr = getScreen().getAttrXY(mouseX, mouseY); |
7b5261bc KL |
187 | attr.setForeColor(attr.getForeColor().invert()); |
188 | attr.setBackColor(attr.getBackColor().invert()); | |
a06459bd | 189 | getScreen().putAttrXY(mouseX, mouseY, attr, false); |
7b5261bc KL |
190 | flush = true; |
191 | ||
a06459bd | 192 | if (windows.size() == 0) { |
7b5261bc KL |
193 | repaint = true; |
194 | } | |
4328bb42 KL |
195 | } |
196 | ||
197 | /** | |
198 | * Draw everything. | |
199 | */ | |
7b5261bc KL |
200 | public final void drawAll() { |
201 | if ((flush) && (!repaint)) { | |
202 | backend.flushScreen(); | |
203 | flush = false; | |
204 | return; | |
205 | } | |
206 | ||
207 | if (!repaint) { | |
208 | return; | |
209 | } | |
210 | ||
211 | // If true, the cursor is not visible | |
212 | boolean cursor = false; | |
213 | ||
214 | // Start with a clean screen | |
a06459bd | 215 | getScreen().clear(); |
7b5261bc KL |
216 | |
217 | // Draw the background | |
218 | CellAttributes background = theme.getColor("tapplication.background"); | |
a06459bd | 219 | getScreen().putAll(GraphicsChars.HATCH, background); |
7b5261bc | 220 | |
7b5261bc | 221 | // Draw each window in reverse Z order |
a06459bd KL |
222 | List<TWindow> sorted = new LinkedList<TWindow>(windows); |
223 | Collections.sort(sorted); | |
224 | Collections.reverse(sorted); | |
225 | for (TWindow window: sorted) { | |
226 | window.drawChildren(); | |
7b5261bc KL |
227 | } |
228 | ||
a06459bd | 229 | /* |
7b5261bc KL |
230 | // Draw the blank menubar line - reset the screen clipping first so |
231 | // it won't trim it out. | |
a06459bd KL |
232 | getScreen().resetClipping(); |
233 | getScreen().hLineXY(0, 0, getScreen().getWidth(), ' ', | |
7b5261bc KL |
234 | theme.getColor("tmenu")); |
235 | // Now draw the menus. | |
236 | int x = 1; | |
a06459bd | 237 | for (TMenu m: menus) { |
7b5261bc KL |
238 | CellAttributes menuColor; |
239 | CellAttributes menuMnemonicColor; | |
a06459bd | 240 | if (menu.active) { |
7b5261bc KL |
241 | menuColor = theme.getColor("tmenu.highlighted"); |
242 | menuMnemonicColor = theme.getColor("tmenu.mnemonic.highlighted"); | |
243 | } else { | |
244 | menuColor = theme.getColor("tmenu"); | |
245 | menuMnemonicColor = theme.getColor("tmenu.mnemonic"); | |
246 | } | |
247 | // Draw the menu title | |
a06459bd | 248 | getScreen().hLineXY(x, 0, menu.title.length() + 2, ' ', |
7b5261bc | 249 | menuColor); |
a06459bd | 250 | getScreen().putStrXY(x + 1, 0, menu.title, menuColor); |
7b5261bc | 251 | // Draw the highlight character |
a06459bd | 252 | getScreen().putCharXY(x + 1 + m.mnemonic.shortcutIdx, 0, |
7b5261bc KL |
253 | m.mnemonic.shortcut, menuMnemonicColor); |
254 | ||
a06459bd KL |
255 | if (menu.active) { |
256 | menu.drawChildren(); | |
7b5261bc | 257 | // Reset the screen clipping so we can draw the next title. |
a06459bd | 258 | getScreen().resetClipping(); |
7b5261bc | 259 | } |
a06459bd | 260 | x += menu.title.length + 2; |
7b5261bc KL |
261 | } |
262 | ||
a06459bd | 263 | for (TMenu menu: subMenus) { |
7b5261bc | 264 | // Reset the screen clipping so we can draw the next sub-menu. |
a06459bd KL |
265 | getScreen().resetClipping(); |
266 | menu.drawChildren(); | |
7b5261bc | 267 | } |
a06459bd | 268 | */ |
7b5261bc KL |
269 | |
270 | // Draw the mouse pointer | |
271 | drawMouse(); | |
272 | ||
7b5261bc KL |
273 | // Place the cursor if it is visible |
274 | TWidget activeWidget = null; | |
a06459bd KL |
275 | if (sorted.size() > 0) { |
276 | activeWidget = sorted.get(sorted.size() - 1).getActiveChild(); | |
277 | if (activeWidget.visibleCursor()) { | |
278 | getScreen().putCursor(true, activeWidget.getCursorAbsoluteX(), | |
7b5261bc KL |
279 | activeWidget.getCursorAbsoluteY()); |
280 | cursor = true; | |
281 | } | |
282 | } | |
283 | ||
284 | // Kill the cursor | |
285 | if (cursor == false) { | |
a06459bd | 286 | getScreen().hideCursor(); |
7b5261bc | 287 | } |
7b5261bc KL |
288 | |
289 | // Flush the screen contents | |
290 | backend.flushScreen(); | |
291 | ||
292 | repaint = false; | |
293 | flush = false; | |
4328bb42 KL |
294 | } |
295 | ||
296 | /** | |
7b5261bc | 297 | * Run this application until it exits. |
4328bb42 KL |
298 | */ |
299 | public final void run() { | |
7b5261bc KL |
300 | List<TInputEvent> events = new LinkedList<TInputEvent>(); |
301 | ||
302 | while (!quit) { | |
303 | // Timeout is in milliseconds, so default timeout after 1 second | |
304 | // of inactivity. | |
305 | int timeout = getSleepTime(1000); | |
306 | ||
307 | if (eventQueue.size() > 0) { | |
308 | // Do not wait if there are definitely events waiting to be | |
309 | // processed or a screen redraw to do. | |
310 | timeout = 0; | |
311 | } | |
312 | ||
313 | // Pull any pending input events | |
314 | backend.getEvents(events, timeout); | |
315 | metaHandleEvents(events); | |
316 | events.clear(); | |
317 | ||
318 | // Process timers and call doIdle()'s | |
319 | doIdle(); | |
320 | ||
321 | // Update the screen | |
322 | drawAll(); | |
323 | } | |
324 | ||
325 | /* | |
326 | ||
327 | // Shutdown the fibers | |
328 | eventQueue.length = 0; | |
329 | if (secondaryEventFiber !is null) { | |
330 | assert(secondaryEventReceiver !is null); | |
331 | secondaryEventReceiver = null; | |
332 | if (secondaryEventFiber.state == Fiber.State.HOLD) { | |
333 | // Wake up the secondary handler so that it can exit. | |
334 | secondaryEventFiber.call(); | |
335 | } | |
336 | } | |
337 | ||
338 | if (primaryEventFiber.state == Fiber.State.HOLD) { | |
339 | // Wake up the primary handler so that it can exit. | |
340 | primaryEventFiber.call(); | |
341 | } | |
342 | */ | |
343 | ||
344 | backend.shutdown(); | |
4328bb42 KL |
345 | } |
346 | ||
347 | /** | |
348 | * Peek at certain application-level events, add to eventQueue, and wake | |
349 | * up the consuming Fiber. | |
350 | * | |
351 | * @param events the input events to consume | |
352 | */ | |
7b5261bc KL |
353 | private void metaHandleEvents(final List<TInputEvent> events) { |
354 | ||
355 | for (TInputEvent event: events) { | |
356 | ||
357 | /* | |
358 | System.err.printf(String.format("metaHandleEvents event: %s\n", | |
359 | event)); System.err.flush(); | |
360 | */ | |
361 | ||
362 | if (quit) { | |
363 | // Do no more processing if the application is already trying | |
364 | // to exit. | |
365 | return; | |
366 | } | |
367 | ||
368 | // DEBUG | |
369 | if (event instanceof TKeypressEvent) { | |
370 | TKeypressEvent keypress = (TKeypressEvent) event; | |
b299e69c | 371 | if (keypress.equals(kbAltX)) { |
7b5261bc KL |
372 | quit = true; |
373 | return; | |
374 | } | |
375 | } | |
376 | // DEBUG | |
377 | ||
378 | // Special application-wide events ------------------------------- | |
379 | ||
380 | // Abort everything | |
381 | if (event instanceof TCommandEvent) { | |
382 | TCommandEvent command = (TCommandEvent) event; | |
383 | if (command.getCmd().equals(cmAbort)) { | |
384 | quit = true; | |
385 | return; | |
386 | } | |
387 | } | |
388 | ||
389 | // Screen resize | |
390 | if (event instanceof TResizeEvent) { | |
391 | TResizeEvent resize = (TResizeEvent) event; | |
a06459bd | 392 | getScreen().setDimensions(resize.getWidth(), |
7b5261bc | 393 | resize.getHeight()); |
a06459bd | 394 | desktopBottom = getScreen().getHeight() - 1; |
7b5261bc KL |
395 | repaint = true; |
396 | mouseX = 0; | |
397 | mouseY = 0; | |
398 | continue; | |
399 | } | |
400 | ||
401 | // Peek at the mouse position | |
402 | if (event instanceof TMouseEvent) { | |
403 | TMouseEvent mouse = (TMouseEvent) event; | |
d4a29741 KL |
404 | if ((mouseX != mouse.getX()) || (mouseY != mouse.getY())) { |
405 | mouseX = mouse.getX(); | |
406 | mouseY = mouse.getY(); | |
7b5261bc KL |
407 | drawMouse(); |
408 | } | |
409 | } | |
410 | ||
a06459bd KL |
411 | // TODO: change to two separate threads |
412 | handleEvent(event); | |
413 | ||
7b5261bc KL |
414 | /* |
415 | ||
416 | // Put into the main queue | |
417 | addEvent(event); | |
418 | ||
419 | // Have one of the two consumer Fibers peel the events off | |
420 | // the queue. | |
421 | if (secondaryEventFiber !is null) { | |
422 | assert(secondaryEventFiber.state == Fiber.State.HOLD); | |
423 | ||
424 | // Wake up the secondary handler for these events | |
425 | secondaryEventFiber.call(); | |
426 | } else { | |
427 | assert(primaryEventFiber.state == Fiber.State.HOLD); | |
428 | ||
429 | // Wake up the primary handler for these events | |
430 | primaryEventFiber.call(); | |
431 | } | |
432 | */ | |
433 | ||
434 | } // for (TInputEvent event: events) | |
4328bb42 KL |
435 | |
436 | } | |
437 | ||
a06459bd KL |
438 | /** |
439 | * Dispatch one event to the appropriate widget or application-level | |
440 | * event handler. | |
441 | * | |
442 | * @param event the input event to consume | |
443 | */ | |
444 | private final void handleEvent(TInputEvent event) { | |
445 | ||
446 | /* | |
447 | // std.stdio.stderr.writefln("Handle event: %s", event); | |
448 | ||
449 | // Special application-wide events ----------------------------------- | |
450 | ||
451 | // Peek at the mouse position | |
452 | if (auto mouse = cast(TMouseEvent)event) { | |
453 | // See if we need to switch focus to another window or the menu | |
454 | checkSwitchFocus(mouse); | |
455 | } | |
456 | ||
457 | // Handle menu events | |
458 | if ((activeMenu !is null) && (!cast(TCommandEvent)event)) { | |
459 | TMenu menu = activeMenu; | |
460 | if (auto mouse = cast(TMouseEvent)event) { | |
461 | ||
462 | while (subMenus.length > 0) { | |
463 | TMenu subMenu = subMenus[$ - 1]; | |
464 | if (subMenu.mouseWouldHit(mouse)) { | |
465 | break; | |
466 | } | |
467 | if ((mouse.type == TMouseEvent.Type.MOUSE_MOTION) && | |
468 | (!mouse.mouse1) && | |
469 | (!mouse.mouse2) && | |
470 | (!mouse.mouse3) && | |
471 | (!mouse.mouseWheelUp) && | |
472 | (!mouse.mouseWheelDown) | |
473 | ) { | |
474 | break; | |
475 | } | |
476 | // We navigated away from a sub-menu, so close it | |
477 | closeSubMenu(); | |
478 | } | |
479 | ||
480 | // Convert the mouse relative x/y to menu coordinates | |
481 | assert(mouse.x == mouse.absoluteX); | |
482 | assert(mouse.y == mouse.absoluteY); | |
483 | if (subMenus.length > 0) { | |
484 | menu = subMenus[$ - 1]; | |
485 | } | |
486 | mouse.x -= menu.x; | |
487 | mouse.y -= menu.y; | |
488 | } | |
489 | menu.handleEvent(event); | |
490 | return; | |
491 | } | |
492 | ||
493 | if (auto keypress = cast(TKeypressEvent)event) { | |
494 | // See if this key matches an accelerator, and if so dispatch the | |
495 | // menu event. | |
496 | TKeypress keypressLowercase = toLower(keypress.key); | |
497 | TMenuItem *item = (keypressLowercase in accelerators); | |
498 | if (item !is null) { | |
499 | // Let the menu item dispatch | |
500 | item.dispatch(); | |
501 | return; | |
502 | } else { | |
503 | // Handle the keypress | |
504 | if (onKeypress(keypress)) { | |
505 | return; | |
506 | } | |
507 | } | |
508 | } | |
509 | ||
510 | if (auto cmd = cast(TCommandEvent)event) { | |
511 | if (onCommand(cmd)) { | |
512 | return; | |
513 | } | |
514 | } | |
515 | ||
516 | if (auto menu = cast(TMenuEvent)event) { | |
517 | if (onMenu(menu)) { | |
518 | return; | |
519 | } | |
520 | } | |
521 | */ | |
522 | ||
523 | // Dispatch events to the active window ------------------------------- | |
524 | for (TWindow window: windows) { | |
525 | if (window.active) { | |
526 | if (event instanceof TMouseEvent) { | |
527 | TMouseEvent mouse = (TMouseEvent) event; | |
528 | // Convert the mouse relative x/y to window coordinates | |
529 | assert (mouse.getX() == mouse.getAbsoluteX()); | |
530 | assert (mouse.getY() == mouse.getAbsoluteY()); | |
531 | mouse.setX(mouse.getX() - window.x); | |
532 | mouse.setY(mouse.getY() - window.y); | |
533 | } | |
534 | // System.err("TApplication dispatch event: %s\n", event); | |
535 | window.handleEvent(event); | |
536 | break; | |
537 | } | |
538 | } | |
539 | } | |
540 | ||
4328bb42 KL |
541 | /** |
542 | * Do stuff when there is no user input. | |
543 | */ | |
544 | private void doIdle() { | |
7b5261bc | 545 | /* |
a06459bd | 546 | TODO |
7b5261bc KL |
547 | // Now run any timers that have timed out |
548 | auto now = Clock.currTime; | |
549 | TTimer [] keepTimers; | |
550 | foreach (t; timers) { | |
551 | if (t.nextTick < now) { | |
552 | t.tick(); | |
553 | if (t.recurring == true) { | |
554 | keepTimers ~= t; | |
555 | } | |
556 | } else { | |
557 | keepTimers ~= t; | |
558 | } | |
559 | } | |
560 | timers = keepTimers; | |
561 | ||
562 | // Call onIdle's | |
563 | foreach (w; windows) { | |
564 | w.onIdle(); | |
565 | } | |
566 | */ | |
4328bb42 | 567 | } |
7d4115a5 | 568 | |
4328bb42 KL |
569 | /** |
570 | * Get the amount of time I can sleep before missing a Timer tick. | |
571 | * | |
572 | * @param timeout = initial (maximum) timeout | |
573 | * @return number of milliseconds between now and the next timer event | |
574 | */ | |
7b5261bc KL |
575 | protected int getSleepTime(final int timeout) { |
576 | /* | |
577 | auto now = Clock.currTime; | |
578 | auto sleepTime = dur!("msecs")(timeout); | |
579 | foreach (t; timers) { | |
580 | if (t.nextTick < now) { | |
581 | return 0; | |
582 | } | |
583 | if ((t.nextTick > now) && | |
584 | ((t.nextTick - now) < sleepTime) | |
585 | ) { | |
586 | sleepTime = t.nextTick - now; | |
587 | } | |
588 | } | |
589 | assert(sleepTime.total!("msecs")() >= 0); | |
590 | return cast(uint)sleepTime.total!("msecs")(); | |
591 | */ | |
592 | // TODO: fix timers. Until then, come back after 250 millis. | |
593 | return 250; | |
7d4115a5 | 594 | } |
4328bb42 | 595 | |
48e27807 KL |
596 | /** |
597 | * Close window. Note that the window's destructor is NOT called by this | |
598 | * method, instead the GC is assumed to do the cleanup. | |
599 | * | |
600 | * @param window the window to remove | |
601 | */ | |
602 | public final void closeWindow(final TWindow window) { | |
603 | /* | |
604 | TODO | |
605 | ||
606 | uint z = window.z; | |
607 | window.z = -1; | |
608 | windows.sort; | |
609 | windows = windows[1 .. $]; | |
610 | TWindow activeWindow = null; | |
611 | foreach (w; windows) { | |
612 | if (w.z > z) { | |
613 | w.z--; | |
614 | if (w.z == 0) { | |
615 | w.active = true; | |
616 | assert(activeWindow is null); | |
617 | activeWindow = w; | |
618 | } else { | |
619 | w.active = false; | |
620 | } | |
621 | } | |
622 | } | |
623 | ||
624 | // Perform window cleanup | |
625 | window.onClose(); | |
626 | ||
627 | // Refresh screen | |
628 | repaint = true; | |
629 | ||
630 | // Check if we are closing a TMessageBox or similar | |
631 | if (secondaryEventReceiver !is null) { | |
632 | assert(secondaryEventFiber !is null); | |
633 | ||
634 | // Do not send events to the secondaryEventReceiver anymore, the | |
635 | // window is closed. | |
636 | secondaryEventReceiver = null; | |
637 | ||
638 | // Special case: if this is called while executing on a | |
639 | // secondaryEventFiber, call it so that widgetEventHandler() can | |
640 | // terminate. | |
641 | if (secondaryEventFiber.state == Fiber.State.HOLD) { | |
642 | secondaryEventFiber.call(); | |
643 | } | |
644 | secondaryEventFiber = null; | |
645 | ||
646 | // Unfreeze the logic in handleEvent() | |
647 | if (primaryEventFiber.state == Fiber.State.HOLD) { | |
648 | primaryEventFiber.call(); | |
649 | } | |
650 | } | |
651 | */ | |
652 | } | |
653 | ||
654 | /** | |
655 | * Switch to the next window. | |
656 | * | |
657 | * @param forward if true, then switch to the next window in the list, | |
658 | * otherwise switch to the previous window in the list | |
659 | */ | |
660 | public final void switchWindow(final boolean forward) { | |
661 | /* | |
662 | TODO | |
663 | ||
664 | // Only switch if there are multiple windows | |
665 | if (windows.length < 2) { | |
666 | return; | |
667 | } | |
668 | ||
669 | // Swap z/active between active window and the next in the | |
670 | // list | |
671 | ptrdiff_t activeWindowI = -1; | |
672 | for (auto i = 0; i < windows.length; i++) { | |
673 | if (windows[i].active) { | |
674 | activeWindowI = i; | |
675 | break; | |
676 | } | |
677 | } | |
678 | assert(activeWindowI >= 0); | |
679 | ||
680 | // Do not switch if a window is modal | |
681 | if (windows[activeWindowI].isModal()) { | |
682 | return; | |
683 | } | |
684 | ||
685 | size_t nextWindowI; | |
686 | if (forward) { | |
687 | nextWindowI = (activeWindowI + 1) % windows.length; | |
688 | } else { | |
689 | if (activeWindowI == 0) { | |
690 | nextWindowI = windows.length - 1; | |
691 | } else { | |
692 | nextWindowI = activeWindowI - 1; | |
693 | } | |
694 | } | |
695 | windows[activeWindowI].active = false; | |
696 | windows[activeWindowI].z = windows[nextWindowI].z; | |
697 | windows[nextWindowI].z = 0; | |
698 | windows[nextWindowI].active = true; | |
699 | ||
700 | // Refresh | |
701 | repaint = true; | |
702 | */ | |
703 | } | |
704 | ||
705 | /** | |
706 | * Add a window to my window list and make it active. | |
707 | * | |
708 | * @param window new window to add | |
709 | */ | |
710 | public final void addWindow(final TWindow window) { | |
48e27807 | 711 | // Do not allow a modal window to spawn a non-modal window |
a06459bd KL |
712 | if ((windows.size() > 0) && (windows.get(0).isModal())) { |
713 | assert (window.isModal()); | |
48e27807 | 714 | } |
a06459bd | 715 | for (TWindow w: windows) { |
48e27807 | 716 | w.active = false; |
a06459bd | 717 | w.setZ(w.getZ() + 1); |
48e27807 | 718 | } |
a06459bd | 719 | windows.add(window); |
48e27807 | 720 | window.active = true; |
a06459bd | 721 | window.setZ(0); |
48e27807 KL |
722 | } |
723 | ||
724 | ||
7d4115a5 | 725 | } |