0001: /*
0002: *
0003: *
0004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
0005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License version
0009: * 2 only, as published by the Free Software Foundation.
0010: *
0011: * This program is distributed in the hope that it will be useful, but
0012: * WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * General Public License version 2 for more details (a copy is
0015: * included at /legal/license.txt).
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * version 2 along with this work; if not, write to the Free Software
0019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0020: * 02110-1301 USA
0021: *
0022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0023: * Clara, CA 95054 or visit www.sun.com if you need additional
0024: * information or have any questions.
0025: */
0026:
0027: package com.sun.midp.lcdui;
0028:
0029: import javax.microedition.lcdui.Font;
0030: import javax.microedition.lcdui.Graphics;
0031:
0032: import com.sun.midp.chameleon.skins.StringItemSkin;
0033: import com.sun.midp.i18n.Resource;
0034: import com.sun.midp.i18n.ResourceConstants;
0035:
0036: /**
0037: * Static method class use to draw and size text.
0038: */
0039:
0040: public class Text {
0041:
0042: /** Character to be used as a truncation indiactor,
0043: * for example, \u2026 which is ellipsis (...). */
0044: private static final char truncationMark = Resource.getString(
0045: ResourceConstants.TRUNCATION_MARK).charAt(0);//'\u2026';
0046:
0047: // the following are used in calling the getNextLine method
0048:
0049: /** Line start. */
0050: private static final int GNL_LINE_START = 0;
0051: /** Line end. */
0052: private static final int GNL_LINE_END = 1;
0053: /** New line start. */
0054: private static final int GNL_NEW_LINE_START = 2;
0055: /** Screen width available. */
0056: private static final int GNL_WIDTH = 3;
0057: /** Screen height available. */
0058: private static final int GNL_HEIGHT = 4;
0059: /** Font height. */
0060: private static final int GNL_FONT_HEIGHT = 5;
0061: /** Line number. */
0062: private static final int GNL_NUM_LINES = 6;
0063: /** Text options (NORMAL, INVERT...) see below. */
0064: private static final int GNL_OPTIONS = 7;
0065: /** Text pixel offset. */
0066: private static final int GNL_OFFSET = 8;
0067: /** Width of the ellipsis in the current font. */
0068: private static final int GNL_ELLIP_WIDTH = 9;
0069: /** Line width in pixels. */
0070: private static final int GNL_LINE_WIDTH = 10;
0071:
0072: /** Number of GNL_ parameter constants. */
0073: private static final int GNL_NUM_PARAMS = 11;
0074:
0075: // constants to affect how text drawing is handled
0076: // These values can be OR'd together. no error checking is performed
0077:
0078: /** NORMAL text. */
0079: public static final int NORMAL = 0x0;
0080: /** INVERTED text color. */
0081: public static final int INVERT = 0x1;
0082: /** Draw a hyperlink for the text. */
0083: public static final int HYPERLINK = 0x2;
0084: /** Truncate the text and put a "..." if the text doesn't fit the bounds. */
0085: public static final int TRUNCATE = 0x4;
0086:
0087: // these values are stored used as setting in a TextCursor object
0088: /**
0089: * When a paint occurs use the cursor index to know when to
0090: * paint the cursor.
0091: */
0092: public static final int PAINT_USE_CURSOR_INDEX = 0;
0093:
0094: /**
0095: * When a paint occurs try to find the best value for the cursor
0096: * index based on the x,y coordinates of the cursor.
0097: */
0098: public static final int PAINT_GET_CURSOR_INDEX = 1;
0099:
0100: /**
0101: * Don't draw a cursor.
0102: */
0103: public static final int PAINT_HIDE_CURSOR = 2;
0104:
0105: /**
0106: * Sets up a new inout structure used in various
0107: * text methods.
0108: * @param font the font to use
0109: * @param w the available width for the text
0110: * @param h the available height for the text
0111: * @param options any of NORMAL | INVERT | HYPERLINK | TRUNCATE
0112: * @param offset the first line pixel offset
0113: * @return initialized GNL_struct
0114: */
0115: public static int[] initGNL(Font font, int w, int h, int options,
0116: int offset) {
0117:
0118: int[] inout = new int[GNL_NUM_PARAMS];
0119:
0120: inout[GNL_FONT_HEIGHT] = font.getHeight();
0121: inout[GNL_WIDTH] = w;
0122: inout[GNL_HEIGHT] = h;
0123: inout[GNL_OPTIONS] = options;
0124: inout[GNL_OFFSET] = offset;
0125: inout[GNL_ELLIP_WIDTH] = font.charWidth(truncationMark);
0126: inout[GNL_LINE_START] = 0;
0127: inout[GNL_LINE_END] = 0;
0128: inout[GNL_NEW_LINE_START] = 0;
0129: inout[GNL_LINE_WIDTH] = 0;
0130: inout[GNL_NUM_LINES] = 0;
0131:
0132: return inout;
0133: }
0134:
0135: /**
0136: * Paints the text in a single line, scrolling left or right as
0137: * necessary to keep the cursor visible within the available
0138: * width for the text. The offset of the text after the
0139: * paintLine call, whether modified or not, is returned.
0140: * <p>
0141: * If the cursor is null, signifying an uneditable TextField is
0142: * being painted, the text will not be scrolled left or right, and
0143: * the returned value will always equal the <code>offset</code>
0144: * argument passed in to this method.
0145: *
0146: * @param g the Graphics object to paint in
0147: * @param str the String to paint
0148: * @param font the font to use
0149: * @param fgColor foreground color
0150: * @param w the available width for the text
0151: * @param h the available height for the text
0152: * @param cursor TextCursor object to use for cursor placement
0153: * @param offset the pixel offset of the text (possibly negative)
0154: * @return the current scroll offset
0155: */
0156: public static int paintLine(Graphics g, String str, Font font,
0157: int fgColor, int w, int h, TextCursor cursor, int offset) {
0158: if (w <= 0
0159: || (cursor == null && (str == null || str.length() == 0))) {
0160: return 0;
0161: }
0162:
0163: if (str == null) {
0164: str = "";
0165: }
0166:
0167: g.setFont(font);
0168: g.setColor(fgColor);
0169:
0170: char[] text = str.toCharArray();
0171: int fontHeight = font.getHeight();
0172:
0173: if (cursor != null && cursor.visible == false) {
0174: cursor = null;
0175: }
0176:
0177: // side-scroll distance in pixels, with default
0178: int scrollPix = w / 2;
0179:
0180: //
0181: // draw a vertical cursor indicator if required
0182: //
0183: if (cursor != null && cursor.option == PAINT_USE_CURSOR_INDEX
0184: && cursor.index >= 0 && cursor.index <= str.length()) {
0185: int pos = offset;
0186: if (cursor.index > 0) {
0187: pos += font.charsWidth(text, 0, cursor.index);
0188: }
0189: // IMPL_NOTE: optimize this with math instead of iteration
0190: cursor.x = pos;
0191: if (cursor.x >= w) {
0192: while (cursor.x >= w) {
0193: offset -= scrollPix;
0194: cursor.x -= scrollPix;
0195: }
0196: } else {
0197: while ((cursor.x < w / 2) && (offset < 0)) {
0198: offset += scrollPix;
0199: cursor.x += scrollPix;
0200: }
0201: }
0202: cursor.y = fontHeight;
0203: cursor.width = 1;
0204: cursor.height = fontHeight;
0205:
0206: cursor.paint(g);
0207: cursor = null;
0208: }
0209: g.drawChars(text, 0, text.length, offset, h, Graphics.BOTTOM
0210: | Graphics.LEFT);
0211:
0212: return offset;
0213: }
0214:
0215: /**
0216: * Creates a current TextInfo struct, linewraping text
0217: * when necessary. TextInfo struct is updated when
0218: * <code>str</code> changes, or when scrolling happens.
0219: * This method does not do any painting, but updates
0220: * <code>info</code> to be current for use by the
0221: * paint routine, <code>paintText</code>...
0222: *
0223: * @param str the text to use
0224: * @param font the font to use for sizing
0225: * @param w the available width for the text
0226: * @param h the available height for the text
0227: * @param offset the pixel offset of the text (possibly negative)
0228: * @param options only TRUNCATE matters here
0229: * @param cursor text cursor object for cursor position
0230: * @param info TextInfo structure to fill
0231: * @return true if successful, false if there was an error
0232: */
0233: // IMPL_NOTE: break into 3 simpler update methods: text, Y scroll, X scroll
0234: public static boolean updateTextInfo(String str, Font font, int w,
0235: int h, int offset, int options, TextCursor cursor,
0236: TextInfo info) {
0237: if (w <= 0
0238: || (cursor == null && (str == null || str.length() == 0))) {
0239: return false;
0240: }
0241:
0242: if (str == null) {
0243: str = "";
0244: }
0245:
0246: char[] text = str.toCharArray();
0247:
0248: int fontHeight = font.getHeight();
0249:
0250: if (cursor != null && cursor.visible == false) {
0251: cursor = null;
0252: }
0253:
0254: if (info.isModified) {
0255:
0256: int[] inout = initGNL(font, w, h, options, offset);
0257:
0258: int numLines = 0;
0259: int height = 0;
0260:
0261: do {
0262: numLines++;
0263: height += fontHeight;
0264: info.numLines = numLines;
0265: if (height < h) {
0266: info.visLines = info.numLines;
0267: }
0268:
0269: inout[GNL_NUM_LINES] = numLines;
0270:
0271: getNextLine(text, font, inout);
0272:
0273: int lineStart = inout[GNL_LINE_START];
0274: int lineEnd = inout[GNL_LINE_END];
0275: int newLineStart = inout[GNL_NEW_LINE_START];
0276:
0277: // IMPL_NOTE: add accessor fn to TextInfo and hide this
0278: //
0279: // check that we don't exceed info's capacity
0280: // before we cache line data and expand if needed
0281: //
0282: if (numLines > info.lineStart.length) {
0283: info.expand();
0284: }
0285: info.lineStart[numLines - 1] = lineStart;
0286: info.lineEnd[numLines - 1] = lineEnd;
0287:
0288: inout[GNL_LINE_START] = newLineStart;
0289: inout[GNL_OFFSET] = 0;
0290: offset = 0;
0291: } while (inout[GNL_LINE_END] < text.length);
0292: info.height = height;
0293: }
0294: if (info.scrollY) {
0295: // if (lineEnd > lineStart) {
0296:
0297: // we are given x,y coordinates and we must calculate
0298: // the best array index to put the cursor
0299: //
0300: if (cursor != null
0301: && cursor.option == PAINT_GET_CURSOR_INDEX
0302: && cursor.x >= 0) {
0303: // cursor.y == height) {
0304:
0305: int curLine = (cursor.y / fontHeight) - 1;
0306: int curX = curLine == 0 ? offset : 0;
0307: int curY = cursor.y;
0308: int bestX = curX;
0309: int bestIndex = info.lineStart[curLine];
0310:
0311: // take one character at a time and check its position
0312: // against the supplied coordinates in cursor
0313: //
0314: int lineStart = info.lineStart[curLine];
0315: int lineEnd = info.lineEnd[curLine];
0316:
0317: for (int i = lineStart; i < lineEnd; i++) {
0318:
0319: char ch = text[i];
0320: if (Math.abs(curX - cursor.preferredX) < Math
0321: .abs(bestX - cursor.preferredX)) {
0322: bestIndex = i;
0323: bestX = curX;
0324: }
0325: curX += font.charWidth(ch);
0326: }
0327:
0328: if (Math.abs(curX - cursor.preferredX) < Math.abs(bestX
0329: - cursor.preferredX)) {
0330: bestIndex = lineEnd;
0331: bestX = curX;
0332: }
0333:
0334: cursor.index = bestIndex;
0335: cursor.x = bestX;
0336: // cursor.y = height;
0337: cursor.option = PAINT_USE_CURSOR_INDEX;
0338: info.cursorLine = curLine;
0339: }
0340: }
0341: if (info.scrollX || info.isModified) {
0342: if (cursor != null
0343: && cursor.option == PAINT_USE_CURSOR_INDEX) {
0344: if (cursor.index >= info.lineStart[info.cursorLine]
0345: && cursor.index <= info.lineEnd[info.cursorLine]) {
0346: // no change to info.cursorLine
0347: } else {
0348: // IMPL_NOTE: start at cursorLine and search before/after
0349: // as this search is non-optimal
0350: for (int i = 0; i < info.numLines; i++) {
0351: // we are given an index...what line is it on?
0352: if (cursor.index >= info.lineStart[i]
0353: && cursor.index <= info.lineEnd[i]) {
0354: info.cursorLine = i;
0355: break;
0356: }
0357: }
0358: }
0359: }
0360: }
0361: // check scroll position and move if needed
0362: if (cursor != null
0363: && (info.isModified || info.scrollX || info.scrollY)) {
0364: if (info.numLines > info.visLines) {
0365: if (info.cursorLine > info.topVis + info.visLines - 1) {
0366: int diff = info.cursorLine
0367: - (info.topVis + info.visLines - 1);
0368: info.topVis += diff;
0369: } else if (info.cursorLine < info.topVis) {
0370: int diff = info.topVis - info.cursorLine;
0371: info.topVis -= diff;
0372: }
0373:
0374: if (info.topVis + info.visLines > info.numLines) {
0375: info.topVis = info.numLines - info.visLines;
0376: }
0377:
0378: } else {
0379: info.topVis = 0;
0380: }
0381: cursor.yOffset = info.topVis * fontHeight;
0382: }
0383: info.scrollX = info.scrollY = info.isModified = false;
0384: return true;
0385: }
0386:
0387: /**
0388: * Paints text from a TextInfo structure.
0389: *
0390: * @param info the TextInfo struct
0391: * @param g the Graphics to paint with
0392: * @param str the text to paint
0393: * @param font the font to use in painting the text
0394: * @param fgColor foreground color
0395: * @param fgHColor foreground hilight color
0396: * @param w the available width for the text
0397: * @param h the available height for the text
0398: * @param offset the first line pixel offset
0399: * @param options any of NORMAL | INVERT | HYPERLINK | TRUNCATE
0400: * @param cursor text cursor object
0401: */
0402: public static void paintText(TextInfo info, Graphics g, String str,
0403: Font font, int fgColor, int fgHColor, int w, int h,
0404: int offset, int options, TextCursor cursor) {
0405:
0406: // NOTE paint not called if TextInfo struct fails
0407: g.setFont(font);
0408: g.setColor(fgColor);
0409:
0410: char[] text = str.toCharArray();
0411: int fontHeight = font.getHeight();
0412:
0413: if (cursor != null && cursor.visible == false) {
0414: cursor = null;
0415: }
0416:
0417: int currentLine = info.topVis;
0418: int height = currentLine * fontHeight;
0419: int y = 0;
0420:
0421: while (currentLine < (info.topVis + info.visLines)) {
0422: height += fontHeight;
0423: y += fontHeight;
0424:
0425: g.drawChars(text, info.lineStart[currentLine],
0426: info.lineEnd[currentLine]
0427: - info.lineStart[currentLine], offset, y,
0428: Graphics.BOTTOM | Graphics.LEFT);
0429:
0430: // draw the vertical cursor indicator if needed
0431: // update the cursor.x and cursor.y info
0432: if (cursor != null
0433: && cursor.option == PAINT_USE_CURSOR_INDEX
0434: && cursor.index >= info.lineStart[currentLine]
0435: && cursor.index <= info.lineEnd[currentLine]) {
0436:
0437: int off = offset;
0438: if (cursor.index > info.lineStart[currentLine]) {
0439: off += font.charsWidth(text,
0440: info.lineStart[currentLine], cursor.index
0441: - info.lineStart[currentLine]);
0442: }
0443:
0444: cursor.x = off;
0445: cursor.y = height;
0446: cursor.width = 1; // IMPL_NOTE: must these always be set?
0447: cursor.height = fontHeight;
0448:
0449: cursor.paint(g);
0450: cursor = null;
0451: }
0452: offset = 0;
0453: currentLine++;
0454: }
0455: }
0456:
0457: /**
0458: * Paints the text, linewrapping when necessary.
0459: *
0460: * @param g the Graphics to use to paint with
0461: * @param str the text to paint
0462: * @param font the font to use to paint the text
0463: * @param fgColor foreground color
0464: * @param fgHColor foreground highlight color
0465: * @param w the available width for the text
0466: * @param h the available height for the text
0467: * @param offset the first line pixel offset
0468: * @param options any of NORMAL | INVERT | HYPERLINK | TRUNCATE
0469: * @param cursor text cursor object to use to draw vertical bar
0470: * @return the width of the last line painted
0471: */
0472: public static int paint(Graphics g, String str, Font font,
0473: int fgColor, int fgHColor, int w, int h, int offset,
0474: int options, TextCursor cursor) {
0475:
0476: if (w <= 0
0477: || (cursor == null && (str == null || str.length() == 0))) {
0478: return 0;
0479: }
0480:
0481: if (str == null) {
0482: str = "";
0483: }
0484:
0485: g.setFont(font);
0486: g.setColor(fgColor);
0487:
0488: char[] text = str.toCharArray();
0489: int fontHeight = font.getHeight();
0490:
0491: if (cursor != null && cursor.visible == false) {
0492: cursor = null;
0493: }
0494:
0495: int[] inout = initGNL(font, w, h, options, offset);
0496:
0497: int numLines = 0;
0498: int height = 0;
0499:
0500: do {
0501: numLines++;
0502: height += fontHeight;
0503:
0504: if (height > h) {
0505: break;
0506: }
0507:
0508: inout[GNL_NUM_LINES] = numLines;
0509:
0510: boolean truncate = getNextLine(text, font, inout);
0511:
0512: int lineStart = inout[GNL_LINE_START];
0513: int lineEnd = inout[GNL_LINE_END];
0514: int newLineStart = inout[GNL_NEW_LINE_START];
0515:
0516: //
0517: // now we can get around to actually draw the text
0518: // lineStart is the array index of the first character to
0519: // start drawing, while lineEnd is the index just after
0520: // the last character to draw.
0521: //
0522: if (lineEnd > lineStart) {
0523:
0524: if ((options & INVERT) == INVERT) {
0525: g.setColor(fgHColor);
0526: } else {
0527: g.setColor(fgColor);
0528: }
0529: if ((options & HYPERLINK) == HYPERLINK) {
0530: drawHyperLink(g, offset, height,
0531: inout[GNL_LINE_WIDTH]);
0532: }
0533:
0534: //
0535: // we are given x,y coordinates and we must calculate
0536: // the best array index to put the cursor
0537: //
0538: if (cursor != null
0539: && cursor.option == PAINT_GET_CURSOR_INDEX
0540: && cursor.x >= 0 && cursor.y == height) {
0541:
0542: int bestIndex = lineStart;
0543: int bestX = offset;
0544: int curX = offset;
0545: int curY = height;
0546:
0547: //
0548: // draw one character at a time and check its position
0549: // against the supplied coordinates in cursor
0550: //
0551: for (int i = lineStart; i < lineEnd; i++) {
0552:
0553: char ch = text[i];
0554:
0555: g.drawChar(ch, curX, curY, Graphics.BOTTOM
0556: | Graphics.LEFT);
0557:
0558: if (Math.abs(curX - cursor.preferredX) < Math
0559: .abs(bestX - cursor.preferredX)) {
0560: bestIndex = i;
0561: bestX = curX;
0562: }
0563:
0564: curX += font.charWidth(ch);
0565: }
0566:
0567: if (Math.abs(curX - cursor.preferredX) < Math
0568: .abs(bestX - cursor.preferredX)) {
0569: bestIndex = lineEnd;
0570: bestX = curX;
0571: }
0572:
0573: //
0574: // draw the ellipsis
0575: //
0576: if (truncate) {
0577: g.drawChar(truncationMark, curX, curY,
0578: Graphics.BOTTOM | Graphics.LEFT);
0579: }
0580:
0581: cursor.index = bestIndex;
0582: cursor.x = bestX;
0583: cursor.y = height;
0584: cursor.option = PAINT_USE_CURSOR_INDEX;
0585:
0586: } else {
0587: g.drawChars(text, lineStart, lineEnd - lineStart,
0588: offset, height, Graphics.BOTTOM
0589: | Graphics.LEFT);
0590:
0591: //
0592: // draw the ellipsis
0593: //
0594: if (truncate) {
0595: g.drawChar(truncationMark, offset
0596: + font.charsWidth(text, lineStart,
0597: lineEnd), height,
0598: Graphics.BOTTOM | Graphics.LEFT);
0599: }
0600: }
0601: }
0602:
0603: //
0604: // try to draw a vertical cursor indicator
0605: //
0606: if (cursor != null
0607: && cursor.option == PAINT_USE_CURSOR_INDEX
0608: && cursor.index >= lineStart
0609: && cursor.index <= lineEnd) {
0610:
0611: int off = offset;
0612: if (cursor.index > lineStart) {
0613: off += font.charsWidth(text, lineStart,
0614: cursor.index - lineStart);
0615: }
0616:
0617: cursor.x = off;
0618: cursor.y = height;
0619: cursor.width = 1;
0620: cursor.height = fontHeight;
0621:
0622: cursor.paint(g);
0623: cursor = null;
0624: }
0625:
0626: inout[GNL_LINE_START] = newLineStart;
0627: inout[GNL_OFFSET] = 0;
0628: offset = 0;
0629:
0630: } while (inout[GNL_LINE_END] < text.length);
0631:
0632: return inout[GNL_LINE_WIDTH];
0633: }
0634:
0635: /**
0636: * Draws a hyperlink image.
0637: *
0638: * @param g the graphics to use to draw the image
0639: * @param x the x location of the image
0640: * @param y the y location of the image
0641: * @param w the width of the hyperlink image
0642: */
0643: public static void drawHyperLink(Graphics g, int x, int y, int w) {
0644:
0645: if (StringItemSkin.IMAGE_LINK == null) {
0646: // System.err.println("Hyperlink image is null");
0647: return;
0648: }
0649: int linkHeight = StringItemSkin.IMAGE_LINK.getHeight();
0650: int linkWidth = StringItemSkin.IMAGE_LINK.getWidth();
0651:
0652: int oldClipX = g.getClipX();
0653: int oldClipW = g.getClipWidth();
0654: int oldClipY = g.getClipY();
0655: int oldClipH = g.getClipHeight();
0656:
0657: g.clipRect(x, oldClipY, w, oldClipH);
0658:
0659: // Then, loop from the end of the string to the beginning,
0660: // drawing the image as we go
0661: for (int j = x + w - linkWidth, first = x - linkWidth; j > first; j -= linkWidth) {
0662: g.drawImage(StringItemSkin.IMAGE_LINK, j, y,
0663: Graphics.BOTTOM | Graphics.LEFT);
0664: }
0665:
0666: g.setClip(oldClipX, oldClipY, oldClipW, oldClipH);
0667: }
0668:
0669: /**
0670: * Gets the height in pixels and the width of the widest line in pixels
0671: * for the given string, calculated based on the availableWidth.
0672: * size[WIDTH] and size[HEIGHT] should be set by this method.
0673: * @param size The array that holds Item content size and location
0674: * in Item internal bounds coordinate system.
0675: * @param availableWidth The width available for this Item
0676: * @param str the string to render
0677: * @param font the font to use to render the string
0678: * @param offset the pixel offset for the first line
0679: *
0680: */
0681: public static void getSizeForWidth(int[] size, int availableWidth,
0682: String str, Font font, int offset) {
0683: // Case 0: null or empty string, no height
0684: if (str == null || str.length() == 0 || availableWidth <= 0) {
0685: size[HEIGHT] = 0;
0686: size[WIDTH] = 0;
0687: return;
0688: }
0689:
0690: char[] text = str.toCharArray();
0691:
0692: int[] inout = initGNL(font, availableWidth, 0, Text.NORMAL,
0693: offset);
0694:
0695: int numLines = 0;
0696: int widest = 0;
0697: boolean widthFound = false;
0698:
0699: do {
0700:
0701: numLines++;
0702:
0703: inout[GNL_NUM_LINES] = numLines;
0704:
0705: getNextLine(text, font, inout);
0706:
0707: if (!widthFound) {
0708: // a long line with no spaces
0709: if (inout[GNL_LINE_WIDTH] > availableWidth
0710: && offset == 0) {
0711: widest = availableWidth;
0712: widthFound = true;
0713: } else if (inout[GNL_LINE_WIDTH] > widest) {
0714: widest = inout[GNL_LINE_WIDTH];
0715: }
0716: }
0717:
0718: inout[GNL_LINE_START] = inout[GNL_NEW_LINE_START];
0719: inout[GNL_OFFSET] = 0;
0720:
0721: } while (inout[GNL_LINE_END] < text.length);
0722:
0723: size[WIDTH] = widest;
0724: size[HEIGHT] = font.getHeight() * numLines;
0725: // return values in size[]
0726: }
0727:
0728: /**
0729: * Gets the height in pixels to render the given string.
0730: * @param str the string to render
0731: * @param font the font to use to render the string
0732: * @param w the available width for the string
0733: * @param offset the pixel offset for the first line
0734: * @return the height in pixels required to render this string completely
0735: */
0736: // IMPL_NOTE - could remove and use getSizeForWidth()
0737: public static int getHeightForWidth(String str, Font font, int w,
0738: int offset) {
0739:
0740: int[] tmpSize = new int[] { 0, 0, 0, 0 };
0741:
0742: getSizeForWidth(tmpSize, w, str, font, offset);
0743: return tmpSize[HEIGHT];
0744: }
0745:
0746: /**
0747: * Utility method to retrieve the length of the longest line of the
0748: * text given the width. this may not necessarily be the entire
0749: * string if there are line breaks or word wraps.
0750: *
0751: * @param str the String to use.
0752: * @param offset a pixel offset for the first line
0753: * @param width the available width for the text
0754: * @param font the font to render the text in
0755: * @return the length of the longest line given the width
0756: */
0757: // IMPL_NOTE - could remove and use getSizeForWidth()
0758: public static int getWidestLineWidth(String str, int offset,
0759: int width, Font font) {
0760:
0761: int[] tmpSize = new int[] { 0, 0, 0, 0 };
0762:
0763: getSizeForWidth(tmpSize, width, str, font, offset);
0764: return tmpSize[WIDTH];
0765: }
0766:
0767: /**
0768: * Calculates the starting and ending points for a new line of
0769: * text given the font and input parameters. Beware of the
0770: * multiple returns statements within the body.
0771: *
0772: * @param text text to process. this must not be null
0773: * @param font font to use for width information
0774: * @param inout an array of in/out parameters, the GNL_ constants
0775: * define the meaning of each element;
0776: * this array implements a structure that keeps data
0777: * between invocations of getNextLine.
0778: * @return true if the text had to be truncated, false otherwise
0779: */
0780: private static boolean getNextLine(char[] text, Font font,
0781: int[] inout) {
0782:
0783: //
0784: // this inner loop will set lineEnd and newLineStart to
0785: // the proper values so that a line is broken correctly
0786: //
0787: int curLoc = inout[GNL_LINE_START];
0788: boolean foundBreak = false;
0789: int leftWidth = 0;
0790:
0791: inout[GNL_LINE_WIDTH] = 0;
0792: int prevLineWidth = 0;
0793: int curLineWidth = 0;
0794:
0795: while (curLoc < text.length) {
0796:
0797: //
0798: // a newLine forces a break and immediately terminates
0799: // the loop
0800: //
0801: // a space will be remembered as a possible place to break
0802: //
0803: if (text[curLoc] == '\n') {
0804: inout[GNL_LINE_END] = curLoc;
0805: inout[GNL_NEW_LINE_START] = curLoc + 1;
0806: inout[GNL_LINE_WIDTH] = prevLineWidth;
0807: return (((inout[GNL_OPTIONS] & TRUNCATE) == TRUNCATE) && ((inout[GNL_NUM_LINES] + 1)
0808: * inout[GNL_FONT_HEIGHT] > inout[GNL_HEIGHT]));
0809:
0810: } else if (text[curLoc] == ' ') {
0811: inout[GNL_LINE_END] = curLoc;
0812: inout[GNL_NEW_LINE_START] = curLoc + 1;
0813: inout[GNL_LINE_WIDTH] = prevLineWidth;
0814: foundBreak = true;
0815: }
0816:
0817: //
0818: // if the text is longer than one line then we
0819: // cut the word at a word boundary if possible,
0820: // otherwise the word is broken.
0821: //
0822:
0823: curLineWidth = prevLineWidth + font.charWidth(text[curLoc]);
0824:
0825: // check up the mode is "truncate" and we reached the end of
0826: // the last line that we can put into the specifed rectangle area
0827: // (inout[GNL_WIDTH] x inout[GNL_HEIGHT])
0828: if (((inout[GNL_OPTIONS] & TRUNCATE) == TRUNCATE)
0829: && ((inout[GNL_NUM_LINES] + 1)
0830: * inout[GNL_FONT_HEIGHT] > inout[GNL_HEIGHT])
0831: && (inout[GNL_OFFSET] + curLineWidth
0832: + inout[GNL_ELLIP_WIDTH] > inout[GNL_WIDTH])) {
0833:
0834: leftWidth = font.charsWidth(text, curLoc + 1,
0835: text.length - curLoc - 1);
0836: //
0837: // we are on the last line and at the point where
0838: // we will need to put an ellipsis if we can't fit
0839: // the rest of the line
0840: //
0841: // if the rest of the line will fit, then don't
0842: // put an ellipsis
0843: //
0844: if (inout[GNL_OFFSET] + curLineWidth + leftWidth > inout[GNL_WIDTH]) {
0845:
0846: prevLineWidth += inout[GNL_ELLIP_WIDTH];
0847:
0848: inout[GNL_LINE_END] = curLoc;
0849: inout[GNL_NEW_LINE_START] = curLoc;
0850: inout[GNL_LINE_WIDTH] = prevLineWidth;
0851:
0852: return true;
0853:
0854: } else {
0855:
0856: curLineWidth += leftWidth;
0857:
0858: inout[GNL_LINE_END] = text.length;
0859: inout[GNL_NEW_LINE_START] = text.length;
0860: inout[GNL_LINE_WIDTH] = curLineWidth;
0861:
0862: return false;
0863: }
0864: // reached the end of the line
0865: } else if (inout[GNL_OFFSET] + curLineWidth > inout[GNL_WIDTH]) {
0866:
0867: if (!foundBreak) {
0868: if (inout[GNL_OFFSET] > 0) {
0869: // move to the next line which will have 0 offset
0870: inout[GNL_LINE_END] = inout[GNL_LINE_START];
0871: inout[GNL_NEW_LINE_START] = inout[GNL_LINE_START];
0872: inout[GNL_LINE_WIDTH] = 0;
0873: } else {
0874: // the line is too long and we need to break it
0875: inout[GNL_LINE_END] = curLoc;
0876: inout[GNL_NEW_LINE_START] = curLoc;
0877: inout[GNL_LINE_WIDTH] = prevLineWidth;
0878: }
0879: }
0880:
0881: return false;
0882: }
0883:
0884: // go to next character
0885: curLoc++;
0886: prevLineWidth = curLineWidth;
0887:
0888: } // while end
0889:
0890: // we reach this code only if we reach the end of the text
0891: inout[GNL_LINE_END] = text.length;
0892: inout[GNL_NEW_LINE_START] = text.length;
0893: inout[GNL_LINE_WIDTH] = curLineWidth;
0894:
0895: return false;
0896: }
0897:
0898: /**
0899: * Utility method to calculate the width and height in which 2
0900: * strings can fit given the strings, fonts and maximum width
0901: * in which those strings should fit. Returned width is either
0902: * the passed in width or a smaller one.
0903: * The offset in pixels for the first string is 0, second string is
0904: * laid out right after the first one with padding in between
0905: * equal to the passed in value.
0906: *
0907: * The width in which both strings would fit given the maximum
0908: * is returned in size[WIDTH]. The height in which both strings
0909: * would fit is returned in size[HEIGHT];
0910: *
0911: * @param size The array that returns contents size
0912: * @param firstStr the first string to use.
0913: * @param secondStr the first string to use.
0914: * @param width the available width for the text
0915: * @param firstFont the font to render the first string in
0916: * @param secondFont the font to render the second string in
0917: * @param pad the horizontal padding that should be used between strings
0918: */
0919: public static void getTwoStringsSize(int[] size, String firstStr,
0920: String secondStr, Font firstFont, Font secondFont,
0921: int width, int pad) {
0922: if (((firstStr == null || firstStr.length() == 0) && (secondStr == null || secondStr
0923: .length() == 0))
0924: || (width <= 0)) {
0925: size[WIDTH] = size[HEIGHT] = 0;
0926: return;
0927: }
0928:
0929: int[] inout = new int[GNL_NUM_PARAMS];
0930:
0931: char[] text;
0932:
0933: int offset = 0;
0934: int widest = 0;
0935: int numLines = 0;
0936: int height = 0;
0937: int fontHeight = 0;
0938:
0939: if (firstStr != null && firstStr.length() > 0) {
0940:
0941: text = firstStr.toCharArray();
0942:
0943: fontHeight = firstFont.getHeight();
0944:
0945: inout = initGNL(firstFont, width, 0, Text.NORMAL, 0);
0946:
0947: do {
0948:
0949: numLines++;
0950: height += fontHeight;
0951:
0952: inout[GNL_NUM_LINES] = numLines;
0953:
0954: getNextLine(text, firstFont, inout);
0955:
0956: if (inout[GNL_LINE_WIDTH] > widest) {
0957: widest = inout[GNL_LINE_WIDTH];
0958: }
0959:
0960: inout[GNL_LINE_START] = inout[GNL_NEW_LINE_START];
0961:
0962: } while (inout[GNL_LINE_END] < firstStr.length());
0963:
0964: offset = inout[GNL_LINE_WIDTH];
0965:
0966: if (secondStr == null || secondStr.length() == 0) {
0967: // last \n in the two strings should be ignored
0968: if (firstStr.charAt(firstStr.length() - 1) == '\n') {
0969: height -= fontHeight;
0970: }
0971: size[HEIGHT] = height;
0972: size[WIDTH] = widest;
0973: return;
0974: }
0975: }
0976: // Second string is not null and it is not empty
0977: if (secondStr != null && secondStr.length() > 0) {
0978: if (offset > 0) {
0979: offset += pad;
0980: }
0981:
0982: text = secondStr.toCharArray();
0983:
0984: fontHeight = secondFont.getHeight();
0985:
0986: // Line that has the end of the first string and the beginning
0987: // of the second one is a special one;
0988: // We have to make sure that it is not counted twice and that
0989: // the right font height is being added (the max of the two)
0990: if (numLines > 0) {
0991: numLines--;
0992: if (inout[GNL_FONT_HEIGHT] > fontHeight) {
0993: height -= fontHeight;
0994: } else {
0995: height -= inout[GNL_FONT_HEIGHT];
0996: }
0997: }
0998:
0999: inout = initGNL(secondFont, width, 0, Text.NORMAL, offset);
1000:
1001: do {
1002: numLines++;
1003: height += fontHeight;
1004:
1005: inout[GNL_NUM_LINES] = numLines;
1006:
1007: getNextLine(text, secondFont, inout);
1008:
1009: if (inout[GNL_OFFSET] + inout[GNL_LINE_WIDTH] > widest) {
1010: widest = inout[GNL_OFFSET] + inout[GNL_LINE_WIDTH];
1011: }
1012:
1013: inout[GNL_LINE_START] = inout[GNL_NEW_LINE_START];
1014: inout[GNL_OFFSET] = 0;
1015:
1016: } while (inout[GNL_LINE_END] < secondStr.length());
1017:
1018: // last \n should be ignored
1019: if (secondStr.charAt(secondStr.length() - 1) == '\n') {
1020: height -= fontHeight;
1021: }
1022: }
1023:
1024: size[WIDTH] = widest;
1025: size[HEIGHT] = height;
1026: return;
1027: }
1028:
1029: // IMPL_NOTE: remove these - there must be a common place to get them
1030: /** Used as an index into the size[], for the x. */
1031: public final static int X = 0;
1032:
1033: /** Used as an index into the size[], for the y. */
1034: public final static int Y = 1;
1035:
1036: /** Used as an index into the size[], for the width. */
1037: public final static int WIDTH = 2;
1038:
1039: /** Used as an index into the size[], for the height. */
1040: public final static int HEIGHT = 3;
1041:
1042: /**
1043: *
1044: *
1045: * @param g the Graphics to paint with
1046: * @param text the string to be painted
1047: * @param font the font to be used
1048: * @param fgColor foreground text color
1049: * @param shdColor shadow color
1050: * @param shdAlign shadow alignment
1051: * @param titlew width
1052:
1053: */
1054: public static void drawTruncStringShadowed(Graphics g, String text,
1055: Font font, int fgColor, int shdColor, int shdAlign,
1056: int titlew) {
1057: int dx = 1, dy = 1;
1058: // draw the shadow
1059: if (shdColor != fgColor) {
1060: switch (shdAlign) {
1061: case (Graphics.TOP | Graphics.LEFT):
1062: dx = -1;
1063: dy = -1;
1064: break;
1065: case (Graphics.TOP | Graphics.RIGHT):
1066: dx = 1;
1067: dy = -1;
1068: break;
1069: case (Graphics.BOTTOM | Graphics.LEFT):
1070: dx = -1;
1071: dy = 1;
1072: break;
1073: case (Graphics.BOTTOM | Graphics.RIGHT):
1074: default:
1075: dx = 1;
1076: dy = 1;
1077: break;
1078: }
1079: g.translate(dx, dy);
1080: drawTruncString(g, text, font, shdColor, titlew);
1081: /* if we wanted multi-line text output, we would use this:
1082: paint(g, text, font,
1083: shdColor, 0,
1084: titlew, titleh, 0,
1085: TRUNCATE, null);
1086: */
1087: g.translate(-dx, -dy);
1088: }
1089: // now draw the text whose shadow we have drawn above
1090: drawTruncString(g, text, font, fgColor, titlew);
1091: /* if we wanted multi-line text output, we would use this:
1092: paint(g, text, font,
1093: fgColor, 0,
1094: titlew, titleh, 0,
1095: TRUNCATE, null);
1096: */
1097: }
1098:
1099: /**
1100: * Given a string, determine the length of a substring that can be drawn
1101: * within the current clipping area.
1102: * If the whole string fits into the clip area,
1103: * return the length of the string.
1104: * Else, return the length of a substring (starting from the beginning
1105: * of the original string) that can be drawn within the current clipping
1106: * area before the truncation indicator.
1107: * The truncation indicator, typically, ellipsis, is not included into
1108: * the returned length.
1109: *
1110: * @param g the Graphics to paint with
1111: * @param str the string to be painted
1112: * @param width the available width, including room
1113: * for the truncation indicator
1114: * @return either the length of str (if it fits into the clip area),
1115: * or the length of the substring that can fit into the clip area
1116: * (not including the truncation mark)
1117: */
1118: public static int canDrawStringPart(Graphics g, String str,
1119: int width) {
1120: if (width < 0) {
1121: return 0;
1122: }
1123: final Font font = g.getFont();
1124: final int stringWidth = font.stringWidth(str);
1125:
1126: if (width >= stringWidth) {
1127: return str.length();
1128: }
1129: final int widthForTruncatedText = width
1130: - font.charWidth(truncationMark);
1131: int availableLength;
1132: for (availableLength = str.length() - 1; font.substringWidth(
1133: str, 0, availableLength) > widthForTruncatedText; availableLength--) {
1134: }
1135: ;
1136: return availableLength;
1137: }
1138:
1139: /**
1140: * Draw the string within the specified width.
1141: * If the string does not fit in the available width,
1142: * it is truncated at the end,
1143: * and a truncation indicator is displayed (usually,
1144: * an ellipsis, but this can be changed).
1145: * Use Graphics.translate(x,y) to specify the anchor point location
1146: * (the alignment will be TOP|LEFT relative to 0,0).
1147: *
1148: * @param g the Graphics to paint with
1149: * @param str the string to be painted
1150: * @param font the font to be used
1151: * @param fgColor the color to paint with
1152: * @param width the width available for painting
1153: */
1154: public static void drawTruncString(Graphics g, String str,
1155: Font font, int fgColor, int width) {
1156: g.setFont(font);
1157: g.setColor(fgColor);
1158: int lengthThatCanBeShown = canDrawStringPart(g, str, width);
1159: if (lengthThatCanBeShown == str.length()) {
1160: g.drawString(str, 0, 0, Graphics.TOP | Graphics.LEFT);
1161: } else {
1162: String s = str.substring(0, lengthThatCanBeShown)
1163: + truncationMark;
1164: g.drawString(s, 0, 0, Graphics.TOP | Graphics.LEFT);
1165: }
1166: }
1167:
1168: }
|