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