view dwt/custom/CLabel.d @ 13:f565d3a95c0a

Ported dwt.internal
author Jacob Carlborg <doob@me.com> <jacob.carlborg@gmail.com>
date Fri, 22 Aug 2008 16:46:34 +0200
parents 380af2bdd8e5
children 6337764516f1
line wrap: on
line source

/*******************************************************************************
 * Copyright (c) 2000, 2006 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     
 * Port to the D programming language:
 *     Jacob Carlborg <jacob.carlborg@gmail.com>
 *******************************************************************************/
module dwt.custom.CLabel;

import dwt.DWT;
import dwt.DWTException;
import dwt.accessibility.ACC;
import dwt.accessibility.Accessible;
import dwt.accessibility.AccessibleAdapter;
import dwt.accessibility.AccessibleControlAdapter;
import dwt.accessibility.AccessibleControlEvent;
import dwt.accessibility.AccessibleEvent;
import dwt.events.DisposeEvent;
import dwt.events.DisposeListener;
import dwt.events.PaintEvent;
import dwt.events.PaintListener;
import dwt.events.TraverseEvent;
import dwt.events.TraverseListener;
import dwt.graphics.Color;
import dwt.graphics.Font;
import dwt.graphics.GC;
import dwt.graphics.Image;
import dwt.graphics.Point;
import dwt.graphics.Rectangle;
import dwt.graphics.TextLayout;
import dwt.widgets.Canvas;
import dwt.widgets.Composite;
import dwt.widgets.Control;
import dwt.widgets.Display;

import dwt.dwthelper.utils;

/**
 * A Label which supports aligned text and/or an image and different border styles.
 * <p>
 * If there is not enough space a CLabel uses the following strategy to fit the 
 * information into the available space:
 * <pre>
 *      ignores the indent in left align mode
 *      ignores the image and the gap
 *      shortens the text by replacing the center portion of the label with an ellipsis
 *      shortens the text by removing the center portion of the label
 * </pre>
 * <p>
 * <dl>
 * <dt><b>Styles:</b>
 * <dd>LEFT, RIGHT, CENTER, SHADOW_IN, SHADOW_OUT, SHADOW_NONE</dd>
 * <dt><b>Events:</b>
 * <dd></dd>
 * </dl>
 * 
 * </p><p>
 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
 * </p>
 */
public class CLabel : Canvas
{

    /** Gap between icon and text */
    private static const int GAP = 5;
    /** Left and right margins */
    private static const int INDENT = 3;
    /** a String inserted in the middle of text that has been shortened */
    private static const String ELLIPSIS = "..."; //$NON-NLS-1$ // could use the ellipsis glyph on some platforms "\u2026"
    /** the alignnment. Either CENTER, RIGHT, LEFT. Default is LEFT*/
    private int alignn = DWT.LEFT;
    private int hIndent = INDENT;
    private int vIndent = INDENT;
    /** the current text */
    private String text;
    /** the current icon */
    private Image image;
    // The tooltip is used for two purposes - the application can set
    // a tooltip or the tooltip can be used to display the full text when the
    // the text has been truncated due to the label being too short.
    // The appToolTip stores the tooltip set by the application.  Control.tooltiptext 
    // contains whatever tooltip is currently being displayed.
    private String appToolTipText;

    private Image backgroundImage;
    private Color[] gradientColors;
    private int[] gradientPercents;
    private bool gradientVertical;
    private Color background;

    private static int
            DRAW_FLAGS = DWT.DRAW_MNEMONIC | DWT.DRAW_TAB | DWT.DRAW_TRANSPARENT | DWT.DRAW_DELIMITER;

    /**
     * Constructs a new instance of this class given its parent
     * and a style value describing its behavior and appearance.
     * <p>
     * The style value is either one of the style constants defined in
     * class <code>DWT</code> which is applicable to instances of this
     * class, or must be built by <em>bitwise OR</em>'ing together 
     * (that is, using the <code>int</code> "|" operator) two or more
     * of those <code>DWT</code> style constants. The class description
     * lists the style constants that are applicable to the class.
     * Style bits are also inherited from superclasses.
     * </p>
     *
     * @param parent a widget which will be the parent of the new instance (cannot be null)
     * @param style the style of widget to construct
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
     * </ul>
     * @exception DWTException <ul>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
     * </ul>
     *
     * @see DWT#LEFT
     * @see DWT#RIGHT
     * @see DWT#CENTER
     * @see DWT#SHADOW_IN
     * @see DWT#SHADOW_OUT
     * @see DWT#SHADOW_NONE
     * @see #getStyle()
     */
    public this (Composite parent, int style)
    {
        super(parent, checkStyle(style));
        if ((style & (DWT.CENTER | DWT.RIGHT)) is 0)
            style |= DWT.LEFT;
        if ((style & DWT.CENTER) !is 0)
            alignn = DWT.CENTER;
        if ((style & DWT.RIGHT) !is 0)
            alignn = DWT.RIGHT;
        if ((style & DWT.LEFT) !is 0)
            alignn = DWT.LEFT;

        addPaintListener(new class PaintListener
        {
            public void paintControl (PaintEvent event)
            {
                onPaint(event);
            }
        });

        addDisposeListener(new class DisposeListener
        {
            public void widgetDisposed (DisposeEvent event)
            {
                onDispose(event);
            }
        });

        addTraverseListener(new class TraverseListener
        {
            public void keyTraversed (TraverseEvent event)
            {
                if (event.detail is DWT.TRAVERSE_MNEMONIC)
                {
                    onMnemonic(event);
                }
            }
        });

        initAccessible();

    }

    /**
     * Check the style bits to ensure that no invalid styles are applied.
     */
    private static int checkStyle (int style)
    {
        if ((style & DWT.BORDER) !is 0)
            style |= DWT.SHADOW_IN;
        int
                mask = DWT.SHADOW_IN | DWT.SHADOW_OUT | DWT.SHADOW_NONE | DWT.LEFT_TO_RIGHT | DWT.RIGHT_TO_LEFT;
        style = style & mask;
        return style |= DWT.NO_FOCUS | DWT.DOUBLE_BUFFERED;
    }

    //protected void checkSubclass () {
    //  String name = getClass().getName ();
    //  String validName = CLabel.class.getName();
    //  if (!validName.opEquals(name)) {
    //      DWT.error (DWT.ERROR_INVALID_SUBCLASS);
    //  }
    //}

    public Point computeSize (int wHint, int hHint, bool changed)
    {
        checkWidget();
        Point e = getTotalSize(image, text);
        if (wHint is DWT.DEFAULT)
        {
            e.x += 2 * hIndent;
        }
        else
        {
            e.x = wHint;
        }
        if (hHint is DWT.DEFAULT)
        {
            e.y += 2 * vIndent;
        }
        else
        {
            e.y = hHint;
        }
        return e;
    }

    /**
     * Draw a rectangle in the given colors.
     */
    private void drawBevelRect (GC gc, int x, int y, int w, int h,
            Color topleft, Color bottomright)
    {
        gc.setForeground(bottomright);
        gc.drawLine(x + w, y, x + w, y + h);
        gc.drawLine(x, y + h, x + w, y + h);

        gc.setForeground(topleft);
        gc.drawLine(x, y, x + w - 1, y);
        gc.drawLine(x, y, x, y + h - 1);
    }

    /*
     * Return the lowercase of the first non-'&' character following
     * an '&' character in the given String. If there are no '&'
     * characters in the given String, return '\0'.
     */
    char _findMnemonic (String str)
    {
        if (str is null)
            return '\0';
        int index = 0;
        int length = str.length();
        do
        {
            while (index < length && str.charAt(index) !is '&')
                index++;
            if (++index >= length)
                return '\0';
            if (str.charAt(index) !is '&')
                return CharacterToLower(str.charAt(index));
            index++;
        } while (index < length);
        return '\0';
    }

    /**
     * Returns the alignnment.
     * The alignnment style (LEFT, CENTER or RIGHT) is returned.
     * 
     * @return DWT.LEFT, DWT.RIGHT or DWT.CENTER
     */
    public int getAlignment ()
    {
        //checkWidget();
        return alignn;
    }

    /**
     * Return the CLabel's image or <code>null</code>.
     * 
     * @return the image of the label or null
     */
    public Image getImage ()
    {
        //checkWidget();
        return image;
    }

    /**
     * Compute the minimum size.
     */
    private Point getTotalSize (Image image, String text)
    {
        Point size = new Point(0, 0);

        if (image !is null)
        {
            Rectangle r = image.getBounds();
            size.x += r.width;
            size.y += r.height;
        }

        GC gc = new GC(this);
        if (text !is null && text.length() > 0)
        {
            Point e = gc.textExtent(text, DRAW_FLAGS);
            size.x += e.x;
            size.y = Math.max(size.y, e.y);
            if (image !is null)
                size.x += GAP;
        }
        else
        {
            size.y = Math.max(size.y, gc.getFontMetrics().getHeight());
        }
        gc.dispose();

        return size;
    }

    public int getStyle ()
    {
        int style = super.getStyle();
        switch (alignn)
        {
            case DWT.RIGHT:
                style |= DWT.RIGHT;
            break;
            case DWT.CENTER:
                style |= DWT.CENTER;
            break;
            case DWT.LEFT:
                style |= DWT.LEFT;
            break;
        }
        return style;
    }

    /**
     * Return the Label's text.
     * 
     * @return the text of the label or null
     */
    public String getText ()
    {
        //checkWidget();
        return text;
    }

    public String getToolTipText ()
    {
        checkWidget();
        return appToolTipText;
    }

    private void initAccessible ()
    {
        Accessible accessible = getAccessible();
        accessible.addAccessibleListener(new class AccessibleAdapter
        {
            public void getName (AccessibleEvent e)
            {
                e.result = getText();
            }

            public void getHelp (AccessibleEvent e)
            {
                e.result = getToolTipText();
            }

            public void getKeyboardShortcut (AccessibleEvent e)
            {
                char mnemonic = _findMnemonic(this.text);
                if (mnemonic !is '\0')
                {
                    e.result = "Alt+" + mnemonic; //$NON-NLS-1$
            }
        }
    }   );

        accessible.addAccessibleControlListener(new class
                AccessibleControlAdapter
        {
            public void getChildAtPoint (AccessibleControlEvent e)
            {
                e.childID = ACC.CHILDID_SELF;
            }

            public void getLocation (AccessibleControlEvent e)
            {
                Rectangle rect = getDisplay().map(getParent(), null,
                        getBounds());
                e.x = rect.x;
                e.y = rect.y;
                e.width = rect.width;
                e.height = rect.height;
            }

            public void getChildCount (AccessibleControlEvent e)
            {
                e.detail = 0;
            }

            public void getRole (AccessibleControlEvent e)
            {
                e.detail = ACC.ROLE_LABEL;
            }

            public void getState (AccessibleControlEvent e)
            {
                e.detail = ACC.STATE_READONLY;
            }
        });
    }

    void onDispose (DisposeEvent event)
    {
        gradientColors = null;
        gradientPercents = null;
        backgroundImage = null;
        text = null;
        image = null;
        appToolTipText = null;
    }

    void onMnemonic (TraverseEvent event)
    {
        char mnemonic = _findMnemonic(text);
        if (mnemonic is '\0')
            return;
        if (CharacterToLower(event.character) !is mnemonic)
            return;
        Composite control = this.getParent();
        while (control !is null)
        {
            Control[] children = control.getChildren();
            int index = 0;
            while (index < children.length)
            {
                if (children[index] is this)
                    break;
                index++;
            }
            index++;
            if (index < children.length)
            {
                if (children[index].setFocus())
                {
                    event.doit = true;
                    event.detail = DWT.TRAVERSE_NONE;
                }
            }
            control = control.getParent();
        }
    }

    void onPaint (PaintEvent event)
    {
        Rectangle rect = getClientArea();
        if (rect.width is 0 || rect.height is 0)
            return;

        bool shortenText = false;
        String t = text;
        Image img = image;
        int availableWidth = Math.max(0, rect.width - 2 * hIndent);
        Point extent = getTotalSize(img, t);
        if (extent.x > availableWidth)
        {
            img = null;
            extent = getTotalSize(img, t);
            if (extent.x > availableWidth)
            {
                shortenText = true;
            }
        }

        GC gc = event.gc;
        String[] lines = text is null ? null : splitString(text);

        // shorten the text
        if (shortenText)
        {
            extent.x = 0;
            for (int i = 0; i < lines.length; i++)
            {
                Point e = gc.textExtent(lines[i], DRAW_FLAGS);
                if (e.x > availableWidth)
                {
                    lines[i] = shortenText(gc, lines[i], availableWidth);
                    extent.x = Math.max(extent.x,
                            getTotalSize(null, lines[i]).x);
                }
                else
                {
                    extent.x = Math.max(extent.x, e.x);
                }
            }
            if (appToolTipText is null)
            {
                super.setToolTipText(text);
            }
        }
        else
        {
            super.setToolTipText(appToolTipText);
        }

        // determine horizontal position
        int x = rect.x + hIndent;
        if (alignn is DWT.CENTER)
        {
            x = (rect.width - extent.x) / 2;
        }
        if (alignn is DWT.RIGHT)
        {
            x = rect.width - hIndent - extent.x;
        }

        // draw a background image behind the text
        try
        {
            if (backgroundImage !is null)
            {
                // draw a background image behind the text
                Rectangle imageRect = backgroundImage.getBounds();
                // tile image to fill space
                gc.setBackground(getBackground());
                gc.fillRectangle(rect);
                int xPos = 0;
                while (xPos < rect.width)
                {
                    int yPos = 0;
                    while (yPos < rect.height)
                    {
                        gc.drawImage(backgroundImage, xPos, yPos);
                        yPos += imageRect.height;
                    }
                    xPos += imageRect.width;
                }
            }
            else if (gradientColors !is null)
            {
                // draw a gradient behind the text
                const Color oldBackground = gc.getBackground();
                if (gradientColors.length is 1)
                {
                    if (gradientColors[0] !is null)
                        gc.setBackground(gradientColors[0]);
                    gc.fillRectangle(0, 0, rect.width, rect.height);
                }
                else
                {
                    const Color oldForeground = gc.getForeground();
                    Color lastColor = gradientColors[0];
                    if (lastColor is null)
                        lastColor = oldBackground;
                    int pos = 0;
                    for (int i = 0; i < gradientPercents.length; ++i)
                    {
                        gc.setForeground(lastColor);
                        lastColor = gradientColors[i + 1];
                        if (lastColor is null)
                            lastColor = oldBackground;
                        gc.setBackground(lastColor);
                        if (gradientVertical)
                        {
                            const int
                                    gradientHeight = (gradientPercents[i] * rect.height / 100) - pos;
                            gc.fillGradientRectangle(0, pos, rect.width,
                                    gradientHeight, true);
                            pos += gradientHeight;
                        }
                        else
                        {
                            const int
                                    gradientWidth = (gradientPercents[i] * rect.width / 100) - pos;
                            gc.fillGradientRectangle(pos, 0, gradientWidth,
                                    rect.height, false);
                            pos += gradientWidth;
                        }
                    }
                    if (gradientVertical && pos < rect.height)
                    {
                        gc.setBackground(getBackground());
                        gc.fillRectangle(0, pos, rect.width, rect.height - pos);
                    }
                    if (!gradientVertical && pos < rect.width)
                    {
                        gc.setBackground(getBackground());
                        gc.fillRectangle(pos, 0, rect.width - pos, rect.height);
                    }
                    gc.setForeground(oldForeground);
                }
                gc.setBackground(oldBackground);
            }
            else
            {
                if (background !is null || (getStyle() & DWT.DOUBLE_BUFFERED) is 0)
                {
                    gc.setBackground(getBackground());
                    gc.fillRectangle(rect);
                }
            }
        }
        catch (DWTException e)
        {
            if ((getStyle() & DWT.DOUBLE_BUFFERED) is 0)
            {
                gc.setBackground(getBackground());
                gc.fillRectangle(rect);
            }
        }

        // draw border
        int style = getStyle();
        if ((style & DWT.SHADOW_IN) !is 0 || (style & DWT.SHADOW_OUT) !is 0)
        {
            paintBorder(gc, rect);
        }

        // draw the image
        if (img !is null)
        {
            Rectangle imageRect = img.getBounds();
            gc.drawImage(img, 0, 0, imageRect.width, imageRect.height, x,
                    (rect.height - imageRect.height) / 2, imageRect.width,
                    imageRect.height);
            x += imageRect.width + GAP;
            extent.x -= imageRect.width + GAP;
        }
        // draw the text
        if (lines !is null)
        {
            int lineHeight = gc.getFontMetrics().getHeight();
            int textHeight = lines.length * lineHeight;
            int lineY = Math.max(vIndent,
                    rect.y + (rect.height - textHeight) / 2);
            gc.setForeground(getForeground());
            for (int i = 0; i < lines.length; i++)
            {
                int lineX = x;
                if (lines.length > 1)
                {
                    if (alignn is DWT.CENTER)
                    {
                        int lineWidth = gc.textExtent(lines[i], DRAW_FLAGS).x;
                        lineX = x + Math.max(0, (extent.x - lineWidth) / 2);
                    }
                    if (alignn is DWT.RIGHT)
                    {
                        int lineWidth = gc.textExtent(lines[i], DRAW_FLAGS).x;
                        lineX = Math.max(x,
                                rect.x + rect.width - hIndent - lineWidth);
                    }
                }
                gc.drawText(lines[i], lineX, lineY, DRAW_FLAGS);
                lineY += lineHeight;
            }
        }
    }

    /**
     * Paint the Label's border.
     */
    private void paintBorder (GC gc, Rectangle r)
    {
        Display disp = getDisplay();

        Color c1 = null;
        Color c2 = null;

        int style = getStyle();
        if ((style & DWT.SHADOW_IN) !is 0)
        {
            c1 = disp.getSystemColor(DWT.COLOR_WIDGET_NORMAL_SHADOW);
            c2 = disp.getSystemColor(DWT.COLOR_WIDGET_HIGHLIGHT_SHADOW);
        }
        if ((style & DWT.SHADOW_OUT) !is 0)
        {
            c1 = disp.getSystemColor(DWT.COLOR_WIDGET_LIGHT_SHADOW);
            c2 = disp.getSystemColor(DWT.COLOR_WIDGET_NORMAL_SHADOW);
        }

        if (c1 !is null && c2 !is null)
        {
            gc.setLineWidth(1);
            drawBevelRect(gc, r.x, r.y, r.width - 1, r.height - 1, c1, c2);
        }
    }

    /**
     * Set the alignnment of the CLabel.
     * Use the values LEFT, CENTER and RIGHT to alignn image and text within the available space.
     * 
     * @param alignn the alignnment style of LEFT, RIGHT or CENTER
     * 
     * @exception DWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     *    <li>ERROR_INVALID_ARGUMENT - if the value of alignn is not one of DWT.LEFT, DWT.RIGHT or DWT.CENTER</li>
     * </ul>
     */
    public void setAlignment (int alignn)
    {
        checkWidget();
        if (alignn !is DWT.LEFT && alignn !is DWT.RIGHT && alignn !is DWT.CENTER)
        {
            DWT.error(DWT.ERROR_INVALID_ARGUMENT);
        }
        if (this.alignn !is alignn)
        {
            this.alignn = alignn;
            redraw();
        }
    }

    public void setBackground (Color color)
    {
        super.setBackground(color);
        // Are these settings the same as before?
        if (backgroundImage is null && gradientColors is null && gradientPercents is null)
        {
            if (color is null)
            {
                if (background is null)
                    return;
            }
            else
            {
                if (color.opEquals(background))
                    return;
            }
        }
        background = color;
        backgroundImage = null;
        gradientColors = null;
        gradientPercents = null;
        redraw();
    }

    /**
     * Specify a gradient of colours to be drawn in the background of the CLabel.
     * <p>For example, to draw a gradient that varies from dark blue to blue and then to
     * white and stays white for the right half of the label, use the following call 
     * to setBackground:</p>
     * <pre>
     *  clabel.setBackground(new Color[]{display.getSystemColor(DWT.COLOR_DARK_BLUE), 
     *                                 display.getSystemColor(DWT.COLOR_BLUE),
     *                                 display.getSystemColor(DWT.COLOR_WHITE), 
     *                                 display.getSystemColor(DWT.COLOR_WHITE)},
     *                     new int[] {25, 50, 100});
     * </pre>
     *
     * @param colors an array of Color that specifies the colors to appear in the gradient 
     *               in order of appearance from left to right;  The value <code>null</code> 
     *               clears the background gradient; the value <code>null</code> can be used 
     *               inside the array of Color to specify the background color.
     * @param percents an array of integers between 0 and 100 specifying the percent of the width 
     *                 of the widget at which the color should change; the size of the percents 
     *                 array must be one less than the size of the colors array.
     * 
     * @exception DWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     *    <li>ERROR_INVALID_ARGUMENT - if the values of colors and percents are not consistent</li>
     * </ul>
     */
    public void setBackground (Color[] colors, int[] percents)
    {
        setBackground(colors, percents, false);
    }

    /**
     * Specify a gradient of colours to be drawn in the background of the CLabel.
     * <p>For example, to draw a gradient that varies from dark blue to white in the vertical,
     * direction use the following call 
     * to setBackground:</p>
     * <pre>
     *  clabel.setBackground(new Color[]{display.getSystemColor(DWT.COLOR_DARK_BLUE), 
     *                                 display.getSystemColor(DWT.COLOR_WHITE)},
     *                       new int[] {100}, true);
     * </pre>
     *
     * @param colors an array of Color that specifies the colors to appear in the gradient 
     *               in order of appearance from left/top to right/bottom;  The value <code>null</code> 
     *               clears the background gradient; the value <code>null</code> can be used 
     *               inside the array of Color to specify the background color.
     * @param percents an array of integers between 0 and 100 specifying the percent of the width/height 
     *                 of the widget at which the color should change; the size of the percents 
     *                 array must be one less than the size of the colors array.
     * @param vertical indicate the direction of the gradient.  True is vertical and false is horizontal.
     * 
     * @exception DWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     *    <li>ERROR_INVALID_ARGUMENT - if the values of colors and percents are not consistent</li>
     * </ul>
     * 
     * @since 3.0
     */
    public void setBackground (Color[] colors, int[] percents, bool vertical)
    {
        checkWidget();
        if (colors !is null)
        {
            if (percents is null || percents.length !is colors.length - 1)
            {
                DWT.error(DWT.ERROR_INVALID_ARGUMENT);
            }
            if (getDisplay().getDepth() < 15)
            {
                // Don't use gradients on low color displays
                colors = new Color[][colors[colors.length - 1]];
                percents = new int[][];
            }
            for (int i = 0; i < percents.length; i++)
            {
                if (percents[i] < 0 || percents[i] > 100)
                {
                    DWT.error(DWT.ERROR_INVALID_ARGUMENT);
                }
                if (i > 0 && percents[i] < percents[i - 1])
                {
                    DWT.error(DWT.ERROR_INVALID_ARGUMENT);
                }
            }
        }

        // Are these settings the same as before?
        const Color background = getBackground();
        if (backgroundImage is null)
        {
            if ((gradientColors !is null) && (colors !is null) && (gradientColors.length is colors.length))
            {
                bool same = false;
                for (int i = 0; i < gradientColors.length; i++)
                {
                    same = (gradientColors[i] is colors[i]) || ((gradientColors[i] is null) && (colors[i] is background)) || ((gradientColors[i] is background) && (colors[i] is null));
                    if (!same)
                        break;
                }
                if (same)
                {
                    for (int i = 0; i < gradientPercents.length; i++)
                    {
                        same = gradientPercents[i] is percents[i];
                        if (!same)
                            break;
                    }
                }
                if (same && this.gradientVertical is vertical)
                    return;
            }
        }
        else
        {
            backgroundImage = null;
        }
        // Store the new settings
        if (colors is null)
        {
            gradientColors = null;
            gradientPercents = null;
            gradientVertical = false;
        }
        else
        {
            gradientColors = new Color[colors.length];
            for (int i = 0; i < colors.length; ++i)
                gradientColors[i] = (colors[i] !is null) ? colors[i] : background;
            gradientPercents = new int[percents.length];
            for (int i = 0; i < percents.length; ++i)
                gradientPercents[i] = percents[i];
            gradientVertical = vertical;
        }
        // Refresh with the new settings
        redraw();
    }

    /**
     * Set the image to be drawn in the background of the label.
     * 
     * @param image the image to be drawn in the background
     * 
     * @exception DWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setBackground (Image image)
    {
        checkWidget();
        if (image is backgroundImage)
            return;
        if (image !is null)
        {
            gradientColors = null;
            gradientPercents = null;
        }
        backgroundImage = image;
        redraw();

    }

    public void setFont (Font font)
    {
        super.setFont(font);
        redraw();
    }

    /**
     * Set the label's Image.
     * The value <code>null</code> clears it.
     * 
     * @param image the image to be displayed in the label or null
     * 
     * @exception DWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setImage (Image image)
    {
        checkWidget();
        if (image !is this.image)
        {
            this.image = image;
            redraw();
        }
    }

    /**
     * Set the label's text.
     * The value <code>null</code> clears it.
     * 
     * @param text the text to be displayed in the label or null
     * 
     * @exception DWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setText (String text)
    {
        checkWidget();
        if (text is null)
            text = ""; //$NON-NLS-1$
        if (!text.opEquals(this.text))
        {
            this.text = text;
            redraw();
        }
    }

    public void setToolTipText (String String)
    {
        super.setToolTipText(String);
        appToolTipText = super.getToolTipText();
    }

    /**
     * Shorten the given text <code>t</code> so that its length doesn't exceed
     * the given width. The default implementation replaces characters in the
     * center of the original String with an ellipsis ("...").
     * Override if you need a different strategy.
     * 
     * @param gc the gc to use for text measurement
     * @param t the text to shorten
     * @param width the width to shorten the text to, in pixels
     * @return the shortened text
     */
    protected String shortenText (GC gc, String t, int width)
    {
        if (t is null)
            return null;
        int w = gc.textExtent(ELLIPSIS, DRAW_FLAGS).x;
        if (width <= w)
            return t;
        int l = t.length();
        int max = l / 2;
        int min = 0;
        int mid = (max + min) / 2 - 1;
        if (mid <= 0)
            return t;
        TextLayout layout = new TextLayout(getDisplay());
        layout.setText(t);
        mid = validateOffset(layout, mid);
        while (min < mid && mid < max)
        {
            String s1 = t.substring(0, mid);
            String s2 = t.substring(validateOffset(layout, l - mid), l);
            int l1 = gc.textExtent(s1, DRAW_FLAGS).x;
            int l2 = gc.textExtent(s2, DRAW_FLAGS).x;
            if (l1 + w + l2 > width)
            {
                max = mid;
                mid = validateOffset(layout, (max + min) / 2);
            }
            else if (l1 + w + l2 < width)
            {
                min = mid;
                mid = validateOffset(layout, (max + min) / 2);
            }
            else
            {
                min = max;
            }
        }
        String
                result = mid is 0 ? t : t.substring(0, mid) + ELLIPSIS + t.substring(
                        validateOffset(layout, l - mid), l);
        layout.dispose();
        return result;
    }

    int validateOffset (TextLayout layout, int offset)
    {
        int nextOffset = layout.getNextOffset(offset, DWT.MOVEMENT_CLUSTER);
        if (nextOffset !is offset)
            return layout.getPreviousOffset(nextOffset, DWT.MOVEMENT_CLUSTER);
        return offset;
    }

    private String[] splitString (String text)
    {
        String[] lines = new String[1];
        int start = 0, pos;
        do
        {
            pos = text.indexOf('\n', start);
            if (pos is -1)
            {
                lines[lines.length - 1] = text.substring(start);
            }
            else
            {
                bool crlf = (pos > 0) && (text.charAt(pos - 1) is '\r');
                lines[lines.length - 1] = text.substring(start,
                        pos - (crlf ? 1 : 0));
                start = pos + 1;
                String[] newLines = new String[lines.length + 1];
                System.arraycopy(lines, 0, newLines, 0, lines.length);
                lines = newLines;
            }
        } while (pos !is -1);
        return lines;
    }
}