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