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