*/
package jexer.backend;
+import java.awt.image.BufferedImage;
+
+import jexer.backend.GlyphMaker;
import jexer.bits.Cell;
import jexer.bits.CellAttributes;
+import jexer.bits.Clipboard;
import jexer.bits.GraphicsChars;
+import jexer.bits.StringUtils;
/**
* A logical screen composed of a 2D array of Cells.
*/
protected int cursorY;
+ /**
+ * The last used height of a character cell in pixels, only used for
+ * full-width chars.
+ */
+ private int lastTextHeight = -1;
+
+ /**
+ * The glyph drawer for full-width chars.
+ */
+ private GlyphMaker glyphMaker = null;
+
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
// Screen -----------------------------------------------------------------
// ------------------------------------------------------------------------
+ /**
+ * Get the width of a character cell in pixels.
+ *
+ * @return the width in pixels of a character cell
+ */
+ public int getTextWidth() {
+ // Default width is 16 pixels.
+ return 16;
+ }
+
+ /**
+ * Get the height of a character cell in pixels.
+ *
+ * @return the height in pixels of a character cell
+ */
+ public int getTextHeight() {
+ // Default height is 20 pixels.
+ return 20;
+ }
+
/**
* Set drawing offset for x.
*
* @param ch character to draw
* @param attr attributes to use (bold, foreColor, backColor)
*/
- public final void putAll(final char ch, final CellAttributes attr) {
+ public final void putAll(final int ch, final CellAttributes attr) {
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
return;
}
+ if ((StringUtils.width(ch.getChar()) == 2) && (!ch.isImage())) {
+ putFullwidthCharXY(x, y, ch);
+ return;
+ }
+
int X = x + offsetX;
int Y = y + offsetY;
* @param ch character to draw
* @param attr attributes to use (bold, foreColor, backColor)
*/
- public final void putCharXY(final int x, final int y, final char ch,
+ public final void putCharXY(final int x, final int y, final int ch,
final CellAttributes attr) {
if ((x < clipLeft)
return;
}
+ if (StringUtils.width(ch) == 2) {
+ putFullwidthCharXY(x, y, ch, attr);
+ return;
+ }
+
int X = x + offsetX;
int Y = y + offsetY;
* @param y row coordinate. 0 is the top-most row.
* @param ch character to draw
*/
- public final void putCharXY(final int x, final int y, final char ch) {
-
+ public final void putCharXY(final int x, final int y, final int ch) {
if ((x < clipLeft)
|| (x >= clipRight)
|| (y < clipTop)
return;
}
+ if (StringUtils.width(ch) == 2) {
+ putFullwidthCharXY(x, y, ch);
+ return;
+ }
+
int X = x + offsetX;
int Y = y + offsetY;
final CellAttributes attr) {
int i = x;
- for (int j = 0; j < str.length(); j++) {
- char ch = str.charAt(j);
+ for (int j = 0; j < str.length();) {
+ int ch = str.codePointAt(j);
+ j += Character.charCount(ch);
putCharXY(i, y, ch, attr);
- i++;
+ i += StringUtils.width(ch);
if (i == width) {
break;
}
public final void putStringXY(final int x, final int y, final String str) {
int i = x;
- for (int j = 0; j < str.length(); j++) {
- char ch = str.charAt(j);
+ for (int j = 0; j < str.length();) {
+ int ch = str.codePointAt(j);
+ j += Character.charCount(ch);
putCharXY(i, y, ch);
- i++;
+ i += StringUtils.width(ch);
if (i == width) {
break;
}
* @param attr attributes to use (bold, foreColor, backColor)
*/
public final void vLineXY(final int x, final int y, final int n,
- final char ch, final CellAttributes attr) {
+ final int ch, final CellAttributes attr) {
for (int i = y; i < y + n; i++) {
putCharXY(x, i, ch, attr);
* @param attr attributes to use (bold, foreColor, backColor)
*/
public final void hLineXY(final int x, final int y, final int n,
- final char ch, final CellAttributes attr) {
+ final int ch, final CellAttributes attr) {
for (int i = x; i < x + n; i++) {
putCharXY(i, y, ch, attr);
*/
public final void setDimensions(final int width, final int height) {
reallocate(width, height);
+ resizeToScreen();
+ }
+
+ /**
+ * Resize the physical screen to match the logical screen dimensions.
+ */
+ public void resizeToScreen() {
+ // Subclasses are expected to override this.
}
/**
clipBottom = height - offsetY;
for (int i = 0; i < boxHeight; i++) {
- putAttrXY(boxLeft + boxWidth, boxTop + 1 + i, shadowAttr);
- putAttrXY(boxLeft + boxWidth + 1, boxTop + 1 + i, shadowAttr);
+ Cell cell = getCharXY(offsetX + boxLeft + boxWidth,
+ offsetY + boxTop + 1 + i);
+ if (cell.getWidth() == Cell.Width.SINGLE) {
+ putAttrXY(boxLeft + boxWidth, boxTop + 1 + i, shadowAttr);
+ } else {
+ putCharXY(boxLeft + boxWidth, boxTop + 1 + i, ' ', shadowAttr);
+ }
+ cell = getCharXY(offsetX + boxLeft + boxWidth + 1,
+ offsetY + boxTop + 1 + i);
+ if (cell.getWidth() == Cell.Width.SINGLE) {
+ putAttrXY(boxLeft + boxWidth + 1, boxTop + 1 + i, shadowAttr);
+ } else {
+ putCharXY(boxLeft + boxWidth + 1, boxTop + 1 + i, ' ',
+ shadowAttr);
+ }
}
for (int i = 0; i < boxWidth; i++) {
- putAttrXY(boxLeft + 2 + i, boxTop + boxHeight, shadowAttr);
+ Cell cell = getCharXY(offsetX + boxLeft + 2 + i,
+ offsetY + boxTop + boxHeight);
+ if (cell.getWidth() == Cell.Width.SINGLE) {
+ putAttrXY(boxLeft + 2 + i, boxTop + boxHeight, shadowAttr);
+ } else {
+ putCharXY(boxLeft + 2 + i, boxTop + boxHeight, ' ', shadowAttr);
+ }
}
clipRight = oldClipRight;
clipBottom = oldClipBottom;
* @param y row coordinate. 0 is the top-most row.
*/
public final void unsetImageRow(final int y) {
+ if ((y < 0) || (y >= height)) {
+ return;
+ }
for (int x = 0; x < width; x++) {
if (logical[x][y].isImage()) {
physical[x][y].unset();
}
}
+ /**
+ * Render one fullwidth cell.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param cell the cell to draw
+ */
+ public final void putFullwidthCharXY(final int x, final int y,
+ final Cell cell) {
+
+ int cellWidth = getTextWidth();
+ int cellHeight = getTextHeight();
+
+ if (lastTextHeight != cellHeight) {
+ glyphMaker = GlyphMaker.getInstance(cellHeight);
+ lastTextHeight = cellHeight;
+ }
+ BufferedImage image = glyphMaker.getImage(cell, cellWidth * 2,
+ cellHeight);
+ BufferedImage leftImage = image.getSubimage(0, 0, cellWidth,
+ cellHeight);
+ BufferedImage rightImage = image.getSubimage(cellWidth, 0, cellWidth,
+ cellHeight);
+
+ Cell left = new Cell(cell);
+ left.setImage(leftImage);
+ left.setWidth(Cell.Width.LEFT);
+ putCharXY(x, y, left);
+
+ Cell right = new Cell(cell);
+ right.setImage(rightImage);
+ right.setWidth(Cell.Width.RIGHT);
+ putCharXY(x + 1, y, right);
+ }
+
+ /**
+ * Render one fullwidth character with attributes.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param ch character to draw
+ * @param attr attributes to use (bold, foreColor, backColor)
+ */
+ public final void putFullwidthCharXY(final int x, final int y,
+ final int ch, final CellAttributes attr) {
+
+ Cell cell = new Cell(ch, attr);
+ putFullwidthCharXY(x, y, cell);
+ }
+
+ /**
+ * Render one fullwidth character with attributes.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param ch character to draw
+ */
+ public final void putFullwidthCharXY(final int x, final int y,
+ final int ch) {
+
+ Cell cell = new Cell(ch);
+ cell.setAttr(getAttrXY(x, y));
+ putFullwidthCharXY(x, y, cell);
+ }
+
+ /**
+ * Invert the cell color at a position, including both halves of a
+ * double-width cell.
+ *
+ * @param x column position
+ * @param y row position
+ */
+ public void invertCell(final int x, final int y) {
+ invertCell(x, y, false);
+ }
+
+ /**
+ * Invert the cell color at a position.
+ *
+ * @param x column position
+ * @param y row position
+ * @param onlyThisCell if true, only invert this cell, otherwise invert
+ * both halves of a double-width cell if necessary
+ */
+ public void invertCell(final int x, final int y,
+ final boolean onlyThisCell) {
+
+ Cell cell = getCharXY(x, y);
+ if (cell.isImage()) {
+ cell.invertImage();
+ }
+ if (cell.getForeColorRGB() < 0) {
+ cell.setForeColor(cell.getForeColor().invert());
+ } else {
+ cell.setForeColorRGB(cell.getForeColorRGB() ^ 0x00ffffff);
+ }
+ if (cell.getBackColorRGB() < 0) {
+ cell.setBackColor(cell.getBackColor().invert());
+ } else {
+ cell.setBackColorRGB(cell.getBackColorRGB() ^ 0x00ffffff);
+ }
+ putCharXY(x, y, cell);
+ if ((onlyThisCell == true) || (cell.getWidth() == Cell.Width.SINGLE)) {
+ return;
+ }
+
+ // This cell is one half of a fullwidth glyph. Invert the other
+ // half.
+ if (cell.getWidth() == Cell.Width.LEFT) {
+ if (x < width - 1) {
+ Cell rightHalf = getCharXY(x + 1, y);
+ if (rightHalf.getWidth() == Cell.Width.RIGHT) {
+ invertCell(x + 1, y, true);
+ return;
+ }
+ }
+ }
+ if (cell.getWidth() == Cell.Width.RIGHT) {
+ if (x > 0) {
+ Cell leftHalf = getCharXY(x - 1, y);
+ if (leftHalf.getWidth() == Cell.Width.LEFT) {
+ invertCell(x - 1, y, true);
+ }
+ }
+ }
+ }
+
+ /**
+ * Set a selection area on the screen.
+ *
+ * @param x0 the starting X position of the selection
+ * @param y0 the starting Y position of the selection
+ * @param x1 the ending X position of the selection
+ * @param y1 the ending Y position of the selection
+ * @param rectangle if true, this is a rectangle select
+ */
+ public void setSelection(final int x0, final int y0,
+ final int x1, final int y1, final boolean rectangle) {
+
+ int startX = x0;
+ int startY = y0;
+ int endX = x1;
+ int endY = y1;
+
+ if (((x1 < x0) && (y1 == y0))
+ || (y1 < y0)
+ ) {
+ // The user dragged from bottom-to-top and/or right-to-left.
+ // Reverse the coordinates for the inverted section.
+ startX = x1;
+ startY = y1;
+ endX = x0;
+ endY = y0;
+ }
+ if (rectangle) {
+ for (int y = startY; y <= endY; y++) {
+ for (int x = startX; x <= endX; x++) {
+ invertCell(x, y);
+ }
+ }
+ } else {
+ if (endY > startY) {
+ for (int x = startX; x < width; x++) {
+ invertCell(x, startY);
+ }
+ for (int y = startY + 1; y < endY; y++) {
+ for (int x = 0; x < width; x++) {
+ invertCell(x, y);
+ }
+ }
+ for (int x = 0; x <= endX; x++) {
+ invertCell(x, endY);
+ }
+ } else {
+ assert (startY == endY);
+ for (int x = startX; x <= endX; x++) {
+ invertCell(x, startY);
+ }
+ }
+ }
+ }
+
+ /**
+ * Copy the screen selection area to the clipboard.
+ *
+ * @param clipboard the clipboard to use
+ * @param x0 the starting X position of the selection
+ * @param y0 the starting Y position of the selection
+ * @param x1 the ending X position of the selection
+ * @param y1 the ending Y position of the selection
+ * @param rectangle if true, this is a rectangle select
+ */
+ public void copySelection(final Clipboard clipboard,
+ final int x0, final int y0, final int x1, final int y1,
+ final boolean rectangle) {
+
+ StringBuilder sb = new StringBuilder();
+
+ int startX = x0;
+ int startY = y0;
+ int endX = x1;
+ int endY = y1;
+
+ if (((x1 < x0) && (y1 == y0))
+ || (y1 < y0)
+ ) {
+ // The user dragged from bottom-to-top and/or right-to-left.
+ // Reverse the coordinates for the inverted section.
+ startX = x1;
+ startY = y1;
+ endX = x0;
+ endY = y0;
+ }
+ if (rectangle) {
+ for (int y = startY; y <= endY; y++) {
+ for (int x = startX; x <= endX; x++) {
+ sb.append(Character.toChars(getCharXY(x, y).getChar()));
+ }
+ sb.append("\n");
+ }
+ } else {
+ if (endY > startY) {
+ for (int x = startX; x < width; x++) {
+ sb.append(Character.toChars(getCharXY(x, startY).getChar()));
+ }
+ sb.append("\n");
+ for (int y = startY + 1; y < endY; y++) {
+ for (int x = 0; x < width; x++) {
+ sb.append(Character.toChars(getCharXY(x, y).getChar()));
+ }
+ sb.append("\n");
+ }
+ for (int x = 0; x <= endX; x++) {
+ sb.append(Character.toChars(getCharXY(x, endY).getChar()));
+ }
+ } else {
+ assert (startY == endY);
+ for (int x = startX; x <= endX; x++) {
+ sb.append(Character.toChars(getCharXY(x, startY).getChar()));
+ }
+ }
+ }
+ clipboard.copyText(sb.toString());
+ }
+
}