cutting v0.0.1
[fanfix.git] / src / jexer / io / AWTScreen.java
CommitLineData
1ac2ccb1
KL
1/**
2 * Jexer - Java Text User Interface
3 *
4 * License: LGPLv3 or later
5 *
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.
9 *
10 * Copyright (C) 2015 Kevin Lamonte
11 *
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.
16 *
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.
21 *
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
26 * 02110-1301 USA
27 *
28 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 * @version 1
30 */
31package jexer.io;
32
33import jexer.bits.Cell;
34import jexer.bits.CellAttributes;
30bd4abd 35import jexer.session.AWTSessionInfo;
1ac2ccb1
KL
36
37import java.awt.Color;
38import java.awt.Cursor;
39import java.awt.Font;
40import java.awt.FontMetrics;
41import java.awt.Frame;
42import java.awt.Graphics;
84614868 43import java.awt.Insets;
30bd4abd
KL
44import java.awt.Point;
45import java.awt.Rectangle;
46import java.awt.Toolkit;
1ac2ccb1 47import java.awt.geom.Rectangle2D;
30bd4abd 48import java.awt.image.BufferedImage;
84614868 49import java.io.InputStream;
1ac2ccb1
KL
50
51/**
52 * This Screen implementation draws to a Java AWT Frame.
53 */
54public final class AWTScreen extends Screen {
55
84614868
KL
56 private static Color MYBLACK;
57 private static Color MYRED;
58 private static Color MYGREEN;
59 private static Color MYYELLOW;
60 private static Color MYBLUE;
61 private static Color MYMAGENTA;
62 private static Color MYCYAN;
63 private static Color MYWHITE;
64
65 private static Color MYBOLD_BLACK;
66 private static Color MYBOLD_RED;
67 private static Color MYBOLD_GREEN;
68 private static Color MYBOLD_YELLOW;
69 private static Color MYBOLD_BLUE;
70 private static Color MYBOLD_MAGENTA;
71 private static Color MYBOLD_CYAN;
72 private static Color MYBOLD_WHITE;
73
74 private static boolean dosColors = false;
75
76 /**
77 * Setup AWT colors to match DOS color palette.
78 */
79 private static void setDOSColors() {
80 if (dosColors) {
81 return;
82 }
83 MYBLACK = new Color(0x00, 0x00, 0x00);
84 MYRED = new Color(0xa8, 0x00, 0x00);
85 MYGREEN = new Color(0x00, 0xa8, 0x00);
86 MYYELLOW = new Color(0xa8, 0x54, 0x00);
87 MYBLUE = new Color(0x00, 0x00, 0xa8);
88 MYMAGENTA = new Color(0xa8, 0x00, 0xa8);
89 MYCYAN = new Color(0x00, 0xa8, 0xa8);
90 MYWHITE = new Color(0xa8, 0xa8, 0xa8);
91 MYBOLD_BLACK = new Color(0x54, 0x54, 0x54);
92 MYBOLD_RED = new Color(0xfc, 0x54, 0x54);
93 MYBOLD_GREEN = new Color(0x54, 0xfc, 0x54);
94 MYBOLD_YELLOW = new Color(0xfc, 0xfc, 0x54);
95 MYBOLD_BLUE = new Color(0x54, 0x54, 0xfc);
96 MYBOLD_MAGENTA = new Color(0xfc, 0x54, 0xfc);
97 MYBOLD_CYAN = new Color(0x54, 0xfc, 0xfc);
98 MYBOLD_WHITE = new Color(0xfc, 0xfc, 0xfc);
99
100 dosColors = true;
101 }
102
1ac2ccb1
KL
103 /**
104 * AWTFrame is our top-level hook into the AWT system.
105 */
106 class AWTFrame extends Frame {
107
84614868
KL
108 /**
109 * The terminus font resource filename.
110 */
111 private static final String FONTFILE = "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf";
112
1ac2ccb1
KL
113 /**
114 * The TUI Screen data.
115 */
116 AWTScreen screen;
117
118 /**
119 * Width of a character cell.
120 */
121 private int textWidth = 1;
122
123 /**
124 * Height of a character cell.
125 */
126 private int textHeight = 1;
127
84614868
KL
128 /**
129 * Descent of a character cell.
130 */
131 private int maxDescent = 0;
132
1ac2ccb1
KL
133 /**
134 * Top pixel value.
135 */
136 private int top = 30;
84614868 137
1ac2ccb1
KL
138 /**
139 * Left pixel value.
140 */
141 private int left = 30;
84614868
KL
142
143 /**
144 * Convert a CellAttributes foreground color to an AWT Color.
145 *
146 * @param attr the text attributes
147 * @return the AWT Color
148 */
149 private Color attrToForegroundColor(final CellAttributes attr) {
150 /*
151 * TODO:
152 * reverse
153 * blink
154 * underline
155 */
156 if (attr.getBold()) {
157 if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
158 return MYBOLD_BLACK;
159 } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
160 return MYBOLD_RED;
161 } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
162 return MYBOLD_BLUE;
163 } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
164 return MYBOLD_GREEN;
165 } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
166 return MYBOLD_YELLOW;
167 } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
168 return MYBOLD_CYAN;
169 } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
170 return MYBOLD_MAGENTA;
171 } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
172 return MYBOLD_WHITE;
173 }
174 } else {
175 if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
176 return MYBLACK;
177 } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
178 return MYRED;
179 } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
180 return MYBLUE;
181 } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
182 return MYGREEN;
183 } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
184 return MYYELLOW;
185 } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
186 return MYCYAN;
187 } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
188 return MYMAGENTA;
189 } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
190 return MYWHITE;
191 }
192 }
193 throw new IllegalArgumentException("Invalid color: " + attr.getForeColor().getValue());
194 }
195
196 /**
197 * Convert a CellAttributes background color to an AWT Color.
198 *
199 * @param attr the text attributes
200 * @return the AWT Color
201 */
202 private Color attrToBackgroundColor(final CellAttributes attr) {
203 /*
204 * TODO:
205 * reverse
206 * blink
207 * underline
208 */
209 if (attr.getBackColor().equals(jexer.bits.Color.BLACK)) {
210 return MYBLACK;
211 } else if (attr.getBackColor().equals(jexer.bits.Color.RED)) {
212 return MYRED;
213 } else if (attr.getBackColor().equals(jexer.bits.Color.BLUE)) {
214 return MYBLUE;
215 } else if (attr.getBackColor().equals(jexer.bits.Color.GREEN)) {
216 return MYGREEN;
217 } else if (attr.getBackColor().equals(jexer.bits.Color.YELLOW)) {
218 return MYYELLOW;
219 } else if (attr.getBackColor().equals(jexer.bits.Color.CYAN)) {
220 return MYCYAN;
221 } else if (attr.getBackColor().equals(jexer.bits.Color.MAGENTA)) {
222 return MYMAGENTA;
223 } else if (attr.getBackColor().equals(jexer.bits.Color.WHITE)) {
224 return MYWHITE;
225 }
226 throw new IllegalArgumentException("Invalid color: " + attr.getBackColor().getValue());
227 }
228
1ac2ccb1
KL
229 /**
230 * Public constructor.
84614868
KL
231 *
232 * @param screen the Screen that Backend talks to
1ac2ccb1 233 */
84614868
KL
234 public AWTFrame(final AWTScreen screen) {
235 this.screen = screen;
236 setDOSColors();
237
1ac2ccb1
KL
238 setTitle("Jexer Application");
239 setBackground(java.awt.Color.black);
30bd4abd 240 // setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
84614868 241 // setFont(new Font("Liberation Mono", Font.BOLD, 16));
1ac2ccb1 242 // setFont(new Font(Font.MONOSPACED, Font.PLAIN, 16));
84614868
KL
243
244 try {
30bd4abd 245 // Always try to use Terminus, the one decent font.
84614868
KL
246 ClassLoader loader = Thread.currentThread().getContextClassLoader();
247 InputStream in = loader.getResourceAsStream(FONTFILE);
248 Font terminusRoot = Font.createFont(Font.TRUETYPE_FONT, in);
249 Font terminus = terminusRoot.deriveFont(Font.PLAIN, 22);
250 setFont(terminus);
251 } catch (Exception e) {
252 e.printStackTrace();
253 // setFont(new Font("Liberation Mono", Font.PLAIN, 24));
254 setFont(new Font(Font.MONOSPACED, Font.PLAIN, 24));
255 }
1ac2ccb1 256 setVisible(true);
84614868 257 resizeToScreen();
30bd4abd
KL
258
259 // Kill the X11 cursor
260 // Transparent 16 x 16 pixel cursor image.
261 BufferedImage cursorImg = new BufferedImage(16, 16,
262 BufferedImage.TYPE_INT_ARGB);
263
264 // Create a new blank cursor.
265 Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(
266 cursorImg, new Point(0, 0), "blank cursor");
267 setCursor(blankCursor);
1ac2ccb1
KL
268 }
269
270 /**
271 * Resize to font dimensions.
272 */
273 public void resizeToScreen() {
84614868
KL
274 Graphics gr = getGraphics();
275 FontMetrics fm = gr.getFontMetrics();
276 maxDescent = fm.getMaxDescent();
277 Rectangle2D bounds = fm.getMaxCharBounds(gr);
278 int leading = fm.getLeading();
279 textWidth = (int)Math.round(bounds.getWidth());
280 textHeight = (int)Math.round(bounds.getHeight()) - maxDescent;
281 // This also produces the same number, but works better for ugly
282 // monospace.
283 textHeight = fm.getMaxAscent() + maxDescent - leading;
284
285 // Figure out the thickness of borders and use that to set the
286 // final size.
287 Insets insets = getInsets();
288 left = insets.left;
289 top = insets.top;
290
291 setSize(textWidth * screen.width + insets.left + insets.right,
292 textHeight * screen.height + insets.top + insets.bottom);
293
294 /*
295 System.err.printf("W: %d H: %d MD: %d L: %d\n", textWidth,
296 textHeight, maxDescent, leading);
297 */
1ac2ccb1
KL
298 }
299
30bd4abd
KL
300 /**
301 * Update redraws the whole screen.
302 *
303 * @param gr the AWT Graphics context
304 */
305 @Override
306 public void update(final Graphics gr) {
307 // The default update clears the area. Don't do that, instead
308 // just paint it directly.
309 paint(gr);
310 }
311
1ac2ccb1
KL
312 /**
313 * Paint redraws the whole screen.
314 *
315 * @param gr the AWT Graphics context
316 */
317 @Override
84614868 318 public void paint(final Graphics gr) {
87a17f3c
KL
319 // Do nothing until the screen reference has been set.
320 if (screen == null) {
321 return;
322 }
323 if (screen.frame == null) {
324 return;
325 }
326
327 int xCellMin = 0;
328 int xCellMax = screen.width;
329 int yCellMin = 0;
330 int yCellMax = screen.height;
331
30bd4abd 332 Rectangle bounds = gr.getClipBounds();
87a17f3c
KL
333 if (bounds != null) {
334 // Only update what is in the bounds
335 xCellMin = screen.textColumn(bounds.x);
336 xCellMax = screen.textColumn(bounds.x + bounds.width) + 1;
337 if (xCellMax > screen.width) {
338 xCellMax = screen.width;
339 }
340 if (xCellMin >= xCellMax) {
341 xCellMin = xCellMax - 2;
342 }
343 if (xCellMin < 0) {
344 xCellMin = 0;
345 }
346 yCellMin = screen.textRow(bounds.y);
347 yCellMax = screen.textRow(bounds.y + bounds.height) + 1;
348 if (yCellMax > screen.height) {
349 yCellMax = screen.height;
350 }
351 if (yCellMin >= yCellMax) {
352 yCellMin = yCellMax - 2;
353 }
354 if (yCellMin < 0) {
355 yCellMin = 0;
356 }
357 }
1ac2ccb1 358
87a17f3c
KL
359 // Prevent updates to the screen's data from the TApplication
360 // threads.
361 synchronized (screen) {
362 /*
363 System.err.printf("bounds %s X %d %d Y %d %d\n",
364 bounds, xCellMin, xCellMax, yCellMin, yCellMax);
365 */
366
367 for (int y = yCellMin; y < yCellMax; y++) {
368 for (int x = xCellMin; x < xCellMax; x++) {
369
370 int xPixel = x * textWidth + left;
371 int yPixel = y * textHeight + top;
372
373 Cell lCell = screen.logical[x][y];
374 Cell pCell = screen.physical[x][y];
375
376 if (!lCell.equals(pCell) || reallyCleared) {
377 // Draw the background rectangle, then the
378 // foreground character.
379 gr.setColor(attrToBackgroundColor(lCell));
380 gr.fillRect(xPixel, yPixel, textWidth, textHeight);
381 gr.setColor(attrToForegroundColor(lCell));
382 char [] chars = new char[1];
383 chars[0] = lCell.getChar();
384 gr.drawChars(chars, 0, 1, xPixel,
385 yPixel + textHeight - maxDescent);
386
387 // Physical is always updated
388 physical[x][y].setTo(lCell);
30bd4abd
KL
389 }
390 }
87a17f3c 391 }
84614868 392
87a17f3c
KL
393 // Draw the cursor if it is visible
394 if ((cursorVisible)
395 && (cursorY <= screen.height - 1)
396 && (cursorX <= screen.width - 1)
397 ) {
398 int xPixel = cursorX * textWidth + left;
399 int yPixel = cursorY * textHeight + top;
400 Cell lCell = screen.logical[cursorX][cursorY];
401 gr.setColor(attrToForegroundColor(lCell));
402 gr.fillRect(xPixel, yPixel + textHeight - 2, textWidth, 2);
1ac2ccb1 403 }
30bd4abd 404
87a17f3c
KL
405 dirty = false;
406 reallyCleared = false;
407 } // synchronized (screen)
1ac2ccb1
KL
408 }
409 }
410
411 /**
84614868 412 * The raw AWT Frame. Note package private access.
1ac2ccb1 413 */
84614868 414 AWTFrame frame;
1ac2ccb1
KL
415
416 /**
417 * Public constructor.
418 */
419 public AWTScreen() {
84614868 420 frame = new AWTFrame(this);
1ac2ccb1
KL
421 }
422
30bd4abd
KL
423 /**
424 * Create the AWTSessionInfo. Note package private access.
425 *
426 * @return the sessionInfo
427 */
428 AWTSessionInfo getSessionInfo() {
429 AWTSessionInfo sessionInfo = new AWTSessionInfo(frame, frame.textWidth,
430 frame.textHeight);
431 return sessionInfo;
432 }
433
1ac2ccb1
KL
434 /**
435 * Push the logical screen to the physical device.
436 */
437 @Override
438 public void flushPhysical() {
87a17f3c
KL
439
440 if (reallyCleared) {
441 // Really refreshed, do it all
442 frame.repaint();
443 return;
444 }
445
446 // Do nothing if nothing happened.
447 if (!dirty) {
448 return;
449 }
450
30bd4abd
KL
451 // Request a repaint, let the frame's repaint/update methods do the
452 // right thing.
87a17f3c 453
30bd4abd
KL
454 // Find the minimum-size damaged region.
455 int xMin = frame.getWidth();
456 int xMax = 0;
457 int yMin = frame.getHeight();
458 int yMax = 0;
30bd4abd 459
87a17f3c
KL
460 synchronized (this) {
461 for (int y = 0; y < height; y++) {
462 for (int x = 0; x < width; x++) {
463 Cell lCell = logical[x][y];
464 Cell pCell = physical[x][y];
465
466 int xPixel = x * frame.textWidth + frame.left;
467 int yPixel = y * frame.textHeight + frame.top;
468
469 if (!lCell.equals(pCell)
470 || ((x == cursorX)
471 && (y == cursorY)
472 && cursorVisible)
473 ) {
474 if (xPixel < xMin) {
475 xMin = xPixel;
476 }
477 if (xPixel + frame.textWidth > xMax) {
478 xMax = xPixel + frame.textWidth;
479 }
480 if (yPixel < yMin) {
481 yMin = yPixel;
482 }
483 if (yPixel + frame.textHeight > yMax) {
484 yMax = yPixel + frame.textHeight;
485 }
30bd4abd
KL
486 }
487 }
488 }
489 }
87a17f3c
KL
490 if (xMin + frame.textWidth >= xMax) {
491 xMax += frame.textWidth;
492 }
493 if (yMin + frame.textHeight >= yMax) {
494 yMax += frame.textHeight;
495 }
30bd4abd 496
87a17f3c
KL
497 // Repaint the desired area
498 frame.repaint(xMin, yMin, xMax - xMin, yMax - yMin);
499 // System.err.printf("REPAINT X %d %d Y %d %d\n", xMin, xMax, yMin, yMax);
1ac2ccb1 500 }
30bd4abd
KL
501
502 /**
503 * Put the cursor at (x,y).
504 *
505 * @param visible if true, the cursor should be visible
506 * @param x column coordinate to put the cursor on
507 * @param y row coordinate to put the cursor on
508 */
509 @Override
510 public void putCursor(final boolean visible, final int x, final int y) {
511 if ((cursorVisible)
512 && (cursorY <= height - 1)
513 && (cursorX <= width - 1)
514 ) {
515 // Make the current cursor position dirty
87a17f3c 516 if (physical[cursorX][cursorY].getChar() == 'Q') {
30bd4abd
KL
517 physical[cursorX][cursorY].setChar('X');
518 } else {
87a17f3c 519 physical[cursorX][cursorY].setChar('Q');
30bd4abd
KL
520 }
521 }
522
523 super.putCursor(visible, x, y);
524 }
525
87a17f3c
KL
526 /**
527 * Convert pixel column position to text cell column position.
528 *
529 * @param x pixel column position
530 * @return text cell column position
531 */
532 public int textColumn(final int x) {
533 return ((x - frame.left) / frame.textWidth);
534 }
535
536 /**
537 * Convert pixel row position to text cell row position.
538 *
539 * @param y pixel row position
540 * @return text cell row position
541 */
542 public int textRow(final int y) {
543 return ((y - frame.top) / frame.textHeight);
544 }
545
1ac2ccb1 546}