/********************************************************************************* * TotalCross Software Development Kit * * Copyright (C) 1998, 1999 Wabasoft <www.wabasoft.com> * * Copyright (C) 2000-2012 SuperWaba Ltda. * * All Rights Reserved * * * * This library and virtual machine 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. * * * * This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 * * A copy of this license is located in file license.txt at the root of this * * SDK or can be downloaded here: * * http://www.gnu.org/licenses/lgpl-3.0.txt * * * *********************************************************************************/ package totalcross.ui; import totalcross.sys.*; import totalcross.ui.event.*; import totalcross.ui.gfx.*; /** * Label is a text label control. It supports multiline, * but you need to separate the text with \n. A Label never receives focus neither dispatches events. * <p> * Note: this class does not do automatic parse of the text. * However, you can use a handy method to parse the text that you want to display: see the * totalcross.sys.Convert.insertLineBreak method. * <p> * Here is an example showing a label being used: * * <pre> * public class MyProgram extends MainWindow * { * public void initUI() * { * add(new Label("Value:"), LEFT, TOP); * } * </pre> */ public class Label extends Control { /** The String with the full text (not splitted) */ protected String text; /** The text alignment; possible values are LEFT, RIGHT, CENTER, FILL (justifies the text). * If align is CENTER and the text is wider than the Label, only the right portion will be lost. */ public int align; // guich@400_71: made public instead of protected /** Set to false if you want to scroll the text a line at a time instead of a page at a time. */ public boolean pageScroll = true; static final String[] emptyStringArray = {""}; protected String []lines = emptyStringArray; private int []linesW; private int linesPerPage,currentLine; private boolean invert; private boolean is3d; private boolean highlighted; private int fColor; // sholtzer@450_21: added support for setEnabled(false) private int dColor=-1; // guich@450: darker color if in 3d mode /** Set automatically to true when an empty string is passed in the constructor. */ public boolean useFillAsPreferred; private int marqueeCount, marqueeStep, marqueeX; private TimerEvent marqueeTimer; private int highlightColor=-1,userHighlightColor=-1; private Insets insets; // guich@tc114_71 private int lastASW; private String originalText; /** By default, the getPreferredWidth uses the current text to compute the width. * However, if you create a Label with a predefined text that will be changed * later, in the advent of a reposition, the preferred width will be recomputed * again using the current text and not the predefined one. * * For example, if you do: * <pre> * Label l = new Label("99"); * l.setText("0"); * </pre> * ... then the preferred width will be computed based on "0", not in "99". * * To change this behaviour, assign to this field the text that you want to be * used to compute the preferred width. * @since TotalCross 1.3 */ public String preferredWidthText; /** Set to a color to let this Label have a border with that color. Defaults to -1, which means no border. * Note that the border affects the Label's size (width and height are increased by 4), so you must set * it this field before setting the rect. * If you want a bigger gap between border and text, you can use setInsets. * @see #setInsets(int, int, int, int) * @since TotalCross 1.27 */ public int borderColor = -1; // guich@tc126_51 /** Specifies a solid background for this button (the default). * @see #VERTICAL_GRADIENT_BACKGROUND * @see #HORIZONTAL_GRADIENT_BACKGROUND * @since TotalCross 1.15 */ public static final byte SOLID_BACKGROUND = 0; /** Specifies a vertical gradient background for this Label. * @see #SOLID_BACKGROUND * @see #HORIZONTAL_GRADIENT_BACKGROUND * @since TotalCross 1.15 */ public static final byte VERTICAL_GRADIENT_BACKGROUND = 1; /** Specifies a horizontal 3d-gradient background for this Label. Used in the setBorder method. * @see #VERTICAL_GRADIENT_BACKGROUND * @see #SOLID_BACKGROUND * @since TotalCross 1.15 */ public static final byte HORIZONTAL_GRADIENT_BACKGROUND = 2; // guich@tc110_11 /** The type of background of this Label (defaults to SOLID_BACKGROUND). * One color is the background color, and the other color is defined by * <code>firstGradientColor</code> and <code>secondGradientColor</code>. * @see #HORIZONTAL_GRADIENT_BACKGROUND * @see #VERTICAL_GRADIENT_BACKGROUND * @see #SOLID_BACKGROUND * @see #firstGradientColor * @see #secondGradientColor * @since TotalCross 1.15 */ public int backgroundType = SOLID_BACKGROUND; /** The first color used in GRADIENT backgrounds. Defaults to Color.WHITE. * @since TotalCross 1.15 */ public int firstGradientColor = Color.WHITE; /** The second color used in GRADIENT backgrounds. Defaults to Color.BLACK. * @since TotalCross 1.15 */ public int secondGradientColor; /** Set to true to let the label split its text based on the width every time its width * changes. If the height is PREFERRED, the Label will change its size accordingly. * You may change the height again calling setRect. * @since TotalCross 1.14 */ public boolean autoSplit; // guich@tc114_74 /** The vertical alignment. Defaults to CENTER * @see #TOP * @see #CENTER * @see #BOTTOM * @since TotalCross 1.14 */ public int vAlign = CENTER; /** * Creates an empty label, using FILL as the preferred width. */ public Label() { this("", LEFT); } /** * Creates a label displaying the given text. Alignment is set to LEFT by default.<br> * Supports inverted text, multiple lines and is scrollable by default. * * @param text * the to be text displayed * * @throws NullPointerException * if text is null. */ public Label(String text) { this(text, LEFT); } /** * Creates a label displaying the given text with the given alignment.<br> * Supports inverted text, multiple lines and is scrollable by default. * * @param text * the text displayed; cannot be null, but can be an empty string. * @param align * the alignment * @throws NullPointerException * if text is null. * @throws IllegalArgumentException * if align value is not either LEFT, RIGHT, CENTER or FILL. * @see #align */ public Label(String text, int align) { if (align != LEFT && align != RIGHT && align != CENTER && align != FILL) throw new IllegalArgumentException("Argument 'align' value must be either LEFT, RIGHT, CENTER or FILL"); this.useFillAsPreferred = text.length() == 0; this.align = align; setText(text); clearValueStr = null; // guich@573_3 focusTraversable = false; } /** Defines a space to be placed around the text of this label. * @since TotalCross 1.14 */ public void setInsets(int left, int right, int top, int bottom) // guich@tc114_71 { if (insets == null) insets = new Insets(); insets.left = left; insets.right = right; insets.top = top; insets.bottom = bottom; } /** Shows this label as a horizontal marquee. The text must have a single line. * To stop the marquee, just call setText. When a window covers the marquee, it * is halted, and resumed when the window closes. * @param text the text to be displayed * @param delay the timer delay in ms used to scroll the marquee. 100 is a good value. * @param loopCount the number of times the text will loop. Set to -1 to loop forever. * When the loop count is reached, the text is cleared. * @param step the step in pixels in which the text will be scrolled. If > 0, will * scroll from left to right; if < 0, will scroll from right to left. * @since TotalCross 1.0 beta 4 */ public void setMarqueeText(String text, int delay, int loopCount, int step) { if (step != 0) marqueeX = step > 0 ? -linesW[0] : width; if (marqueeTimer != null) stopMarquee(); this.text = text; lines = new String[]{text}; currentLine = 0; onFontChanged(); if (step != 0) { marqueeCount = loopCount; marqueeStep = step; marqueeTimer = addTimer(delay); } } /** Returns true if the marquee is running. * @since TotalCross 1.0 beta 4 */ public boolean isMarqueeRunning() { return marqueeTimer != null; } public void onEvent(Event e) { if (marqueeTimer != null && marqueeTimer.triggered) { Window w = getParentWindow(); if (w != null && w.isTopMost()) // update only if our window is the one being shown { marqueeX += marqueeStep; repaintNow(); // using this instead of repaint prevents an Edit to be painted too often, because repaint affects all controls on screen, while repaintNow affects only this control } } } /** Draws the label with a 3d effect. Automatically turns off invert. */ public void set3d(boolean on) { is3d = on; if (on) invert = false; if (on && dColor == -1) onColorsChanged(true); // guich@500_3: create dColor } /** Inverts the back and fore colors. Automatically turns off 3d. */ public void setInvert(boolean on) { invert = on; if (on) is3d = false; } /** Highlights the text, i.e., paints the text in all directions with a brighter color, then centered, with the foreground color. * @see #setHighlightedColor(int) * @since TotalCross 1.01 */ public void setHighlighted(boolean on) // guich@tc110_14 { highlighted = on; if (on) is3d = invert = false; highlightColor = userHighlightColor != -1 ? userHighlightColor : Color.brighter(backColor); } /** The color used when highlighting is on. Defaults to a brighter foreground color. * @param c The color to be used as highlighted color. Pass -1 to use the default one. * @see #setHighlighted(boolean) * @since TotalCross 1.01 */ public void setHighlightedColor(int c) // guich@tc110_14 { highlightColor = userHighlightColor = c; } /** Splits the text to the given width. Remember to set the font (or add the Label to its parent) * before calling this method. * @since TotalCross 1.14 * @see #autoSplit */ public void split(int maxWidth) // guich@tc114_73 { String text = originalText; // originalText will be changed by setText setText(Convert.insertLineBreak(maxWidth, fm, text)); // guich@tc126_18: text cannot be assigned here or originalText will be overwritten originalText = text; } /** Sets the text that is displayed in the label. Newline is represented as \n. */ public void setText(String text) { originalText = text; if (marqueeTimer != null) stopMarquee(); this.text = autoSplit && width > 0 ? Convert.insertLineBreak(this.width, fm, text) : text; lines = this.text.equals("") ? new String[]{""} : Convert.tokenizeString(this.text,'\n'); // guich@tc100: now we use \n currentLine = 0; onFontChanged(); Window.needsPaint = true; } /** Stops the marquee, but does not change the current text. */ public void stopMarquee() { if (marqueeTimer != null) { TimerEvent t = marqueeTimer; marqueeTimer = null; removeTimer(t); } marqueeStep = 0; } /** Gets the text that is displayed in the label. */ public String getText() { return text; } /** Returns the preffered width of this control. */ public int getPreferredWidth() { return useFillAsPreferred ? FILL : preferredWidthText != null ? fm.stringWidth(preferredWidthText) : (getMaxTextWidth() + (insets==null ? 0 : insets.left+insets.right)) + (borderColor == -1 ? 0 : 4); } /** Returns the maximum text width for the lines of this Label. */ public int getMaxTextWidth() { int w = 0; for (int i =lines.length-1; i >= 0; i--) if (linesW[i] > w) // guich@450_36: why call stringWidth if linesW has everything? w = linesW[i]; return w+(invert || highlighted?2:0); } /** Returns the preffered height of this control. */ public int getPreferredHeight() { return fmH*lines.length + Edit.prefH + (highlighted ? 2 : 0) +(insets==null ? 0 : insets.top+insets.bottom) + (borderColor == -1 ? 0 : 4); // if inverted, make sure the string is surrounded by the black box - guich@401_18: added commonVGap } protected void onColorsChanged(boolean colorsChanged) { fColor = getForeColor(); // sholtzer@450_21: added support for setEnabled(false) if (is3d) dColor = Color.darker(backColor); if (highlighted) highlightColor = userHighlightColor != -1 ? userHighlightColor : Color.brighter(backColor); } protected void onFontChanged() { int i; if (linesW == null || linesW.length != lines.length) // guich@450_36: avoid keep recreating the int array linesW = new int[lines.length]; int inv = (invert && align==RIGHT)?1:0; // guich@400_88 int []linesW = this.linesW; // guich@450_36: use local var for (i = lines.length-1; i >= 0; i--) linesW[i] = fm.stringWidth(lines[i]) + inv; } protected void onBoundsChanged(boolean screenChanged) { if (autoSplit && this.width > 0 && this.width != lastASW) // guich@tc114_74 - guich@tc120_5: only if PREFERRED was choosen in first setRect - guich@tc126_35 { lastASW = this.width; split(this.width); if (PREFERRED-RANGE <= setH && setH <= PREFERRED+RANGE) setRect(KEEP,KEEP,KEEP,getPreferredHeight() + setH-PREFERRED); } linesPerPage = height / fmH; if (linesPerPage < 1) linesPerPage = 1; } /** Returns if the label can scroll in the given direction. A Label can scroll if the number of * text lines is greater than the actual height. */ public boolean canScroll(boolean down) // guich@200b4_142 { if (lines.length > linesPerPage) return down ? (currentLine+linesPerPage < lines.length) : (currentLine >= linesPerPage); return false; } /** Scrolls the text to the begining. */ public void scrollToBegin() { currentLine = 0; } /** Scroll the text to the end. */ public void scrollToEnd() { currentLine = lines.length - linesPerPage; } /** Scroll to the given line. * Can be used to scroll the Label using a ScrollBar. Here's a sample, assuming that sbVert has been added to the container: * <pre> * sbVert.setMaximum(lab.getLineCount()); * sbVert.setVisibleItems(lab.getLinesPerPage()); * sbVert.setUnitIncrement(1); * sbVert.setLiveScrolling(true); * </pre> * At the onEvent: * <pre> * if (event.type == ControlEvent.PRESSED && event.target == sbVert) * lab.scrollTo(sbVert.getValue()); * </pre> * @since TotalCross 1.2 */ public void scrollTo(int line) { if (lines.length > linesPerPage && line != linesPerPage && 0 <= line && line < lines.length) { currentLine = line; Window.needsPaint = true; } } /** Return the number of lines per page of this label. * @since TotalCross 1.2 */ public int getLinesPerPage() { return this.linesPerPage; } /** Return the number of lines of this label. * @since TotalCross 1.2 */ public int getLineCount() { return lines.length; } /** Scroll one page. returns true if success, false if no scroll possible * @see #pageScroll */ public boolean scroll(boolean down) { int n = pageScroll ? linesPerPage : 1; if (lines.length > linesPerPage) { int lastLine = currentLine; if (down) { if (currentLine+linesPerPage < lines.length) currentLine += n; } else { currentLine -= n; if (currentLine < 0) currentLine = 0; } if (lastLine != currentLine) { Window.needsPaint = true; return true; } } return false; } /** Called by the system to draw the button. */ public void onPaint(Graphics g) { // draw label if (invert) { g.foreColor = backColor; g.backColor = fColor; } else { g.foreColor = fColor; g.backColor = backColor; } // guich@200b4_126: repaint the background always. if (!transparentBackground) { if (backgroundType == SOLID_BACKGROUND || !isEnabled()) // guich@tc115_78: use solid or gradient backgrounds g.fillRect(0,0,width,height); // guich@200b4_120: make sure the label is painted with the correct color else { g.fillShadedRect(0,0,width, height, true, backgroundType == HORIZONTAL_GRADIENT_BACKGROUND,firstGradientColor, secondGradientColor,100); g.foreColor = invert ? backColor : fColor; g.backColor = invert ? fColor : backColor; } } if (borderColor != -1) // only after background has been filled g.translate(2,2); if (text.length() > 0) { int y; switch (vAlign) { case TOP: y = (insets == null ? 0 : insets.top); break; case BOTTOM: y = this.height - fmH*Math.min(lines.length,linesPerPage) - (insets == null ? 0 : insets.bottom); break; default: y = ((this.height - fmH*Math.min(lines.length,linesPerPage)) >> 1) + (insets == null ? 0 : insets.top); break; // guich@tc115_34: min of lines.length and linesPerPage } if (marqueeStep != 0) { int shadow = textShadowColor != -1 ? textShadowColor : highlighted ? highlightColor : -1; g.drawText(lines[0], marqueeX, y, shadow != -1, shadow); if (marqueeStep < 0) { if (marqueeX <= -linesW[0]) if (--marqueeCount == 0) setText(""); else marqueeX = width - (insets == null ? 0 : insets.right); } else // > 0 if (marqueeX >= width) if (--marqueeCount == 0) setText(""); else marqueeX = -linesW[0] + (insets == null ? 0 : insets.left); } else { int n = Math.min(currentLine+linesPerPage, lines.length); int x0 = (insets == null ? 0 : insets.left); int xx = invert || highlighted ? 1 : 0 + x0; int fmH = this.fmH; // guich@450_36: use local var int []linesW = this.linesW; // same for (int i =currentLine; i < n; i++,y+=fmH) { int justify = align == FILL && (i < lines.length-1) ? this.width-1 : 0; // don't justify the text line int x = x0; if (align != LEFT) { if (align == CENTER) { x = (width - linesW[i]) >> 1; if (x < x0) x = x0; // guich@tc114_70 } else if (align == RIGHT) x = width - linesW[i] - (insets == null ? 0 : insets.right); } if (is3d) // if 3d, invert = false { g.foreColor = dColor; g.drawText(lines[i], xx+x+1, y+1, justify); g.foreColor = fColor; g.drawText(lines[i], xx+x, y, justify); } else g.drawText(lines[i], xx+x, y, justify, textShadowColor != -1, textShadowColor); } } } if (borderColor != -1) { g.translate(-2,-2); g.foreColor = borderColor; g.drawRect(0,0,width,height); } } /** This method does nothing: clear a label is usually not a desired action. * However, if you really want to clear it, set its clearValueStr property. */ public void clear() // guich@572_19 { if (clearValueStr != null) // guich@573_3 setText(clearValueStr); } }