Commit | Line | Data |
---|---|---|
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 | 29 | package jexer.backend; |
df8de03f | 30 | |
3af53a35 KL |
31 | import java.awt.image.BufferedImage; |
32 | ||
33 | import jexer.backend.GlyphMaker; | |
df8de03f KL |
34 | import jexer.bits.Cell; |
35 | import jexer.bits.CellAttributes; | |
54eaded0 | 36 | import jexer.bits.Clipboard; |
df8de03f | 37 | import jexer.bits.GraphicsChars; |
3af53a35 | 38 | import jexer.bits.StringUtils; |
df8de03f KL |
39 | |
40 | /** | |
42873e30 | 41 | * A logical screen composed of a 2D array of Cells. |
df8de03f | 42 | */ |
42873e30 | 43 | public 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 | } |