#49 cell attributes to int
[fanfix.git] / src / jexer / bits / Cell.java
CommitLineData
daa4106c 1/*
624ce48e
KL
2 * Jexer - Java Text User Interface
3 *
e16dda65 4 * The MIT License (MIT)
624ce48e 5 *
a69ed767 6 * Copyright (C) 2019 Kevin Lamonte
624ce48e 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:
624ce48e 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.
624ce48e 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
624ce48e
KL
28 */
29package jexer.bits;
30
a69ed767
KL
31import java.awt.Color;
32import java.awt.image.BufferedImage;
33
624ce48e 34/**
a69ed767 35 * This class represents a single text cell or bit of image on the screen.
624ce48e 36 */
2b9c27db 37public final class Cell extends CellAttributes {
624ce48e 38
a69ed767
KL
39 // ------------------------------------------------------------------------
40 // Constants --------------------------------------------------------------
41 // ------------------------------------------------------------------------
42
9588c713
KL
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
a69ed767
KL
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
d36057df
KL
69 // ------------------------------------------------------------------------
70 // Variables --------------------------------------------------------------
71 // ------------------------------------------------------------------------
72
624ce48e 73 /**
7b5261bc
KL
74 * The character at this cell.
75 */
027de5ae 76 private char ch = ' ';
7b5261bc 77
9588c713
KL
78 /**
79 * The display width of this cell.
80 */
81 private Width width = Width.SINGLE;
82
a69ed767
KL
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 */
027de5ae 97 private Color background = Color.BLACK;
a69ed767
KL
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
d36057df
KL
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() {
027de5ae 122 // NOP
d36057df
KL
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 char ch) {
027de5ae
KL
133 this.ch = ch;
134 }
135
136 /**
137 * Public constructor sets the character and attributes.
138 *
139 * @param ch character to set to
140 * @param attr attributes to use
141 */
142 public Cell(final char ch, final CellAttributes attr) {
143 super(attr);
d36057df
KL
144 this.ch = ch;
145 }
146
ab215e38
KL
147 /**
148 * Public constructor creates a duplicate.
149 *
150 * @param cell the instance to copy
151 */
152 public Cell(final Cell cell) {
153 setTo(cell);
154 }
155
d36057df
KL
156 // ------------------------------------------------------------------------
157 // Cell -------------------------------------------------------------------
158 // ------------------------------------------------------------------------
159
a69ed767
KL
160 /**
161 * Set the image data for this cell.
162 *
163 * @param image the image for this cell
164 */
165 public void setImage(final BufferedImage image) {
166 this.image = image;
167 imageHashCode = image.hashCode();
9588c713 168 width = Width.SINGLE;
a69ed767
KL
169 }
170
171 /**
172 * Get the image data for this cell.
173 *
174 * @return the image for this cell
175 */
176 public BufferedImage getImage() {
177 if (invertedImage != null) {
178 return invertedImage;
179 }
180 return image;
181 }
182
183 /**
184 * Get the bitmap image background color for this cell.
185 *
186 * @return the bitmap image background color
187 */
188 public Color getBackground() {
189 return background;
190 }
191
192 /**
193 * If true, this cell has image data.
194 *
195 * @return true if this cell is an image rather than a character with
196 * attributes
197 */
198 public boolean isImage() {
199 if (image != null) {
200 return true;
201 }
202 return false;
203 }
204
205 /**
206 * Restore the image in this cell to its normal version, if it has one.
207 */
208 public void restoreImage() {
209 invertedImage = null;
210 }
211
212 /**
213 * If true, this cell has image data, and that data is inverted.
214 *
215 * @return true if this cell is an image rather than a character with
216 * attributes, and the data is inverted
217 */
218 public boolean isInvertedImage() {
219 if ((image != null) && (invertedImage != null)) {
220 return true;
221 }
222 return false;
223 }
224
225 /**
226 * Invert the image in this cell, if it has one.
227 */
228 public void invertImage() {
229 if (image == null) {
230 return;
231 }
232 if (invertedImage == null) {
233 invertedImage = new BufferedImage(image.getWidth(),
234 image.getHeight(), BufferedImage.TYPE_INT_ARGB);
235
236 int [] rgbArray = image.getRGB(0, 0,
237 image.getWidth(), image.getHeight(), null, 0, image.getWidth());
238
239 for (int i = 0; i < rgbArray.length; i++) {
240 // Set the colors to fully inverted.
241 if (rgbArray[i] != 0x00FFFFFF) {
242 rgbArray[i] ^= 0x00FFFFFF;
243 }
244 // Also set alpha to non-transparent.
245 rgbArray[i] |= 0xFF000000;
246 }
247 invertedImage.setRGB(0, 0, image.getWidth(), image.getHeight(),
248 rgbArray, 0, image.getWidth());
249 }
250 }
251
7b5261bc
KL
252 /**
253 * Getter for cell character.
254 *
255 * @return cell character
256 */
2b9c27db 257 public char getChar() {
7b5261bc
KL
258 return ch;
259 }
260
261 /**
262 * Setter for cell character.
263 *
264 * @param ch new cell character
624ce48e 265 */
2b9c27db 266 public void setChar(final char ch) {
7b5261bc
KL
267 this.ch = ch;
268 }
624ce48e 269
9588c713
KL
270 /**
271 * Getter for cell width.
272 *
273 * @return Width.SINGLE, Width.LEFT, or Width.RIGHT
274 */
275 public Width getWidth() {
276 return width;
277 }
278
279 /**
280 * Setter for cell width.
281 *
ab215e38 282 * @param width new cell width, one of Width.SINGLE, Width.LEFT, or
9588c713
KL
283 * Width.RIGHT
284 */
285 public void setWidth(final Width width) {
286 this.width = width;
287 }
288
624ce48e 289 /**
7b5261bc 290 * Reset this cell to a blank.
624ce48e
KL
291 */
292 @Override
2b9c27db 293 public void reset() {
7b5261bc
KL
294 super.reset();
295 ch = ' ';
9588c713 296 width = Width.SINGLE;
a69ed767
KL
297 image = null;
298 imageHashCode = 0;
299 invertedImage = null;
300 background = Color.BLACK;
301 backgroundHashCode = 0;
302 }
303
304 /**
305 * UNset this cell. It will not be equal to any other cell until it has
306 * been assigned attributes and a character.
307 */
308 public void unset() {
309 super.reset();
310 ch = UNSET_VALUE;
9588c713 311 width = Width.SINGLE;
a69ed767
KL
312 image = null;
313 imageHashCode = 0;
314 invertedImage = null;
315 background = Color.BLACK;
316 backgroundHashCode = 0;
624ce48e
KL
317 }
318
319 /**
7b5261bc
KL
320 * Check to see if this cell has default attributes: white foreground,
321 * black background, no bold/blink/reverse/underline/protect, and a
322 * character value of ' ' (space).
323 *
324 * @return true if this cell has default attributes.
624ce48e 325 */
2b9c27db 326 public boolean isBlank() {
a69ed767
KL
327 if ((ch == UNSET_VALUE) || (image != null)) {
328 return false;
329 }
7b5261bc
KL
330 if ((getForeColor().equals(Color.WHITE))
331 && (getBackColor().equals(Color.BLACK))
7c870d89
KL
332 && !isBold()
333 && !isBlink()
334 && !isReverse()
335 && !isUnderline()
336 && !isProtect()
051e2913 337 && !isRGB()
a69ed767 338 && !isImage()
9588c713 339 && (width == Width.SINGLE)
7b5261bc
KL
340 && (ch == ' ')
341 ) {
342 return true;
343 }
624ce48e 344
7b5261bc 345 return false;
624ce48e
KL
346 }
347
348 /**
7b5261bc
KL
349 * Comparison check. All fields must match to return true.
350 *
351 * @param rhs another Cell instance
352 * @return true if all fields are equal
624ce48e
KL
353 */
354 @Override
2b9c27db 355 public boolean equals(final Object rhs) {
7b5261bc
KL
356 if (!(rhs instanceof Cell)) {
357 return false;
358 }
624ce48e 359
7b5261bc 360 Cell that = (Cell) rhs;
6358f6e5 361
a69ed767
KL
362 // Unsetted cells can never be equal.
363 if ((ch == UNSET_VALUE) || (that.ch == UNSET_VALUE)) {
364 return false;
365 }
366
367 // If this or rhs has an image and the other doesn't, these are not
368 // equal.
369 if ((image != null) && (that.image == null)) {
370 return false;
371 }
372 if ((image == null) && (that.image != null)) {
373 return false;
374 }
375 // If this and rhs have images, both must match.
376 if ((image != null) && (that.image != null)) {
377 if ((invertedImage == null) && (that.invertedImage != null)) {
378 return false;
379 }
380 if ((invertedImage != null) && (that.invertedImage == null)) {
381 return false;
382 }
383 // Either both objects have their image inverted, or neither do.
384 // Now if the images are identical the cells are the same
385 // visually.
386 if (image.equals(that.image)
387 && (background.equals(that.background))
388 ) {
389 return true;
390 } else {
391 return false;
392 }
393 }
394
395 // Normal case: character and attributes must match.
9588c713 396 if ((ch == that.ch) && (width == that.width)) {
6358f6e5
KL
397 return super.equals(rhs);
398 }
399 return false;
624ce48e
KL
400 }
401
e826b451
KL
402 /**
403 * Hashcode uses all fields in equals().
404 *
405 * @return the hash
406 */
407 @Override
408 public int hashCode() {
409 int A = 13;
410 int B = 23;
411 int hash = A;
412 hash = (B * hash) + super.hashCode();
413 hash = (B * hash) + (int)ch;
9588c713 414 hash = (B * hash) + width.hashCode();
a69ed767
KL
415 if (image != null) {
416 /*
417 hash = (B * hash) + image.hashCode();
418 hash = (B * hash) + background.hashCode();
419 */
420 hash = (B * hash) + imageHashCode;
421 hash = (B * hash) + backgroundHashCode;
422 }
423 if (invertedImage != null) {
424 hash = (B * hash) + invertedImage.hashCode();
425 }
e826b451
KL
426 return hash;
427 }
428
624ce48e 429 /**
7b5261bc
KL
430 * Set my field values to that's field.
431 *
432 * @param rhs an instance of either Cell or CellAttributes
624ce48e
KL
433 */
434 @Override
2b9c27db 435 public void setTo(final Object rhs) {
7b5261bc
KL
436 // Let this throw a ClassCastException
437 CellAttributes thatAttr = (CellAttributes) rhs;
a69ed767
KL
438 this.image = null;
439 this.imageHashCode = 0;
440 this.backgroundHashCode = 0;
9588c713 441 this.width = Width.SINGLE;
7b5261bc 442 super.setTo(thatAttr);
624ce48e 443
7b5261bc
KL
444 if (rhs instanceof Cell) {
445 Cell that = (Cell) rhs;
446 this.ch = that.ch;
9588c713 447 this.width = that.width;
a69ed767
KL
448 this.image = that.image;
449 this.invertedImage = that.invertedImage;
450 this.background = that.background;
451 this.imageHashCode = that.imageHashCode;
452 this.backgroundHashCode = that.backgroundHashCode;
7b5261bc 453 }
624ce48e
KL
454 }
455
456 /**
7b5261bc
KL
457 * Set my field attr values to that's field.
458 *
459 * @param that a CellAttributes instance
624ce48e 460 */
2b9c27db 461 public void setAttr(final CellAttributes that) {
a69ed767 462 image = null;
7b5261bc 463 super.setTo(that);
624ce48e
KL
464 }
465
624ce48e 466 /**
7b5261bc
KL
467 * Make human-readable description of this Cell.
468 *
469 * @return displayable String
624ce48e
KL
470 */
471 @Override
2b9c27db 472 public String toString() {
8a632d71 473 return String.format("fore: %s back: %s bold: %s blink: %s ch %c",
7c870d89 474 getForeColor(), getBackColor(), isBold(), isBlink(), ch);
624ce48e
KL
475 }
476}