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