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