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