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