double buffer swing
[nikiroo-utils.git] / src / jexer / io / SwingTerminal.java
1 /**
2 * Jexer - Java Text User Interface
3 *
4 * License: LGPLv3 or later
5 *
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.
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
27 *
28 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 * @version 1
30 */
31 package jexer.io;
32
33 import java.awt.event.ComponentEvent;
34 import java.awt.event.ComponentListener;
35 import java.awt.event.KeyEvent;
36 import java.awt.event.KeyListener;
37 import java.awt.event.MouseEvent;
38 import java.awt.event.MouseListener;
39 import java.awt.event.MouseMotionListener;
40 import java.awt.event.MouseWheelEvent;
41 import java.awt.event.MouseWheelListener;
42 import java.awt.event.WindowEvent;
43 import java.awt.event.WindowListener;
44 import java.util.List;
45 import java.util.LinkedList;
46
47 import jexer.TKeypress;
48 import jexer.event.TCommandEvent;
49 import jexer.event.TInputEvent;
50 import jexer.event.TKeypressEvent;
51 import jexer.event.TMouseEvent;
52 import jexer.event.TResizeEvent;
53 import jexer.session.SessionInfo;
54 import jexer.session.SwingSessionInfo;
55 import static jexer.TCommand.*;
56 import static jexer.TKeypress.*;
57
58 /**
59 * This class reads keystrokes and mouse events from an Swing JFrame.
60 */
61 public final class SwingTerminal implements ComponentListener, KeyListener,
62 MouseListener, MouseMotionListener,
63 MouseWheelListener, WindowListener {
64
65 /**
66 * The backend Screen.
67 */
68 private SwingScreen screen;
69
70 /**
71 * The session information.
72 */
73 private SwingSessionInfo sessionInfo;
74
75 /**
76 * Getter for sessionInfo.
77 *
78 * @return the SessionInfo
79 */
80 public SessionInfo getSessionInfo() {
81 return sessionInfo;
82 }
83
84 /**
85 * The listening object that run() wakes up on new input.
86 */
87 private Object listener;
88
89 /**
90 * The event queue, filled up by a thread reading on input.
91 */
92 private List<TInputEvent> eventQueue;
93
94 /**
95 * The last reported mouse X position.
96 */
97 private int oldMouseX = -1;
98
99 /**
100 * The last reported mouse Y position.
101 */
102 private int oldMouseY = -1;
103
104 /**
105 * true if mouse1 was down. Used to report mouse1 on the release event.
106 */
107 private boolean mouse1 = false;
108
109 /**
110 * true if mouse2 was down. Used to report mouse2 on the release event.
111 */
112 private boolean mouse2 = false;
113
114 /**
115 * true if mouse3 was down. Used to report mouse3 on the release event.
116 */
117 private boolean mouse3 = false;
118
119 /**
120 * Check if there are events in the queue.
121 *
122 * @return if true, getEvents() has something to return to the backend
123 */
124 public boolean hasEvents() {
125 synchronized (eventQueue) {
126 return (eventQueue.size() > 0);
127 }
128 }
129
130 /**
131 * Constructor sets up state for getEvent().
132 *
133 * @param listener the object this backend needs to wake up when new
134 * input comes in
135 * @param screen the top-level Swing frame
136 */
137 public SwingTerminal(final Object listener, final SwingScreen screen) {
138 this.listener = listener;
139 this.screen = screen;
140 mouse1 = false;
141 mouse2 = false;
142 mouse3 = false;
143 sessionInfo = screen.getSessionInfo();
144 eventQueue = new LinkedList<TInputEvent>();
145
146 screen.frame.addKeyListener(this);
147 screen.frame.addWindowListener(this);
148 screen.frame.addComponentListener(this);
149 screen.frame.addMouseListener(this);
150 screen.frame.addMouseMotionListener(this);
151 screen.frame.addMouseWheelListener(this);
152 }
153
154 /**
155 * Return any events in the IO queue.
156 *
157 * @param queue list to append new events to
158 */
159 public void getEvents(final List<TInputEvent> queue) {
160 synchronized (eventQueue) {
161 if (eventQueue.size() > 0) {
162 synchronized (queue) {
163 queue.addAll(eventQueue);
164 }
165 eventQueue.clear();
166 }
167 }
168 }
169
170 /**
171 * Pass Swing keystrokes into the event queue.
172 *
173 * @param key keystroke received
174 */
175 @Override
176 public void keyReleased(final KeyEvent key) {
177 // Ignore release events
178 }
179
180 /**
181 * Pass Swing keystrokes into the event queue.
182 *
183 * @param key keystroke received
184 */
185 @Override
186 public void keyTyped(final KeyEvent key) {
187 // Ignore typed events
188 }
189
190 /**
191 * Pass Swing keystrokes into the event queue.
192 *
193 * @param key keystroke received
194 */
195 @Override
196 public void keyPressed(final KeyEvent key) {
197 boolean alt = false;
198 boolean shift = false;
199 boolean ctrl = false;
200 char ch = ' ';
201 boolean isKey = false;
202 if (key.isActionKey()) {
203 isKey = true;
204 } else {
205 ch = key.getKeyChar();
206 }
207 alt = key.isAltDown();
208 ctrl = key.isControlDown();
209 shift = key.isShiftDown();
210
211 /*
212 System.err.printf("Swing Key: %s\n", key);
213 System.err.printf(" isKey: %s\n", isKey);
214 System.err.printf(" alt: %s\n", alt);
215 System.err.printf(" ctrl: %s\n", ctrl);
216 System.err.printf(" shift: %s\n", shift);
217 System.err.printf(" ch: %s\n", ch);
218 */
219
220 // Special case: not return the bare modifier presses
221 switch (key.getKeyCode()) {
222 case KeyEvent.VK_ALT:
223 return;
224 case KeyEvent.VK_ALT_GRAPH:
225 return;
226 case KeyEvent.VK_CONTROL:
227 return;
228 case KeyEvent.VK_SHIFT:
229 return;
230 case KeyEvent.VK_META:
231 return;
232 default:
233 break;
234 }
235
236 TKeypress keypress = null;
237 if (isKey) {
238 switch (key.getKeyCode()) {
239 case KeyEvent.VK_F1:
240 keypress = new TKeypress(true, TKeypress.F1, ' ',
241 alt, ctrl, shift);
242 break;
243 case KeyEvent.VK_F2:
244 keypress = new TKeypress(true, TKeypress.F2, ' ',
245 alt, ctrl, shift);
246 break;
247 case KeyEvent.VK_F3:
248 keypress = new TKeypress(true, TKeypress.F3, ' ',
249 alt, ctrl, shift);
250 break;
251 case KeyEvent.VK_F4:
252 keypress = new TKeypress(true, TKeypress.F4, ' ',
253 alt, ctrl, shift);
254 break;
255 case KeyEvent.VK_F5:
256 keypress = new TKeypress(true, TKeypress.F5, ' ',
257 alt, ctrl, shift);
258 break;
259 case KeyEvent.VK_F6:
260 keypress = new TKeypress(true, TKeypress.F6, ' ',
261 alt, ctrl, shift);
262 break;
263 case KeyEvent.VK_F7:
264 keypress = new TKeypress(true, TKeypress.F7, ' ',
265 alt, ctrl, shift);
266 break;
267 case KeyEvent.VK_F8:
268 keypress = new TKeypress(true, TKeypress.F8, ' ',
269 alt, ctrl, shift);
270 break;
271 case KeyEvent.VK_F9:
272 keypress = new TKeypress(true, TKeypress.F9, ' ',
273 alt, ctrl, shift);
274 break;
275 case KeyEvent.VK_F10:
276 keypress = new TKeypress(true, TKeypress.F10, ' ',
277 alt, ctrl, shift);
278 break;
279 case KeyEvent.VK_F11:
280 keypress = new TKeypress(true, TKeypress.F11, ' ',
281 alt, ctrl, shift);
282 break;
283 case KeyEvent.VK_F12:
284 keypress = new TKeypress(true, TKeypress.F12, ' ',
285 alt, ctrl, shift);
286 break;
287 case KeyEvent.VK_HOME:
288 keypress = new TKeypress(true, TKeypress.HOME, ' ',
289 alt, ctrl, shift);
290 break;
291 case KeyEvent.VK_END:
292 keypress = new TKeypress(true, TKeypress.END, ' ',
293 alt, ctrl, shift);
294 break;
295 case KeyEvent.VK_PAGE_UP:
296 keypress = new TKeypress(true, TKeypress.PGUP, ' ',
297 alt, ctrl, shift);
298 break;
299 case KeyEvent.VK_PAGE_DOWN:
300 keypress = new TKeypress(true, TKeypress.PGDN, ' ',
301 alt, ctrl, shift);
302 break;
303 case KeyEvent.VK_INSERT:
304 keypress = new TKeypress(true, TKeypress.INS, ' ',
305 alt, ctrl, shift);
306 break;
307 case KeyEvent.VK_DELETE:
308 keypress = new TKeypress(true, TKeypress.DEL, ' ',
309 alt, ctrl, shift);
310 break;
311 case KeyEvent.VK_RIGHT:
312 keypress = new TKeypress(true, TKeypress.RIGHT, ' ',
313 alt, ctrl, shift);
314 break;
315 case KeyEvent.VK_LEFT:
316 keypress = new TKeypress(true, TKeypress.LEFT, ' ',
317 alt, ctrl, shift);
318 break;
319 case KeyEvent.VK_UP:
320 keypress = new TKeypress(true, TKeypress.UP, ' ',
321 alt, ctrl, shift);
322 break;
323 case KeyEvent.VK_DOWN:
324 keypress = new TKeypress(true, TKeypress.DOWN, ' ',
325 alt, ctrl, shift);
326 break;
327 case KeyEvent.VK_TAB:
328 // Special case: distinguish TAB vs BTAB
329 if (shift) {
330 keypress = kbShiftTab;
331 } else {
332 keypress = kbTab;
333 }
334 break;
335 case KeyEvent.VK_ENTER:
336 keypress = new TKeypress(true, TKeypress.ENTER, ' ',
337 alt, ctrl, shift);
338 break;
339 case KeyEvent.VK_ESCAPE:
340 keypress = new TKeypress(true, TKeypress.ESC, ' ',
341 alt, ctrl, shift);
342 break;
343 case KeyEvent.VK_BACK_SPACE:
344 // Special case: return it as kbBackspace (Ctrl-H)
345 keypress = new TKeypress(false, 0, 'H', false, true, false);
346 break;
347 default:
348 // Unsupported, ignore
349 return;
350 }
351 }
352
353 if (keypress == null) {
354 switch (ch) {
355 case 0x08:
356 keypress = kbBackspace;
357 break;
358 case 0x0A:
359 keypress = kbEnter;
360 break;
361 case 0x1B:
362 keypress = kbEsc;
363 break;
364 case 0x0D:
365 keypress = kbEnter;
366 break;
367 case 0x09:
368 if (shift) {
369 keypress = kbShiftTab;
370 } else {
371 keypress = kbTab;
372 }
373 break;
374 case 0x7F:
375 keypress = kbDel;
376 break;
377 default:
378 if (!alt && ctrl && !shift) {
379 ch = KeyEvent.getKeyText(key.getKeyCode()).charAt(0);
380 }
381 // Not a special key, put it together
382 keypress = new TKeypress(false, 0, ch, alt, ctrl, shift);
383 }
384 }
385
386 // Save it and we are done.
387 synchronized (eventQueue) {
388 eventQueue.add(new TKeypressEvent(keypress));
389 }
390 synchronized (listener) {
391 listener.notifyAll();
392 }
393 }
394
395 /**
396 * Pass window events into the event queue.
397 *
398 * @param event window event received
399 */
400 @Override
401 public void windowActivated(final WindowEvent event) {
402 // Force a total repaint
403 synchronized (screen) {
404 screen.clearPhysical();
405 }
406 }
407
408 /**
409 * Pass window events into the event queue.
410 *
411 * @param event window event received
412 */
413 @Override
414 public void windowClosed(final WindowEvent event) {
415 // Ignore
416 }
417
418 /**
419 * Pass window events into the event queue.
420 *
421 * @param event window event received
422 */
423 @Override
424 public void windowClosing(final WindowEvent event) {
425 // Drop a cmAbort and walk away
426 synchronized (eventQueue) {
427 eventQueue.add(new TCommandEvent(cmAbort));
428 }
429 synchronized (listener) {
430 listener.notifyAll();
431 }
432 }
433
434 /**
435 * Pass window events into the event queue.
436 *
437 * @param event window event received
438 */
439 @Override
440 public void windowDeactivated(final WindowEvent event) {
441 // Ignore
442 }
443
444 /**
445 * Pass window events into the event queue.
446 *
447 * @param event window event received
448 */
449 @Override
450 public void windowDeiconified(final WindowEvent event) {
451 // Ignore
452 }
453
454 /**
455 * Pass window events into the event queue.
456 *
457 * @param event window event received
458 */
459 @Override
460 public void windowIconified(final WindowEvent event) {
461 // Ignore
462 }
463
464 /**
465 * Pass window events into the event queue.
466 *
467 * @param event window event received
468 */
469 @Override
470 public void windowOpened(final WindowEvent event) {
471 // Ignore
472 }
473
474 /**
475 * Pass component events into the event queue.
476 *
477 * @param event component event received
478 */
479 @Override
480 public void componentHidden(final ComponentEvent event) {
481 // Ignore
482 }
483
484 /**
485 * Pass component events into the event queue.
486 *
487 * @param event component event received
488 */
489 @Override
490 public void componentShown(final ComponentEvent event) {
491 // Ignore
492 }
493
494 /**
495 * Pass component events into the event queue.
496 *
497 * @param event component event received
498 */
499 @Override
500 public void componentMoved(final ComponentEvent event) {
501 // Ignore
502 }
503
504 /**
505 * Pass component events into the event queue.
506 *
507 * @param event component event received
508 */
509 @Override
510 public void componentResized(final ComponentEvent event) {
511 // Drop a new TResizeEvent into the queue
512 sessionInfo.queryWindowSize();
513 synchronized (eventQueue) {
514 TResizeEvent windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN,
515 sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight());
516 eventQueue.add(windowResize);
517 }
518 synchronized (listener) {
519 listener.notifyAll();
520 }
521 }
522
523 /**
524 * Pass mouse events into the event queue.
525 *
526 * @param mouse mouse event received
527 */
528 @Override
529 public void mouseDragged(final MouseEvent mouse) {
530 int modifiers = mouse.getModifiersEx();
531 boolean eventMouse1 = false;
532 boolean eventMouse2 = false;
533 boolean eventMouse3 = false;
534 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
535 eventMouse1 = true;
536 }
537 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
538 eventMouse2 = true;
539 }
540 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
541 eventMouse3 = true;
542 }
543 mouse1 = eventMouse1;
544 mouse2 = eventMouse2;
545 mouse3 = eventMouse3;
546 int x = screen.textColumn(mouse.getX());
547 int y = screen.textRow(mouse.getY());
548
549 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION,
550 x, y, x, y, mouse1, mouse2, mouse3, false, false);
551
552 synchronized (eventQueue) {
553 eventQueue.add(mouseEvent);
554 }
555 synchronized (listener) {
556 listener.notifyAll();
557 }
558 }
559
560 /**
561 * Pass mouse events into the event queue.
562 *
563 * @param mouse mouse event received
564 */
565 @Override
566 public void mouseMoved(final MouseEvent mouse) {
567 int x = screen.textColumn(mouse.getX());
568 int y = screen.textRow(mouse.getY());
569 if ((x == oldMouseX) && (y == oldMouseY)) {
570 // Bail out, we've moved some pixels but not a whole text cell.
571 return;
572 }
573 oldMouseX = x;
574 oldMouseY = y;
575
576 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION,
577 x, y, x, y, mouse1, mouse2, mouse3, false, false);
578
579 synchronized (eventQueue) {
580 eventQueue.add(mouseEvent);
581 }
582 synchronized (listener) {
583 listener.notifyAll();
584 }
585 }
586
587 /**
588 * Pass mouse events into the event queue.
589 *
590 * @param mouse mouse event received
591 */
592 @Override
593 public void mouseClicked(final MouseEvent mouse) {
594 // Ignore
595 }
596
597 /**
598 * Pass mouse events into the event queue.
599 *
600 * @param mouse mouse event received
601 */
602 @Override
603 public void mouseEntered(final MouseEvent mouse) {
604 // Ignore
605 }
606
607 /**
608 * Pass mouse events into the event queue.
609 *
610 * @param mouse mouse event received
611 */
612 @Override
613 public void mouseExited(final MouseEvent mouse) {
614 // Ignore
615 }
616
617 /**
618 * Pass mouse events into the event queue.
619 *
620 * @param mouse mouse event received
621 */
622 @Override
623 public void mousePressed(final MouseEvent mouse) {
624 int modifiers = mouse.getModifiersEx();
625 boolean eventMouse1 = false;
626 boolean eventMouse2 = false;
627 boolean eventMouse3 = false;
628 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
629 eventMouse1 = true;
630 }
631 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
632 eventMouse2 = true;
633 }
634 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
635 eventMouse3 = true;
636 }
637 mouse1 = eventMouse1;
638 mouse2 = eventMouse2;
639 mouse3 = eventMouse3;
640 int x = screen.textColumn(mouse.getX());
641 int y = screen.textRow(mouse.getY());
642
643 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN,
644 x, y, x, y, mouse1, mouse2, mouse3, false, false);
645
646 synchronized (eventQueue) {
647 eventQueue.add(mouseEvent);
648 }
649 synchronized (listener) {
650 listener.notifyAll();
651 }
652 }
653
654 /**
655 * Pass mouse events into the event queue.
656 *
657 * @param mouse mouse event received
658 */
659 @Override
660 public void mouseReleased(final MouseEvent mouse) {
661 int modifiers = mouse.getModifiersEx();
662 boolean eventMouse1 = false;
663 boolean eventMouse2 = false;
664 boolean eventMouse3 = false;
665 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
666 eventMouse1 = true;
667 }
668 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
669 eventMouse2 = true;
670 }
671 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
672 eventMouse3 = true;
673 }
674 if (mouse1) {
675 mouse1 = false;
676 eventMouse1 = true;
677 }
678 if (mouse2) {
679 mouse2 = false;
680 eventMouse2 = true;
681 }
682 if (mouse3) {
683 mouse3 = false;
684 eventMouse3 = true;
685 }
686 int x = screen.textColumn(mouse.getX());
687 int y = screen.textRow(mouse.getY());
688
689 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_UP,
690 x, y, x, y, eventMouse1, eventMouse2, eventMouse3, false, false);
691
692 synchronized (eventQueue) {
693 eventQueue.add(mouseEvent);
694 }
695 synchronized (listener) {
696 listener.notifyAll();
697 }
698 }
699
700 /**
701 * Pass mouse events into the event queue.
702 *
703 * @param mouse mouse event received
704 */
705 @Override
706 public void mouseWheelMoved(final MouseWheelEvent mouse) {
707 int modifiers = mouse.getModifiersEx();
708 boolean eventMouse1 = false;
709 boolean eventMouse2 = false;
710 boolean eventMouse3 = false;
711 boolean mouseWheelUp = false;
712 boolean mouseWheelDown = false;
713 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
714 eventMouse1 = true;
715 }
716 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
717 eventMouse2 = true;
718 }
719 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
720 eventMouse3 = true;
721 }
722 mouse1 = eventMouse1;
723 mouse2 = eventMouse2;
724 mouse3 = eventMouse3;
725 int x = screen.textColumn(mouse.getX());
726 int y = screen.textRow(mouse.getY());
727 if (mouse.getWheelRotation() > 0) {
728 mouseWheelDown = true;
729 }
730 if (mouse.getWheelRotation() < 0) {
731 mouseWheelUp = true;
732 }
733
734 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN,
735 x, y, x, y, mouse1, mouse2, mouse3, mouseWheelUp, mouseWheelDown);
736
737 synchronized (eventQueue) {
738 eventQueue.add(mouseEvent);
739 }
740 synchronized (listener) {
741 listener.notifyAll();
742 }
743 }
744
745 }