Merge branch 'subtree'
[nikiroo-utils.git] / src / jexer / bits / Cell.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.bits;
30
31 import java.awt.Color;
32 import java.awt.image.BufferedImage;
33
34 /**
35 * This class represents a single text cell or bit of image on the screen.
36 */
37 public final class Cell extends CellAttributes {
38
39 // ------------------------------------------------------------------------
40 // Constants --------------------------------------------------------------
41 // ------------------------------------------------------------------------
42
43 /**
44 * How this cell needs to be displayed if it is part of a larger glyph.
45 */
46 public enum Width {
47 /**
48 * This cell is an entire glyph on its own.
49 */
50 SINGLE,
51
52 /**
53 * This cell is the left half of a wide glyph.
54 */
55 LEFT,
56
57 /**
58 * This cell is the right half of a wide glyph.
59 */
60 RIGHT,
61 }
62
63 /**
64 * The special "this cell is unset" (null) value. This is the Unicode
65 * "not a character" value.
66 */
67 private static final char UNSET_VALUE = (char) 65535;
68
69 // ------------------------------------------------------------------------
70 // Variables --------------------------------------------------------------
71 // ------------------------------------------------------------------------
72
73 /**
74 * The character at this cell.
75 */
76 private int ch = ' ';
77
78 /**
79 * The display width of this cell.
80 */
81 private Width width = Width.SINGLE;
82
83 /**
84 * The image at this cell.
85 */
86 private BufferedImage image = null;
87
88 /**
89 * The image at this cell, inverted.
90 */
91 private BufferedImage invertedImage = null;
92
93 /**
94 * The background color used for the area the image portion might not
95 * cover.
96 */
97 private Color background = Color.BLACK;
98
99 /**
100 * hashCode() needs to call image.hashCode(), which can get quite
101 * expensive.
102 */
103 private int imageHashCode = 0;
104
105 /**
106 * hashCode() needs to call background.hashCode(), which can get quite
107 * expensive.
108 */
109 private int backgroundHashCode = 0;
110
111 // ------------------------------------------------------------------------
112 // Constructors -----------------------------------------------------------
113 // ------------------------------------------------------------------------
114
115 /**
116 * Public constructor sets default values of the cell to blank.
117 *
118 * @see #isBlank()
119 * @see #reset()
120 */
121 public Cell() {
122 // NOP
123 }
124
125 /**
126 * Public constructor sets the character. Attributes are the same as
127 * default.
128 *
129 * @param ch character to set to
130 * @see #reset()
131 */
132 public Cell(final int ch) {
133 this.ch = ch;
134 }
135
136 /**
137 * Public constructor sets the attributes.
138 *
139 * @param attr attributes to use
140 */
141 public Cell(final CellAttributes attr) {
142 super(attr);
143 }
144
145 /**
146 * Public constructor sets the character and attributes.
147 *
148 * @param ch character to set to
149 * @param attr attributes to use
150 */
151 public Cell(final int ch, final CellAttributes attr) {
152 super(attr);
153 this.ch = ch;
154 }
155
156 /**
157 * Public constructor creates a duplicate.
158 *
159 * @param cell the instance to copy
160 */
161 public Cell(final Cell cell) {
162 setTo(cell);
163 }
164
165 // ------------------------------------------------------------------------
166 // Cell -------------------------------------------------------------------
167 // ------------------------------------------------------------------------
168
169 /**
170 * Set the image data for this cell.
171 *
172 * @param image the image for this cell
173 */
174 public void setImage(final BufferedImage image) {
175 this.image = image;
176 imageHashCode = image.hashCode();
177 width = Width.SINGLE;
178 }
179
180 /**
181 * Get the image data for this cell.
182 *
183 * @return the image for this cell
184 */
185 public BufferedImage getImage() {
186 if (invertedImage != null) {
187 return invertedImage;
188 }
189 return image;
190 }
191
192 /**
193 * Get the bitmap image background color for this cell.
194 *
195 * @return the bitmap image background color
196 */
197 public Color getBackground() {
198 return background;
199 }
200
201 /**
202 * If true, this cell has image data.
203 *
204 * @return true if this cell is an image rather than a character with
205 * attributes
206 */
207 public boolean isImage() {
208 if (image != null) {
209 return true;
210 }
211 return false;
212 }
213
214 /**
215 * Restore the image in this cell to its normal version, if it has one.
216 */
217 public void restoreImage() {
218 invertedImage = null;
219 }
220
221 /**
222 * If true, this cell has image data, and that data is inverted.
223 *
224 * @return true if this cell is an image rather than a character with
225 * attributes, and the data is inverted
226 */
227 public boolean isInvertedImage() {
228 if ((image != null) && (invertedImage != null)) {
229 return true;
230 }
231 return false;
232 }
233
234 /**
235 * Invert the image in this cell, if it has one.
236 */
237 public void invertImage() {
238 if (image == null) {
239 return;
240 }
241 if (invertedImage == null) {
242 invertedImage = new BufferedImage(image.getWidth(),
243 image.getHeight(), BufferedImage.TYPE_INT_ARGB);
244
245 int [] rgbArray = image.getRGB(0, 0,
246 image.getWidth(), image.getHeight(), null, 0, image.getWidth());
247
248 for (int i = 0; i < rgbArray.length; i++) {
249 // Set the colors to fully inverted.
250 if (rgbArray[i] != 0x00FFFFFF) {
251 rgbArray[i] ^= 0x00FFFFFF;
252 }
253 // Also set alpha to non-transparent.
254 rgbArray[i] |= 0xFF000000;
255 }
256 invertedImage.setRGB(0, 0, image.getWidth(), image.getHeight(),
257 rgbArray, 0, image.getWidth());
258 }
259 }
260
261 /**
262 * Getter for cell character.
263 *
264 * @return cell character
265 */
266 public int getChar() {
267 return ch;
268 }
269
270 /**
271 * Setter for cell character.
272 *
273 * @param ch new cell character
274 */
275 public void setChar(final int ch) {
276 this.ch = ch;
277 }
278
279 /**
280 * Getter for cell width.
281 *
282 * @return Width.SINGLE, Width.LEFT, or Width.RIGHT
283 */
284 public Width getWidth() {
285 return width;
286 }
287
288 /**
289 * Setter for cell width.
290 *
291 * @param width new cell width, one of Width.SINGLE, Width.LEFT, or
292 * Width.RIGHT
293 */
294 public void setWidth(final Width width) {
295 this.width = width;
296 }
297
298 /**
299 * Reset this cell to a blank.
300 */
301 @Override
302 public void reset() {
303 super.reset();
304 ch = ' ';
305 width = Width.SINGLE;
306 image = null;
307 imageHashCode = 0;
308 invertedImage = null;
309 background = Color.BLACK;
310 backgroundHashCode = 0;
311 }
312
313 /**
314 * UNset this cell. It will not be equal to any other cell until it has
315 * been assigned attributes and a character.
316 */
317 public void unset() {
318 super.reset();
319 ch = UNSET_VALUE;
320 width = Width.SINGLE;
321 image = null;
322 imageHashCode = 0;
323 invertedImage = null;
324 background = Color.BLACK;
325 backgroundHashCode = 0;
326 }
327
328 /**
329 * Check to see if this cell has default attributes: white foreground,
330 * black background, no bold/blink/reverse/underline/protect, and a
331 * character value of ' ' (space).
332 *
333 * @return true if this cell has default attributes.
334 */
335 public boolean isBlank() {
336 if ((ch == UNSET_VALUE) || (image != null)) {
337 return false;
338 }
339 if ((getForeColor().equals(Color.WHITE))
340 && (getBackColor().equals(Color.BLACK))
341 && !isBold()
342 && !isBlink()
343 && !isReverse()
344 && !isUnderline()
345 && !isProtect()
346 && !isRGB()
347 && !isImage()
348 && (width == Width.SINGLE)
349 && (ch == ' ')
350 ) {
351 return true;
352 }
353
354 return false;
355 }
356
357 /**
358 * Comparison check. All fields must match to return true.
359 *
360 * @param rhs another Cell instance
361 * @return true if all fields are equal
362 */
363 @Override
364 public boolean equals(final Object rhs) {
365 if (!(rhs instanceof Cell)) {
366 return false;
367 }
368
369 Cell that = (Cell) rhs;
370
371 // Unsetted cells can never be equal.
372 if ((ch == UNSET_VALUE) || (that.ch == UNSET_VALUE)) {
373 return false;
374 }
375
376 // If this or rhs has an image and the other doesn't, these are not
377 // equal.
378 if ((image != null) && (that.image == null)) {
379 return false;
380 }
381 if ((image == null) && (that.image != null)) {
382 return false;
383 }
384 // If this and rhs have images, both must match.
385 if ((image != null) && (that.image != null)) {
386 if ((invertedImage == null) && (that.invertedImage != null)) {
387 return false;
388 }
389 if ((invertedImage != null) && (that.invertedImage == null)) {
390 return false;
391 }
392 // Either both objects have their image inverted, or neither do.
393 // Now if the images are identical the cells are the same
394 // visually.
395 if (image.equals(that.image)
396 && (background.equals(that.background))
397 ) {
398 return true;
399 } else {
400 return false;
401 }
402 }
403
404 // Normal case: character and attributes must match.
405 if ((ch == that.ch) && (width == that.width)) {
406 return super.equals(rhs);
407 }
408 return false;
409 }
410
411 /**
412 * Hashcode uses all fields in equals().
413 *
414 * @return the hash
415 */
416 @Override
417 public int hashCode() {
418 int A = 13;
419 int B = 23;
420 int hash = A;
421 hash = (B * hash) + super.hashCode();
422 hash = (B * hash) + ch;
423 hash = (B * hash) + width.hashCode();
424 if (image != null) {
425 /*
426 hash = (B * hash) + image.hashCode();
427 hash = (B * hash) + background.hashCode();
428 */
429 hash = (B * hash) + imageHashCode;
430 hash = (B * hash) + backgroundHashCode;
431 }
432 if (invertedImage != null) {
433 hash = (B * hash) + invertedImage.hashCode();
434 }
435 return hash;
436 }
437
438 /**
439 * Set my field values to that's field.
440 *
441 * @param rhs an instance of either Cell or CellAttributes
442 */
443 @Override
444 public void setTo(final Object rhs) {
445 // Let this throw a ClassCastException
446 CellAttributes thatAttr = (CellAttributes) rhs;
447 this.image = null;
448 this.imageHashCode = 0;
449 this.backgroundHashCode = 0;
450 this.width = Width.SINGLE;
451 super.setTo(thatAttr);
452
453 if (rhs instanceof Cell) {
454 Cell that = (Cell) rhs;
455 this.ch = that.ch;
456 this.width = that.width;
457 this.image = that.image;
458 this.invertedImage = that.invertedImage;
459 this.background = that.background;
460 this.imageHashCode = that.imageHashCode;
461 this.backgroundHashCode = that.backgroundHashCode;
462 }
463 }
464
465 /**
466 * Set my field attr values to that's field.
467 *
468 * @param that a CellAttributes instance
469 */
470 public void setAttr(final CellAttributes that) {
471 image = null;
472 super.setTo(that);
473 }
474
475 /**
476 * Make human-readable description of this Cell.
477 *
478 * @return displayable String
479 */
480 @Override
481 public String toString() {
482 return String.format("fore: %s back: %s bold: %s blink: %s ch %c",
483 getForeColor(), getBackColor(), isBold(), isBlink(), ch);
484 }
485 }