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