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