/*******************************************************************************
 * Copyright (c) 2000, 2018 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.swt.custom;

import org.eclipse.swt.*;
import org.eclipse.swt.accessibility.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;

/**
 * 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>
 * <dl>
 * <dt><b>Styles:</b>
 * <dd>LEFT, RIGHT, CENTER, SHADOW_IN, SHADOW_OUT, SHADOW_NONE</dd>
 * <dt><b>Events:</b>
 * <dd>(NONE)</dd>
 * </dl>
 *
 *<p>
 * This class may be subclassed for the purpose of overriding the default string
 * shortening algorithm that is implemented in method <code>shortenText()</code>.
 * </p>
 *
 * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: CustomControlExample</a>
 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
 * @see CLabel#shortenText(GC, String, int)
 */
public class CLabel extends Canvas {

	/** Gap between icon and text */
	private static final int GAP = 5;
	/** Left and right margins */
	private static final int DEFAULT_MARGIN = 3;
	/** a string inserted in the middle of text that has been shortened */
	private static final String ELLIPSIS = "..."; //$NON-NLS-1$ // could use the ellipsis glyph on some platforms "\u2026"
	/** the alignment. Either CENTER, RIGHT, LEFT. Default is LEFT*/
	private int align = SWT.LEFT;
	private int leftMargin = DEFAULT_MARGIN;
	private int topMargin = DEFAULT_MARGIN;
	private int rightMargin = DEFAULT_MARGIN;
	private int bottomMargin = DEFAULT_MARGIN;
	/** 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 boolean ignoreDispose;

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

	private static int DRAW_FLAGS = SWT.DRAW_MNEMONIC | SWT.DRAW_TAB | SWT.DRAW_TRANSPARENT | SWT.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>SWT</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>SWT</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 SWTException <ul>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
 * </ul>
 *
 * @see SWT#LEFT
 * @see SWT#RIGHT
 * @see SWT#CENTER
 * @see SWT#SHADOW_IN
 * @see SWT#SHADOW_OUT
 * @see SWT#SHADOW_NONE
 * @see #getStyle()
 */
public CLabel(Composite parent, int style) {
	super(parent, checkStyle(style));
	if ((style & (SWT.CENTER | SWT.RIGHT)) == 0) style |= SWT.LEFT;
	if ((style & SWT.CENTER) != 0) align = SWT.CENTER;
	if ((style & SWT.RIGHT) != 0)  align = SWT.RIGHT;
	if ((style & SWT.LEFT) != 0)   align = SWT.LEFT;

	addPaintListener(this::onPaint);

	addTraverseListener(event -> {
		if (event.detail == SWT.TRAVERSE_MNEMONIC) {
			onMnemonic(event);
		}
	});

	addListener(SWT.Dispose, this::onDispose);

	initAccessible();

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

@Override
public Point computeSize(int wHint, int hHint, boolean changed) {
	checkWidget();
	Point e = getTotalSize(image, text);
	if (wHint == SWT.DEFAULT){
		e.x += leftMargin + rightMargin;
	} else {
		e.x = wHint;
	}
	if (hHint == SWT.DEFAULT) {
		e.y += topMargin + bottomMargin;
	} 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 string) {
	if (string == null) return '\0';
	int index = 0;
	int length = string.length ();
	do {
		while (index < length && string.charAt (index) != '&') index++;
		if (++index >= length) return '\0';
		if (string.charAt (index) != '&') return Character.toLowerCase (string.charAt (index));
		index++;
	} while (index < length);
	return '\0';
}
/**
 * Returns the horizontal alignment.
 * The alignment style (LEFT, CENTER or RIGHT) is returned.
 *
 * @return SWT.LEFT, SWT.RIGHT or SWT.CENTER
 */
public int getAlignment() {
	/*
	 * This call is intentionally commented out, to allow this getter method to be
	 * called from a thread which is different from one that created the widget.
	 */
	//checkWidget();
	return align;
}
/**
 * Return the CLabel's bottom margin.
 *
 * @return the bottom margin of the label
 *
 * @since 3.6
 */
public int getBottomMargin() {
	/*
	 * This call is intentionally commented out, to allow this getter method to be
	 * called from a thread which is different from one that created the widget.
	 */
	//checkWidget();
	return bottomMargin;
}
/**
 * Return the CLabel's image or <code>null</code>.
 *
 * @return the image of the label or null
 */
public Image getImage() {
	/*
	 * This call is intentionally commented out, to allow this getter method to be
	 * called from a thread which is different from one that created the widget.
	 */
	//checkWidget();
	return image;
}
/**
 * Return the CLabel's left margin.
 *
 * @return the left margin of the label
 *
 * @since 3.6
 */
public int getLeftMargin() {
	/*
	 * This call is intentionally commented out, to allow this getter method to be
	 * called from a thread which is different from one that created the widget.
	 */
	//checkWidget();
	return leftMargin;
}
/**
 * Return the CLabel's right margin.
 *
 * @return the right margin of the label
 *
 * @since 3.6
 */
public int getRightMargin() {
	/*
	 * This call is intentionally commented out, to allow this getter method to be
	 * called from a thread which is different from one that created the widget.
	 */
	//checkWidget();
	return rightMargin;
}
/**
 * Compute the minimum size.
 */
private Point getTotalSize(Image image, String text) {
	Point size = new Point(0, 0);

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

	GC gc = new GC(this);
	if (text != 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 != null) size.x += GAP;
	} else {
		size.y = Math.max(size.y, gc.getFontMetrics().getHeight());
	}
	gc.dispose();

	return size;
}
@Override
public int getStyle () {
	int style = super.getStyle();
	switch (align) {
		case SWT.RIGHT: style |= SWT.RIGHT; break;
		case SWT.CENTER: style |= SWT.CENTER; break;
		case SWT.LEFT: style |= SWT.LEFT; break;
	}
	return style;
}

/**
 * Return the Label's text.
 *
 * @return the text of the label or null
 */
public String getText() {
	/*
	 * This call is intentionally commented out, to allow this getter method to be
	 * called from a thread which is different from one that created the widget.
	 */
	//checkWidget();
	return text;
}
@Override
public String getToolTipText () {
	checkWidget();
	return appToolTipText;
}
/**
 * Return the CLabel's top margin.
 *
 * @return the top margin of the label
 *
 * @since 3.6
 */
public int getTopMargin() {
	/*
	 * This call is intentionally commented out, to allow this getter method to be
	 * called from a thread which is different from one that created the widget.
	 */
	//checkWidget();
	return topMargin;
}
private void initAccessible() {
	Accessible accessible = getAccessible();
	accessible.addAccessibleListener(new AccessibleAdapter() {
		@Override
		public void getName(AccessibleEvent e) {
			e.result = getText();
		}

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

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

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

		@Override
		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;
		}

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

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

		@Override
		public void getState(AccessibleControlEvent e) {
			e.detail = ACC.STATE_READONLY;
		}
	});
}
void onDispose(Event event) {
	/* make this handler run after other dispose listeners */
	if (ignoreDispose) {
		ignoreDispose = false;
		return;
	}
	ignoreDispose = true;
	notifyListeners (event.type, event);
	event.type = SWT.NONE;

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

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

	boolean shortenText = false;
	String t = text;
	Image img = image;
	int availableWidth = Math.max(0, rect.width - (leftMargin + rightMargin));
	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 == 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 == null) {
			super.setToolTipText(text);
		}
	} else {
		super.setToolTipText(appToolTipText);
	}

	// determine horizontal position
	int x = rect.x + leftMargin;
	if (align == SWT.CENTER) {
		x = (rect.width - extent.x)/2;
	}
	if (align == SWT.RIGHT) {
		x = rect.width - rightMargin - extent.x;
	}

	// draw a background image behind the text
	try {
		if (backgroundImage != 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 != null) {
			// draw a gradient behind the text
			final Color oldBackground = gc.getBackground();
			if (gradientColors.length == 1) {
				if (gradientColors[0] != null) gc.setBackground(gradientColors[0]);
				gc.fillRectangle(0, 0, rect.width, rect.height);
			} else {
				final Color oldForeground = gc.getForeground();
				Color lastColor = gradientColors[0];
				if (lastColor == null) lastColor = oldBackground;
				int pos = 0;
				for (int i = 0; i < gradientPercents.length; ++i) {
					gc.setForeground(lastColor);
					lastColor = gradientColors[i + 1];
					if (lastColor == null) lastColor = oldBackground;
					gc.setBackground(lastColor);
					if (gradientVertical) {
						final int gradientHeight = (gradientPercents[i] * rect.height / 100) - pos;
						gc.fillGradientRectangle(0, pos, rect.width, gradientHeight, true);
						pos += gradientHeight;
					} else {
						final 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 != null || (getStyle() & SWT.DOUBLE_BUFFERED) == 0) && background.getAlpha() > 0) {
				gc.setBackground(getBackground());
				gc.fillRectangle(rect);
			}
		}
	} catch (SWTException e) {
		if ((getStyle() & SWT.DOUBLE_BUFFERED) == 0) {
			gc.setBackground(getBackground());
			gc.fillRectangle(rect);
		}
	}

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

	/*
	 * Compute text height and image height. If image height is more than
	 * the text height, draw image starting from top margin. Else draw text
	 * starting from top margin.
	 */
	Rectangle imageRect = null;
	int lineHeight = 0, textHeight = 0, imageHeight = 0;

	if (img != null) {
		imageRect = img.getBounds();
		imageHeight = imageRect.height;
	}
	if (lines != null) {
		lineHeight = gc.getFontMetrics().getHeight();
		textHeight = lines.length * lineHeight;
	}

	int imageY = 0, midPoint = 0, lineY = 0;
	if (imageHeight > textHeight ) {
		if (topMargin == DEFAULT_MARGIN && bottomMargin == DEFAULT_MARGIN) imageY = rect.y + (rect.height - imageHeight) / 2;
		else imageY = topMargin;
		midPoint = imageY + imageHeight/2;
		lineY = midPoint - textHeight / 2;
	}
	else {
		if (topMargin == DEFAULT_MARGIN && bottomMargin == DEFAULT_MARGIN) lineY = rect.y + (rect.height - textHeight) / 2;
		else lineY = topMargin;
		midPoint = lineY + textHeight/2;
		imageY = midPoint - imageHeight / 2;
	}

	// draw the image
	if (img != null) {
		gc.drawImage(img, 0, 0, imageRect.width, imageHeight,
						x, imageY, imageRect.width, imageHeight);
		x +=  imageRect.width + GAP;
		extent.x -= imageRect.width + GAP;
	}

	// draw the text
	if (lines != null) {
		gc.setForeground(getForeground());
		for (String line : lines) {
			int lineX = x;
			if (lines.length > 1) {
				if (align == SWT.CENTER) {
					int lineWidth = gc.textExtent(line, DRAW_FLAGS).x;
					lineX = x + Math.max(0, (extent.x - lineWidth) / 2);
				}
				if (align == SWT.RIGHT) {
					int lineWidth = gc.textExtent(line, DRAW_FLAGS).x;
					lineX = Math.max(x, rect.x + rect.width - rightMargin - lineWidth);
				}
			}
			gc.drawText(line, 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 & SWT.SHADOW_IN) != 0) {
		c1 = disp.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
		c2 = disp.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW);
	}
	if ((style & SWT.SHADOW_OUT) != 0) {
		c1 = disp.getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW);
		c2 = disp.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
	}

	if (c1 != null && c2 != null) {
		gc.setLineWidth(1);
		drawBevelRect(gc, r.x, r.y, r.width-1, r.height-1, c1, c2);
	}
}
/**
 * Set the horizontal alignment of the CLabel.
 * Use the values LEFT, CENTER and RIGHT to align image and text within the available space.
 *
 * @param align the alignment style of LEFT, RIGHT or CENTER
 *
 * @exception SWTException <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 align is not one of SWT.LEFT, SWT.RIGHT or SWT.CENTER</li>
 * </ul>
 */
public void setAlignment(int align) {
	checkWidget();
	if (align != SWT.LEFT && align != SWT.RIGHT && align != SWT.CENTER) {
		SWT.error(SWT.ERROR_INVALID_ARGUMENT);
	}
	if (this.align != align) {
		this.align = align;
		redraw();
	}
}

@Override
public void setBackground (Color color) {
	super.setBackground (color);
	// Are these settings the same as before?
	if (backgroundImage == null &&
		gradientColors == null &&
		gradientPercents == null) {
		if (color == null) {
			if (background == null) return;
		} else {
			if (color.equals(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(SWT.COLOR_DARK_BLUE),
 *		                           display.getSystemColor(SWT.COLOR_BLUE),
 *		                           display.getSystemColor(SWT.COLOR_WHITE),
 *		                           display.getSystemColor(SWT.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 SWTException <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(SWT.COLOR_DARK_BLUE),
 *		                           display.getSystemColor(SWT.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 SWTException <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, boolean vertical) {
	checkWidget();
	if (colors != null) {
		if (percents == null || percents.length != colors.length - 1) {
			SWT.error(SWT.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) {
				SWT.error(SWT.ERROR_INVALID_ARGUMENT);
			}
			if (i > 0 && percents[i] < percents[i-1]) {
				SWT.error(SWT.ERROR_INVALID_ARGUMENT);
			}
		}
	}

	// Are these settings the same as before?
	final Color background = getBackground();
	if (backgroundImage == null) {
		if ((gradientColors != null) && (colors != null) &&
			(gradientColors.length == colors.length)) {
			boolean same = false;
			for (int i = 0; i < gradientColors.length; i++) {
				same = (gradientColors[i] == colors[i]) ||
					((gradientColors[i] == null) && (colors[i] == background)) ||
					((gradientColors[i] == background) && (colors[i] == null));
				if (!same) break;
			}
			if (same) {
				for (int i = 0; i < gradientPercents.length; i++) {
					same = gradientPercents[i] == percents[i];
					if (!same) break;
				}
			}
			if (same && this.gradientVertical == vertical) return;
		}
	} else {
		backgroundImage = null;
	}
	// Store the new settings
	if (colors == 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] != 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 SWTException <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 == backgroundImage) return;
	if (image != null) {
		gradientColors = null;
		gradientPercents = null;
	}
	backgroundImage = image;
	redraw();

}
/**
 * Set the label's bottom margin, in points.
 *
 * @param bottomMargin the bottom margin of the label, which must be equal to or greater than zero
 *
 * @exception SWTException <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>
 *
 * @since 3.6
 */
public void setBottomMargin(int bottomMargin) {
	checkWidget();
	if (this.bottomMargin == bottomMargin || bottomMargin < 0) return;
	this.bottomMargin = bottomMargin;
	redraw();
}
@Override
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 SWTException <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 != this.image) {
		this.image = image;
		redraw();
	}
}
/**
 * Set the label's horizontal left margin, in points.
 *
 * @param leftMargin the left margin of the label, which must be equal to or greater than zero
 *
 * @exception SWTException <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>
 *
 * @since 3.6
 */
public void setLeftMargin(int leftMargin) {
	checkWidget();
	if (this.leftMargin == leftMargin || leftMargin < 0) return;
	this.leftMargin = leftMargin;
	redraw();
}
/**
 * Set the label's margins, in points.
 *
 * @param leftMargin the left margin.
 * @param topMargin the top margin.
 * @param rightMargin the right margin.
 * @param bottomMargin the bottom margin.
 * @exception SWTException <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>
 *
 * @since 3.6
 */
public void setMargins (int leftMargin, int topMargin, int rightMargin, int bottomMargin) {
	checkWidget();
	this.leftMargin = Math.max(0, leftMargin);
	this.topMargin = Math.max(0, topMargin);
	this.rightMargin = Math.max(0, rightMargin);
	this.bottomMargin = Math.max(0, bottomMargin);
	redraw();
}
/**
 * Set the label's right margin, in points.
 *
 * @param rightMargin the right margin of the label, which must be equal to or greater than zero
 *
 * @exception SWTException <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>
 *
 * @since 3.6
 */
public void setRightMargin(int rightMargin) {
	checkWidget();
	if (this.rightMargin == rightMargin || rightMargin < 0) return;
	this.rightMargin = rightMargin;
	redraw();
}
/**
 * Set the label's text.
 * The value <code>null</code> clears it.
 * <p>
 * Mnemonics are indicated by an '&amp;' that causes the next
 * character to be the mnemonic.  When the user presses a
 * key sequence that matches the mnemonic, focus is assigned
 * to the control that follows the label. On most platforms,
 * the mnemonic appears underlined but may be emphasised in a
 * platform specific manner.  The mnemonic indicator character
 * '&amp;' can be escaped by doubling it in the string, causing
 * a single '&amp;' to be displayed.
 * </p><p>
 * Note: If control characters like '\n', '\t' etc. are used
 * in the string, then the behavior is platform dependent.
 * </p>
 *
 * @param text the text to be displayed in the label or null
 *
 * @exception SWTException <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 == null) text = ""; //$NON-NLS-1$
	if (! text.equals(this.text)) {
		this.text = text;
		redraw();
	}
}
@Override
public void setToolTipText (String string) {
	super.setToolTipText (string);
	appToolTipText = super.getToolTipText();
}
/**
 * Set the label's top margin, in points.
 *
 * @param topMargin the top margin of the label, which must be equal to or greater than zero
 *
 * @exception SWTException <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>
 *
 * @since 3.6
 */
public void setTopMargin(int topMargin) {
	checkWidget();
	if (this.topMargin == topMargin || topMargin < 0) return;
	this.topMargin = topMargin;
	redraw();
}
/**
 * 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 points
 * @return the shortened text
 */
protected String shortenText(GC gc, String t, int width) {
	if (t == 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 == 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, SWT.MOVEMENT_CLUSTER);
	if (nextOffset != offset) return layout.getPreviousOffset(nextOffset, SWT.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 == -1) {
			lines[lines.length - 1] = text.substring(start);
		} else {
			boolean crlf = (pos > 0) && (text.charAt(pos - 1) == '\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 != -1);
	return lines;
}
}
