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 }