2 * Jexer - Java Text User Interface
4 * License: LGPLv3 or later
6 * This module is licensed under the GNU Lesser General Public License
7 * Version 3. Please see the file "COPYING" in this directory for more
8 * information about the GNU Lesser General Public License Version 3.
10 * Copyright (C) 2015 Kevin Lamonte
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public License
14 * as published by the Free Software Foundation; either version 3 of
15 * the License, or (at your option) any later version.
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this program; if not, see
24 * http://www.gnu.org/licenses/, or write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
28 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
33 import jexer
.bits
.Cell
;
34 import jexer
.bits
.CellAttributes
;
35 import jexer
.session
.AWTSessionInfo
;
37 import java
.awt
.Color
;
38 import java
.awt
.Cursor
;
40 import java
.awt
.FontMetrics
;
41 import java
.awt
.Graphics
;
42 import java
.awt
.Insets
;
43 import java
.awt
.Point
;
44 import java
.awt
.Rectangle
;
45 import java
.awt
.Toolkit
;
46 import java
.awt
.geom
.Rectangle2D
;
47 import java
.awt
.image
.BufferedImage
;
48 import java
.io
.InputStream
;
49 import javax
.swing
.JFrame
;
50 import javax
.swing
.SwingUtilities
;
53 * This Screen implementation draws to a Java AWT Frame.
55 public final class AWTScreen
extends Screen
{
57 private static Color MYBLACK
;
58 private static Color MYRED
;
59 private static Color MYGREEN
;
60 private static Color MYYELLOW
;
61 private static Color MYBLUE
;
62 private static Color MYMAGENTA
;
63 private static Color MYCYAN
;
64 private static Color MYWHITE
;
66 private static Color MYBOLD_BLACK
;
67 private static Color MYBOLD_RED
;
68 private static Color MYBOLD_GREEN
;
69 private static Color MYBOLD_YELLOW
;
70 private static Color MYBOLD_BLUE
;
71 private static Color MYBOLD_MAGENTA
;
72 private static Color MYBOLD_CYAN
;
73 private static Color MYBOLD_WHITE
;
75 private static boolean dosColors
= false;
78 * Setup AWT colors to match DOS color palette.
80 private static void setDOSColors() {
84 MYBLACK
= new Color(0x00, 0x00, 0x00);
85 MYRED
= new Color(0xa8, 0x00, 0x00);
86 MYGREEN
= new Color(0x00, 0xa8, 0x00);
87 MYYELLOW
= new Color(0xa8, 0x54, 0x00);
88 MYBLUE
= new Color(0x00, 0x00, 0xa8);
89 MYMAGENTA
= new Color(0xa8, 0x00, 0xa8);
90 MYCYAN
= new Color(0x00, 0xa8, 0xa8);
91 MYWHITE
= new Color(0xa8, 0xa8, 0xa8);
92 MYBOLD_BLACK
= new Color(0x54, 0x54, 0x54);
93 MYBOLD_RED
= new Color(0xfc, 0x54, 0x54);
94 MYBOLD_GREEN
= new Color(0x54, 0xfc, 0x54);
95 MYBOLD_YELLOW
= new Color(0xfc, 0xfc, 0x54);
96 MYBOLD_BLUE
= new Color(0x54, 0x54, 0xfc);
97 MYBOLD_MAGENTA
= new Color(0xfc, 0x54, 0xfc);
98 MYBOLD_CYAN
= new Color(0x54, 0xfc, 0xfc);
99 MYBOLD_WHITE
= new Color(0xfc, 0xfc, 0xfc);
105 * AWTFrame is our top-level hook into the AWT system.
107 class AWTFrame
extends JFrame
{
110 * Serializable version.
112 private static final long serialVersionUID
= 1;
115 * The terminus font resource filename.
117 private static final String FONTFILE
= "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf";
120 * The TUI Screen data.
125 * Width of a character cell.
127 private int textWidth
= 1;
130 * Height of a character cell.
132 private int textHeight
= 1;
135 * Descent of a character cell.
137 private int maxDescent
= 0;
142 private int top
= 30;
147 private int left
= 30;
150 * Convert a CellAttributes foreground color to an AWT Color.
152 * @param attr the text attributes
153 * @return the AWT Color
155 private Color
attrToForegroundColor(final CellAttributes attr
) {
162 if (attr
.getBold()) {
163 if (attr
.getForeColor().equals(jexer
.bits
.Color
.BLACK
)) {
165 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.RED
)) {
167 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.BLUE
)) {
169 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.GREEN
)) {
171 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.YELLOW
)) {
172 return MYBOLD_YELLOW
;
173 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.CYAN
)) {
175 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.MAGENTA
)) {
176 return MYBOLD_MAGENTA
;
177 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.WHITE
)) {
181 if (attr
.getForeColor().equals(jexer
.bits
.Color
.BLACK
)) {
183 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.RED
)) {
185 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.BLUE
)) {
187 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.GREEN
)) {
189 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.YELLOW
)) {
191 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.CYAN
)) {
193 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.MAGENTA
)) {
195 } else if (attr
.getForeColor().equals(jexer
.bits
.Color
.WHITE
)) {
199 throw new IllegalArgumentException("Invalid color: " + attr
.getForeColor().getValue());
203 * Convert a CellAttributes background color to an AWT Color.
205 * @param attr the text attributes
206 * @return the AWT Color
208 private Color
attrToBackgroundColor(final CellAttributes attr
) {
215 if (attr
.getBackColor().equals(jexer
.bits
.Color
.BLACK
)) {
217 } else if (attr
.getBackColor().equals(jexer
.bits
.Color
.RED
)) {
219 } else if (attr
.getBackColor().equals(jexer
.bits
.Color
.BLUE
)) {
221 } else if (attr
.getBackColor().equals(jexer
.bits
.Color
.GREEN
)) {
223 } else if (attr
.getBackColor().equals(jexer
.bits
.Color
.YELLOW
)) {
225 } else if (attr
.getBackColor().equals(jexer
.bits
.Color
.CYAN
)) {
227 } else if (attr
.getBackColor().equals(jexer
.bits
.Color
.MAGENTA
)) {
229 } else if (attr
.getBackColor().equals(jexer
.bits
.Color
.WHITE
)) {
232 throw new IllegalArgumentException("Invalid color: " + attr
.getBackColor().getValue());
236 * Public constructor.
238 * @param screen the Screen that Backend talks to
240 public AWTFrame(final AWTScreen screen
) {
241 this.screen
= screen
;
244 setTitle("Jexer Application");
245 setBackground(Color
.black
);
246 // setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
247 // setFont(new Font("Liberation Mono", Font.BOLD, 16));
248 // setFont(new Font(Font.MONOSPACED, Font.PLAIN, 16));
251 // Always try to use Terminus, the one decent font.
252 ClassLoader loader
= Thread
.currentThread().getContextClassLoader();
253 InputStream in
= loader
.getResourceAsStream(FONTFILE
);
254 Font terminusRoot
= Font
.createFont(Font
.TRUETYPE_FONT
, in
);
255 Font terminus
= terminusRoot
.deriveFont(Font
.PLAIN
, 22);
257 } catch (Exception e
) {
259 // setFont(new Font("Liberation Mono", Font.PLAIN, 24));
260 setFont(new Font(Font
.MONOSPACED
, Font
.PLAIN
, 24));
264 // Kill the X11 cursor
265 // Transparent 16 x 16 pixel cursor image.
266 BufferedImage cursorImg
= new BufferedImage(16, 16,
267 BufferedImage
.TYPE_INT_ARGB
);
268 // Create a new blank cursor.
269 Cursor blankCursor
= Toolkit
.getDefaultToolkit().createCustomCursor(
270 cursorImg
, new Point(0, 0), "blank cursor");
271 setCursor(blankCursor
);
273 // Be capable of seeing Tab / Shift-Tab
274 setFocusTraversalKeysEnabled(false);
276 // Save the text cell width/height
281 * Figure out my font dimensions.
283 private void getFontDimensions() {
284 Graphics gr
= getGraphics();
285 FontMetrics fm
= gr
.getFontMetrics();
286 maxDescent
= fm
.getMaxDescent();
287 Rectangle2D bounds
= fm
.getMaxCharBounds(gr
);
288 int leading
= fm
.getLeading();
289 textWidth
= (int)Math
.round(bounds
.getWidth());
290 textHeight
= (int)Math
.round(bounds
.getHeight()) - maxDescent
;
291 // This also produces the same number, but works better for ugly
293 textHeight
= fm
.getMaxAscent() + maxDescent
- leading
;
297 * Resize to font dimensions.
299 public void resizeToScreen() {
300 // Figure out the thickness of borders and use that to set the
302 Insets insets
= getInsets();
306 setSize(textWidth
* screen
.width
+ insets
.left
+ insets
.right
,
307 textHeight
* screen
.height
+ insets
.top
+ insets
.bottom
);
311 * Update redraws the whole screen.
313 * @param gr the AWT Graphics context
316 public void update(final Graphics gr
) {
317 // The default update clears the area. Don't do that, instead
318 // just paint it directly.
323 * Paint redraws the whole screen.
325 * @param gr the AWT Graphics context
328 public void paint(final Graphics gr
) {
329 // Do nothing until the screen reference has been set.
330 if (screen
== null) {
333 if (screen
.frame
== null) {
338 int xCellMax
= screen
.width
;
340 int yCellMax
= screen
.height
;
342 Rectangle bounds
= gr
.getClipBounds();
343 if (bounds
!= null) {
344 // Only update what is in the bounds
345 xCellMin
= screen
.textColumn(bounds
.x
);
346 xCellMax
= screen
.textColumn(bounds
.x
+ bounds
.width
);
347 if (xCellMax
> screen
.width
) {
348 xCellMax
= screen
.width
;
350 if (xCellMin
>= xCellMax
) {
351 xCellMin
= xCellMax
- 2;
356 yCellMin
= screen
.textRow(bounds
.y
);
357 yCellMax
= screen
.textRow(bounds
.y
+ bounds
.height
);
358 if (yCellMax
> screen
.height
) {
359 yCellMax
= screen
.height
;
361 if (yCellMin
>= yCellMax
) {
362 yCellMin
= yCellMax
- 2;
368 // We need a total repaint
369 reallyCleared
= true;
372 // Prevent updates to the screen's data from the TApplication
374 synchronized (screen
) {
376 System.err.printf("bounds %s X %d %d Y %d %d\n",
377 bounds, xCellMin, xCellMax, yCellMin, yCellMax);
380 for (int y
= yCellMin
; y
< yCellMax
; y
++) {
381 for (int x
= xCellMin
; x
< xCellMax
; x
++) {
383 int xPixel
= x
* textWidth
+ left
;
384 int yPixel
= y
* textHeight
+ top
;
386 Cell lCell
= screen
.logical
[x
][y
];
387 Cell pCell
= screen
.physical
[x
][y
];
389 if (!lCell
.equals(pCell
) || reallyCleared
) {
390 // Draw the background rectangle, then the
391 // foreground character.
392 gr
.setColor(attrToBackgroundColor(lCell
));
393 gr
.fillRect(xPixel
, yPixel
, textWidth
, textHeight
);
394 gr
.setColor(attrToForegroundColor(lCell
));
395 char [] chars
= new char[1];
396 chars
[0] = lCell
.getChar();
397 gr
.drawChars(chars
, 0, 1, xPixel
,
398 yPixel
+ textHeight
- maxDescent
);
400 // Physical is always updated
401 physical
[x
][y
].setTo(lCell
);
406 // Draw the cursor if it is visible
408 && (cursorY
<= screen
.height
- 1)
409 && (cursorX
<= screen
.width
- 1)
411 int xPixel
= cursorX
* textWidth
+ left
;
412 int yPixel
= cursorY
* textHeight
+ top
;
413 Cell lCell
= screen
.logical
[cursorX
][cursorY
];
414 gr
.setColor(attrToForegroundColor(lCell
));
415 gr
.fillRect(xPixel
, yPixel
+ textHeight
- 2, textWidth
, 2);
419 reallyCleared
= false;
420 } // synchronized (screen)
426 * The raw AWT Frame. Note package private access.
431 * Public constructor.
435 SwingUtilities
.invokeAndWait(new Runnable() {
437 AWTScreen
.this.frame
= new AWTFrame(AWTScreen
.this);
438 AWTScreen
.this.sessionInfo
=
439 new AWTSessionInfo(AWTScreen
.this.frame
,
443 AWTScreen
.this.setDimensions(sessionInfo
.getWindowWidth(),
444 sessionInfo
.getWindowHeight());
446 AWTScreen
.this.frame
.resizeToScreen();
447 AWTScreen
.this.frame
.setVisible(true);
450 } catch (Exception e
) {
458 private AWTSessionInfo sessionInfo
;
461 * Create the AWTSessionInfo. Note package private access.
463 * @return the sessionInfo
465 AWTSessionInfo
getSessionInfo() {
470 * Push the logical screen to the physical device.
473 public void flushPhysical() {
476 // Really refreshed, do it all
481 // Do nothing if nothing happened.
486 // Request a repaint, let the frame's repaint/update methods do the
489 // Find the minimum-size damaged region.
490 int xMin
= frame
.getWidth();
492 int yMin
= frame
.getHeight();
495 synchronized (this) {
496 for (int y
= 0; y
< height
; y
++) {
497 for (int x
= 0; x
< width
; x
++) {
498 Cell lCell
= logical
[x
][y
];
499 Cell pCell
= physical
[x
][y
];
501 int xPixel
= x
* frame
.textWidth
+ frame
.left
;
502 int yPixel
= y
* frame
.textHeight
+ frame
.top
;
504 if (!lCell
.equals(pCell
)
512 if (xPixel
+ frame
.textWidth
> xMax
) {
513 xMax
= xPixel
+ frame
.textWidth
;
518 if (yPixel
+ frame
.textHeight
> yMax
) {
519 yMax
= yPixel
+ frame
.textHeight
;
525 if (xMin
+ frame
.textWidth
>= xMax
) {
526 xMax
+= frame
.textWidth
;
528 if (yMin
+ frame
.textHeight
>= yMax
) {
529 yMax
+= frame
.textHeight
;
532 // Repaint the desired area
533 frame
.repaint(xMin
, yMin
, xMax
- xMin
, yMax
- yMin
);
534 // System.err.printf("REPAINT X %d %d Y %d %d\n", xMin, xMax, yMin, yMax);
538 * Put the cursor at (x,y).
540 * @param visible if true, the cursor should be visible
541 * @param x column coordinate to put the cursor on
542 * @param y row coordinate to put the cursor on
545 public void putCursor(final boolean visible
, final int x
, final int y
) {
547 && (cursorY
<= height
- 1)
548 && (cursorX
<= width
- 1)
550 // Make the current cursor position dirty
551 if (physical
[cursorX
][cursorY
].getChar() == 'Q') {
552 physical
[cursorX
][cursorY
].setChar('X');
554 physical
[cursorX
][cursorY
].setChar('Q');
558 super.putCursor(visible
, x
, y
);
562 * Convert pixel column position to text cell column position.
564 * @param x pixel column position
565 * @return text cell column position
567 public int textColumn(final int x
) {
568 return ((x
- frame
.left
) / frame
.textWidth
);
572 * Convert pixel row position to text cell row position.
574 * @param y pixel row position
575 * @return text cell row position
577 public int textRow(final int y
) {
578 return ((y
- frame
.top
) / frame
.textHeight
);