2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2017 Kevin Lamonte
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:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
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.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
31 import java
.awt
.Color
;
32 import java
.awt
.Cursor
;
34 import java
.awt
.FontMetrics
;
35 import java
.awt
.Graphics
;
36 import java
.awt
.Graphics2D
;
37 import java
.awt
.Insets
;
38 import java
.awt
.Point
;
39 import java
.awt
.Rectangle
;
40 import java
.awt
.Toolkit
;
41 import java
.awt
.geom
.Rectangle2D
;
42 import java
.awt
.image
.BufferedImage
;
43 import java
.awt
.image
.BufferStrategy
;
44 import java
.io
.InputStream
;
45 import java
.util
.Date
;
46 import java
.util
.HashMap
;
47 import javax
.swing
.JFrame
;
48 import javax
.swing
.SwingUtilities
;
50 import jexer
.bits
.Cell
;
51 import jexer
.bits
.CellAttributes
;
52 import jexer
.session
.SwingSessionInfo
;
55 * This Screen implementation draws to a Java Swing JFrame.
57 public final class SwingScreen
extends Screen
{
60 * If true, use triple buffering thread.
62 private static boolean tripleBuffer
= true;
65 * Cursor style to draw.
67 public enum CursorStyle
{
69 * Use an underscore for the cursor.
74 * Use a solid block for the cursor.
79 * Use an outlined block for the cursor.
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
;
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
;
102 private static boolean dosColors
= false;
105 * Setup Swing colors to match DOS color palette.
107 private static void setDOSColors() {
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);
132 * SwingFrame is our top-level hook into the Swing system.
134 class SwingFrame
extends JFrame
{
137 * Serializable version.
139 private static final long serialVersionUID
= 1;
142 * The terminus font resource filename.
144 private static final String FONTFILE
= "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf";
147 * The BufferStrategy object needed for triple-buffering.
149 private BufferStrategy bufferStrategy
;
152 * A cache of previously-rendered glyphs for blinking text, when it
155 private HashMap
<Cell
, BufferedImage
> glyphCacheBlink
;
158 * A cache of previously-rendered glyphs for non-blinking, or
159 * blinking-and-visible, text.
161 private HashMap
<Cell
, BufferedImage
> glyphCache
;
164 * The TUI Screen data.
169 * If true, we were successful getting Terminus.
171 private boolean gotTerminus
= false;
174 * Width of a character cell.
176 private int textWidth
= 1;
179 * Height of a character cell.
181 private int textHeight
= 1;
184 * Descent of a character cell.
186 private int maxDescent
= 0;
189 * System-dependent Y adjustment for text in the character cell.
191 private int textAdjustY
= 0;
194 * System-dependent X adjustment for text in the character cell.
196 private int textAdjustX
= 0;
199 * Top pixel absolute location.
201 private int top
= 30;
204 * Left pixel absolute location.
206 private int left
= 30;
209 * The cursor style to draw.
211 private CursorStyle cursorStyle
= CursorStyle
.UNDERLINE
;
214 * The number of millis to wait before switching the blink from
215 * visible to invisible.
217 private long blinkMillis
= 500;
220 * If true, the cursor should be visible right now based on the blink
223 private boolean cursorBlinkVisible
= true;
226 * The time that the blink last flipped from visible to invisible or
227 * from invisible to visible.
229 private long lastBlinkTime
= 0;
232 * Convert a CellAttributes foreground color to an Swing Color.
234 * @param attr the text attributes
235 * @return the Swing Color
237 private Color
attrToForegroundColor(final CellAttributes attr
) {
239 if (attr
.getForeColor().equals(jexer
.bits
.Color
.BLACK
)) {
241 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.RED
)) {
243 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.BLUE
)) {
245 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.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
)) {
251 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.MAGENTA
)) {
252 return MYBOLD_MAGENTA
;
253 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.WHITE
)) {
257 if (attr
.getForeColor().equals(jexer
.bits
.Color
.BLACK
)) {
259 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.RED
)) {
261 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.BLUE
)) {
263 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.GREEN
)) {
265 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.YELLOW
)) {
267 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.CYAN
)) {
269 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.MAGENTA
)) {
271 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.WHITE
)) {
275 throw new IllegalArgumentException("Invalid color: " +
276 attr
.getForeColor().getValue());
280 * Convert a CellAttributes background color to an Swing Color.
282 * @param attr the text attributes
283 * @return the Swing Color
285 private Color
attrToBackgroundColor(final CellAttributes attr
) {
286 if (attr
.getBackColor().equals(jexer
.bits
.Color
.BLACK
)) {
288 } else if (attr
.getBackColor().equals(jexer
.bits
.Color
.RED
)) {
290 } else if (attr
.getBackColor().equals(jexer
.bits
.Color
.BLUE
)) {
292 } else if (attr
.getBackColor().equals(jexer
.bits
.Color
.GREEN
)) {
294 } else if (attr
.getBackColor().equals(jexer
.bits
.Color
.YELLOW
)) {
296 } else if (attr
.getBackColor().equals(jexer
.bits
.Color
.CYAN
)) {
298 } else if (attr
.getBackColor().equals(jexer
.bits
.Color
.MAGENTA
)) {
300 } else if (attr
.getBackColor().equals(jexer
.bits
.Color
.WHITE
)) {
303 throw new IllegalArgumentException("Invalid color: " +
304 attr
.getBackColor().getValue());
308 * Public constructor.
310 * @param screen the Screen that Backend talks to
311 * @param fontSize the size in points. Good values to pick are: 16,
314 public SwingFrame(final SwingScreen screen
, final int fontSize
) {
315 this.screen
= screen
;
318 // Figure out my cursor style
319 String cursorStyleString
= System
.getProperty(
320 "jexer.Swing.cursorStyle", "underline").toLowerCase();
322 if (cursorStyleString
.equals("underline")) {
323 cursorStyle
= CursorStyle
.UNDERLINE
;
324 } else if (cursorStyleString
.equals("outline")) {
325 cursorStyle
= CursorStyle
.OUTLINE
;
326 } else if (cursorStyleString
.equals("block")) {
327 cursorStyle
= CursorStyle
.BLOCK
;
330 if (System
.getProperty("jexer.Swing.tripleBuffer") != null) {
331 if (System
.getProperty("jexer.Swing.tripleBuffer").
334 SwingScreen
.tripleBuffer
= false;
338 setTitle("Jexer Application");
339 setBackground(Color
.black
);
342 // Always try to use Terminus, the one decent font.
343 ClassLoader loader
= Thread
.currentThread().
344 getContextClassLoader();
345 InputStream in
= loader
.getResourceAsStream(FONTFILE
);
346 Font terminusRoot
= Font
.createFont(Font
.TRUETYPE_FONT
, in
);
347 Font terminus
= terminusRoot
.deriveFont(Font
.PLAIN
, fontSize
);
350 } catch (Exception e
) {
352 // setFont(new Font("Liberation Mono", Font.PLAIN, 24));
353 setFont(new Font(Font
.MONOSPACED
, Font
.PLAIN
, fontSize
));
357 // Kill the X11 cursor
358 // Transparent 16 x 16 pixel cursor image.
359 BufferedImage cursorImg
= new BufferedImage(16, 16,
360 BufferedImage
.TYPE_INT_ARGB
);
361 // Create a new blank cursor.
362 Cursor blankCursor
= Toolkit
.getDefaultToolkit().createCustomCursor(
363 cursorImg
, new Point(0, 0), "blank cursor");
364 setCursor(blankCursor
);
366 // Be capable of seeing Tab / Shift-Tab
367 setFocusTraversalKeysEnabled(false);
369 // Save the text cell width/height
372 // Cache glyphs as they are rendered
373 glyphCacheBlink
= new HashMap
<Cell
, BufferedImage
>();
374 glyphCache
= new HashMap
<Cell
, BufferedImage
>();
376 // Setup triple-buffering
377 if (SwingScreen
.tripleBuffer
) {
378 setIgnoreRepaint(true);
379 createBufferStrategy(3);
380 bufferStrategy
= getBufferStrategy();
385 * Figure out what textAdjustX and textAdjustY should be, based on
386 * the location of a vertical bar (to find textAdjustY) and a
387 * horizontal bar (to find textAdjustX).
389 * @return true if textAdjustX and textAdjustY were guessed at
392 private boolean getFontAdjustments() {
393 BufferedImage image
= null;
395 // What SHOULD happen is that the topmost/leftmost white pixel is
396 // at position (gr2x, gr2y). But it might also be off by a pixel
397 // in either direction.
399 Graphics2D gr2
= null;
402 image
= new BufferedImage(textWidth
* 2, textHeight
* 2,
403 BufferedImage
.TYPE_INT_ARGB
);
405 gr2
= image
.createGraphics();
406 gr2
.setFont(getFont());
407 gr2
.setColor(java
.awt
.Color
.BLACK
);
408 gr2
.fillRect(0, 0, textWidth
* 2, textHeight
* 2);
409 gr2
.setColor(java
.awt
.Color
.WHITE
);
410 char [] chars
= new char[1];
411 chars
[0] = jexer
.bits
.GraphicsChars
.VERTICAL_BAR
;
412 gr2
.drawChars(chars
, 0, 1, gr2x
, gr2y
+ textHeight
- maxDescent
);
415 for (int x
= 0; x
< textWidth
; x
++) {
416 for (int y
= 0; y
< textHeight
; y
++) {
419 System.err.println("X: " + x + " Y: " + y + " " +
423 if ((image
.getRGB(x
, y
) & 0xFFFFFF) != 0) {
424 textAdjustY
= (gr2y
- y
);
426 // System.err.println("textAdjustY: " + textAdjustY);
433 gr2
= image
.createGraphics();
434 gr2
.setFont(getFont());
435 gr2
.setColor(java
.awt
.Color
.BLACK
);
436 gr2
.fillRect(0, 0, textWidth
* 2, textHeight
* 2);
437 gr2
.setColor(java
.awt
.Color
.WHITE
);
438 chars
[0] = jexer
.bits
.GraphicsChars
.SINGLE_BAR
;
439 gr2
.drawChars(chars
, 0, 1, gr2x
, gr2y
+ textHeight
- maxDescent
);
442 for (int x
= 0; x
< textWidth
; x
++) {
443 for (int y
= 0; y
< textHeight
; y
++) {
446 System.err.println("X: " + x + " Y: " + y + " " +
450 if ((image
.getRGB(x
, y
) & 0xFFFFFF) != 0) {
451 textAdjustX
= (gr2x
- x
);
453 // System.err.println("textAdjustX: " + textAdjustX);
459 // Something weird happened, don't rely on this function.
460 // System.err.println("getFontAdjustments: false");
466 * Figure out my font dimensions.
468 private void getFontDimensions() {
469 Graphics gr
= getGraphics();
470 FontMetrics fm
= gr
.getFontMetrics();
471 maxDescent
= fm
.getMaxDescent();
472 Rectangle2D bounds
= fm
.getMaxCharBounds(gr
);
473 int leading
= fm
.getLeading();
474 textWidth
= (int)Math
.round(bounds
.getWidth());
475 // textHeight = (int)Math.round(bounds.getHeight()) - maxDescent;
477 // This produces the same number, but works better for ugly
479 textHeight
= fm
.getMaxAscent() + maxDescent
- leading
;
481 if (gotTerminus
== true) {
485 if (getFontAdjustments() == false) {
486 // We were unable to programmatically determine textAdjustX
487 // and textAdjustY, so try some guesses based on VM vendor.
488 String runtime
= System
.getProperty("java.runtime.name");
489 if ((runtime
!= null) && (runtime
.contains("Java(TM)"))) {
497 * Resize to font dimensions.
499 public void resizeToScreen() {
500 // Figure out the thickness of borders and use that to set the
502 Insets insets
= getInsets();
506 setSize(textWidth
* screen
.width
+ insets
.left
+ insets
.right
,
507 textHeight
* screen
.height
+ insets
.top
+ insets
.bottom
);
511 * Update redraws the whole screen.
513 * @param gr the Swing Graphics context
516 public void update(final Graphics gr
) {
517 // The default update clears the area. Don't do that, instead
518 // just paint it directly.
523 * Draw one glyph to the screen.
525 * @param gr the Swing Graphics context
526 * @param cell the Cell to draw
527 * @param xPixel the x-coordinate to render to. 0 means the
528 * left-most pixel column.
529 * @param yPixel the y-coordinate to render to. 0 means the top-most
532 private void drawGlyph(final Graphics gr
, final Cell cell
,
533 final int xPixel
, final int yPixel
) {
535 BufferedImage image
= null;
536 if (cell
.isBlink() && !cursorBlinkVisible
) {
537 image
= glyphCacheBlink
.get(cell
);
539 image
= glyphCache
.get(cell
);
542 gr
.drawImage(image
, xPixel
, yPixel
, this);
546 // Generate glyph and draw it.
547 Graphics2D gr2
= null;
551 image
= new BufferedImage(textWidth
, textHeight
,
552 BufferedImage
.TYPE_INT_ARGB
);
553 gr2
= image
.createGraphics();
554 gr2
.setFont(getFont());
558 gr2
= (Graphics2D
) gr
;
561 Cell cellColor
= new Cell();
562 cellColor
.setTo(cell
);
565 if (cell
.isReverse()) {
566 cellColor
.setForeColor(cell
.getBackColor());
567 cellColor
.setBackColor(cell
.getForeColor());
570 // Draw the background rectangle, then the foreground character.
571 gr2
.setColor(attrToBackgroundColor(cellColor
));
572 gr2
.fillRect(gr2x
, gr2y
, textWidth
, textHeight
);
574 // Handle blink and underline
576 || (cell
.isBlink() && cursorBlinkVisible
)
578 gr2
.setColor(attrToForegroundColor(cellColor
));
579 char [] chars
= new char[1];
580 chars
[0] = cell
.getChar();
581 gr2
.drawChars(chars
, 0, 1, gr2x
+ textAdjustX
,
582 gr2y
+ textHeight
- maxDescent
+ textAdjustY
);
584 if (cell
.isUnderline()) {
585 gr2
.fillRect(gr2x
, gr2y
+ textHeight
- 2, textWidth
, 2);
592 // We need a new key that will not be mutated by
594 Cell key
= new Cell();
596 if (cell
.isBlink() && !cursorBlinkVisible
) {
597 glyphCacheBlink
.put(key
, image
);
599 glyphCache
.put(key
, image
);
602 gr
.drawImage(image
, xPixel
, yPixel
, this);
608 * Check if the cursor is visible, and if so draw it.
610 * @param gr the Swing Graphics context
612 private void drawCursor(final Graphics gr
) {
615 && (cursorY
<= screen
.height
- 1)
616 && (cursorX
<= screen
.width
- 1)
617 && cursorBlinkVisible
619 int xPixel
= cursorX
* textWidth
+ left
;
620 int yPixel
= cursorY
* textHeight
+ top
;
621 Cell lCell
= screen
.logical
[cursorX
][cursorY
];
622 gr
.setColor(attrToForegroundColor(lCell
));
623 switch (cursorStyle
) {
627 gr
.fillRect(xPixel
, yPixel
+ textHeight
- 2, textWidth
, 2);
630 gr
.fillRect(xPixel
, yPixel
, textWidth
, textHeight
);
633 gr
.drawRect(xPixel
, yPixel
, textWidth
- 1, textHeight
- 1);
640 * Paint redraws the whole screen.
642 * @param gr the Swing Graphics context
645 public void paint(final Graphics gr
) {
646 // Do nothing until the screen reference has been set.
647 if (screen
== null) {
650 if (screen
.frame
== null) {
654 // See if it is time to flip the blink time.
655 long nowTime
= (new Date()).getTime();
656 if (nowTime
> blinkMillis
+ lastBlinkTime
) {
657 lastBlinkTime
= nowTime
;
658 cursorBlinkVisible
= !cursorBlinkVisible
;
662 int xCellMax
= screen
.width
;
664 int yCellMax
= screen
.height
;
666 Rectangle bounds
= gr
.getClipBounds();
667 if (bounds
!= null) {
668 // Only update what is in the bounds
669 xCellMin
= screen
.textColumn(bounds
.x
);
670 xCellMax
= screen
.textColumn(bounds
.x
+ bounds
.width
);
671 if (xCellMax
> screen
.width
) {
672 xCellMax
= screen
.width
;
674 if (xCellMin
>= xCellMax
) {
675 xCellMin
= xCellMax
- 2;
680 yCellMin
= screen
.textRow(bounds
.y
);
681 yCellMax
= screen
.textRow(bounds
.y
+ bounds
.height
);
682 if (yCellMax
> screen
.height
) {
683 yCellMax
= screen
.height
;
685 if (yCellMin
>= yCellMax
) {
686 yCellMin
= yCellMax
- 2;
692 // We need a total repaint
693 reallyCleared
= true;
696 // Prevent updates to the screen's data from the TApplication
698 synchronized (screen
) {
700 System.err.printf("bounds %s X %d %d Y %d %d\n",
701 bounds, xCellMin, xCellMax, yCellMin, yCellMax);
704 for (int y
= yCellMin
; y
< yCellMax
; y
++) {
705 for (int x
= xCellMin
; x
< xCellMax
; x
++) {
707 int xPixel
= x
* textWidth
+ left
;
708 int yPixel
= y
* textHeight
+ top
;
710 Cell lCell
= screen
.logical
[x
][y
];
711 Cell pCell
= screen
.physical
[x
][y
];
713 if (!lCell
.equals(pCell
)
717 drawGlyph(gr
, lCell
, xPixel
, yPixel
);
719 // Physical is always updated
720 physical
[x
][y
].setTo(lCell
);
727 reallyCleared
= false;
728 } // synchronized (screen)
731 } // class SwingFrame
734 * The raw Swing JFrame. Note package private access.
739 * Restore terminal to normal state.
741 public void shutdown() {
746 * Public constructor.
748 * @param windowWidth the number of text columns to start with
749 * @param windowHeight the number of text rows to start with
750 * @param fontSize the size in points. Good values to pick are: 16, 20,
753 public SwingScreen(final int windowWidth
, final int windowHeight
,
754 final int fontSize
) {
757 SwingUtilities
.invokeAndWait(new Runnable() {
759 SwingScreen
.this.frame
= new SwingFrame(SwingScreen
.this,
761 SwingScreen
.this.sessionInfo
=
762 new SwingSessionInfo(SwingScreen
.this.frame
,
763 frame
.textWidth
, frame
.textHeight
,
764 windowWidth
, windowHeight
);
766 SwingScreen
.this.setDimensions(sessionInfo
.getWindowWidth(),
767 sessionInfo
.getWindowHeight());
769 SwingScreen
.this.frame
.resizeToScreen();
770 SwingScreen
.this.frame
.setVisible(true);
773 } catch (Exception e
) {
781 private SwingSessionInfo sessionInfo
;
784 * Create the SwingSessionInfo. Note package private access.
786 * @return the sessionInfo
788 SwingSessionInfo
getSessionInfo() {
793 * Push the logical screen to the physical device.
796 public void flushPhysical() {
799 System.err.printf("flushPhysical(): reallyCleared %s dirty %s\n",
800 reallyCleared, dirty);
803 // If reallyCleared is set, we have to draw everything.
804 if ((frame
.bufferStrategy
!= null) && (reallyCleared
== true)) {
805 // Triple-buffering: we have to redraw everything on this thread.
806 Graphics gr
= frame
.bufferStrategy
.getDrawGraphics();
809 frame
.bufferStrategy
.show();
810 // sync() doesn't seem to help the tearing for me.
811 // Toolkit.getDefaultToolkit().sync();
813 } else if ((frame
.bufferStrategy
== null) && (reallyCleared
== true)) {
814 // Repaint everything on the Swing thread.
819 // Do nothing if nothing happened.
824 if (frame
.bufferStrategy
!= null) {
825 // See if it is time to flip the blink time.
826 long nowTime
= (new Date()).getTime();
827 if (nowTime
> frame
.blinkMillis
+ frame
.lastBlinkTime
) {
828 frame
.lastBlinkTime
= nowTime
;
829 frame
.cursorBlinkVisible
= !frame
.cursorBlinkVisible
;
832 Graphics gr
= frame
.bufferStrategy
.getDrawGraphics();
834 synchronized (this) {
835 for (int y
= 0; y
< height
; y
++) {
836 for (int x
= 0; x
< width
; x
++) {
837 Cell lCell
= logical
[x
][y
];
838 Cell pCell
= physical
[x
][y
];
840 int xPixel
= x
* frame
.textWidth
+ frame
.left
;
841 int yPixel
= y
* frame
.textHeight
+ frame
.top
;
843 if (!lCell
.equals(pCell
)
849 frame
.drawGlyph(gr
, lCell
, xPixel
, yPixel
);
850 physical
[x
][y
].setTo(lCell
);
854 frame
.drawCursor(gr
);
855 } // synchronized (this)
858 frame
.bufferStrategy
.show();
859 // sync() doesn't seem to help the tearing for me.
860 // Toolkit.getDefaultToolkit().sync();
864 // Swing thread version: request a repaint, but limit it to the area
867 // Find the minimum-size damaged region.
868 int xMin
= frame
.getWidth();
870 int yMin
= frame
.getHeight();
873 synchronized (this) {
874 for (int y
= 0; y
< height
; y
++) {
875 for (int x
= 0; x
< width
; x
++) {
876 Cell lCell
= logical
[x
][y
];
877 Cell pCell
= physical
[x
][y
];
879 int xPixel
= x
* frame
.textWidth
+ frame
.left
;
880 int yPixel
= y
* frame
.textHeight
+ frame
.top
;
882 if (!lCell
.equals(pCell
)
891 if (xPixel
+ frame
.textWidth
> xMax
) {
892 xMax
= xPixel
+ frame
.textWidth
;
897 if (yPixel
+ frame
.textHeight
> yMax
) {
898 yMax
= yPixel
+ frame
.textHeight
;
904 if (xMin
+ frame
.textWidth
>= xMax
) {
905 xMax
+= frame
.textWidth
;
907 if (yMin
+ frame
.textHeight
>= yMax
) {
908 yMax
+= frame
.textHeight
;
911 // Repaint the desired area
913 System.err.printf("REPAINT X %d %d Y %d %d\n", xMin, xMax,
916 if (frame
.bufferStrategy
!= null) {
917 // This path should never be taken, but is left here for
919 Graphics gr
= frame
.bufferStrategy
.getDrawGraphics();
920 Rectangle bounds
= new Rectangle(xMin
, yMin
, xMax
- xMin
,
925 frame
.bufferStrategy
.show();
926 // sync() doesn't seem to help the tearing for me.
927 // Toolkit.getDefaultToolkit().sync();
929 // Repaint on the Swing thread.
930 frame
.repaint(xMin
, yMin
, xMax
- xMin
, yMax
- yMin
);
935 * Put the cursor at (x,y).
937 * @param visible if true, the cursor should be visible
938 * @param x column coordinate to put the cursor on
939 * @param y row coordinate to put the cursor on
942 public void putCursor(final boolean visible
, final int x
, final int y
) {
944 if ((visible
== cursorVisible
) && ((x
== cursorX
) && (y
== cursorY
))) {
945 // See if it is time to flip the blink time.
946 long nowTime
= (new Date()).getTime();
947 if (nowTime
< frame
.blinkMillis
+ frame
.lastBlinkTime
) {
948 // Nothing has changed, so don't do anything.
954 && (cursorY
<= height
- 1)
955 && (cursorX
<= width
- 1)
957 // Make the current cursor position dirty
958 if (physical
[cursorX
][cursorY
].getChar() == 'Q') {
959 physical
[cursorX
][cursorY
].setChar('X');
961 physical
[cursorX
][cursorY
].setChar('Q');
965 super.putCursor(visible
, x
, y
);
969 * Convert pixel column position to text cell column position.
971 * @param x pixel column position
972 * @return text cell column position
974 public int textColumn(final int x
) {
975 return ((x
- frame
.left
) / frame
.textWidth
);
979 * Convert pixel row position to text cell row position.
981 * @param y pixel row position
982 * @return text cell row position
984 public int textRow(final int y
) {
985 return ((y
- frame
.top
) / frame
.textHeight
);
989 * Set the window title.
991 * @param title the new title
993 public void setTitle(final String title
) {
994 frame
.setTitle(title
);