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.CTabItem;
14 
15 import java.lang.all;
16 
17 
18 
19 import org.eclipse.swt.SWT;
20 import org.eclipse.swt.SWTException;
21 import org.eclipse.swt.graphics.Color;
22 import org.eclipse.swt.graphics.Font;
23 import org.eclipse.swt.graphics.GC;
24 import org.eclipse.swt.graphics.Image;
25 import org.eclipse.swt.graphics.Point;
26 import org.eclipse.swt.graphics.RGB;
27 import org.eclipse.swt.graphics.Rectangle;
28 import org.eclipse.swt.graphics.TextLayout;
29 import org.eclipse.swt.widgets.Control;
30 import org.eclipse.swt.widgets.Display;
31 import org.eclipse.swt.widgets.Item;
32 import org.eclipse.swt.widgets.Widget;
33 import org.eclipse.swt.custom.CTabFolder;
34 
35 version(Tango){
36     import tango.text.convert.Utf;
37 } else {
38     import std.conv;
39     alias to!(string) toString;
40     alias to!(dstring) toString32;
41 }
42 
43 /**
44  * Instances of this class represent a selectable user interface object
45  * that represent a page in a notebook widget.
46  *
47  * <dl>
48  * <dt><b>Styles:</b></dt>
49  * <dd>SWT.CLOSE</dd>
50  * <dt><b>Events:</b></dt>
51  * <dd>(none)</dd>
52  * </dl>
53  * <p>
54  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
55  * </p>
56  *
57  * @see <a href="http://www.eclipse.org/swt/snippets/#ctabfolder">CTabFolder, CTabItem snippets</a>
58  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
59  */
60 public class CTabItem : Item {
61     CTabFolder parent;
62     int x,y,width,height = 0;
63     Control control; // the tab page
64 
65     String toolTipText;
66     String shortenedText;
67     int shortenedTextWidth;
68 
69     // Appearance
70     Font font;
71     Image disabledImage;
72 
73     Rectangle closeRect;
74     int closeImageState = CTabFolder.NONE;
75     bool showClose = false;
76     bool showing = false;
77 
78     // internal constants
79     static const int TOP_MARGIN = 2;
80     static const int BOTTOM_MARGIN = 2;
81     static const int LEFT_MARGIN = 4;
82     static const int RIGHT_MARGIN = 4;
83     static const int INTERNAL_SPACING = 4;
84     static const int FLAGS = SWT.DRAW_TRANSPARENT | SWT.DRAW_MNEMONIC;
85     static const String ELLIPSIS = "..."; //$NON-NLS-1$ // could use the ellipsis glyph on some platforms "\u2026"
86 
87 /**
88  * Constructs a new instance of this class given its parent
89  * (which must be a <code>CTabFolder</code>) and a style value
90  * describing its behavior and appearance. The item is added
91  * to the end of the items maintained by its parent.
92  * <p>
93  * The style value is either one of the style constants defined in
94  * class <code>SWT</code> which is applicable to instances of this
95  * class, or must be built by <em>bitwise OR</em>'ing together
96  * (that is, using the <code>int</code> "|" operator) two or more
97  * of those <code>SWT</code> style constants. The class description
98  * lists the style constants that are applicable to the class.
99  * Style bits are also inherited from superclasses.
100  * </p>
101  *
102  * @param parent a CTabFolder which will be the parent of the new instance (cannot be null)
103  * @param style the style of control to construct
104  *
105  * @exception IllegalArgumentException <ul>
106  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
107  * </ul>
108  * @exception SWTException <ul>
109  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
110  * </ul>
111  *
112  * @see SWT
113  * @see Widget#getStyle()
114  */
115 public this (CTabFolder parent, int style) {
116     this(parent, style, parent.getItemCount());
117 }
118 /**
119  * Constructs a new instance of this class given its parent
120  * (which must be a <code>CTabFolder</code>), a style value
121  * describing its behavior and appearance, and the index
122  * at which to place it in the items maintained by its parent.
123  * <p>
124  * The style value is either one of the style constants defined in
125  * class <code>SWT</code> which is applicable to instances of this
126  * class, or must be built by <em>bitwise OR</em>'ing together
127  * (that is, using the <code>int</code> "|" operator) two or more
128  * of those <code>SWT</code> style constants. The class description
129  * lists the style constants that are applicable to the class.
130  * Style bits are also inherited from superclasses.
131  * </p>
132  *
133  * @param parent a CTabFolder which will be the parent of the new instance (cannot be null)
134  * @param style the style of control to construct
135  * @param index the zero-relative index to store the receiver in its parent
136  *
137  * @exception IllegalArgumentException <ul>
138  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
139  *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the parent (inclusive)</li>
140  * </ul>
141  * @exception SWTException <ul>
142  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
143  * </ul>
144  *
145  * @see SWT
146  * @see Widget#getStyle()
147  */
148 public this (CTabFolder parent, int style, int index) {
149     closeRect = new Rectangle(0, 0, 0, 0);
150     super (parent, style);
151     showClose = (style & SWT.CLOSE) !is 0;
152     parent.createItem (this, index);
153 }
154 
155 /*
156  * Return whether to use ellipses or just truncate labels
157  */
158 bool useEllipses() {
159     return parent.simple;
160 }
161 
162 String shortenText(GC gc, String text, int width) {
163     return useEllipses()
164         ? shortenText(gc, text, width, ELLIPSIS)
165         : shortenText(gc, text, width, ""); //$NON-NLS-1$
166 }
167 
168 String shortenText(GC gc, String text, int width, String ellipses) {
169     if (gc.textExtent(text, FLAGS).x <= width) return text;
170     int ellipseWidth = gc.textExtent(ellipses, FLAGS).x;
171     int length = cast(int)/*64bit*/text.length;
172     TextLayout layout = new TextLayout(getDisplay());
173     layout.setText(text);
174     int end = layout.getPreviousOffset(length, SWT.MOVEMENT_CLUSTER);
175     while (end > 0) {
176         text = text[ 0 .. end ];
177         int l = gc.textExtent(text, FLAGS).x;
178         if (l + ellipseWidth <= width) {
179             break;
180         }
181         end = layout.getPreviousOffset(end, SWT.MOVEMENT_CLUSTER);
182     }
183     layout.dispose();
184     return end is 0 ? .toString(toString32(text)[0 .. 1]) : text ~ ellipses;
185 }
186 
187 public override void dispose() {
188     if (isDisposed ()) return;
189     //if (!isValidThread ()) error (SWT.ERROR_THREAD_INVALID_ACCESS);
190     parent.destroyItem(this);
191     super.dispose();
192     parent = null;
193     control = null;
194     toolTipText = null;
195     shortenedText = null;
196     font = null;
197 }
198 void drawClose(GC gc) {
199     if (closeRect.width is 0 || closeRect.height is 0) return;
200     Display display = getDisplay();
201 
202     // draw X 9x9
203     int indent = Math.max(1, (CTabFolder.BUTTON_SIZE-9)/2);
204     int x = closeRect.x + indent;
205     int y = closeRect.y + indent;
206     y += parent.onBottom ? -1 : 1;
207 
208     Color closeBorder = display.getSystemColor(CTabFolder.BUTTON_BORDER);
209     switch (closeImageState) {
210         case CTabFolder.NORMAL: {
211             int[] shape = [x,y, x+2,y, x+4,y+2, x+5,y+2, x+7,y, x+9,y,
212                                      x+9,y+2, x+7,y+4, x+7,y+5, x+9,y+7, x+9,y+9,
213                                      x+7,y+9, x+5,y+7, x+4,y+7, x+2,y+9, x,y+9,
214                                      x,y+7, x+2,y+5, x+2,y+4, x,y+2];
215             gc.setBackground(display.getSystemColor(CTabFolder.BUTTON_FILL));
216             gc.fillPolygon(shape);
217             gc.setForeground(closeBorder);
218             gc.drawPolygon(shape);
219             break;
220         }
221         case CTabFolder.HOT: {
222             int[] shape = [x,y, x+2,y, x+4,y+2, x+5,y+2, x+7,y, x+9,y,
223                                      x+9,y+2, x+7,y+4, x+7,y+5, x+9,y+7, x+9,y+9,
224                                      x+7,y+9, x+5,y+7, x+4,y+7, x+2,y+9, x,y+9,
225                                      x,y+7, x+2,y+5, x+2,y+4, x,y+2];
226             Color fill = new Color(display, CTabFolder.CLOSE_FILL);
227             gc.setBackground(fill);
228             gc.fillPolygon(shape);
229             fill.dispose();
230             gc.setForeground(closeBorder);
231             gc.drawPolygon(shape);
232             break;
233         }
234         case CTabFolder.SELECTED: {
235             int[] shape = [x+1,y+1, x+3,y+1, x+5,y+3, x+6,y+3, x+8,y+1, x+10,y+1,
236                                      x+10,y+3, x+8,y+5, x+8,y+6, x+10,y+8, x+10,y+10,
237                                      x+8,y+10, x+6,y+8, x+5,y+8, x+3,y+10, x+1,y+10,
238                                      x+1,y+8, x+3,y+6, x+3,y+5, x+1,y+3];
239             Color fill = new Color(display, CTabFolder.CLOSE_FILL);
240             gc.setBackground(fill);
241             gc.fillPolygon(shape);
242             fill.dispose();
243             gc.setForeground(closeBorder);
244             gc.drawPolygon(shape);
245             break;
246         }
247         case CTabFolder.NONE: {
248             int[] shape = [x,y, x+10,y, x+10,y+10, x,y+10];
249             if (parent.gradientColors !is null && !parent.gradientVertical) {
250                 parent.drawBackground(gc, shape, false);
251             } else {
252                 Color defaultBackground = parent.getBackground();
253                 Image image = parent.bgImage;
254                 Color[] colors = parent.gradientColors;
255                 int[] percents = parent.gradientPercents;
256                 bool vertical = parent.gradientVertical;
257                 parent.drawBackground(gc, shape, x, y, 10, 10, defaultBackground, image, colors, percents, vertical);
258             }
259             break;
260         }
261         default:
262     }
263 }
264 void drawSelected(GC gc ) {
265     Point size = parent.getSize();
266     int rightEdge = Math.min (x + width, parent.getRightItemEdge());
267 
268     //   Draw selection border across all tabs
269     int xx = parent.borderLeft;
270     int yy = parent.onBottom ? size.y - parent.borderBottom - parent.tabHeight - parent.highlight_header : parent.borderTop + parent.tabHeight + 1;
271     int ww = size.x - parent.borderLeft - parent.borderRight;
272     int hh = parent.highlight_header - 1;
273     int[] shape = [xx,yy, xx+ww,yy, xx+ww,yy+hh, xx,yy+hh];
274     if (parent.selectionGradientColors !is null && !parent.selectionGradientVertical) {
275         parent.drawBackground(gc, shape, true);
276     } else {
277         gc.setBackground(parent.selectionBackground);
278         gc.fillRectangle(xx, yy, ww, hh);
279     }
280 
281     if (parent.single) {
282         if (!showing) return;
283     } else {
284         // if selected tab scrolled out of view or partially out of view
285         // just draw bottom line
286         if (!showing){
287             int x1 = Math.max(0, parent.borderLeft - 1);
288             int y1 = (parent.onBottom) ? y - 1 : y + height;
289             int x2 = size.x - parent.borderRight;
290             gc.setForeground(CTabFolder.borderColor);
291             gc.drawLine(x1, y1, x2, y1);
292             return;
293         }
294 
295         // draw selected tab background and outline
296         shape = null;
297         if (this.parent.onBottom) {
298             TryConst!(int)[] left = parent.simple ? CTabFolder.SIMPLE_BOTTOM_LEFT_CORNER : CTabFolder.BOTTOM_LEFT_CORNER;
299             TryConst!(int[]) right = parent.simple ? CTabFolder.SIMPLE_BOTTOM_RIGHT_CORNER : parent.curve;
300             if (parent.borderLeft is 0 && parent.indexOf(this) is parent.firstIndex) {
301                 left = [x, y+height];
302             }
303             shape = new int[left.length+right.length+8];
304             int index = 0;
305             shape[index++] = x; // first point repeated here because below we reuse shape to draw outline
306             shape[index++] = y - 1;
307             shape[index++] = x;
308             shape[index++] = y - 1;
309             for (int i = 0; i < left.length/2; i++) {
310                 shape[index++] = x + left[2*i];
311                 shape[index++] = y + height + left[2*i+1] - 1;
312             }
313             for (int i = 0; i < right.length/2; i++) {
314                 shape[index++] = parent.simple ? rightEdge - 1 + right[2*i] : rightEdge - parent.curveIndent + right[2*i];
315                 shape[index++] = parent.simple ? y + height + right[2*i+1] - 1 : y + right[2*i+1] - 2;
316             }
317             shape[index++] = parent.simple ? rightEdge - 1 : rightEdge + parent.curveWidth - parent.curveIndent;
318             shape[index++] = y - 1;
319             shape[index++] = parent.simple ? rightEdge - 1 : rightEdge + parent.curveWidth - parent.curveIndent;
320             shape[index++] = y - 1;
321         } else {
322             TryConst!(int)[] left = parent.simple ? CTabFolder.SIMPLE_TOP_LEFT_CORNER : CTabFolder.TOP_LEFT_CORNER;
323             TryConst!(int[]) right = parent.simple ? CTabFolder.SIMPLE_TOP_RIGHT_CORNER : parent.curve;
324             if (parent.borderLeft is 0 && parent.indexOf(this) is parent.firstIndex) {
325                 left = [x, y];
326             }
327             shape = new int[left.length+right.length+8];
328             int index = 0;
329             shape[index++] = x; // first point repeated here because below we reuse shape to draw outline
330             shape[index++] = y + height + 1;
331             shape[index++] = x;
332             shape[index++] = y + height + 1;
333             for (int i = 0; i < left.length/2; i++) {
334                 shape[index++] = x + left[2*i];
335                 shape[index++] = y + left[2*i+1];
336             }
337             for (int i = 0; i < right.length/2; i++) {
338                 shape[index++] = parent.simple ? rightEdge - 1 + right[2*i] : rightEdge - parent.curveIndent + right[2*i];
339                 shape[index++] = y + right[2*i+1];
340             }
341             shape[index++] = parent.simple ? rightEdge - 1 : rightEdge + parent.curveWidth - parent.curveIndent;
342             shape[index++] = y + height + 1;
343             shape[index++] = parent.simple ? rightEdge - 1 : rightEdge + parent.curveWidth - parent.curveIndent;
344             shape[index++] = y + height + 1;
345         }
346 
347         Rectangle clipping = gc.getClipping();
348         Rectangle bounds = getBounds();
349         bounds.height += 1;
350         if (parent.onBottom) bounds.y -= 1;
351         bool tabInPaint = clipping.intersects(bounds);
352 
353         if (tabInPaint) {
354             // fill in tab background
355             if (parent.selectionGradientColors !is null && !parent.selectionGradientVertical) {
356                 parent.drawBackground(gc, shape, true);
357             } else {
358                 Color defaultBackground = parent.selectionBackground;
359                 Image image = parent.selectionBgImage;
360                 Color[] colors = parent.selectionGradientColors;
361                 int[] percents = parent.selectionGradientPercents;
362                 bool vertical = parent.selectionGradientVertical;
363                 xx = x;
364                 yy = parent.onBottom ? y -1 : y + 1;
365                 ww = width;
366                 hh = height;
367                 if (!parent.single && !parent.simple) ww += parent.curveWidth - parent.curveIndent;
368                 parent.drawBackground(gc, shape, xx, yy, ww, hh, defaultBackground, image, colors, percents, vertical);
369             }
370         }
371 
372         //Highlight MUST be drawn before the outline so that outline can cover it in the right spots (start of swoop)
373         //otherwise the curve looks jagged
374         drawHighlight(gc, rightEdge);
375 
376         // draw outline
377         shape[0] = Math.max(0, parent.borderLeft - 1);
378         if (parent.borderLeft is 0 && parent.indexOf(this) is parent.firstIndex) {
379             shape[1] = parent.onBottom ? y + height - 1 : y;
380             shape[5] = shape[3] = shape[1];
381         }
382         shape[shape.length - 2] = size.x - parent.borderRight + 1;
383         for (int i = 0; i < shape.length/2; i++) {
384             if (shape[2*i + 1] is y + height + 1) shape[2*i + 1] -= 1;
385         }
386         RGB inside = parent.selectionBackground.getRGB();
387         if (parent.selectionBgImage !is null ||
388             (parent.selectionGradientColors !is null && parent.selectionGradientColors.length > 1)) {
389             inside = null;
390         }
391         RGB outside = parent.getBackground().getRGB();
392         if (parent.bgImage !is null ||
393             (parent.gradientColors !is null && parent.gradientColors.length > 1)) {
394             outside = null;
395         }
396         parent.antialias(shape, CTabFolder.borderColor.getRGB(), inside, outside, gc);
397         gc.setForeground(CTabFolder.borderColor);
398         gc.drawPolyline(shape);
399 
400         if (!tabInPaint) return;
401     }
402 
403     // draw Image
404     int xDraw = x + LEFT_MARGIN;
405     if (parent.single && (parent.showClose || showClose)) xDraw += CTabFolder.BUTTON_SIZE;
406     Image image = getImage();
407     if (image !is null) {
408         Rectangle imageBounds = image.getBounds();
409         // only draw image if it won't overlap with close button
410         int maxImageWidth = rightEdge - xDraw - RIGHT_MARGIN;
411         if (!parent.single && closeRect.width > 0) maxImageWidth -= closeRect.width + INTERNAL_SPACING;
412         if (imageBounds.width < maxImageWidth) {
413             int imageX = xDraw;
414             int imageY = y + (height - imageBounds.height) / 2;
415             imageY += parent.onBottom ? -1 : 1;
416             gc.drawImage(image, imageX, imageY);
417             xDraw += imageBounds.width + INTERNAL_SPACING;
418         }
419     }
420 
421     // draw Text
422     int textWidth = rightEdge - xDraw - RIGHT_MARGIN;
423     if (!parent.single && closeRect.width > 0) textWidth -= closeRect.width + INTERNAL_SPACING;
424     if (textWidth > 0) {
425         Font gcFont = gc.getFont();
426         gc.setFont(font is null ? parent.getFont() : font);
427 
428         if (shortenedText is null || shortenedTextWidth !is textWidth) {
429             shortenedText = shortenText(gc, getText(), textWidth);
430             shortenedTextWidth = textWidth;
431         }
432         Point extent = gc.textExtent(shortenedText, FLAGS);
433         int textY = y + (height - extent.y) / 2;
434         textY += parent.onBottom ? -1 : 1;
435 
436         gc.setForeground(parent.selectionForeground);
437         gc.drawText(shortenedText, xDraw, textY, FLAGS);
438         gc.setFont(gcFont);
439 
440         // draw a Focus rectangle
441         if (parent.isFocusControl()) {
442             Display display = getDisplay();
443             if (parent.simple || parent.single) {
444                 gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
445                 gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
446                 gc.drawFocus(xDraw-1, textY-1, extent.x+2, extent.y+2);
447             } else {
448                 gc.setForeground(display.getSystemColor(CTabFolder.BUTTON_BORDER));
449                 gc.drawLine(xDraw, textY+extent.y+1, xDraw+extent.x+1, textY+extent.y+1);
450             }
451         }
452     }
453     if (parent.showClose || showClose) drawClose(gc);
454 }
455 
456 /*
457  * Draw a highlight effect along the left, top, and right edges of the tab.
458  * Only for curved tabs, on top.
459  * Do not draw if insufficient colors.
460  */
461 void drawHighlight(GC gc, int rightEdge) {
462     //only draw for curvy tabs and only draw for top tabs
463     if(parent.simple || this.parent.onBottom)
464         return;
465 
466     if(parent.selectionHighlightGradientBegin is null)
467         return;
468 
469     Color[] gradients = parent.selectionHighlightGradientColorsCache;
470     if(gradients is null)
471         return;
472     int gradientsSize = cast(int)/*64bit*/gradients.length;
473     if(gradientsSize is 0)
474         return;     //shouldn't happen but just to be tidy
475 
476     gc.setForeground(gradients[0]);
477 
478     //draw top horizontal line
479     gc.drawLine(
480             CTabFolder.TOP_LEFT_CORNER_HILITE[0] + x + 1, //rely on fact that first pair is top/right of curve
481             1 + y,
482             rightEdge - parent.curveIndent,
483             1 + y);
484 
485     const int[] leftHighlightCurve = CTabFolder.TOP_LEFT_CORNER_HILITE;
486 
487     int d = parent.tabHeight - cast(int)/*64bit*/parent.topCurveHighlightEnd.length /2;
488 
489     int lastX = 0;
490     int lastY = 0;
491     int lastColorIndex = 0;
492 
493     //draw upper left curve highlight
494     for (int i = 0; i < leftHighlightCurve.length /2; i++) {
495         int rawX = leftHighlightCurve[i * 2];
496         int rawY = leftHighlightCurve[i * 2 + 1];
497         lastX = rawX + x;
498         lastY = rawY + y;
499         lastColorIndex = rawY - 1;
500         gc.setForeground(gradients[lastColorIndex]);
501         gc.drawPoint(lastX, lastY);
502     }
503     //draw left vertical line highlight
504     for(int i = lastColorIndex; i < gradientsSize; i++) {
505         gc.setForeground(gradients[i]);
506         gc.drawPoint(lastX, 1 + lastY++);
507     }
508 
509     int rightEdgeOffset = rightEdge - parent.curveIndent;
510 
511     //draw right swoop highlight up to diagonal portion
512     for (int i = 0; i < parent.topCurveHighlightStart.length /2; i++) {
513         int rawX = parent.topCurveHighlightStart[i * 2];
514         int rawY = parent.topCurveHighlightStart[i * 2 + 1];
515         lastX = rawX + rightEdgeOffset;
516         lastY = rawY + y;
517         lastColorIndex = rawY - 1;
518         if(lastColorIndex >= gradientsSize)
519             break;  //can happen if tabs are unusually short and cut off the curve
520         gc.setForeground(gradients[lastColorIndex]);
521         gc.drawPoint(lastX, lastY);
522     }
523     //draw right diagonal line highlight
524     for(int i = lastColorIndex; i < lastColorIndex + d; i++) {
525         if(i >= gradientsSize)
526             break;  //can happen if tabs are unusually short and cut off the curve
527         gc.setForeground(gradients[i]);
528         gc.drawPoint(1 + lastX++, 1 + lastY++);
529     }
530 
531     //draw right swoop highlight from diagonal portion to end
532     for (int i = 0; i < parent.topCurveHighlightEnd.length /2; i++) {
533         int rawX = parent.topCurveHighlightEnd[i * 2]; //d is already encoded in this value
534         int rawY = parent.topCurveHighlightEnd[i * 2 + 1]; //d already encoded
535         lastX = rawX + rightEdgeOffset;
536         lastY = rawY + y;
537         lastColorIndex = rawY - 1;
538         if(lastColorIndex >= gradientsSize)
539             break;  //can happen if tabs are unusually short and cut off the curve
540         gc.setForeground(gradients[lastColorIndex]);
541         gc.drawPoint(lastX, lastY);
542     }
543 }
544 
545 /*
546  * Draw the unselected border for the receiver on the right.
547  *
548  * @param gc
549  */
550 void drawRightUnselectedBorder(GC gc) {
551 
552     int[] shape = null;
553     int startX = x + width - 1;
554 
555     if (this.parent.onBottom) {
556         TryConst!(int[]) right = parent.simple
557             ? CTabFolder.SIMPLE_UNSELECTED_INNER_CORNER
558             : CTabFolder.BOTTOM_RIGHT_CORNER;
559 
560         shape = new int[right.length + 2];
561         int index = 0;
562 
563         for (int i = 0; i < right.length / 2; i++) {
564             shape[index++] = startX + right[2 * i];
565             shape[index++] = y + height + right[2 * i + 1] - 1;
566         }
567         shape[index++] = startX;
568         shape[index++] = y - 1;
569     } else {
570         TryConst!(int[]) right = parent.simple
571             ? CTabFolder.SIMPLE_UNSELECTED_INNER_CORNER
572             : CTabFolder.TOP_RIGHT_CORNER;
573 
574         shape = new int[right.length + 2];
575         int index = 0;
576 
577         for (int i = 0; i < right.length / 2; i++) {
578             shape[index++] = startX + right[2 * i];
579             shape[index++] = y + right[2 * i + 1];
580         }
581 
582         shape[index++] = startX;
583         shape[index++] = y + height;
584 
585     }
586 
587     drawBorder(gc, shape);
588 
589 }
590 
591 /*
592  * Draw the border of the tab
593  *
594  * @param gc
595  * @param shape
596  */
597 void drawBorder(GC gc, int[] shape) {
598 
599     gc.setForeground(CTabFolder.borderColor);
600     gc.drawPolyline(shape);
601 }
602 
603 /*
604  * Draw the unselected border for the receiver on the left.
605  *
606  * @param gc
607  */
608 void drawLeftUnselectedBorder(GC gc) {
609 
610     int[] shape = null;
611     if (this.parent.onBottom) {
612         TryConst!(int[]) left = parent.simple
613             ? CTabFolder.SIMPLE_UNSELECTED_INNER_CORNER
614             : CTabFolder.BOTTOM_LEFT_CORNER;
615 
616         shape = new int[left.length + 2];
617         int index = 0;
618         shape[index++] = x;
619         shape[index++] = y - 1;
620         for (int i = 0; i < left.length / 2; i++) {
621             shape[index++] = x + left[2 * i];
622             shape[index++] = y + height + left[2 * i + 1] - 1;
623         }
624     } else {
625         TryConst!(int[]) left = parent.simple
626             ? CTabFolder.SIMPLE_UNSELECTED_INNER_CORNER
627             : CTabFolder.TOP_LEFT_CORNER;
628 
629         shape = new int[left.length + 2];
630         int index = 0;
631         shape[index++] = x;
632         shape[index++] = y + height;
633         for (int i = 0; i < left.length / 2; i++) {
634             shape[index++] = x + left[2 * i];
635             shape[index++] = y + left[2 * i + 1];
636         }
637 
638     }
639 
640     drawBorder(gc, shape);
641 }
642 
643 void drawUnselected(GC gc) {
644     // Do not draw partial items
645     if (!showing) return;
646 
647     Rectangle clipping = gc.getClipping();
648     Rectangle bounds = getBounds();
649     if (!clipping.intersects(bounds)) return;
650 
651     // draw border
652     int index = parent.indexOf(this);
653 
654     if (index > 0 && index < parent.selectedIndex)
655         drawLeftUnselectedBorder(gc);
656     // If it is the last one then draw a line
657     if (index > parent.selectedIndex)
658         drawRightUnselectedBorder(gc);
659 
660     // draw Image
661     int xDraw = x + LEFT_MARGIN;
662     Image image = getImage();
663     if (image !is null && parent.showUnselectedImage) {
664         Rectangle imageBounds = image.getBounds();
665         // only draw image if it won't overlap with close button
666         int maxImageWidth = x + width - xDraw - RIGHT_MARGIN;
667         if (parent.showUnselectedClose && (parent.showClose || showClose)) {
668             maxImageWidth -= closeRect.width + INTERNAL_SPACING;
669         }
670         if (imageBounds.width < maxImageWidth) {
671             int imageX = xDraw;
672             int imageHeight = imageBounds.height;
673             int imageY = y + (height - imageHeight) / 2;
674             imageY += parent.onBottom ? -1 : 1;
675             int imageWidth = imageBounds.width * imageHeight / imageBounds.height;
676             gc.drawImage(image,
677                          imageBounds.x, imageBounds.y, imageBounds.width, imageBounds.height,
678                          imageX, imageY, imageWidth, imageHeight);
679             xDraw += imageWidth + INTERNAL_SPACING;
680         }
681     }
682     // draw Text
683     int textWidth = x + width - xDraw - RIGHT_MARGIN;
684     if (parent.showUnselectedClose && (parent.showClose || showClose)) {
685         textWidth -= closeRect.width + INTERNAL_SPACING;
686     }
687     if (textWidth > 0) {
688         Font gcFont = gc.getFont();
689         gc.setFont(font is null ? parent.getFont() : font);
690         if (shortenedText is null || shortenedTextWidth !is textWidth) {
691             shortenedText = shortenText(gc, getText(), textWidth);
692             shortenedTextWidth = textWidth;
693         }
694         Point extent = gc.textExtent(shortenedText, FLAGS);
695         int textY = y + (height - extent.y) / 2;
696         textY += parent.onBottom ? -1 : 1;
697         gc.setForeground(parent.getForeground());
698         gc.drawText(shortenedText, xDraw, textY, FLAGS);
699         gc.setFont(gcFont);
700     }
701     // draw close
702     if (parent.showUnselectedClose && (parent.showClose || showClose)) drawClose(gc);
703 }
704 /**
705  * Returns a rectangle describing the receiver's size and location
706  * relative to its parent.
707  *
708  * @return the receiver's bounding column rectangle
709  *
710  * @exception SWTException <ul>
711  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
712  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
713  * </ul>
714  */
715 public Rectangle getBounds () {
716     //checkWidget();
717     int w = width;
718     if (!parent.simple && !parent.single && parent.indexOf(this) is parent.selectedIndex) w += parent.curveWidth - parent.curveIndent;
719     return new Rectangle(x, y, w, height);
720 }
721 /**
722 * Gets the control that is displayed in the content area of the tab item.
723 *
724 * @return the control
725 *
726 * @exception SWTException <ul>
727 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
728 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
729 * </ul>
730 */
731 public Control getControl () {
732     checkWidget();
733     return control;
734 }
735 /**
736  * Get the image displayed in the tab if the tab is disabled.
737  *
738  * @return the disabled image or null
739  *
740  * @exception SWTException <ul>
741  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
742  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
743  * </ul>
744  *
745  * @deprecated the disabled image is not used
746  */
747 public Image getDisabledImage(){
748     checkWidget();
749     return disabledImage;
750 }
751 /**
752  * Returns the font that the receiver will use to paint textual information.
753  *
754  * @return the receiver's font
755  *
756  * @exception SWTException <ul>
757  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
758  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
759  * </ul>
760  *
761  *  @since 3.0
762  */
763 public Font getFont() {
764     checkWidget();
765     if (font !is null) return font;
766     return parent.getFont();
767 }
768 /**
769  * Returns the receiver's parent, which must be a <code>CTabFolder</code>.
770  *
771  * @return the receiver's parent
772  *
773  * @exception SWTException <ul>
774  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
775  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
776  * </ul>
777  */
778 public CTabFolder getParent () {
779     //checkWidget();
780     return parent;
781 }
782 /**
783  * Returns <code>true</code> to indicate that the receiver's close button should be shown.
784  * Otherwise return <code>false</code>. The initial value is defined by the style (SWT.CLOSE)
785  * that was used to create the receiver.
786  *
787  * @return <code>true</code> if the close button should be shown
788  *
789  * @exception SWTException <ul>
790  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
791  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
792  * </ul>
793  *
794  * @since 3.4
795  */
796 public bool getShowClose() {
797     checkWidget();
798     return showClose;
799 }
800 /**
801  * Returns the receiver's tool tip text, or null if it has
802  * not been set.
803  *
804  * @return the receiver's tool tip text
805  *
806  * @exception SWTException <ul>
807  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
808  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
809  * </ul>
810  */
811 public String getToolTipText () {
812     checkWidget();
813     if (toolTipText is null && shortenedText !is null) {
814         String text = getText();
815         if (shortenedText!=text) return text;
816     }
817     return toolTipText;
818 }
819 /**
820 * Returns <code>true</code> if the item will be rendered in the visible area of the CTabFolder. Returns false otherwise.
821 *
822 *  @return <code>true</code> if the item will be rendered in the visible area of the CTabFolder. Returns false otherwise.
823 *
824 *  @exception SWTException <ul>
825  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
826  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
827  * </ul>
828  *
829 * @since 3.0
830 */
831 public bool isShowing () {
832     checkWidget();
833     return showing;
834 }
835 void onPaint(GC gc, bool isSelected) {
836     if (width is 0 || height is 0) return;
837     if (isSelected) {
838         drawSelected(gc);
839     } else {
840         drawUnselected(gc);
841     }
842 }
843 int preferredHeight(GC gc) {
844     Image image = getImage();
845     int h = (image is null) ? 0 : image.getBounds().height;
846     String text = getText();
847     if (font is null) {
848         h = Math.max(h, gc.textExtent(text, FLAGS).y);
849     } else {
850         Font gcFont = gc.getFont();
851         gc.setFont(font);
852         h = Math.max(h, gc.textExtent(text, FLAGS).y);
853         gc.setFont(gcFont);
854     }
855     return h + TOP_MARGIN + BOTTOM_MARGIN;
856 }
857 int preferredWidth(GC gc, bool isSelected, bool minimum) {
858     // NOTE: preferred width does not include the "dead space" caused
859     // by the curve.
860     if (isDisposed()) return 0;
861     int w = 0;
862     Image image = getImage();
863     if (image !is null && (isSelected || parent.showUnselectedImage)) {
864         w += image.getBounds().width;
865     }
866     String text = null;
867     if (minimum) {
868         int minChars = parent.minChars;
869         text = minChars is 0 ? null : getText();
870         if (text !is null && toString32(text).length > minChars) {
871             if (useEllipses()) {
872                 int end = cast(int)/*64bit*/(minChars < ELLIPSIS.length + 1 ? minChars : minChars - ELLIPSIS.length);
873                 text = .toString(toString32(text)[ 0 .. end ]);
874                 if (minChars > ELLIPSIS.length + 1) text ~= ELLIPSIS;
875             } else {
876                 int end = minChars;
877                 text = .toString(toString32(text)[ 0 .. end ]);
878             }
879         }
880     } else {
881         text = getText();
882     }
883     if (text !is null) {
884         if (w > 0) w += INTERNAL_SPACING;
885         if (font is null) {
886             w += gc.textExtent(text, FLAGS).x;
887         } else {
888             Font gcFont = gc.getFont();
889             gc.setFont(font);
890             w += gc.textExtent(text, FLAGS).x;
891             gc.setFont(gcFont);
892         }
893     }
894     if (parent.showClose || showClose) {
895         if (isSelected || parent.showUnselectedClose) {
896             if (w > 0) w += INTERNAL_SPACING;
897             w += CTabFolder.BUTTON_SIZE;
898         }
899     }
900     return w + LEFT_MARGIN + RIGHT_MARGIN;
901 }
902 /**
903  * Sets the control that is used to fill the client area of
904  * the tab folder when the user selects the tab item.
905  *
906  * @param control the new control (or null)
907  *
908  * @exception IllegalArgumentException <ul>
909  *    <li>ERROR_INVALID_ARGUMENT - if the control has been disposed</li>
910  *    <li>ERROR_INVALID_PARENT - if the control is not in the same widget tree</li>
911  * </ul>
912  * @exception SWTException <ul>
913  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
914  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
915  * </ul>
916  */
917 public void setControl (Control control) {
918     checkWidget();
919     if (control !is null) {
920         if (control.isDisposed()) SWT.error (SWT.ERROR_INVALID_ARGUMENT);
921         if (control.getParent() !is parent) SWT.error (SWT.ERROR_INVALID_PARENT);
922     }
923     if (this.control !is null && !this.control.isDisposed()) {
924         this.control.setVisible(false);
925     }
926     this.control = control;
927     if (this.control !is null) {
928         int index = parent.indexOf (this);
929         if (index is parent.getSelectionIndex ()){
930             this.control.setBounds(parent.getClientArea ());
931             this.control.setVisible(true);
932         } else {
933             this.control.setVisible(false);
934         }
935     }
936 }
937 /**
938  * Sets the image that is displayed if the tab item is disabled.
939  * Null will clear the image.
940  *
941  * @param image the image to be displayed when the item is disabled or null
942  *
943  * @exception SWTException <ul>
944  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
945  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
946  * </ul>
947  *
948  * @deprecated This image is not used
949  */
950 public void setDisabledImage (Image image) {
951     checkWidget();
952     if (image !is null && image.isDisposed ()) {
953         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
954     }
955     this.disabledImage = image;
956 }
957 /**
958  * Sets the font that the receiver will use to paint textual information
959  * for this item to the font specified by the argument, or to the default font
960  * for that kind of control if the argument is null.
961  *
962  * @param font the new font (or null)
963  *
964  * @exception IllegalArgumentException <ul>
965  *    <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
966  * </ul>
967  * @exception SWTException <ul>
968  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
969  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
970  * </ul>
971  *
972  * @since 3.0
973  */
974 public void setFont (Font font){
975     checkWidget();
976     if (font !is null && font.isDisposed ()) {
977         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
978     }
979     if (font is null && this.font is null) return;
980     if (font !is null && font==this.font) return;
981     this.font = font;
982     if (!parent.updateTabHeight(false)) {
983         parent.updateItems();
984         parent.redrawTabs();
985     }
986 }
987 public override void setImage (Image image) {
988     checkWidget();
989     if (image !is null && image.isDisposed ()) {
990         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
991     }
992     Image oldImage = getImage();
993     if (image is null && oldImage is null) return;
994     if (image !is null && image==oldImage) return;
995     super.setImage(image);
996     if (!parent.updateTabHeight(false)) {
997         // If image is the same size as before,
998         // redraw only the image
999         if (oldImage !is null && image !is null) {
1000             Rectangle oldBounds = oldImage.getBounds();
1001             Rectangle bounds = image.getBounds();
1002             if (bounds.width is oldBounds.width && bounds.height is oldBounds.height) {
1003                 if (showing) {
1004                     bool selected = parent.indexOf(this) is parent.selectedIndex;
1005                     if (selected || parent.showUnselectedImage) {
1006                         int imageX = x + LEFT_MARGIN, maxImageWidth;
1007                         if (selected) {
1008                             if (parent.single && (parent.showClose || showClose)) imageX += CTabFolder.BUTTON_SIZE;
1009                             int rightEdge = Math.min (x + width, parent.getRightItemEdge());
1010                             maxImageWidth = rightEdge - imageX - RIGHT_MARGIN;
1011                             if (!parent.single && closeRect.width > 0) maxImageWidth -= closeRect.width + INTERNAL_SPACING;
1012                         } else {
1013                             maxImageWidth = x + width - imageX - RIGHT_MARGIN;
1014                             if (parent.showUnselectedClose && (parent.showClose || showClose)) {
1015                                 maxImageWidth -= closeRect.width + INTERNAL_SPACING;
1016                             }
1017                         }
1018                         if (bounds.width < maxImageWidth) {
1019                             int imageY = y + (height - bounds.height) / 2 + (parent.onBottom ? -1 : 1);
1020                             parent.redraw(imageX, imageY, bounds.width, bounds.height, false);
1021                         }
1022                     }
1023                 }
1024                 return;
1025             }
1026         }
1027         parent.updateItems();
1028         parent.redrawTabs();
1029     }
1030 }
1031 /**
1032  * Sets to <code>true</code> to indicate that the receiver's close button should be shown.
1033  * If the parent (CTabFolder) was created with SWT.CLOSE style, changing this value has
1034  * no effect.
1035  *
1036  * @param close the new state of the close button
1037  *
1038  * @exception SWTException <ul>
1039  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1040  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1041  * </ul>
1042  *
1043  * @since 3.4
1044  */
1045 public void setShowClose(bool close) {
1046     checkWidget();
1047     if (showClose is close) return;
1048     showClose = close;
1049     parent.updateItems();
1050     parent.redrawTabs();
1051 }
1052 public override void setText (String string) {
1053     checkWidget();
1054     // SWT extension: allow null for zero length string
1055     //if (string is null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
1056     if (string.equals (getText())) return;
1057     super.setText(string);
1058     shortenedText = null;
1059     shortenedTextWidth = 0;
1060     if (!parent.updateTabHeight(false)) {
1061         parent.updateItems();
1062         parent.redrawTabs();
1063     }
1064 }
1065 /**
1066  * Sets the receiver's tool tip text to the argument, which
1067  * may be null indicating that no tool tip text should be shown.
1068  *
1069  * @param string the new tool tip text (or null)
1070  *
1071  * @exception SWTException <ul>
1072  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1073  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1074  * </ul>
1075  */
1076 public void setToolTipText (String string) {
1077     checkWidget();
1078     toolTipText = string;
1079 }
1080 
1081 }