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