/* * This file is part of lanterna (http://code.google.com/p/lanterna/). * * lanterna is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * Copyright (C) 2010-2017 Martin Berglund */ package com.googlecode.lanterna; import java.util.Arrays; import java.util.Collection; import java.util.EnumSet; /** * Represents a single character with additional metadata such as colors and modifiers. This class is immutable and * cannot be modified after creation. * @author Martin */ public class TextCharacter { private static EnumSet<SGR> toEnumSet(SGR... modifiers) { if(modifiers.length == 0) { return EnumSet.noneOf(SGR.class); } else { return EnumSet.copyOf(Arrays.asList(modifiers)); } } public static final TextCharacter DEFAULT_CHARACTER = new TextCharacter(' ', TextColor.ANSI.DEFAULT, TextColor.ANSI.DEFAULT); private final char character; private final TextColor foregroundColor; private final TextColor backgroundColor; private final EnumSet<SGR> modifiers; //This isn't immutable, but we should treat it as such and not expose it! /** * Creates a {@code ScreenCharacter} based on a supplied character, with default colors and no extra modifiers. * @param character Physical character to use */ public TextCharacter(char character) { this(character, TextColor.ANSI.DEFAULT, TextColor.ANSI.DEFAULT); } /** * Copies another {@code ScreenCharacter} * @param character screenCharacter to copy from */ public TextCharacter(TextCharacter character) { this(character.getCharacter(), character.getForegroundColor(), character.getBackgroundColor(), character.getModifiers().toArray(new SGR[character.getModifiers().size()])); } /** * Creates a new {@code ScreenCharacter} based on a physical character, color information and optional modifiers. * @param character Physical character to refer to * @param foregroundColor Foreground color the character has * @param backgroundColor Background color the character has * @param styles Optional list of modifiers to apply when drawing the character */ @SuppressWarnings("WeakerAccess") public TextCharacter( char character, TextColor foregroundColor, TextColor backgroundColor, SGR... styles) { this(character, foregroundColor, backgroundColor, toEnumSet(styles)); } /** * Creates a new {@code ScreenCharacter} based on a physical character, color information and a set of modifiers. * @param character Physical character to refer to * @param foregroundColor Foreground color the character has * @param backgroundColor Background color the character has * @param modifiers Set of modifiers to apply when drawing the character */ public TextCharacter( char character, TextColor foregroundColor, TextColor backgroundColor, EnumSet<SGR> modifiers) { // Don't allow creating a TextCharacter containing a control character // For backward-compatibility, do allow tab for now // TODO: In lanterna 3.1, don't allow tab if(TerminalTextUtils.isControlCharacter(character) && character != '\t') { throw new IllegalArgumentException("Cannot create a TextCharacter from a control character (0x" + Integer.toHexString(character) + ")"); } if(foregroundColor == null) { foregroundColor = TextColor.ANSI.DEFAULT; } if(backgroundColor == null) { backgroundColor = TextColor.ANSI.DEFAULT; } this.character = character; this.foregroundColor = foregroundColor; this.backgroundColor = backgroundColor; this.modifiers = EnumSet.copyOf(modifiers); } /** * The actual character this TextCharacter represents * @return character of the TextCharacter */ public char getCharacter() { return character; } /** * Foreground color specified for this TextCharacter * @return Foreground color of this TextCharacter */ public TextColor getForegroundColor() { return foregroundColor; } /** * Background color specified for this TextCharacter * @return Background color of this TextCharacter */ public TextColor getBackgroundColor() { return backgroundColor; } /** * Returns a set of all active modifiers on this TextCharacter * @return Set of active SGR codes */ public EnumSet<SGR> getModifiers() { return EnumSet.copyOf(modifiers); } /** * Returns true if this TextCharacter has the bold modifier active * @return {@code true} if this TextCharacter has the bold modifier active */ public boolean isBold() { return modifiers.contains(SGR.BOLD); } /** * Returns true if this TextCharacter has the reverse modifier active * @return {@code true} if this TextCharacter has the reverse modifier active */ public boolean isReversed() { return modifiers.contains(SGR.REVERSE); } /** * Returns true if this TextCharacter has the underline modifier active * @return {@code true} if this TextCharacter has the underline modifier active */ public boolean isUnderlined() { return modifiers.contains(SGR.UNDERLINE); } /** * Returns true if this TextCharacter has the blink modifier active * @return {@code true} if this TextCharacter has the blink modifier active */ public boolean isBlinking() { return modifiers.contains(SGR.BLINK); } /** * Returns true if this TextCharacter has the bordered modifier active * @return {@code true} if this TextCharacter has the bordered modifier active */ public boolean isBordered() { return modifiers.contains(SGR.BORDERED); } /** * Returns true if this TextCharacter has the crossed-out modifier active * @return {@code true} if this TextCharacter has the crossed-out modifier active */ public boolean isCrossedOut() { return modifiers.contains(SGR.CROSSED_OUT); } /** * Returns true if this TextCharacter has the italic modifier active * @return {@code true} if this TextCharacter has the italic modifier active */ public boolean isItalic() { return modifiers.contains(SGR.ITALIC); } /** * Returns a new TextCharacter with the same colors and modifiers but a different underlying character * @param character Character the copy should have * @return Copy of this TextCharacter with different underlying character */ @SuppressWarnings("SameParameterValue") public TextCharacter withCharacter(char character) { if(this.character == character) { return this; } return new TextCharacter(character, foregroundColor, backgroundColor, modifiers); } /** * Returns a copy of this TextCharacter with a specified foreground color * @param foregroundColor Foreground color the copy should have * @return Copy of the TextCharacter with a different foreground color */ public TextCharacter withForegroundColor(TextColor foregroundColor) { if(this.foregroundColor == foregroundColor || this.foregroundColor.equals(foregroundColor)) { return this; } return new TextCharacter(character, foregroundColor, backgroundColor, modifiers); } /** * Returns a copy of this TextCharacter with a specified background color * @param backgroundColor Background color the copy should have * @return Copy of the TextCharacter with a different background color */ public TextCharacter withBackgroundColor(TextColor backgroundColor) { if(this.backgroundColor == backgroundColor || this.backgroundColor.equals(backgroundColor)) { return this; } return new TextCharacter(character, foregroundColor, backgroundColor, modifiers); } /** * Returns a copy of this TextCharacter with specified list of SGR modifiers. None of the currently active SGR codes * will be carried over to the copy, only those in the passed in value. * @param modifiers SGR modifiers the copy should have * @return Copy of the TextCharacter with a different set of SGR modifiers */ public TextCharacter withModifiers(Collection<SGR> modifiers) { EnumSet<SGR> newSet = EnumSet.copyOf(modifiers); if(modifiers.equals(newSet)) { return this; } return new TextCharacter(character, foregroundColor, backgroundColor, newSet); } /** * Returns a copy of this TextCharacter with an additional SGR modifier. All of the currently active SGR codes * will be carried over to the copy, in addition to the one specified. * @param modifier SGR modifiers the copy should have in additional to all currently present * @return Copy of the TextCharacter with a new SGR modifier */ public TextCharacter withModifier(SGR modifier) { if(modifiers.contains(modifier)) { return this; } EnumSet<SGR> newSet = EnumSet.copyOf(this.modifiers); newSet.add(modifier); return new TextCharacter(character, foregroundColor, backgroundColor, newSet); } /** * Returns a copy of this TextCharacter with an SGR modifier removed. All of the currently active SGR codes * will be carried over to the copy, except for the one specified. If the current TextCharacter doesn't have the * SGR specified, it will return itself. * @param modifier SGR modifiers the copy should not have * @return Copy of the TextCharacter without the SGR modifier */ public TextCharacter withoutModifier(SGR modifier) { if(!modifiers.contains(modifier)) { return this; } EnumSet<SGR> newSet = EnumSet.copyOf(this.modifiers); newSet.remove(modifier); return new TextCharacter(character, foregroundColor, backgroundColor, newSet); } public boolean isDoubleWidth() { return TerminalTextUtils.isCharDoubleWidth(character); } @SuppressWarnings("SimplifiableIfStatement") @Override public boolean equals(Object obj) { if(obj == null) { return false; } if(getClass() != obj.getClass()) { return false; } final TextCharacter other = (TextCharacter) obj; if(this.character != other.character) { return false; } if(this.foregroundColor != other.foregroundColor && (this.foregroundColor == null || !this.foregroundColor.equals(other.foregroundColor))) { return false; } if(this.backgroundColor != other.backgroundColor && (this.backgroundColor == null || !this.backgroundColor.equals(other.backgroundColor))) { return false; } return !(this.modifiers != other.modifiers && (this.modifiers == null || !this.modifiers.equals(other.modifiers))); } @Override public int hashCode() { int hash = 7; hash = 37 * hash + this.character; hash = 37 * hash + (this.foregroundColor != null ? this.foregroundColor.hashCode() : 0); hash = 37 * hash + (this.backgroundColor != null ? this.backgroundColor.hashCode() : 0); hash = 37 * hash + (this.modifiers != null ? this.modifiers.hashCode() : 0); return hash; } @Override public String toString() { return "TextCharacter{" + "character=" + character + ", foregroundColor=" + foregroundColor + ", backgroundColor=" + backgroundColor + ", modifiers=" + modifiers + '}'; } }