#35 Blinking double-width text for tterminal
[fanfix.git] / src / jexer / backend / SwingTerminal.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 Kevin Lamonte
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29 package jexer.backend;
30
31 import java.awt.BorderLayout;
32 import java.awt.Color;
33 import java.awt.Font;
34 import java.awt.FontMetrics;
35 import java.awt.Graphics2D;
36 import java.awt.Graphics;
37 import java.awt.Insets;
38 import java.awt.Rectangle;
39 import java.awt.Toolkit;
40 import java.awt.event.ComponentEvent;
41 import java.awt.event.ComponentListener;
42 import java.awt.event.KeyEvent;
43 import java.awt.event.KeyListener;
44 import java.awt.event.MouseEvent;
45 import java.awt.event.MouseListener;
46 import java.awt.event.MouseMotionListener;
47 import java.awt.event.MouseWheelEvent;
48 import java.awt.event.MouseWheelListener;
49 import java.awt.event.WindowEvent;
50 import java.awt.event.WindowListener;
51 import java.awt.geom.Rectangle2D;
52 import java.awt.image.BufferedImage;
53 import java.io.InputStream;
54 import java.util.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 // ------------------------------------------------------------------------
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 ArrayList<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 ArrayList<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 clearPhysical();
571 do {
572 drawToSwing();
573 } while (swing.getBufferStrategy().contentsRestored());
574
575 swing.getBufferStrategy().show();
576 Toolkit.getDefaultToolkit().sync();
577 } while (swing.getBufferStrategy().contentsLost());
578
579 } else {
580 // Non-triple-buffered, call drawToSwing() once
581 drawToSwing();
582 }
583 }
584
585 // ------------------------------------------------------------------------
586 // TerminalReader ---------------------------------------------------------
587 // ------------------------------------------------------------------------
588
589 /**
590 * Check if there are events in the queue.
591 *
592 * @return if true, getEvents() has something to return to the backend
593 */
594 public boolean hasEvents() {
595 synchronized (eventQueue) {
596 return (eventQueue.size() > 0);
597 }
598 }
599
600 /**
601 * Return any events in the IO queue.
602 *
603 * @param queue list to append new events to
604 */
605 public void getEvents(final List<TInputEvent> queue) {
606 synchronized (eventQueue) {
607 if (eventQueue.size() > 0) {
608 synchronized (queue) {
609 queue.addAll(eventQueue);
610 }
611 eventQueue.clear();
612 }
613 }
614 }
615
616 /**
617 * Restore terminal to normal state.
618 */
619 public void closeTerminal() {
620 shutdown();
621 }
622
623 /**
624 * Set listener to a different Object.
625 *
626 * @param listener the new listening object that run() wakes up on new
627 * input
628 */
629 public void setListener(final Object listener) {
630 this.listener = listener;
631 }
632
633 /**
634 * Reload options from System properties.
635 */
636 public void reloadOptions() {
637 // Figure out my cursor style.
638 String cursorStyleString = System.getProperty(
639 "jexer.Swing.cursorStyle", "underline").toLowerCase();
640 if (cursorStyleString.equals("underline")) {
641 cursorStyle = CursorStyle.UNDERLINE;
642 } else if (cursorStyleString.equals("outline")) {
643 cursorStyle = CursorStyle.OUTLINE;
644 } else if (cursorStyleString.equals("block")) {
645 cursorStyle = CursorStyle.BLOCK;
646 }
647
648 // Pull the system property for triple buffering.
649 if (System.getProperty("jexer.Swing.tripleBuffer",
650 "true").equals("true")
651 ) {
652 SwingComponent.tripleBuffer = true;
653 } else {
654 SwingComponent.tripleBuffer = false;
655 }
656 }
657
658 // ------------------------------------------------------------------------
659 // SwingTerminal ----------------------------------------------------------
660 // ------------------------------------------------------------------------
661
662 /**
663 * Get the width of a character cell in pixels.
664 *
665 * @return the width in pixels of a character cell
666 */
667 public int getTextWidth() {
668 return textWidth;
669 }
670
671 /**
672 * Get the height of a character cell in pixels.
673 *
674 * @return the height in pixels of a character cell
675 */
676 public int getTextHeight() {
677 return textHeight;
678 }
679
680 /**
681 * Setup Swing colors to match DOS color palette.
682 */
683 private static void setDOSColors() {
684 if (dosColors) {
685 return;
686 }
687 MYBLACK = new Color(0x00, 0x00, 0x00);
688 MYRED = new Color(0xa8, 0x00, 0x00);
689 MYGREEN = new Color(0x00, 0xa8, 0x00);
690 MYYELLOW = new Color(0xa8, 0x54, 0x00);
691 MYBLUE = new Color(0x00, 0x00, 0xa8);
692 MYMAGENTA = new Color(0xa8, 0x00, 0xa8);
693 MYCYAN = new Color(0x00, 0xa8, 0xa8);
694 MYWHITE = new Color(0xa8, 0xa8, 0xa8);
695 MYBOLD_BLACK = new Color(0x54, 0x54, 0x54);
696 MYBOLD_RED = new Color(0xfc, 0x54, 0x54);
697 MYBOLD_GREEN = new Color(0x54, 0xfc, 0x54);
698 MYBOLD_YELLOW = new Color(0xfc, 0xfc, 0x54);
699 MYBOLD_BLUE = new Color(0x54, 0x54, 0xfc);
700 MYBOLD_MAGENTA = new Color(0xfc, 0x54, 0xfc);
701 MYBOLD_CYAN = new Color(0x54, 0xfc, 0xfc);
702 MYBOLD_WHITE = new Color(0xfc, 0xfc, 0xfc);
703
704 dosColors = true;
705 }
706
707 /**
708 * Get the number of millis to wait before switching the blink from
709 * visible to invisible.
710 *
711 * @return the number of milli to wait before switching the blink from
712 * visible to invisible
713 */
714 public long getBlinkMillis() {
715 return blinkMillis;
716 }
717
718 /**
719 * Get the current status of the blink flag.
720 *
721 * @return true if the cursor and blinking text should be visible
722 */
723 public boolean getCursorBlinkVisible() {
724 return cursorBlinkVisible;
725 }
726
727 /**
728 * Get the font size in points.
729 *
730 * @return font size in points
731 */
732 public int getFontSize() {
733 return fontSize;
734 }
735
736 /**
737 * Set the font size in points.
738 *
739 * @param fontSize font size in points
740 */
741 public void setFontSize(final int fontSize) {
742 this.fontSize = fontSize;
743 Font newFont = font.deriveFont((float) fontSize);
744 setFont(newFont);
745 }
746
747 /**
748 * Set to a new font, and resize the screen to match its dimensions.
749 *
750 * @param font the new font
751 */
752 public void setFont(final Font font) {
753 synchronized (this) {
754 this.font = font;
755 getFontDimensions();
756 swing.setFont(font);
757 glyphCacheBlink = new HashMap<Cell, BufferedImage>();
758 glyphCache = new HashMap<Cell, BufferedImage>();
759 resizeToScreen();
760 }
761 }
762
763 /**
764 * Get the font this screen was last set to.
765 *
766 * @return the font
767 */
768 public Font getFont() {
769 return font;
770 }
771
772 /**
773 * Set the font to Terminus, the best all-around font for both CP437 and
774 * ISO8859-1.
775 */
776 public void setDefaultFont() {
777 try {
778 ClassLoader loader = Thread.currentThread().getContextClassLoader();
779 InputStream in = loader.getResourceAsStream(FONTFILE);
780 Font terminusRoot = Font.createFont(Font.TRUETYPE_FONT, in);
781 Font terminus = terminusRoot.deriveFont(Font.PLAIN, fontSize);
782 font = terminus;
783 } catch (java.awt.FontFormatException e) {
784 e.printStackTrace();
785 font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize);
786 } catch (java.io.IOException e) {
787 e.printStackTrace();
788 font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize);
789 }
790
791 setFont(font);
792 }
793
794 /**
795 * Get the X text adjustment.
796 *
797 * @return X text adjustment
798 */
799 public int getTextAdjustX() {
800 return textAdjustX;
801 }
802
803 /**
804 * Set the X text adjustment.
805 *
806 * @param textAdjustX the X text adjustment
807 */
808 public void setTextAdjustX(final int textAdjustX) {
809 synchronized (this) {
810 this.textAdjustX = textAdjustX;
811 glyphCacheBlink = new HashMap<Cell, BufferedImage>();
812 glyphCache = new HashMap<Cell, BufferedImage>();
813 clearPhysical();
814 }
815 }
816
817 /**
818 * Get the Y text adjustment.
819 *
820 * @return Y text adjustment
821 */
822 public int getTextAdjustY() {
823 return textAdjustY;
824 }
825
826 /**
827 * Set the Y text adjustment.
828 *
829 * @param textAdjustY the Y text adjustment
830 */
831 public void setTextAdjustY(final int textAdjustY) {
832 synchronized (this) {
833 this.textAdjustY = textAdjustY;
834 glyphCacheBlink = new HashMap<Cell, BufferedImage>();
835 glyphCache = new HashMap<Cell, BufferedImage>();
836 clearPhysical();
837 }
838 }
839
840 /**
841 * Get the height text adjustment.
842 *
843 * @return height text adjustment
844 */
845 public int getTextAdjustHeight() {
846 return textAdjustHeight;
847 }
848
849 /**
850 * Set the height text adjustment.
851 *
852 * @param textAdjustHeight the height text adjustment
853 */
854 public void setTextAdjustHeight(final int textAdjustHeight) {
855 synchronized (this) {
856 this.textAdjustHeight = textAdjustHeight;
857 textHeight = fontTextHeight + textAdjustHeight;
858 glyphCacheBlink = new HashMap<Cell, BufferedImage>();
859 glyphCache = new HashMap<Cell, BufferedImage>();
860 clearPhysical();
861 }
862 }
863
864 /**
865 * Get the width text adjustment.
866 *
867 * @return width text adjustment
868 */
869 public int getTextAdjustWidth() {
870 return textAdjustWidth;
871 }
872
873 /**
874 * Set the width text adjustment.
875 *
876 * @param textAdjustWidth the width text adjustment
877 */
878 public void setTextAdjustWidth(final int textAdjustWidth) {
879 synchronized (this) {
880 this.textAdjustWidth = textAdjustWidth;
881 textWidth = fontTextWidth + textAdjustWidth;
882 glyphCacheBlink = new HashMap<Cell, BufferedImage>();
883 glyphCache = new HashMap<Cell, BufferedImage>();
884 clearPhysical();
885 }
886 }
887
888 /**
889 * Convert a CellAttributes foreground color to an Swing Color.
890 *
891 * @param attr the text attributes
892 * @return the Swing Color
893 */
894 public static Color attrToForegroundColor(final CellAttributes attr) {
895 int rgb = attr.getForeColorRGB();
896 if (rgb >= 0) {
897 int red = (rgb >> 16) & 0xFF;
898 int green = (rgb >> 8) & 0xFF;
899 int blue = rgb & 0xFF;
900
901 return new Color(red, green, blue);
902 }
903
904 if (attr.isBold()) {
905 if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
906 return MYBOLD_BLACK;
907 } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
908 return MYBOLD_RED;
909 } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
910 return MYBOLD_BLUE;
911 } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
912 return MYBOLD_GREEN;
913 } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
914 return MYBOLD_YELLOW;
915 } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
916 return MYBOLD_CYAN;
917 } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
918 return MYBOLD_MAGENTA;
919 } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
920 return MYBOLD_WHITE;
921 }
922 } else {
923 if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
924 return MYBLACK;
925 } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
926 return MYRED;
927 } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
928 return MYBLUE;
929 } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
930 return MYGREEN;
931 } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
932 return MYYELLOW;
933 } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
934 return MYCYAN;
935 } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
936 return MYMAGENTA;
937 } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
938 return MYWHITE;
939 }
940 }
941 throw new IllegalArgumentException("Invalid color: " +
942 attr.getForeColor().getValue());
943 }
944
945 /**
946 * Convert a CellAttributes background color to an Swing Color.
947 *
948 * @param attr the text attributes
949 * @return the Swing Color
950 */
951 public static Color attrToBackgroundColor(final CellAttributes attr) {
952 int rgb = attr.getBackColorRGB();
953 if (rgb >= 0) {
954 int red = (rgb >> 16) & 0xFF;
955 int green = (rgb >> 8) & 0xFF;
956 int blue = rgb & 0xFF;
957
958 return new Color(red, green, blue);
959 }
960
961 if (attr.getBackColor().equals(jexer.bits.Color.BLACK)) {
962 return MYBLACK;
963 } else if (attr.getBackColor().equals(jexer.bits.Color.RED)) {
964 return MYRED;
965 } else if (attr.getBackColor().equals(jexer.bits.Color.BLUE)) {
966 return MYBLUE;
967 } else if (attr.getBackColor().equals(jexer.bits.Color.GREEN)) {
968 return MYGREEN;
969 } else if (attr.getBackColor().equals(jexer.bits.Color.YELLOW)) {
970 return MYYELLOW;
971 } else if (attr.getBackColor().equals(jexer.bits.Color.CYAN)) {
972 return MYCYAN;
973 } else if (attr.getBackColor().equals(jexer.bits.Color.MAGENTA)) {
974 return MYMAGENTA;
975 } else if (attr.getBackColor().equals(jexer.bits.Color.WHITE)) {
976 return MYWHITE;
977 }
978 throw new IllegalArgumentException("Invalid color: " +
979 attr.getBackColor().getValue());
980 }
981
982 /**
983 * Figure out what textAdjustX, textAdjustY, textAdjustHeight, and
984 * textAdjustWidth should be, based on the location of a vertical bar and
985 * a horizontal bar.
986 */
987 private void getFontAdjustments() {
988 BufferedImage image = null;
989
990 // What SHOULD happen is that the topmost/leftmost white pixel is at
991 // position (gr2x, gr2y). But it might also be off by a pixel in
992 // either direction.
993
994 Graphics2D gr2 = null;
995 int gr2x = 3;
996 int gr2y = 3;
997 image = new BufferedImage(fontTextWidth * 2, fontTextHeight * 2,
998 BufferedImage.TYPE_INT_ARGB);
999
1000 gr2 = image.createGraphics();
1001 gr2.setFont(swing.getFont());
1002 gr2.setColor(java.awt.Color.BLACK);
1003 gr2.fillRect(0, 0, fontTextWidth * 2, fontTextHeight * 2);
1004 gr2.setColor(java.awt.Color.WHITE);
1005 char [] chars = new char[1];
1006 chars[0] = jexer.bits.GraphicsChars.SINGLE_BAR;
1007 gr2.drawChars(chars, 0, 1, gr2x, gr2y + fontTextHeight - maxDescent);
1008 chars[0] = jexer.bits.GraphicsChars.VERTICAL_BAR;
1009 gr2.drawChars(chars, 0, 1, gr2x, gr2y + fontTextHeight - maxDescent);
1010 gr2.dispose();
1011
1012 int top = fontTextHeight * 2;
1013 int bottom = -1;
1014 int left = fontTextWidth * 2;
1015 int right = -1;
1016 textAdjustX = 0;
1017 textAdjustY = 0;
1018 textAdjustHeight = 0;
1019 textAdjustWidth = 0;
1020
1021 for (int x = 0; x < fontTextWidth * 2; x++) {
1022 for (int y = 0; y < fontTextHeight * 2; y++) {
1023
1024 /*
1025 System.err.println("H X: " + x + " Y: " + y + " " +
1026 image.getRGB(x, y));
1027 */
1028
1029 if ((image.getRGB(x, y) & 0xFFFFFF) != 0) {
1030 // Pixel is present.
1031 if (y < top) {
1032 top = y;
1033 }
1034 if (y > bottom) {
1035 bottom = y;
1036 }
1037 if (x < left) {
1038 left = x;
1039 }
1040 if (x > right) {
1041 right = x;
1042 }
1043 }
1044 }
1045 }
1046 if (left < right) {
1047 textAdjustX = (gr2x - left);
1048 textAdjustWidth = fontTextWidth - (right - left + 1);
1049 }
1050 if (top < bottom) {
1051 textAdjustY = (gr2y - top);
1052 textAdjustHeight = fontTextHeight - (bottom - top + 1);
1053 }
1054 // System.err.println("top " + top + " bottom " + bottom);
1055 // System.err.println("left " + left + " right " + right);
1056
1057 // Special case: do not believe fonts that claim to be wider than
1058 // they are tall.
1059 if (fontTextWidth >= fontTextHeight) {
1060 textAdjustX = 0;
1061 textAdjustWidth = 0;
1062 fontTextWidth = fontTextHeight / 2;
1063 }
1064 }
1065
1066 /**
1067 * Figure out my font dimensions. This code path works OK for the JFrame
1068 * case, and can be called immediately after JFrame creation.
1069 */
1070 private void getFontDimensions() {
1071 swing.setFont(font);
1072 Graphics gr = swing.getGraphics();
1073 if (gr == null) {
1074 return;
1075 }
1076 getFontDimensions(gr);
1077 }
1078
1079 /**
1080 * Figure out my font dimensions. This code path is needed to lazy-load
1081 * the information inside paint().
1082 *
1083 * @param gr Graphics object to use
1084 */
1085 private void getFontDimensions(final Graphics gr) {
1086 swing.setFont(font);
1087 FontMetrics fm = gr.getFontMetrics();
1088 maxDescent = fm.getMaxDescent();
1089 Rectangle2D bounds = fm.getMaxCharBounds(gr);
1090 int leading = fm.getLeading();
1091 fontTextWidth = (int)Math.round(bounds.getWidth());
1092 // fontTextHeight = (int)Math.round(bounds.getHeight()) - maxDescent;
1093
1094 // This produces the same number, but works better for ugly
1095 // monospace.
1096 fontTextHeight = fm.getMaxAscent() + maxDescent - leading;
1097
1098 getFontAdjustments();
1099 textHeight = fontTextHeight + textAdjustHeight;
1100 textWidth = fontTextWidth + textAdjustWidth;
1101
1102 if (sessionInfo != null) {
1103 sessionInfo.setTextCellDimensions(textWidth, textHeight);
1104 }
1105 gotFontDimensions = true;
1106 }
1107
1108 /**
1109 * Resize the physical screen to match the logical screen dimensions.
1110 */
1111 @Override
1112 public void resizeToScreen() {
1113 swing.setDimensions(textWidth * width, textHeight * height);
1114 clearPhysical();
1115 }
1116
1117 /**
1118 * Draw one cell's image to the screen.
1119 *
1120 * @param gr the Swing Graphics context
1121 * @param cell the Cell to draw
1122 * @param xPixel the x-coordinate to render to. 0 means the
1123 * left-most pixel column.
1124 * @param yPixel the y-coordinate to render to. 0 means the top-most
1125 * pixel row.
1126 */
1127 private void drawImage(final Graphics gr, final Cell cell,
1128 final int xPixel, final int yPixel) {
1129
1130 /*
1131 System.err.println("drawImage(): " + xPixel + " " + yPixel +
1132 " " + cell);
1133 */
1134
1135 // Draw the background rectangle, then the foreground character.
1136 assert (cell.isImage());
1137 gr.setColor(cell.getBackground());
1138 gr.fillRect(xPixel, yPixel, textWidth, textHeight);
1139
1140 BufferedImage image = cell.getImage();
1141 if (image != null) {
1142 if (swing.getFrame() != null) {
1143 gr.drawImage(image, xPixel, yPixel, swing.getFrame());
1144 } else {
1145 gr.drawImage(image, xPixel, yPixel, swing.getComponent());
1146 }
1147 return;
1148 }
1149 }
1150
1151 /**
1152 * Draw one glyph to the screen.
1153 *
1154 * @param gr the Swing Graphics context
1155 * @param cell the Cell to draw
1156 * @param xPixel the x-coordinate to render to. 0 means the
1157 * left-most pixel column.
1158 * @param yPixel the y-coordinate to render to. 0 means the top-most
1159 * pixel row.
1160 */
1161 private void drawGlyph(final Graphics gr, final Cell cell,
1162 final int xPixel, final int yPixel) {
1163
1164 /*
1165 System.err.println("drawGlyph(): " + xPixel + " " + yPixel +
1166 " " + cell);
1167 */
1168
1169 BufferedImage image = null;
1170 if (cell.isBlink() && !cursorBlinkVisible) {
1171 image = glyphCacheBlink.get(cell);
1172 } else {
1173 image = glyphCache.get(cell);
1174 }
1175 if (image != null) {
1176 if (swing.getFrame() != null) {
1177 gr.drawImage(image, xPixel, yPixel, swing.getFrame());
1178 } else {
1179 gr.drawImage(image, xPixel, yPixel, swing.getComponent());
1180 }
1181 return;
1182 }
1183
1184 // Generate glyph and draw it.
1185 Graphics2D gr2 = null;
1186 int gr2x = xPixel;
1187 int gr2y = yPixel;
1188 if ((SwingComponent.tripleBuffer) && (swing.getFrame() != null)) {
1189 image = new BufferedImage(textWidth, textHeight,
1190 BufferedImage.TYPE_INT_ARGB);
1191 gr2 = image.createGraphics();
1192 gr2.setFont(swing.getFont());
1193 gr2x = 0;
1194 gr2y = 0;
1195 } else {
1196 gr2 = (Graphics2D) gr;
1197 }
1198
1199 Cell cellColor = new Cell();
1200 cellColor.setTo(cell);
1201
1202 // Check for reverse
1203 if (cell.isReverse()) {
1204 cellColor.setForeColor(cell.getBackColor());
1205 cellColor.setBackColor(cell.getForeColor());
1206 }
1207
1208 // Draw the background rectangle, then the foreground character.
1209 gr2.setColor(attrToBackgroundColor(cellColor));
1210 gr2.fillRect(gr2x, gr2y, textWidth, textHeight);
1211
1212 // Handle blink and underline
1213 if (!cell.isBlink()
1214 || (cell.isBlink() && cursorBlinkVisible)
1215 ) {
1216 gr2.setColor(attrToForegroundColor(cellColor));
1217 char [] chars = new char[1];
1218 chars[0] = cell.getChar();
1219 gr2.drawChars(chars, 0, 1, gr2x + textAdjustX,
1220 gr2y + textHeight - maxDescent + textAdjustY);
1221
1222 if (cell.isUnderline()) {
1223 gr2.fillRect(gr2x, gr2y + textHeight - 2, textWidth, 2);
1224 }
1225 }
1226
1227 if ((SwingComponent.tripleBuffer) && (swing.getFrame() != null)) {
1228 gr2.dispose();
1229
1230 // We need a new key that will not be mutated by
1231 // invertCell().
1232 Cell key = new Cell();
1233 key.setTo(cell);
1234 if (cell.isBlink() && !cursorBlinkVisible) {
1235 glyphCacheBlink.put(key, image);
1236 } else {
1237 glyphCache.put(key, image);
1238 }
1239
1240 if (swing.getFrame() != null) {
1241 gr.drawImage(image, xPixel, yPixel, swing.getFrame());
1242 } else {
1243 gr.drawImage(image, xPixel, yPixel, swing.getComponent());
1244 }
1245 }
1246
1247 }
1248
1249 /**
1250 * Check if the cursor is visible, and if so draw it.
1251 *
1252 * @param gr the Swing Graphics context
1253 */
1254 private void drawCursor(final Graphics gr) {
1255
1256 if (cursorVisible
1257 && (cursorY >= 0)
1258 && (cursorX >= 0)
1259 && (cursorY <= height - 1)
1260 && (cursorX <= width - 1)
1261 && cursorBlinkVisible
1262 ) {
1263 int xPixel = cursorX * textWidth + left;
1264 int yPixel = cursorY * textHeight + top;
1265 Cell lCell = logical[cursorX][cursorY];
1266 gr.setColor(attrToForegroundColor(lCell));
1267 switch (cursorStyle) {
1268 default:
1269 // Fall through...
1270 case UNDERLINE:
1271 gr.fillRect(xPixel, yPixel + textHeight - 2, textWidth, 2);
1272 break;
1273 case BLOCK:
1274 gr.fillRect(xPixel, yPixel, textWidth, textHeight);
1275 break;
1276 case OUTLINE:
1277 gr.drawRect(xPixel, yPixel, textWidth - 1, textHeight - 1);
1278 break;
1279 }
1280 }
1281 }
1282
1283 /**
1284 * Reset the blink timer.
1285 */
1286 private void resetBlinkTimer() {
1287 lastBlinkTime = System.currentTimeMillis();
1288 cursorBlinkVisible = true;
1289 }
1290
1291 /**
1292 * Paint redraws the whole screen.
1293 *
1294 * @param gr the Swing Graphics context
1295 */
1296 public void paint(final Graphics gr) {
1297
1298 if (gotFontDimensions == false) {
1299 // Lazy-load the text width/height
1300 getFontDimensions(gr);
1301 /*
1302 System.err.println("textWidth " + textWidth +
1303 " textHeight " + textHeight);
1304 System.err.println("FONT: " + swing.getFont() + " font " + font);
1305 */
1306 }
1307
1308 if ((swing.getFrame() != null)
1309 && (swing.getBufferStrategy() != null)
1310 && (SwingUtilities.isEventDispatchThread())
1311 ) {
1312 // System.err.println("paint(), skip first paint on swing thread");
1313 return;
1314 }
1315
1316 int xCellMin = 0;
1317 int xCellMax = width;
1318 int yCellMin = 0;
1319 int yCellMax = height;
1320
1321 Rectangle bounds = gr.getClipBounds();
1322 if (bounds != null) {
1323 // Only update what is in the bounds
1324 xCellMin = textColumn(bounds.x);
1325 xCellMax = textColumn(bounds.x + bounds.width);
1326 if (xCellMax > width) {
1327 xCellMax = width;
1328 }
1329 if (xCellMin >= xCellMax) {
1330 xCellMin = xCellMax - 2;
1331 }
1332 if (xCellMin < 0) {
1333 xCellMin = 0;
1334 }
1335 yCellMin = textRow(bounds.y);
1336 yCellMax = textRow(bounds.y + bounds.height);
1337 if (yCellMax > height) {
1338 yCellMax = height;
1339 }
1340 if (yCellMin >= yCellMax) {
1341 yCellMin = yCellMax - 2;
1342 }
1343 if (yCellMin < 0) {
1344 yCellMin = 0;
1345 }
1346 } else {
1347 // We need a total repaint
1348 reallyCleared = true;
1349 }
1350
1351 // Prevent updates to the screen's data from the TApplication
1352 // threads.
1353 synchronized (this) {
1354
1355 /*
1356 System.err.printf("bounds %s X %d %d Y %d %d\n",
1357 bounds, xCellMin, xCellMax, yCellMin, yCellMax);
1358 */
1359
1360 for (int y = yCellMin; y < yCellMax; y++) {
1361 for (int x = xCellMin; x < xCellMax; x++) {
1362
1363 int xPixel = x * textWidth + left;
1364 int yPixel = y * textHeight + top;
1365
1366 Cell lCell = logical[x][y];
1367 Cell pCell = physical[x][y];
1368
1369 if (!lCell.equals(pCell)
1370 || lCell.isBlink()
1371 || reallyCleared
1372 || (swing.getFrame() == null)) {
1373
1374 if (lCell.isImage()) {
1375 drawImage(gr, lCell, xPixel, yPixel);
1376 } else {
1377 drawGlyph(gr, lCell, xPixel, yPixel);
1378 }
1379
1380 // Physical is always updated
1381 physical[x][y].setTo(lCell);
1382 }
1383 }
1384 }
1385 drawCursor(gr);
1386
1387 reallyCleared = false;
1388 } // synchronized (this)
1389 }
1390
1391 /**
1392 * Restore terminal to normal state.
1393 */
1394 public void shutdown() {
1395 swing.dispose();
1396 }
1397
1398 /**
1399 * Push the logical screen to the physical device.
1400 */
1401 private void drawToSwing() {
1402
1403 /*
1404 System.err.printf("drawToSwing(): reallyCleared %s dirty %s\n",
1405 reallyCleared, dirty);
1406 */
1407
1408 // If reallyCleared is set, we have to draw everything.
1409 if ((swing.getFrame() != null)
1410 && (swing.getBufferStrategy() != null)
1411 && (reallyCleared == true)
1412 ) {
1413 // Triple-buffering: we have to redraw everything on this thread.
1414 Graphics gr = swing.getBufferStrategy().getDrawGraphics();
1415 swing.paint(gr);
1416 gr.dispose();
1417 swing.getBufferStrategy().show();
1418 Toolkit.getDefaultToolkit().sync();
1419 return;
1420 } else if (((swing.getFrame() != null)
1421 && (swing.getBufferStrategy() == null))
1422 || (reallyCleared == true)
1423 ) {
1424 // Repaint everything on the Swing thread.
1425 // System.err.println("REPAINT ALL");
1426 swing.repaint();
1427 return;
1428 }
1429
1430 if ((swing.getFrame() != null) && (swing.getBufferStrategy() != null)) {
1431 Graphics gr = swing.getBufferStrategy().getDrawGraphics();
1432
1433 synchronized (this) {
1434 for (int y = 0; y < height; y++) {
1435 for (int x = 0; x < width; x++) {
1436 Cell lCell = logical[x][y];
1437 Cell pCell = physical[x][y];
1438
1439 int xPixel = x * textWidth + left;
1440 int yPixel = y * textHeight + top;
1441
1442 if (!lCell.equals(pCell)
1443 || ((x == cursorX)
1444 && (y == cursorY)
1445 && cursorVisible)
1446 || (lCell.isBlink())
1447 ) {
1448 if (lCell.isImage()) {
1449 drawImage(gr, lCell, xPixel, yPixel);
1450 } else {
1451 drawGlyph(gr, lCell, xPixel, yPixel);
1452 }
1453 physical[x][y].setTo(lCell);
1454 }
1455 }
1456 }
1457 drawCursor(gr);
1458 } // synchronized (this)
1459
1460 gr.dispose();
1461 swing.getBufferStrategy().show();
1462 Toolkit.getDefaultToolkit().sync();
1463 return;
1464 }
1465
1466 // Swing thread version: request a repaint, but limit it to the area
1467 // that has changed.
1468
1469 // Find the minimum-size damaged region.
1470 int xMin = swing.getWidth();
1471 int xMax = 0;
1472 int yMin = swing.getHeight();
1473 int yMax = 0;
1474
1475 synchronized (this) {
1476 for (int y = 0; y < height; y++) {
1477 for (int x = 0; x < width; x++) {
1478 Cell lCell = logical[x][y];
1479 Cell pCell = physical[x][y];
1480
1481 int xPixel = x * textWidth + left;
1482 int yPixel = y * textHeight + top;
1483
1484 if (!lCell.equals(pCell)
1485 || ((x == cursorX)
1486 && (y == cursorY)
1487 && cursorVisible)
1488 || lCell.isBlink()
1489 ) {
1490 if (xPixel < xMin) {
1491 xMin = xPixel;
1492 }
1493 if (xPixel + textWidth > xMax) {
1494 xMax = xPixel + textWidth;
1495 }
1496 if (yPixel < yMin) {
1497 yMin = yPixel;
1498 }
1499 if (yPixel + textHeight > yMax) {
1500 yMax = yPixel + textHeight;
1501 }
1502 }
1503 }
1504 }
1505 }
1506 if (xMin + textWidth >= xMax) {
1507 xMax += textWidth;
1508 }
1509 if (yMin + textHeight >= yMax) {
1510 yMax += textHeight;
1511 }
1512
1513 // Repaint the desired area
1514 /*
1515 System.err.printf("REPAINT X %d %d Y %d %d\n", xMin, xMax,
1516 yMin, yMax);
1517 */
1518
1519 if ((swing.getFrame() != null) && (swing.getBufferStrategy() != null)) {
1520 // This path should never be taken, but is left here for
1521 // completeness.
1522 Graphics gr = swing.getBufferStrategy().getDrawGraphics();
1523 Rectangle bounds = new Rectangle(xMin, yMin, xMax - xMin,
1524 yMax - yMin);
1525 gr.setClip(bounds);
1526 swing.paint(gr);
1527 gr.dispose();
1528 swing.getBufferStrategy().show();
1529 Toolkit.getDefaultToolkit().sync();
1530 } else {
1531 // Repaint on the Swing thread.
1532 swing.repaint(xMin, yMin, xMax - xMin, yMax - yMin);
1533 }
1534 }
1535
1536 /**
1537 * Convert pixel column position to text cell column position.
1538 *
1539 * @param x pixel column position
1540 * @return text cell column position
1541 */
1542 public int textColumn(final int x) {
1543 int column = ((x - left) / textWidth);
1544 if (column < 0) {
1545 column = 0;
1546 }
1547 if (column > width - 1) {
1548 column = width - 1;
1549 }
1550 return column;
1551 }
1552
1553 /**
1554 * Convert pixel row position to text cell row position.
1555 *
1556 * @param y pixel row position
1557 * @return text cell row position
1558 */
1559 public int textRow(final int y) {
1560 int row = ((y - top) / textHeight);
1561 if (row < 0) {
1562 row = 0;
1563 }
1564 if (row > height - 1) {
1565 row = height - 1;
1566 }
1567 return row;
1568 }
1569
1570 /**
1571 * Getter for sessionInfo.
1572 *
1573 * @return the SessionInfo
1574 */
1575 public SessionInfo getSessionInfo() {
1576 return sessionInfo;
1577 }
1578
1579 /**
1580 * Getter for the underlying Swing component.
1581 *
1582 * @return the SwingComponent
1583 */
1584 public SwingComponent getSwingComponent() {
1585 return swing;
1586 }
1587
1588 // ------------------------------------------------------------------------
1589 // KeyListener ------------------------------------------------------------
1590 // ------------------------------------------------------------------------
1591
1592 /**
1593 * Pass Swing keystrokes into the event queue.
1594 *
1595 * @param key keystroke received
1596 */
1597 public void keyReleased(final KeyEvent key) {
1598 // Ignore release events
1599 }
1600
1601 /**
1602 * Pass Swing keystrokes into the event queue.
1603 *
1604 * @param key keystroke received
1605 */
1606 public void keyTyped(final KeyEvent key) {
1607 // Ignore typed events
1608 }
1609
1610 /**
1611 * Pass Swing keystrokes into the event queue.
1612 *
1613 * @param key keystroke received
1614 */
1615 public void keyPressed(final KeyEvent key) {
1616 boolean alt = false;
1617 boolean shift = false;
1618 boolean ctrl = false;
1619 char ch = ' ';
1620 boolean isKey = false;
1621 if (key.isActionKey()) {
1622 isKey = true;
1623 } else {
1624 ch = key.getKeyChar();
1625 }
1626 alt = key.isAltDown();
1627 ctrl = key.isControlDown();
1628 shift = key.isShiftDown();
1629
1630 /*
1631 System.err.printf("Swing Key: %s\n", key);
1632 System.err.printf(" isKey: %s\n", isKey);
1633 System.err.printf(" alt: %s\n", alt);
1634 System.err.printf(" ctrl: %s\n", ctrl);
1635 System.err.printf(" shift: %s\n", shift);
1636 System.err.printf(" ch: %s\n", ch);
1637 */
1638
1639 // Special case: not return the bare modifier presses
1640 switch (key.getKeyCode()) {
1641 case KeyEvent.VK_ALT:
1642 return;
1643 case KeyEvent.VK_ALT_GRAPH:
1644 return;
1645 case KeyEvent.VK_CONTROL:
1646 return;
1647 case KeyEvent.VK_SHIFT:
1648 return;
1649 case KeyEvent.VK_META:
1650 return;
1651 default:
1652 break;
1653 }
1654
1655 TKeypress keypress = null;
1656 if (isKey) {
1657 switch (key.getKeyCode()) {
1658 case KeyEvent.VK_F1:
1659 keypress = new TKeypress(true, TKeypress.F1, ' ',
1660 alt, ctrl, shift);
1661 break;
1662 case KeyEvent.VK_F2:
1663 keypress = new TKeypress(true, TKeypress.F2, ' ',
1664 alt, ctrl, shift);
1665 break;
1666 case KeyEvent.VK_F3:
1667 keypress = new TKeypress(true, TKeypress.F3, ' ',
1668 alt, ctrl, shift);
1669 break;
1670 case KeyEvent.VK_F4:
1671 keypress = new TKeypress(true, TKeypress.F4, ' ',
1672 alt, ctrl, shift);
1673 break;
1674 case KeyEvent.VK_F5:
1675 keypress = new TKeypress(true, TKeypress.F5, ' ',
1676 alt, ctrl, shift);
1677 break;
1678 case KeyEvent.VK_F6:
1679 keypress = new TKeypress(true, TKeypress.F6, ' ',
1680 alt, ctrl, shift);
1681 break;
1682 case KeyEvent.VK_F7:
1683 keypress = new TKeypress(true, TKeypress.F7, ' ',
1684 alt, ctrl, shift);
1685 break;
1686 case KeyEvent.VK_F8:
1687 keypress = new TKeypress(true, TKeypress.F8, ' ',
1688 alt, ctrl, shift);
1689 break;
1690 case KeyEvent.VK_F9:
1691 keypress = new TKeypress(true, TKeypress.F9, ' ',
1692 alt, ctrl, shift);
1693 break;
1694 case KeyEvent.VK_F10:
1695 keypress = new TKeypress(true, TKeypress.F10, ' ',
1696 alt, ctrl, shift);
1697 break;
1698 case KeyEvent.VK_F11:
1699 keypress = new TKeypress(true, TKeypress.F11, ' ',
1700 alt, ctrl, shift);
1701 break;
1702 case KeyEvent.VK_F12:
1703 keypress = new TKeypress(true, TKeypress.F12, ' ',
1704 alt, ctrl, shift);
1705 break;
1706 case KeyEvent.VK_HOME:
1707 keypress = new TKeypress(true, TKeypress.HOME, ' ',
1708 alt, ctrl, shift);
1709 break;
1710 case KeyEvent.VK_END:
1711 keypress = new TKeypress(true, TKeypress.END, ' ',
1712 alt, ctrl, shift);
1713 break;
1714 case KeyEvent.VK_PAGE_UP:
1715 keypress = new TKeypress(true, TKeypress.PGUP, ' ',
1716 alt, ctrl, shift);
1717 break;
1718 case KeyEvent.VK_PAGE_DOWN:
1719 keypress = new TKeypress(true, TKeypress.PGDN, ' ',
1720 alt, ctrl, shift);
1721 break;
1722 case KeyEvent.VK_INSERT:
1723 keypress = new TKeypress(true, TKeypress.INS, ' ',
1724 alt, ctrl, shift);
1725 break;
1726 case KeyEvent.VK_DELETE:
1727 keypress = new TKeypress(true, TKeypress.DEL, ' ',
1728 alt, ctrl, shift);
1729 break;
1730 case KeyEvent.VK_RIGHT:
1731 keypress = new TKeypress(true, TKeypress.RIGHT, ' ',
1732 alt, ctrl, shift);
1733 break;
1734 case KeyEvent.VK_LEFT:
1735 keypress = new TKeypress(true, TKeypress.LEFT, ' ',
1736 alt, ctrl, shift);
1737 break;
1738 case KeyEvent.VK_UP:
1739 keypress = new TKeypress(true, TKeypress.UP, ' ',
1740 alt, ctrl, shift);
1741 break;
1742 case KeyEvent.VK_DOWN:
1743 keypress = new TKeypress(true, TKeypress.DOWN, ' ',
1744 alt, ctrl, shift);
1745 break;
1746 case KeyEvent.VK_TAB:
1747 // Special case: distinguish TAB vs BTAB
1748 if (shift) {
1749 keypress = kbShiftTab;
1750 } else {
1751 keypress = kbTab;
1752 }
1753 break;
1754 case KeyEvent.VK_ENTER:
1755 keypress = new TKeypress(true, TKeypress.ENTER, ' ',
1756 alt, ctrl, shift);
1757 break;
1758 case KeyEvent.VK_ESCAPE:
1759 keypress = new TKeypress(true, TKeypress.ESC, ' ',
1760 alt, ctrl, shift);
1761 break;
1762 case KeyEvent.VK_BACK_SPACE:
1763 keypress = kbBackspace;
1764 break;
1765 default:
1766 // Unsupported, ignore
1767 return;
1768 }
1769 }
1770
1771 if (keypress == null) {
1772 switch (ch) {
1773 case 0x08:
1774 // Disambiguate ^H from Backspace.
1775 if (KeyEvent.getKeyText(key.getKeyCode()).equals("H")) {
1776 // This is ^H.
1777 keypress = kbBackspace;
1778 } else {
1779 // We are emulating Xterm here, where the backspace key
1780 // on the keyboard returns ^?.
1781 keypress = kbBackspaceDel;
1782 }
1783 break;
1784 case 0x0A:
1785 keypress = kbEnter;
1786 break;
1787 case 0x1B:
1788 keypress = kbEsc;
1789 break;
1790 case 0x0D:
1791 keypress = kbEnter;
1792 break;
1793 case 0x09:
1794 if (shift) {
1795 keypress = kbShiftTab;
1796 } else {
1797 keypress = kbTab;
1798 }
1799 break;
1800 case 0x7F:
1801 keypress = kbDel;
1802 break;
1803 default:
1804 if (!alt && ctrl && !shift) {
1805 ch = KeyEvent.getKeyText(key.getKeyCode()).charAt(0);
1806 }
1807 // Not a special key, put it together
1808 keypress = new TKeypress(false, 0, ch, alt, ctrl, shift);
1809 }
1810 }
1811
1812 // Save it and we are done.
1813 synchronized (eventQueue) {
1814 eventQueue.add(new TKeypressEvent(keypress));
1815 resetBlinkTimer();
1816 }
1817 if (listener != null) {
1818 synchronized (listener) {
1819 listener.notifyAll();
1820 }
1821 }
1822 }
1823
1824 // ------------------------------------------------------------------------
1825 // WindowListener ---------------------------------------------------------
1826 // ------------------------------------------------------------------------
1827
1828 /**
1829 * Pass window events into the event queue.
1830 *
1831 * @param event window event received
1832 */
1833 public void windowActivated(final WindowEvent event) {
1834 // Force a total repaint
1835 synchronized (this) {
1836 clearPhysical();
1837 }
1838 }
1839
1840 /**
1841 * Pass window events into the event queue.
1842 *
1843 * @param event window event received
1844 */
1845 public void windowClosed(final WindowEvent event) {
1846 // Ignore
1847 }
1848
1849 /**
1850 * Pass window events into the event queue.
1851 *
1852 * @param event window event received
1853 */
1854 public void windowClosing(final WindowEvent event) {
1855 // Drop a cmBackendDisconnect and walk away
1856 synchronized (eventQueue) {
1857 eventQueue.add(new TCommandEvent(cmBackendDisconnect));
1858 resetBlinkTimer();
1859 }
1860 if (listener != null) {
1861 synchronized (listener) {
1862 listener.notifyAll();
1863 }
1864 }
1865 }
1866
1867 /**
1868 * Pass window events into the event queue.
1869 *
1870 * @param event window event received
1871 */
1872 public void windowDeactivated(final WindowEvent event) {
1873 // Ignore
1874 }
1875
1876 /**
1877 * Pass window events into the event queue.
1878 *
1879 * @param event window event received
1880 */
1881 public void windowDeiconified(final WindowEvent event) {
1882 // Ignore
1883 }
1884
1885 /**
1886 * Pass window events into the event queue.
1887 *
1888 * @param event window event received
1889 */
1890 public void windowIconified(final WindowEvent event) {
1891 // Ignore
1892 }
1893
1894 /**
1895 * Pass window events into the event queue.
1896 *
1897 * @param event window event received
1898 */
1899 public void windowOpened(final WindowEvent event) {
1900 // Ignore
1901 }
1902
1903 // ------------------------------------------------------------------------
1904 // ComponentListener ------------------------------------------------------
1905 // ------------------------------------------------------------------------
1906
1907 /**
1908 * Pass component events into the event queue.
1909 *
1910 * @param event component event received
1911 */
1912 public void componentHidden(final ComponentEvent event) {
1913 // Ignore
1914 }
1915
1916 /**
1917 * Pass component events into the event queue.
1918 *
1919 * @param event component event received
1920 */
1921 public void componentShown(final ComponentEvent event) {
1922 // Ignore
1923 }
1924
1925 /**
1926 * Pass component events into the event queue.
1927 *
1928 * @param event component event received
1929 */
1930 public void componentMoved(final ComponentEvent event) {
1931 // Ignore
1932 }
1933
1934 /**
1935 * Pass component events into the event queue.
1936 *
1937 * @param event component event received
1938 */
1939 public void componentResized(final ComponentEvent event) {
1940 if (gotFontDimensions == false) {
1941 // We are still waiting to get font information. Don't pass a
1942 // resize event up.
1943 // System.err.println("size " + swing.getComponent().getSize());
1944 return;
1945 }
1946
1947 if (sessionInfo == null) {
1948 // This is the initial component resize in construction, bail
1949 // out.
1950 return;
1951 }
1952
1953 // Drop a new TResizeEvent into the queue
1954 sessionInfo.queryWindowSize();
1955 synchronized (eventQueue) {
1956 TResizeEvent windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN,
1957 sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight());
1958 eventQueue.add(windowResize);
1959 resetBlinkTimer();
1960 /*
1961 System.err.println("Add resize event: " + windowResize.getWidth() +
1962 " x " + windowResize.getHeight());
1963 */
1964 }
1965 if (listener != null) {
1966 synchronized (listener) {
1967 listener.notifyAll();
1968 }
1969 }
1970 }
1971
1972 // ------------------------------------------------------------------------
1973 // MouseMotionListener ----------------------------------------------------
1974 // ------------------------------------------------------------------------
1975
1976 /**
1977 * Pass mouse events into the event queue.
1978 *
1979 * @param mouse mouse event received
1980 */
1981 public void mouseDragged(final MouseEvent mouse) {
1982 int modifiers = mouse.getModifiersEx();
1983 boolean eventMouse1 = false;
1984 boolean eventMouse2 = false;
1985 boolean eventMouse3 = false;
1986 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
1987 eventMouse1 = true;
1988 }
1989 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
1990 eventMouse2 = true;
1991 }
1992 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
1993 eventMouse3 = true;
1994 }
1995 mouse1 = eventMouse1;
1996 mouse2 = eventMouse2;
1997 mouse3 = eventMouse3;
1998 int x = textColumn(mouse.getX());
1999 int y = textRow(mouse.getY());
2000
2001 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION,
2002 x, y, x, y, mouse1, mouse2, mouse3, false, false);
2003
2004 synchronized (eventQueue) {
2005 eventQueue.add(mouseEvent);
2006 resetBlinkTimer();
2007 }
2008 if (listener != null) {
2009 synchronized (listener) {
2010 listener.notifyAll();
2011 }
2012 }
2013 }
2014
2015 /**
2016 * Pass mouse events into the event queue.
2017 *
2018 * @param mouse mouse event received
2019 */
2020 public void mouseMoved(final MouseEvent mouse) {
2021 int x = textColumn(mouse.getX());
2022 int y = textRow(mouse.getY());
2023 if ((x == oldMouseX) && (y == oldMouseY)) {
2024 // Bail out, we've moved some pixels but not a whole text cell.
2025 return;
2026 }
2027 oldMouseX = x;
2028 oldMouseY = y;
2029
2030 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION,
2031 x, y, x, y, mouse1, mouse2, mouse3, false, false);
2032
2033 synchronized (eventQueue) {
2034 eventQueue.add(mouseEvent);
2035 resetBlinkTimer();
2036 }
2037 if (listener != null) {
2038 synchronized (listener) {
2039 listener.notifyAll();
2040 }
2041 }
2042 }
2043
2044 // ------------------------------------------------------------------------
2045 // MouseListener ----------------------------------------------------------
2046 // ------------------------------------------------------------------------
2047
2048 /**
2049 * Pass mouse events into the event queue.
2050 *
2051 * @param mouse mouse event received
2052 */
2053 public void mouseClicked(final MouseEvent mouse) {
2054 // Ignore
2055 }
2056
2057 /**
2058 * Pass mouse events into the event queue.
2059 *
2060 * @param mouse mouse event received
2061 */
2062 public void mouseEntered(final MouseEvent mouse) {
2063 // Ignore
2064 }
2065
2066 /**
2067 * Pass mouse events into the event queue.
2068 *
2069 * @param mouse mouse event received
2070 */
2071 public void mouseExited(final MouseEvent mouse) {
2072 // Ignore
2073 }
2074
2075 /**
2076 * Pass mouse events into the event queue.
2077 *
2078 * @param mouse mouse event received
2079 */
2080 public void mousePressed(final MouseEvent mouse) {
2081 int modifiers = mouse.getModifiersEx();
2082 boolean eventMouse1 = false;
2083 boolean eventMouse2 = false;
2084 boolean eventMouse3 = false;
2085 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
2086 eventMouse1 = true;
2087 }
2088 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
2089 eventMouse2 = true;
2090 }
2091 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
2092 eventMouse3 = true;
2093 }
2094 mouse1 = eventMouse1;
2095 mouse2 = eventMouse2;
2096 mouse3 = eventMouse3;
2097 int x = textColumn(mouse.getX());
2098 int y = textRow(mouse.getY());
2099
2100 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN,
2101 x, y, x, y, mouse1, mouse2, mouse3, false, false);
2102
2103 synchronized (eventQueue) {
2104 eventQueue.add(mouseEvent);
2105 resetBlinkTimer();
2106 }
2107 if (listener != null) {
2108 synchronized (listener) {
2109 listener.notifyAll();
2110 }
2111 }
2112 }
2113
2114 /**
2115 * Pass mouse events into the event queue.
2116 *
2117 * @param mouse mouse event received
2118 */
2119 public void mouseReleased(final MouseEvent mouse) {
2120 int modifiers = mouse.getModifiersEx();
2121 boolean eventMouse1 = false;
2122 boolean eventMouse2 = false;
2123 boolean eventMouse3 = false;
2124 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
2125 eventMouse1 = true;
2126 }
2127 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
2128 eventMouse2 = true;
2129 }
2130 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
2131 eventMouse3 = true;
2132 }
2133 if (mouse1) {
2134 mouse1 = false;
2135 eventMouse1 = true;
2136 }
2137 if (mouse2) {
2138 mouse2 = false;
2139 eventMouse2 = true;
2140 }
2141 if (mouse3) {
2142 mouse3 = false;
2143 eventMouse3 = true;
2144 }
2145 int x = textColumn(mouse.getX());
2146 int y = textRow(mouse.getY());
2147
2148 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_UP,
2149 x, y, x, y, eventMouse1, eventMouse2, eventMouse3, false, false);
2150
2151 synchronized (eventQueue) {
2152 eventQueue.add(mouseEvent);
2153 resetBlinkTimer();
2154 }
2155 if (listener != null) {
2156 synchronized (listener) {
2157 listener.notifyAll();
2158 }
2159 }
2160 }
2161
2162 // ------------------------------------------------------------------------
2163 // MouseWheelListener -----------------------------------------------------
2164 // ------------------------------------------------------------------------
2165
2166 /**
2167 * Pass mouse events into the event queue.
2168 *
2169 * @param mouse mouse event received
2170 */
2171 public void mouseWheelMoved(final MouseWheelEvent mouse) {
2172 int modifiers = mouse.getModifiersEx();
2173 boolean eventMouse1 = false;
2174 boolean eventMouse2 = false;
2175 boolean eventMouse3 = false;
2176 boolean mouseWheelUp = false;
2177 boolean mouseWheelDown = false;
2178 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
2179 eventMouse1 = true;
2180 }
2181 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
2182 eventMouse2 = true;
2183 }
2184 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
2185 eventMouse3 = true;
2186 }
2187 mouse1 = eventMouse1;
2188 mouse2 = eventMouse2;
2189 mouse3 = eventMouse3;
2190 int x = textColumn(mouse.getX());
2191 int y = textRow(mouse.getY());
2192 if (mouse.getWheelRotation() > 0) {
2193 mouseWheelDown = true;
2194 }
2195 if (mouse.getWheelRotation() < 0) {
2196 mouseWheelUp = true;
2197 }
2198
2199 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN,
2200 x, y, x, y, mouse1, mouse2, mouse3, mouseWheelUp, mouseWheelDown);
2201
2202 synchronized (eventQueue) {
2203 eventQueue.add(mouseEvent);
2204 resetBlinkTimer();
2205 }
2206 if (listener != null) {
2207 synchronized (listener) {
2208 listener.notifyAll();
2209 }
2210 }
2211 }
2212
2213 }