PMD code sweep, #6 don't add MyWindow twice to MyApplication
[fanfix.git] / src / jexer / backend / SwingTerminal.java
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 */
29 package jexer.backend;
30
31 import java.awt.BorderLayout;
32 import java.awt.Color;
33 import java.awt.Font;
34 import java.awt.FontMetrics;
35 import java.awt.Graphics2D;
36 import java.awt.Graphics;
37 import java.awt.Insets;
38 import java.awt.Rectangle;
39 import java.awt.Toolkit;
40 import java.awt.event.ComponentEvent;
41 import java.awt.event.ComponentListener;
42 import java.awt.event.KeyEvent;
43 import java.awt.event.KeyListener;
44 import java.awt.event.MouseEvent;
45 import java.awt.event.MouseListener;
46 import java.awt.event.MouseMotionListener;
47 import java.awt.event.MouseWheelEvent;
48 import java.awt.event.MouseWheelListener;
49 import java.awt.event.WindowEvent;
50 import java.awt.event.WindowListener;
51 import java.awt.geom.Rectangle2D;
52 import java.awt.image.BufferedImage;
53 import java.io.InputStream;
54 import java.util.HashMap;
55 import java.util.LinkedList;
56 import java.util.List;
57 import java.util.Map;
58 import javax.swing.JComponent;
59 import javax.swing.JFrame;
60 import javax.swing.ImageIcon;
61 import javax.swing.SwingUtilities;
62
63 import jexer.TKeypress;
64 import jexer.bits.Cell;
65 import jexer.bits.CellAttributes;
66 import jexer.event.TCommandEvent;
67 import jexer.event.TInputEvent;
68 import jexer.event.TKeypressEvent;
69 import jexer.event.TMouseEvent;
70 import jexer.event.TResizeEvent;
71 import static jexer.TCommand.*;
72 import static jexer.TKeypress.*;
73
74 /**
75 * This Screen backend reads keystrokes and mouse events and draws to either
76 * a Java Swing JFrame (potentially triple-buffered) or a JComponent.
77 *
78 * This class is a bit of an inversion of typical GUI classes. It performs
79 * all of the drawing logic from SwingTerminal (which is not a Swing class),
80 * and uses a SwingComponent wrapper class to call the JFrame or JComponent
81 * methods.
82 */
83 public final class SwingTerminal extends LogicalScreen
84 implements TerminalReader,
85 ComponentListener, KeyListener,
86 MouseListener, MouseMotionListener,
87 MouseWheelListener, WindowListener {
88
89 // ------------------------------------------------------------------------
90 // Constants --------------------------------------------------------------
91 // ------------------------------------------------------------------------
92
93 /**
94 * The icon image location.
95 */
96 private static final String ICONFILE = "jexer_logo_128.png";
97
98 /**
99 * The terminus font resource filename.
100 */
101 private static final String FONTFILE = "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf";
102
103 /**
104 * Cursor style to draw.
105 */
106 public enum CursorStyle {
107 /**
108 * Use an underscore for the cursor.
109 */
110 UNDERLINE,
111
112 /**
113 * Use a solid block for the cursor.
114 */
115 BLOCK,
116
117 /**
118 * Use an outlined block for the cursor.
119 */
120 OUTLINE
121 }
122
123 // ------------------------------------------------------------------------
124 // Variables --------------------------------------------------------------
125 // ------------------------------------------------------------------------
126
127 // Colors to map DOS colors to AWT colors.
128 private static Color MYBLACK;
129 private static Color MYRED;
130 private static Color MYGREEN;
131 private static Color MYYELLOW;
132 private static Color MYBLUE;
133 private static Color MYMAGENTA;
134 private static Color MYCYAN;
135 private static Color MYWHITE;
136 private static Color MYBOLD_BLACK;
137 private static Color MYBOLD_RED;
138 private static Color MYBOLD_GREEN;
139 private static Color MYBOLD_YELLOW;
140 private static Color MYBOLD_BLUE;
141 private static Color MYBOLD_MAGENTA;
142 private static Color MYBOLD_CYAN;
143 private static Color MYBOLD_WHITE;
144
145 /**
146 * When true, all the MYBLACK, MYRED, etc. colors are set.
147 */
148 private static boolean dosColors = false;
149
150 /**
151 * The Swing component or frame to draw to.
152 */
153 private SwingComponent swing;
154
155 /**
156 * A cache of previously-rendered glyphs for blinking text, when it is
157 * not visible.
158 */
159 private Map<Cell, BufferedImage> glyphCacheBlink;
160
161 /**
162 * A cache of previously-rendered glyphs for non-blinking, or
163 * blinking-and-visible, text.
164 */
165 private Map<Cell, BufferedImage> glyphCache;
166
167 /**
168 * If true, we were successful getting Terminus.
169 */
170 private boolean gotTerminus = false;
171
172 /**
173 * If true, we were successful at getting the font dimensions.
174 */
175 private boolean gotFontDimensions = false;
176
177 /**
178 * The currently selected font.
179 */
180 private Font font = null;
181
182 /**
183 * The currently selected font size in points.
184 */
185 private int fontSize = 16;
186
187 /**
188 * Width of a character cell in pixels.
189 */
190 private int textWidth = 1;
191
192 /**
193 * Height of a character cell in pixels.
194 */
195 private int textHeight = 1;
196
197 /**
198 * Descent of a character cell in pixels.
199 */
200 private int maxDescent = 0;
201
202 /**
203 * System-dependent Y adjustment for text in the character cell.
204 */
205 private int textAdjustY = 0;
206
207 /**
208 * System-dependent X adjustment for text in the character cell.
209 */
210 private int textAdjustX = 0;
211
212 /**
213 * Top pixel absolute location.
214 */
215 private int top = 30;
216
217 /**
218 * Left pixel absolute location.
219 */
220 private int left = 30;
221
222 /**
223 * The cursor style to draw.
224 */
225 private CursorStyle cursorStyle = CursorStyle.UNDERLINE;
226
227 /**
228 * The number of millis to wait before switching the blink from visible
229 * to invisible. Set to 0 or negative to disable blinking.
230 */
231 private long blinkMillis = 500;
232
233 /**
234 * If true, the cursor should be visible right now based on the blink
235 * time.
236 */
237 private boolean cursorBlinkVisible = true;
238
239 /**
240 * The time that the blink last flipped from visible to invisible or
241 * from invisible to visible.
242 */
243 private long lastBlinkTime = 0;
244
245 /**
246 * The session information.
247 */
248 private SwingSessionInfo sessionInfo;
249
250 /**
251 * The listening object that run() wakes up on new input.
252 */
253 private Object listener;
254
255 /**
256 * The event queue, filled up by a thread reading on input.
257 */
258 private List<TInputEvent> eventQueue;
259
260 /**
261 * The last reported mouse X position.
262 */
263 private int oldMouseX = -1;
264
265 /**
266 * The last reported mouse Y position.
267 */
268 private int oldMouseY = -1;
269
270 /**
271 * true if mouse1 was down. Used to report mouse1 on the release event.
272 */
273 private boolean mouse1 = false;
274
275 /**
276 * true if mouse2 was down. Used to report mouse2 on the release event.
277 */
278 private boolean mouse2 = false;
279
280 /**
281 * true if mouse3 was down. Used to report mouse3 on the release event.
282 */
283 private boolean mouse3 = false;
284
285 // ------------------------------------------------------------------------
286 // Constructors -----------------------------------------------------------
287 // ------------------------------------------------------------------------
288
289 /**
290 * Public constructor creates a new JFrame to render to.
291 *
292 * @param windowWidth the number of text columns to start with
293 * @param windowHeight the number of text rows to start with
294 * @param fontSize the size in points. Good values to pick are: 16, 20,
295 * 22, and 24.
296 * @param listener the object this backend needs to wake up when new
297 * input comes in
298 */
299 public SwingTerminal(final int windowWidth, final int windowHeight,
300 final int fontSize, final Object listener) {
301
302 this.fontSize = fontSize;
303
304 setDOSColors();
305
306 // Figure out my cursor style.
307 String cursorStyleString = System.getProperty(
308 "jexer.Swing.cursorStyle", "underline").toLowerCase();
309 if (cursorStyleString.equals("underline")) {
310 cursorStyle = CursorStyle.UNDERLINE;
311 } else if (cursorStyleString.equals("outline")) {
312 cursorStyle = CursorStyle.OUTLINE;
313 } else if (cursorStyleString.equals("block")) {
314 cursorStyle = CursorStyle.BLOCK;
315 }
316
317 // Pull the system property for triple buffering.
318 if (System.getProperty("jexer.Swing.tripleBuffer") != null) {
319 if (System.getProperty("jexer.Swing.tripleBuffer").equals("true")) {
320 SwingComponent.tripleBuffer = true;
321 } else {
322 SwingComponent.tripleBuffer = false;
323 }
324 }
325
326 try {
327 SwingUtilities.invokeAndWait(new Runnable() {
328 public void run() {
329
330 JFrame frame = new JFrame() {
331
332 /**
333 * Serializable version.
334 */
335 private static final long serialVersionUID = 1;
336
337 /**
338 * The code that performs the actual drawing.
339 */
340 public SwingTerminal screen = null;
341
342 /*
343 * Anonymous class initializer saves the screen
344 * reference, so that paint() and the like call out
345 * to SwingTerminal.
346 */
347 {
348 this.screen = SwingTerminal.this;
349 }
350
351 /**
352 * Update redraws the whole screen.
353 *
354 * @param gr the Swing Graphics context
355 */
356 @Override
357 public void update(final Graphics gr) {
358 // The default update clears the area. Don't do
359 // that, instead just paint it directly.
360 paint(gr);
361 }
362
363 /**
364 * Paint redraws the whole screen.
365 *
366 * @param gr the Swing Graphics context
367 */
368 @Override
369 public void paint(final Graphics gr) {
370 if (screen != null) {
371 screen.paint(gr);
372 }
373 }
374 };
375
376 // Set icon
377 ClassLoader loader = Thread.currentThread().
378 getContextClassLoader();
379 frame.setIconImage((new ImageIcon(loader.
380 getResource(ICONFILE))).getImage());
381
382 // Get the Swing component
383 SwingTerminal.this.swing = new SwingComponent(frame);
384
385 // Hang onto top and left for drawing.
386 Insets insets = SwingTerminal.this.swing.getInsets();
387 SwingTerminal.this.left = insets.left;
388 SwingTerminal.this.top = insets.top;
389
390 // Load the font so that we can set sessionInfo.
391 getDefaultFont();
392
393 // Get the default cols x rows and set component size
394 // accordingly.
395 SwingTerminal.this.sessionInfo =
396 new SwingSessionInfo(SwingTerminal.this.swing,
397 SwingTerminal.this.textWidth,
398 SwingTerminal.this.textHeight,
399 windowWidth, windowHeight);
400
401 SwingTerminal.this.setDimensions(sessionInfo.getWindowWidth(),
402 sessionInfo.getWindowHeight());
403
404 SwingTerminal.this.resizeToScreen();
405 SwingTerminal.this.swing.setVisible(true);
406 }
407 });
408 } catch (Exception e) {
409 e.printStackTrace();
410 }
411
412 this.listener = listener;
413 mouse1 = false;
414 mouse2 = false;
415 mouse3 = false;
416 eventQueue = new LinkedList<TInputEvent>();
417
418 // Add listeners to Swing.
419 swing.addKeyListener(this);
420 swing.addWindowListener(this);
421 swing.addComponentListener(this);
422 swing.addMouseListener(this);
423 swing.addMouseMotionListener(this);
424 swing.addMouseWheelListener(this);
425 }
426
427 /**
428 * Public constructor renders to an existing JComponent.
429 *
430 * @param component the Swing component to render to
431 * @param windowWidth the number of text columns to start with
432 * @param windowHeight the number of text rows to start with
433 * @param fontSize the size in points. Good values to pick are: 16, 20,
434 * 22, and 24.
435 * @param listener the object this backend needs to wake up when new
436 * input comes in
437 */
438 public SwingTerminal(final JComponent component, final int windowWidth,
439 final int windowHeight, final int fontSize, final Object listener) {
440
441 this.fontSize = fontSize;
442
443 setDOSColors();
444
445 // Figure out my cursor style.
446 String cursorStyleString = System.getProperty(
447 "jexer.Swing.cursorStyle", "underline").toLowerCase();
448 if (cursorStyleString.equals("underline")) {
449 cursorStyle = CursorStyle.UNDERLINE;
450 } else if (cursorStyleString.equals("outline")) {
451 cursorStyle = CursorStyle.OUTLINE;
452 } else if (cursorStyleString.equals("block")) {
453 cursorStyle = CursorStyle.BLOCK;
454 }
455
456 try {
457 SwingUtilities.invokeAndWait(new Runnable() {
458 public void run() {
459
460 JComponent newComponent = new JComponent() {
461
462 /**
463 * Serializable version.
464 */
465 private static final long serialVersionUID = 1;
466
467 /**
468 * The code that performs the actual drawing.
469 */
470 public SwingTerminal screen = null;
471
472 /*
473 * Anonymous class initializer saves the screen
474 * reference, so that paint() and the like call out
475 * to SwingTerminal.
476 */
477 {
478 this.screen = SwingTerminal.this;
479 }
480
481 /**
482 * Update redraws the whole screen.
483 *
484 * @param gr the Swing Graphics context
485 */
486 @Override
487 public void update(final Graphics gr) {
488 // The default update clears the area. Don't do
489 // that, instead just paint it directly.
490 paint(gr);
491 }
492
493 /**
494 * Paint redraws the whole screen.
495 *
496 * @param gr the Swing Graphics context
497 */
498 @Override
499 public void paint(final Graphics gr) {
500 if (screen != null) {
501 screen.paint(gr);
502 }
503 }
504 };
505 component.setLayout(new BorderLayout());
506 component.add(newComponent);
507
508 // Allow key events to be received
509 component.setFocusable(true);
510
511 // Get the Swing component
512 SwingTerminal.this.swing = new SwingComponent(component);
513
514 // Hang onto top and left for drawing.
515 Insets insets = SwingTerminal.this.swing.getInsets();
516 SwingTerminal.this.left = insets.left;
517 SwingTerminal.this.top = insets.top;
518
519 // Load the font so that we can set sessionInfo.
520 getDefaultFont();
521
522 // Get the default cols x rows and set component size
523 // accordingly.
524 SwingTerminal.this.sessionInfo =
525 new SwingSessionInfo(SwingTerminal.this.swing,
526 SwingTerminal.this.textWidth,
527 SwingTerminal.this.textHeight);
528 }
529 });
530 } catch (Exception e) {
531 e.printStackTrace();
532 }
533
534 this.listener = listener;
535 mouse1 = false;
536 mouse2 = false;
537 mouse3 = false;
538 eventQueue = new LinkedList<TInputEvent>();
539
540 // Add listeners to Swing.
541 swing.addKeyListener(this);
542 swing.addWindowListener(this);
543 swing.addComponentListener(this);
544 swing.addMouseListener(this);
545 swing.addMouseMotionListener(this);
546 swing.addMouseWheelListener(this);
547 }
548
549 // ------------------------------------------------------------------------
550 // LogicalScreen ----------------------------------------------------------
551 // ------------------------------------------------------------------------
552
553 /**
554 * Set the window title.
555 *
556 * @param title the new title
557 */
558 @Override
559 public void setTitle(final String title) {
560 swing.setTitle(title);
561 }
562
563 /**
564 * Push the logical screen to the physical device.
565 */
566 @Override
567 public void flushPhysical() {
568 // See if it is time to flip the blink time.
569 long nowTime = System.currentTimeMillis();
570 if (nowTime >= blinkMillis + lastBlinkTime) {
571 lastBlinkTime = nowTime;
572 cursorBlinkVisible = !cursorBlinkVisible;
573 // System.err.println("New lastBlinkTime: " + lastBlinkTime);
574 }
575
576 if ((swing.getFrame() != null)
577 && (swing.getBufferStrategy() != null)
578 ) {
579 do {
580 do {
581 drawToSwing();
582 } while (swing.getBufferStrategy().contentsRestored());
583
584 swing.getBufferStrategy().show();
585 Toolkit.getDefaultToolkit().sync();
586 } while (swing.getBufferStrategy().contentsLost());
587
588 } else {
589 // Non-triple-buffered, call drawToSwing() once
590 drawToSwing();
591 }
592 }
593
594 // ------------------------------------------------------------------------
595 // TerminalReader ---------------------------------------------------------
596 // ------------------------------------------------------------------------
597
598 /**
599 * Check if there are events in the queue.
600 *
601 * @return if true, getEvents() has something to return to the backend
602 */
603 public boolean hasEvents() {
604 synchronized (eventQueue) {
605 return (eventQueue.size() > 0);
606 }
607 }
608
609 /**
610 * Return any events in the IO queue.
611 *
612 * @param queue list to append new events to
613 */
614 public void getEvents(final List<TInputEvent> queue) {
615 synchronized (eventQueue) {
616 if (eventQueue.size() > 0) {
617 synchronized (queue) {
618 queue.addAll(eventQueue);
619 }
620 eventQueue.clear();
621 }
622 }
623 }
624
625 /**
626 * Restore terminal to normal state.
627 */
628 public void closeTerminal() {
629 shutdown();
630 }
631
632 /**
633 * Set listener to a different Object.
634 *
635 * @param listener the new listening object that run() wakes up on new
636 * input
637 */
638 public void setListener(final Object listener) {
639 this.listener = listener;
640 }
641
642 // ------------------------------------------------------------------------
643 // SwingTerminal ----------------------------------------------------------
644 // ------------------------------------------------------------------------
645
646 /**
647 * Setup Swing colors to match DOS color palette.
648 */
649 private static void setDOSColors() {
650 if (dosColors) {
651 return;
652 }
653 MYBLACK = new Color(0x00, 0x00, 0x00);
654 MYRED = new Color(0xa8, 0x00, 0x00);
655 MYGREEN = new Color(0x00, 0xa8, 0x00);
656 MYYELLOW = new Color(0xa8, 0x54, 0x00);
657 MYBLUE = new Color(0x00, 0x00, 0xa8);
658 MYMAGENTA = new Color(0xa8, 0x00, 0xa8);
659 MYCYAN = new Color(0x00, 0xa8, 0xa8);
660 MYWHITE = new Color(0xa8, 0xa8, 0xa8);
661 MYBOLD_BLACK = new Color(0x54, 0x54, 0x54);
662 MYBOLD_RED = new Color(0xfc, 0x54, 0x54);
663 MYBOLD_GREEN = new Color(0x54, 0xfc, 0x54);
664 MYBOLD_YELLOW = new Color(0xfc, 0xfc, 0x54);
665 MYBOLD_BLUE = new Color(0x54, 0x54, 0xfc);
666 MYBOLD_MAGENTA = new Color(0xfc, 0x54, 0xfc);
667 MYBOLD_CYAN = new Color(0x54, 0xfc, 0xfc);
668 MYBOLD_WHITE = new Color(0xfc, 0xfc, 0xfc);
669
670 dosColors = true;
671 }
672
673 /**
674 * Get the number of millis to wait before switching the blink from
675 * visible to invisible.
676 *
677 * @return the number of milli to wait before switching the blink from
678 * visible to invisible
679 */
680 public long getBlinkMillis() {
681 return blinkMillis;
682 }
683
684 /**
685 * Get the font size in points.
686 *
687 * @return font size in points
688 */
689 public int getFontSize() {
690 return fontSize;
691 }
692
693 /**
694 * Set the font size in points.
695 *
696 * @param fontSize font size in points
697 */
698 public void setFontSize(final int fontSize) {
699 this.fontSize = fontSize;
700 Font newFont = font.deriveFont((float) fontSize);
701 setFont(newFont);
702 }
703
704 /**
705 * Set to a new font, and resize the screen to match its dimensions.
706 *
707 * @param font the new font
708 */
709 public void setFont(final Font font) {
710 this.font = font;
711 getFontDimensions();
712 swing.setFont(font);
713 glyphCacheBlink = new HashMap<Cell, BufferedImage>();
714 glyphCache = new HashMap<Cell, BufferedImage>();
715 resizeToScreen();
716 }
717
718 /**
719 * Set the font to Terminus, the best all-around font for both CP437 and
720 * ISO8859-1.
721 */
722 public void getDefaultFont() {
723 try {
724 ClassLoader loader = Thread.currentThread().getContextClassLoader();
725 InputStream in = loader.getResourceAsStream(FONTFILE);
726 Font terminusRoot = Font.createFont(Font.TRUETYPE_FONT, in);
727 Font terminus = terminusRoot.deriveFont(Font.PLAIN, fontSize);
728 gotTerminus = true;
729 font = terminus;
730 } catch (Exception e) {
731 e.printStackTrace();
732 font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize);
733 }
734
735 setFont(font);
736 }
737
738 /**
739 * Convert a CellAttributes foreground color to an Swing Color.
740 *
741 * @param attr the text attributes
742 * @return the Swing Color
743 */
744 private Color attrToForegroundColor(final CellAttributes attr) {
745 if (attr.isBold()) {
746 if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
747 return MYBOLD_BLACK;
748 } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
749 return MYBOLD_RED;
750 } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
751 return MYBOLD_BLUE;
752 } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
753 return MYBOLD_GREEN;
754 } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
755 return MYBOLD_YELLOW;
756 } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
757 return MYBOLD_CYAN;
758 } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
759 return MYBOLD_MAGENTA;
760 } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
761 return MYBOLD_WHITE;
762 }
763 } else {
764 if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
765 return MYBLACK;
766 } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
767 return MYRED;
768 } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
769 return MYBLUE;
770 } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
771 return MYGREEN;
772 } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
773 return MYYELLOW;
774 } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
775 return MYCYAN;
776 } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
777 return MYMAGENTA;
778 } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
779 return MYWHITE;
780 }
781 }
782 throw new IllegalArgumentException("Invalid color: " +
783 attr.getForeColor().getValue());
784 }
785
786 /**
787 * Convert a CellAttributes background color to an Swing Color.
788 *
789 * @param attr the text attributes
790 * @return the Swing Color
791 */
792 private Color attrToBackgroundColor(final CellAttributes attr) {
793 if (attr.getBackColor().equals(jexer.bits.Color.BLACK)) {
794 return MYBLACK;
795 } else if (attr.getBackColor().equals(jexer.bits.Color.RED)) {
796 return MYRED;
797 } else if (attr.getBackColor().equals(jexer.bits.Color.BLUE)) {
798 return MYBLUE;
799 } else if (attr.getBackColor().equals(jexer.bits.Color.GREEN)) {
800 return MYGREEN;
801 } else if (attr.getBackColor().equals(jexer.bits.Color.YELLOW)) {
802 return MYYELLOW;
803 } else if (attr.getBackColor().equals(jexer.bits.Color.CYAN)) {
804 return MYCYAN;
805 } else if (attr.getBackColor().equals(jexer.bits.Color.MAGENTA)) {
806 return MYMAGENTA;
807 } else if (attr.getBackColor().equals(jexer.bits.Color.WHITE)) {
808 return MYWHITE;
809 }
810 throw new IllegalArgumentException("Invalid color: " +
811 attr.getBackColor().getValue());
812 }
813
814 /**
815 * Figure out what textAdjustX and textAdjustY should be, based on the
816 * location of a vertical bar (to find textAdjustY) and a horizontal bar
817 * (to find textAdjustX).
818 *
819 * @return true if textAdjustX and textAdjustY were guessed at correctly
820 */
821 private boolean getFontAdjustments() {
822 BufferedImage image = null;
823
824 // What SHOULD happen is that the topmost/leftmost white pixel is at
825 // position (gr2x, gr2y). But it might also be off by a pixel in
826 // either direction.
827
828 Graphics2D gr2 = null;
829 int gr2x = 3;
830 int gr2y = 3;
831 image = new BufferedImage(textWidth * 2, textHeight * 2,
832 BufferedImage.TYPE_INT_ARGB);
833
834 gr2 = image.createGraphics();
835 gr2.setFont(swing.getFont());
836 gr2.setColor(java.awt.Color.BLACK);
837 gr2.fillRect(0, 0, textWidth * 2, textHeight * 2);
838 gr2.setColor(java.awt.Color.WHITE);
839 char [] chars = new char[1];
840 chars[0] = jexer.bits.GraphicsChars.VERTICAL_BAR;
841 gr2.drawChars(chars, 0, 1, gr2x, gr2y + textHeight - maxDescent);
842 gr2.dispose();
843
844 for (int x = 0; x < textWidth; x++) {
845 for (int y = 0; y < textHeight; y++) {
846
847 /*
848 System.err.println("X: " + x + " Y: " + y + " " +
849 image.getRGB(x, y));
850 */
851
852 if ((image.getRGB(x, y) & 0xFFFFFF) != 0) {
853 textAdjustY = (gr2y - y);
854
855 // System.err.println("textAdjustY: " + textAdjustY);
856 x = textWidth;
857 break;
858 }
859 }
860 }
861
862 gr2 = image.createGraphics();
863 gr2.setFont(swing.getFont());
864 gr2.setColor(java.awt.Color.BLACK);
865 gr2.fillRect(0, 0, textWidth * 2, textHeight * 2);
866 gr2.setColor(java.awt.Color.WHITE);
867 chars[0] = jexer.bits.GraphicsChars.SINGLE_BAR;
868 gr2.drawChars(chars, 0, 1, gr2x, gr2y + textHeight - maxDescent);
869 gr2.dispose();
870
871 for (int x = 0; x < textWidth; x++) {
872 for (int y = 0; y < textHeight; y++) {
873
874 /*
875 System.err.println("X: " + x + " Y: " + y + " " +
876 image.getRGB(x, y));
877 */
878
879 if ((image.getRGB(x, y) & 0xFFFFFF) != 0) {
880 textAdjustX = (gr2x - x);
881
882 // System.err.println("textAdjustX: " + textAdjustX);
883 return true;
884 }
885 }
886 }
887
888 // Something weird happened, don't rely on this function.
889 // System.err.println("getFontAdjustments: false");
890 return false;
891 }
892
893 /**
894 * Figure out my font dimensions. This code path works OK for the JFrame
895 * case, and can be called immediately after JFrame creation.
896 */
897 private void getFontDimensions() {
898 swing.setFont(font);
899 Graphics gr = swing.getGraphics();
900 if (gr == null) {
901 return;
902 }
903 getFontDimensions(gr);
904 }
905
906 /**
907 * Figure out my font dimensions. This code path is needed to lazy-load
908 * the information inside paint().
909 *
910 * @param gr Graphics object to use
911 */
912 private void getFontDimensions(final Graphics gr) {
913 swing.setFont(font);
914 FontMetrics fm = gr.getFontMetrics();
915 maxDescent = fm.getMaxDescent();
916 Rectangle2D bounds = fm.getMaxCharBounds(gr);
917 int leading = fm.getLeading();
918 textWidth = (int)Math.round(bounds.getWidth());
919 // textHeight = (int)Math.round(bounds.getHeight()) - maxDescent;
920
921 // This produces the same number, but works better for ugly
922 // monospace.
923 textHeight = fm.getMaxAscent() + maxDescent - leading;
924
925 if (gotTerminus == true) {
926 textHeight++;
927 }
928
929 if (getFontAdjustments() == false) {
930 // We were unable to programmatically determine textAdjustX and
931 // textAdjustY, so try some guesses based on VM vendor.
932 String runtime = System.getProperty("java.runtime.name");
933 if ((runtime != null) && (runtime.contains("Java(TM)"))) {
934 textAdjustY = -1;
935 textAdjustX = 0;
936 }
937 }
938
939 if (sessionInfo != null) {
940 sessionInfo.setTextCellDimensions(textWidth, textHeight);
941 }
942 gotFontDimensions = true;
943 }
944
945 /**
946 * Resize to font dimensions.
947 */
948 public void resizeToScreen() {
949 swing.setDimensions(textWidth * width, textHeight * height);
950 }
951
952 /**
953 * Draw one glyph to the screen.
954 *
955 * @param gr the Swing Graphics context
956 * @param cell the Cell to draw
957 * @param xPixel the x-coordinate to render to. 0 means the
958 * left-most pixel column.
959 * @param yPixel the y-coordinate to render to. 0 means the top-most
960 * pixel row.
961 */
962 private void drawGlyph(final Graphics gr, final Cell cell,
963 final int xPixel, final int yPixel) {
964
965 /*
966 System.err.println("drawGlyph(): " + xPixel + " " + yPixel +
967 " " + cell);
968 */
969
970 BufferedImage image = null;
971 if (cell.isBlink() && !cursorBlinkVisible) {
972 image = glyphCacheBlink.get(cell);
973 } else {
974 image = glyphCache.get(cell);
975 }
976 if (image != null) {
977 if (swing.getFrame() != null) {
978 gr.drawImage(image, xPixel, yPixel, swing.getFrame());
979 } else {
980 gr.drawImage(image, xPixel, yPixel, swing.getComponent());
981 }
982 return;
983 }
984
985 // Generate glyph and draw it.
986 Graphics2D gr2 = null;
987 int gr2x = xPixel;
988 int gr2y = yPixel;
989 if ((SwingComponent.tripleBuffer) && (swing.getFrame() != null)) {
990 image = new BufferedImage(textWidth, textHeight,
991 BufferedImage.TYPE_INT_ARGB);
992 gr2 = image.createGraphics();
993 gr2.setFont(swing.getFont());
994 gr2x = 0;
995 gr2y = 0;
996 } else {
997 gr2 = (Graphics2D) gr;
998 }
999
1000 Cell cellColor = new Cell();
1001 cellColor.setTo(cell);
1002
1003 // Check for reverse
1004 if (cell.isReverse()) {
1005 cellColor.setForeColor(cell.getBackColor());
1006 cellColor.setBackColor(cell.getForeColor());
1007 }
1008
1009 // Draw the background rectangle, then the foreground character.
1010 gr2.setColor(attrToBackgroundColor(cellColor));
1011 gr2.fillRect(gr2x, gr2y, textWidth, textHeight);
1012
1013 // Handle blink and underline
1014 if (!cell.isBlink()
1015 || (cell.isBlink() && cursorBlinkVisible)
1016 ) {
1017 gr2.setColor(attrToForegroundColor(cellColor));
1018 char [] chars = new char[1];
1019 chars[0] = cell.getChar();
1020 gr2.drawChars(chars, 0, 1, gr2x + textAdjustX,
1021 gr2y + textHeight - maxDescent + textAdjustY);
1022
1023 if (cell.isUnderline()) {
1024 gr2.fillRect(gr2x, gr2y + textHeight - 2, textWidth, 2);
1025 }
1026 }
1027
1028 if ((SwingComponent.tripleBuffer) && (swing.getFrame() != null)) {
1029 gr2.dispose();
1030
1031 // We need a new key that will not be mutated by
1032 // invertCell().
1033 Cell key = new Cell();
1034 key.setTo(cell);
1035 if (cell.isBlink() && !cursorBlinkVisible) {
1036 glyphCacheBlink.put(key, image);
1037 } else {
1038 glyphCache.put(key, image);
1039 }
1040
1041 if (swing.getFrame() != null) {
1042 gr.drawImage(image, xPixel, yPixel, swing.getFrame());
1043 } else {
1044 gr.drawImage(image, xPixel, yPixel, swing.getComponent());
1045 }
1046 }
1047
1048 }
1049
1050 /**
1051 * Check if the cursor is visible, and if so draw it.
1052 *
1053 * @param gr the Swing Graphics context
1054 */
1055 private void drawCursor(final Graphics gr) {
1056
1057 if (cursorVisible
1058 && (cursorY >= 0)
1059 && (cursorX >= 0)
1060 && (cursorY <= height - 1)
1061 && (cursorX <= width - 1)
1062 && cursorBlinkVisible
1063 ) {
1064 int xPixel = cursorX * textWidth + left;
1065 int yPixel = cursorY * textHeight + top;
1066 Cell lCell = logical[cursorX][cursorY];
1067 gr.setColor(attrToForegroundColor(lCell));
1068 switch (cursorStyle) {
1069 default:
1070 // Fall through...
1071 case UNDERLINE:
1072 gr.fillRect(xPixel, yPixel + textHeight - 2, textWidth, 2);
1073 break;
1074 case BLOCK:
1075 gr.fillRect(xPixel, yPixel, textWidth, textHeight);
1076 break;
1077 case OUTLINE:
1078 gr.drawRect(xPixel, yPixel, textWidth - 1, textHeight - 1);
1079 break;
1080 }
1081 }
1082 }
1083
1084 /**
1085 * Reset the blink timer.
1086 */
1087 private void resetBlinkTimer() {
1088 lastBlinkTime = System.currentTimeMillis();
1089 cursorBlinkVisible = true;
1090 }
1091
1092 /**
1093 * Paint redraws the whole screen.
1094 *
1095 * @param gr the Swing Graphics context
1096 */
1097 public void paint(final Graphics gr) {
1098
1099 if (gotFontDimensions == false) {
1100 // Lazy-load the text width/height
1101 getFontDimensions(gr);
1102 /*
1103 System.err.println("textWidth " + textWidth +
1104 " textHeight " + textHeight);
1105 System.err.println("FONT: " + swing.getFont() + " font " + font);
1106 */
1107 }
1108
1109 if ((swing.getBufferStrategy() != null)
1110 && (SwingUtilities.isEventDispatchThread())
1111 ) {
1112 // System.err.println("paint(), skip first paint on swing thread");
1113 return;
1114 }
1115
1116 int xCellMin = 0;
1117 int xCellMax = width;
1118 int yCellMin = 0;
1119 int yCellMax = height;
1120
1121 Rectangle bounds = gr.getClipBounds();
1122 if (bounds != null) {
1123 // Only update what is in the bounds
1124 xCellMin = textColumn(bounds.x);
1125 xCellMax = textColumn(bounds.x + bounds.width);
1126 if (xCellMax > width) {
1127 xCellMax = width;
1128 }
1129 if (xCellMin >= xCellMax) {
1130 xCellMin = xCellMax - 2;
1131 }
1132 if (xCellMin < 0) {
1133 xCellMin = 0;
1134 }
1135 yCellMin = textRow(bounds.y);
1136 yCellMax = textRow(bounds.y + bounds.height);
1137 if (yCellMax > height) {
1138 yCellMax = height;
1139 }
1140 if (yCellMin >= yCellMax) {
1141 yCellMin = yCellMax - 2;
1142 }
1143 if (yCellMin < 0) {
1144 yCellMin = 0;
1145 }
1146 } else {
1147 // We need a total repaint
1148 reallyCleared = true;
1149 }
1150
1151 // Prevent updates to the screen's data from the TApplication
1152 // threads.
1153 synchronized (this) {
1154
1155 /*
1156 System.err.printf("bounds %s X %d %d Y %d %d\n",
1157 bounds, xCellMin, xCellMax, yCellMin, yCellMax);
1158 */
1159
1160 for (int y = yCellMin; y < yCellMax; y++) {
1161 for (int x = xCellMin; x < xCellMax; x++) {
1162
1163 int xPixel = x * textWidth + left;
1164 int yPixel = y * textHeight + top;
1165
1166 Cell lCell = logical[x][y];
1167 Cell pCell = physical[x][y];
1168
1169 if (!lCell.equals(pCell)
1170 || lCell.isBlink()
1171 || reallyCleared
1172 || (swing.getFrame() == null)) {
1173
1174 drawGlyph(gr, lCell, xPixel, yPixel);
1175
1176 // Physical is always updated
1177 physical[x][y].setTo(lCell);
1178 }
1179 }
1180 }
1181 drawCursor(gr);
1182
1183 reallyCleared = false;
1184 } // synchronized (this)
1185 }
1186
1187 /**
1188 * Restore terminal to normal state.
1189 */
1190 public void shutdown() {
1191 swing.dispose();
1192 }
1193
1194 /**
1195 * Push the logical screen to the physical device.
1196 */
1197 private void drawToSwing() {
1198
1199 /*
1200 System.err.printf("drawToSwing(): reallyCleared %s dirty %s\n",
1201 reallyCleared, dirty);
1202 */
1203
1204 // If reallyCleared is set, we have to draw everything.
1205 if ((swing.getFrame() != null)
1206 && (swing.getBufferStrategy() != null)
1207 && (reallyCleared == true)
1208 ) {
1209 // Triple-buffering: we have to redraw everything on this thread.
1210 Graphics gr = swing.getBufferStrategy().getDrawGraphics();
1211 swing.paint(gr);
1212 gr.dispose();
1213 swing.getBufferStrategy().show();
1214 Toolkit.getDefaultToolkit().sync();
1215 return;
1216 } else if (((swing.getFrame() != null)
1217 && (swing.getBufferStrategy() == null))
1218 || (reallyCleared == true)
1219 ) {
1220 // Repaint everything on the Swing thread.
1221 // System.err.println("REPAINT ALL");
1222 swing.repaint();
1223 return;
1224 }
1225
1226 if ((swing.getFrame() != null) && (swing.getBufferStrategy() != null)) {
1227 Graphics gr = swing.getBufferStrategy().getDrawGraphics();
1228
1229 synchronized (this) {
1230 for (int y = 0; y < height; y++) {
1231 for (int x = 0; x < width; x++) {
1232 Cell lCell = logical[x][y];
1233 Cell pCell = physical[x][y];
1234
1235 int xPixel = x * textWidth + left;
1236 int yPixel = y * textHeight + top;
1237
1238 if (!lCell.equals(pCell)
1239 || ((x == cursorX)
1240 && (y == cursorY)
1241 && cursorVisible)
1242 || (lCell.isBlink())
1243 ) {
1244 drawGlyph(gr, lCell, xPixel, yPixel);
1245 physical[x][y].setTo(lCell);
1246 }
1247 }
1248 }
1249 drawCursor(gr);
1250 } // synchronized (this)
1251
1252 gr.dispose();
1253 swing.getBufferStrategy().show();
1254 Toolkit.getDefaultToolkit().sync();
1255 return;
1256 }
1257
1258 // Swing thread version: request a repaint, but limit it to the area
1259 // that has changed.
1260
1261 // Find the minimum-size damaged region.
1262 int xMin = swing.getWidth();
1263 int xMax = 0;
1264 int yMin = swing.getHeight();
1265 int yMax = 0;
1266
1267 synchronized (this) {
1268 for (int y = 0; y < height; y++) {
1269 for (int x = 0; x < width; x++) {
1270 Cell lCell = logical[x][y];
1271 Cell pCell = physical[x][y];
1272
1273 int xPixel = x * textWidth + left;
1274 int yPixel = y * textHeight + top;
1275
1276 if (!lCell.equals(pCell)
1277 || ((x == cursorX)
1278 && (y == cursorY)
1279 && cursorVisible)
1280 || lCell.isBlink()
1281 ) {
1282 if (xPixel < xMin) {
1283 xMin = xPixel;
1284 }
1285 if (xPixel + textWidth > xMax) {
1286 xMax = xPixel + textWidth;
1287 }
1288 if (yPixel < yMin) {
1289 yMin = yPixel;
1290 }
1291 if (yPixel + textHeight > yMax) {
1292 yMax = yPixel + textHeight;
1293 }
1294 }
1295 }
1296 }
1297 }
1298 if (xMin + textWidth >= xMax) {
1299 xMax += textWidth;
1300 }
1301 if (yMin + textHeight >= yMax) {
1302 yMax += textHeight;
1303 }
1304
1305 // Repaint the desired area
1306 /*
1307 System.err.printf("REPAINT X %d %d Y %d %d\n", xMin, xMax,
1308 yMin, yMax);
1309 */
1310
1311 if ((swing.getFrame() != null) && (swing.getBufferStrategy() != null)) {
1312 // This path should never be taken, but is left here for
1313 // completeness.
1314 Graphics gr = swing.getBufferStrategy().getDrawGraphics();
1315 Rectangle bounds = new Rectangle(xMin, yMin, xMax - xMin,
1316 yMax - yMin);
1317 gr.setClip(bounds);
1318 swing.paint(gr);
1319 gr.dispose();
1320 swing.getBufferStrategy().show();
1321 Toolkit.getDefaultToolkit().sync();
1322 } else {
1323 // Repaint on the Swing thread.
1324 swing.repaint(xMin, yMin, xMax - xMin, yMax - yMin);
1325 }
1326 }
1327
1328 /**
1329 * Convert pixel column position to text cell column position.
1330 *
1331 * @param x pixel column position
1332 * @return text cell column position
1333 */
1334 public int textColumn(final int x) {
1335 int column = ((x - left) / textWidth);
1336 if (column < 0) {
1337 column = 0;
1338 }
1339 if (column > width - 1) {
1340 column = width - 1;
1341 }
1342 return column;
1343 }
1344
1345 /**
1346 * Convert pixel row position to text cell row position.
1347 *
1348 * @param y pixel row position
1349 * @return text cell row position
1350 */
1351 public int textRow(final int y) {
1352 int row = ((y - top) / textHeight);
1353 if (row < 0) {
1354 row = 0;
1355 }
1356 if (row > height - 1) {
1357 row = height - 1;
1358 }
1359 return row;
1360 }
1361
1362 /**
1363 * Getter for sessionInfo.
1364 *
1365 * @return the SessionInfo
1366 */
1367 public SessionInfo getSessionInfo() {
1368 return sessionInfo;
1369 }
1370
1371 // ------------------------------------------------------------------------
1372 // KeyListener ------------------------------------------------------------
1373 // ------------------------------------------------------------------------
1374
1375 /**
1376 * Pass Swing keystrokes into the event queue.
1377 *
1378 * @param key keystroke received
1379 */
1380 public void keyReleased(final KeyEvent key) {
1381 // Ignore release events
1382 }
1383
1384 /**
1385 * Pass Swing keystrokes into the event queue.
1386 *
1387 * @param key keystroke received
1388 */
1389 public void keyTyped(final KeyEvent key) {
1390 // Ignore typed events
1391 }
1392
1393 /**
1394 * Pass Swing keystrokes into the event queue.
1395 *
1396 * @param key keystroke received
1397 */
1398 public void keyPressed(final KeyEvent key) {
1399 boolean alt = false;
1400 boolean shift = false;
1401 boolean ctrl = false;
1402 char ch = ' ';
1403 boolean isKey = false;
1404 if (key.isActionKey()) {
1405 isKey = true;
1406 } else {
1407 ch = key.getKeyChar();
1408 }
1409 alt = key.isAltDown();
1410 ctrl = key.isControlDown();
1411 shift = key.isShiftDown();
1412
1413 /*
1414 System.err.printf("Swing Key: %s\n", key);
1415 System.err.printf(" isKey: %s\n", isKey);
1416 System.err.printf(" alt: %s\n", alt);
1417 System.err.printf(" ctrl: %s\n", ctrl);
1418 System.err.printf(" shift: %s\n", shift);
1419 System.err.printf(" ch: %s\n", ch);
1420 */
1421
1422 // Special case: not return the bare modifier presses
1423 switch (key.getKeyCode()) {
1424 case KeyEvent.VK_ALT:
1425 return;
1426 case KeyEvent.VK_ALT_GRAPH:
1427 return;
1428 case KeyEvent.VK_CONTROL:
1429 return;
1430 case KeyEvent.VK_SHIFT:
1431 return;
1432 case KeyEvent.VK_META:
1433 return;
1434 default:
1435 break;
1436 }
1437
1438 TKeypress keypress = null;
1439 if (isKey) {
1440 switch (key.getKeyCode()) {
1441 case KeyEvent.VK_F1:
1442 keypress = new TKeypress(true, TKeypress.F1, ' ',
1443 alt, ctrl, shift);
1444 break;
1445 case KeyEvent.VK_F2:
1446 keypress = new TKeypress(true, TKeypress.F2, ' ',
1447 alt, ctrl, shift);
1448 break;
1449 case KeyEvent.VK_F3:
1450 keypress = new TKeypress(true, TKeypress.F3, ' ',
1451 alt, ctrl, shift);
1452 break;
1453 case KeyEvent.VK_F4:
1454 keypress = new TKeypress(true, TKeypress.F4, ' ',
1455 alt, ctrl, shift);
1456 break;
1457 case KeyEvent.VK_F5:
1458 keypress = new TKeypress(true, TKeypress.F5, ' ',
1459 alt, ctrl, shift);
1460 break;
1461 case KeyEvent.VK_F6:
1462 keypress = new TKeypress(true, TKeypress.F6, ' ',
1463 alt, ctrl, shift);
1464 break;
1465 case KeyEvent.VK_F7:
1466 keypress = new TKeypress(true, TKeypress.F7, ' ',
1467 alt, ctrl, shift);
1468 break;
1469 case KeyEvent.VK_F8:
1470 keypress = new TKeypress(true, TKeypress.F8, ' ',
1471 alt, ctrl, shift);
1472 break;
1473 case KeyEvent.VK_F9:
1474 keypress = new TKeypress(true, TKeypress.F9, ' ',
1475 alt, ctrl, shift);
1476 break;
1477 case KeyEvent.VK_F10:
1478 keypress = new TKeypress(true, TKeypress.F10, ' ',
1479 alt, ctrl, shift);
1480 break;
1481 case KeyEvent.VK_F11:
1482 keypress = new TKeypress(true, TKeypress.F11, ' ',
1483 alt, ctrl, shift);
1484 break;
1485 case KeyEvent.VK_F12:
1486 keypress = new TKeypress(true, TKeypress.F12, ' ',
1487 alt, ctrl, shift);
1488 break;
1489 case KeyEvent.VK_HOME:
1490 keypress = new TKeypress(true, TKeypress.HOME, ' ',
1491 alt, ctrl, shift);
1492 break;
1493 case KeyEvent.VK_END:
1494 keypress = new TKeypress(true, TKeypress.END, ' ',
1495 alt, ctrl, shift);
1496 break;
1497 case KeyEvent.VK_PAGE_UP:
1498 keypress = new TKeypress(true, TKeypress.PGUP, ' ',
1499 alt, ctrl, shift);
1500 break;
1501 case KeyEvent.VK_PAGE_DOWN:
1502 keypress = new TKeypress(true, TKeypress.PGDN, ' ',
1503 alt, ctrl, shift);
1504 break;
1505 case KeyEvent.VK_INSERT:
1506 keypress = new TKeypress(true, TKeypress.INS, ' ',
1507 alt, ctrl, shift);
1508 break;
1509 case KeyEvent.VK_DELETE:
1510 keypress = new TKeypress(true, TKeypress.DEL, ' ',
1511 alt, ctrl, shift);
1512 break;
1513 case KeyEvent.VK_RIGHT:
1514 keypress = new TKeypress(true, TKeypress.RIGHT, ' ',
1515 alt, ctrl, shift);
1516 break;
1517 case KeyEvent.VK_LEFT:
1518 keypress = new TKeypress(true, TKeypress.LEFT, ' ',
1519 alt, ctrl, shift);
1520 break;
1521 case KeyEvent.VK_UP:
1522 keypress = new TKeypress(true, TKeypress.UP, ' ',
1523 alt, ctrl, shift);
1524 break;
1525 case KeyEvent.VK_DOWN:
1526 keypress = new TKeypress(true, TKeypress.DOWN, ' ',
1527 alt, ctrl, shift);
1528 break;
1529 case KeyEvent.VK_TAB:
1530 // Special case: distinguish TAB vs BTAB
1531 if (shift) {
1532 keypress = kbShiftTab;
1533 } else {
1534 keypress = kbTab;
1535 }
1536 break;
1537 case KeyEvent.VK_ENTER:
1538 keypress = new TKeypress(true, TKeypress.ENTER, ' ',
1539 alt, ctrl, shift);
1540 break;
1541 case KeyEvent.VK_ESCAPE:
1542 keypress = new TKeypress(true, TKeypress.ESC, ' ',
1543 alt, ctrl, shift);
1544 break;
1545 case KeyEvent.VK_BACK_SPACE:
1546 keypress = kbBackspace;
1547 break;
1548 default:
1549 // Unsupported, ignore
1550 return;
1551 }
1552 }
1553
1554 if (keypress == null) {
1555 switch (ch) {
1556 case 0x08:
1557 // Disambiguate ^H from Backspace.
1558 if (KeyEvent.getKeyText(key.getKeyCode()).equals("H")) {
1559 // This is ^H.
1560 keypress = kbBackspace;
1561 } else {
1562 // We are emulating Xterm here, where the backspace key
1563 // on the keyboard returns ^?.
1564 keypress = kbBackspaceDel;
1565 }
1566 break;
1567 case 0x0A:
1568 keypress = kbEnter;
1569 break;
1570 case 0x1B:
1571 keypress = kbEsc;
1572 break;
1573 case 0x0D:
1574 keypress = kbEnter;
1575 break;
1576 case 0x09:
1577 if (shift) {
1578 keypress = kbShiftTab;
1579 } else {
1580 keypress = kbTab;
1581 }
1582 break;
1583 case 0x7F:
1584 keypress = kbDel;
1585 break;
1586 default:
1587 if (!alt && ctrl && !shift) {
1588 ch = KeyEvent.getKeyText(key.getKeyCode()).charAt(0);
1589 }
1590 // Not a special key, put it together
1591 keypress = new TKeypress(false, 0, ch, alt, ctrl, shift);
1592 }
1593 }
1594
1595 // Save it and we are done.
1596 synchronized (eventQueue) {
1597 eventQueue.add(new TKeypressEvent(keypress));
1598 resetBlinkTimer();
1599 }
1600 if (listener != null) {
1601 synchronized (listener) {
1602 listener.notifyAll();
1603 }
1604 }
1605 }
1606
1607 // ------------------------------------------------------------------------
1608 // WindowListener ---------------------------------------------------------
1609 // ------------------------------------------------------------------------
1610
1611 /**
1612 * Pass window events into the event queue.
1613 *
1614 * @param event window event received
1615 */
1616 public void windowActivated(final WindowEvent event) {
1617 // Force a total repaint
1618 synchronized (this) {
1619 clearPhysical();
1620 }
1621 }
1622
1623 /**
1624 * Pass window events into the event queue.
1625 *
1626 * @param event window event received
1627 */
1628 public void windowClosed(final WindowEvent event) {
1629 // Ignore
1630 }
1631
1632 /**
1633 * Pass window events into the event queue.
1634 *
1635 * @param event window event received
1636 */
1637 public void windowClosing(final WindowEvent event) {
1638 // Drop a cmAbort and walk away
1639 synchronized (eventQueue) {
1640 eventQueue.add(new TCommandEvent(cmAbort));
1641 resetBlinkTimer();
1642 }
1643 if (listener != null) {
1644 synchronized (listener) {
1645 listener.notifyAll();
1646 }
1647 }
1648 }
1649
1650 /**
1651 * Pass window events into the event queue.
1652 *
1653 * @param event window event received
1654 */
1655 public void windowDeactivated(final WindowEvent event) {
1656 // Ignore
1657 }
1658
1659 /**
1660 * Pass window events into the event queue.
1661 *
1662 * @param event window event received
1663 */
1664 public void windowDeiconified(final WindowEvent event) {
1665 // Ignore
1666 }
1667
1668 /**
1669 * Pass window events into the event queue.
1670 *
1671 * @param event window event received
1672 */
1673 public void windowIconified(final WindowEvent event) {
1674 // Ignore
1675 }
1676
1677 /**
1678 * Pass window events into the event queue.
1679 *
1680 * @param event window event received
1681 */
1682 public void windowOpened(final WindowEvent event) {
1683 // Ignore
1684 }
1685
1686 // ------------------------------------------------------------------------
1687 // ComponentListener ------------------------------------------------------
1688 // ------------------------------------------------------------------------
1689
1690 /**
1691 * Pass component events into the event queue.
1692 *
1693 * @param event component event received
1694 */
1695 public void componentHidden(final ComponentEvent event) {
1696 // Ignore
1697 }
1698
1699 /**
1700 * Pass component events into the event queue.
1701 *
1702 * @param event component event received
1703 */
1704 public void componentShown(final ComponentEvent event) {
1705 // Ignore
1706 }
1707
1708 /**
1709 * Pass component events into the event queue.
1710 *
1711 * @param event component event received
1712 */
1713 public void componentMoved(final ComponentEvent event) {
1714 // Ignore
1715 }
1716
1717 /**
1718 * Pass component events into the event queue.
1719 *
1720 * @param event component event received
1721 */
1722 public void componentResized(final ComponentEvent event) {
1723 if (gotFontDimensions == false) {
1724 // We are still waiting to get font information. Don't pass a
1725 // resize event up.
1726 // System.err.println("size " + swing.getComponent().getSize());
1727 return;
1728 }
1729
1730 // Drop a new TResizeEvent into the queue
1731 sessionInfo.queryWindowSize();
1732 synchronized (eventQueue) {
1733 TResizeEvent windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN,
1734 sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight());
1735 eventQueue.add(windowResize);
1736 resetBlinkTimer();
1737 /*
1738 System.err.println("Add resize event: " + windowResize.getWidth() +
1739 " x " + windowResize.getHeight());
1740 */
1741 }
1742 if (listener != null) {
1743 synchronized (listener) {
1744 listener.notifyAll();
1745 }
1746 }
1747 }
1748
1749 // ------------------------------------------------------------------------
1750 // MouseMotionListener ----------------------------------------------------
1751 // ------------------------------------------------------------------------
1752
1753 /**
1754 * Pass mouse events into the event queue.
1755 *
1756 * @param mouse mouse event received
1757 */
1758 public void mouseDragged(final MouseEvent mouse) {
1759 int modifiers = mouse.getModifiersEx();
1760 boolean eventMouse1 = false;
1761 boolean eventMouse2 = false;
1762 boolean eventMouse3 = false;
1763 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
1764 eventMouse1 = true;
1765 }
1766 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
1767 eventMouse2 = true;
1768 }
1769 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
1770 eventMouse3 = true;
1771 }
1772 mouse1 = eventMouse1;
1773 mouse2 = eventMouse2;
1774 mouse3 = eventMouse3;
1775 int x = textColumn(mouse.getX());
1776 int y = textRow(mouse.getY());
1777
1778 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION,
1779 x, y, x, y, mouse1, mouse2, mouse3, false, false);
1780
1781 synchronized (eventQueue) {
1782 eventQueue.add(mouseEvent);
1783 resetBlinkTimer();
1784 }
1785 if (listener != null) {
1786 synchronized (listener) {
1787 listener.notifyAll();
1788 }
1789 }
1790 }
1791
1792 /**
1793 * Pass mouse events into the event queue.
1794 *
1795 * @param mouse mouse event received
1796 */
1797 public void mouseMoved(final MouseEvent mouse) {
1798 int x = textColumn(mouse.getX());
1799 int y = textRow(mouse.getY());
1800 if ((x == oldMouseX) && (y == oldMouseY)) {
1801 // Bail out, we've moved some pixels but not a whole text cell.
1802 return;
1803 }
1804 oldMouseX = x;
1805 oldMouseY = y;
1806
1807 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION,
1808 x, y, x, y, mouse1, mouse2, mouse3, false, false);
1809
1810 synchronized (eventQueue) {
1811 eventQueue.add(mouseEvent);
1812 resetBlinkTimer();
1813 }
1814 if (listener != null) {
1815 synchronized (listener) {
1816 listener.notifyAll();
1817 }
1818 }
1819 }
1820
1821 // ------------------------------------------------------------------------
1822 // MouseListener ----------------------------------------------------------
1823 // ------------------------------------------------------------------------
1824
1825 /**
1826 * Pass mouse events into the event queue.
1827 *
1828 * @param mouse mouse event received
1829 */
1830 public void mouseClicked(final MouseEvent mouse) {
1831 // Ignore
1832 }
1833
1834 /**
1835 * Pass mouse events into the event queue.
1836 *
1837 * @param mouse mouse event received
1838 */
1839 public void mouseEntered(final MouseEvent mouse) {
1840 // Ignore
1841 }
1842
1843 /**
1844 * Pass mouse events into the event queue.
1845 *
1846 * @param mouse mouse event received
1847 */
1848 public void mouseExited(final MouseEvent mouse) {
1849 // Ignore
1850 }
1851
1852 /**
1853 * Pass mouse events into the event queue.
1854 *
1855 * @param mouse mouse event received
1856 */
1857 public void mousePressed(final MouseEvent mouse) {
1858 int modifiers = mouse.getModifiersEx();
1859 boolean eventMouse1 = false;
1860 boolean eventMouse2 = false;
1861 boolean eventMouse3 = false;
1862 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
1863 eventMouse1 = true;
1864 }
1865 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
1866 eventMouse2 = true;
1867 }
1868 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
1869 eventMouse3 = true;
1870 }
1871 mouse1 = eventMouse1;
1872 mouse2 = eventMouse2;
1873 mouse3 = eventMouse3;
1874 int x = textColumn(mouse.getX());
1875 int y = textRow(mouse.getY());
1876
1877 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN,
1878 x, y, x, y, mouse1, mouse2, mouse3, false, false);
1879
1880 synchronized (eventQueue) {
1881 eventQueue.add(mouseEvent);
1882 resetBlinkTimer();
1883 }
1884 if (listener != null) {
1885 synchronized (listener) {
1886 listener.notifyAll();
1887 }
1888 }
1889 }
1890
1891 /**
1892 * Pass mouse events into the event queue.
1893 *
1894 * @param mouse mouse event received
1895 */
1896 public void mouseReleased(final MouseEvent mouse) {
1897 int modifiers = mouse.getModifiersEx();
1898 boolean eventMouse1 = false;
1899 boolean eventMouse2 = false;
1900 boolean eventMouse3 = false;
1901 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
1902 eventMouse1 = true;
1903 }
1904 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
1905 eventMouse2 = true;
1906 }
1907 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
1908 eventMouse3 = true;
1909 }
1910 if (mouse1) {
1911 mouse1 = false;
1912 eventMouse1 = true;
1913 }
1914 if (mouse2) {
1915 mouse2 = false;
1916 eventMouse2 = true;
1917 }
1918 if (mouse3) {
1919 mouse3 = false;
1920 eventMouse3 = true;
1921 }
1922 int x = textColumn(mouse.getX());
1923 int y = textRow(mouse.getY());
1924
1925 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_UP,
1926 x, y, x, y, eventMouse1, eventMouse2, eventMouse3, false, false);
1927
1928 synchronized (eventQueue) {
1929 eventQueue.add(mouseEvent);
1930 resetBlinkTimer();
1931 }
1932 if (listener != null) {
1933 synchronized (listener) {
1934 listener.notifyAll();
1935 }
1936 }
1937 }
1938
1939 // ------------------------------------------------------------------------
1940 // MouseWheelListener -----------------------------------------------------
1941 // ------------------------------------------------------------------------
1942
1943 /**
1944 * Pass mouse events into the event queue.
1945 *
1946 * @param mouse mouse event received
1947 */
1948 public void mouseWheelMoved(final MouseWheelEvent mouse) {
1949 int modifiers = mouse.getModifiersEx();
1950 boolean eventMouse1 = false;
1951 boolean eventMouse2 = false;
1952 boolean eventMouse3 = false;
1953 boolean mouseWheelUp = false;
1954 boolean mouseWheelDown = false;
1955 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
1956 eventMouse1 = true;
1957 }
1958 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
1959 eventMouse2 = true;
1960 }
1961 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
1962 eventMouse3 = true;
1963 }
1964 mouse1 = eventMouse1;
1965 mouse2 = eventMouse2;
1966 mouse3 = eventMouse3;
1967 int x = textColumn(mouse.getX());
1968 int y = textRow(mouse.getY());
1969 if (mouse.getWheelRotation() > 0) {
1970 mouseWheelDown = true;
1971 }
1972 if (mouse.getWheelRotation() < 0) {
1973 mouseWheelUp = true;
1974 }
1975
1976 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN,
1977 x, y, x, y, mouse1, mouse2, mouse3, mouseWheelUp, mouseWheelDown);
1978
1979 synchronized (eventQueue) {
1980 eventQueue.add(mouseEvent);
1981 resetBlinkTimer();
1982 }
1983 if (listener != null) {
1984 synchronized (listener) {
1985 listener.notifyAll();
1986 }
1987 }
1988 }
1989
1990 }