reduce thread contention
[nikiroo-utils.git] / src / jexer / io / SwingScreen.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
1ac2ccb1
KL
33import java.awt.Color;
34import java.awt.Cursor;
35import java.awt.Font;
36import java.awt.FontMetrics;
1ac2ccb1 37import java.awt.Graphics;
84614868 38import java.awt.Insets;
30bd4abd
KL
39import java.awt.Point;
40import java.awt.Rectangle;
41import java.awt.Toolkit;
1ac2ccb1 42import java.awt.geom.Rectangle2D;
30bd4abd 43import java.awt.image.BufferedImage;
e3dfbd23 44import java.awt.image.BufferStrategy;
84614868 45import java.io.InputStream;
b5f2a6db 46import java.util.Date;
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
KL
59 /**
60 * If true, use double buffering thread.
61 */
62 private static final boolean doubleBuffer = true;
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
KL
146 /**
147 * The BufferStrategy object needed for double-buffering.
148 */
149 private BufferStrategy bufferStrategy;
150
1ac2ccb1
KL
151 /**
152 * The TUI Screen data.
153 */
a4406f4e 154 SwingScreen screen;
1ac2ccb1
KL
155
156 /**
157 * Width of a character cell.
158 */
159 private int textWidth = 1;
160
161 /**
162 * Height of a character cell.
163 */
164 private int textHeight = 1;
165
84614868
KL
166 /**
167 * Descent of a character cell.
168 */
169 private int maxDescent = 0;
170
1ac2ccb1 171 /**
b5f2a6db 172 * Top pixel absolute location.
1ac2ccb1
KL
173 */
174 private int top = 30;
84614868 175
1ac2ccb1 176 /**
b5f2a6db 177 * Left pixel absolute location.
1ac2ccb1
KL
178 */
179 private int left = 30;
84614868 180
b5f2a6db
KL
181 /**
182 * The cursor style to draw.
183 */
184 private CursorStyle cursorStyle = CursorStyle.UNDERLINE;
185
186 /**
187 * The number of millis to wait before switching the blink from
188 * visible to invisible.
189 */
190 private long blinkMillis = 500;
191
192 /**
193 * If true, the cursor should be visible right now based on the blink
194 * time.
195 */
196 private boolean cursorBlinkVisible = true;
197
198 /**
199 * The time that the blink last flipped from visible to invisible or
200 * from invisible to visible.
201 */
202 private long lastBlinkTime = 0;
203
84614868 204 /**
a4406f4e 205 * Convert a CellAttributes foreground color to an Swing Color.
84614868
KL
206 *
207 * @param attr the text attributes
a4406f4e 208 * @return the Swing Color
84614868
KL
209 */
210 private Color attrToForegroundColor(final CellAttributes attr) {
7c870d89 211 if (attr.isBold()) {
84614868
KL
212 if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
213 return MYBOLD_BLACK;
214 } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
215 return MYBOLD_RED;
216 } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
217 return MYBOLD_BLUE;
218 } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
219 return MYBOLD_GREEN;
220 } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
221 return MYBOLD_YELLOW;
222 } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
223 return MYBOLD_CYAN;
224 } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
225 return MYBOLD_MAGENTA;
226 } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
227 return MYBOLD_WHITE;
228 }
229 } else {
230 if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
231 return MYBLACK;
232 } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
233 return MYRED;
234 } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
235 return MYBLUE;
236 } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
237 return MYGREEN;
238 } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
239 return MYYELLOW;
240 } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
241 return MYCYAN;
242 } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
243 return MYMAGENTA;
244 } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
245 return MYWHITE;
246 }
247 }
248 throw new IllegalArgumentException("Invalid color: " + attr.getForeColor().getValue());
249 }
250
251 /**
a4406f4e 252 * Convert a CellAttributes background color to an Swing Color.
84614868
KL
253 *
254 * @param attr the text attributes
a4406f4e 255 * @return the Swing Color
84614868
KL
256 */
257 private Color attrToBackgroundColor(final CellAttributes attr) {
84614868
KL
258 if (attr.getBackColor().equals(jexer.bits.Color.BLACK)) {
259 return MYBLACK;
260 } else if (attr.getBackColor().equals(jexer.bits.Color.RED)) {
261 return MYRED;
262 } else if (attr.getBackColor().equals(jexer.bits.Color.BLUE)) {
263 return MYBLUE;
264 } else if (attr.getBackColor().equals(jexer.bits.Color.GREEN)) {
265 return MYGREEN;
266 } else if (attr.getBackColor().equals(jexer.bits.Color.YELLOW)) {
267 return MYYELLOW;
268 } else if (attr.getBackColor().equals(jexer.bits.Color.CYAN)) {
269 return MYCYAN;
270 } else if (attr.getBackColor().equals(jexer.bits.Color.MAGENTA)) {
271 return MYMAGENTA;
272 } else if (attr.getBackColor().equals(jexer.bits.Color.WHITE)) {
273 return MYWHITE;
274 }
275 throw new IllegalArgumentException("Invalid color: " + attr.getBackColor().getValue());
276 }
277
1ac2ccb1
KL
278 /**
279 * Public constructor.
84614868
KL
280 *
281 * @param screen the Screen that Backend talks to
1ac2ccb1 282 */
a4406f4e 283 public SwingFrame(final SwingScreen screen) {
84614868
KL
284 this.screen = screen;
285 setDOSColors();
286
b5f2a6db
KL
287 // Figure out my cursor style
288 String cursorStyleString = System.getProperty("jexer.Swing.cursorStyle",
289 "underline").toLowerCase();
290
291 if (cursorStyleString.equals("underline")) {
292 cursorStyle = CursorStyle.UNDERLINE;
293 } else if (cursorStyleString.equals("outline")) {
294 cursorStyle = CursorStyle.OUTLINE;
295 } else if (cursorStyleString.equals("block")) {
296 cursorStyle = CursorStyle.BLOCK;
297 }
298
1ac2ccb1 299 setTitle("Jexer Application");
aed33687 300 setBackground(Color.black);
84614868
KL
301
302 try {
30bd4abd 303 // Always try to use Terminus, the one decent font.
84614868
KL
304 ClassLoader loader = Thread.currentThread().getContextClassLoader();
305 InputStream in = loader.getResourceAsStream(FONTFILE);
306 Font terminusRoot = Font.createFont(Font.TRUETYPE_FONT, in);
307 Font terminus = terminusRoot.deriveFont(Font.PLAIN, 22);
308 setFont(terminus);
309 } catch (Exception e) {
310 e.printStackTrace();
311 // setFont(new Font("Liberation Mono", Font.PLAIN, 24));
312 setFont(new Font(Font.MONOSPACED, Font.PLAIN, 24));
313 }
aed33687 314 pack();
30bd4abd
KL
315
316 // Kill the X11 cursor
317 // Transparent 16 x 16 pixel cursor image.
318 BufferedImage cursorImg = new BufferedImage(16, 16,
319 BufferedImage.TYPE_INT_ARGB);
30bd4abd
KL
320 // Create a new blank cursor.
321 Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(
322 cursorImg, new Point(0, 0), "blank cursor");
323 setCursor(blankCursor);
091d8a06
KL
324
325 // Be capable of seeing Tab / Shift-Tab
326 setFocusTraversalKeysEnabled(false);
327
328 // Save the text cell width/height
bd8d51fa 329 getFontDimensions();
e3dfbd23
KL
330
331 // Setup double-buffering
332 if (screen.doubleBuffer) {
333 setIgnoreRepaint(true);
334 createBufferStrategy(2);
335 bufferStrategy = getBufferStrategy();
336 }
1ac2ccb1
KL
337 }
338
339 /**
bd8d51fa 340 * Figure out my font dimensions.
1ac2ccb1 341 */
bd8d51fa 342 private void getFontDimensions() {
84614868
KL
343 Graphics gr = getGraphics();
344 FontMetrics fm = gr.getFontMetrics();
345 maxDescent = fm.getMaxDescent();
346 Rectangle2D bounds = fm.getMaxCharBounds(gr);
347 int leading = fm.getLeading();
348 textWidth = (int)Math.round(bounds.getWidth());
349 textHeight = (int)Math.round(bounds.getHeight()) - maxDescent;
350 // This also produces the same number, but works better for ugly
351 // monospace.
352 textHeight = fm.getMaxAscent() + maxDescent - leading;
bd8d51fa 353 }
84614868 354
bd8d51fa
KL
355 /**
356 * Resize to font dimensions.
357 */
358 public void resizeToScreen() {
84614868
KL
359 // Figure out the thickness of borders and use that to set the
360 // final size.
361 Insets insets = getInsets();
362 left = insets.left;
363 top = insets.top;
364
365 setSize(textWidth * screen.width + insets.left + insets.right,
366 textHeight * screen.height + insets.top + insets.bottom);
1ac2ccb1
KL
367 }
368
30bd4abd
KL
369 /**
370 * Update redraws the whole screen.
371 *
a4406f4e 372 * @param gr the Swing Graphics context
30bd4abd
KL
373 */
374 @Override
375 public void update(final Graphics gr) {
376 // The default update clears the area. Don't do that, instead
377 // just paint it directly.
378 paint(gr);
379 }
380
1ac2ccb1
KL
381 /**
382 * Paint redraws the whole screen.
383 *
a4406f4e 384 * @param gr the Swing Graphics context
1ac2ccb1
KL
385 */
386 @Override
84614868 387 public void paint(final Graphics gr) {
87a17f3c
KL
388 // Do nothing until the screen reference has been set.
389 if (screen == null) {
390 return;
391 }
392 if (screen.frame == null) {
393 return;
394 }
395
b5f2a6db
KL
396 // See if it is time to flip the blink time.
397 long nowTime = (new Date()).getTime();
398 if (nowTime > blinkMillis + lastBlinkTime) {
399 lastBlinkTime = nowTime;
400 cursorBlinkVisible = !cursorBlinkVisible;
401 }
402
87a17f3c
KL
403 int xCellMin = 0;
404 int xCellMax = screen.width;
405 int yCellMin = 0;
406 int yCellMax = screen.height;
407
30bd4abd 408 Rectangle bounds = gr.getClipBounds();
87a17f3c
KL
409 if (bounds != null) {
410 // Only update what is in the bounds
411 xCellMin = screen.textColumn(bounds.x);
bd8d51fa 412 xCellMax = screen.textColumn(bounds.x + bounds.width);
87a17f3c
KL
413 if (xCellMax > screen.width) {
414 xCellMax = screen.width;
415 }
416 if (xCellMin >= xCellMax) {
417 xCellMin = xCellMax - 2;
418 }
419 if (xCellMin < 0) {
420 xCellMin = 0;
421 }
422 yCellMin = screen.textRow(bounds.y);
bd8d51fa 423 yCellMax = screen.textRow(bounds.y + bounds.height);
87a17f3c
KL
424 if (yCellMax > screen.height) {
425 yCellMax = screen.height;
426 }
427 if (yCellMin >= yCellMax) {
428 yCellMin = yCellMax - 2;
429 }
430 if (yCellMin < 0) {
431 yCellMin = 0;
432 }
bd8d51fa
KL
433 } else {
434 // We need a total repaint
435 reallyCleared = true;
87a17f3c 436 }
1ac2ccb1 437
87a17f3c
KL
438 // Prevent updates to the screen's data from the TApplication
439 // threads.
440 synchronized (screen) {
441 /*
442 System.err.printf("bounds %s X %d %d Y %d %d\n",
443 bounds, xCellMin, xCellMax, yCellMin, yCellMax);
444 */
445
446 for (int y = yCellMin; y < yCellMax; y++) {
447 for (int x = xCellMin; x < xCellMax; x++) {
448
449 int xPixel = x * textWidth + left;
450 int yPixel = y * textHeight + top;
451
452 Cell lCell = screen.logical[x][y];
453 Cell pCell = screen.physical[x][y];
454
455 if (!lCell.equals(pCell) || reallyCleared) {
e3dfbd23
KL
456
457 /*
458 * TODO:
459 * reverse
460 * blink
461 * underline
462 */
463
87a17f3c
KL
464 // Draw the background rectangle, then the
465 // foreground character.
466 gr.setColor(attrToBackgroundColor(lCell));
467 gr.fillRect(xPixel, yPixel, textWidth, textHeight);
468 gr.setColor(attrToForegroundColor(lCell));
469 char [] chars = new char[1];
470 chars[0] = lCell.getChar();
471 gr.drawChars(chars, 0, 1, xPixel,
472 yPixel + textHeight - maxDescent);
473
474 // Physical is always updated
475 physical[x][y].setTo(lCell);
30bd4abd
KL
476 }
477 }
87a17f3c 478 }
84614868 479
87a17f3c 480 // Draw the cursor if it is visible
b5f2a6db 481 if (cursorVisible
87a17f3c
KL
482 && (cursorY <= screen.height - 1)
483 && (cursorX <= screen.width - 1)
b5f2a6db 484 && cursorBlinkVisible
87a17f3c
KL
485 ) {
486 int xPixel = cursorX * textWidth + left;
487 int yPixel = cursorY * textHeight + top;
488 Cell lCell = screen.logical[cursorX][cursorY];
489 gr.setColor(attrToForegroundColor(lCell));
b5f2a6db
KL
490 switch (cursorStyle) {
491 case UNDERLINE:
492 gr.fillRect(xPixel, yPixel + textHeight - 2,
493 textWidth, 2);
494 break;
495 case BLOCK:
496 gr.fillRect(xPixel, yPixel, textWidth, textHeight);
497 break;
498 case OUTLINE:
499 gr.drawRect(xPixel, yPixel, textWidth - 1,
500 textHeight - 1);
501 break;
502 }
1ac2ccb1 503 }
30bd4abd 504
87a17f3c
KL
505 dirty = false;
506 reallyCleared = false;
507 } // synchronized (screen)
1ac2ccb1 508 }
bd8d51fa 509
a4406f4e 510 } // class SwingFrame
1ac2ccb1
KL
511
512 /**
a4406f4e 513 * The raw Swing JFrame. Note package private access.
1ac2ccb1 514 */
a4406f4e 515 SwingFrame frame;
1ac2ccb1 516
e3dfbd23
KL
517 /**
518 * Restore terminal to normal state.
519 */
520 public void shutdown() {
521 frame.dispose();
522 }
523
1ac2ccb1
KL
524 /**
525 * Public constructor.
526 */
a4406f4e 527 public SwingScreen() {
aed33687
KL
528 try {
529 SwingUtilities.invokeAndWait(new Runnable() {
530 public void run() {
a4406f4e
KL
531 SwingScreen.this.frame = new SwingFrame(SwingScreen.this);
532 SwingScreen.this.sessionInfo =
533 new SwingSessionInfo(SwingScreen.this.frame,
bd8d51fa
KL
534 frame.textWidth,
535 frame.textHeight);
536
a4406f4e 537 SwingScreen.this.setDimensions(sessionInfo.getWindowWidth(),
bd8d51fa
KL
538 sessionInfo.getWindowHeight());
539
a4406f4e
KL
540 SwingScreen.this.frame.resizeToScreen();
541 SwingScreen.this.frame.setVisible(true);
aed33687
KL
542 }
543 } );
544 } catch (Exception e) {
545 e.printStackTrace();
546 }
1ac2ccb1
KL
547 }
548
bd8d51fa
KL
549 /**
550 * The sessionInfo.
551 */
a4406f4e 552 private SwingSessionInfo sessionInfo;
bd8d51fa 553
30bd4abd 554 /**
a4406f4e 555 * Create the SwingSessionInfo. Note package private access.
30bd4abd
KL
556 *
557 * @return the sessionInfo
558 */
a4406f4e 559 SwingSessionInfo getSessionInfo() {
30bd4abd
KL
560 return sessionInfo;
561 }
562
1ac2ccb1
KL
563 /**
564 * Push the logical screen to the physical device.
565 */
566 @Override
567 public void flushPhysical() {
87a17f3c
KL
568
569 if (reallyCleared) {
570 // Really refreshed, do it all
e3dfbd23
KL
571 if (doubleBuffer) {
572 Graphics gr = frame.bufferStrategy.getDrawGraphics();
573 frame.paint(gr);
574 gr.dispose();
575 frame.bufferStrategy.show();
576 Toolkit.getDefaultToolkit().sync();
577 } else {
578 frame.repaint();
579 }
87a17f3c
KL
580 return;
581 }
582
583 // Do nothing if nothing happened.
584 if (!dirty) {
585 return;
586 }
587
30bd4abd
KL
588 // Request a repaint, let the frame's repaint/update methods do the
589 // right thing.
87a17f3c 590
30bd4abd
KL
591 // Find the minimum-size damaged region.
592 int xMin = frame.getWidth();
593 int xMax = 0;
594 int yMin = frame.getHeight();
595 int yMax = 0;
30bd4abd 596
87a17f3c
KL
597 synchronized (this) {
598 for (int y = 0; y < height; y++) {
599 for (int x = 0; x < width; x++) {
600 Cell lCell = logical[x][y];
601 Cell pCell = physical[x][y];
602
603 int xPixel = x * frame.textWidth + frame.left;
604 int yPixel = y * frame.textHeight + frame.top;
605
606 if (!lCell.equals(pCell)
607 || ((x == cursorX)
608 && (y == cursorY)
609 && cursorVisible)
610 ) {
611 if (xPixel < xMin) {
612 xMin = xPixel;
613 }
614 if (xPixel + frame.textWidth > xMax) {
615 xMax = xPixel + frame.textWidth;
616 }
617 if (yPixel < yMin) {
618 yMin = yPixel;
619 }
620 if (yPixel + frame.textHeight > yMax) {
621 yMax = yPixel + frame.textHeight;
622 }
30bd4abd
KL
623 }
624 }
625 }
626 }
87a17f3c
KL
627 if (xMin + frame.textWidth >= xMax) {
628 xMax += frame.textWidth;
629 }
630 if (yMin + frame.textHeight >= yMax) {
631 yMax += frame.textHeight;
632 }
30bd4abd 633
87a17f3c 634 // Repaint the desired area
e3dfbd23
KL
635 // System.err.printf("REPAINT X %d %d Y %d %d\n", xMin, xMax,
636 // yMin, yMax);
637 if (doubleBuffer) {
638 Graphics gr = frame.bufferStrategy.getDrawGraphics();
639 Rectangle bounds = new Rectangle(xMin, yMin, xMax - xMin,
640 yMax - yMin);
641 gr.setClip(bounds);
642 frame.paint(gr);
643 gr.dispose();
644 frame.bufferStrategy.show();
645 Toolkit.getDefaultToolkit().sync();
646 } else {
647 frame.repaint(xMin, yMin, xMax - xMin, yMax - yMin);
648 }
1ac2ccb1 649 }
30bd4abd
KL
650
651 /**
652 * Put the cursor at (x,y).
653 *
654 * @param visible if true, the cursor should be visible
655 * @param x column coordinate to put the cursor on
656 * @param y row coordinate to put the cursor on
657 */
658 @Override
659 public void putCursor(final boolean visible, final int x, final int y) {
b5f2a6db
KL
660
661 if ((visible == cursorVisible) && ((x == cursorX) && (y == cursorY))) {
662 // See if it is time to flip the blink time.
663 long nowTime = (new Date()).getTime();
664 if (nowTime < frame.blinkMillis + frame.lastBlinkTime) {
665 // Nothing has changed, so don't do anything.
666 return;
667 }
668 }
669
670 if (cursorVisible
30bd4abd
KL
671 && (cursorY <= height - 1)
672 && (cursorX <= width - 1)
673 ) {
674 // Make the current cursor position dirty
87a17f3c 675 if (physical[cursorX][cursorY].getChar() == 'Q') {
30bd4abd
KL
676 physical[cursorX][cursorY].setChar('X');
677 } else {
87a17f3c 678 physical[cursorX][cursorY].setChar('Q');
30bd4abd
KL
679 }
680 }
681
682 super.putCursor(visible, x, y);
683 }
684
87a17f3c
KL
685 /**
686 * Convert pixel column position to text cell column position.
687 *
688 * @param x pixel column position
689 * @return text cell column position
690 */
691 public int textColumn(final int x) {
692 return ((x - frame.left) / frame.textWidth);
693 }
694
695 /**
696 * Convert pixel row position to text cell row position.
697 *
698 * @param y pixel row position
699 * @return text cell row position
700 */
701 public int textRow(final int y) {
702 return ((y - frame.top) / frame.textHeight);
703 }
704
1ac2ccb1 705}