#49 cell attributes to int
[fanfix.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 char 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 char ch) {
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);
144 this.ch = ch;
145 }
146
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
156 // ------------------------------------------------------------------------
157 // Cell -------------------------------------------------------------------
158 // ------------------------------------------------------------------------
159
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();
168 width = Width.SINGLE;
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
252 /**
253 * Getter for cell character.
254 *
255 * @return cell character
256 */
257 public char getChar() {
258 return ch;
259 }
260
261 /**
262 * Setter for cell character.
263 *
264 * @param ch new cell character
265 */
266 public void setChar(final char ch) {
267 this.ch = ch;
268 }
269
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 *
282 * @param width new cell width, one of Width.SINGLE, Width.LEFT, or
283 * Width.RIGHT
284 */
285 public void setWidth(final Width width) {
286 this.width = width;
287 }
288
289 /**
290 * Reset this cell to a blank.
291 */
292 @Override
293 public void reset() {
294 super.reset();
295 ch = ' ';
296 width = Width.SINGLE;
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;
311 width = Width.SINGLE;
312 image = null;
313 imageHashCode = 0;
314 invertedImage = null;
315 background = Color.BLACK;
316 backgroundHashCode = 0;
317 }
318
319 /**
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.
325 */
326 public boolean isBlank() {
327 if ((ch == UNSET_VALUE) || (image != null)) {
328 return false;
329 }
330 if ((getForeColor().equals(Color.WHITE))
331 && (getBackColor().equals(Color.BLACK))
332 && !isBold()
333 && !isBlink()
334 && !isReverse()
335 && !isUnderline()
336 && !isProtect()
337 && !isRGB()
338 && !isImage()
339 && (width == Width.SINGLE)
340 && (ch == ' ')
341 ) {
342 return true;
343 }
344
345 return false;
346 }
347
348 /**
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
353 */
354 @Override
355 public boolean equals(final Object rhs) {
356 if (!(rhs instanceof Cell)) {
357 return false;
358 }
359
360 Cell that = (Cell) rhs;
361
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.
396 if ((ch == that.ch) && (width == that.width)) {
397 return super.equals(rhs);
398 }
399 return false;
400 }
401
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;
414 hash = (B * hash) + width.hashCode();
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 }
426 return hash;
427 }
428
429 /**
430 * Set my field values to that's field.
431 *
432 * @param rhs an instance of either Cell or CellAttributes
433 */
434 @Override
435 public void setTo(final Object rhs) {
436 // Let this throw a ClassCastException
437 CellAttributes thatAttr = (CellAttributes) rhs;
438 this.image = null;
439 this.imageHashCode = 0;
440 this.backgroundHashCode = 0;
441 this.width = Width.SINGLE;
442 super.setTo(thatAttr);
443
444 if (rhs instanceof Cell) {
445 Cell that = (Cell) rhs;
446 this.ch = that.ch;
447 this.width = that.width;
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;
453 }
454 }
455
456 /**
457 * Set my field attr values to that's field.
458 *
459 * @param that a CellAttributes instance
460 */
461 public void setAttr(final CellAttributes that) {
462 image = null;
463 super.setTo(that);
464 }
465
466 /**
467 * Make human-readable description of this Cell.
468 *
469 * @return displayable String
470 */
471 @Override
472 public String toString() {
473 return String.format("fore: %s back: %s bold: %s blink: %s ch %c",
474 getForeColor(), getBackColor(), isBold(), isBlink(), ch);
475 }
476 }