1 /*******************************************************************************
2  * Copyright (c) 2000, 2018 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.swt.custom;
15 
16 import org.eclipse.swt.*;
17 import org.eclipse.swt.accessibility.*;
18 import org.eclipse.swt.events.*;
19 import org.eclipse.swt.graphics.*;
20 import org.eclipse.swt.widgets.*;
21 
22 /**
23  * A Label which supports aligned text and/or an image and different border styles.
24  * <p>
25  * If there is not enough space a CLabel uses the following strategy to fit the
26  * information into the available space:
27  * <pre>
28  * 		ignores the indent in left align mode
29  * 		ignores the image and the gap
30  * 		shortens the text by replacing the center portion of the label with an ellipsis
31  * 		shortens the text by removing the center portion of the label
32  * </pre>
33  * <dl>
34  * <dt><b>Styles:</b>
35  * <dd>LEFT, RIGHT, CENTER, SHADOW_IN, SHADOW_OUT, SHADOW_NONE</dd>
36  * <dt><b>Events:</b>
37  * <dd>(NONE)</dd>
38  * </dl>
39  *
40  *<p>
41  * This class may be subclassed for the purpose of overriding the default string
42  * shortening algorithm that is implemented in method <code>shortenText()</code>.
43  * </p>
44  *
45  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: CustomControlExample</a>
46  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
47  * @see CLabel#shortenText(GC, String, int)
48  */
49 public class CLabel extends Canvas {
50 
51 	/** Gap between icon and text */
52 	private static final int GAP = 5;
53 	/** Left and right margins */
54 	private static final int DEFAULT_MARGIN = 3;
55 	/** a string inserted in the middle of text that has been shortened */
56 	private static final String ELLIPSIS = "..."; //$NON-NLS-1$ // could use the ellipsis glyph on some platforms "\u2026"
57 	/** the alignment. Either CENTER, RIGHT, LEFT. Default is LEFT*/
58 	private int align = SWT.LEFT;
59 	private int leftMargin = DEFAULT_MARGIN;
60 	private int topMargin = DEFAULT_MARGIN;
61 	private int rightMargin = DEFAULT_MARGIN;
62 	private int bottomMargin = DEFAULT_MARGIN;
63 	/** the current text */
64 	private String text;
65 	/** the current icon */
66 	private Image image;
67 	// The tooltip is used for two purposes - the application can set
68 	// a tooltip or the tooltip can be used to display the full text when the
69 	// the text has been truncated due to the label being too short.
70 	// The appToolTip stores the tooltip set by the application.  Control.tooltiptext
71 	// contains whatever tooltip is currently being displayed.
72 	private String appToolTipText;
73 	private boolean ignoreDispose;
74 
75 	private Image backgroundImage;
76 	private Color[] gradientColors;
77 	private int[] gradientPercents;
78 	private boolean gradientVertical;
79 	private Color background;
80 
81 	private static int DRAW_FLAGS = SWT.DRAW_MNEMONIC | SWT.DRAW_TAB | SWT.DRAW_TRANSPARENT | SWT.DRAW_DELIMITER;
82 
83 /**
84  * Constructs a new instance of this class given its parent
85  * and a style value describing its behavior and appearance.
86  * <p>
87  * The style value is either one of the style constants defined in
88  * class <code>SWT</code> which is applicable to instances of this
89  * class, or must be built by <em>bitwise OR</em>'ing together
90  * (that is, using the <code>int</code> "|" operator) two or more
91  * of those <code>SWT</code> style constants. The class description
92  * lists the style constants that are applicable to the class.
93  * Style bits are also inherited from superclasses.
94  * </p>
95  *
96  * @param parent a widget which will be the parent of the new instance (cannot be null)
97  * @param style the style of widget to construct
98  *
99  * @exception IllegalArgumentException <ul>
100  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
101  * </ul>
102  * @exception SWTException <ul>
103  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
104  * </ul>
105  *
106  * @see SWT#LEFT
107  * @see SWT#RIGHT
108  * @see SWT#CENTER
109  * @see SWT#SHADOW_IN
110  * @see SWT#SHADOW_OUT
111  * @see SWT#SHADOW_NONE
112  * @see #getStyle()
113  */
CLabel(Composite parent, int style)114 public CLabel(Composite parent, int style) {
115 	super(parent, checkStyle(style));
116 	if ((style & (SWT.CENTER | SWT.RIGHT)) == 0) style |= SWT.LEFT;
117 	if ((style & SWT.CENTER) != 0) align = SWT.CENTER;
118 	if ((style & SWT.RIGHT) != 0)  align = SWT.RIGHT;
119 	if ((style & SWT.LEFT) != 0)   align = SWT.LEFT;
120 
121 	addPaintListener(event -> onPaint(event));
122 
123 	addTraverseListener(event -> {
124 		if (event.detail == SWT.TRAVERSE_MNEMONIC) {
125 			onMnemonic(event);
126 		}
127 	});
128 
129 	addListener(SWT.Dispose, event -> onDispose(event));
130 
131 	initAccessible();
132 
133 }
134 /**
135  * Check the style bits to ensure that no invalid styles are applied.
136  */
checkStyle(int style)137 private static int checkStyle (int style) {
138 	if ((style & SWT.BORDER) != 0) style |= SWT.SHADOW_IN;
139 	int mask = SWT.SHADOW_IN | SWT.SHADOW_OUT | SWT.SHADOW_NONE | SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT;
140 	style = style & mask;
141 	return style |= SWT.NO_FOCUS | SWT.DOUBLE_BUFFERED;
142 }
143 
144 @Override
computeSize(int wHint, int hHint, boolean changed)145 public Point computeSize(int wHint, int hHint, boolean changed) {
146 	checkWidget();
147 	Point e = getTotalSize(image, text);
148 	if (wHint == SWT.DEFAULT){
149 		e.x += leftMargin + rightMargin;
150 	} else {
151 		e.x = wHint;
152 	}
153 	if (hHint == SWT.DEFAULT) {
154 		e.y += topMargin + bottomMargin;
155 	} else {
156 		e.y = hHint;
157 	}
158 	return e;
159 }
160 /**
161  * Draw a rectangle in the given colors.
162  */
drawBevelRect(GC gc, int x, int y, int w, int h, Color topleft, Color bottomright)163 private void drawBevelRect(GC gc, int x, int y, int w, int h, Color topleft, Color bottomright) {
164 	gc.setForeground(bottomright);
165 	gc.drawLine(x+w, y,   x+w, y+h);
166 	gc.drawLine(x,   y+h, x+w, y+h);
167 
168 	gc.setForeground(topleft);
169 	gc.drawLine(x, y, x+w-1, y);
170 	gc.drawLine(x, y, x,     y+h-1);
171 }
172 /*
173  * Return the lowercase of the first non-'&' character following
174  * an '&' character in the given string. If there are no '&'
175  * characters in the given string, return '\0'.
176  */
_findMnemonic(String string)177 char _findMnemonic (String string) {
178 	if (string == null) return '\0';
179 	int index = 0;
180 	int length = string.length ();
181 	do {
182 		while (index < length && string.charAt (index) != '&') index++;
183 		if (++index >= length) return '\0';
184 		if (string.charAt (index) != '&') return Character.toLowerCase (string.charAt (index));
185 		index++;
186 	} while (index < length);
187 	return '\0';
188 }
189 /**
190  * Returns the horizontal alignment.
191  * The alignment style (LEFT, CENTER or RIGHT) is returned.
192  *
193  * @return SWT.LEFT, SWT.RIGHT or SWT.CENTER
194  */
getAlignment()195 public int getAlignment() {
196 	/*
197 	 * This call is intentionally commented out, to allow this getter method to be
198 	 * called from a thread which is different from one that created the widget.
199 	 */
200 	//checkWidget();
201 	return align;
202 }
203 /**
204  * Return the CLabel's bottom margin.
205  *
206  * @return the bottom margin of the label
207  *
208  * @since 3.6
209  */
getBottomMargin()210 public int getBottomMargin() {
211 	/*
212 	 * This call is intentionally commented out, to allow this getter method to be
213 	 * called from a thread which is different from one that created the widget.
214 	 */
215 	//checkWidget();
216 	return bottomMargin;
217 }
218 /**
219  * Return the CLabel's image or <code>null</code>.
220  *
221  * @return the image of the label or null
222  */
getImage()223 public Image getImage() {
224 	/*
225 	 * This call is intentionally commented out, to allow this getter method to be
226 	 * called from a thread which is different from one that created the widget.
227 	 */
228 	//checkWidget();
229 	return image;
230 }
231 /**
232  * Return the CLabel's left margin.
233  *
234  * @return the left margin of the label
235  *
236  * @since 3.6
237  */
getLeftMargin()238 public int getLeftMargin() {
239 	/*
240 	 * This call is intentionally commented out, to allow this getter method to be
241 	 * called from a thread which is different from one that created the widget.
242 	 */
243 	//checkWidget();
244 	return leftMargin;
245 }
246 /**
247  * Return the CLabel's right margin.
248  *
249  * @return the right margin of the label
250  *
251  * @since 3.6
252  */
getRightMargin()253 public int getRightMargin() {
254 	/*
255 	 * This call is intentionally commented out, to allow this getter method to be
256 	 * called from a thread which is different from one that created the widget.
257 	 */
258 	//checkWidget();
259 	return rightMargin;
260 }
261 /**
262  * Compute the minimum size.
263  */
getTotalSize(Image image, String text)264 private Point getTotalSize(Image image, String text) {
265 	Point size = new Point(0, 0);
266 
267 	if (image != null) {
268 		Rectangle r = image.getBounds();
269 		size.x += r.width;
270 		size.y += r.height;
271 	}
272 
273 	GC gc = new GC(this);
274 	if (text != null && text.length() > 0) {
275 		Point e = gc.textExtent(text, DRAW_FLAGS);
276 		size.x += e.x;
277 		size.y = Math.max(size.y, e.y);
278 		if (image != null) size.x += GAP;
279 	} else {
280 		size.y = Math.max(size.y, gc.getFontMetrics().getHeight());
281 	}
282 	gc.dispose();
283 
284 	return size;
285 }
286 @Override
getStyle()287 public int getStyle () {
288 	int style = super.getStyle();
289 	switch (align) {
290 		case SWT.RIGHT: style |= SWT.RIGHT; break;
291 		case SWT.CENTER: style |= SWT.CENTER; break;
292 		case SWT.LEFT: style |= SWT.LEFT; break;
293 	}
294 	return style;
295 }
296 
297 /**
298  * Return the Label's text.
299  *
300  * @return the text of the label or null
301  */
getText()302 public String getText() {
303 	/*
304 	 * This call is intentionally commented out, to allow this getter method to be
305 	 * called from a thread which is different from one that created the widget.
306 	 */
307 	//checkWidget();
308 	return text;
309 }
310 @Override
getToolTipText()311 public String getToolTipText () {
312 	checkWidget();
313 	return appToolTipText;
314 }
315 /**
316  * Return the CLabel's top margin.
317  *
318  * @return the top margin of the label
319  *
320  * @since 3.6
321  */
getTopMargin()322 public int getTopMargin() {
323 	/*
324 	 * This call is intentionally commented out, to allow this getter method to be
325 	 * called from a thread which is different from one that created the widget.
326 	 */
327 	//checkWidget();
328 	return topMargin;
329 }
initAccessible()330 private void initAccessible() {
331 	Accessible accessible = getAccessible();
332 	accessible.addAccessibleListener(new AccessibleAdapter() {
333 		@Override
334 		public void getName(AccessibleEvent e) {
335 			e.result = getText();
336 		}
337 
338 		@Override
339 		public void getHelp(AccessibleEvent e) {
340 			e.result = getToolTipText();
341 		}
342 
343 		@Override
344 		public void getKeyboardShortcut(AccessibleEvent e) {
345 			char mnemonic = _findMnemonic(CLabel.this.text);
346 			if (mnemonic != '\0') {
347 				e.result = "Alt+"+mnemonic; //$NON-NLS-1$
348 			}
349 		}
350 	});
351 
352 	accessible.addAccessibleControlListener(new AccessibleControlAdapter() {
353 		@Override
354 		public void getChildAtPoint(AccessibleControlEvent e) {
355 			e.childID = ACC.CHILDID_SELF;
356 		}
357 
358 		@Override
359 		public void getLocation(AccessibleControlEvent e) {
360 			Rectangle rect = getDisplay().map(getParent(), null, getBounds());
361 			e.x = rect.x;
362 			e.y = rect.y;
363 			e.width = rect.width;
364 			e.height = rect.height;
365 		}
366 
367 		@Override
368 		public void getChildCount(AccessibleControlEvent e) {
369 			e.detail = 0;
370 		}
371 
372 		@Override
373 		public void getRole(AccessibleControlEvent e) {
374 			e.detail = ACC.ROLE_LABEL;
375 		}
376 
377 		@Override
378 		public void getState(AccessibleControlEvent e) {
379 			e.detail = ACC.STATE_READONLY;
380 		}
381 	});
382 }
onDispose(Event event)383 void onDispose(Event event) {
384 	/* make this handler run after other dispose listeners */
385 	if (ignoreDispose) {
386 		ignoreDispose = false;
387 		return;
388 	}
389 	ignoreDispose = true;
390 	notifyListeners (event.type, event);
391 	event.type = SWT.NONE;
392 
393 	gradientColors = null;
394 	gradientPercents = null;
395 	backgroundImage = null;
396 	text = null;
397 	image = null;
398 	appToolTipText = null;
399 }
onMnemonic(TraverseEvent event)400 void onMnemonic(TraverseEvent event) {
401 	char mnemonic = _findMnemonic(text);
402 	if (mnemonic == '\0') return;
403 	if (Character.toLowerCase(event.character) != mnemonic) return;
404 	Composite control = this.getParent();
405 	while (control != null) {
406 		Control [] children = control.getChildren();
407 		int index = 0;
408 		while (index < children.length) {
409 			if (children [index] == this) break;
410 			index++;
411 		}
412 		index++;
413 		if (index < children.length) {
414 			if (children [index].setFocus ()) {
415 				event.doit = true;
416 				event.detail = SWT.TRAVERSE_NONE;
417 			}
418 		}
419 		control = control.getParent();
420 	}
421 }
422 
onPaint(PaintEvent event)423 void onPaint(PaintEvent event) {
424 	Rectangle rect = getClientArea();
425 	if (rect.width == 0 || rect.height == 0) return;
426 
427 	boolean shortenText = false;
428 	String t = text;
429 	Image img = image;
430 	int availableWidth = Math.max(0, rect.width - (leftMargin + rightMargin));
431 	Point extent = getTotalSize(img, t);
432 	if (extent.x > availableWidth) {
433 		img = null;
434 		extent = getTotalSize(img, t);
435 		if (extent.x > availableWidth) {
436 			shortenText = true;
437 		}
438 	}
439 
440 	GC gc = event.gc;
441 	String[] lines = text == null ? null : splitString(text);
442 
443 	// shorten the text
444 	if (shortenText) {
445 		extent.x = 0;
446 		for(int i = 0; i < lines.length; i++) {
447 			Point e = gc.textExtent(lines[i], DRAW_FLAGS);
448 			if (e.x > availableWidth) {
449 				lines[i] = shortenText(gc, lines[i], availableWidth);
450 				extent.x = Math.max(extent.x, getTotalSize(null, lines[i]).x);
451 			} else {
452 				extent.x = Math.max(extent.x, e.x);
453 			}
454 		}
455 		if (appToolTipText == null) {
456 			super.setToolTipText(text);
457 		}
458 	} else {
459 		super.setToolTipText(appToolTipText);
460 	}
461 
462 	// determine horizontal position
463 	int x = rect.x + leftMargin;
464 	if (align == SWT.CENTER) {
465 		x = (rect.width - extent.x)/2;
466 	}
467 	if (align == SWT.RIGHT) {
468 		x = rect.width - rightMargin - extent.x;
469 	}
470 
471 	// draw a background image behind the text
472 	try {
473 		if (backgroundImage != null) {
474 			// draw a background image behind the text
475 			Rectangle imageRect = backgroundImage.getBounds();
476 			// tile image to fill space
477 			gc.setBackground(getBackground());
478 			gc.fillRectangle(rect);
479 			int xPos = 0;
480 			while (xPos < rect.width) {
481 				int yPos = 0;
482 				while (yPos < rect.height) {
483 					gc.drawImage(backgroundImage, xPos, yPos);
484 					yPos += imageRect.height;
485 				}
486 				xPos += imageRect.width;
487 			}
488 		} else if (gradientColors != null) {
489 			// draw a gradient behind the text
490 			final Color oldBackground = gc.getBackground();
491 			if (gradientColors.length == 1) {
492 				if (gradientColors[0] != null) gc.setBackground(gradientColors[0]);
493 				gc.fillRectangle(0, 0, rect.width, rect.height);
494 			} else {
495 				final Color oldForeground = gc.getForeground();
496 				Color lastColor = gradientColors[0];
497 				if (lastColor == null) lastColor = oldBackground;
498 				int pos = 0;
499 				for (int i = 0; i < gradientPercents.length; ++i) {
500 					gc.setForeground(lastColor);
501 					lastColor = gradientColors[i + 1];
502 					if (lastColor == null) lastColor = oldBackground;
503 					gc.setBackground(lastColor);
504 					if (gradientVertical) {
505 						final int gradientHeight = (gradientPercents[i] * rect.height / 100) - pos;
506 						gc.fillGradientRectangle(0, pos, rect.width, gradientHeight, true);
507 						pos += gradientHeight;
508 					} else {
509 						final int gradientWidth = (gradientPercents[i] * rect.width / 100) - pos;
510 						gc.fillGradientRectangle(pos, 0, gradientWidth, rect.height, false);
511 						pos += gradientWidth;
512 					}
513 				}
514 				if (gradientVertical && pos < rect.height) {
515 					gc.setBackground(getBackground());
516 					gc.fillRectangle(0, pos, rect.width, rect.height - pos);
517 				}
518 				if (!gradientVertical && pos < rect.width) {
519 					gc.setBackground(getBackground());
520 					gc.fillRectangle(pos, 0, rect.width - pos, rect.height);
521 				}
522 				gc.setForeground(oldForeground);
523 			}
524 			gc.setBackground(oldBackground);
525 		} else {
526 			if ((background != null || (getStyle() & SWT.DOUBLE_BUFFERED) == 0) && background.getAlpha() > 0) {
527 				gc.setBackground(getBackground());
528 				gc.fillRectangle(rect);
529 			}
530 		}
531 	} catch (SWTException e) {
532 		if ((getStyle() & SWT.DOUBLE_BUFFERED) == 0) {
533 			gc.setBackground(getBackground());
534 			gc.fillRectangle(rect);
535 		}
536 	}
537 
538 	// draw border
539 	int style = getStyle();
540 	if ((style & SWT.SHADOW_IN) != 0 || (style & SWT.SHADOW_OUT) != 0) {
541 		paintBorder(gc, rect);
542 	}
543 
544 	/*
545 	 * Compute text height and image height. If image height is more than
546 	 * the text height, draw image starting from top margin. Else draw text
547 	 * starting from top margin.
548 	 */
549 	Rectangle imageRect = null;
550 	int lineHeight = 0, textHeight = 0, imageHeight = 0;
551 
552 	if (img != null) {
553 		imageRect = img.getBounds();
554 		imageHeight = imageRect.height;
555 	}
556 	if (lines != null) {
557 		lineHeight = gc.getFontMetrics().getHeight();
558 		textHeight = lines.length * lineHeight;
559 	}
560 
561 	int imageY = 0, midPoint = 0, lineY = 0;
562 	if (imageHeight > textHeight ) {
563 		if (topMargin == DEFAULT_MARGIN && bottomMargin == DEFAULT_MARGIN) imageY = rect.y + (rect.height - imageHeight) / 2;
564 		else imageY = topMargin;
565 		midPoint = imageY + imageHeight/2;
566 		lineY = midPoint - textHeight / 2;
567 	}
568 	else {
569 		if (topMargin == DEFAULT_MARGIN && bottomMargin == DEFAULT_MARGIN) lineY = rect.y + (rect.height - textHeight) / 2;
570 		else lineY = topMargin;
571 		midPoint = lineY + textHeight/2;
572 		imageY = midPoint - imageHeight / 2;
573 	}
574 
575 	// draw the image
576 	if (img != null) {
577 		gc.drawImage(img, 0, 0, imageRect.width, imageHeight,
578 						x, imageY, imageRect.width, imageHeight);
579 		x +=  imageRect.width + GAP;
580 		extent.x -= imageRect.width + GAP;
581 	}
582 
583 	// draw the text
584 	if (lines != null) {
585 		gc.setForeground(getForeground());
586 		for (String line : lines) {
587 			int lineX = x;
588 			if (lines.length > 1) {
589 				if (align == SWT.CENTER) {
590 					int lineWidth = gc.textExtent(line, DRAW_FLAGS).x;
591 					lineX = x + Math.max(0, (extent.x - lineWidth) / 2);
592 				}
593 				if (align == SWT.RIGHT) {
594 					int lineWidth = gc.textExtent(line, DRAW_FLAGS).x;
595 					lineX = Math.max(x, rect.x + rect.width - rightMargin - lineWidth);
596 				}
597 			}
598 			gc.drawText(line, lineX, lineY, DRAW_FLAGS);
599 			lineY += lineHeight;
600 		}
601 	}
602 }
603 /**
604  * Paint the Label's border.
605  */
paintBorder(GC gc, Rectangle r)606 private void paintBorder(GC gc, Rectangle r) {
607 	Display disp= getDisplay();
608 
609 	Color c1 = null;
610 	Color c2 = null;
611 
612 	int style = getStyle();
613 	if ((style & SWT.SHADOW_IN) != 0) {
614 		c1 = disp.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
615 		c2 = disp.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW);
616 	}
617 	if ((style & SWT.SHADOW_OUT) != 0) {
618 		c1 = disp.getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW);
619 		c2 = disp.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
620 	}
621 
622 	if (c1 != null && c2 != null) {
623 		gc.setLineWidth(1);
624 		drawBevelRect(gc, r.x, r.y, r.width-1, r.height-1, c1, c2);
625 	}
626 }
627 /**
628  * Set the horizontal alignment of the CLabel.
629  * Use the values LEFT, CENTER and RIGHT to align image and text within the available space.
630  *
631  * @param align the alignment style of LEFT, RIGHT or CENTER
632  *
633  * @exception SWTException <ul>
634  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
635  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
636  *    <li>ERROR_INVALID_ARGUMENT - if the value of align is not one of SWT.LEFT, SWT.RIGHT or SWT.CENTER</li>
637  * </ul>
638  */
setAlignment(int align)639 public void setAlignment(int align) {
640 	checkWidget();
641 	if (align != SWT.LEFT && align != SWT.RIGHT && align != SWT.CENTER) {
642 		SWT.error(SWT.ERROR_INVALID_ARGUMENT);
643 	}
644 	if (this.align != align) {
645 		this.align = align;
646 		redraw();
647 	}
648 }
649 
650 @Override
setBackground(Color color)651 public void setBackground (Color color) {
652 	super.setBackground (color);
653 	// Are these settings the same as before?
654 	if (backgroundImage == null &&
655 		gradientColors == null &&
656 		gradientPercents == null) {
657 		if (color == null) {
658 			if (background == null) return;
659 		} else {
660 			if (color.equals(background)) return;
661 		}
662 	}
663 	background = color;
664 	backgroundImage = null;
665 	gradientColors = null;
666 	gradientPercents = null;
667 	redraw ();
668 }
669 
670 /**
671  * Specify a gradient of colours to be drawn in the background of the CLabel.
672  * <p>For example, to draw a gradient that varies from dark blue to blue and then to
673  * white and stays white for the right half of the label, use the following call
674  * to setBackground:</p>
675  * <pre>
676  *	clabel.setBackground(new Color[]{display.getSystemColor(SWT.COLOR_DARK_BLUE),
677  *		                           display.getSystemColor(SWT.COLOR_BLUE),
678  *		                           display.getSystemColor(SWT.COLOR_WHITE),
679  *		                           display.getSystemColor(SWT.COLOR_WHITE)},
680  *		               new int[] {25, 50, 100});
681  * </pre>
682  *
683  * @param colors an array of Color that specifies the colors to appear in the gradient
684  *               in order of appearance from left to right;  The value <code>null</code>
685  *               clears the background gradient; the value <code>null</code> can be used
686  *               inside the array of Color to specify the background color.
687  * @param percents an array of integers between 0 and 100 specifying the percent of the width
688  *                 of the widget at which the color should change; the size of the percents
689  *                 array must be one less than the size of the colors array.
690  *
691  * @exception SWTException <ul>
692  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
693  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
694  *    <li>ERROR_INVALID_ARGUMENT - if the values of colors and percents are not consistent</li>
695  * </ul>
696  */
setBackground(Color[] colors, int[] percents)697 public void setBackground(Color[] colors, int[] percents) {
698 	setBackground(colors, percents, false);
699 }
700 /**
701  * Specify a gradient of colours to be drawn in the background of the CLabel.
702  * <p>For example, to draw a gradient that varies from dark blue to white in the vertical,
703  * direction use the following call
704  * to setBackground:</p>
705  * <pre>
706  *	clabel.setBackground(new Color[]{display.getSystemColor(SWT.COLOR_DARK_BLUE),
707  *		                           display.getSystemColor(SWT.COLOR_WHITE)},
708  *		                 new int[] {100}, true);
709  * </pre>
710  *
711  * @param colors an array of Color that specifies the colors to appear in the gradient
712  *               in order of appearance from left/top to right/bottom;  The value <code>null</code>
713  *               clears the background gradient; the value <code>null</code> can be used
714  *               inside the array of Color to specify the background color.
715  * @param percents an array of integers between 0 and 100 specifying the percent of the width/height
716  *                 of the widget at which the color should change; the size of the percents
717  *                 array must be one less than the size of the colors array.
718  * @param vertical indicate the direction of the gradient.  True is vertical and false is horizontal.
719  *
720  * @exception SWTException <ul>
721  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
722  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
723  *    <li>ERROR_INVALID_ARGUMENT - if the values of colors and percents are not consistent</li>
724  * </ul>
725  *
726  * @since 3.0
727  */
setBackground(Color[] colors, int[] percents, boolean vertical)728 public void setBackground(Color[] colors, int[] percents, boolean vertical) {
729 	checkWidget();
730 	if (colors != null) {
731 		if (percents == null || percents.length != colors.length - 1) {
732 			SWT.error(SWT.ERROR_INVALID_ARGUMENT);
733 		}
734 		if (getDisplay().getDepth() < 15) {
735 			// Don't use gradients on low color displays
736 			colors = new Color[] {colors[colors.length - 1]};
737 			percents = new int[] { };
738 		}
739 		for (int i = 0; i < percents.length; i++) {
740 			if (percents[i] < 0 || percents[i] > 100) {
741 				SWT.error(SWT.ERROR_INVALID_ARGUMENT);
742 			}
743 			if (i > 0 && percents[i] < percents[i-1]) {
744 				SWT.error(SWT.ERROR_INVALID_ARGUMENT);
745 			}
746 		}
747 	}
748 
749 	// Are these settings the same as before?
750 	final Color background = getBackground();
751 	if (backgroundImage == null) {
752 		if ((gradientColors != null) && (colors != null) &&
753 			(gradientColors.length == colors.length)) {
754 			boolean same = false;
755 			for (int i = 0; i < gradientColors.length; i++) {
756 				same = (gradientColors[i] == colors[i]) ||
757 					((gradientColors[i] == null) && (colors[i] == background)) ||
758 					((gradientColors[i] == background) && (colors[i] == null));
759 				if (!same) break;
760 			}
761 			if (same) {
762 				for (int i = 0; i < gradientPercents.length; i++) {
763 					same = gradientPercents[i] == percents[i];
764 					if (!same) break;
765 				}
766 			}
767 			if (same && this.gradientVertical == vertical) return;
768 		}
769 	} else {
770 		backgroundImage = null;
771 	}
772 	// Store the new settings
773 	if (colors == null) {
774 		gradientColors = null;
775 		gradientPercents = null;
776 		gradientVertical = false;
777 	} else {
778 		gradientColors = new Color[colors.length];
779 		for (int i = 0; i < colors.length; ++i)
780 			gradientColors[i] = (colors[i] != null) ? colors[i] : background;
781 		gradientPercents = new int[percents.length];
782 		for (int i = 0; i < percents.length; ++i)
783 			gradientPercents[i] = percents[i];
784 		gradientVertical = vertical;
785 	}
786 	// Refresh with the new settings
787 	redraw();
788 }
789 /**
790  * Set the image to be drawn in the background of the label.
791  *
792  * @param image the image to be drawn in the background
793  *
794  * @exception SWTException <ul>
795  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
796  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
797  * </ul>
798  */
setBackground(Image image)799 public void setBackground(Image image) {
800 	checkWidget();
801 	if (image == backgroundImage) return;
802 	if (image != null) {
803 		gradientColors = null;
804 		gradientPercents = null;
805 	}
806 	backgroundImage = image;
807 	redraw();
808 
809 }
810 /**
811  * Set the label's bottom margin, in points.
812  *
813  * @param bottomMargin the bottom margin of the label, which must be equal to or greater than zero
814  *
815  * @exception SWTException <ul>
816  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
817  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
818  * </ul>
819  *
820  * @since 3.6
821  */
setBottomMargin(int bottomMargin)822 public void setBottomMargin(int bottomMargin) {
823 	checkWidget();
824 	if (this.bottomMargin == bottomMargin || bottomMargin < 0) return;
825 	this.bottomMargin = bottomMargin;
826 	redraw();
827 }
828 @Override
setFont(Font font)829 public void setFont(Font font) {
830 	super.setFont(font);
831 	redraw();
832 }
833 /**
834  * Set the label's Image.
835  * The value <code>null</code> clears it.
836  *
837  * @param image the image to be displayed in the label or null
838  *
839  * @exception SWTException <ul>
840  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
841  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
842  * </ul>
843  */
setImage(Image image)844 public void setImage(Image image) {
845 	checkWidget();
846 	if (image != this.image) {
847 		this.image = image;
848 		redraw();
849 	}
850 }
851 /**
852  * Set the label's horizontal left margin, in points.
853  *
854  * @param leftMargin the left margin of the label, which must be equal to or greater than zero
855  *
856  * @exception SWTException <ul>
857  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
858  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
859  * </ul>
860  *
861  * @since 3.6
862  */
setLeftMargin(int leftMargin)863 public void setLeftMargin(int leftMargin) {
864 	checkWidget();
865 	if (this.leftMargin == leftMargin || leftMargin < 0) return;
866 	this.leftMargin = leftMargin;
867 	redraw();
868 }
869 /**
870  * Set the label's margins, in points.
871  *
872  * @param leftMargin the left margin.
873  * @param topMargin the top margin.
874  * @param rightMargin the right margin.
875  * @param bottomMargin the bottom margin.
876  * @exception SWTException <ul>
877  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
878  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
879  * </ul>
880  *
881  * @since 3.6
882  */
setMargins(int leftMargin, int topMargin, int rightMargin, int bottomMargin)883 public void setMargins (int leftMargin, int topMargin, int rightMargin, int bottomMargin) {
884 	checkWidget();
885 	this.leftMargin = Math.max(0, leftMargin);
886 	this.topMargin = Math.max(0, topMargin);
887 	this.rightMargin = Math.max(0, rightMargin);
888 	this.bottomMargin = Math.max(0, bottomMargin);
889 	redraw();
890 }
891 /**
892  * Set the label's right margin, in points.
893  *
894  * @param rightMargin the right margin of the label, which must be equal to or greater than zero
895  *
896  * @exception SWTException <ul>
897  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
898  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
899  * </ul>
900  *
901  * @since 3.6
902  */
setRightMargin(int rightMargin)903 public void setRightMargin(int rightMargin) {
904 	checkWidget();
905 	if (this.rightMargin == rightMargin || rightMargin < 0) return;
906 	this.rightMargin = rightMargin;
907 	redraw();
908 }
909 /**
910  * Set the label's text.
911  * The value <code>null</code> clears it.
912  * <p>
913  * Mnemonics are indicated by an '&amp;' that causes the next
914  * character to be the mnemonic.  When the user presses a
915  * key sequence that matches the mnemonic, focus is assigned
916  * to the control that follows the label. On most platforms,
917  * the mnemonic appears underlined but may be emphasised in a
918  * platform specific manner.  The mnemonic indicator character
919  * '&amp;' can be escaped by doubling it in the string, causing
920  * a single '&amp;' to be displayed.
921  * </p><p>
922  * Note: If control characters like '\n', '\t' etc. are used
923  * in the string, then the behavior is platform dependent.
924  * </p>
925  *
926  * @param text the text to be displayed in the label or null
927  *
928  * @exception SWTException <ul>
929  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
930  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
931  * </ul>
932  */
setText(String text)933 public void setText(String text) {
934 	checkWidget();
935 	if (text == null) text = ""; //$NON-NLS-1$
936 	if (! text.equals(this.text)) {
937 		this.text = text;
938 		redraw();
939 	}
940 }
941 @Override
setToolTipText(String string)942 public void setToolTipText (String string) {
943 	super.setToolTipText (string);
944 	appToolTipText = super.getToolTipText();
945 }
946 /**
947  * Set the label's top margin, in points.
948  *
949  * @param topMargin the top margin of the label, which must be equal to or greater than zero
950  *
951  * @exception SWTException <ul>
952  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
953  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
954  * </ul>
955  *
956  * @since 3.6
957  */
setTopMargin(int topMargin)958 public void setTopMargin(int topMargin) {
959 	checkWidget();
960 	if (this.topMargin == topMargin || topMargin < 0) return;
961 	this.topMargin = topMargin;
962 	redraw();
963 }
964 /**
965  * Shorten the given text <code>t</code> so that its length doesn't exceed
966  * the given width. The default implementation replaces characters in the
967  * center of the original string with an ellipsis ("...").
968  * Override if you need a different strategy.
969  *
970  * @param gc the gc to use for text measurement
971  * @param t the text to shorten
972  * @param width the width to shorten the text to, in points
973  * @return the shortened text
974  */
shortenText(GC gc, String t, int width)975 protected String shortenText(GC gc, String t, int width) {
976 	if (t == null) return null;
977 	int w = gc.textExtent(ELLIPSIS, DRAW_FLAGS).x;
978 	if (width<=w) return t;
979 	int l = t.length();
980 	int max = l/2;
981 	int min = 0;
982 	int mid = (max+min)/2 - 1;
983 	if (mid <= 0) return t;
984 	TextLayout layout = new TextLayout (getDisplay());
985 	layout.setText(t);
986 	mid = validateOffset(layout, mid);
987 	while (min < mid && mid < max) {
988 		String s1 = t.substring(0, mid);
989 		String s2 = t.substring(validateOffset(layout, l-mid), l);
990 		int l1 = gc.textExtent(s1, DRAW_FLAGS).x;
991 		int l2 = gc.textExtent(s2, DRAW_FLAGS).x;
992 		if (l1+w+l2 > width) {
993 			max = mid;
994 			mid = validateOffset(layout, (max+min)/2);
995 		} else if (l1+w+l2 < width) {
996 			min = mid;
997 			mid = validateOffset(layout, (max+min)/2);
998 		} else {
999 			min = max;
1000 		}
1001 	}
1002 	String result = mid == 0 ? t : t.substring(0, mid) + ELLIPSIS + t.substring(validateOffset(layout, l-mid), l);
1003 	layout.dispose();
1004 	return result;
1005 }
validateOffset(TextLayout layout, int offset)1006 int validateOffset(TextLayout layout, int offset) {
1007 	int nextOffset = layout.getNextOffset(offset, SWT.MOVEMENT_CLUSTER);
1008 	if (nextOffset != offset) return layout.getPreviousOffset(nextOffset, SWT.MOVEMENT_CLUSTER);
1009 	return offset;
1010 }
splitString(String text)1011 private String[] splitString(String text) {
1012 	String[] lines = new String[1];
1013 	int start = 0, pos;
1014 	do {
1015 		pos = text.indexOf('\n', start);
1016 		if (pos == -1) {
1017 			lines[lines.length - 1] = text.substring(start);
1018 		} else {
1019 			boolean crlf = (pos > 0) && (text.charAt(pos - 1) == '\r');
1020 			lines[lines.length - 1] = text.substring(start, pos - (crlf ? 1 : 0));
1021 			start = pos + 1;
1022 			String[] newLines = new String[lines.length+1];
1023 			System.arraycopy(lines, 0, newLines, 0, lines.length);
1024 			lines = newLines;
1025 		}
1026 	} while (pos != -1);
1027 	return lines;
1028 }
1029 }
1030