#33 expose SwingComponent
[nikiroo-utils.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 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 int rgb = attr.getForeColorRGB();
746 if (rgb >= 0) {
747 int red = (rgb >> 16) & 0xFF;
748 int green = (rgb >> 8) & 0xFF;
749 int blue = rgb & 0xFF;
750
751 return new Color(red, green, blue);
752 }
753
754 if (attr.isBold()) {
755 if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
756 return MYBOLD_BLACK;
757 } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
758 return MYBOLD_RED;
759 } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
760 return MYBOLD_BLUE;
761 } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
762 return MYBOLD_GREEN;
763 } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
764 return MYBOLD_YELLOW;
765 } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
766 return MYBOLD_CYAN;
767 } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
768 return MYBOLD_MAGENTA;
769 } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
770 return MYBOLD_WHITE;
771 }
772 } else {
773 if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
774 return MYBLACK;
775 } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
776 return MYRED;
777 } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
778 return MYBLUE;
779 } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
780 return MYGREEN;
781 } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
782 return MYYELLOW;
783 } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
784 return MYCYAN;
785 } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
786 return MYMAGENTA;
787 } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
788 return MYWHITE;
789 }
790 }
791 throw new IllegalArgumentException("Invalid color: " +
792 attr.getForeColor().getValue());
793 }
794
795 /**
796 * Convert a CellAttributes background color to an Swing Color.
797 *
798 * @param attr the text attributes
799 * @return the Swing Color
800 */
801 private Color attrToBackgroundColor(final CellAttributes attr) {
802 int rgb = attr.getBackColorRGB();
803 if (rgb >= 0) {
804 int red = (rgb >> 16) & 0xFF;
805 int green = (rgb >> 8) & 0xFF;
806 int blue = rgb & 0xFF;
807
808 return new Color(red, green, blue);
809 }
810
811 if (attr.getBackColor().equals(jexer.bits.Color.BLACK)) {
812 return MYBLACK;
813 } else if (attr.getBackColor().equals(jexer.bits.Color.RED)) {
814 return MYRED;
815 } else if (attr.getBackColor().equals(jexer.bits.Color.BLUE)) {
816 return MYBLUE;
817 } else if (attr.getBackColor().equals(jexer.bits.Color.GREEN)) {
818 return MYGREEN;
819 } else if (attr.getBackColor().equals(jexer.bits.Color.YELLOW)) {
820 return MYYELLOW;
821 } else if (attr.getBackColor().equals(jexer.bits.Color.CYAN)) {
822 return MYCYAN;
823 } else if (attr.getBackColor().equals(jexer.bits.Color.MAGENTA)) {
824 return MYMAGENTA;
825 } else if (attr.getBackColor().equals(jexer.bits.Color.WHITE)) {
826 return MYWHITE;
827 }
828 throw new IllegalArgumentException("Invalid color: " +
829 attr.getBackColor().getValue());
830 }
831
832 /**
833 * Figure out what textAdjustX and textAdjustY should be, based on the
834 * location of a vertical bar (to find textAdjustY) and a horizontal bar
835 * (to find textAdjustX).
836 *
837 * @return true if textAdjustX and textAdjustY were guessed at correctly
838 */
839 private boolean getFontAdjustments() {
840 BufferedImage image = null;
841
842 // What SHOULD happen is that the topmost/leftmost white pixel is at
843 // position (gr2x, gr2y). But it might also be off by a pixel in
844 // either direction.
845
846 Graphics2D gr2 = null;
847 int gr2x = 3;
848 int gr2y = 3;
849 image = new BufferedImage(textWidth * 2, textHeight * 2,
850 BufferedImage.TYPE_INT_ARGB);
851
852 gr2 = image.createGraphics();
853 gr2.setFont(swing.getFont());
854 gr2.setColor(java.awt.Color.BLACK);
855 gr2.fillRect(0, 0, textWidth * 2, textHeight * 2);
856 gr2.setColor(java.awt.Color.WHITE);
857 char [] chars = new char[1];
858 chars[0] = jexer.bits.GraphicsChars.VERTICAL_BAR;
859 gr2.drawChars(chars, 0, 1, gr2x, gr2y + textHeight - maxDescent);
860 gr2.dispose();
861
862 for (int x = 0; x < textWidth; x++) {
863 for (int y = 0; y < textHeight; y++) {
864
865 /*
866 System.err.println("X: " + x + " Y: " + y + " " +
867 image.getRGB(x, y));
868 */
869
870 if ((image.getRGB(x, y) & 0xFFFFFF) != 0) {
871 textAdjustY = (gr2y - y);
872
873 // System.err.println("textAdjustY: " + textAdjustY);
874 x = textWidth;
875 break;
876 }
877 }
878 }
879
880 gr2 = image.createGraphics();
881 gr2.setFont(swing.getFont());
882 gr2.setColor(java.awt.Color.BLACK);
883 gr2.fillRect(0, 0, textWidth * 2, textHeight * 2);
884 gr2.setColor(java.awt.Color.WHITE);
885 chars[0] = jexer.bits.GraphicsChars.SINGLE_BAR;
886 gr2.drawChars(chars, 0, 1, gr2x, gr2y + textHeight - maxDescent);
887 gr2.dispose();
888
889 for (int x = 0; x < textWidth; x++) {
890 for (int y = 0; y < textHeight; y++) {
891
892 /*
893 System.err.println("X: " + x + " Y: " + y + " " +
894 image.getRGB(x, y));
895 */
896
897 if ((image.getRGB(x, y) & 0xFFFFFF) != 0) {
898 textAdjustX = (gr2x - x);
899
900 // System.err.println("textAdjustX: " + textAdjustX);
901 return true;
902 }
903 }
904 }
905
906 // Something weird happened, don't rely on this function.
907 // System.err.println("getFontAdjustments: false");
908 return false;
909 }
910
911 /**
912 * Figure out my font dimensions. This code path works OK for the JFrame
913 * case, and can be called immediately after JFrame creation.
914 */
915 private void getFontDimensions() {
916 swing.setFont(font);
917 Graphics gr = swing.getGraphics();
918 if (gr == null) {
919 return;
920 }
921 getFontDimensions(gr);
922 }
923
924 /**
925 * Figure out my font dimensions. This code path is needed to lazy-load
926 * the information inside paint().
927 *
928 * @param gr Graphics object to use
929 */
930 private void getFontDimensions(final Graphics gr) {
931 swing.setFont(font);
932 FontMetrics fm = gr.getFontMetrics();
933 maxDescent = fm.getMaxDescent();
934 Rectangle2D bounds = fm.getMaxCharBounds(gr);
935 int leading = fm.getLeading();
936 textWidth = (int)Math.round(bounds.getWidth());
937 // textHeight = (int)Math.round(bounds.getHeight()) - maxDescent;
938
939 // This produces the same number, but works better for ugly
940 // monospace.
941 textHeight = fm.getMaxAscent() + maxDescent - leading;
942
943 if (gotTerminus == true) {
944 textHeight++;
945 }
946
947 if (getFontAdjustments() == false) {
948 // We were unable to programmatically determine textAdjustX and
949 // textAdjustY, so try some guesses based on VM vendor.
950 String runtime = System.getProperty("java.runtime.name");
951 if ((runtime != null) && (runtime.contains("Java(TM)"))) {
952 textAdjustY = -1;
953 textAdjustX = 0;
954 }
955 }
956
957 if (sessionInfo != null) {
958 sessionInfo.setTextCellDimensions(textWidth, textHeight);
959 }
960 gotFontDimensions = true;
961 }
962
963 /**
964 * Resize to font dimensions.
965 */
966 public void resizeToScreen() {
967 swing.setDimensions(textWidth * width, textHeight * height);
968 }
969
970 /**
971 * Draw one glyph to the screen.
972 *
973 * @param gr the Swing Graphics context
974 * @param cell the Cell to draw
975 * @param xPixel the x-coordinate to render to. 0 means the
976 * left-most pixel column.
977 * @param yPixel the y-coordinate to render to. 0 means the top-most
978 * pixel row.
979 */
980 private void drawGlyph(final Graphics gr, final Cell cell,
981 final int xPixel, final int yPixel) {
982
983 /*
984 System.err.println("drawGlyph(): " + xPixel + " " + yPixel +
985 " " + cell);
986 */
987
988 BufferedImage image = null;
989 if (cell.isBlink() && !cursorBlinkVisible) {
990 image = glyphCacheBlink.get(cell);
991 } else {
992 image = glyphCache.get(cell);
993 }
994 if (image != null) {
995 if (swing.getFrame() != null) {
996 gr.drawImage(image, xPixel, yPixel, swing.getFrame());
997 } else {
998 gr.drawImage(image, xPixel, yPixel, swing.getComponent());
999 }
1000 return;
1001 }
1002
1003 // Generate glyph and draw it.
1004 Graphics2D gr2 = null;
1005 int gr2x = xPixel;
1006 int gr2y = yPixel;
1007 if ((SwingComponent.tripleBuffer) && (swing.getFrame() != null)) {
1008 image = new BufferedImage(textWidth, textHeight,
1009 BufferedImage.TYPE_INT_ARGB);
1010 gr2 = image.createGraphics();
1011 gr2.setFont(swing.getFont());
1012 gr2x = 0;
1013 gr2y = 0;
1014 } else {
1015 gr2 = (Graphics2D) gr;
1016 }
1017
1018 Cell cellColor = new Cell();
1019 cellColor.setTo(cell);
1020
1021 // Check for reverse
1022 if (cell.isReverse()) {
1023 cellColor.setForeColor(cell.getBackColor());
1024 cellColor.setBackColor(cell.getForeColor());
1025 }
1026
1027 // Draw the background rectangle, then the foreground character.
1028 gr2.setColor(attrToBackgroundColor(cellColor));
1029 gr2.fillRect(gr2x, gr2y, textWidth, textHeight);
1030
1031 // Handle blink and underline
1032 if (!cell.isBlink()
1033 || (cell.isBlink() && cursorBlinkVisible)
1034 ) {
1035 gr2.setColor(attrToForegroundColor(cellColor));
1036 char [] chars = new char[1];
1037 chars[0] = cell.getChar();
1038 gr2.drawChars(chars, 0, 1, gr2x + textAdjustX,
1039 gr2y + textHeight - maxDescent + textAdjustY);
1040
1041 if (cell.isUnderline()) {
1042 gr2.fillRect(gr2x, gr2y + textHeight - 2, textWidth, 2);
1043 }
1044 }
1045
1046 if ((SwingComponent.tripleBuffer) && (swing.getFrame() != null)) {
1047 gr2.dispose();
1048
1049 // We need a new key that will not be mutated by
1050 // invertCell().
1051 Cell key = new Cell();
1052 key.setTo(cell);
1053 if (cell.isBlink() && !cursorBlinkVisible) {
1054 glyphCacheBlink.put(key, image);
1055 } else {
1056 glyphCache.put(key, image);
1057 }
1058
1059 if (swing.getFrame() != null) {
1060 gr.drawImage(image, xPixel, yPixel, swing.getFrame());
1061 } else {
1062 gr.drawImage(image, xPixel, yPixel, swing.getComponent());
1063 }
1064 }
1065
1066 }
1067
1068 /**
1069 * Check if the cursor is visible, and if so draw it.
1070 *
1071 * @param gr the Swing Graphics context
1072 */
1073 private void drawCursor(final Graphics gr) {
1074
1075 if (cursorVisible
1076 && (cursorY >= 0)
1077 && (cursorX >= 0)
1078 && (cursorY <= height - 1)
1079 && (cursorX <= width - 1)
1080 && cursorBlinkVisible
1081 ) {
1082 int xPixel = cursorX * textWidth + left;
1083 int yPixel = cursorY * textHeight + top;
1084 Cell lCell = logical[cursorX][cursorY];
1085 gr.setColor(attrToForegroundColor(lCell));
1086 switch (cursorStyle) {
1087 default:
1088 // Fall through...
1089 case UNDERLINE:
1090 gr.fillRect(xPixel, yPixel + textHeight - 2, textWidth, 2);
1091 break;
1092 case BLOCK:
1093 gr.fillRect(xPixel, yPixel, textWidth, textHeight);
1094 break;
1095 case OUTLINE:
1096 gr.drawRect(xPixel, yPixel, textWidth - 1, textHeight - 1);
1097 break;
1098 }
1099 }
1100 }
1101
1102 /**
1103 * Reset the blink timer.
1104 */
1105 private void resetBlinkTimer() {
1106 lastBlinkTime = System.currentTimeMillis();
1107 cursorBlinkVisible = true;
1108 }
1109
1110 /**
1111 * Paint redraws the whole screen.
1112 *
1113 * @param gr the Swing Graphics context
1114 */
1115 public void paint(final Graphics gr) {
1116
1117 if (gotFontDimensions == false) {
1118 // Lazy-load the text width/height
1119 getFontDimensions(gr);
1120 /*
1121 System.err.println("textWidth " + textWidth +
1122 " textHeight " + textHeight);
1123 System.err.println("FONT: " + swing.getFont() + " font " + font);
1124 */
1125 }
1126
1127 if ((swing.getFrame() != null)
1128 && (swing.getBufferStrategy() != null)
1129 && (SwingUtilities.isEventDispatchThread())
1130 ) {
1131 // System.err.println("paint(), skip first paint on swing thread");
1132 return;
1133 }
1134
1135 int xCellMin = 0;
1136 int xCellMax = width;
1137 int yCellMin = 0;
1138 int yCellMax = height;
1139
1140 Rectangle bounds = gr.getClipBounds();
1141 if (bounds != null) {
1142 // Only update what is in the bounds
1143 xCellMin = textColumn(bounds.x);
1144 xCellMax = textColumn(bounds.x + bounds.width);
1145 if (xCellMax > width) {
1146 xCellMax = width;
1147 }
1148 if (xCellMin >= xCellMax) {
1149 xCellMin = xCellMax - 2;
1150 }
1151 if (xCellMin < 0) {
1152 xCellMin = 0;
1153 }
1154 yCellMin = textRow(bounds.y);
1155 yCellMax = textRow(bounds.y + bounds.height);
1156 if (yCellMax > height) {
1157 yCellMax = height;
1158 }
1159 if (yCellMin >= yCellMax) {
1160 yCellMin = yCellMax - 2;
1161 }
1162 if (yCellMin < 0) {
1163 yCellMin = 0;
1164 }
1165 } else {
1166 // We need a total repaint
1167 reallyCleared = true;
1168 }
1169
1170 // Prevent updates to the screen's data from the TApplication
1171 // threads.
1172 synchronized (this) {
1173
1174 /*
1175 System.err.printf("bounds %s X %d %d Y %d %d\n",
1176 bounds, xCellMin, xCellMax, yCellMin, yCellMax);
1177 */
1178
1179 for (int y = yCellMin; y < yCellMax; y++) {
1180 for (int x = xCellMin; x < xCellMax; x++) {
1181
1182 int xPixel = x * textWidth + left;
1183 int yPixel = y * textHeight + top;
1184
1185 Cell lCell = logical[x][y];
1186 Cell pCell = physical[x][y];
1187
1188 if (!lCell.equals(pCell)
1189 || lCell.isBlink()
1190 || reallyCleared
1191 || (swing.getFrame() == null)) {
1192
1193 drawGlyph(gr, lCell, xPixel, yPixel);
1194
1195 // Physical is always updated
1196 physical[x][y].setTo(lCell);
1197 }
1198 }
1199 }
1200 drawCursor(gr);
1201
1202 reallyCleared = false;
1203 } // synchronized (this)
1204 }
1205
1206 /**
1207 * Restore terminal to normal state.
1208 */
1209 public void shutdown() {
1210 swing.dispose();
1211 }
1212
1213 /**
1214 * Push the logical screen to the physical device.
1215 */
1216 private void drawToSwing() {
1217
1218 /*
1219 System.err.printf("drawToSwing(): reallyCleared %s dirty %s\n",
1220 reallyCleared, dirty);
1221 */
1222
1223 // If reallyCleared is set, we have to draw everything.
1224 if ((swing.getFrame() != null)
1225 && (swing.getBufferStrategy() != null)
1226 && (reallyCleared == true)
1227 ) {
1228 // Triple-buffering: we have to redraw everything on this thread.
1229 Graphics gr = swing.getBufferStrategy().getDrawGraphics();
1230 swing.paint(gr);
1231 gr.dispose();
1232 swing.getBufferStrategy().show();
1233 Toolkit.getDefaultToolkit().sync();
1234 return;
1235 } else if (((swing.getFrame() != null)
1236 && (swing.getBufferStrategy() == null))
1237 || (reallyCleared == true)
1238 ) {
1239 // Repaint everything on the Swing thread.
1240 // System.err.println("REPAINT ALL");
1241 swing.repaint();
1242 return;
1243 }
1244
1245 if ((swing.getFrame() != null) && (swing.getBufferStrategy() != null)) {
1246 Graphics gr = swing.getBufferStrategy().getDrawGraphics();
1247
1248 synchronized (this) {
1249 for (int y = 0; y < height; y++) {
1250 for (int x = 0; x < width; x++) {
1251 Cell lCell = logical[x][y];
1252 Cell pCell = physical[x][y];
1253
1254 int xPixel = x * textWidth + left;
1255 int yPixel = y * textHeight + top;
1256
1257 if (!lCell.equals(pCell)
1258 || ((x == cursorX)
1259 && (y == cursorY)
1260 && cursorVisible)
1261 || (lCell.isBlink())
1262 ) {
1263 drawGlyph(gr, lCell, xPixel, yPixel);
1264 physical[x][y].setTo(lCell);
1265 }
1266 }
1267 }
1268 drawCursor(gr);
1269 } // synchronized (this)
1270
1271 gr.dispose();
1272 swing.getBufferStrategy().show();
1273 Toolkit.getDefaultToolkit().sync();
1274 return;
1275 }
1276
1277 // Swing thread version: request a repaint, but limit it to the area
1278 // that has changed.
1279
1280 // Find the minimum-size damaged region.
1281 int xMin = swing.getWidth();
1282 int xMax = 0;
1283 int yMin = swing.getHeight();
1284 int yMax = 0;
1285
1286 synchronized (this) {
1287 for (int y = 0; y < height; y++) {
1288 for (int x = 0; x < width; x++) {
1289 Cell lCell = logical[x][y];
1290 Cell pCell = physical[x][y];
1291
1292 int xPixel = x * textWidth + left;
1293 int yPixel = y * textHeight + top;
1294
1295 if (!lCell.equals(pCell)
1296 || ((x == cursorX)
1297 && (y == cursorY)
1298 && cursorVisible)
1299 || lCell.isBlink()
1300 ) {
1301 if (xPixel < xMin) {
1302 xMin = xPixel;
1303 }
1304 if (xPixel + textWidth > xMax) {
1305 xMax = xPixel + textWidth;
1306 }
1307 if (yPixel < yMin) {
1308 yMin = yPixel;
1309 }
1310 if (yPixel + textHeight > yMax) {
1311 yMax = yPixel + textHeight;
1312 }
1313 }
1314 }
1315 }
1316 }
1317 if (xMin + textWidth >= xMax) {
1318 xMax += textWidth;
1319 }
1320 if (yMin + textHeight >= yMax) {
1321 yMax += textHeight;
1322 }
1323
1324 // Repaint the desired area
1325 /*
1326 System.err.printf("REPAINT X %d %d Y %d %d\n", xMin, xMax,
1327 yMin, yMax);
1328 */
1329
1330 if ((swing.getFrame() != null) && (swing.getBufferStrategy() != null)) {
1331 // This path should never be taken, but is left here for
1332 // completeness.
1333 Graphics gr = swing.getBufferStrategy().getDrawGraphics();
1334 Rectangle bounds = new Rectangle(xMin, yMin, xMax - xMin,
1335 yMax - yMin);
1336 gr.setClip(bounds);
1337 swing.paint(gr);
1338 gr.dispose();
1339 swing.getBufferStrategy().show();
1340 Toolkit.getDefaultToolkit().sync();
1341 } else {
1342 // Repaint on the Swing thread.
1343 swing.repaint(xMin, yMin, xMax - xMin, yMax - yMin);
1344 }
1345 }
1346
1347 /**
1348 * Convert pixel column position to text cell column position.
1349 *
1350 * @param x pixel column position
1351 * @return text cell column position
1352 */
1353 public int textColumn(final int x) {
1354 int column = ((x - left) / textWidth);
1355 if (column < 0) {
1356 column = 0;
1357 }
1358 if (column > width - 1) {
1359 column = width - 1;
1360 }
1361 return column;
1362 }
1363
1364 /**
1365 * Convert pixel row position to text cell row position.
1366 *
1367 * @param y pixel row position
1368 * @return text cell row position
1369 */
1370 public int textRow(final int y) {
1371 int row = ((y - top) / textHeight);
1372 if (row < 0) {
1373 row = 0;
1374 }
1375 if (row > height - 1) {
1376 row = height - 1;
1377 }
1378 return row;
1379 }
1380
1381 /**
1382 * Getter for sessionInfo.
1383 *
1384 * @return the SessionInfo
1385 */
1386 public SessionInfo getSessionInfo() {
1387 return sessionInfo;
1388 }
1389
1390 /**
1391 * Getter for the underlying Swing component.
1392 *
1393 * @return the SwingComponent
1394 */
1395 public SwingComponent getSwingComponent() {
1396 return swing;
1397 }
1398
1399 // ------------------------------------------------------------------------
1400 // KeyListener ------------------------------------------------------------
1401 // ------------------------------------------------------------------------
1402
1403 /**
1404 * Pass Swing keystrokes into the event queue.
1405 *
1406 * @param key keystroke received
1407 */
1408 public void keyReleased(final KeyEvent key) {
1409 // Ignore release events
1410 }
1411
1412 /**
1413 * Pass Swing keystrokes into the event queue.
1414 *
1415 * @param key keystroke received
1416 */
1417 public void keyTyped(final KeyEvent key) {
1418 // Ignore typed events
1419 }
1420
1421 /**
1422 * Pass Swing keystrokes into the event queue.
1423 *
1424 * @param key keystroke received
1425 */
1426 public void keyPressed(final KeyEvent key) {
1427 boolean alt = false;
1428 boolean shift = false;
1429 boolean ctrl = false;
1430 char ch = ' ';
1431 boolean isKey = false;
1432 if (key.isActionKey()) {
1433 isKey = true;
1434 } else {
1435 ch = key.getKeyChar();
1436 }
1437 alt = key.isAltDown();
1438 ctrl = key.isControlDown();
1439 shift = key.isShiftDown();
1440
1441 /*
1442 System.err.printf("Swing Key: %s\n", key);
1443 System.err.printf(" isKey: %s\n", isKey);
1444 System.err.printf(" alt: %s\n", alt);
1445 System.err.printf(" ctrl: %s\n", ctrl);
1446 System.err.printf(" shift: %s\n", shift);
1447 System.err.printf(" ch: %s\n", ch);
1448 */
1449
1450 // Special case: not return the bare modifier presses
1451 switch (key.getKeyCode()) {
1452 case KeyEvent.VK_ALT:
1453 return;
1454 case KeyEvent.VK_ALT_GRAPH:
1455 return;
1456 case KeyEvent.VK_CONTROL:
1457 return;
1458 case KeyEvent.VK_SHIFT:
1459 return;
1460 case KeyEvent.VK_META:
1461 return;
1462 default:
1463 break;
1464 }
1465
1466 TKeypress keypress = null;
1467 if (isKey) {
1468 switch (key.getKeyCode()) {
1469 case KeyEvent.VK_F1:
1470 keypress = new TKeypress(true, TKeypress.F1, ' ',
1471 alt, ctrl, shift);
1472 break;
1473 case KeyEvent.VK_F2:
1474 keypress = new TKeypress(true, TKeypress.F2, ' ',
1475 alt, ctrl, shift);
1476 break;
1477 case KeyEvent.VK_F3:
1478 keypress = new TKeypress(true, TKeypress.F3, ' ',
1479 alt, ctrl, shift);
1480 break;
1481 case KeyEvent.VK_F4:
1482 keypress = new TKeypress(true, TKeypress.F4, ' ',
1483 alt, ctrl, shift);
1484 break;
1485 case KeyEvent.VK_F5:
1486 keypress = new TKeypress(true, TKeypress.F5, ' ',
1487 alt, ctrl, shift);
1488 break;
1489 case KeyEvent.VK_F6:
1490 keypress = new TKeypress(true, TKeypress.F6, ' ',
1491 alt, ctrl, shift);
1492 break;
1493 case KeyEvent.VK_F7:
1494 keypress = new TKeypress(true, TKeypress.F7, ' ',
1495 alt, ctrl, shift);
1496 break;
1497 case KeyEvent.VK_F8:
1498 keypress = new TKeypress(true, TKeypress.F8, ' ',
1499 alt, ctrl, shift);
1500 break;
1501 case KeyEvent.VK_F9:
1502 keypress = new TKeypress(true, TKeypress.F9, ' ',
1503 alt, ctrl, shift);
1504 break;
1505 case KeyEvent.VK_F10:
1506 keypress = new TKeypress(true, TKeypress.F10, ' ',
1507 alt, ctrl, shift);
1508 break;
1509 case KeyEvent.VK_F11:
1510 keypress = new TKeypress(true, TKeypress.F11, ' ',
1511 alt, ctrl, shift);
1512 break;
1513 case KeyEvent.VK_F12:
1514 keypress = new TKeypress(true, TKeypress.F12, ' ',
1515 alt, ctrl, shift);
1516 break;
1517 case KeyEvent.VK_HOME:
1518 keypress = new TKeypress(true, TKeypress.HOME, ' ',
1519 alt, ctrl, shift);
1520 break;
1521 case KeyEvent.VK_END:
1522 keypress = new TKeypress(true, TKeypress.END, ' ',
1523 alt, ctrl, shift);
1524 break;
1525 case KeyEvent.VK_PAGE_UP:
1526 keypress = new TKeypress(true, TKeypress.PGUP, ' ',
1527 alt, ctrl, shift);
1528 break;
1529 case KeyEvent.VK_PAGE_DOWN:
1530 keypress = new TKeypress(true, TKeypress.PGDN, ' ',
1531 alt, ctrl, shift);
1532 break;
1533 case KeyEvent.VK_INSERT:
1534 keypress = new TKeypress(true, TKeypress.INS, ' ',
1535 alt, ctrl, shift);
1536 break;
1537 case KeyEvent.VK_DELETE:
1538 keypress = new TKeypress(true, TKeypress.DEL, ' ',
1539 alt, ctrl, shift);
1540 break;
1541 case KeyEvent.VK_RIGHT:
1542 keypress = new TKeypress(true, TKeypress.RIGHT, ' ',
1543 alt, ctrl, shift);
1544 break;
1545 case KeyEvent.VK_LEFT:
1546 keypress = new TKeypress(true, TKeypress.LEFT, ' ',
1547 alt, ctrl, shift);
1548 break;
1549 case KeyEvent.VK_UP:
1550 keypress = new TKeypress(true, TKeypress.UP, ' ',
1551 alt, ctrl, shift);
1552 break;
1553 case KeyEvent.VK_DOWN:
1554 keypress = new TKeypress(true, TKeypress.DOWN, ' ',
1555 alt, ctrl, shift);
1556 break;
1557 case KeyEvent.VK_TAB:
1558 // Special case: distinguish TAB vs BTAB
1559 if (shift) {
1560 keypress = kbShiftTab;
1561 } else {
1562 keypress = kbTab;
1563 }
1564 break;
1565 case KeyEvent.VK_ENTER:
1566 keypress = new TKeypress(true, TKeypress.ENTER, ' ',
1567 alt, ctrl, shift);
1568 break;
1569 case KeyEvent.VK_ESCAPE:
1570 keypress = new TKeypress(true, TKeypress.ESC, ' ',
1571 alt, ctrl, shift);
1572 break;
1573 case KeyEvent.VK_BACK_SPACE:
1574 keypress = kbBackspace;
1575 break;
1576 default:
1577 // Unsupported, ignore
1578 return;
1579 }
1580 }
1581
1582 if (keypress == null) {
1583 switch (ch) {
1584 case 0x08:
1585 // Disambiguate ^H from Backspace.
1586 if (KeyEvent.getKeyText(key.getKeyCode()).equals("H")) {
1587 // This is ^H.
1588 keypress = kbBackspace;
1589 } else {
1590 // We are emulating Xterm here, where the backspace key
1591 // on the keyboard returns ^?.
1592 keypress = kbBackspaceDel;
1593 }
1594 break;
1595 case 0x0A:
1596 keypress = kbEnter;
1597 break;
1598 case 0x1B:
1599 keypress = kbEsc;
1600 break;
1601 case 0x0D:
1602 keypress = kbEnter;
1603 break;
1604 case 0x09:
1605 if (shift) {
1606 keypress = kbShiftTab;
1607 } else {
1608 keypress = kbTab;
1609 }
1610 break;
1611 case 0x7F:
1612 keypress = kbDel;
1613 break;
1614 default:
1615 if (!alt && ctrl && !shift) {
1616 ch = KeyEvent.getKeyText(key.getKeyCode()).charAt(0);
1617 }
1618 // Not a special key, put it together
1619 keypress = new TKeypress(false, 0, ch, alt, ctrl, shift);
1620 }
1621 }
1622
1623 // Save it and we are done.
1624 synchronized (eventQueue) {
1625 eventQueue.add(new TKeypressEvent(keypress));
1626 resetBlinkTimer();
1627 }
1628 if (listener != null) {
1629 synchronized (listener) {
1630 listener.notifyAll();
1631 }
1632 }
1633 }
1634
1635 // ------------------------------------------------------------------------
1636 // WindowListener ---------------------------------------------------------
1637 // ------------------------------------------------------------------------
1638
1639 /**
1640 * Pass window events into the event queue.
1641 *
1642 * @param event window event received
1643 */
1644 public void windowActivated(final WindowEvent event) {
1645 // Force a total repaint
1646 synchronized (this) {
1647 clearPhysical();
1648 }
1649 }
1650
1651 /**
1652 * Pass window events into the event queue.
1653 *
1654 * @param event window event received
1655 */
1656 public void windowClosed(final WindowEvent event) {
1657 // Ignore
1658 }
1659
1660 /**
1661 * Pass window events into the event queue.
1662 *
1663 * @param event window event received
1664 */
1665 public void windowClosing(final WindowEvent event) {
1666 // Drop a cmAbort and walk away
1667 synchronized (eventQueue) {
1668 eventQueue.add(new TCommandEvent(cmAbort));
1669 resetBlinkTimer();
1670 }
1671 if (listener != null) {
1672 synchronized (listener) {
1673 listener.notifyAll();
1674 }
1675 }
1676 }
1677
1678 /**
1679 * Pass window events into the event queue.
1680 *
1681 * @param event window event received
1682 */
1683 public void windowDeactivated(final WindowEvent event) {
1684 // Ignore
1685 }
1686
1687 /**
1688 * Pass window events into the event queue.
1689 *
1690 * @param event window event received
1691 */
1692 public void windowDeiconified(final WindowEvent event) {
1693 // Ignore
1694 }
1695
1696 /**
1697 * Pass window events into the event queue.
1698 *
1699 * @param event window event received
1700 */
1701 public void windowIconified(final WindowEvent event) {
1702 // Ignore
1703 }
1704
1705 /**
1706 * Pass window events into the event queue.
1707 *
1708 * @param event window event received
1709 */
1710 public void windowOpened(final WindowEvent event) {
1711 // Ignore
1712 }
1713
1714 // ------------------------------------------------------------------------
1715 // ComponentListener ------------------------------------------------------
1716 // ------------------------------------------------------------------------
1717
1718 /**
1719 * Pass component events into the event queue.
1720 *
1721 * @param event component event received
1722 */
1723 public void componentHidden(final ComponentEvent event) {
1724 // Ignore
1725 }
1726
1727 /**
1728 * Pass component events into the event queue.
1729 *
1730 * @param event component event received
1731 */
1732 public void componentShown(final ComponentEvent event) {
1733 // Ignore
1734 }
1735
1736 /**
1737 * Pass component events into the event queue.
1738 *
1739 * @param event component event received
1740 */
1741 public void componentMoved(final ComponentEvent event) {
1742 // Ignore
1743 }
1744
1745 /**
1746 * Pass component events into the event queue.
1747 *
1748 * @param event component event received
1749 */
1750 public void componentResized(final ComponentEvent event) {
1751 if (gotFontDimensions == false) {
1752 // We are still waiting to get font information. Don't pass a
1753 // resize event up.
1754 // System.err.println("size " + swing.getComponent().getSize());
1755 return;
1756 }
1757
1758 // Drop a new TResizeEvent into the queue
1759 sessionInfo.queryWindowSize();
1760 synchronized (eventQueue) {
1761 TResizeEvent windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN,
1762 sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight());
1763 eventQueue.add(windowResize);
1764 resetBlinkTimer();
1765 /*
1766 System.err.println("Add resize event: " + windowResize.getWidth() +
1767 " x " + windowResize.getHeight());
1768 */
1769 }
1770 if (listener != null) {
1771 synchronized (listener) {
1772 listener.notifyAll();
1773 }
1774 }
1775 }
1776
1777 // ------------------------------------------------------------------------
1778 // MouseMotionListener ----------------------------------------------------
1779 // ------------------------------------------------------------------------
1780
1781 /**
1782 * Pass mouse events into the event queue.
1783 *
1784 * @param mouse mouse event received
1785 */
1786 public void mouseDragged(final MouseEvent mouse) {
1787 int modifiers = mouse.getModifiersEx();
1788 boolean eventMouse1 = false;
1789 boolean eventMouse2 = false;
1790 boolean eventMouse3 = false;
1791 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
1792 eventMouse1 = true;
1793 }
1794 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
1795 eventMouse2 = true;
1796 }
1797 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
1798 eventMouse3 = true;
1799 }
1800 mouse1 = eventMouse1;
1801 mouse2 = eventMouse2;
1802 mouse3 = eventMouse3;
1803 int x = textColumn(mouse.getX());
1804 int y = textRow(mouse.getY());
1805
1806 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION,
1807 x, y, x, y, mouse1, mouse2, mouse3, false, false);
1808
1809 synchronized (eventQueue) {
1810 eventQueue.add(mouseEvent);
1811 resetBlinkTimer();
1812 }
1813 if (listener != null) {
1814 synchronized (listener) {
1815 listener.notifyAll();
1816 }
1817 }
1818 }
1819
1820 /**
1821 * Pass mouse events into the event queue.
1822 *
1823 * @param mouse mouse event received
1824 */
1825 public void mouseMoved(final MouseEvent mouse) {
1826 int x = textColumn(mouse.getX());
1827 int y = textRow(mouse.getY());
1828 if ((x == oldMouseX) && (y == oldMouseY)) {
1829 // Bail out, we've moved some pixels but not a whole text cell.
1830 return;
1831 }
1832 oldMouseX = x;
1833 oldMouseY = y;
1834
1835 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION,
1836 x, y, x, y, mouse1, mouse2, mouse3, false, false);
1837
1838 synchronized (eventQueue) {
1839 eventQueue.add(mouseEvent);
1840 resetBlinkTimer();
1841 }
1842 if (listener != null) {
1843 synchronized (listener) {
1844 listener.notifyAll();
1845 }
1846 }
1847 }
1848
1849 // ------------------------------------------------------------------------
1850 // MouseListener ----------------------------------------------------------
1851 // ------------------------------------------------------------------------
1852
1853 /**
1854 * Pass mouse events into the event queue.
1855 *
1856 * @param mouse mouse event received
1857 */
1858 public void mouseClicked(final MouseEvent mouse) {
1859 // Ignore
1860 }
1861
1862 /**
1863 * Pass mouse events into the event queue.
1864 *
1865 * @param mouse mouse event received
1866 */
1867 public void mouseEntered(final MouseEvent mouse) {
1868 // Ignore
1869 }
1870
1871 /**
1872 * Pass mouse events into the event queue.
1873 *
1874 * @param mouse mouse event received
1875 */
1876 public void mouseExited(final MouseEvent mouse) {
1877 // Ignore
1878 }
1879
1880 /**
1881 * Pass mouse events into the event queue.
1882 *
1883 * @param mouse mouse event received
1884 */
1885 public void mousePressed(final MouseEvent mouse) {
1886 int modifiers = mouse.getModifiersEx();
1887 boolean eventMouse1 = false;
1888 boolean eventMouse2 = false;
1889 boolean eventMouse3 = false;
1890 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
1891 eventMouse1 = true;
1892 }
1893 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
1894 eventMouse2 = true;
1895 }
1896 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
1897 eventMouse3 = true;
1898 }
1899 mouse1 = eventMouse1;
1900 mouse2 = eventMouse2;
1901 mouse3 = eventMouse3;
1902 int x = textColumn(mouse.getX());
1903 int y = textRow(mouse.getY());
1904
1905 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN,
1906 x, y, x, y, mouse1, mouse2, mouse3, false, false);
1907
1908 synchronized (eventQueue) {
1909 eventQueue.add(mouseEvent);
1910 resetBlinkTimer();
1911 }
1912 if (listener != null) {
1913 synchronized (listener) {
1914 listener.notifyAll();
1915 }
1916 }
1917 }
1918
1919 /**
1920 * Pass mouse events into the event queue.
1921 *
1922 * @param mouse mouse event received
1923 */
1924 public void mouseReleased(final MouseEvent mouse) {
1925 int modifiers = mouse.getModifiersEx();
1926 boolean eventMouse1 = false;
1927 boolean eventMouse2 = false;
1928 boolean eventMouse3 = false;
1929 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
1930 eventMouse1 = true;
1931 }
1932 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
1933 eventMouse2 = true;
1934 }
1935 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
1936 eventMouse3 = true;
1937 }
1938 if (mouse1) {
1939 mouse1 = false;
1940 eventMouse1 = true;
1941 }
1942 if (mouse2) {
1943 mouse2 = false;
1944 eventMouse2 = true;
1945 }
1946 if (mouse3) {
1947 mouse3 = false;
1948 eventMouse3 = true;
1949 }
1950 int x = textColumn(mouse.getX());
1951 int y = textRow(mouse.getY());
1952
1953 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_UP,
1954 x, y, x, y, eventMouse1, eventMouse2, eventMouse3, false, false);
1955
1956 synchronized (eventQueue) {
1957 eventQueue.add(mouseEvent);
1958 resetBlinkTimer();
1959 }
1960 if (listener != null) {
1961 synchronized (listener) {
1962 listener.notifyAll();
1963 }
1964 }
1965 }
1966
1967 // ------------------------------------------------------------------------
1968 // MouseWheelListener -----------------------------------------------------
1969 // ------------------------------------------------------------------------
1970
1971 /**
1972 * Pass mouse events into the event queue.
1973 *
1974 * @param mouse mouse event received
1975 */
1976 public void mouseWheelMoved(final MouseWheelEvent mouse) {
1977 int modifiers = mouse.getModifiersEx();
1978 boolean eventMouse1 = false;
1979 boolean eventMouse2 = false;
1980 boolean eventMouse3 = false;
1981 boolean mouseWheelUp = false;
1982 boolean mouseWheelDown = false;
1983 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
1984 eventMouse1 = true;
1985 }
1986 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
1987 eventMouse2 = true;
1988 }
1989 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
1990 eventMouse3 = true;
1991 }
1992 mouse1 = eventMouse1;
1993 mouse2 = eventMouse2;
1994 mouse3 = eventMouse3;
1995 int x = textColumn(mouse.getX());
1996 int y = textRow(mouse.getY());
1997 if (mouse.getWheelRotation() > 0) {
1998 mouseWheelDown = true;
1999 }
2000 if (mouse.getWheelRotation() < 0) {
2001 mouseWheelUp = true;
2002 }
2003
2004 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN,
2005 x, y, x, y, mouse1, mouse2, mouse3, mouseWheelUp, mouseWheelDown);
2006
2007 synchronized (eventQueue) {
2008 eventQueue.add(mouseEvent);
2009 resetBlinkTimer();
2010 }
2011 if (listener != null) {
2012 synchronized (listener) {
2013 listener.notifyAll();
2014 }
2015 }
2016 }
2017
2018 }