3fa07cedae2d140fa008352fa32a253e7419405e
[fanfix.git] / 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 public void keyReleased(final KeyEvent key) {
176 // Ignore release events
177 }
178
179 /**
180 * Pass Swing keystrokes into the event queue.
181 *
182 * @param key keystroke received
183 */
184 public void keyTyped(final KeyEvent key) {
185 // Ignore typed events
186 }
187
188 /**
189 * Pass Swing keystrokes into the event queue.
190 *
191 * @param key keystroke received
192 */
193 public void keyPressed(final KeyEvent key) {
194 boolean alt = false;
195 boolean shift = false;
196 boolean ctrl = false;
197 char ch = ' ';
198 boolean isKey = false;
199 if (key.isActionKey()) {
200 isKey = true;
201 } else {
202 ch = key.getKeyChar();
203 }
204 alt = key.isAltDown();
205 ctrl = key.isControlDown();
206 shift = key.isShiftDown();
207
208 /*
209 System.err.printf("Swing Key: %s\n", key);
210 System.err.printf(" isKey: %s\n", isKey);
211 System.err.printf(" alt: %s\n", alt);
212 System.err.printf(" ctrl: %s\n", ctrl);
213 System.err.printf(" shift: %s\n", shift);
214 System.err.printf(" ch: %s\n", ch);
215 */
216
217 // Special case: not return the bare modifier presses
218 switch (key.getKeyCode()) {
219 case KeyEvent.VK_ALT:
220 return;
221 case KeyEvent.VK_ALT_GRAPH:
222 return;
223 case KeyEvent.VK_CONTROL:
224 return;
225 case KeyEvent.VK_SHIFT:
226 return;
227 case KeyEvent.VK_META:
228 return;
229 default:
230 break;
231 }
232
233 TKeypress keypress = null;
234 if (isKey) {
235 switch (key.getKeyCode()) {
236 case KeyEvent.VK_F1:
237 keypress = new TKeypress(true, TKeypress.F1, ' ',
238 alt, ctrl, shift);
239 break;
240 case KeyEvent.VK_F2:
241 keypress = new TKeypress(true, TKeypress.F2, ' ',
242 alt, ctrl, shift);
243 break;
244 case KeyEvent.VK_F3:
245 keypress = new TKeypress(true, TKeypress.F3, ' ',
246 alt, ctrl, shift);
247 break;
248 case KeyEvent.VK_F4:
249 keypress = new TKeypress(true, TKeypress.F4, ' ',
250 alt, ctrl, shift);
251 break;
252 case KeyEvent.VK_F5:
253 keypress = new TKeypress(true, TKeypress.F5, ' ',
254 alt, ctrl, shift);
255 break;
256 case KeyEvent.VK_F6:
257 keypress = new TKeypress(true, TKeypress.F6, ' ',
258 alt, ctrl, shift);
259 break;
260 case KeyEvent.VK_F7:
261 keypress = new TKeypress(true, TKeypress.F7, ' ',
262 alt, ctrl, shift);
263 break;
264 case KeyEvent.VK_F8:
265 keypress = new TKeypress(true, TKeypress.F8, ' ',
266 alt, ctrl, shift);
267 break;
268 case KeyEvent.VK_F9:
269 keypress = new TKeypress(true, TKeypress.F9, ' ',
270 alt, ctrl, shift);
271 break;
272 case KeyEvent.VK_F10:
273 keypress = new TKeypress(true, TKeypress.F10, ' ',
274 alt, ctrl, shift);
275 break;
276 case KeyEvent.VK_F11:
277 keypress = new TKeypress(true, TKeypress.F11, ' ',
278 alt, ctrl, shift);
279 break;
280 case KeyEvent.VK_F12:
281 keypress = new TKeypress(true, TKeypress.F12, ' ',
282 alt, ctrl, shift);
283 break;
284 case KeyEvent.VK_HOME:
285 keypress = new TKeypress(true, TKeypress.HOME, ' ',
286 alt, ctrl, shift);
287 break;
288 case KeyEvent.VK_END:
289 keypress = new TKeypress(true, TKeypress.END, ' ',
290 alt, ctrl, shift);
291 break;
292 case KeyEvent.VK_PAGE_UP:
293 keypress = new TKeypress(true, TKeypress.PGUP, ' ',
294 alt, ctrl, shift);
295 break;
296 case KeyEvent.VK_PAGE_DOWN:
297 keypress = new TKeypress(true, TKeypress.PGDN, ' ',
298 alt, ctrl, shift);
299 break;
300 case KeyEvent.VK_INSERT:
301 keypress = new TKeypress(true, TKeypress.INS, ' ',
302 alt, ctrl, shift);
303 break;
304 case KeyEvent.VK_DELETE:
305 keypress = new TKeypress(true, TKeypress.DEL, ' ',
306 alt, ctrl, shift);
307 break;
308 case KeyEvent.VK_RIGHT:
309 keypress = new TKeypress(true, TKeypress.RIGHT, ' ',
310 alt, ctrl, shift);
311 break;
312 case KeyEvent.VK_LEFT:
313 keypress = new TKeypress(true, TKeypress.LEFT, ' ',
314 alt, ctrl, shift);
315 break;
316 case KeyEvent.VK_UP:
317 keypress = new TKeypress(true, TKeypress.UP, ' ',
318 alt, ctrl, shift);
319 break;
320 case KeyEvent.VK_DOWN:
321 keypress = new TKeypress(true, TKeypress.DOWN, ' ',
322 alt, ctrl, shift);
323 break;
324 case KeyEvent.VK_TAB:
325 // Special case: distinguish TAB vs BTAB
326 if (shift) {
327 keypress = kbShiftTab;
328 } else {
329 keypress = kbTab;
330 }
331 break;
332 case KeyEvent.VK_ENTER:
333 keypress = new TKeypress(true, TKeypress.ENTER, ' ',
334 alt, ctrl, shift);
335 break;
336 case KeyEvent.VK_ESCAPE:
337 keypress = new TKeypress(true, TKeypress.ESC, ' ',
338 alt, ctrl, shift);
339 break;
340 case KeyEvent.VK_BACK_SPACE:
341 // Special case: return it as kbBackspace (Ctrl-H)
342 keypress = new TKeypress(false, 0, 'H', false, true, false);
343 break;
344 default:
345 // Unsupported, ignore
346 return;
347 }
348 }
349
350 if (keypress == null) {
351 switch (ch) {
352 case 0x08:
353 keypress = kbBackspace;
354 break;
355 case 0x0A:
356 keypress = kbEnter;
357 break;
358 case 0x1B:
359 keypress = kbEsc;
360 break;
361 case 0x0D:
362 keypress = kbEnter;
363 break;
364 case 0x09:
365 if (shift) {
366 keypress = kbShiftTab;
367 } else {
368 keypress = kbTab;
369 }
370 break;
371 case 0x7F:
372 keypress = kbDel;
373 break;
374 default:
375 if (!alt && ctrl && !shift) {
376 ch = KeyEvent.getKeyText(key.getKeyCode()).charAt(0);
377 }
378 // Not a special key, put it together
379 keypress = new TKeypress(false, 0, ch, alt, ctrl, shift);
380 }
381 }
382
383 // Save it and we are done.
384 synchronized (eventQueue) {
385 eventQueue.add(new TKeypressEvent(keypress));
386 }
387 synchronized (listener) {
388 listener.notifyAll();
389 }
390 }
391
392 /**
393 * Pass window events into the event queue.
394 *
395 * @param event window event received
396 */
397 public void windowActivated(final WindowEvent event) {
398 // Force a total repaint
399 synchronized (screen) {
400 screen.clearPhysical();
401 }
402 }
403
404 /**
405 * Pass window events into the event queue.
406 *
407 * @param event window event received
408 */
409 public void windowClosed(final WindowEvent event) {
410 // Ignore
411 }
412
413 /**
414 * Pass window events into the event queue.
415 *
416 * @param event window event received
417 */
418 public void windowClosing(final WindowEvent event) {
419 // Drop a cmAbort and walk away
420 synchronized (eventQueue) {
421 eventQueue.add(new TCommandEvent(cmAbort));
422 }
423 synchronized (listener) {
424 listener.notifyAll();
425 }
426 }
427
428 /**
429 * Pass window events into the event queue.
430 *
431 * @param event window event received
432 */
433 public void windowDeactivated(final WindowEvent event) {
434 // Ignore
435 }
436
437 /**
438 * Pass window events into the event queue.
439 *
440 * @param event window event received
441 */
442 public void windowDeiconified(final WindowEvent event) {
443 // Ignore
444 }
445
446 /**
447 * Pass window events into the event queue.
448 *
449 * @param event window event received
450 */
451 public void windowIconified(final WindowEvent event) {
452 // Ignore
453 }
454
455 /**
456 * Pass window events into the event queue.
457 *
458 * @param event window event received
459 */
460 public void windowOpened(final WindowEvent event) {
461 // Ignore
462 }
463
464 /**
465 * Pass component events into the event queue.
466 *
467 * @param event component event received
468 */
469 public void componentHidden(final ComponentEvent event) {
470 // Ignore
471 }
472
473 /**
474 * Pass component events into the event queue.
475 *
476 * @param event component event received
477 */
478 public void componentShown(final ComponentEvent event) {
479 // Ignore
480 }
481
482 /**
483 * Pass component events into the event queue.
484 *
485 * @param event component event received
486 */
487 public void componentMoved(final ComponentEvent event) {
488 // Ignore
489 }
490
491 /**
492 * Pass component events into the event queue.
493 *
494 * @param event component event received
495 */
496 public void componentResized(final ComponentEvent event) {
497 // Drop a new TResizeEvent into the queue
498 sessionInfo.queryWindowSize();
499 synchronized (eventQueue) {
500 TResizeEvent windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN,
501 sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight());
502 eventQueue.add(windowResize);
503 }
504 synchronized (listener) {
505 listener.notifyAll();
506 }
507 }
508
509 /**
510 * Pass mouse events into the event queue.
511 *
512 * @param mouse mouse event received
513 */
514 public void mouseDragged(final MouseEvent mouse) {
515 int modifiers = mouse.getModifiersEx();
516 boolean eventMouse1 = false;
517 boolean eventMouse2 = false;
518 boolean eventMouse3 = false;
519 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
520 eventMouse1 = true;
521 }
522 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
523 eventMouse2 = true;
524 }
525 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
526 eventMouse3 = true;
527 }
528 mouse1 = eventMouse1;
529 mouse2 = eventMouse2;
530 mouse3 = eventMouse3;
531 int x = screen.textColumn(mouse.getX());
532 int y = screen.textRow(mouse.getY());
533
534 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION,
535 x, y, x, y, mouse1, mouse2, mouse3, false, false);
536
537 synchronized (eventQueue) {
538 eventQueue.add(mouseEvent);
539 }
540 synchronized (listener) {
541 listener.notifyAll();
542 }
543 }
544
545 /**
546 * Pass mouse events into the event queue.
547 *
548 * @param mouse mouse event received
549 */
550 public void mouseMoved(final MouseEvent mouse) {
551 int x = screen.textColumn(mouse.getX());
552 int y = screen.textRow(mouse.getY());
553 if ((x == oldMouseX) && (y == oldMouseY)) {
554 // Bail out, we've moved some pixels but not a whole text cell.
555 return;
556 }
557 oldMouseX = x;
558 oldMouseY = y;
559
560 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION,
561 x, y, x, y, mouse1, mouse2, mouse3, false, false);
562
563 synchronized (eventQueue) {
564 eventQueue.add(mouseEvent);
565 }
566 synchronized (listener) {
567 listener.notifyAll();
568 }
569 }
570
571 /**
572 * Pass mouse events into the event queue.
573 *
574 * @param mouse mouse event received
575 */
576 public void mouseClicked(final MouseEvent mouse) {
577 // Ignore
578 }
579
580 /**
581 * Pass mouse events into the event queue.
582 *
583 * @param mouse mouse event received
584 */
585 public void mouseEntered(final MouseEvent mouse) {
586 // Ignore
587 }
588
589 /**
590 * Pass mouse events into the event queue.
591 *
592 * @param mouse mouse event received
593 */
594 public void mouseExited(final MouseEvent mouse) {
595 // Ignore
596 }
597
598 /**
599 * Pass mouse events into the event queue.
600 *
601 * @param mouse mouse event received
602 */
603 public void mousePressed(final MouseEvent mouse) {
604 int modifiers = mouse.getModifiersEx();
605 boolean eventMouse1 = false;
606 boolean eventMouse2 = false;
607 boolean eventMouse3 = false;
608 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
609 eventMouse1 = true;
610 }
611 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
612 eventMouse2 = true;
613 }
614 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
615 eventMouse3 = true;
616 }
617 mouse1 = eventMouse1;
618 mouse2 = eventMouse2;
619 mouse3 = eventMouse3;
620 int x = screen.textColumn(mouse.getX());
621 int y = screen.textRow(mouse.getY());
622
623 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN,
624 x, y, x, y, mouse1, mouse2, mouse3, false, false);
625
626 synchronized (eventQueue) {
627 eventQueue.add(mouseEvent);
628 }
629 synchronized (listener) {
630 listener.notifyAll();
631 }
632 }
633
634 /**
635 * Pass mouse events into the event queue.
636 *
637 * @param mouse mouse event received
638 */
639 public void mouseReleased(final MouseEvent mouse) {
640 int modifiers = mouse.getModifiersEx();
641 boolean eventMouse1 = false;
642 boolean eventMouse2 = false;
643 boolean eventMouse3 = false;
644 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
645 eventMouse1 = true;
646 }
647 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
648 eventMouse2 = true;
649 }
650 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
651 eventMouse3 = true;
652 }
653 if (mouse1) {
654 mouse1 = false;
655 eventMouse1 = true;
656 }
657 if (mouse2) {
658 mouse2 = false;
659 eventMouse2 = true;
660 }
661 if (mouse3) {
662 mouse3 = false;
663 eventMouse3 = true;
664 }
665 int x = screen.textColumn(mouse.getX());
666 int y = screen.textRow(mouse.getY());
667
668 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_UP,
669 x, y, x, y, eventMouse1, eventMouse2, eventMouse3, false, false);
670
671 synchronized (eventQueue) {
672 eventQueue.add(mouseEvent);
673 }
674 synchronized (listener) {
675 listener.notifyAll();
676 }
677 }
678
679 /**
680 * Pass mouse events into the event queue.
681 *
682 * @param mouse mouse event received
683 */
684 public void mouseWheelMoved(final MouseWheelEvent mouse) {
685 int modifiers = mouse.getModifiersEx();
686 boolean eventMouse1 = false;
687 boolean eventMouse2 = false;
688 boolean eventMouse3 = false;
689 boolean mouseWheelUp = false;
690 boolean mouseWheelDown = false;
691 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
692 eventMouse1 = true;
693 }
694 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
695 eventMouse2 = true;
696 }
697 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
698 eventMouse3 = true;
699 }
700 mouse1 = eventMouse1;
701 mouse2 = eventMouse2;
702 mouse3 = eventMouse3;
703 int x = screen.textColumn(mouse.getX());
704 int y = screen.textRow(mouse.getY());
705 if (mouse.getWheelRotation() > 0) {
706 mouseWheelDown = true;
707 }
708 if (mouse.getWheelRotation() < 0) {
709 mouseWheelUp = true;
710 }
711
712 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN,
713 x, y, x, y, mouse1, mouse2, mouse3, mouseWheelUp, mouseWheelDown);
714
715 synchronized (eventQueue) {
716 eventQueue.add(mouseEvent);
717 }
718 synchronized (listener) {
719 listener.notifyAll();
720 }
721 }
722
723 }