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