dev version bump to 0.0.6
[nikiroo-utils.git] / src / jexer / backend / SwingTerminal.java
CommitLineData
42873e30
KL
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 */
29package jexer.backend;
30
31import java.awt.BorderLayout;
32import java.awt.Color;
33import java.awt.Font;
34import java.awt.FontMetrics;
35import java.awt.Graphics2D;
36import java.awt.Graphics;
37import java.awt.Insets;
38import java.awt.Rectangle;
39import java.awt.event.ComponentEvent;
40import java.awt.event.ComponentListener;
41import java.awt.event.KeyEvent;
42import java.awt.event.KeyListener;
43import java.awt.event.MouseEvent;
44import java.awt.event.MouseListener;
45import java.awt.event.MouseMotionListener;
46import java.awt.event.MouseWheelEvent;
47import java.awt.event.MouseWheelListener;
48import java.awt.event.WindowEvent;
49import java.awt.event.WindowListener;
50import java.awt.geom.Rectangle2D;
51import java.awt.image.BufferedImage;
52import java.io.InputStream;
53import java.util.Date;
54import java.util.HashMap;
55import java.util.LinkedList;
56import java.util.List;
57import javax.swing.JComponent;
58import javax.swing.JFrame;
59import javax.swing.SwingUtilities;
60
61import jexer.TKeypress;
62import jexer.bits.Cell;
63import jexer.bits.CellAttributes;
64import jexer.event.TCommandEvent;
65import jexer.event.TInputEvent;
66import jexer.event.TKeypressEvent;
67import jexer.event.TMouseEvent;
68import jexer.event.TResizeEvent;
69import static jexer.TCommand.*;
70import 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 */
81public 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
42873e30
KL
503 if (gotTerminus == true) {
504 textHeight++;
505 }
506
507 if (getFontAdjustments() == false) {
508 // We were unable to programmatically determine textAdjustX and
509 // textAdjustY, so try some guesses based on VM vendor.
510 String runtime = System.getProperty("java.runtime.name");
511 if ((runtime != null) && (runtime.contains("Java(TM)"))) {
512 textAdjustY = -1;
513 textAdjustX = 0;
514 }
515 }
516
517 if (sessionInfo != null) {
518 sessionInfo.setTextCellDimensions(textWidth, textHeight);
519 }
520 gotFontDimensions = true;
521 }
522
523 /**
524 * Resize to font dimensions.
525 */
526 public void resizeToScreen() {
527 swing.setDimensions(textWidth * width, textHeight * height);
528 }
529
530 /**
531 * Draw one glyph to the screen.
532 *
533 * @param gr the Swing Graphics context
534 * @param cell the Cell to draw
535 * @param xPixel the x-coordinate to render to. 0 means the
536 * left-most pixel column.
537 * @param yPixel the y-coordinate to render to. 0 means the top-most
538 * pixel row.
539 */
540 private void drawGlyph(final Graphics gr, final Cell cell,
541 final int xPixel, final int yPixel) {
542
543 /*
544 System.err.println("drawGlyph(): " + xPixel + " " + yPixel +
545 " " + cell);
546 */
547
548 BufferedImage image = null;
549 if (cell.isBlink() && !cursorBlinkVisible) {
550 image = glyphCacheBlink.get(cell);
551 } else {
552 image = glyphCache.get(cell);
553 }
554 if (image != null) {
555 if (swing.getFrame() != null) {
556 gr.drawImage(image, xPixel, yPixel, swing.getFrame());
557 } else {
558 gr.drawImage(image, xPixel, yPixel, swing.getComponent());
559 }
560 return;
561 }
562
563 // Generate glyph and draw it.
564 Graphics2D gr2 = null;
565 int gr2x = xPixel;
566 int gr2y = yPixel;
567 if ((SwingComponent.tripleBuffer) && (swing.getFrame() != null)) {
568 image = new BufferedImage(textWidth, textHeight,
569 BufferedImage.TYPE_INT_ARGB);
570 gr2 = image.createGraphics();
571 gr2.setFont(swing.getFont());
572 gr2x = 0;
573 gr2y = 0;
574 } else {
575 gr2 = (Graphics2D) gr;
576 }
577
578 Cell cellColor = new Cell();
579 cellColor.setTo(cell);
580
581 // Check for reverse
582 if (cell.isReverse()) {
583 cellColor.setForeColor(cell.getBackColor());
584 cellColor.setBackColor(cell.getForeColor());
585 }
586
587 // Draw the background rectangle, then the foreground character.
588 gr2.setColor(attrToBackgroundColor(cellColor));
589 gr2.fillRect(gr2x, gr2y, textWidth, textHeight);
590
591 // Handle blink and underline
592 if (!cell.isBlink()
593 || (cell.isBlink() && cursorBlinkVisible)
594 ) {
595 gr2.setColor(attrToForegroundColor(cellColor));
596 char [] chars = new char[1];
597 chars[0] = cell.getChar();
598 gr2.drawChars(chars, 0, 1, gr2x + textAdjustX,
599 gr2y + textHeight - maxDescent + textAdjustY);
600
601 if (cell.isUnderline()) {
602 gr2.fillRect(gr2x, gr2y + textHeight - 2, textWidth, 2);
603 }
604 }
605
606 if ((SwingComponent.tripleBuffer) && (swing.getFrame() != null)) {
607 gr2.dispose();
608
609 // We need a new key that will not be mutated by
610 // invertCell().
611 Cell key = new Cell();
612 key.setTo(cell);
613 if (cell.isBlink() && !cursorBlinkVisible) {
614 glyphCacheBlink.put(key, image);
615 } else {
616 glyphCache.put(key, image);
617 }
618
619 if (swing.getFrame() != null) {
620 gr.drawImage(image, xPixel, yPixel, swing.getFrame());
621 } else {
622 gr.drawImage(image, xPixel, yPixel, swing.getComponent());
623 }
624 }
625
626 }
627
628 /**
629 * Check if the cursor is visible, and if so draw it.
630 *
631 * @param gr the Swing Graphics context
632 */
633 private void drawCursor(final Graphics gr) {
634
635 if (cursorVisible
fe0770f9
KL
636 && (cursorY >= 0)
637 && (cursorX >= 0)
42873e30
KL
638 && (cursorY <= height - 1)
639 && (cursorX <= width - 1)
640 && cursorBlinkVisible
641 ) {
642 int xPixel = cursorX * textWidth + left;
643 int yPixel = cursorY * textHeight + top;
644 Cell lCell = logical[cursorX][cursorY];
645 gr.setColor(attrToForegroundColor(lCell));
646 switch (cursorStyle) {
647 default:
648 // Fall through...
649 case UNDERLINE:
650 gr.fillRect(xPixel, yPixel + textHeight - 2, textWidth, 2);
651 break;
652 case BLOCK:
653 gr.fillRect(xPixel, yPixel, textWidth, textHeight);
654 break;
655 case OUTLINE:
656 gr.drawRect(xPixel, yPixel, textWidth - 1, textHeight - 1);
657 break;
658 }
659 }
660 }
661
e8a11f98
KL
662 /**
663 * Reset the blink timer.
664 */
665 private void resetBlinkTimer() {
666 // See if it is time to flip the blink time.
667 long nowTime = (new Date()).getTime();
668 lastBlinkTime = nowTime;
669 cursorBlinkVisible = true;
670 }
671
42873e30
KL
672 /**
673 * Paint redraws the whole screen.
674 *
675 * @param gr the Swing Graphics context
676 */
677 public void paint(final Graphics gr) {
678
679 if (gotFontDimensions == false) {
680 // Lazy-load the text width/height
681 // System.err.println("calling getFontDimensions...");
682 getFontDimensions(gr);
683 /*
684 System.err.println("textWidth " + textWidth +
685 " textHeight " + textHeight);
686 System.err.println("FONT: " + swing.getFont() + " font " + font);
687 */
688 // resizeToScreen();
689 }
690
691 // See if it is time to flip the blink time.
692 long nowTime = (new Date()).getTime();
693 if (nowTime > blinkMillis + lastBlinkTime) {
694 lastBlinkTime = nowTime;
695 cursorBlinkVisible = !cursorBlinkVisible;
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 dirty = false;
766 reallyCleared = false;
767 } // synchronized (this)
768 }
769
770 /**
771 * Restore terminal to normal state.
772 */
773 public void shutdown() {
774 swing.dispose();
775 }
776
777 /**
778 * Push the logical screen to the physical device.
779 */
780 @Override
781 public void flushPhysical() {
782
783 /*
784 System.err.printf("flushPhysical(): reallyCleared %s dirty %s\n",
785 reallyCleared, dirty);
786 */
787
788 // If reallyCleared is set, we have to draw everything.
789 if ((swing.getFrame() != null)
790 && (swing.getBufferStrategy() != null)
791 && (reallyCleared == true)
792 ) {
793 // Triple-buffering: we have to redraw everything on this thread.
794 Graphics gr = swing.getBufferStrategy().getDrawGraphics();
795 swing.paint(gr);
796 gr.dispose();
797 swing.getBufferStrategy().show();
798 // sync() doesn't seem to help the tearing for me.
799 // Toolkit.getDefaultToolkit().sync();
800 return;
801 } else if (((swing.getFrame() != null)
802 && (swing.getBufferStrategy() == null))
803 || (reallyCleared == true)
804 ) {
805 // Repaint everything on the Swing thread.
806 // System.err.println("REPAINT ALL");
807 swing.repaint();
808 return;
809 }
810
811 // Do nothing if nothing happened.
812 if (!dirty) {
813 return;
814 }
815
816 if ((swing.getFrame() != null) && (swing.getBufferStrategy() != null)) {
817 // See if it is time to flip the blink time.
818 long nowTime = (new Date()).getTime();
819 if (nowTime > blinkMillis + lastBlinkTime) {
820 lastBlinkTime = nowTime;
821 cursorBlinkVisible = !cursorBlinkVisible;
822 }
823
824 Graphics gr = swing.getBufferStrategy().getDrawGraphics();
825
826 synchronized (this) {
827 for (int y = 0; y < height; y++) {
828 for (int x = 0; x < width; x++) {
829 Cell lCell = logical[x][y];
830 Cell pCell = physical[x][y];
831
832 int xPixel = x * textWidth + left;
833 int yPixel = y * textHeight + top;
834
835 if (!lCell.equals(pCell)
836 || ((x == cursorX)
837 && (y == cursorY)
838 && cursorVisible)
839 || (lCell.isBlink())
840 ) {
841 drawGlyph(gr, lCell, xPixel, yPixel);
842 physical[x][y].setTo(lCell);
843 }
844 }
845 }
846 drawCursor(gr);
847 } // synchronized (this)
848
849 gr.dispose();
850 swing.getBufferStrategy().show();
851 // sync() doesn't seem to help the tearing for me.
852 // Toolkit.getDefaultToolkit().sync();
853 return;
854 }
855
856 // Swing thread version: request a repaint, but limit it to the area
857 // that has changed.
858
859 // Find the minimum-size damaged region.
860 int xMin = swing.getWidth();
861 int xMax = 0;
862 int yMin = swing.getHeight();
863 int yMax = 0;
864
865 synchronized (this) {
866 for (int y = 0; y < height; y++) {
867 for (int x = 0; x < width; x++) {
868 Cell lCell = logical[x][y];
869 Cell pCell = physical[x][y];
870
871 int xPixel = x * textWidth + left;
872 int yPixel = y * textHeight + top;
873
874 if (!lCell.equals(pCell)
875 || ((x == cursorX)
876 && (y == cursorY)
877 && cursorVisible)
878 || lCell.isBlink()
879 ) {
880 if (xPixel < xMin) {
881 xMin = xPixel;
882 }
883 if (xPixel + textWidth > xMax) {
884 xMax = xPixel + textWidth;
885 }
886 if (yPixel < yMin) {
887 yMin = yPixel;
888 }
889 if (yPixel + textHeight > yMax) {
890 yMax = yPixel + textHeight;
891 }
892 }
893 }
894 }
895 }
896 if (xMin + textWidth >= xMax) {
897 xMax += textWidth;
898 }
899 if (yMin + textHeight >= yMax) {
900 yMax += textHeight;
901 }
902
903 // Repaint the desired area
904 /*
905 System.err.printf("REPAINT X %d %d Y %d %d\n", xMin, xMax,
906 yMin, yMax);
907 */
908
909 if ((swing.getFrame() != null) && (swing.getBufferStrategy() != null)) {
910 // This path should never be taken, but is left here for
911 // completeness.
912 Graphics gr = swing.getBufferStrategy().getDrawGraphics();
913 Rectangle bounds = new Rectangle(xMin, yMin, xMax - xMin,
914 yMax - yMin);
915 gr.setClip(bounds);
916 swing.paint(gr);
917 gr.dispose();
918 swing.getBufferStrategy().show();
919 // sync() doesn't seem to help the tearing for me.
920 // Toolkit.getDefaultToolkit().sync();
921 } else {
922 // Repaint on the Swing thread.
923 swing.repaint(xMin, yMin, xMax - xMin, yMax - yMin);
924 }
925 }
926
927 /**
928 * Put the cursor at (x,y).
929 *
930 * @param visible if true, the cursor should be visible
931 * @param x column coordinate to put the cursor on
932 * @param y row coordinate to put the cursor on
933 */
934 @Override
935 public void putCursor(final boolean visible, final int x, final int y) {
936
937 if ((visible == cursorVisible) && ((x == cursorX) && (y == cursorY))) {
938 // See if it is time to flip the blink time.
939 long nowTime = (new Date()).getTime();
940 if (nowTime < blinkMillis + lastBlinkTime) {
941 // Nothing has changed, so don't do anything.
942 return;
943 }
944 }
945
946 if (cursorVisible
fe0770f9
KL
947 && (cursorY >= 0)
948 && (cursorX >= 0)
42873e30
KL
949 && (cursorY <= height - 1)
950 && (cursorX <= width - 1)
951 ) {
952 // Make the current cursor position dirty
953 if (physical[cursorX][cursorY].getChar() == 'Q') {
954 physical[cursorX][cursorY].setChar('X');
955 } else {
956 physical[cursorX][cursorY].setChar('Q');
957 }
958 }
959
960 super.putCursor(visible, x, y);
961 }
962
963 /**
964 * Convert pixel column position to text cell column position.
965 *
966 * @param x pixel column position
967 * @return text cell column position
968 */
969 public int textColumn(final int x) {
970 return ((x - left) / textWidth);
971 }
972
973 /**
974 * Convert pixel row position to text cell row position.
975 *
976 * @param y pixel row position
977 * @return text cell row position
978 */
979 public int textRow(final int y) {
980 return ((y - top) / textHeight);
981 }
982
983 /**
984 * Set the window title.
985 *
986 * @param title the new title
987 */
988 public void setTitle(final String title) {
989 swing.setTitle(title);
990 }
991
992 // ------------------------------------------------------------------------
993 // TerminalReader ---------------------------------------------------------
994 // ------------------------------------------------------------------------
995
996 /**
997 * The session information.
998 */
999 private SwingSessionInfo sessionInfo;
1000
1001 /**
1002 * Getter for sessionInfo.
1003 *
1004 * @return the SessionInfo
1005 */
1006 public SessionInfo getSessionInfo() {
1007 return sessionInfo;
1008 }
1009
1010 /**
1011 * The listening object that run() wakes up on new input.
1012 */
1013 private Object listener;
1014
1015 /**
1016 * Set listener to a different Object.
1017 *
1018 * @param listener the new listening object that run() wakes up on new
1019 * input
1020 */
1021 public void setListener(final Object listener) {
1022 this.listener = listener;
1023 }
1024
1025 /**
1026 * The event queue, filled up by a thread reading on input.
1027 */
1028 private List<TInputEvent> eventQueue;
1029
1030 /**
1031 * The last reported mouse X position.
1032 */
1033 private int oldMouseX = -1;
1034
1035 /**
1036 * The last reported mouse Y position.
1037 */
1038 private int oldMouseY = -1;
1039
1040 /**
1041 * true if mouse1 was down. Used to report mouse1 on the release event.
1042 */
1043 private boolean mouse1 = false;
1044
1045 /**
1046 * true if mouse2 was down. Used to report mouse2 on the release event.
1047 */
1048 private boolean mouse2 = false;
1049
1050 /**
1051 * true if mouse3 was down. Used to report mouse3 on the release event.
1052 */
1053 private boolean mouse3 = false;
1054
1055 /**
1056 * Public constructor creates a new JFrame to render to.
1057 *
1058 * @param windowWidth the number of text columns to start with
1059 * @param windowHeight the number of text rows to start with
1060 * @param fontSize the size in points. Good values to pick are: 16, 20,
1061 * 22, and 24.
1062 * @param listener the object this backend needs to wake up when new
1063 * input comes in
1064 */
1065 public SwingTerminal(final int windowWidth, final int windowHeight,
1066 final int fontSize, final Object listener) {
1067
1068 this.fontSize = fontSize;
1069
1070 setDOSColors();
1071
1072 // Figure out my cursor style.
1073 String cursorStyleString = System.getProperty(
1074 "jexer.Swing.cursorStyle", "underline").toLowerCase();
1075 if (cursorStyleString.equals("underline")) {
1076 cursorStyle = CursorStyle.UNDERLINE;
1077 } else if (cursorStyleString.equals("outline")) {
1078 cursorStyle = CursorStyle.OUTLINE;
1079 } else if (cursorStyleString.equals("block")) {
1080 cursorStyle = CursorStyle.BLOCK;
1081 }
1082
1083 // Pull the system property for triple buffering.
1084 if (System.getProperty("jexer.Swing.tripleBuffer") != null) {
3e074355
KL
1085 if (System.getProperty("jexer.Swing.tripleBuffer").equals("true")) {
1086 SwingComponent.tripleBuffer = true;
1087 } else {
42873e30
KL
1088 SwingComponent.tripleBuffer = false;
1089 }
1090 }
1091
1092 try {
1093 SwingUtilities.invokeAndWait(new Runnable() {
1094 public void run() {
1095
1096 JFrame frame = new JFrame() {
1097
1098 /**
1099 * Serializable version.
1100 */
1101 private static final long serialVersionUID = 1;
1102
1103 /**
1104 * The code that performs the actual drawing.
1105 */
1106 public SwingTerminal screen = null;
1107
1108 /*
1109 * Anonymous class initializer saves the screen
1110 * reference, so that paint() and the like call out
1111 * to SwingTerminal.
1112 */
1113 {
1114 this.screen = SwingTerminal.this;
1115 }
1116
1117 /**
1118 * Update redraws the whole screen.
1119 *
1120 * @param gr the Swing Graphics context
1121 */
1122 @Override
1123 public void update(final Graphics gr) {
1124 // The default update clears the area. Don't do
1125 // that, instead just paint it directly.
1126 paint(gr);
1127 }
1128
1129 /**
1130 * Paint redraws the whole screen.
1131 *
1132 * @param gr the Swing Graphics context
1133 */
1134 @Override
1135 public void paint(final Graphics gr) {
1136 if (screen != null) {
1137 screen.paint(gr);
1138 }
1139 }
1140 };
1141
1142 // Get the Swing component
1143 SwingTerminal.this.swing = new SwingComponent(frame);
1144
1145 // Hang onto top and left for drawing.
1146 Insets insets = SwingTerminal.this.swing.getInsets();
1147 SwingTerminal.this.left = insets.left;
1148 SwingTerminal.this.top = insets.top;
1149
1150 // Load the font so that we can set sessionInfo.
1151 getDefaultFont();
1152
1153 // Get the default cols x rows and set component size
1154 // accordingly.
1155 SwingTerminal.this.sessionInfo =
1156 new SwingSessionInfo(SwingTerminal.this.swing,
1157 SwingTerminal.this.textWidth,
3e074355
KL
1158 SwingTerminal.this.textHeight,
1159 windowWidth, windowHeight);
42873e30
KL
1160
1161 SwingTerminal.this.setDimensions(sessionInfo.getWindowWidth(),
1162 sessionInfo.getWindowHeight());
1163
1164 SwingTerminal.this.resizeToScreen();
1165 SwingTerminal.this.swing.setVisible(true);
1166 }
1167 });
1168 } catch (Exception e) {
1169 e.printStackTrace();
1170 }
1171
1172 this.listener = listener;
1173 mouse1 = false;
1174 mouse2 = false;
1175 mouse3 = false;
1176 eventQueue = new LinkedList<TInputEvent>();
1177
1178 // Add listeners to Swing.
1179 swing.addKeyListener(this);
1180 swing.addWindowListener(this);
1181 swing.addComponentListener(this);
1182 swing.addMouseListener(this);
1183 swing.addMouseMotionListener(this);
1184 swing.addMouseWheelListener(this);
1185 }
1186
1187 /**
3e074355 1188 * Public constructor renders to an existing JComponent.
42873e30
KL
1189 *
1190 * @param component the Swing component to render to
1191 * @param windowWidth the number of text columns to start with
1192 * @param windowHeight the number of text rows to start with
1193 * @param fontSize the size in points. Good values to pick are: 16, 20,
1194 * 22, and 24.
1195 * @param listener the object this backend needs to wake up when new
1196 * input comes in
1197 */
1198 public SwingTerminal(final JComponent component, final int windowWidth,
1199 final int windowHeight, final int fontSize, final Object listener) {
1200
1201 this.fontSize = fontSize;
1202
1203 setDOSColors();
1204
1205 // Figure out my cursor style.
1206 String cursorStyleString = System.getProperty(
1207 "jexer.Swing.cursorStyle", "underline").toLowerCase();
1208 if (cursorStyleString.equals("underline")) {
1209 cursorStyle = CursorStyle.UNDERLINE;
1210 } else if (cursorStyleString.equals("outline")) {
1211 cursorStyle = CursorStyle.OUTLINE;
1212 } else if (cursorStyleString.equals("block")) {
1213 cursorStyle = CursorStyle.BLOCK;
1214 }
1215
1216 try {
1217 SwingUtilities.invokeAndWait(new Runnable() {
1218 public void run() {
1219
1220 JComponent newComponent = new JComponent() {
1221
1222 /**
1223 * Serializable version.
1224 */
1225 private static final long serialVersionUID = 1;
1226
1227 /**
1228 * The code that performs the actual drawing.
1229 */
1230 public SwingTerminal screen = null;
1231
1232 /*
1233 * Anonymous class initializer saves the screen
1234 * reference, so that paint() and the like call out
1235 * to SwingTerminal.
1236 */
1237 {
1238 this.screen = SwingTerminal.this;
1239 }
1240
1241 /**
1242 * Update redraws the whole screen.
1243 *
1244 * @param gr the Swing Graphics context
1245 */
1246 @Override
1247 public void update(final Graphics gr) {
1248 // The default update clears the area. Don't do
1249 // that, instead just paint it directly.
1250 paint(gr);
1251 }
1252
1253 /**
1254 * Paint redraws the whole screen.
1255 *
1256 * @param gr the Swing Graphics context
1257 */
1258 @Override
1259 public void paint(final Graphics gr) {
1260 if (screen != null) {
1261 screen.paint(gr);
1262 }
1263 }
1264 };
1265 component.setLayout(new BorderLayout());
1266 component.add(newComponent);
1267
1268 // Get the Swing component
1269 SwingTerminal.this.swing = new SwingComponent(component);
1270
1271 // Hang onto top and left for drawing.
1272 Insets insets = SwingTerminal.this.swing.getInsets();
1273 SwingTerminal.this.left = insets.left;
1274 SwingTerminal.this.top = insets.top;
1275
1276 // Load the font so that we can set sessionInfo.
1277 getDefaultFont();
1278
1279 // Get the default cols x rows and set component size
1280 // accordingly.
1281 SwingTerminal.this.sessionInfo =
1282 new SwingSessionInfo(SwingTerminal.this.swing,
1283 SwingTerminal.this.textWidth,
1284 SwingTerminal.this.textHeight);
1285 }
1286 });
1287 } catch (Exception e) {
1288 e.printStackTrace();
1289 }
1290
1291 this.listener = listener;
1292 mouse1 = false;
1293 mouse2 = false;
1294 mouse3 = false;
1295 eventQueue = new LinkedList<TInputEvent>();
1296
1297 // Add listeners to Swing.
1298 swing.addKeyListener(this);
1299 swing.addWindowListener(this);
1300 swing.addComponentListener(this);
1301 swing.addMouseListener(this);
1302 swing.addMouseMotionListener(this);
1303 swing.addMouseWheelListener(this);
1304 }
1305
1306 /**
1307 * Check if there are events in the queue.
1308 *
1309 * @return if true, getEvents() has something to return to the backend
1310 */
1311 public boolean hasEvents() {
1312 synchronized (eventQueue) {
1313 return (eventQueue.size() > 0);
1314 }
1315 }
1316
1317 /**
1318 * Return any events in the IO queue.
1319 *
1320 * @param queue list to append new events to
1321 */
1322 public void getEvents(final List<TInputEvent> queue) {
1323 synchronized (eventQueue) {
1324 if (eventQueue.size() > 0) {
1325 synchronized (queue) {
1326 queue.addAll(eventQueue);
1327 }
1328 eventQueue.clear();
1329 }
1330 }
1331 }
1332
1333 /**
1334 * Restore terminal to normal state.
1335 */
1336 public void closeTerminal() {
1337 shutdown();
1338 }
1339
1340 /**
1341 * Pass Swing keystrokes into the event queue.
1342 *
1343 * @param key keystroke received
1344 */
1345 public void keyReleased(final KeyEvent key) {
1346 // Ignore release events
1347 }
1348
1349 /**
1350 * Pass Swing keystrokes into the event queue.
1351 *
1352 * @param key keystroke received
1353 */
1354 public void keyTyped(final KeyEvent key) {
1355 // Ignore typed events
1356 }
1357
1358 /**
1359 * Pass Swing keystrokes into the event queue.
1360 *
1361 * @param key keystroke received
1362 */
1363 public void keyPressed(final KeyEvent key) {
1364 boolean alt = false;
1365 boolean shift = false;
1366 boolean ctrl = false;
1367 char ch = ' ';
1368 boolean isKey = false;
1369 if (key.isActionKey()) {
1370 isKey = true;
1371 } else {
1372 ch = key.getKeyChar();
1373 }
1374 alt = key.isAltDown();
1375 ctrl = key.isControlDown();
1376 shift = key.isShiftDown();
1377
1378 /*
1379 System.err.printf("Swing Key: %s\n", key);
1380 System.err.printf(" isKey: %s\n", isKey);
1381 System.err.printf(" alt: %s\n", alt);
1382 System.err.printf(" ctrl: %s\n", ctrl);
1383 System.err.printf(" shift: %s\n", shift);
1384 System.err.printf(" ch: %s\n", ch);
1385 */
1386
1387 // Special case: not return the bare modifier presses
1388 switch (key.getKeyCode()) {
1389 case KeyEvent.VK_ALT:
1390 return;
1391 case KeyEvent.VK_ALT_GRAPH:
1392 return;
1393 case KeyEvent.VK_CONTROL:
1394 return;
1395 case KeyEvent.VK_SHIFT:
1396 return;
1397 case KeyEvent.VK_META:
1398 return;
1399 default:
1400 break;
1401 }
1402
1403 TKeypress keypress = null;
1404 if (isKey) {
1405 switch (key.getKeyCode()) {
1406 case KeyEvent.VK_F1:
1407 keypress = new TKeypress(true, TKeypress.F1, ' ',
1408 alt, ctrl, shift);
1409 break;
1410 case KeyEvent.VK_F2:
1411 keypress = new TKeypress(true, TKeypress.F2, ' ',
1412 alt, ctrl, shift);
1413 break;
1414 case KeyEvent.VK_F3:
1415 keypress = new TKeypress(true, TKeypress.F3, ' ',
1416 alt, ctrl, shift);
1417 break;
1418 case KeyEvent.VK_F4:
1419 keypress = new TKeypress(true, TKeypress.F4, ' ',
1420 alt, ctrl, shift);
1421 break;
1422 case KeyEvent.VK_F5:
1423 keypress = new TKeypress(true, TKeypress.F5, ' ',
1424 alt, ctrl, shift);
1425 break;
1426 case KeyEvent.VK_F6:
1427 keypress = new TKeypress(true, TKeypress.F6, ' ',
1428 alt, ctrl, shift);
1429 break;
1430 case KeyEvent.VK_F7:
1431 keypress = new TKeypress(true, TKeypress.F7, ' ',
1432 alt, ctrl, shift);
1433 break;
1434 case KeyEvent.VK_F8:
1435 keypress = new TKeypress(true, TKeypress.F8, ' ',
1436 alt, ctrl, shift);
1437 break;
1438 case KeyEvent.VK_F9:
1439 keypress = new TKeypress(true, TKeypress.F9, ' ',
1440 alt, ctrl, shift);
1441 break;
1442 case KeyEvent.VK_F10:
1443 keypress = new TKeypress(true, TKeypress.F10, ' ',
1444 alt, ctrl, shift);
1445 break;
1446 case KeyEvent.VK_F11:
1447 keypress = new TKeypress(true, TKeypress.F11, ' ',
1448 alt, ctrl, shift);
1449 break;
1450 case KeyEvent.VK_F12:
1451 keypress = new TKeypress(true, TKeypress.F12, ' ',
1452 alt, ctrl, shift);
1453 break;
1454 case KeyEvent.VK_HOME:
1455 keypress = new TKeypress(true, TKeypress.HOME, ' ',
1456 alt, ctrl, shift);
1457 break;
1458 case KeyEvent.VK_END:
1459 keypress = new TKeypress(true, TKeypress.END, ' ',
1460 alt, ctrl, shift);
1461 break;
1462 case KeyEvent.VK_PAGE_UP:
1463 keypress = new TKeypress(true, TKeypress.PGUP, ' ',
1464 alt, ctrl, shift);
1465 break;
1466 case KeyEvent.VK_PAGE_DOWN:
1467 keypress = new TKeypress(true, TKeypress.PGDN, ' ',
1468 alt, ctrl, shift);
1469 break;
1470 case KeyEvent.VK_INSERT:
1471 keypress = new TKeypress(true, TKeypress.INS, ' ',
1472 alt, ctrl, shift);
1473 break;
1474 case KeyEvent.VK_DELETE:
1475 keypress = new TKeypress(true, TKeypress.DEL, ' ',
1476 alt, ctrl, shift);
1477 break;
1478 case KeyEvent.VK_RIGHT:
1479 keypress = new TKeypress(true, TKeypress.RIGHT, ' ',
1480 alt, ctrl, shift);
1481 break;
1482 case KeyEvent.VK_LEFT:
1483 keypress = new TKeypress(true, TKeypress.LEFT, ' ',
1484 alt, ctrl, shift);
1485 break;
1486 case KeyEvent.VK_UP:
1487 keypress = new TKeypress(true, TKeypress.UP, ' ',
1488 alt, ctrl, shift);
1489 break;
1490 case KeyEvent.VK_DOWN:
1491 keypress = new TKeypress(true, TKeypress.DOWN, ' ',
1492 alt, ctrl, shift);
1493 break;
1494 case KeyEvent.VK_TAB:
1495 // Special case: distinguish TAB vs BTAB
1496 if (shift) {
1497 keypress = kbShiftTab;
1498 } else {
1499 keypress = kbTab;
1500 }
1501 break;
1502 case KeyEvent.VK_ENTER:
1503 keypress = new TKeypress(true, TKeypress.ENTER, ' ',
1504 alt, ctrl, shift);
1505 break;
1506 case KeyEvent.VK_ESCAPE:
1507 keypress = new TKeypress(true, TKeypress.ESC, ' ',
1508 alt, ctrl, shift);
1509 break;
1510 case KeyEvent.VK_BACK_SPACE:
1511 // Special case: return it as kbBackspace (Ctrl-H)
1512 keypress = new TKeypress(false, 0, 'H', false, true, false);
1513 break;
1514 default:
1515 // Unsupported, ignore
1516 return;
1517 }
1518 }
1519
1520 if (keypress == null) {
1521 switch (ch) {
1522 case 0x08:
1523 keypress = kbBackspace;
1524 break;
1525 case 0x0A:
1526 keypress = kbEnter;
1527 break;
1528 case 0x1B:
1529 keypress = kbEsc;
1530 break;
1531 case 0x0D:
1532 keypress = kbEnter;
1533 break;
1534 case 0x09:
1535 if (shift) {
1536 keypress = kbShiftTab;
1537 } else {
1538 keypress = kbTab;
1539 }
1540 break;
1541 case 0x7F:
1542 keypress = kbDel;
1543 break;
1544 default:
1545 if (!alt && ctrl && !shift) {
1546 ch = KeyEvent.getKeyText(key.getKeyCode()).charAt(0);
1547 }
1548 // Not a special key, put it together
1549 keypress = new TKeypress(false, 0, ch, alt, ctrl, shift);
1550 }
1551 }
1552
1553 // Save it and we are done.
1554 synchronized (eventQueue) {
1555 eventQueue.add(new TKeypressEvent(keypress));
e8a11f98 1556 resetBlinkTimer();
42873e30 1557 }
3e074355
KL
1558 if (listener != null) {
1559 synchronized (listener) {
1560 listener.notifyAll();
1561 }
42873e30
KL
1562 }
1563 }
1564
1565 /**
1566 * Pass window events into the event queue.
1567 *
1568 * @param event window event received
1569 */
1570 public void windowActivated(final WindowEvent event) {
1571 // Force a total repaint
1572 synchronized (this) {
1573 clearPhysical();
1574 }
1575 }
1576
1577 /**
1578 * Pass window events into the event queue.
1579 *
1580 * @param event window event received
1581 */
1582 public void windowClosed(final WindowEvent event) {
1583 // Ignore
1584 }
1585
1586 /**
1587 * Pass window events into the event queue.
1588 *
1589 * @param event window event received
1590 */
1591 public void windowClosing(final WindowEvent event) {
1592 // Drop a cmAbort and walk away
1593 synchronized (eventQueue) {
1594 eventQueue.add(new TCommandEvent(cmAbort));
e8a11f98 1595 resetBlinkTimer();
42873e30 1596 }
3e074355
KL
1597 if (listener != null) {
1598 synchronized (listener) {
1599 listener.notifyAll();
1600 }
42873e30
KL
1601 }
1602 }
1603
1604 /**
1605 * Pass window events into the event queue.
1606 *
1607 * @param event window event received
1608 */
1609 public void windowDeactivated(final WindowEvent event) {
1610 // Ignore
1611 }
1612
1613 /**
1614 * Pass window events into the event queue.
1615 *
1616 * @param event window event received
1617 */
1618 public void windowDeiconified(final WindowEvent event) {
1619 // Ignore
1620 }
1621
1622 /**
1623 * Pass window events into the event queue.
1624 *
1625 * @param event window event received
1626 */
1627 public void windowIconified(final WindowEvent event) {
1628 // Ignore
1629 }
1630
1631 /**
1632 * Pass window events into the event queue.
1633 *
1634 * @param event window event received
1635 */
1636 public void windowOpened(final WindowEvent event) {
1637 // Ignore
1638 }
1639
1640 /**
1641 * Pass component events into the event queue.
1642 *
1643 * @param event component event received
1644 */
1645 public void componentHidden(final ComponentEvent event) {
1646 // Ignore
1647 }
1648
1649 /**
1650 * Pass component events into the event queue.
1651 *
1652 * @param event component event received
1653 */
1654 public void componentShown(final ComponentEvent event) {
1655 // Ignore
1656 }
1657
1658 /**
1659 * Pass component events into the event queue.
1660 *
1661 * @param event component event received
1662 */
1663 public void componentMoved(final ComponentEvent event) {
1664 // Ignore
1665 }
1666
1667 /**
1668 * Pass component events into the event queue.
1669 *
1670 * @param event component event received
1671 */
1672 public void componentResized(final ComponentEvent event) {
1673 if (gotFontDimensions == false) {
1674 // We are still waiting to get font information. Don't pass a
1675 // resize event up.
1676 // System.err.println("size " + swing.getComponent().getSize());
1677 return;
1678 }
1679
1680 // Drop a new TResizeEvent into the queue
1681 sessionInfo.queryWindowSize();
1682 synchronized (eventQueue) {
1683 TResizeEvent windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN,
1684 sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight());
1685 eventQueue.add(windowResize);
e8a11f98 1686 resetBlinkTimer();
42873e30 1687 }
3e074355
KL
1688 if (listener != null) {
1689 synchronized (listener) {
1690 listener.notifyAll();
1691 }
42873e30
KL
1692 }
1693 }
1694
1695 /**
1696 * Pass mouse events into the event queue.
1697 *
1698 * @param mouse mouse event received
1699 */
1700 public void mouseDragged(final MouseEvent mouse) {
1701 int modifiers = mouse.getModifiersEx();
1702 boolean eventMouse1 = false;
1703 boolean eventMouse2 = false;
1704 boolean eventMouse3 = false;
1705 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
1706 eventMouse1 = true;
1707 }
1708 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
1709 eventMouse2 = true;
1710 }
1711 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
1712 eventMouse3 = true;
1713 }
1714 mouse1 = eventMouse1;
1715 mouse2 = eventMouse2;
1716 mouse3 = eventMouse3;
1717 int x = textColumn(mouse.getX());
1718 int y = textRow(mouse.getY());
1719
1720 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION,
1721 x, y, x, y, mouse1, mouse2, mouse3, false, false);
1722
1723 synchronized (eventQueue) {
1724 eventQueue.add(mouseEvent);
e8a11f98 1725 resetBlinkTimer();
42873e30 1726 }
3e074355
KL
1727 if (listener != null) {
1728 synchronized (listener) {
1729 listener.notifyAll();
1730 }
42873e30
KL
1731 }
1732 }
1733
1734 /**
1735 * Pass mouse events into the event queue.
1736 *
1737 * @param mouse mouse event received
1738 */
1739 public void mouseMoved(final MouseEvent mouse) {
1740 int x = textColumn(mouse.getX());
1741 int y = textRow(mouse.getY());
1742 if ((x == oldMouseX) && (y == oldMouseY)) {
1743 // Bail out, we've moved some pixels but not a whole text cell.
1744 return;
1745 }
1746 oldMouseX = x;
1747 oldMouseY = y;
1748
1749 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION,
1750 x, y, x, y, mouse1, mouse2, mouse3, false, false);
1751
1752 synchronized (eventQueue) {
1753 eventQueue.add(mouseEvent);
e8a11f98 1754 resetBlinkTimer();
42873e30 1755 }
3e074355
KL
1756 if (listener != null) {
1757 synchronized (listener) {
1758 listener.notifyAll();
1759 }
42873e30
KL
1760 }
1761 }
1762
1763 /**
1764 * Pass mouse events into the event queue.
1765 *
1766 * @param mouse mouse event received
1767 */
1768 public void mouseClicked(final MouseEvent mouse) {
1769 // Ignore
1770 }
1771
1772 /**
1773 * Pass mouse events into the event queue.
1774 *
1775 * @param mouse mouse event received
1776 */
1777 public void mouseEntered(final MouseEvent mouse) {
1778 // Ignore
1779 }
1780
1781 /**
1782 * Pass mouse events into the event queue.
1783 *
1784 * @param mouse mouse event received
1785 */
1786 public void mouseExited(final MouseEvent mouse) {
1787 // Ignore
1788 }
1789
1790 /**
1791 * Pass mouse events into the event queue.
1792 *
1793 * @param mouse mouse event received
1794 */
1795 public void mousePressed(final MouseEvent mouse) {
1796 int modifiers = mouse.getModifiersEx();
1797 boolean eventMouse1 = false;
1798 boolean eventMouse2 = false;
1799 boolean eventMouse3 = false;
1800 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
1801 eventMouse1 = true;
1802 }
1803 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
1804 eventMouse2 = true;
1805 }
1806 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
1807 eventMouse3 = true;
1808 }
1809 mouse1 = eventMouse1;
1810 mouse2 = eventMouse2;
1811 mouse3 = eventMouse3;
1812 int x = textColumn(mouse.getX());
1813 int y = textRow(mouse.getY());
1814
1815 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN,
1816 x, y, x, y, mouse1, mouse2, mouse3, false, false);
1817
1818 synchronized (eventQueue) {
1819 eventQueue.add(mouseEvent);
e8a11f98 1820 resetBlinkTimer();
42873e30 1821 }
3e074355
KL
1822 if (listener != null) {
1823 synchronized (listener) {
1824 listener.notifyAll();
1825 }
42873e30
KL
1826 }
1827 }
1828
1829 /**
1830 * Pass mouse events into the event queue.
1831 *
1832 * @param mouse mouse event received
1833 */
1834 public void mouseReleased(final MouseEvent mouse) {
1835 int modifiers = mouse.getModifiersEx();
1836 boolean eventMouse1 = false;
1837 boolean eventMouse2 = false;
1838 boolean eventMouse3 = false;
1839 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
1840 eventMouse1 = true;
1841 }
1842 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
1843 eventMouse2 = true;
1844 }
1845 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
1846 eventMouse3 = true;
1847 }
1848 if (mouse1) {
1849 mouse1 = false;
1850 eventMouse1 = true;
1851 }
1852 if (mouse2) {
1853 mouse2 = false;
1854 eventMouse2 = true;
1855 }
1856 if (mouse3) {
1857 mouse3 = false;
1858 eventMouse3 = true;
1859 }
1860 int x = textColumn(mouse.getX());
1861 int y = textRow(mouse.getY());
1862
1863 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_UP,
1864 x, y, x, y, eventMouse1, eventMouse2, eventMouse3, false, false);
1865
1866 synchronized (eventQueue) {
1867 eventQueue.add(mouseEvent);
e8a11f98 1868 resetBlinkTimer();
42873e30 1869 }
3e074355
KL
1870 if (listener != null) {
1871 synchronized (listener) {
1872 listener.notifyAll();
1873 }
42873e30
KL
1874 }
1875 }
1876
1877 /**
1878 * Pass mouse events into the event queue.
1879 *
1880 * @param mouse mouse event received
1881 */
1882 public void mouseWheelMoved(final MouseWheelEvent mouse) {
1883 int modifiers = mouse.getModifiersEx();
1884 boolean eventMouse1 = false;
1885 boolean eventMouse2 = false;
1886 boolean eventMouse3 = false;
1887 boolean mouseWheelUp = false;
1888 boolean mouseWheelDown = false;
1889 if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
1890 eventMouse1 = true;
1891 }
1892 if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
1893 eventMouse2 = true;
1894 }
1895 if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
1896 eventMouse3 = true;
1897 }
1898 mouse1 = eventMouse1;
1899 mouse2 = eventMouse2;
1900 mouse3 = eventMouse3;
1901 int x = textColumn(mouse.getX());
1902 int y = textRow(mouse.getY());
1903 if (mouse.getWheelRotation() > 0) {
1904 mouseWheelDown = true;
1905 }
1906 if (mouse.getWheelRotation() < 0) {
1907 mouseWheelUp = true;
1908 }
1909
1910 TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN,
1911 x, y, x, y, mouse1, mouse2, mouse3, mouseWheelUp, mouseWheelDown);
1912
1913 synchronized (eventQueue) {
1914 eventQueue.add(mouseEvent);
e8a11f98 1915 resetBlinkTimer();
42873e30 1916 }
3e074355
KL
1917 if (listener != null) {
1918 synchronized (listener) {
1919 listener.notifyAll();
1920 }
42873e30
KL
1921 }
1922 }
1923
1924}