Fix NPE
[nikiroo-utils.git] / src / jexer / io / SwingScreen.java
CommitLineData
daa4106c 1/*
1ac2ccb1
KL
2 * Jexer - Java Text User Interface
3 *
e16dda65 4 * The MIT License (MIT)
1ac2ccb1 5 *
a2018e99 6 * Copyright (C) 2017 Kevin Lamonte
1ac2ccb1 7 *
e16dda65
KL
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:
1ac2ccb1 14 *
e16dda65
KL
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
1ac2ccb1 17 *
e16dda65
KL
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.
1ac2ccb1
KL
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29package jexer.io;
30
1ac2ccb1
KL
31import java.awt.Color;
32import java.awt.Cursor;
33import java.awt.Font;
34import java.awt.FontMetrics;
1ac2ccb1 35import java.awt.Graphics;
c8165631 36import java.awt.Graphics2D;
84614868 37import java.awt.Insets;
30bd4abd
KL
38import java.awt.Point;
39import java.awt.Rectangle;
40import java.awt.Toolkit;
1ac2ccb1 41import java.awt.geom.Rectangle2D;
30bd4abd 42import java.awt.image.BufferedImage;
e3dfbd23 43import java.awt.image.BufferStrategy;
84614868 44import java.io.InputStream;
b5f2a6db 45import java.util.Date;
c8165631 46import java.util.HashMap;
aed33687
KL
47import javax.swing.JFrame;
48import javax.swing.SwingUtilities;
1ac2ccb1 49
b5f2a6db
KL
50import jexer.bits.Cell;
51import jexer.bits.CellAttributes;
52import jexer.session.SwingSessionInfo;
53
1ac2ccb1 54/**
a4406f4e 55 * This Screen implementation draws to a Java Swing JFrame.
1ac2ccb1 56 */
a4406f4e 57public final class SwingScreen extends Screen {
1ac2ccb1 58
e3dfbd23 59 /**
1d14ffab 60 * If true, use triple buffering thread.
e3dfbd23 61 */
e685a47d 62 private static boolean tripleBuffer = true;
e3dfbd23 63
b5f2a6db
KL
64 /**
65 * Cursor style to draw.
66 */
67 public enum CursorStyle {
68 /**
69 * Use an underscore for the cursor.
70 */
71 UNDERLINE,
72
73 /**
74 * Use a solid block for the cursor.
75 */
76 BLOCK,
77
78 /**
79 * Use an outlined block for the cursor.
80 */
81 OUTLINE
82 }
83
84614868
KL
84 private static Color MYBLACK;
85 private static Color MYRED;
86 private static Color MYGREEN;
87 private static Color MYYELLOW;
88 private static Color MYBLUE;
89 private static Color MYMAGENTA;
90 private static Color MYCYAN;
91 private static Color MYWHITE;
92
93 private static Color MYBOLD_BLACK;
94 private static Color MYBOLD_RED;
95 private static Color MYBOLD_GREEN;
96 private static Color MYBOLD_YELLOW;
97 private static Color MYBOLD_BLUE;
98 private static Color MYBOLD_MAGENTA;
99 private static Color MYBOLD_CYAN;
100 private static Color MYBOLD_WHITE;
101
102 private static boolean dosColors = false;
103
104 /**
a4406f4e 105 * Setup Swing colors to match DOS color palette.
84614868
KL
106 */
107 private static void setDOSColors() {
108 if (dosColors) {
109 return;
110 }
111 MYBLACK = new Color(0x00, 0x00, 0x00);
112 MYRED = new Color(0xa8, 0x00, 0x00);
113 MYGREEN = new Color(0x00, 0xa8, 0x00);
114 MYYELLOW = new Color(0xa8, 0x54, 0x00);
115 MYBLUE = new Color(0x00, 0x00, 0xa8);
116 MYMAGENTA = new Color(0xa8, 0x00, 0xa8);
117 MYCYAN = new Color(0x00, 0xa8, 0xa8);
118 MYWHITE = new Color(0xa8, 0xa8, 0xa8);
119 MYBOLD_BLACK = new Color(0x54, 0x54, 0x54);
120 MYBOLD_RED = new Color(0xfc, 0x54, 0x54);
121 MYBOLD_GREEN = new Color(0x54, 0xfc, 0x54);
122 MYBOLD_YELLOW = new Color(0xfc, 0xfc, 0x54);
123 MYBOLD_BLUE = new Color(0x54, 0x54, 0xfc);
124 MYBOLD_MAGENTA = new Color(0xfc, 0x54, 0xfc);
125 MYBOLD_CYAN = new Color(0x54, 0xfc, 0xfc);
126 MYBOLD_WHITE = new Color(0xfc, 0xfc, 0xfc);
127
128 dosColors = true;
129 }
130
1ac2ccb1 131 /**
a4406f4e 132 * SwingFrame is our top-level hook into the Swing system.
1ac2ccb1 133 */
a4406f4e 134 class SwingFrame extends JFrame {
1ac2ccb1 135
cf9af8df
KL
136 /**
137 * Serializable version.
138 */
139 private static final long serialVersionUID = 1;
140
84614868
KL
141 /**
142 * The terminus font resource filename.
143 */
144 private static final String FONTFILE = "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf";
145
e3dfbd23 146 /**
1d14ffab 147 * The BufferStrategy object needed for triple-buffering.
e3dfbd23
KL
148 */
149 private BufferStrategy bufferStrategy;
150
c8165631
KL
151 /**
152 * A cache of previously-rendered glyphs for blinking text, when it
153 * is not visible.
154 */
155 private HashMap<Cell, BufferedImage> glyphCacheBlink;
156
157 /**
158 * A cache of previously-rendered glyphs for non-blinking, or
159 * blinking-and-visible, text.
160 */
161 private HashMap<Cell, BufferedImage> glyphCache;
162
1ac2ccb1
KL
163 /**
164 * The TUI Screen data.
165 */
a4406f4e 166 SwingScreen screen;
1ac2ccb1 167
55d2b2c2
KL
168 /**
169 * If true, we were successful getting Terminus.
170 */
171 private boolean gotTerminus = false;
172
1ac2ccb1
KL
173 /**
174 * Width of a character cell.
175 */
176 private int textWidth = 1;
177
178 /**
179 * Height of a character cell.
180 */
181 private int textHeight = 1;
182
84614868
KL
183 /**
184 * Descent of a character cell.
185 */
186 private int maxDescent = 0;
187
847a4bc5
KL
188 /**
189 * System-dependent Y adjustment for text in the character cell.
190 */
191 private int textAdjustY = 0;
192
193 /**
194 * System-dependent X adjustment for text in the character cell.
195 */
196 private int textAdjustX = 0;
197
1ac2ccb1 198 /**
b5f2a6db 199 * Top pixel absolute location.
1ac2ccb1
KL
200 */
201 private int top = 30;
84614868 202
1ac2ccb1 203 /**
b5f2a6db 204 * Left pixel absolute location.
1ac2ccb1
KL
205 */
206 private int left = 30;
84614868 207
b5f2a6db
KL
208 /**
209 * The cursor style to draw.
210 */
211 private CursorStyle cursorStyle = CursorStyle.UNDERLINE;
212
213 /**
214 * The number of millis to wait before switching the blink from
215 * visible to invisible.
216 */
217 private long blinkMillis = 500;
218
219 /**
220 * If true, the cursor should be visible right now based on the blink
221 * time.
222 */
223 private boolean cursorBlinkVisible = true;
224
225 /**
226 * The time that the blink last flipped from visible to invisible or
227 * from invisible to visible.
228 */
229 private long lastBlinkTime = 0;
230
84614868 231 /**
a4406f4e 232 * Convert a CellAttributes foreground color to an Swing Color.
84614868
KL
233 *
234 * @param attr the text attributes
a4406f4e 235 * @return the Swing Color
84614868
KL
236 */
237 private Color attrToForegroundColor(final CellAttributes attr) {
7c870d89 238 if (attr.isBold()) {
84614868
KL
239 if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
240 return MYBOLD_BLACK;
241 } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
242 return MYBOLD_RED;
243 } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
244 return MYBOLD_BLUE;
245 } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
246 return MYBOLD_GREEN;
247 } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
248 return MYBOLD_YELLOW;
249 } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
250 return MYBOLD_CYAN;
251 } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
252 return MYBOLD_MAGENTA;
253 } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
254 return MYBOLD_WHITE;
255 }
256 } else {
257 if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
258 return MYBLACK;
259 } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
260 return MYRED;
261 } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
262 return MYBLUE;
263 } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
264 return MYGREEN;
265 } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
266 return MYYELLOW;
267 } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
268 return MYCYAN;
269 } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
270 return MYMAGENTA;
271 } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
272 return MYWHITE;
273 }
274 }
c8165631
KL
275 throw new IllegalArgumentException("Invalid color: " +
276 attr.getForeColor().getValue());
84614868
KL
277 }
278
279 /**
a4406f4e 280 * Convert a CellAttributes background color to an Swing Color.
84614868
KL
281 *
282 * @param attr the text attributes
a4406f4e 283 * @return the Swing Color
84614868
KL
284 */
285 private Color attrToBackgroundColor(final CellAttributes attr) {
84614868
KL
286 if (attr.getBackColor().equals(jexer.bits.Color.BLACK)) {
287 return MYBLACK;
288 } else if (attr.getBackColor().equals(jexer.bits.Color.RED)) {
289 return MYRED;
290 } else if (attr.getBackColor().equals(jexer.bits.Color.BLUE)) {
291 return MYBLUE;
292 } else if (attr.getBackColor().equals(jexer.bits.Color.GREEN)) {
293 return MYGREEN;
294 } else if (attr.getBackColor().equals(jexer.bits.Color.YELLOW)) {
295 return MYYELLOW;
296 } else if (attr.getBackColor().equals(jexer.bits.Color.CYAN)) {
297 return MYCYAN;
298 } else if (attr.getBackColor().equals(jexer.bits.Color.MAGENTA)) {
299 return MYMAGENTA;
300 } else if (attr.getBackColor().equals(jexer.bits.Color.WHITE)) {
301 return MYWHITE;
302 }
c8165631
KL
303 throw new IllegalArgumentException("Invalid color: " +
304 attr.getBackColor().getValue());
84614868
KL
305 }
306
1ac2ccb1
KL
307 /**
308 * Public constructor.
84614868
KL
309 *
310 * @param screen the Screen that Backend talks to
1ac2ccb1 311 */
a4406f4e 312 public SwingFrame(final SwingScreen screen) {
84614868
KL
313 this.screen = screen;
314 setDOSColors();
315
b5f2a6db 316 // Figure out my cursor style
c8165631
KL
317 String cursorStyleString = System.getProperty(
318 "jexer.Swing.cursorStyle", "underline").toLowerCase();
b5f2a6db
KL
319
320 if (cursorStyleString.equals("underline")) {
321 cursorStyle = CursorStyle.UNDERLINE;
322 } else if (cursorStyleString.equals("outline")) {
323 cursorStyle = CursorStyle.OUTLINE;
324 } else if (cursorStyleString.equals("block")) {
325 cursorStyle = CursorStyle.BLOCK;
326 }
327
0ab16dbb
KL
328 if (System.getProperty("jexer.Swing.tripleBuffer") != null) {
329 if (System.getProperty("jexer.Swing.tripleBuffer").
330 equals("false")) {
e685a47d 331
0ab16dbb
KL
332 SwingScreen.tripleBuffer = false;
333 }
e685a47d
KL
334 }
335
1ac2ccb1 336 setTitle("Jexer Application");
aed33687 337 setBackground(Color.black);
84614868
KL
338
339 try {
30bd4abd 340 // Always try to use Terminus, the one decent font.
c8165631
KL
341 ClassLoader loader = Thread.currentThread().
342 getContextClassLoader();
84614868
KL
343 InputStream in = loader.getResourceAsStream(FONTFILE);
344 Font terminusRoot = Font.createFont(Font.TRUETYPE_FONT, in);
55d2b2c2 345 Font terminus = terminusRoot.deriveFont(Font.PLAIN, 20);
84614868 346 setFont(terminus);
55d2b2c2 347 gotTerminus = true;
84614868
KL
348 } catch (Exception e) {
349 e.printStackTrace();
350 // setFont(new Font("Liberation Mono", Font.PLAIN, 24));
351 setFont(new Font(Font.MONOSPACED, Font.PLAIN, 24));
352 }
aed33687 353 pack();
30bd4abd
KL
354
355 // Kill the X11 cursor
356 // Transparent 16 x 16 pixel cursor image.
357 BufferedImage cursorImg = new BufferedImage(16, 16,
358 BufferedImage.TYPE_INT_ARGB);
30bd4abd
KL
359 // Create a new blank cursor.
360 Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(
361 cursorImg, new Point(0, 0), "blank cursor");
362 setCursor(blankCursor);
091d8a06
KL
363
364 // Be capable of seeing Tab / Shift-Tab
365 setFocusTraversalKeysEnabled(false);
366
367 // Save the text cell width/height
bd8d51fa 368 getFontDimensions();
e3dfbd23 369
c8165631
KL
370 // Cache glyphs as they are rendered
371 glyphCacheBlink = new HashMap<Cell, BufferedImage>();
372 glyphCache = new HashMap<Cell, BufferedImage>();
373
1d14ffab
KL
374 // Setup triple-buffering
375 if (SwingScreen.tripleBuffer) {
e3dfbd23 376 setIgnoreRepaint(true);
1d14ffab 377 createBufferStrategy(3);
e3dfbd23
KL
378 bufferStrategy = getBufferStrategy();
379 }
1ac2ccb1
KL
380 }
381
382 /**
bd8d51fa 383 * Figure out my font dimensions.
1ac2ccb1 384 */
bd8d51fa 385 private void getFontDimensions() {
84614868
KL
386 Graphics gr = getGraphics();
387 FontMetrics fm = gr.getFontMetrics();
388 maxDescent = fm.getMaxDescent();
389 Rectangle2D bounds = fm.getMaxCharBounds(gr);
390 int leading = fm.getLeading();
391 textWidth = (int)Math.round(bounds.getWidth());
55d2b2c2
KL
392 // textHeight = (int)Math.round(bounds.getHeight()) - maxDescent;
393
394 // This produces the same number, but works better for ugly
84614868
KL
395 // monospace.
396 textHeight = fm.getMaxAscent() + maxDescent - leading;
847a4bc5 397
55d2b2c2
KL
398 if (gotTerminus == true) {
399 textHeight++;
400 }
401
847a4bc5
KL
402 if (System.getProperty("os.name").startsWith("Windows")) {
403 textAdjustY = -1;
404 textAdjustX = 0;
405 }
55d2b2c2
KL
406 if (System.getProperty("os.name").startsWith("Mac")) {
407 textAdjustY = -1;
408 textAdjustX = 0;
409 }
bd8d51fa 410 }
84614868 411
bd8d51fa
KL
412 /**
413 * Resize to font dimensions.
414 */
415 public void resizeToScreen() {
84614868
KL
416 // Figure out the thickness of borders and use that to set the
417 // final size.
418 Insets insets = getInsets();
419 left = insets.left;
420 top = insets.top;
421
422 setSize(textWidth * screen.width + insets.left + insets.right,
423 textHeight * screen.height + insets.top + insets.bottom);
1ac2ccb1
KL
424 }
425
30bd4abd
KL
426 /**
427 * Update redraws the whole screen.
428 *
a4406f4e 429 * @param gr the Swing Graphics context
30bd4abd
KL
430 */
431 @Override
432 public void update(final Graphics gr) {
433 // The default update clears the area. Don't do that, instead
434 // just paint it directly.
435 paint(gr);
436 }
437
c8165631
KL
438 /**
439 * Draw one glyph to the screen.
440 *
441 * @param gr the Swing Graphics context
442 * @param cell the Cell to draw
443 * @param xPixel the x-coordinate to render to. 0 means the
444 * left-most pixel column.
445 * @param yPixel the y-coordinate to render to. 0 means the top-most
446 * pixel row.
447 */
448 private void drawGlyph(final Graphics gr, final Cell cell,
449 final int xPixel, final int yPixel) {
450
451 BufferedImage image = null;
452 if (cell.isBlink() && !cursorBlinkVisible) {
453 image = glyphCacheBlink.get(cell);
454 } else {
455 image = glyphCache.get(cell);
456 }
457 if (image != null) {
458 gr.drawImage(image, xPixel, yPixel, this);
459 return;
460 }
461
462 // Generate glyph and draw it.
e685a47d
KL
463 Graphics2D gr2 = null;
464 int gr2x = xPixel;
465 int gr2y = yPixel;
466 if (tripleBuffer) {
467 image = new BufferedImage(textWidth, textHeight,
468 BufferedImage.TYPE_INT_ARGB);
469 gr2 = image.createGraphics();
470 gr2.setFont(getFont());
471 gr2x = 0;
472 gr2y = 0;
473 } else {
474 gr2 = (Graphics2D) gr;
475 }
c8165631
KL
476
477 Cell cellColor = new Cell();
478 cellColor.setTo(cell);
479
480 // Check for reverse
481 if (cell.isReverse()) {
482 cellColor.setForeColor(cell.getBackColor());
483 cellColor.setBackColor(cell.getForeColor());
484 }
485
486 // Draw the background rectangle, then the foreground character.
487 gr2.setColor(attrToBackgroundColor(cellColor));
e685a47d 488 gr2.fillRect(gr2x, gr2y, textWidth, textHeight);
c8165631
KL
489
490 // Handle blink and underline
491 if (!cell.isBlink()
492 || (cell.isBlink() && cursorBlinkVisible)
493 ) {
494 gr2.setColor(attrToForegroundColor(cellColor));
495 char [] chars = new char[1];
496 chars[0] = cell.getChar();
e685a47d
KL
497 gr2.drawChars(chars, 0, 1, gr2x + textAdjustX,
498 gr2y + textHeight - maxDescent + textAdjustY);
c8165631
KL
499
500 if (cell.isUnderline()) {
e685a47d 501 gr2.fillRect(gr2x, gr2y + textHeight - 2, textWidth, 2);
c8165631
KL
502 }
503 }
c8165631 504
e685a47d
KL
505 if (tripleBuffer) {
506 gr2.dispose();
507
508 // We need a new key that will not be mutated by
509 // invertCell().
510 Cell key = new Cell();
511 key.setTo(cell);
512 if (cell.isBlink() && !cursorBlinkVisible) {
513 glyphCacheBlink.put(key, image);
514 } else {
515 glyphCache.put(key, image);
516 }
517
518 gr.drawImage(image, xPixel, yPixel, this);
c8165631
KL
519 }
520
c8165631
KL
521 }
522
523 /**
524 * Check if the cursor is visible, and if so draw it.
525 *
526 * @param gr the Swing Graphics context
527 */
528 private void drawCursor(final Graphics gr) {
529
530 if (cursorVisible
531 && (cursorY <= screen.height - 1)
532 && (cursorX <= screen.width - 1)
533 && cursorBlinkVisible
534 ) {
535 int xPixel = cursorX * textWidth + left;
536 int yPixel = cursorY * textHeight + top;
537 Cell lCell = screen.logical[cursorX][cursorY];
538 gr.setColor(attrToForegroundColor(lCell));
539 switch (cursorStyle) {
540 default:
541 // Fall through...
542 case UNDERLINE:
543 gr.fillRect(xPixel, yPixel + textHeight - 2, textWidth, 2);
544 break;
545 case BLOCK:
546 gr.fillRect(xPixel, yPixel, textWidth, textHeight);
547 break;
548 case OUTLINE:
549 gr.drawRect(xPixel, yPixel, textWidth - 1, textHeight - 1);
550 break;
551 }
552 }
553 }
554
1ac2ccb1
KL
555 /**
556 * Paint redraws the whole screen.
557 *
a4406f4e 558 * @param gr the Swing Graphics context
1ac2ccb1
KL
559 */
560 @Override
84614868 561 public void paint(final Graphics gr) {
87a17f3c
KL
562 // Do nothing until the screen reference has been set.
563 if (screen == null) {
564 return;
565 }
566 if (screen.frame == null) {
567 return;
568 }
569
b5f2a6db
KL
570 // See if it is time to flip the blink time.
571 long nowTime = (new Date()).getTime();
572 if (nowTime > blinkMillis + lastBlinkTime) {
573 lastBlinkTime = nowTime;
574 cursorBlinkVisible = !cursorBlinkVisible;
575 }
576
87a17f3c
KL
577 int xCellMin = 0;
578 int xCellMax = screen.width;
579 int yCellMin = 0;
580 int yCellMax = screen.height;
581
30bd4abd 582 Rectangle bounds = gr.getClipBounds();
87a17f3c
KL
583 if (bounds != null) {
584 // Only update what is in the bounds
585 xCellMin = screen.textColumn(bounds.x);
bd8d51fa 586 xCellMax = screen.textColumn(bounds.x + bounds.width);
87a17f3c
KL
587 if (xCellMax > screen.width) {
588 xCellMax = screen.width;
589 }
590 if (xCellMin >= xCellMax) {
591 xCellMin = xCellMax - 2;
592 }
593 if (xCellMin < 0) {
594 xCellMin = 0;
595 }
596 yCellMin = screen.textRow(bounds.y);
bd8d51fa 597 yCellMax = screen.textRow(bounds.y + bounds.height);
87a17f3c
KL
598 if (yCellMax > screen.height) {
599 yCellMax = screen.height;
600 }
601 if (yCellMin >= yCellMax) {
602 yCellMin = yCellMax - 2;
603 }
604 if (yCellMin < 0) {
605 yCellMin = 0;
606 }
bd8d51fa
KL
607 } else {
608 // We need a total repaint
609 reallyCleared = true;
87a17f3c 610 }
1ac2ccb1 611
87a17f3c
KL
612 // Prevent updates to the screen's data from the TApplication
613 // threads.
614 synchronized (screen) {
615 /*
616 System.err.printf("bounds %s X %d %d Y %d %d\n",
617 bounds, xCellMin, xCellMax, yCellMin, yCellMax);
618 */
619
620 for (int y = yCellMin; y < yCellMax; y++) {
621 for (int x = xCellMin; x < xCellMax; x++) {
622
623 int xPixel = x * textWidth + left;
624 int yPixel = y * textHeight + top;
625
626 Cell lCell = screen.logical[x][y];
627 Cell pCell = screen.physical[x][y];
628
55b4f29b
KL
629 if (!lCell.equals(pCell)
630 || lCell.isBlink()
631 || reallyCleared) {
e3dfbd23 632
c8165631 633 drawGlyph(gr, lCell, xPixel, yPixel);
87a17f3c
KL
634
635 // Physical is always updated
636 physical[x][y].setTo(lCell);
30bd4abd
KL
637 }
638 }
87a17f3c 639 }
c8165631 640 drawCursor(gr);
30bd4abd 641
87a17f3c
KL
642 dirty = false;
643 reallyCleared = false;
644 } // synchronized (screen)
1ac2ccb1 645 }
bd8d51fa 646
a4406f4e 647 } // class SwingFrame
1ac2ccb1
KL
648
649 /**
a4406f4e 650 * The raw Swing JFrame. Note package private access.
1ac2ccb1 651 */
a4406f4e 652 SwingFrame frame;
1ac2ccb1 653
e3dfbd23
KL
654 /**
655 * Restore terminal to normal state.
656 */
657 public void shutdown() {
658 frame.dispose();
659 }
660
1ac2ccb1
KL
661 /**
662 * Public constructor.
663 */
a4406f4e 664 public SwingScreen() {
aed33687
KL
665 try {
666 SwingUtilities.invokeAndWait(new Runnable() {
667 public void run() {
a4406f4e
KL
668 SwingScreen.this.frame = new SwingFrame(SwingScreen.this);
669 SwingScreen.this.sessionInfo =
670 new SwingSessionInfo(SwingScreen.this.frame,
bd8d51fa
KL
671 frame.textWidth,
672 frame.textHeight);
673
a4406f4e 674 SwingScreen.this.setDimensions(sessionInfo.getWindowWidth(),
bd8d51fa
KL
675 sessionInfo.getWindowHeight());
676
a4406f4e
KL
677 SwingScreen.this.frame.resizeToScreen();
678 SwingScreen.this.frame.setVisible(true);
aed33687 679 }
329fd62e 680 });
aed33687
KL
681 } catch (Exception e) {
682 e.printStackTrace();
683 }
1ac2ccb1
KL
684 }
685
bd8d51fa
KL
686 /**
687 * The sessionInfo.
688 */
a4406f4e 689 private SwingSessionInfo sessionInfo;
bd8d51fa 690
30bd4abd 691 /**
a4406f4e 692 * Create the SwingSessionInfo. Note package private access.
30bd4abd
KL
693 *
694 * @return the sessionInfo
695 */
a4406f4e 696 SwingSessionInfo getSessionInfo() {
30bd4abd
KL
697 return sessionInfo;
698 }
699
1ac2ccb1
KL
700 /**
701 * Push the logical screen to the physical device.
702 */
703 @Override
704 public void flushPhysical() {
87a17f3c 705
1d14ffab
KL
706 /*
707 System.err.printf("flushPhysical(): reallyCleared %s dirty %s\n",
708 reallyCleared, dirty);
709 */
710
711 // If reallyCleared is set, we have to draw everything.
712 if ((frame.bufferStrategy != null) && (reallyCleared == true)) {
713 // Triple-buffering: we have to redraw everything on this thread.
714 Graphics gr = frame.bufferStrategy.getDrawGraphics();
715 frame.paint(gr);
716 gr.dispose();
717 frame.bufferStrategy.show();
718 // sync() doesn't seem to help the tearing for me.
719 // Toolkit.getDefaultToolkit().sync();
720 return;
721 } else if ((frame.bufferStrategy == null) && (reallyCleared == true)) {
722 // Repaint everything on the Swing thread.
723 frame.repaint();
87a17f3c
KL
724 return;
725 }
726
727 // Do nothing if nothing happened.
728 if (!dirty) {
729 return;
730 }
731
c8165631
KL
732 if (frame.bufferStrategy != null) {
733 // See if it is time to flip the blink time.
734 long nowTime = (new Date()).getTime();
735 if (nowTime > frame.blinkMillis + frame.lastBlinkTime) {
736 frame.lastBlinkTime = nowTime;
737 frame.cursorBlinkVisible = !frame.cursorBlinkVisible;
738 }
739
740 Graphics gr = frame.bufferStrategy.getDrawGraphics();
741
742 synchronized (this) {
743 for (int y = 0; y < height; y++) {
744 for (int x = 0; x < width; x++) {
745 Cell lCell = logical[x][y];
746 Cell pCell = physical[x][y];
747
748 int xPixel = x * frame.textWidth + frame.left;
749 int yPixel = y * frame.textHeight + frame.top;
750
751 if (!lCell.equals(pCell)
752 || ((x == cursorX)
753 && (y == cursorY)
754 && cursorVisible)
755 || (lCell.isBlink())
756 ) {
757 frame.drawGlyph(gr, lCell, xPixel, yPixel);
758 physical[x][y].setTo(lCell);
759 }
760 }
761 }
762 frame.drawCursor(gr);
763 } // synchronized (this)
764
765 gr.dispose();
766 frame.bufferStrategy.show();
767 // sync() doesn't seem to help the tearing for me.
768 // Toolkit.getDefaultToolkit().sync();
769 return;
770 }
771
772 // Swing thread version: request a repaint, but limit it to the area
773 // that has changed.
87a17f3c 774
30bd4abd
KL
775 // Find the minimum-size damaged region.
776 int xMin = frame.getWidth();
777 int xMax = 0;
778 int yMin = frame.getHeight();
779 int yMax = 0;
30bd4abd 780
87a17f3c
KL
781 synchronized (this) {
782 for (int y = 0; y < height; y++) {
783 for (int x = 0; x < width; x++) {
784 Cell lCell = logical[x][y];
785 Cell pCell = physical[x][y];
786
787 int xPixel = x * frame.textWidth + frame.left;
788 int yPixel = y * frame.textHeight + frame.top;
789
790 if (!lCell.equals(pCell)
791 || ((x == cursorX)
792 && (y == cursorY)
793 && cursorVisible)
55b4f29b 794 || lCell.isBlink()
87a17f3c
KL
795 ) {
796 if (xPixel < xMin) {
797 xMin = xPixel;
798 }
799 if (xPixel + frame.textWidth > xMax) {
800 xMax = xPixel + frame.textWidth;
801 }
802 if (yPixel < yMin) {
803 yMin = yPixel;
804 }
805 if (yPixel + frame.textHeight > yMax) {
806 yMax = yPixel + frame.textHeight;
807 }
30bd4abd
KL
808 }
809 }
810 }
811 }
87a17f3c
KL
812 if (xMin + frame.textWidth >= xMax) {
813 xMax += frame.textWidth;
814 }
815 if (yMin + frame.textHeight >= yMax) {
816 yMax += frame.textHeight;
817 }
30bd4abd 818
87a17f3c 819 // Repaint the desired area
1d14ffab
KL
820 /*
821 System.err.printf("REPAINT X %d %d Y %d %d\n", xMin, xMax,
822 yMin, yMax);
823 */
824 if (frame.bufferStrategy != null) {
c8165631
KL
825 // This path should never be taken, but is left here for
826 // completeness.
e3dfbd23
KL
827 Graphics gr = frame.bufferStrategy.getDrawGraphics();
828 Rectangle bounds = new Rectangle(xMin, yMin, xMax - xMin,
829 yMax - yMin);
830 gr.setClip(bounds);
831 frame.paint(gr);
832 gr.dispose();
833 frame.bufferStrategy.show();
1d14ffab
KL
834 // sync() doesn't seem to help the tearing for me.
835 // Toolkit.getDefaultToolkit().sync();
e3dfbd23 836 } else {
c8165631 837 // Repaint on the Swing thread.
e3dfbd23
KL
838 frame.repaint(xMin, yMin, xMax - xMin, yMax - yMin);
839 }
1ac2ccb1 840 }
30bd4abd
KL
841
842 /**
843 * Put the cursor at (x,y).
844 *
845 * @param visible if true, the cursor should be visible
846 * @param x column coordinate to put the cursor on
847 * @param y row coordinate to put the cursor on
848 */
849 @Override
850 public void putCursor(final boolean visible, final int x, final int y) {
b5f2a6db
KL
851
852 if ((visible == cursorVisible) && ((x == cursorX) && (y == cursorY))) {
853 // See if it is time to flip the blink time.
854 long nowTime = (new Date()).getTime();
855 if (nowTime < frame.blinkMillis + frame.lastBlinkTime) {
856 // Nothing has changed, so don't do anything.
857 return;
858 }
859 }
860
861 if (cursorVisible
30bd4abd
KL
862 && (cursorY <= height - 1)
863 && (cursorX <= width - 1)
864 ) {
865 // Make the current cursor position dirty
87a17f3c 866 if (physical[cursorX][cursorY].getChar() == 'Q') {
30bd4abd
KL
867 physical[cursorX][cursorY].setChar('X');
868 } else {
87a17f3c 869 physical[cursorX][cursorY].setChar('Q');
30bd4abd
KL
870 }
871 }
872
873 super.putCursor(visible, x, y);
874 }
875
87a17f3c
KL
876 /**
877 * Convert pixel column position to text cell column position.
878 *
879 * @param x pixel column position
880 * @return text cell column position
881 */
882 public int textColumn(final int x) {
883 return ((x - frame.left) / frame.textWidth);
884 }
885
886 /**
887 * Convert pixel row position to text cell row position.
888 *
889 * @param y pixel row position
890 * @return text cell row position
891 */
892 public int textRow(final int y) {
893 return ((y - frame.top) / frame.textHeight);
894 }
895
55d2b2c2
KL
896 /**
897 * Set the window title.
898 *
899 * @param title the new title
900 */
901 public void setTitle(final String title) {
902 frame.setTitle(title);
903 }
904
1ac2ccb1 905}