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