Merge commit '77d3a60869e7a780c6ae069e51530e1eacece5e2'
[fanfix.git] / src / jexer / backend / LogicalScreen.java
CommitLineData
daa4106c 1/*
df8de03f
KL
2 * Jexer - Java Text User Interface
3 *
e16dda65 4 * The MIT License (MIT)
df8de03f 5 *
a69ed767 6 * Copyright (C) 2019 Kevin Lamonte
df8de03f 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:
df8de03f 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.
df8de03f 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.
7b5261bc
KL
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
df8de03f 28 */
42873e30 29package jexer.backend;
df8de03f 30
3af53a35
KL
31import java.awt.image.BufferedImage;
32
33import jexer.backend.GlyphMaker;
df8de03f
KL
34import jexer.bits.Cell;
35import jexer.bits.CellAttributes;
54eaded0 36import jexer.bits.Clipboard;
df8de03f 37import jexer.bits.GraphicsChars;
3af53a35 38import jexer.bits.StringUtils;
df8de03f
KL
39
40/**
42873e30 41 * A logical screen composed of a 2D array of Cells.
df8de03f 42 */
42873e30 43public class LogicalScreen implements Screen {
df8de03f 44
d36057df
KL
45 // ------------------------------------------------------------------------
46 // Variables --------------------------------------------------------------
47 // ------------------------------------------------------------------------
48
df8de03f 49 /**
7b5261bc 50 * Width of the visible window.
df8de03f
KL
51 */
52 protected int width;
53
54 /**
7b5261bc 55 * Height of the visible window.
df8de03f
KL
56 */
57 protected int height;
58
59 /**
7b5261bc 60 * Drawing offset for x.
df8de03f 61 */
fca67db0 62 private int offsetX;
48e27807 63
d36057df
KL
64 /**
65 * Drawing offset for y.
66 */
67 private int offsetY;
68
69 /**
70 * Ignore anything drawn right of clipRight.
71 */
72 private int clipRight;
73
74 /**
75 * Ignore anything drawn below clipBottom.
76 */
77 private int clipBottom;
78
79 /**
80 * Ignore anything drawn left of clipLeft.
81 */
82 private int clipLeft;
83
84 /**
85 * Ignore anything drawn above clipTop.
86 */
87 private int clipTop;
88
89 /**
90 * The physical screen last sent out on flush().
91 */
92 protected Cell [][] physical;
93
94 /**
95 * The logical screen being rendered to.
96 */
97 protected Cell [][] logical;
98
99 /**
100 * Set if the user explicitly wants to redraw everything starting with a
101 * ECMATerminal.clearAll().
102 */
103 protected boolean reallyCleared;
104
105 /**
106 * If true, the cursor is visible and should be placed onscreen at
107 * (cursorX, cursorY) during a call to flushPhysical().
108 */
109 protected boolean cursorVisible;
110
111 /**
112 * Cursor X position if visible.
113 */
114 protected int cursorX;
115
116 /**
117 * Cursor Y position if visible.
118 */
119 protected int cursorY;
120
3af53a35
KL
121 /**
122 * The last used height of a character cell in pixels, only used for
123 * full-width chars.
124 */
125 private int lastTextHeight = -1;
126
127 /**
128 * The glyph drawer for full-width chars.
129 */
130 private GlyphMaker glyphMaker = null;
131
d36057df
KL
132 // ------------------------------------------------------------------------
133 // Constructors -----------------------------------------------------------
134 // ------------------------------------------------------------------------
135
136 /**
137 * Public constructor. Sets everything to not-bold, white-on-black.
138 */
139 protected LogicalScreen() {
140 offsetX = 0;
141 offsetY = 0;
142 width = 80;
143 height = 24;
144 logical = null;
145 physical = null;
146 reallocate(width, height);
147 }
148
149 // ------------------------------------------------------------------------
150 // Screen -----------------------------------------------------------------
151 // ------------------------------------------------------------------------
152
03ae544a
KL
153 /**
154 * Get the width of a character cell in pixels.
155 *
156 * @return the width in pixels of a character cell
157 */
158 public int getTextWidth() {
159 // Default width is 16 pixels.
160 return 16;
161 }
162
163 /**
164 * Get the height of a character cell in pixels.
165 *
166 * @return the height in pixels of a character cell
167 */
168 public int getTextHeight() {
169 // Default height is 20 pixels.
170 return 20;
171 }
172
48e27807
KL
173 /**
174 * Set drawing offset for x.
175 *
176 * @param offsetX new drawing offset
177 */
178 public final void setOffsetX(final int offsetX) {
179 this.offsetX = offsetX;
180 }
df8de03f 181
48e27807
KL
182 /**
183 * Set drawing offset for y.
184 *
185 * @param offsetY new drawing offset
186 */
187 public final void setOffsetY(final int offsetY) {
188 this.offsetY = offsetY;
189 }
fca67db0 190
48e27807
KL
191 /**
192 * Get right drawing clipping boundary.
193 *
194 * @return drawing boundary
195 */
196 public final int getClipRight() {
197 return clipRight;
198 }
199
200 /**
201 * Set right drawing clipping boundary.
202 *
203 * @param clipRight new boundary
204 */
205 public final void setClipRight(final int clipRight) {
206 this.clipRight = clipRight;
207 }
df8de03f 208
48e27807
KL
209 /**
210 * Get bottom drawing clipping boundary.
211 *
212 * @return drawing boundary
213 */
214 public final int getClipBottom() {
215 return clipBottom;
216 }
217
218 /**
219 * Set bottom drawing clipping boundary.
220 *
221 * @param clipBottom new boundary
222 */
223 public final void setClipBottom(final int clipBottom) {
224 this.clipBottom = clipBottom;
225 }
df8de03f 226
48e27807
KL
227 /**
228 * Get left drawing clipping boundary.
229 *
230 * @return drawing boundary
231 */
232 public final int getClipLeft() {
233 return clipLeft;
234 }
235
236 /**
237 * Set left drawing clipping boundary.
238 *
239 * @param clipLeft new boundary
240 */
241 public final void setClipLeft(final int clipLeft) {
242 this.clipLeft = clipLeft;
243 }
df8de03f 244
48e27807
KL
245 /**
246 * Get top drawing clipping boundary.
247 *
248 * @return drawing boundary
249 */
250 public final int getClipTop() {
251 return clipTop;
252 }
253
254 /**
255 * Set top drawing clipping boundary.
256 *
257 * @param clipTop new boundary
258 */
259 public final void setClipTop(final int clipTop) {
260 this.clipTop = clipTop;
261 }
df8de03f 262
92554d64
KL
263 /**
264 * Get dirty flag.
265 *
266 * @return if true, the logical screen is not in sync with the physical
267 * screen
268 */
269 public final boolean isDirty() {
be72cb5c
KL
270 for (int x = 0; x < width; x++) {
271 for (int y = 0; y < height; y++) {
272 if (!logical[x][y].equals(physical[x][y])) {
273 return true;
274 }
275 if (logical[x][y].isBlink()) {
276 // Blinking screens are always dirty. There is
277 // opportunity for a Netscape blink tag joke here...
278 return true;
279 }
280 }
281 }
282
283 return false;
92554d64
KL
284 }
285
df8de03f
KL
286 /**
287 * Get the attributes at one location.
288 *
289 * @param x column coordinate. 0 is the left-most column.
290 * @param y row coordinate. 0 is the top-most row.
291 * @return attributes at (x, y)
292 */
fca67db0 293 public final CellAttributes getAttrXY(final int x, final int y) {
7b5261bc 294 CellAttributes attr = new CellAttributes();
30bd4abd
KL
295 if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) {
296 attr.setTo(logical[x][y]);
297 }
7b5261bc 298 return attr;
df8de03f
KL
299 }
300
3e074355
KL
301 /**
302 * Get the cell at one location.
303 *
304 * @param x column coordinate. 0 is the left-most column.
305 * @param y row coordinate. 0 is the top-most row.
306 * @return the character + attributes
307 */
308 public Cell getCharXY(final int x, final int y) {
309 Cell cell = new Cell();
310 if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) {
311 cell.setTo(logical[x][y]);
312 }
313 return cell;
314 }
315
df8de03f
KL
316 /**
317 * Set the attributes at one location.
318 *
319 * @param x column coordinate. 0 is the left-most column.
320 * @param y row coordinate. 0 is the top-most row.
321 * @param attr attributes to use (bold, foreColor, backColor)
df8de03f 322 */
fca67db0
KL
323 public final void putAttrXY(final int x, final int y,
324 final CellAttributes attr) {
325
7b5261bc 326 putAttrXY(x, y, attr, true);
df8de03f 327 }
7b5261bc 328
df8de03f
KL
329 /**
330 * Set the attributes at one location.
331 *
332 * @param x column coordinate. 0 is the left-most column.
333 * @param y row coordinate. 0 is the top-most row.
334 * @param attr attributes to use (bold, foreColor, backColor)
335 * @param clip if true, honor clipping/offset
336 */
a1879051
KL
337 public final void putAttrXY(final int x, final int y,
338 final CellAttributes attr, final boolean clip) {
7b5261bc
KL
339
340 int X = x;
341 int Y = y;
342
343 if (clip) {
344 if ((x < clipLeft)
345 || (x >= clipRight)
346 || (y < clipTop)
347 || (y >= clipBottom)
348 ) {
349 return;
350 }
351 X += offsetX;
352 Y += offsetY;
353 }
354
355 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
be72cb5c 356 logical[X][Y].setTo(attr);
24489803
KL
357
358 // If this happens to be the cursor position, make the position
359 // dirty.
360 if ((cursorX == X) && (cursorY == Y)) {
a69ed767
KL
361 physical[cursorX][cursorY].unset();
362 unsetImageRow(cursorY);
24489803 363 }
7b5261bc 364 }
df8de03f
KL
365 }
366
367 /**
368 * Fill the entire screen with one character with attributes.
369 *
370 * @param ch character to draw
371 * @param attr attributes to use (bold, foreColor, backColor)
372 */
218d18db 373 public final void putAll(final int ch, final CellAttributes attr) {
87a17f3c 374
7b5261bc
KL
375 for (int x = 0; x < width; x++) {
376 for (int y = 0; y < height; y++) {
377 putCharXY(x, y, ch, attr);
378 }
379 }
df8de03f
KL
380 }
381
382 /**
383 * Render one character with attributes.
384 *
385 * @param x column coordinate. 0 is the left-most column.
386 * @param y row coordinate. 0 is the top-most row.
387 * @param ch character + attributes to draw
388 */
fca67db0 389 public final void putCharXY(final int x, final int y, final Cell ch) {
a69ed767
KL
390 if ((x < clipLeft)
391 || (x >= clipRight)
392 || (y < clipTop)
393 || (y >= clipBottom)
394 ) {
395 return;
396 }
397
3af53a35
KL
398 if ((StringUtils.width(ch.getChar()) == 2) && (!ch.isImage())) {
399 putFullwidthCharXY(x, y, ch);
400 return;
401 }
402
a69ed767
KL
403 int X = x + offsetX;
404 int Y = y + offsetY;
405
406 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
407
408 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
409
410 // Do not put control characters on the display
411 if (!ch.isImage()) {
412 assert (ch.getChar() >= 0x20);
413 assert (ch.getChar() != 0x7F);
414 }
415 logical[X][Y].setTo(ch);
416
417 // If this happens to be the cursor position, make the position
418 // dirty.
419 if ((cursorX == X) && (cursorY == Y)) {
420 physical[cursorX][cursorY].unset();
421 unsetImageRow(cursorY);
422 }
423 }
df8de03f
KL
424 }
425
426 /**
427 * Render one character with attributes.
428 *
429 * @param x column coordinate. 0 is the left-most column.
430 * @param y row coordinate. 0 is the top-most row.
431 * @param ch character to draw
432 * @param attr attributes to use (bold, foreColor, backColor)
433 */
218d18db 434 public final void putCharXY(final int x, final int y, final int ch,
7b5261bc
KL
435 final CellAttributes attr) {
436
437 if ((x < clipLeft)
438 || (x >= clipRight)
439 || (y < clipTop)
440 || (y >= clipBottom)
441 ) {
442 return;
443 }
444
3af53a35
KL
445 if (StringUtils.width(ch) == 2) {
446 putFullwidthCharXY(x, y, ch, attr);
447 return;
448 }
449
7b5261bc
KL
450 int X = x + offsetX;
451 int Y = y + offsetY;
452
453 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
454
455 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
7b5261bc
KL
456
457 // Do not put control characters on the display
458 assert (ch >= 0x20);
459 assert (ch != 0x7F);
460
be72cb5c 461 logical[X][Y].setTo(attr);
7b5261bc 462 logical[X][Y].setChar(ch);
24489803
KL
463
464 // If this happens to be the cursor position, make the position
465 // dirty.
466 if ((cursorX == X) && (cursorY == Y)) {
a69ed767
KL
467 physical[cursorX][cursorY].unset();
468 unsetImageRow(cursorY);
24489803 469 }
7b5261bc 470 }
df8de03f
KL
471 }
472
473 /**
474 * Render one character without changing the underlying attributes.
475 *
476 * @param x column coordinate. 0 is the left-most column.
477 * @param y row coordinate. 0 is the top-most row.
478 * @param ch character to draw
479 */
218d18db 480 public final void putCharXY(final int x, final int y, final int ch) {
7b5261bc
KL
481 if ((x < clipLeft)
482 || (x >= clipRight)
483 || (y < clipTop)
484 || (y >= clipBottom)
485 ) {
486 return;
487 }
df8de03f 488
3af53a35
KL
489 if (StringUtils.width(ch) == 2) {
490 putFullwidthCharXY(x, y, ch);
491 return;
492 }
493
7b5261bc
KL
494 int X = x + offsetX;
495 int Y = y + offsetY;
df8de03f 496
7b5261bc 497 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
df8de03f 498
7b5261bc 499 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
7b5261bc 500 logical[X][Y].setChar(ch);
24489803
KL
501
502 // If this happens to be the cursor position, make the position
503 // dirty.
504 if ((cursorX == X) && (cursorY == Y)) {
a69ed767
KL
505 physical[cursorX][cursorY].unset();
506 unsetImageRow(cursorY);
24489803 507 }
7b5261bc 508 }
df8de03f
KL
509 }
510
511 /**
512 * Render a string. Does not wrap if the string exceeds the line.
513 *
514 * @param x column coordinate. 0 is the left-most column.
515 * @param y row coordinate. 0 is the top-most row.
516 * @param str string to draw
517 * @param attr attributes to use (bold, foreColor, backColor)
518 */
0d47c546 519 public final void putStringXY(final int x, final int y, final String str,
7b5261bc
KL
520 final CellAttributes attr) {
521
522 int i = x;
218d18db
KL
523 for (int j = 0; j < str.length();) {
524 int ch = str.codePointAt(j);
525 j += Character.charCount(ch);
7b5261bc 526 putCharXY(i, y, ch, attr);
97bc3f29 527 i += StringUtils.width(ch);
7b5261bc
KL
528 if (i == width) {
529 break;
530 }
531 }
df8de03f
KL
532 }
533
534 /**
535 * Render a string without changing the underlying attribute. Does not
536 * wrap if the string exceeds the line.
537 *
538 * @param x column coordinate. 0 is the left-most column.
539 * @param y row coordinate. 0 is the top-most row.
540 * @param str string to draw
541 */
0d47c546 542 public final void putStringXY(final int x, final int y, final String str) {
87a17f3c 543
7b5261bc 544 int i = x;
218d18db
KL
545 for (int j = 0; j < str.length();) {
546 int ch = str.codePointAt(j);
547 j += Character.charCount(ch);
7b5261bc 548 putCharXY(i, y, ch);
97bc3f29 549 i += StringUtils.width(ch);
7b5261bc
KL
550 if (i == width) {
551 break;
552 }
553 }
df8de03f
KL
554 }
555
556 /**
7b5261bc 557 * Draw a vertical line from (x, y) to (x, y + n).
df8de03f
KL
558 *
559 * @param x column coordinate. 0 is the left-most column.
560 * @param y row coordinate. 0 is the top-most row.
561 * @param n number of characters to draw
562 * @param ch character to draw
563 * @param attr attributes to use (bold, foreColor, backColor)
564 */
fca67db0 565 public final void vLineXY(final int x, final int y, final int n,
218d18db 566 final int ch, final CellAttributes attr) {
7b5261bc
KL
567
568 for (int i = y; i < y + n; i++) {
569 putCharXY(x, i, ch, attr);
570 }
df8de03f
KL
571 }
572
573 /**
7b5261bc 574 * Draw a horizontal line from (x, y) to (x + n, y).
df8de03f
KL
575 *
576 * @param x column coordinate. 0 is the left-most column.
577 * @param y row coordinate. 0 is the top-most row.
578 * @param n number of characters to draw
579 * @param ch character to draw
580 * @param attr attributes to use (bold, foreColor, backColor)
581 */
fca67db0 582 public final void hLineXY(final int x, final int y, final int n,
218d18db 583 final int ch, final CellAttributes attr) {
7b5261bc
KL
584
585 for (int i = x; i < x + n; i++) {
586 putCharXY(i, y, ch, attr);
587 }
df8de03f
KL
588 }
589
df8de03f
KL
590 /**
591 * Change the width. Everything on-screen will be destroyed and must be
592 * redrawn.
7b5261bc 593 *
df8de03f
KL
594 * @param width new screen width
595 */
87a17f3c 596 public final synchronized void setWidth(final int width) {
7b5261bc 597 reallocate(width, this.height);
df8de03f
KL
598 }
599
600 /**
601 * Change the height. Everything on-screen will be destroyed and must be
602 * redrawn.
603 *
604 * @param height new screen height
605 */
87a17f3c 606 public final synchronized void setHeight(final int height) {
7b5261bc 607 reallocate(this.width, height);
df8de03f
KL
608 }
609
610 /**
611 * Change the width and height. Everything on-screen will be destroyed
612 * and must be redrawn.
613 *
614 * @param width new screen width
615 * @param height new screen height
616 */
fca67db0 617 public final void setDimensions(final int width, final int height) {
7b5261bc 618 reallocate(width, height);
9696a8f6
KL
619 resizeToScreen();
620 }
621
622 /**
623 * Resize the physical screen to match the logical screen dimensions.
624 */
625 public void resizeToScreen() {
626 // Subclasses are expected to override this.
df8de03f
KL
627 }
628
629 /**
630 * Get the height.
631 *
632 * @return current screen height
633 */
87a17f3c 634 public final synchronized int getHeight() {
7b5261bc 635 return this.height;
df8de03f
KL
636 }
637
638 /**
639 * Get the width.
640 *
641 * @return current screen width
642 */
87a17f3c 643 public final synchronized int getWidth() {
7b5261bc 644 return this.width;
df8de03f
KL
645 }
646
df8de03f
KL
647 /**
648 * Reset screen to not-bold, white-on-black. Also flushes the offset and
649 * clip variables.
650 */
87a17f3c 651 public final synchronized void reset() {
7b5261bc
KL
652 for (int row = 0; row < height; row++) {
653 for (int col = 0; col < width; col++) {
654 logical[col][row].reset();
655 }
656 }
657 resetClipping();
df8de03f
KL
658 }
659
660 /**
661 * Flush the offset and clip variables.
662 */
fca67db0 663 public final void resetClipping() {
7b5261bc
KL
664 offsetX = 0;
665 offsetY = 0;
666 clipLeft = 0;
667 clipTop = 0;
668 clipRight = width;
669 clipBottom = height;
df8de03f
KL
670 }
671
672 /**
bd8d51fa 673 * Clear the logical screen.
df8de03f 674 */
fca67db0 675 public final void clear() {
7b5261bc 676 reset();
df8de03f
KL
677 }
678
679 /**
680 * Draw a box with a border and empty background.
681 *
a69ed767 682 * @param left left column of box. 0 is the left-most column.
df8de03f
KL
683 * @param top top row of the box. 0 is the top-most row.
684 * @param right right column of box
685 * @param bottom bottom row of the box
7b5261bc 686 * @param border attributes to use for the border
df8de03f
KL
687 * @param background attributes to use for the background
688 */
fca67db0 689 public final void drawBox(final int left, final int top,
7b5261bc
KL
690 final int right, final int bottom,
691 final CellAttributes border, final CellAttributes background) {
692
693 drawBox(left, top, right, bottom, border, background, 1, false);
df8de03f
KL
694 }
695
696 /**
697 * Draw a box with a border and empty background.
698 *
a69ed767 699 * @param left left column of box. 0 is the left-most column.
df8de03f
KL
700 * @param top top row of the box. 0 is the top-most row.
701 * @param right right column of box
702 * @param bottom bottom row of the box
7b5261bc 703 * @param border attributes to use for the border
df8de03f 704 * @param background attributes to use for the background
7b5261bc
KL
705 * @param borderType if 1, draw a single-line border; if 2, draw a
706 * double-line border; if 3, draw double-line top/bottom edges and
707 * single-line left/right edges (like Qmodem)
df8de03f
KL
708 * @param shadow if true, draw a "shadow" on the box
709 */
fca67db0 710 public final void drawBox(final int left, final int top,
7b5261bc
KL
711 final int right, final int bottom,
712 final CellAttributes border, final CellAttributes background,
713 final int borderType, final boolean shadow) {
714
7b5261bc
KL
715 int boxWidth = right - left;
716 int boxHeight = bottom - top;
717
718 char cTopLeft;
719 char cTopRight;
720 char cBottomLeft;
721 char cBottomRight;
722 char cHSide;
723 char cVSide;
724
725 switch (borderType) {
726 case 1:
727 cTopLeft = GraphicsChars.ULCORNER;
728 cTopRight = GraphicsChars.URCORNER;
729 cBottomLeft = GraphicsChars.LLCORNER;
730 cBottomRight = GraphicsChars.LRCORNER;
731 cHSide = GraphicsChars.SINGLE_BAR;
732 cVSide = GraphicsChars.WINDOW_SIDE;
733 break;
734
735 case 2:
736 cTopLeft = GraphicsChars.WINDOW_LEFT_TOP_DOUBLE;
737 cTopRight = GraphicsChars.WINDOW_RIGHT_TOP_DOUBLE;
738 cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM_DOUBLE;
739 cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM_DOUBLE;
740 cHSide = GraphicsChars.DOUBLE_BAR;
741 cVSide = GraphicsChars.WINDOW_SIDE_DOUBLE;
742 break;
743
744 case 3:
745 cTopLeft = GraphicsChars.WINDOW_LEFT_TOP;
746 cTopRight = GraphicsChars.WINDOW_RIGHT_TOP;
747 cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM;
748 cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM;
749 cHSide = GraphicsChars.WINDOW_TOP;
750 cVSide = GraphicsChars.WINDOW_SIDE;
751 break;
752 default:
753 throw new IllegalArgumentException("Invalid border type: "
754 + borderType);
755 }
756
757 // Place the corner characters
758 putCharXY(left, top, cTopLeft, border);
759 putCharXY(left + boxWidth - 1, top, cTopRight, border);
760 putCharXY(left, top + boxHeight - 1, cBottomLeft, border);
761 putCharXY(left + boxWidth - 1, top + boxHeight - 1, cBottomRight,
762 border);
763
764 // Draw the box lines
765 hLineXY(left + 1, top, boxWidth - 2, cHSide, border);
766 vLineXY(left, top + 1, boxHeight - 2, cVSide, border);
767 hLineXY(left + 1, top + boxHeight - 1, boxWidth - 2, cHSide, border);
768 vLineXY(left + boxWidth - 1, top + 1, boxHeight - 2, cVSide, border);
769
770 // Fill in the interior background
771 for (int i = 1; i < boxHeight - 1; i++) {
772 hLineXY(1 + left, i + top, boxWidth - 2, ' ', background);
773 }
774
775 if (shadow) {
776 // Draw a shadow
777 drawBoxShadow(left, top, right, bottom);
778 }
df8de03f
KL
779 }
780
781 /**
fca67db0 782 * Draw a box shadow.
df8de03f 783 *
a69ed767 784 * @param left left column of box. 0 is the left-most column.
df8de03f
KL
785 * @param top top row of the box. 0 is the top-most row.
786 * @param right right column of box
787 * @param bottom bottom row of the box
788 */
fca67db0 789 public final void drawBoxShadow(final int left, final int top,
7b5261bc
KL
790 final int right, final int bottom) {
791
792 int boxTop = top;
793 int boxLeft = left;
794 int boxWidth = right - left;
795 int boxHeight = bottom - top;
796 CellAttributes shadowAttr = new CellAttributes();
797
798 // Shadows do not honor clipping but they DO honor offset.
799 int oldClipRight = clipRight;
800 int oldClipBottom = clipBottom;
a69ed767
KL
801 // When offsetX or offsetY go negative, we need to increase the clip
802 // bounds.
803 clipRight = width - offsetX;
804 clipBottom = height - offsetY;
7b5261bc
KL
805
806 for (int i = 0; i < boxHeight; i++) {
3fe82fa7
KL
807 Cell cell = getCharXY(offsetX + boxLeft + boxWidth,
808 offsetY + boxTop + 1 + i);
809 if (cell.getWidth() == Cell.Width.SINGLE) {
810 putAttrXY(boxLeft + boxWidth, boxTop + 1 + i, shadowAttr);
811 } else {
812 putCharXY(boxLeft + boxWidth, boxTop + 1 + i, ' ', shadowAttr);
813 }
814 cell = getCharXY(offsetX + boxLeft + boxWidth + 1,
815 offsetY + boxTop + 1 + i);
816 if (cell.getWidth() == Cell.Width.SINGLE) {
817 putAttrXY(boxLeft + boxWidth + 1, boxTop + 1 + i, shadowAttr);
818 } else {
819 putCharXY(boxLeft + boxWidth + 1, boxTop + 1 + i, ' ',
820 shadowAttr);
821 }
7b5261bc
KL
822 }
823 for (int i = 0; i < boxWidth; i++) {
3fe82fa7
KL
824 Cell cell = getCharXY(offsetX + boxLeft + 2 + i,
825 offsetY + boxTop + boxHeight);
826 if (cell.getWidth() == Cell.Width.SINGLE) {
827 putAttrXY(boxLeft + 2 + i, boxTop + boxHeight, shadowAttr);
828 } else {
829 putCharXY(boxLeft + 2 + i, boxTop + boxHeight, ' ', shadowAttr);
830 }
7b5261bc
KL
831 }
832 clipRight = oldClipRight;
833 clipBottom = oldClipBottom;
df8de03f
KL
834 }
835
836 /**
42873e30 837 * Default implementation does nothing.
df8de03f 838 */
42873e30 839 public void flushPhysical() {}
df8de03f
KL
840
841 /**
842 * Put the cursor at (x,y).
843 *
844 * @param visible if true, the cursor should be visible
845 * @param x column coordinate to put the cursor on
846 * @param y row coordinate to put the cursor on
847 */
30bd4abd 848 public void putCursor(final boolean visible, final int x, final int y) {
be72cb5c
KL
849 if ((cursorY >= 0)
850 && (cursorX >= 0)
851 && (cursorY <= height - 1)
852 && (cursorX <= width - 1)
853 ) {
854 // Make the current cursor position dirty
a69ed767
KL
855 physical[cursorX][cursorY].unset();
856 unsetImageRow(cursorY);
be72cb5c 857 }
fca67db0 858
7b5261bc
KL
859 cursorVisible = visible;
860 cursorX = x;
861 cursorY = y;
df8de03f
KL
862 }
863
864 /**
fca67db0 865 * Hide the cursor.
df8de03f 866 */
fca67db0 867 public final void hideCursor() {
7b5261bc 868 cursorVisible = false;
df8de03f 869 }
42873e30 870
3e074355
KL
871 /**
872 * Get the cursor visibility.
873 *
874 * @return true if the cursor is visible
875 */
876 public boolean isCursorVisible() {
877 return cursorVisible;
878 }
879
880 /**
881 * Get the cursor X position.
882 *
883 * @return the cursor x column position
884 */
885 public int getCursorX() {
886 return cursorX;
887 }
888
889 /**
890 * Get the cursor Y position.
891 *
892 * @return the cursor y row position
893 */
894 public int getCursorY() {
895 return cursorY;
896 }
897
42873e30
KL
898 /**
899 * Set the window title. Default implementation does nothing.
900 *
901 * @param title the new title
902 */
903 public void setTitle(final String title) {}
904
d36057df
KL
905 // ------------------------------------------------------------------------
906 // LogicalScreen ----------------------------------------------------------
907 // ------------------------------------------------------------------------
908
909 /**
910 * Reallocate screen buffers.
911 *
912 * @param width new width
913 * @param height new height
914 */
915 private synchronized void reallocate(final int width, final int height) {
916 if (logical != null) {
917 for (int row = 0; row < this.height; row++) {
918 for (int col = 0; col < this.width; col++) {
919 logical[col][row] = null;
920 }
921 }
922 logical = null;
923 }
924 logical = new Cell[width][height];
925 if (physical != null) {
926 for (int row = 0; row < this.height; row++) {
927 for (int col = 0; col < this.width; col++) {
928 physical[col][row] = null;
929 }
930 }
931 physical = null;
932 }
933 physical = new Cell[width][height];
934
935 for (int row = 0; row < height; row++) {
936 for (int col = 0; col < width; col++) {
937 physical[col][row] = new Cell();
938 logical[col][row] = new Cell();
939 }
940 }
941
942 this.width = width;
943 this.height = height;
944
945 clipLeft = 0;
946 clipTop = 0;
947 clipRight = width;
948 clipBottom = height;
949
950 reallyCleared = true;
951 }
952
953 /**
954 * Clear the physical screen.
955 */
956 public final void clearPhysical() {
957 for (int row = 0; row < height; row++) {
958 for (int col = 0; col < width; col++) {
a69ed767
KL
959 physical[col][row].unset();
960 }
961 }
962 }
963
964 /**
965 * Unset every image cell on one row of the physical screen, forcing
966 * images on that row to be redrawn.
967 *
968 * @param y row coordinate. 0 is the top-most row.
969 */
970 public final void unsetImageRow(final int y) {
9696a8f6
KL
971 if ((y < 0) || (y >= height)) {
972 return;
973 }
a69ed767
KL
974 for (int x = 0; x < width; x++) {
975 if (logical[x][y].isImage()) {
976 physical[x][y].unset();
d36057df
KL
977 }
978 }
979 }
980
3af53a35
KL
981 /**
982 * Render one fullwidth cell.
983 *
984 * @param x column coordinate. 0 is the left-most column.
985 * @param y row coordinate. 0 is the top-most row.
986 * @param cell the cell to draw
987 */
988 public final void putFullwidthCharXY(final int x, final int y,
989 final Cell cell) {
990
bfa37f3b
KL
991 int cellWidth = getTextWidth();
992 int cellHeight = getTextHeight();
993
994 if (lastTextHeight != cellHeight) {
995 glyphMaker = GlyphMaker.getInstance(cellHeight);
996 lastTextHeight = cellHeight;
997 }
998 BufferedImage image = glyphMaker.getImage(cell, cellWidth * 2,
999 cellHeight);
1000 BufferedImage leftImage = image.getSubimage(0, 0, cellWidth,
1001 cellHeight);
1002 BufferedImage rightImage = image.getSubimage(cellWidth, 0, cellWidth,
1003 cellHeight);
3af53a35 1004
027de5ae 1005 Cell left = new Cell(cell);
3af53a35
KL
1006 left.setImage(leftImage);
1007 left.setWidth(Cell.Width.LEFT);
3af53a35
KL
1008 putCharXY(x, y, left);
1009
027de5ae 1010 Cell right = new Cell(cell);
3af53a35
KL
1011 right.setImage(rightImage);
1012 right.setWidth(Cell.Width.RIGHT);
3af53a35
KL
1013 putCharXY(x + 1, y, right);
1014 }
1015
1016 /**
1017 * Render one fullwidth character with attributes.
1018 *
1019 * @param x column coordinate. 0 is the left-most column.
1020 * @param y row coordinate. 0 is the top-most row.
1021 * @param ch character to draw
1022 * @param attr attributes to use (bold, foreColor, backColor)
1023 */
1024 public final void putFullwidthCharXY(final int x, final int y,
218d18db 1025 final int ch, final CellAttributes attr) {
3af53a35 1026
027de5ae 1027 Cell cell = new Cell(ch, attr);
3af53a35
KL
1028 putFullwidthCharXY(x, y, cell);
1029 }
1030
1031 /**
1032 * Render one fullwidth character with attributes.
1033 *
1034 * @param x column coordinate. 0 is the left-most column.
1035 * @param y row coordinate. 0 is the top-most row.
1036 * @param ch character to draw
1037 */
1038 public final void putFullwidthCharXY(final int x, final int y,
218d18db 1039 final int ch) {
3af53a35
KL
1040
1041 Cell cell = new Cell(ch);
1042 cell.setAttr(getAttrXY(x, y));
1043 putFullwidthCharXY(x, y, cell);
1044 }
1045
aac4f7d6
KL
1046 /**
1047 * Invert the cell color at a position, including both halves of a
1048 * double-width cell.
1049 *
1050 * @param x column position
1051 * @param y row position
1052 */
1053 public void invertCell(final int x, final int y) {
1054 invertCell(x, y, false);
1055 }
1056
1057 /**
1058 * Invert the cell color at a position.
1059 *
1060 * @param x column position
1061 * @param y row position
1062 * @param onlyThisCell if true, only invert this cell, otherwise invert
1063 * both halves of a double-width cell if necessary
1064 */
1065 public void invertCell(final int x, final int y,
1066 final boolean onlyThisCell) {
1067
1068 Cell cell = getCharXY(x, y);
1069 if (cell.isImage()) {
1070 cell.invertImage();
1071 }
1072 if (cell.getForeColorRGB() < 0) {
1073 cell.setForeColor(cell.getForeColor().invert());
1074 } else {
1075 cell.setForeColorRGB(cell.getForeColorRGB() ^ 0x00ffffff);
1076 }
1077 if (cell.getBackColorRGB() < 0) {
1078 cell.setBackColor(cell.getBackColor().invert());
1079 } else {
1080 cell.setBackColorRGB(cell.getBackColorRGB() ^ 0x00ffffff);
1081 }
1082 putCharXY(x, y, cell);
1083 if ((onlyThisCell == true) || (cell.getWidth() == Cell.Width.SINGLE)) {
1084 return;
1085 }
1086
1087 // This cell is one half of a fullwidth glyph. Invert the other
1088 // half.
1089 if (cell.getWidth() == Cell.Width.LEFT) {
1090 if (x < width - 1) {
1091 Cell rightHalf = getCharXY(x + 1, y);
1092 if (rightHalf.getWidth() == Cell.Width.RIGHT) {
1093 invertCell(x + 1, y, true);
1094 return;
1095 }
1096 }
1097 }
1098 if (cell.getWidth() == Cell.Width.RIGHT) {
1099 if (x > 0) {
1100 Cell leftHalf = getCharXY(x - 1, y);
1101 if (leftHalf.getWidth() == Cell.Width.LEFT) {
1102 invertCell(x - 1, y, true);
1103 }
1104 }
1105 }
1106 }
1107
54eaded0
KL
1108 /**
1109 * Set a selection area on the screen.
1110 *
1111 * @param x0 the starting X position of the selection
1112 * @param y0 the starting Y position of the selection
1113 * @param x1 the ending X position of the selection
1114 * @param y1 the ending Y position of the selection
1115 * @param rectangle if true, this is a rectangle select
1116 */
1117 public void setSelection(final int x0, final int y0,
1118 final int x1, final int y1, final boolean rectangle) {
1119
1120 int startX = x0;
1121 int startY = y0;
1122 int endX = x1;
1123 int endY = y1;
1124
8d3480c7
KL
1125 if (((x1 < x0) && (y1 == y0))
1126 || (y1 < y0)
54eaded0 1127 ) {
8d3480c7
KL
1128 // The user dragged from bottom-to-top and/or right-to-left.
1129 // Reverse the coordinates for the inverted section.
54eaded0
KL
1130 startX = x1;
1131 startY = y1;
1132 endX = x0;
1133 endY = y0;
1134 }
1135 if (rectangle) {
1136 for (int y = startY; y <= endY; y++) {
1137 for (int x = startX; x <= endX; x++) {
1138 invertCell(x, y);
1139 }
1140 }
1141 } else {
1142 if (endY > startY) {
1143 for (int x = startX; x < width; x++) {
1144 invertCell(x, startY);
1145 }
1146 for (int y = startY + 1; y < endY; y++) {
1147 for (int x = 0; x < width; x++) {
1148 invertCell(x, y);
1149 }
1150 }
1151 for (int x = 0; x <= endX; x++) {
1152 invertCell(x, endY);
1153 }
1154 } else {
1155 assert (startY == endY);
1156 for (int x = startX; x <= endX; x++) {
1157 invertCell(x, startY);
1158 }
1159 }
1160 }
1161 }
1162
1163 /**
1164 * Copy the screen selection area to the clipboard.
1165 *
1166 * @param clipboard the clipboard to use
1167 * @param x0 the starting X position of the selection
1168 * @param y0 the starting Y position of the selection
1169 * @param x1 the ending X position of the selection
1170 * @param y1 the ending Y position of the selection
1171 * @param rectangle if true, this is a rectangle select
1172 */
1173 public void copySelection(final Clipboard clipboard,
1174 final int x0, final int y0, final int x1, final int y1,
1175 final boolean rectangle) {
1176
1177 StringBuilder sb = new StringBuilder();
1178
1179 int startX = x0;
1180 int startY = y0;
1181 int endX = x1;
1182 int endY = y1;
1183
8d3480c7
KL
1184 if (((x1 < x0) && (y1 == y0))
1185 || (y1 < y0)
54eaded0 1186 ) {
8d3480c7
KL
1187 // The user dragged from bottom-to-top and/or right-to-left.
1188 // Reverse the coordinates for the inverted section.
54eaded0
KL
1189 startX = x1;
1190 startY = y1;
1191 endX = x0;
1192 endY = y0;
1193 }
1194 if (rectangle) {
1195 for (int y = startY; y <= endY; y++) {
1196 for (int x = startX; x <= endX; x++) {
1197 sb.append(Character.toChars(getCharXY(x, y).getChar()));
1198 }
1199 sb.append("\n");
1200 }
1201 } else {
1202 if (endY > startY) {
1203 for (int x = startX; x < width; x++) {
1204 sb.append(Character.toChars(getCharXY(x, startY).getChar()));
1205 }
1206 sb.append("\n");
1207 for (int y = startY + 1; y < endY; y++) {
1208 for (int x = 0; x < width; x++) {
1209 sb.append(Character.toChars(getCharXY(x, y).getChar()));
1210 }
1211 sb.append("\n");
1212 }
1213 for (int x = 0; x <= endX; x++) {
1214 sb.append(Character.toChars(getCharXY(x, endY).getChar()));
1215 }
1216 } else {
1217 assert (startY == endY);
1218 for (int x = startX; x <= endX; x++) {
1219 sb.append(Character.toChars(getCharXY(x, startY).getChar()));
1220 }
1221 }
1222 }
1223 clipboard.copyText(sb.toString());
1224 }
1225
df8de03f 1226}