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