refactor, sixel performance
[nikiroo-utils.git] / src / jexer / backend / LogicalScreen.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 Kevin Lamonte
7 *
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:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
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.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29 package jexer.backend;
30
31 import jexer.bits.Cell;
32 import jexer.bits.CellAttributes;
33 import jexer.bits.GraphicsChars;
34
35 /**
36 * A logical screen composed of a 2D array of Cells.
37 */
38 public class LogicalScreen implements Screen {
39
40 // ------------------------------------------------------------------------
41 // Variables --------------------------------------------------------------
42 // ------------------------------------------------------------------------
43
44 /**
45 * Width of the visible window.
46 */
47 protected int width;
48
49 /**
50 * Height of the visible window.
51 */
52 protected int height;
53
54 /**
55 * Drawing offset for x.
56 */
57 private int offsetX;
58
59 /**
60 * Drawing offset for y.
61 */
62 private int offsetY;
63
64 /**
65 * Ignore anything drawn right of clipRight.
66 */
67 private int clipRight;
68
69 /**
70 * Ignore anything drawn below clipBottom.
71 */
72 private int clipBottom;
73
74 /**
75 * Ignore anything drawn left of clipLeft.
76 */
77 private int clipLeft;
78
79 /**
80 * Ignore anything drawn above clipTop.
81 */
82 private int clipTop;
83
84 /**
85 * The physical screen last sent out on flush().
86 */
87 protected Cell [][] physical;
88
89 /**
90 * The logical screen being rendered to.
91 */
92 protected Cell [][] logical;
93
94 /**
95 * Set if the user explicitly wants to redraw everything starting with a
96 * ECMATerminal.clearAll().
97 */
98 protected boolean reallyCleared;
99
100 /**
101 * If true, the cursor is visible and should be placed onscreen at
102 * (cursorX, cursorY) during a call to flushPhysical().
103 */
104 protected boolean cursorVisible;
105
106 /**
107 * Cursor X position if visible.
108 */
109 protected int cursorX;
110
111 /**
112 * Cursor Y position if visible.
113 */
114 protected int cursorY;
115
116 // ------------------------------------------------------------------------
117 // Constructors -----------------------------------------------------------
118 // ------------------------------------------------------------------------
119
120 /**
121 * Public constructor. Sets everything to not-bold, white-on-black.
122 */
123 protected LogicalScreen() {
124 offsetX = 0;
125 offsetY = 0;
126 width = 80;
127 height = 24;
128 logical = null;
129 physical = null;
130 reallocate(width, height);
131 }
132
133 // ------------------------------------------------------------------------
134 // Screen -----------------------------------------------------------------
135 // ------------------------------------------------------------------------
136
137 /**
138 * Get the width of a character cell in pixels.
139 *
140 * @return the width in pixels of a character cell
141 */
142 public int getTextWidth() {
143 // Default width is 16 pixels.
144 return 16;
145 }
146
147 /**
148 * Get the height of a character cell in pixels.
149 *
150 * @return the height in pixels of a character cell
151 */
152 public int getTextHeight() {
153 // Default height is 20 pixels.
154 return 20;
155 }
156
157 /**
158 * Set drawing offset for x.
159 *
160 * @param offsetX new drawing offset
161 */
162 public final void setOffsetX(final int offsetX) {
163 this.offsetX = offsetX;
164 }
165
166 /**
167 * Set drawing offset for y.
168 *
169 * @param offsetY new drawing offset
170 */
171 public final void setOffsetY(final int offsetY) {
172 this.offsetY = offsetY;
173 }
174
175 /**
176 * Get right drawing clipping boundary.
177 *
178 * @return drawing boundary
179 */
180 public final int getClipRight() {
181 return clipRight;
182 }
183
184 /**
185 * Set right drawing clipping boundary.
186 *
187 * @param clipRight new boundary
188 */
189 public final void setClipRight(final int clipRight) {
190 this.clipRight = clipRight;
191 }
192
193 /**
194 * Get bottom drawing clipping boundary.
195 *
196 * @return drawing boundary
197 */
198 public final int getClipBottom() {
199 return clipBottom;
200 }
201
202 /**
203 * Set bottom drawing clipping boundary.
204 *
205 * @param clipBottom new boundary
206 */
207 public final void setClipBottom(final int clipBottom) {
208 this.clipBottom = clipBottom;
209 }
210
211 /**
212 * Get left drawing clipping boundary.
213 *
214 * @return drawing boundary
215 */
216 public final int getClipLeft() {
217 return clipLeft;
218 }
219
220 /**
221 * Set left drawing clipping boundary.
222 *
223 * @param clipLeft new boundary
224 */
225 public final void setClipLeft(final int clipLeft) {
226 this.clipLeft = clipLeft;
227 }
228
229 /**
230 * Get top drawing clipping boundary.
231 *
232 * @return drawing boundary
233 */
234 public final int getClipTop() {
235 return clipTop;
236 }
237
238 /**
239 * Set top drawing clipping boundary.
240 *
241 * @param clipTop new boundary
242 */
243 public final void setClipTop(final int clipTop) {
244 this.clipTop = clipTop;
245 }
246
247 /**
248 * Get dirty flag.
249 *
250 * @return if true, the logical screen is not in sync with the physical
251 * screen
252 */
253 public final boolean isDirty() {
254 for (int x = 0; x < width; x++) {
255 for (int y = 0; y < height; y++) {
256 if (!logical[x][y].equals(physical[x][y])) {
257 return true;
258 }
259 if (logical[x][y].isBlink()) {
260 // Blinking screens are always dirty. There is
261 // opportunity for a Netscape blink tag joke here...
262 return true;
263 }
264 }
265 }
266
267 return false;
268 }
269
270 /**
271 * Get the attributes at one location.
272 *
273 * @param x column coordinate. 0 is the left-most column.
274 * @param y row coordinate. 0 is the top-most row.
275 * @return attributes at (x, y)
276 */
277 public final CellAttributes getAttrXY(final int x, final int y) {
278 CellAttributes attr = new CellAttributes();
279 if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) {
280 attr.setTo(logical[x][y]);
281 }
282 return attr;
283 }
284
285 /**
286 * Get the cell at one location.
287 *
288 * @param x column coordinate. 0 is the left-most column.
289 * @param y row coordinate. 0 is the top-most row.
290 * @return the character + attributes
291 */
292 public Cell getCharXY(final int x, final int y) {
293 Cell cell = new Cell();
294 if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) {
295 cell.setTo(logical[x][y]);
296 }
297 return cell;
298 }
299
300 /**
301 * Set the attributes at one location.
302 *
303 * @param x column coordinate. 0 is the left-most column.
304 * @param y row coordinate. 0 is the top-most row.
305 * @param attr attributes to use (bold, foreColor, backColor)
306 */
307 public final void putAttrXY(final int x, final int y,
308 final CellAttributes attr) {
309
310 putAttrXY(x, y, attr, true);
311 }
312
313 /**
314 * Set the attributes at one location.
315 *
316 * @param x column coordinate. 0 is the left-most column.
317 * @param y row coordinate. 0 is the top-most row.
318 * @param attr attributes to use (bold, foreColor, backColor)
319 * @param clip if true, honor clipping/offset
320 */
321 public final void putAttrXY(final int x, final int y,
322 final CellAttributes attr, final boolean clip) {
323
324 int X = x;
325 int Y = y;
326
327 if (clip) {
328 if ((x < clipLeft)
329 || (x >= clipRight)
330 || (y < clipTop)
331 || (y >= clipBottom)
332 ) {
333 return;
334 }
335 X += offsetX;
336 Y += offsetY;
337 }
338
339 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
340 logical[X][Y].setTo(attr);
341
342 // If this happens to be the cursor position, make the position
343 // dirty.
344 if ((cursorX == X) && (cursorY == Y)) {
345 physical[cursorX][cursorY].unset();
346 unsetImageRow(cursorY);
347 }
348 }
349 }
350
351 /**
352 * Fill the entire screen with one character with attributes.
353 *
354 * @param ch character to draw
355 * @param attr attributes to use (bold, foreColor, backColor)
356 */
357 public final void putAll(final char ch, final CellAttributes attr) {
358
359 for (int x = 0; x < width; x++) {
360 for (int y = 0; y < height; y++) {
361 putCharXY(x, y, ch, attr);
362 }
363 }
364 }
365
366 /**
367 * Render one character with attributes.
368 *
369 * @param x column coordinate. 0 is the left-most column.
370 * @param y row coordinate. 0 is the top-most row.
371 * @param ch character + attributes to draw
372 */
373 public final void putCharXY(final int x, final int y, final Cell ch) {
374 if ((x < clipLeft)
375 || (x >= clipRight)
376 || (y < clipTop)
377 || (y >= clipBottom)
378 ) {
379 return;
380 }
381
382 int X = x + offsetX;
383 int Y = y + offsetY;
384
385 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
386
387 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
388
389 // Do not put control characters on the display
390 if (!ch.isImage()) {
391 assert (ch.getChar() >= 0x20);
392 assert (ch.getChar() != 0x7F);
393 }
394 logical[X][Y].setTo(ch);
395
396 // If this happens to be the cursor position, make the position
397 // dirty.
398 if ((cursorX == X) && (cursorY == Y)) {
399 physical[cursorX][cursorY].unset();
400 unsetImageRow(cursorY);
401 }
402 }
403 }
404
405 /**
406 * Render one character with attributes.
407 *
408 * @param x column coordinate. 0 is the left-most column.
409 * @param y row coordinate. 0 is the top-most row.
410 * @param ch character to draw
411 * @param attr attributes to use (bold, foreColor, backColor)
412 */
413 public final void putCharXY(final int x, final int y, final char ch,
414 final CellAttributes attr) {
415
416 if ((x < clipLeft)
417 || (x >= clipRight)
418 || (y < clipTop)
419 || (y >= clipBottom)
420 ) {
421 return;
422 }
423
424 int X = x + offsetX;
425 int Y = y + offsetY;
426
427 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
428
429 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
430
431 // Do not put control characters on the display
432 assert (ch >= 0x20);
433 assert (ch != 0x7F);
434
435 logical[X][Y].setTo(attr);
436 logical[X][Y].setChar(ch);
437
438 // If this happens to be the cursor position, make the position
439 // dirty.
440 if ((cursorX == X) && (cursorY == Y)) {
441 physical[cursorX][cursorY].unset();
442 unsetImageRow(cursorY);
443 }
444 }
445 }
446
447 /**
448 * Render one character without changing the underlying attributes.
449 *
450 * @param x column coordinate. 0 is the left-most column.
451 * @param y row coordinate. 0 is the top-most row.
452 * @param ch character to draw
453 */
454 public final void putCharXY(final int x, final int y, final char ch) {
455
456 if ((x < clipLeft)
457 || (x >= clipRight)
458 || (y < clipTop)
459 || (y >= clipBottom)
460 ) {
461 return;
462 }
463
464 int X = x + offsetX;
465 int Y = y + offsetY;
466
467 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
468
469 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
470 logical[X][Y].setChar(ch);
471
472 // If this happens to be the cursor position, make the position
473 // dirty.
474 if ((cursorX == X) && (cursorY == Y)) {
475 physical[cursorX][cursorY].unset();
476 unsetImageRow(cursorY);
477 }
478 }
479 }
480
481 /**
482 * Render a string. Does not wrap if the string exceeds the line.
483 *
484 * @param x column coordinate. 0 is the left-most column.
485 * @param y row coordinate. 0 is the top-most row.
486 * @param str string to draw
487 * @param attr attributes to use (bold, foreColor, backColor)
488 */
489 public final void putStringXY(final int x, final int y, final String str,
490 final CellAttributes attr) {
491
492 int i = x;
493 for (int j = 0; j < str.length(); j++) {
494 char ch = str.charAt(j);
495 putCharXY(i, y, ch, attr);
496 i++;
497 if (i == width) {
498 break;
499 }
500 }
501 }
502
503 /**
504 * Render a string without changing the underlying attribute. Does not
505 * wrap if the string exceeds the line.
506 *
507 * @param x column coordinate. 0 is the left-most column.
508 * @param y row coordinate. 0 is the top-most row.
509 * @param str string to draw
510 */
511 public final void putStringXY(final int x, final int y, final String str) {
512
513 int i = x;
514 for (int j = 0; j < str.length(); j++) {
515 char ch = str.charAt(j);
516 putCharXY(i, y, ch);
517 i++;
518 if (i == width) {
519 break;
520 }
521 }
522 }
523
524 /**
525 * Draw a vertical line from (x, y) to (x, y + n).
526 *
527 * @param x column coordinate. 0 is the left-most column.
528 * @param y row coordinate. 0 is the top-most row.
529 * @param n number of characters to draw
530 * @param ch character to draw
531 * @param attr attributes to use (bold, foreColor, backColor)
532 */
533 public final void vLineXY(final int x, final int y, final int n,
534 final char ch, final CellAttributes attr) {
535
536 for (int i = y; i < y + n; i++) {
537 putCharXY(x, i, ch, attr);
538 }
539 }
540
541 /**
542 * Draw a horizontal line from (x, y) to (x + n, y).
543 *
544 * @param x column coordinate. 0 is the left-most column.
545 * @param y row coordinate. 0 is the top-most row.
546 * @param n number of characters to draw
547 * @param ch character to draw
548 * @param attr attributes to use (bold, foreColor, backColor)
549 */
550 public final void hLineXY(final int x, final int y, final int n,
551 final char ch, final CellAttributes attr) {
552
553 for (int i = x; i < x + n; i++) {
554 putCharXY(i, y, ch, attr);
555 }
556 }
557
558 /**
559 * Change the width. Everything on-screen will be destroyed and must be
560 * redrawn.
561 *
562 * @param width new screen width
563 */
564 public final synchronized void setWidth(final int width) {
565 reallocate(width, this.height);
566 }
567
568 /**
569 * Change the height. Everything on-screen will be destroyed and must be
570 * redrawn.
571 *
572 * @param height new screen height
573 */
574 public final synchronized void setHeight(final int height) {
575 reallocate(this.width, height);
576 }
577
578 /**
579 * Change the width and height. Everything on-screen will be destroyed
580 * and must be redrawn.
581 *
582 * @param width new screen width
583 * @param height new screen height
584 */
585 public final void setDimensions(final int width, final int height) {
586 reallocate(width, height);
587 resizeToScreen();
588 }
589
590 /**
591 * Resize the physical screen to match the logical screen dimensions.
592 */
593 public void resizeToScreen() {
594 // Subclasses are expected to override this.
595 }
596
597 /**
598 * Get the height.
599 *
600 * @return current screen height
601 */
602 public final synchronized int getHeight() {
603 return this.height;
604 }
605
606 /**
607 * Get the width.
608 *
609 * @return current screen width
610 */
611 public final synchronized int getWidth() {
612 return this.width;
613 }
614
615 /**
616 * Reset screen to not-bold, white-on-black. Also flushes the offset and
617 * clip variables.
618 */
619 public final synchronized void reset() {
620 for (int row = 0; row < height; row++) {
621 for (int col = 0; col < width; col++) {
622 logical[col][row].reset();
623 }
624 }
625 resetClipping();
626 }
627
628 /**
629 * Flush the offset and clip variables.
630 */
631 public final void resetClipping() {
632 offsetX = 0;
633 offsetY = 0;
634 clipLeft = 0;
635 clipTop = 0;
636 clipRight = width;
637 clipBottom = height;
638 }
639
640 /**
641 * Clear the logical screen.
642 */
643 public final void clear() {
644 reset();
645 }
646
647 /**
648 * Draw a box with a border and empty background.
649 *
650 * @param left left column of box. 0 is the left-most column.
651 * @param top top row of the box. 0 is the top-most row.
652 * @param right right column of box
653 * @param bottom bottom row of the box
654 * @param border attributes to use for the border
655 * @param background attributes to use for the background
656 */
657 public final void drawBox(final int left, final int top,
658 final int right, final int bottom,
659 final CellAttributes border, final CellAttributes background) {
660
661 drawBox(left, top, right, bottom, border, background, 1, false);
662 }
663
664 /**
665 * Draw a box with a border and empty background.
666 *
667 * @param left left column of box. 0 is the left-most column.
668 * @param top top row of the box. 0 is the top-most row.
669 * @param right right column of box
670 * @param bottom bottom row of the box
671 * @param border attributes to use for the border
672 * @param background attributes to use for the background
673 * @param borderType if 1, draw a single-line border; if 2, draw a
674 * double-line border; if 3, draw double-line top/bottom edges and
675 * single-line left/right edges (like Qmodem)
676 * @param shadow if true, draw a "shadow" on the box
677 */
678 public final void drawBox(final int left, final int top,
679 final int right, final int bottom,
680 final CellAttributes border, final CellAttributes background,
681 final int borderType, final boolean shadow) {
682
683 int boxWidth = right - left;
684 int boxHeight = bottom - top;
685
686 char cTopLeft;
687 char cTopRight;
688 char cBottomLeft;
689 char cBottomRight;
690 char cHSide;
691 char cVSide;
692
693 switch (borderType) {
694 case 1:
695 cTopLeft = GraphicsChars.ULCORNER;
696 cTopRight = GraphicsChars.URCORNER;
697 cBottomLeft = GraphicsChars.LLCORNER;
698 cBottomRight = GraphicsChars.LRCORNER;
699 cHSide = GraphicsChars.SINGLE_BAR;
700 cVSide = GraphicsChars.WINDOW_SIDE;
701 break;
702
703 case 2:
704 cTopLeft = GraphicsChars.WINDOW_LEFT_TOP_DOUBLE;
705 cTopRight = GraphicsChars.WINDOW_RIGHT_TOP_DOUBLE;
706 cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM_DOUBLE;
707 cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM_DOUBLE;
708 cHSide = GraphicsChars.DOUBLE_BAR;
709 cVSide = GraphicsChars.WINDOW_SIDE_DOUBLE;
710 break;
711
712 case 3:
713 cTopLeft = GraphicsChars.WINDOW_LEFT_TOP;
714 cTopRight = GraphicsChars.WINDOW_RIGHT_TOP;
715 cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM;
716 cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM;
717 cHSide = GraphicsChars.WINDOW_TOP;
718 cVSide = GraphicsChars.WINDOW_SIDE;
719 break;
720 default:
721 throw new IllegalArgumentException("Invalid border type: "
722 + borderType);
723 }
724
725 // Place the corner characters
726 putCharXY(left, top, cTopLeft, border);
727 putCharXY(left + boxWidth - 1, top, cTopRight, border);
728 putCharXY(left, top + boxHeight - 1, cBottomLeft, border);
729 putCharXY(left + boxWidth - 1, top + boxHeight - 1, cBottomRight,
730 border);
731
732 // Draw the box lines
733 hLineXY(left + 1, top, boxWidth - 2, cHSide, border);
734 vLineXY(left, top + 1, boxHeight - 2, cVSide, border);
735 hLineXY(left + 1, top + boxHeight - 1, boxWidth - 2, cHSide, border);
736 vLineXY(left + boxWidth - 1, top + 1, boxHeight - 2, cVSide, border);
737
738 // Fill in the interior background
739 for (int i = 1; i < boxHeight - 1; i++) {
740 hLineXY(1 + left, i + top, boxWidth - 2, ' ', background);
741 }
742
743 if (shadow) {
744 // Draw a shadow
745 drawBoxShadow(left, top, right, bottom);
746 }
747 }
748
749 /**
750 * Draw a box shadow.
751 *
752 * @param left left column of box. 0 is the left-most column.
753 * @param top top row of the box. 0 is the top-most row.
754 * @param right right column of box
755 * @param bottom bottom row of the box
756 */
757 public final void drawBoxShadow(final int left, final int top,
758 final int right, final int bottom) {
759
760 int boxTop = top;
761 int boxLeft = left;
762 int boxWidth = right - left;
763 int boxHeight = bottom - top;
764 CellAttributes shadowAttr = new CellAttributes();
765
766 // Shadows do not honor clipping but they DO honor offset.
767 int oldClipRight = clipRight;
768 int oldClipBottom = clipBottom;
769 // When offsetX or offsetY go negative, we need to increase the clip
770 // bounds.
771 clipRight = width - offsetX;
772 clipBottom = height - offsetY;
773
774 for (int i = 0; i < boxHeight; i++) {
775 putAttrXY(boxLeft + boxWidth, boxTop + 1 + i, shadowAttr);
776 putAttrXY(boxLeft + boxWidth + 1, boxTop + 1 + i, shadowAttr);
777 }
778 for (int i = 0; i < boxWidth; i++) {
779 putAttrXY(boxLeft + 2 + i, boxTop + boxHeight, shadowAttr);
780 }
781 clipRight = oldClipRight;
782 clipBottom = oldClipBottom;
783 }
784
785 /**
786 * Default implementation does nothing.
787 */
788 public void flushPhysical() {}
789
790 /**
791 * Put the cursor at (x,y).
792 *
793 * @param visible if true, the cursor should be visible
794 * @param x column coordinate to put the cursor on
795 * @param y row coordinate to put the cursor on
796 */
797 public void putCursor(final boolean visible, final int x, final int y) {
798 if ((cursorY >= 0)
799 && (cursorX >= 0)
800 && (cursorY <= height - 1)
801 && (cursorX <= width - 1)
802 ) {
803 // Make the current cursor position dirty
804 physical[cursorX][cursorY].unset();
805 unsetImageRow(cursorY);
806 }
807
808 cursorVisible = visible;
809 cursorX = x;
810 cursorY = y;
811 }
812
813 /**
814 * Hide the cursor.
815 */
816 public final void hideCursor() {
817 cursorVisible = false;
818 }
819
820 /**
821 * Get the cursor visibility.
822 *
823 * @return true if the cursor is visible
824 */
825 public boolean isCursorVisible() {
826 return cursorVisible;
827 }
828
829 /**
830 * Get the cursor X position.
831 *
832 * @return the cursor x column position
833 */
834 public int getCursorX() {
835 return cursorX;
836 }
837
838 /**
839 * Get the cursor Y position.
840 *
841 * @return the cursor y row position
842 */
843 public int getCursorY() {
844 return cursorY;
845 }
846
847 /**
848 * Set the window title. Default implementation does nothing.
849 *
850 * @param title the new title
851 */
852 public void setTitle(final String title) {}
853
854 // ------------------------------------------------------------------------
855 // LogicalScreen ----------------------------------------------------------
856 // ------------------------------------------------------------------------
857
858 /**
859 * Reallocate screen buffers.
860 *
861 * @param width new width
862 * @param height new height
863 */
864 private synchronized void reallocate(final int width, final int height) {
865 if (logical != null) {
866 for (int row = 0; row < this.height; row++) {
867 for (int col = 0; col < this.width; col++) {
868 logical[col][row] = null;
869 }
870 }
871 logical = null;
872 }
873 logical = new Cell[width][height];
874 if (physical != null) {
875 for (int row = 0; row < this.height; row++) {
876 for (int col = 0; col < this.width; col++) {
877 physical[col][row] = null;
878 }
879 }
880 physical = null;
881 }
882 physical = new Cell[width][height];
883
884 for (int row = 0; row < height; row++) {
885 for (int col = 0; col < width; col++) {
886 physical[col][row] = new Cell();
887 logical[col][row] = new Cell();
888 }
889 }
890
891 this.width = width;
892 this.height = height;
893
894 clipLeft = 0;
895 clipTop = 0;
896 clipRight = width;
897 clipBottom = height;
898
899 reallyCleared = true;
900 }
901
902 /**
903 * Clear the physical screen.
904 */
905 public final void clearPhysical() {
906 for (int row = 0; row < height; row++) {
907 for (int col = 0; col < width; col++) {
908 physical[col][row].unset();
909 }
910 }
911 }
912
913 /**
914 * Unset every image cell on one row of the physical screen, forcing
915 * images on that row to be redrawn.
916 *
917 * @param y row coordinate. 0 is the top-most row.
918 */
919 public final void unsetImageRow(final int y) {
920 if ((y < 0) || (y >= height)) {
921 return;
922 }
923 for (int x = 0; x < width; x++) {
924 if (logical[x][y].isImage()) {
925 physical[x][y].unset();
926 }
927 }
928 }
929
930 }