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