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