1 /*******************************************************************************
2 * Copyright (c) 2000, 2016 IBM Corporation and others.
4 * This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
9 * SPDX-License-Identifier: EPL-2.0
12 * IBM Corporation - initial API and implementation
13 * Lars Vogel <Lars.Vogel@vogella.com> - Bug 455263
14 *******************************************************************************/
15 package org.eclipse.swt.custom;
17 import org.eclipse.swt.*;
18 import org.eclipse.swt.graphics.*;
19 import org.eclipse.swt.widgets.*;
22 * Instances of this class provide all of the measuring and drawing functionality
23 * required by <code>CTabFolder</code>. This class can be subclassed in order to
24 * customize the look of a CTabFolder.
26 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
29 public class CTabFolderRenderer {
31 protected CTabFolder parent;
34 int[] topCurveHighlightStart;
35 int[] topCurveHighlightEnd;
38 int lastTabHeight = -1;
41 /* Selected item appearance */
42 Color selectionHighlightGradientBegin = null; //null == no highlight
43 //Although we are given new colours all the time to show different states (active, etc),
44 //some of which may have a highlight and some not, we'd like to retain the highlight colours
45 //as a cache so that we can reuse them if we're again told to show the highlight.
46 //We are relying on the fact that only one tab state usually gets a highlight, so only
47 //a single cache is required. If that happens to not be true, cache simply becomes less effective,
48 //but we don't leak colours.
49 Color[] selectionHighlightGradientColorsCache = null; //null is a legal value, check on access
50 /* Colors for anti-aliasing */
51 Color selectedOuterColor = null;
52 Color selectedInnerColor = null;
53 Color tabAreaColor = null;
54 Color minMaxBorderColor = null;
56 * Border color that was used in computing the cached anti-alias Colors.
57 * We have to recompute the colors if the border color changes
59 Color lastBorderColor = null;
61 Font chevronFont = null;
63 //TOP_LEFT_CORNER_HILITE is laid out in reverse (ie. top to bottom)
64 //so can fade in same direction as right swoop curve
65 static final int[] TOP_LEFT_CORNER_HILITE = new int[] {5,2, 4,2, 3,3, 2,4, 2,5, 1,6};
67 static final int[] TOP_LEFT_CORNER = new int[] {0,6, 1,5, 1,4, 4,1, 5,1, 6,0};
68 static final int[] TOP_RIGHT_CORNER = new int[] {-6,0, -5,1, -4,1, -1,4, -1,5, 0,6};
69 static final int[] BOTTOM_LEFT_CORNER = new int[] {0,-6, 1,-5, 1,-4, 4,-1, 5,-1, 6,0};
70 static final int[] BOTTOM_RIGHT_CORNER = new int[] {-6,0, -5,-1, -4,-1, -1,-4, -1,-5, 0,-6};
72 static final int[] SIMPLE_TOP_LEFT_CORNER = new int[] {0,2, 1,1, 2,0};
73 static final int[] SIMPLE_TOP_RIGHT_CORNER = new int[] {-2,0, -1,1, 0,2};
74 static final int[] SIMPLE_BOTTOM_LEFT_CORNER = new int[] {0,-2, 1,-1, 2,0};
75 static final int[] SIMPLE_BOTTOM_RIGHT_CORNER = new int[] {-2,0, -1,-1, 0,-2};
76 static final int[] SIMPLE_UNSELECTED_INNER_CORNER = new int[] {0,0};
78 static final int[] TOP_LEFT_CORNER_BORDERLESS = new int[] {0,6, 1,5, 1,4, 4,1, 5,1, 6,0};
79 static final int[] TOP_RIGHT_CORNER_BORDERLESS = new int[] {-7,0, -6,1, -5,1, -2,4, -2,5, -1,6};
80 static final int[] BOTTOM_LEFT_CORNER_BORDERLESS = new int[] {0,-6, 1,-6, 1,-5, 2,-4, 4,-2, 5,-1, 6,-1, 6,0};
81 static final int[] BOTTOM_RIGHT_CORNER_BORDERLESS = new int[] {-7,0, -7,-1, -6,-1, -5,-2, -3,-4, -2,-5, -2,-6, -1,-6};
83 static final int[] SIMPLE_TOP_LEFT_CORNER_BORDERLESS = new int[] {0,2, 1,1, 2,0};
84 static final int[] SIMPLE_TOP_RIGHT_CORNER_BORDERLESS= new int[] {-3,0, -2,1, -1,2};
85 static final int[] SIMPLE_BOTTOM_LEFT_CORNER_BORDERLESS = new int[] {0,-3, 1,-2, 2,-1, 3,0};
86 static final int[] SIMPLE_BOTTOM_RIGHT_CORNER_BORDERLESS = new int[] {-4,0, -3,-1, -2,-2, -1,-3};
88 static final RGB CLOSE_FILL = new RGB(252, 160, 160);
90 static final int BUTTON_SIZE = 16;
91 static final int BUTTON_TRIM = 1;
93 static final int BUTTON_BORDER = SWT.COLOR_WIDGET_DARK_SHADOW;
94 static final int BUTTON_FILL = SWT.COLOR_LIST_BACKGROUND;
95 static final int BORDER1_COLOR = SWT.COLOR_WIDGET_NORMAL_SHADOW;
97 static final int ITEM_TOP_MARGIN = 2;
98 static final int ITEM_BOTTOM_MARGIN = 2;
99 static final int ITEM_LEFT_MARGIN = 4;
100 static final int ITEM_RIGHT_MARGIN = 4;
101 static final int INTERNAL_SPACING = 4;
102 static final int FLAGS = SWT.DRAW_TRANSPARENT | SWT.DRAW_MNEMONIC | SWT.DRAW_DELIMITER;
103 static final String ELLIPSIS = "..."; //$NON-NLS-1$
104 private static final String CHEVRON_ELLIPSIS = "99+"; //$NON-NLS-1$
105 private static final int CHEVRON_FONT_HEIGHT = 10;
109 * Part constant indicating the body of the tab folder. The body is the
110 * underlying container for all of the tab folder and all other parts are
111 * drawn on top of it. (value is -1).
113 * @see #computeSize(int, int, GC, int, int)
114 * @see #computeTrim(int, int, int, int, int, int)
115 * @see #draw(int, int, Rectangle, GC)
117 public static final int PART_BODY = -1;
119 * Part constant indicating the tab header of the folder (value is -2). The
120 * header is drawn on top of the body and provides an area for the tabs and
121 * other tab folder buttons to be rendered.
123 * @see #computeSize(int, int, GC, int, int)
124 * @see #computeTrim(int, int, int, int, int, int)
125 * @see #draw(int, int, Rectangle, GC)
127 public static final int PART_HEADER = -2;
129 * Part constant indicating the border of the tab folder. (value is -3). The
130 * border is drawn around the body and is part of the body trim.
132 * @see #computeSize(int, int, GC, int, int)
133 * @see #computeTrim(int, int, int, int, int, int)
134 * @see #draw(int, int, Rectangle, GC)
136 public static final int PART_BORDER = -3;
138 * Part constant indicating the background of the tab folder. (value is -4).
140 * @see #computeSize(int, int, GC, int, int)
141 * @see #computeTrim(int, int, int, int, int, int)
142 * @see #draw(int, int, Rectangle, GC)
144 public static final int PART_BACKGROUND = -4;
146 * Part constant indicating the maximize button of the tab folder. (value is
149 * @see #computeSize(int, int, GC, int, int)
150 * @see #computeTrim(int, int, int, int, int, int)
151 * @see #draw(int, int, Rectangle, GC)
153 public static final int PART_MAX_BUTTON = -5;
155 * Part constant indicating the minimize button of the tab folder. (value is
158 * @see #computeSize(int, int, GC, int, int)
159 * @see #computeTrim(int, int, int, int, int, int)
160 * @see #draw(int, int, Rectangle, GC)
162 public static final int PART_MIN_BUTTON = -6;
164 * Part constant indicating the chevron button of the tab folder. (value is
167 * @see #computeSize(int, int, GC, int, int)
168 * @see #computeTrim(int, int, int, int, int, int)
169 * @see #draw(int, int, Rectangle, GC)
171 public static final int PART_CHEVRON_BUTTON = -7;
173 * Part constant indicating the close button of a tab item. (value is -8).
175 * @see #computeSize(int, int, GC, int, int)
176 * @see #computeTrim(int, int, int, int, int, int)
177 * @see #draw(int, int, Rectangle, GC)
179 public static final int PART_CLOSE_BUTTON = -8;
181 public static final int MINIMUM_SIZE = 1 << 24; //TODO: Should this be a state?
185 * Constructs a new instance of this class given its parent.
187 * @param parent CTabFolder
189 * @exception IllegalArgumentException <ul>
190 * <li>ERROR_INVALID_ARGUMENT - if the parent is disposed</li>
193 * @see Widget#getStyle
195 protected CTabFolderRenderer(CTabFolder parent) {
196 if (parent == null) return;
197 if (parent.isDisposed ()) SWT.error (SWT.ERROR_INVALID_ARGUMENT);
198 this.parent = parent;
201 void antialias (int[] shape, Color innerColor, Color outerColor, GC gc){
202 // Don't perform anti-aliasing on Mac because the platform
203 // already does it. The simple style also does not require anti-aliasing.
204 if (parent.simple) return;
205 String platform = SWT.getPlatform();
206 if ("cocoa".equals(platform)) return; //$NON-NLS-1$
207 // Don't perform anti-aliasing on low resolution displays
208 if (parent.getDisplay().getDepth() < 15) return;
209 if (outerColor != null) {
212 int oldY = parent.onBottom ? 0 : parent.getSize().y;
213 int[] outer = new int[shape.length];
214 for (int i = 0; i < shape.length/2; i++) {
215 if (left && (index + 3 < shape.length)) {
216 left = parent.onBottom ? oldY <= shape[index+3] : oldY >= shape[index+3];
217 oldY = shape[index+1];
219 outer[index] = shape[index++] + (left ? -1 : +1);
220 outer[index] = shape[index++];
222 gc.setForeground(outerColor);
223 gc.drawPolyline(outer);
225 if (innerColor != null) {
226 int[] inner = new int[shape.length];
229 int oldY = parent.onBottom ? 0 : parent.getSize().y;
230 for (int i = 0; i < shape.length/2; i++) {
231 if (left && (index + 3 < shape.length)) {
232 left = parent.onBottom ? oldY <= shape[index+3] : oldY >= shape[index+3];
233 oldY = shape[index+1];
235 inner[index] = shape[index++] + (left ? +1 : -1);
236 inner[index] = shape[index++];
238 gc.setForeground(innerColor);
239 gc.drawPolyline(inner);
244 * Returns the preferred size of a part.
246 * The <em>preferred size</em> of a part is the size that it would
247 * best be displayed at. The width hint and height hint arguments
248 * allow the caller to ask a control questions such as "Given a particular
249 * width, how high does the part need to be to show all of the contents?"
250 * To indicate that the caller does not wish to constrain a particular
251 * dimension, the constant <code>SWT.DEFAULT</code> is passed for the hint.
253 * The <code>part</code> value indicated what component the preferred size is
254 * to be calculated for. Valid values are any of the part constants:
258 * <li>PART_HEADER</li>
259 * <li>PART_BORDER</li>
260 * <li>PART_BACKGROUND</li>
261 * <li>PART_MAX_BUTTON</li>
262 * <li>PART_MIN_BUTTON</li>
263 * <li>PART_CHEVRON_BUTTON</li>
264 * <li>PART_CLOSE_BUTTON</li>
265 * <li>A positive integer which is the index of an item in the CTabFolder.</li>
268 * The <code>state</code> parameter may be one of the following:
272 * <li>SWT.SELECTED - whether the part is selected</li>
274 * @param part a part constant
275 * @param state current state
276 * @param gc the gc to use for measuring
277 * @param wHint the width hint (can be <code>SWT.DEFAULT</code>)
278 * @param hHint the height hint (can be <code>SWT.DEFAULT</code>)
279 * @return the preferred size of the part
283 protected Point computeSize (int part, int state, GC gc, int wHint, int hHint) {
284 int width = 0, height = 0;
287 if (parent.fixedTabHeight != SWT.DEFAULT) {
288 height = parent.fixedTabHeight == 0 ? 0 : parent.fixedTabHeight + 1; // +1 for line drawn across top of tab
290 CTabItem[] items = parent.items;
291 if (items.length == 0) {
292 height = gc.textExtent("Default", FLAGS).y + ITEM_TOP_MARGIN + ITEM_BOTTOM_MARGIN; //$NON-NLS-1$
294 for (int i=0; i < items.length; i++) {
295 height = Math.max(height, computeSize(i, SWT.NONE, gc, wHint, hHint).y);
301 case PART_MAX_BUTTON:
302 case PART_MIN_BUTTON:
303 case PART_CLOSE_BUTTON:
304 width = height = BUTTON_SIZE;
306 case PART_CHEVRON_BUTTON:
307 Font prevFont = gc.getFont();
308 gc.setFont(getChevronFont(parent.getDisplay()));
309 int widthOfDoubleArrows = 8; // see drawChevron method
310 width = gc.textExtent(CHEVRON_ELLIPSIS).x + widthOfDoubleArrows;
311 height = BUTTON_SIZE;
312 gc.setFont(prevFont);
315 if (0 <= part && part < parent.getItemCount()) {
317 CTabItem item = parent.items[part];
318 if (item.isDisposed()) return new Point(0,0);
319 Image image = item.getImage();
320 if (image != null && !image.isDisposed()) {
321 Rectangle bounds = image.getBounds();
322 if ((state & SWT.SELECTED) != 0 || parent.showUnselectedImage) {
323 width += bounds.width;
325 height = bounds.height;
328 if ((state & MINIMUM_SIZE) != 0) {
329 int minChars = parent.minChars;
330 text = minChars == 0 ? null : item.getText();
331 if (text != null && text.length() > minChars) {
333 int end = minChars < ELLIPSIS.length() + 1 ? minChars : minChars - ELLIPSIS.length();
334 text = text.substring(0, end);
335 if (minChars > ELLIPSIS.length() + 1) text += ELLIPSIS;
338 text = text.substring(0, end);
342 text = item.getText();
345 if (width > 0) width += INTERNAL_SPACING;
346 if (item.font == null) {
347 Point size = gc.textExtent(text, FLAGS);
349 height = Math.max(height, size.y);
351 Font gcFont = gc.getFont();
352 gc.setFont(item.font);
353 Point size = gc.textExtent(text, FLAGS);
355 height = Math.max(height, size.y);
359 if (parent.showClose || item.showClose) {
360 if ((state & SWT.SELECTED) != 0 || parent.showUnselectedClose) {
361 if (width > 0) width += INTERNAL_SPACING;
362 width += computeSize(PART_CLOSE_BUTTON, SWT.NONE, gc, SWT.DEFAULT, SWT.DEFAULT).x;
368 Rectangle trim = computeTrim(part, state, 0, 0, width, height);
370 height = trim.height;
371 return new Point(width, height);
375 * Given a desired <em>client area</em> for the part
376 * (as described by the arguments), returns the bounding
377 * rectangle which would be required to produce that client
380 * In other words, it returns a rectangle such that, if the
381 * part's bounds were set to that rectangle, the area
382 * of the part which is capable of displaying data
383 * (that is, not covered by the "trimmings") would be the
384 * rectangle described by the arguments (relative to the
385 * receiver's parent).
388 * @param part one of the part constants
389 * @param state the state of the part
390 * @param x the desired x coordinate of the client area
391 * @param y the desired y coordinate of the client area
392 * @param width the desired width of the client area
393 * @param height the desired height of the client area
394 * @return the required bounds to produce the given client area
396 * @see CTabFolderRenderer#computeSize(int, int, GC, int, int) valid part and state values
400 protected Rectangle computeTrim (int part, int state, int x, int y, int width, int height) {
401 int borderLeft = parent.borderVisible ? 1 : 0;
402 int borderRight = borderLeft;
403 int borderTop = parent.onBottom ? borderLeft : 0;
404 int borderBottom = parent.onBottom ? 0 : borderLeft;
405 int tabHeight = parent.tabHeight;
408 int style = parent.getStyle();
409 int highlight_header = (style & SWT.FLAT) != 0 ? 1 : 3;
410 int highlight_margin = (style & SWT.FLAT) != 0 ? 0 : 2;
411 if (parent.fixedTabHeight == 0 && (style & SWT.FLAT) != 0 && (style & SWT.BORDER) == 0) {
412 highlight_header = 0;
414 int marginWidth = parent.marginWidth;
415 int marginHeight = parent.marginHeight;
416 x = x - marginWidth - highlight_margin - borderLeft;
417 width = width + borderLeft + borderRight + 2*marginWidth + 2*highlight_margin;
418 if (parent.minimized) {
419 y = parent.onBottom ? y - borderTop : y - highlight_header - tabHeight - borderTop;
420 height = borderTop + borderBottom + tabHeight + highlight_header;
422 y = parent.onBottom ? y - marginHeight - highlight_margin - borderTop : y - marginHeight - highlight_header - tabHeight - borderTop;
423 height = height + borderTop + borderBottom + 2*marginHeight + tabHeight + highlight_header + highlight_margin;
429 case PART_MAX_BUTTON:
430 case PART_MIN_BUTTON:
431 case PART_CLOSE_BUTTON:
432 case PART_CHEVRON_BUTTON:
435 width += BUTTON_TRIM*2;
436 height += BUTTON_TRIM*2;
440 width = width + borderLeft + borderRight;
441 if (!parent.simple) width += 2; // TOP_RIGHT_CORNER needs more space
443 height = height + borderTop + borderBottom;
446 if (0 <= part && part < parent.getItemCount()) {
448 x = x - ITEM_LEFT_MARGIN;
449 width = width + ITEM_LEFT_MARGIN + ITEM_RIGHT_MARGIN;
450 if (!parent.simple && !parent.single && (state & SWT.SELECTED) != 0) {
451 width += curveWidth - curveIndent;
453 y = y - ITEM_TOP_MARGIN;
454 height = height + ITEM_TOP_MARGIN + ITEM_BOTTOM_MARGIN;
458 return new Rectangle(x, y, width, height);
461 void createAntialiasColors() {
462 disposeAntialiasColors();
463 lastBorderColor = parent.getDisplay().getSystemColor(BORDER1_COLOR);
464 RGB lineRGB = lastBorderColor.getRGB();
465 /* compute the selected color */
466 RGB innerRGB = parent.selectionBackground.getRGB();
467 if (parent.selectionBgImage != null ||
468 (parent.selectionGradientColors != null && parent.selectionGradientColors.length > 1)) {
471 RGB outerRGB = parent.getBackground().getRGB();
472 if (parent.gradientColors != null && parent.gradientColors.length > 1) {
475 if (outerRGB != null) {
478 int red = from.red + 2*(to.red - from.red)/3;
479 int green = from.green + 2*(to.green - from.green)/3;
480 int blue = from.blue + 2*(to.blue - from.blue)/3;
481 selectedOuterColor = new Color(parent.getDisplay(), red, green, blue);
483 if (innerRGB != null) {
486 int red = from.red + 2*(to.red - from.red)/3;
487 int green = from.green + 2*(to.green - from.green)/3;
488 int blue = from.blue + 2*(to.blue - from.blue)/3;
489 selectedInnerColor = new Color(parent.getDisplay(), red, green, blue);
491 /* compute the tabArea color */
492 outerRGB = parent.getParent().getBackground().getRGB();
493 if (outerRGB != null) {
496 int red = from.red + 2*(to.red - from.red)/3;
497 int green = from.green + 2*(to.green - from.green)/3;
498 int blue = from.blue + 2*(to.blue - from.blue)/3;
499 tabAreaColor = new Color(parent.getDisplay(), red, green, blue);
504 * Allocate colors for the highlight line.
505 * Colours will be a gradual blend ranging from to.
506 * Blend length will be tab height.
507 * Recompute this if tab height changes.
508 * Could remain null if there'd be no gradient (start=end or low colour display)
510 void createSelectionHighlightGradientColors(Color start) {
511 disposeSelectionHighlightGradientColors(); //dispose if existing
513 if(start == null) //shouldn't happen but just to be safe
516 //alloc colours for entire height to ensure it matches wherever we stop drawing
517 int fadeGradientSize = parent.tabHeight;
519 RGB from = start.getRGB();
520 RGB to = parent.selectionBackground.getRGB();
522 selectionHighlightGradientColorsCache = new Color[fadeGradientSize];
523 int denom = fadeGradientSize - 1;
525 for (int i = 0; i < fadeGradientSize; i++) {
526 int propFrom = denom - i;
528 int red = (to.red * propTo + from.red * propFrom) / denom;
529 int green = (to.green * propTo + from.green * propFrom) / denom;
530 int blue = (to.blue * propTo + from.blue * propFrom) / denom;
531 selectionHighlightGradientColorsCache[i] = new Color(parent.getDisplay(), red, green, blue);
536 * Dispose of any operating system resources associated with
537 * the renderer. Called by the CTabFolder parent upon receiving
538 * the dispose event or when changing the renderer.
542 protected void dispose() {
543 disposeAntialiasColors();
544 disposeSelectionHighlightGradientColors();
545 if (fillColor != null) {
549 if (chevronFont != null) {
550 chevronFont.dispose();
553 if (minMaxBorderColor != null) {
554 minMaxBorderColor.dispose();
555 minMaxBorderColor = null;
559 void disposeAntialiasColors() {
560 if (tabAreaColor != null) tabAreaColor.dispose();
561 if (selectedInnerColor != null) selectedInnerColor.dispose();
562 if (selectedOuterColor != null) selectedOuterColor.dispose();
563 tabAreaColor = selectedInnerColor = selectedOuterColor = null;
566 void disposeSelectionHighlightGradientColors() {
567 if(selectionHighlightGradientColorsCache == null)
569 for (Color element : selectionHighlightGradientColorsCache) {
572 selectionHighlightGradientColorsCache = null;
576 * Draw a specified <code>part</code> of the CTabFolder using the provided <code>bounds</code> and <code>GC</code>.
577 * <p>The valid CTabFolder <code>part</code> constants are:
580 * <li>PART_BODY - the entire body of the CTabFolder</li>
581 * <li>PART_HEADER - the upper tab area of the CTabFolder</li>
582 * <li>PART_BORDER - the border of the CTabFolder</li>
583 * <li>PART_BACKGROUND - the background of the CTabFolder</li>
584 * <li>PART_MAX_BUTTON</li>
585 * <li>PART_MIN_BUTTON</li>
586 * <li>PART_CHEVRON_BUTTON</li>
587 * <li>PART_CLOSE_BUTTON</li>
588 * <li>A positive integer which is the index of an item in the CTabFolder.</li>
591 * The <code>state</code> parameter may be a combination of:
594 * <li>SWT.BACKGROUND - whether the background should be drawn</li>
595 * <li>SWT.FOREGROUND - whether the foreground should be drawn</li>
596 * <li>SWT.SELECTED - whether the part is selected</li>
597 * <li>SWT.HOT - whether the part is hot (i.e. mouse is over the part)</li>
600 * @param part part to draw
601 * @param state state of the part
602 * @param bounds the bounds of the part
603 * @param gc the gc to draw the part on
607 protected void draw (int part, int state, Rectangle bounds, GC gc) {
608 if (minMaxBorderColor == null) {
609 // this color has to be identical to the colors used in the PNG files of
610 // the view drop down menu located in
611 // org.eclipse.e4.ui.workbench.renderers.swt/icons/full/elcl16/view_menu.png
612 minMaxBorderColor = new Color (gc.getDevice(), 105, 105, 105);
615 case PART_BACKGROUND:
616 this.drawBackground(gc, bounds, state);
619 drawBody(gc, bounds, state);
622 drawTabArea(gc, bounds, state);
624 case PART_MAX_BUTTON:
625 drawMaximize(gc, bounds, state);
627 case PART_MIN_BUTTON:
628 drawMinimize(gc, bounds, state);
630 case PART_CHEVRON_BUTTON:
631 drawChevron(gc, bounds, state);
634 if (0 <= part && part < parent.getItemCount()) {
635 if (bounds.width == 0 || bounds.height == 0) return;
636 if ((state & SWT.SELECTED) != 0 ) {
637 drawSelected(part, gc, bounds, state);
639 drawUnselected(part, gc, bounds, state);
646 void drawBackground(GC gc, Rectangle bounds, int state) {
647 boolean selected = (state & SWT.SELECTED) != 0;
648 Color defaultBackground = selected && parent.shouldHighlight() ? parent.selectionBackground : parent.getBackground();
649 Image image = selected ? parent.selectionBgImage : null;
650 Color[] colors = selected & parent.shouldHighlight() ? parent.selectionGradientColors : parent.gradientColors;
651 int[] percents = selected ? parent.selectionGradientPercents : parent.gradientPercents;
652 boolean vertical = selected ? parent.selectionGradientVertical : parent.gradientVertical;
654 drawBackground(gc, null, bounds.x, bounds.y, bounds.width, bounds.height, defaultBackground, image, colors, percents, vertical);
657 void drawBackground(GC gc, int[] shape, boolean selected) {
658 Color defaultBackground = selected && parent.shouldHighlight() ? parent.selectionBackground : parent.getBackground();
659 Image image = selected ? parent.selectionBgImage : null;
660 Color[] colors = selected && parent.shouldHighlight() ? parent.selectionGradientColors : parent.gradientColors;
661 int[] percents = selected ? parent.selectionGradientPercents : parent.gradientPercents;
662 boolean vertical = selected ? parent.selectionGradientVertical : parent.gradientVertical;
663 Point size = parent.getSize();
665 int height = parent.tabHeight + ((parent.getStyle() & SWT.FLAT) != 0 ? 1 : 3);
668 int borderLeft = parent.borderVisible ? 1 : 0;
669 int borderTop = parent.onBottom ? borderLeft : 0;
670 int borderBottom = parent.onBottom ? 0 : borderLeft;
672 if (borderLeft > 0) {
675 int y = parent.onBottom ? size.y - borderBottom - height : borderTop;
676 drawBackground(gc, shape, x, y, width, height, defaultBackground, image, colors, percents, vertical);
679 void drawBackground(GC gc, int[] shape, int x, int y, int width, int height, Color defaultBackground, Image image, Color[] colors, int[] percents, boolean vertical) {
680 Region clipping = null, region = null;
682 clipping = new Region();
683 gc.getClipping(clipping);
684 region = new Region();
686 region.intersect(clipping);
687 gc.setClipping(region);
690 // draw the background image in shape
691 gc.setBackground(defaultBackground);
692 gc.fillRectangle(x, y, width, height);
693 Rectangle imageRect = image.getBounds();
694 gc.drawImage(image, imageRect.x, imageRect.y, imageRect.width, imageRect.height, x, y, width, height);
695 } else if (colors != null) {
697 if (colors.length == 1) {
698 Color background = colors[0] != null ? colors[0] : defaultBackground;
699 gc.setBackground(background);
700 gc.fillRectangle(x, y, width, height);
703 if (parent.onBottom) {
705 if (percents[percents.length - 1] < 100) {
706 pos = (100 - percents[percents.length - 1]) * height / 100;
707 gc.setBackground(defaultBackground);
708 gc.fillRectangle(x, y, width, pos);
710 Color lastColor = colors[colors.length-1];
711 if (lastColor == null) lastColor = defaultBackground;
712 for (int i = percents.length-1; i >= 0; i--) {
713 gc.setForeground(lastColor);
714 lastColor = colors[i];
715 if (lastColor == null) lastColor = defaultBackground;
716 gc.setBackground(lastColor);
717 int percentage = i > 0 ? percents[i] - percents[i-1] : percents[i];
718 int gradientHeight = percentage * height / 100;
719 gc.fillGradientRectangle(x, y+pos, width, gradientHeight, true);
720 pos += gradientHeight;
723 Color lastColor = colors[0];
724 if (lastColor == null) lastColor = defaultBackground;
726 for (int i = 0; i < percents.length; i++) {
727 gc.setForeground(lastColor);
728 lastColor = colors[i + 1];
729 if (lastColor == null) lastColor = defaultBackground;
730 gc.setBackground(lastColor);
731 int percentage = i > 0 ? percents[i] - percents[i-1] : percents[i];
732 int gradientHeight = percentage * height / 100;
733 gc.fillGradientRectangle(x, y+pos, width, gradientHeight, true);
734 pos += gradientHeight;
737 gc.setBackground(defaultBackground);
738 gc.fillRectangle(x, pos, width, height-pos+1);
741 } else { //horizontal gradient
743 height = parent.getSize().y;
744 Color lastColor = colors[0];
745 if (lastColor == null) lastColor = defaultBackground;
747 for (int i = 0; i < percents.length; ++i) {
748 gc.setForeground(lastColor);
749 lastColor = colors[i + 1];
750 if (lastColor == null) lastColor = defaultBackground;
751 gc.setBackground(lastColor);
752 int gradientWidth = (percents[i] * width / 100) - pos;
753 gc.fillGradientRectangle(x+pos, y, gradientWidth, height, false);
754 pos += gradientWidth;
757 gc.setBackground(defaultBackground);
758 gc.fillRectangle(x+pos, y, width-pos, height);
763 // draw a solid background using default background in shape
764 if ((parent.getStyle() & SWT.NO_BACKGROUND) != 0 || !defaultBackground.equals(parent.getBackground())) {
765 gc.setBackground(defaultBackground);
766 gc.fillRectangle(x, y, width, height);
770 gc.setClipping(clipping);
777 * Draw the border of the tab
782 void drawBorder(GC gc, int[] shape) {
784 gc.setForeground(parent.getDisplay().getSystemColor(BORDER1_COLOR));
785 gc.drawPolyline(shape);
788 void drawBody(GC gc, Rectangle bounds, int state) {
789 Point size = new Point(bounds.width, bounds.height);
790 int selectedIndex = parent.selectedIndex;
791 int tabHeight = parent.tabHeight;
793 int borderLeft = parent.borderVisible ? 1 : 0;
794 int borderRight = borderLeft;
795 int borderTop = parent.onBottom ? borderLeft : 0;
796 int borderBottom = parent.onBottom ? 0 : borderLeft;
798 int style = parent.getStyle();
799 int highlight_header = (style & SWT.FLAT) != 0 ? 1 : 3;
800 int highlight_margin = (style & SWT.FLAT) != 0 ? 0 : 2;
803 if (!parent.minimized){
804 int width = size.x - borderLeft - borderRight - 2*highlight_margin;
805 int height = size.y - borderTop - borderBottom - tabHeight - highlight_header - highlight_margin;
806 // Draw highlight margin
807 if (highlight_margin > 0) {
809 if (parent.onBottom) {
812 int x2 = size.x - borderRight;
813 int y2 = size.y - borderBottom - tabHeight - highlight_header;
814 shape = new int[] {x1,y1, x2,y1, x2,y2, x2-highlight_margin,y2,
815 x2-highlight_margin, y1+highlight_margin, x1+highlight_margin,y1+highlight_margin,
816 x1+highlight_margin,y2, x1,y2};
819 int y1 = borderTop + tabHeight + highlight_header;
820 int x2 = size.x - borderRight;
821 int y2 = size.y - borderBottom;
822 shape = new int[] {x1,y1, x1+highlight_margin,y1, x1+highlight_margin,y2-highlight_margin,
823 x2-highlight_margin,y2-highlight_margin, x2-highlight_margin,y1,
824 x2,y1, x2,y2, x1,y2};
826 // If horizontal gradient, show gradient across the whole area
827 if (selectedIndex != -1 && parent.selectionGradientColors != null && parent.selectionGradientColors.length > 1 && !parent.selectionGradientVertical) {
828 drawBackground(gc, shape, true);
829 } else if (selectedIndex == -1 && parent.gradientColors != null && parent.gradientColors.length > 1 && !parent.gradientVertical) {
830 drawBackground(gc, shape, false);
832 gc.setBackground(selectedIndex == -1 ? parent.getBackground() : parent.selectionBackground);
833 gc.fillPolygon(shape);
837 if ((parent.getStyle() & SWT.NO_BACKGROUND) != 0) {
838 gc.setBackground(parent.getBackground());
839 int marginWidth = parent.marginWidth;
840 int marginHeight = parent.marginHeight;
841 int xClient = borderLeft + marginWidth + highlight_margin, yClient;
842 if (parent.onBottom) {
843 yClient = borderTop + highlight_margin + marginHeight;
845 yClient = borderTop + tabHeight + highlight_header + marginHeight;
847 gc.fillRectangle(xClient - marginWidth, yClient - marginHeight, width, height);
850 if ((parent.getStyle() & SWT.NO_BACKGROUND) != 0) {
851 int height = borderTop + tabHeight + highlight_header + borderBottom;
852 if (size.y > height) {
853 gc.setBackground(parent.getParent().getBackground());
854 gc.fillRectangle(0, height, size.x, size.y - height);
859 //draw 1 pixel border around outside
860 if (borderLeft > 0) {
861 gc.setForeground(parent.getDisplay().getSystemColor(BORDER1_COLOR));
862 int x1 = borderLeft - 1;
863 int x2 = size.x - borderRight;
864 int y1 = parent.onBottom ? borderTop - 1 : borderTop + tabHeight;
865 int y2 = parent.onBottom ? size.y - tabHeight - borderBottom - 1 : size.y - borderBottom;
866 gc.drawLine(x1, y1, x1, y2); // left
867 gc.drawLine(x2, y1, x2, y2); // right
868 if (parent.onBottom) {
869 gc.drawLine(x1, y1, x2, y1); // top
871 gc.drawLine(x1, y2, x2, y2); // bottom
876 void drawClose(GC gc, Rectangle closeRect, int closeImageState) {
877 if (closeRect.width == 0 || closeRect.height == 0) return;
878 Display display = parent.getDisplay();
881 int x = closeRect.x + Math.max(1, (closeRect.width-9)/2);
882 int y = closeRect.y + Math.max(1, (closeRect.height-9)/2);
883 y += parent.onBottom ? -1 : 1;
885 Color closeBorder = display.getSystemColor(BUTTON_BORDER);
886 switch (closeImageState & (SWT.HOT | SWT.SELECTED | SWT.BACKGROUND)) {
888 int[] shape = new int[] {x,y, x+2,y, x+4,y+2, x+5,y+2, x+7,y, x+9,y,
889 x+9,y+2, x+7,y+4, x+7,y+5, x+9,y+7, x+9,y+9,
890 x+7,y+9, x+5,y+7, x+4,y+7, x+2,y+9, x,y+9,
891 x,y+7, x+2,y+5, x+2,y+4, x,y+2};
892 gc.setBackground(display.getSystemColor(BUTTON_FILL));
893 gc.fillPolygon(shape);
894 gc.setForeground(closeBorder);
895 gc.drawPolygon(shape);
899 int[] shape = new int[] {x,y, x+2,y, x+4,y+2, x+5,y+2, x+7,y, x+9,y,
900 x+9,y+2, x+7,y+4, x+7,y+5, x+9,y+7, x+9,y+9,
901 x+7,y+9, x+5,y+7, x+4,y+7, x+2,y+9, x,y+9,
902 x,y+7, x+2,y+5, x+2,y+4, x,y+2};
903 gc.setBackground(getFillColor());
904 gc.fillPolygon(shape);
905 gc.setForeground(closeBorder);
906 gc.drawPolygon(shape);
910 int[] shape = new int[] {x+1,y+1, x+3,y+1, x+5,y+3, x+6,y+3, x+8,y+1, x+10,y+1,
911 x+10,y+3, x+8,y+5, x+8,y+6, x+10,y+8, x+10,y+10,
912 x+8,y+10, x+6,y+8, x+5,y+8, x+3,y+10, x+1,y+10,
913 x+1,y+8, x+3,y+6, x+3,y+5, x+1,y+3};
914 gc.setBackground(getFillColor());
915 gc.fillPolygon(shape);
916 gc.setForeground(closeBorder);
917 gc.drawPolygon(shape);
920 case SWT.BACKGROUND: {
921 int[] shape = new int[] {x,y, x+10,y, x+10,y+10, x,y+10};
922 drawBackground(gc, shape, false);
928 void drawChevron(GC gc, Rectangle chevronRect, int chevronImageState) {
929 if (chevronRect.width == 0 || chevronRect.height == 0) return;
930 int selectedIndex = parent.selectedIndex;
931 // draw chevron (10x7)
932 Display display = parent.getDisplay();
933 Font font = getChevronFont(display);
934 int fontHeight = font.getFontData()[0].getHeight();
935 int indent = Math.max(2, (chevronRect.height - fontHeight - 4) /2);
936 int x = chevronRect.x + 2;
937 int y = chevronRect.y + indent;
939 int itemCount = parent.getItemCount();
941 count = selectedIndex == -1 ? itemCount : itemCount - 1;
944 while (showCount < parent.priority.length && parent.items[parent.priority[showCount]].showing) {
947 count = itemCount - showCount;
949 String chevronString = count > 99 ? CHEVRON_ELLIPSIS : String.valueOf(count);
950 switch (chevronImageState & (SWT.HOT | SWT.SELECTED)) {
952 Color chevronBorder = parent.single ? parent.getSelectionForeground() : parent.getForeground();
953 gc.setForeground(chevronBorder);
955 drawChevronContent(gc, x, y, chevronString);
959 gc.setForeground(display.getSystemColor(BUTTON_BORDER));
960 gc.setBackground(display.getSystemColor(BUTTON_FILL));
962 drawRoundRectangle(gc, chevronRect);
963 drawChevronContent(gc, x, y, chevronString);
967 gc.setForeground(display.getSystemColor(BUTTON_BORDER));
968 gc.setBackground(display.getSystemColor(BUTTON_FILL));
970 drawRoundRectangle(gc, chevronRect);
971 drawChevronContent(gc, x+1, y+1, chevronString);
977 private void drawRoundRectangle(GC gc, Rectangle chevronRect) {
978 gc.fillRoundRectangle(chevronRect.x, chevronRect.y, chevronRect.width, chevronRect.height, 6, 6);
979 gc.drawRoundRectangle(chevronRect.x, chevronRect.y, chevronRect.width - 1, chevronRect.height - 1, 6, 6);
982 private void drawChevronContent(GC gc, int x, int y, String chevronString) {
983 gc.drawLine(x,y, x+2,y+2);
984 gc.drawLine(x+2,y+2, x,y+4);
985 gc.drawLine(x+1,y, x+3,y+2);
986 gc.drawLine(x+3,y+2, x+1,y+4);
987 gc.drawLine(x+4,y, x+6,y+2);
988 gc.drawLine(x+6,y+2, x+4,y+4);
989 gc.drawLine(x+5,y, x+7,y+2);
990 gc.drawLine(x+7,y+2, x+5,y+4);
991 gc.drawString(chevronString, x+7, y+3, true);
995 * Draw a highlight effect along the left, top, and right edges of the tab.
996 * Only for curved tabs, on top.
997 * Do not draw if insufficient colors.
999 void drawHighlight(GC gc, Rectangle bounds, int state, int rightEdge) {
1000 //only draw for curvy tabs and only draw for top tabs
1001 if(parent.simple || parent.onBottom)
1004 if(selectionHighlightGradientBegin == null)
1007 Color[] gradients = selectionHighlightGradientColorsCache;
1008 if(gradients == null)
1010 int gradientsSize = gradients.length;
1011 if(gradientsSize == 0)
1012 return; //shouldn't happen but just to be tidy
1017 gc.setForeground(gradients[0]);
1019 //draw top horizontal line
1021 TOP_LEFT_CORNER_HILITE[0] + x + 1, //rely on fact that first pair is top/right of curve
1023 rightEdge - curveIndent,
1026 int[] leftHighlightCurve = TOP_LEFT_CORNER_HILITE;
1028 int d = parent.tabHeight - topCurveHighlightEnd.length /2;
1032 int lastColorIndex = 0;
1034 //draw upper left curve highlight
1035 for (int i = 0; i < leftHighlightCurve.length /2; i++) {
1036 int rawX = leftHighlightCurve[i * 2];
1037 int rawY = leftHighlightCurve[i * 2 + 1];
1040 lastColorIndex = rawY - 1;
1041 gc.setForeground(gradients[lastColorIndex]);
1042 gc.drawPoint(lastX, lastY);
1044 //draw left vertical line highlight
1045 for(int i = lastColorIndex; i < gradientsSize; i++) {
1046 gc.setForeground(gradients[i]);
1047 gc.drawPoint(lastX, 1 + lastY++);
1050 int rightEdgeOffset = rightEdge - curveIndent;
1052 //draw right swoop highlight up to diagonal portion
1053 for (int i = 0; i < topCurveHighlightStart.length /2; i++) {
1054 int rawX = topCurveHighlightStart[i * 2];
1055 int rawY = topCurveHighlightStart[i * 2 + 1];
1056 lastX = rawX + rightEdgeOffset;
1058 lastColorIndex = rawY - 1;
1059 if(lastColorIndex >= gradientsSize)
1060 break; //can happen if tabs are unusually short and cut off the curve
1061 gc.setForeground(gradients[lastColorIndex]);
1062 gc.drawPoint(lastX, lastY);
1064 //draw right diagonal line highlight
1065 for(int i = lastColorIndex; i < lastColorIndex + d; i++) {
1066 if(i >= gradientsSize)
1067 break; //can happen if tabs are unusually short and cut off the curve
1068 gc.setForeground(gradients[i]);
1069 gc.drawPoint(1 + lastX++, 1 + lastY++);
1072 //draw right swoop highlight from diagonal portion to end
1073 for (int i = 0; i < topCurveHighlightEnd.length /2; i++) {
1074 int rawX = topCurveHighlightEnd[i * 2]; //d is already encoded in this value
1075 int rawY = topCurveHighlightEnd[i * 2 + 1]; //d already encoded
1076 lastX = rawX + rightEdgeOffset;
1078 lastColorIndex = rawY - 1;
1079 if(lastColorIndex >= gradientsSize)
1080 break; //can happen if tabs are unusually short and cut off the curve
1081 gc.setForeground(gradients[lastColorIndex]);
1082 gc.drawPoint(lastX, lastY);
1087 * Draw the unselected border for the receiver on the left.
1091 void drawLeftUnselectedBorder(GC gc, Rectangle bounds, int state) {
1094 int height = bounds.height;
1097 if (parent.onBottom) {
1098 int[] left = parent.simple
1099 ? SIMPLE_UNSELECTED_INNER_CORNER
1100 : BOTTOM_LEFT_CORNER;
1102 shape = new int[left.length + 2];
1105 shape[index++] = y - 1;
1106 for (int i = 0; i < left.length / 2; i++) {
1107 shape[index++] = x + left[2 * i];
1108 shape[index++] = y + height + left[2 * i + 1] - 1;
1111 int[] left = parent.simple
1112 ? SIMPLE_UNSELECTED_INNER_CORNER
1115 shape = new int[left.length + 2];
1118 shape[index++] = y + height;
1119 for (int i = 0; i < left.length / 2; i++) {
1120 shape[index++] = x + left[2 * i];
1121 shape[index++] = y + left[2 * i + 1];
1126 drawBorder(gc, shape);
1129 void drawMaximize(GC gc, Rectangle maxRect, int maxImageState) {
1130 if (maxRect.width == 0 || maxRect.height == 0) return;
1131 Display display = parent.getDisplay();
1133 int x = maxRect.x + (maxRect.width - 10)/2;
1134 int y = maxRect.y + 3;
1136 gc.setForeground(minMaxBorderColor);
1137 gc.setBackground(display.getSystemColor(BUTTON_FILL));
1139 switch (maxImageState & (SWT.HOT | SWT.SELECTED)) {
1141 if (!parent.getMaximized()) {
1142 gc.fillRectangle(x, y, 9, 9);
1143 gc.drawRectangle(x, y, 9, 9);
1144 gc.drawLine(x, y+2, x+9, y+2);
1146 gc.fillRectangle(x, y+3, 5, 4);
1147 gc.fillRectangle(x+2, y, 5, 4);
1148 gc.drawRectangle(x, y+3, 5, 4);
1149 gc.drawRectangle(x+2, y, 5, 4);
1150 gc.drawLine(x+2, y+1, x+7, y+1);
1151 gc.drawLine(x, y+4, x+5, y+4);
1156 drawRoundRectangle(gc, maxRect);
1157 if (!parent.getMaximized()) {
1158 gc.fillRectangle(x, y, 9, 9);
1159 gc.drawRectangle(x, y, 9, 9);
1160 gc.drawLine(x, y+2, x+9, y+2);
1162 gc.fillRectangle(x, y+3, 5, 4);
1163 gc.fillRectangle(x+2, y, 5, 4);
1164 gc.drawRectangle(x, y+3, 5, 4);
1165 gc.drawRectangle(x+2, y, 5, 4);
1166 gc.drawLine(x+2, y+1, x+7, y+1);
1167 gc.drawLine(x, y+4, x+5, y+4);
1171 case SWT.SELECTED: {
1172 drawRoundRectangle(gc, maxRect);
1173 if (!parent.getMaximized()) {
1174 gc.fillRectangle(x+1, y+1, 9, 9);
1175 gc.drawRectangle(x+1, y+1, 9, 9);
1176 gc.drawLine(x+1, y+3, x+10, y+3);
1178 gc.fillRectangle(x+1, y+4, 5, 4);
1179 gc.fillRectangle(x+3, y+1, 5, 4);
1180 gc.drawRectangle(x+1, y+4, 5, 4);
1181 gc.drawRectangle(x+3, y+1, 5, 4);
1182 gc.drawLine(x+3, y+2, x+8, y+2);
1183 gc.drawLine(x+1, y+5, x+6, y+5);
1189 void drawMinimize(GC gc, Rectangle minRect, int minImageState) {
1190 if (minRect.width == 0 || minRect.height == 0) return;
1191 Display display = parent.getDisplay();
1193 int x = minRect.x + (minRect.width - 10)/2;
1194 int y = minRect.y + 3;
1196 gc.setForeground(minMaxBorderColor);
1197 gc.setBackground(display.getSystemColor(BUTTON_FILL));
1199 switch (minImageState & (SWT.HOT | SWT.SELECTED)) {
1201 if (!parent.getMinimized()) {
1202 gc.fillRectangle(x, y, 9, 3);
1203 gc.drawRectangle(x, y, 9, 3);
1205 gc.fillRectangle(x, y+3, 5, 4);
1206 gc.fillRectangle(x+2, y, 5, 4);
1207 gc.drawRectangle(x, y+3, 5, 4);
1208 gc.drawRectangle(x+2, y, 5, 4);
1209 gc.drawLine(x+3, y+1, x+6, y+1);
1210 gc.drawLine(x+1, y+4, x+4, y+4);
1215 drawRoundRectangle(gc, minRect);
1216 if (!parent.getMinimized()) {
1217 gc.fillRectangle(x, y, 9, 3);
1218 gc.drawRectangle(x, y, 9, 3);
1220 gc.fillRectangle(x, y+3, 5, 4);
1221 gc.fillRectangle(x+2, y, 5, 4);
1222 gc.drawRectangle(x, y+3, 5, 4);
1223 gc.drawRectangle(x+2, y, 5, 4);
1224 gc.drawLine(x+3, y+1, x+6, y+1);
1225 gc.drawLine(x+1, y+4, x+4, y+4);
1229 case SWT.SELECTED: {
1230 drawRoundRectangle(gc, minRect);
1231 if (!parent.getMinimized()) {
1232 gc.fillRectangle(x+1, y+1, 9, 3);
1233 gc.drawRectangle(x+1, y+1, 9, 3);
1235 gc.fillRectangle(x+1, y+4, 5, 4);
1236 gc.fillRectangle(x+3, y+1, 5, 4);
1237 gc.drawRectangle(x+1, y+4, 5, 4);
1238 gc.drawRectangle(x+3, y+1, 5, 4);
1239 gc.drawLine(x+4, y+2, x+7, y+2);
1240 gc.drawLine(x+2, y+5, x+5, y+5);
1248 * Draw the unselected border for the receiver on the right.
1252 void drawRightUnselectedBorder(GC gc, Rectangle bounds, int state) {
1255 int width = bounds.width;
1256 int height = bounds.height;
1259 int startX = x + width - 1;
1261 if (parent.onBottom) {
1262 int[] right = parent.simple
1263 ? SIMPLE_UNSELECTED_INNER_CORNER
1264 : BOTTOM_RIGHT_CORNER;
1266 shape = new int[right.length + 2];
1269 for (int i = 0; i < right.length / 2; i++) {
1270 shape[index++] = startX + right[2 * i];
1271 shape[index++] = y + height + right[2 * i + 1] - 1;
1273 shape[index++] = startX;
1274 shape[index++] = y - 1;
1276 int[] right = parent.simple
1277 ? SIMPLE_UNSELECTED_INNER_CORNER
1280 shape = new int[right.length + 2];
1283 for (int i = 0; i < right.length / 2; i++) {
1284 shape[index++] = startX + right[2 * i];
1285 shape[index++] = y + right[2 * i + 1];
1288 shape[index++] = startX;
1289 shape[index++] = y + height;
1293 drawBorder(gc, shape);
1297 void drawSelected(int itemIndex, GC gc, Rectangle bounds, int state ) {
1298 CTabItem item = parent.items[itemIndex];
1301 int height = bounds.height;
1302 int width = bounds.width;
1303 if (!parent.simple && !parent.single) width -= (curveWidth - curveIndent);
1304 int borderLeft = parent.borderVisible ? 1 : 0;
1305 int borderRight = borderLeft;
1306 int borderTop = parent.onBottom ? borderLeft : 0;
1307 int borderBottom = parent.onBottom ? 0 : borderLeft;
1309 Point size = parent.getSize();
1311 int rightEdge = Math.min (x + width, parent.getRightItemEdge(gc));
1312 // Draw selection border across all tabs
1314 if ((state & SWT.BACKGROUND) != 0) {
1315 int highlight_header = (parent.getStyle() & SWT.FLAT) != 0 ? 1 : 3;
1316 int xx = borderLeft;
1317 int yy = parent.onBottom ? size.y - borderBottom - parent.tabHeight - highlight_header : borderTop + parent.tabHeight + 1;
1318 int ww = size.x - borderLeft - borderRight;
1319 int hh = highlight_header - 1;
1320 int[] shape = new int[] {xx,yy, xx+ww,yy, xx+ww,yy+hh, xx,yy+hh};
1321 if (parent.selectionGradientColors != null && !parent.selectionGradientVertical) {
1322 drawBackground(gc, shape, parent.shouldHighlight());
1324 gc.setBackground(parent.shouldHighlight() ? parent.selectionBackground : parent.getBackground());
1325 gc.fillRectangle(xx, yy, ww, hh);
1328 if (parent.single) {
1329 if (!item.showing) return;
1331 // if selected tab scrolled out of view or partially out of view
1332 // just draw bottom line
1334 int x1 = Math.max(0, borderLeft - 1);
1335 int y1 = (parent.onBottom) ? y - 1 : y + height;
1336 int x2 = size.x - borderRight;
1337 gc.setForeground(parent.getDisplay().getSystemColor(BORDER1_COLOR));
1338 gc.drawLine(x1, y1, x2, y1);
1342 // draw selected tab background and outline
1344 if (parent.onBottom) {
1345 int[] left = parent.simple ? SIMPLE_BOTTOM_LEFT_CORNER : BOTTOM_LEFT_CORNER;
1346 int[] right = parent.simple ? SIMPLE_BOTTOM_RIGHT_CORNER : curve;
1347 if (borderLeft == 0 && itemIndex == parent.firstIndex) {
1348 left = new int[]{x, y+height};
1350 shape = new int[left.length+right.length+8];
1352 shape[index++] = x; // first point repeated here because below we reuse shape to draw outline
1353 shape[index++] = y - 1;
1355 shape[index++] = y - 1;
1356 for (int i = 0; i < left.length/2; i++) {
1357 shape[index++] = x + left[2*i];
1358 shape[index++] = y + height + left[2*i+1] - 1;
1360 for (int i = 0; i < right.length/2; i++) {
1361 shape[index++] = parent.simple ? rightEdge - 1 + right[2*i] : rightEdge - curveIndent + right[2*i];
1362 shape[index++] = parent.simple ? y + height + right[2*i+1] - 1 : y + right[2*i+1] - 2;
1364 shape[index++] = parent.simple ? rightEdge - 1 : rightEdge + curveWidth - curveIndent;
1365 shape[index++] = y - 1;
1366 shape[index++] = parent.simple ? rightEdge - 1 : rightEdge + curveWidth - curveIndent;
1367 shape[index++] = y - 1;
1369 int[] left = parent.simple ? SIMPLE_TOP_LEFT_CORNER : TOP_LEFT_CORNER;
1370 int[] right = parent.simple ? SIMPLE_TOP_RIGHT_CORNER : curve;
1371 if (borderLeft == 0 && itemIndex == parent.firstIndex) {
1372 left = new int[]{x, y};
1374 shape = new int[left.length+right.length+8];
1376 shape[index++] = x; // first point repeated here because below we reuse shape to draw outline
1377 shape[index++] = y + height + 1;
1379 shape[index++] = y + height + 1;
1380 for (int i = 0; i < left.length/2; i++) {
1381 shape[index++] = x + left[2*i];
1382 shape[index++] = y + left[2*i+1];
1384 for (int i = 0; i < right.length/2; i++) {
1385 shape[index++] = parent.simple ? rightEdge - 1 + right[2*i] : rightEdge - curveIndent + right[2*i];
1386 shape[index++] = y + right[2*i+1];
1388 shape[index++] = parent.simple ? rightEdge - 1 : rightEdge + curveWidth - curveIndent;
1389 shape[index++] = y + height + 1;
1390 shape[index++] = parent.simple ? rightEdge - 1 : rightEdge + curveWidth - curveIndent;
1391 shape[index++] = y + height + 1;
1394 Rectangle clipping = gc.getClipping();
1395 Rectangle clipBounds = item.getBounds();
1396 clipBounds.height += 1;
1397 if (parent.onBottom) clipBounds.y -= 1;
1398 boolean tabInPaint = clipping.intersects(clipBounds);
1401 // fill in tab background
1402 if (parent.selectionGradientColors != null && !parent.selectionGradientVertical) {
1403 drawBackground(gc, shape, true);
1405 Color defaultBackground = parent.shouldHighlight() ? parent.selectionBackground : parent.getBackground();
1406 Image image = parent.selectionBgImage;
1407 Color[] colors = parent.selectionGradientColors;
1408 int[] percents = parent.selectionGradientPercents;
1409 boolean vertical = parent.selectionGradientVertical;
1411 yy = parent.onBottom ? y -1 : y + 1;
1414 if (!parent.single && !parent.simple) ww += curveWidth - curveIndent;
1415 drawBackground(gc, shape, xx, yy, ww, hh, defaultBackground, image, colors, percents, vertical);
1419 //Highlight MUST be drawn before the outline so that outline can cover it in the right spots (start of swoop)
1420 //otherwise the curve looks jagged
1421 drawHighlight(gc, bounds, state, rightEdge);
1424 shape[0] = Math.max(0, borderLeft - 1);
1425 if (borderLeft == 0 && itemIndex == parent.firstIndex) {
1426 shape[1] = parent.onBottom ? y + height - 1 : y;
1427 shape[5] = shape[3] = shape[1];
1429 shape[shape.length - 2] = size.x - borderRight + 1;
1430 for (int i = 0; i < shape.length/2; i++) {
1431 if (shape[2*i + 1] == y + height + 1) shape[2*i + 1] -= 1;
1433 Color borderColor = parent.getDisplay().getSystemColor(BORDER1_COLOR);
1434 if (! borderColor.equals(lastBorderColor)) createAntialiasColors();
1435 antialias(shape, selectedInnerColor, selectedOuterColor, gc);
1436 gc.setForeground(borderColor);
1437 gc.drawPolyline(shape);
1439 if (!tabInPaint) return;
1443 if ((state & SWT.FOREGROUND) != 0) {
1445 Rectangle trim = computeTrim(itemIndex, SWT.NONE, 0, 0, 0, 0);
1446 int xDraw = x - trim.x;
1447 if (parent.single && (parent.showClose || item.showClose)) xDraw += item.closeRect.width;
1448 Image image = item.getImage();
1449 if (image != null && !image.isDisposed()) {
1450 Rectangle imageBounds = image.getBounds();
1451 // only draw image if it won't overlap with close button
1452 int maxImageWidth = rightEdge - xDraw - (trim.width + trim.x);
1453 if (!parent.single && item.closeRect.width > 0) maxImageWidth -= item.closeRect.width + INTERNAL_SPACING;
1454 if (imageBounds.width < maxImageWidth) {
1456 int imageY = y + (height - imageBounds.height) / 2;
1457 imageY += parent.onBottom ? -1 : 1;
1458 gc.drawImage(image, imageX, imageY);
1459 xDraw += imageBounds.width + INTERNAL_SPACING;
1464 int textWidth = rightEdge - xDraw - (trim.width + trim.x);
1465 if (!parent.single && item.closeRect.width > 0) textWidth -= item.closeRect.width + INTERNAL_SPACING;
1466 if (textWidth > 0) {
1467 Font gcFont = gc.getFont();
1468 gc.setFont(item.font == null ? parent.getFont() : item.font);
1470 if (item.shortenedText == null || item.shortenedTextWidth != textWidth) {
1471 item.shortenedText = shortenText(gc, item.getText(), textWidth);
1472 item.shortenedTextWidth = textWidth;
1474 Point extent = gc.textExtent(item.shortenedText, FLAGS);
1475 int textY = y + (height - extent.y) / 2;
1476 textY += parent.onBottom ? -1 : 1;
1478 gc.setForeground(parent.selectionForeground);
1479 gc.drawText(item.shortenedText, xDraw, textY, FLAGS);
1482 // draw a Focus rectangle
1483 if (parent.isFocusControl()) {
1484 Display display = parent.getDisplay();
1485 if (parent.simple || parent.single) {
1486 gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
1487 gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
1488 gc.drawFocus(xDraw-1, textY-1, extent.x+2, extent.y+2);
1490 gc.setForeground(display.getSystemColor(BUTTON_BORDER));
1491 gc.drawLine(xDraw, textY+extent.y+1, xDraw+extent.x+1, textY+extent.y+1);
1495 if (parent.showClose || item.showClose) drawClose(gc, item.closeRect, item.closeImageState);
1499 void drawTabArea(GC gc, Rectangle bounds, int state) {
1500 Point size = parent.getSize();
1502 Color borderColor = parent.getDisplay().getSystemColor(BORDER1_COLOR);
1503 int tabHeight = parent.tabHeight;
1504 int style = parent.getStyle();
1506 int borderLeft = parent.borderVisible ? 1 : 0;
1507 int borderRight = borderLeft;
1508 int borderTop = parent.onBottom ? borderLeft : 0;
1509 int borderBottom = parent.onBottom ? 0 : borderLeft;
1511 int selectedIndex = parent.selectedIndex;
1512 int highlight_header = (style & SWT.FLAT) != 0 ? 1 : 3;
1513 if (tabHeight == 0) {
1514 if ((style & SWT.FLAT) != 0 && (style & SWT.BORDER) == 0) return;
1515 int x1 = borderLeft - 1;
1516 int x2 = size.x - borderRight;
1517 int y1 = parent.onBottom ? size.y - borderBottom - highlight_header - 1 : borderTop + highlight_header;
1518 int y2 = parent.onBottom ? size.y - borderBottom : borderTop;
1519 if (borderLeft > 0 && parent.onBottom) y2 -= 1;
1521 shape = new int[] {x1, y1, x1,y2, x2,y2, x2,y1};
1523 // If horizontal gradient, show gradient across the whole area
1524 if (selectedIndex != -1 && parent.selectionGradientColors != null && parent.selectionGradientColors.length > 1 && !parent.selectionGradientVertical) {
1525 drawBackground(gc, shape, true);
1526 } else if (selectedIndex == -1 && parent.gradientColors != null && parent.gradientColors.length > 1 && !parent.gradientVertical) {
1527 drawBackground(gc, shape, false);
1529 gc.setBackground(selectedIndex == -1 ? parent.getBackground() : parent.selectionBackground);
1530 gc.fillPolygon(shape);
1533 //draw 1 pixel border
1534 if (borderLeft > 0) {
1535 gc.setForeground(borderColor);
1536 gc.drawPolyline(shape);
1541 int x = Math.max(0, borderLeft - 1);
1542 int y = parent.onBottom ? size.y - borderBottom - tabHeight : borderTop;
1543 int width = size.x - borderLeft - borderRight + 1;
1544 int height = tabHeight - 1;
1545 boolean simple = parent.simple;
1547 if (parent.onBottom) {
1549 if ((style & SWT.BORDER) != 0) {
1550 left = simple ? SIMPLE_BOTTOM_LEFT_CORNER : BOTTOM_LEFT_CORNER;
1551 right = simple ? SIMPLE_BOTTOM_RIGHT_CORNER : BOTTOM_RIGHT_CORNER;
1553 left = simple ? SIMPLE_BOTTOM_LEFT_CORNER_BORDERLESS : BOTTOM_LEFT_CORNER_BORDERLESS;
1554 right = simple ? SIMPLE_BOTTOM_RIGHT_CORNER_BORDERLESS : BOTTOM_RIGHT_CORNER_BORDERLESS;
1556 shape = new int[left.length + right.length + 4];
1559 shape[index++] = y-highlight_header;
1560 for (int i = 0; i < left.length/2; i++) {
1561 shape[index++] = x+left[2*i];
1562 shape[index++] = y+height+left[2*i+1];
1563 if (borderLeft == 0) shape[index-1] += 1;
1565 for (int i = 0; i < right.length/2; i++) {
1566 shape[index++] = x+width+right[2*i];
1567 shape[index++] = y+height+right[2*i+1];
1568 if (borderLeft == 0) shape[index-1] += 1;
1570 shape[index++] = x+width;
1571 shape[index++] = y-highlight_header;
1574 if ((style & SWT.BORDER) != 0) {
1575 left = simple ? SIMPLE_TOP_LEFT_CORNER : TOP_LEFT_CORNER;
1576 right = simple ? SIMPLE_TOP_RIGHT_CORNER : TOP_RIGHT_CORNER;
1578 left = simple ? SIMPLE_TOP_LEFT_CORNER_BORDERLESS : TOP_LEFT_CORNER_BORDERLESS;
1579 right = simple ? SIMPLE_TOP_RIGHT_CORNER_BORDERLESS : TOP_RIGHT_CORNER_BORDERLESS;
1581 shape = new int[left.length + right.length + 4];
1584 shape[index++] = y+height+highlight_header + 1;
1585 for (int i = 0; i < left.length/2; i++) {
1586 shape[index++] = x+left[2*i];
1587 shape[index++] = y+left[2*i+1];
1589 for (int i = 0; i < right.length/2; i++) {
1590 shape[index++] = x+width+right[2*i];
1591 shape[index++] = y+right[2*i+1];
1593 shape[index++] = x+width;
1594 shape[index++] = y+height+highlight_header + 1;
1596 // Fill in background
1597 boolean single = parent.single;
1598 boolean bkSelected = single && selectedIndex != -1;
1599 drawBackground(gc, shape, bkSelected);
1600 // Fill in parent background for non-rectangular shape
1601 Region r = new Region();
1602 r.add(new Rectangle(x, y, width + 1, height + 1));
1604 gc.setBackground(parent.getParent().getBackground());
1608 // Draw selected tab
1609 if (selectedIndex == -1) {
1610 // if no selected tab - draw line across bottom of all tabs
1611 int x1 = borderLeft;
1612 int y1 = (parent.onBottom) ? size.y - borderBottom - tabHeight - 1 : borderTop + tabHeight;
1613 int x2 = size.x - borderRight;
1614 gc.setForeground(borderColor);
1615 gc.drawLine(x1, y1, x2, y1);
1619 if (borderLeft > 0) {
1620 if (! borderColor.equals(lastBorderColor)) createAntialiasColors();
1621 antialias(shape, null, tabAreaColor, gc);
1622 gc.setForeground(borderColor);
1623 gc.drawPolyline(shape);
1627 void drawUnselected(int index, GC gc, Rectangle bounds, int state) {
1628 CTabItem item = parent.items[index];
1631 int height = bounds.height;
1632 int width = bounds.width;
1634 // Do not draw partial items
1635 if (!item.showing) return;
1637 Rectangle clipping = gc.getClipping();
1638 if (!clipping.intersects(bounds)) return;
1640 if ((state & SWT.BACKGROUND) != 0) {
1641 if (index > 0 && index < parent.selectedIndex)
1642 drawLeftUnselectedBorder(gc, bounds, state);
1643 // If it is the last one then draw a line
1644 if (index > parent.selectedIndex)
1645 drawRightUnselectedBorder(gc, bounds, state);
1648 if ((state & SWT.FOREGROUND) != 0) {
1650 Rectangle trim = computeTrim(index, SWT.NONE, 0, 0, 0, 0);
1651 int xDraw = x - trim.x;
1652 Image image = item.getImage();
1653 if (image != null && !image.isDisposed() && parent.showUnselectedImage) {
1654 Rectangle imageBounds = image.getBounds();
1655 // only draw image if it won't overlap with close button
1656 int maxImageWidth = x + width - xDraw - (trim.width + trim.x);
1657 if (parent.showUnselectedClose && (parent.showClose || item.showClose)) {
1658 maxImageWidth -= item.closeRect.width + INTERNAL_SPACING;
1660 if (imageBounds.width < maxImageWidth) {
1662 int imageHeight = imageBounds.height;
1663 int imageY = y + (height - imageHeight) / 2;
1664 imageY += parent.onBottom ? -1 : 1;
1665 int imageWidth = imageBounds.width * imageHeight / imageBounds.height;
1667 imageBounds.x, imageBounds.y, imageBounds.width, imageBounds.height,
1668 imageX, imageY, imageWidth, imageHeight);
1669 xDraw += imageWidth + INTERNAL_SPACING;
1673 int textWidth = x + width - xDraw - (trim.width + trim.x);
1674 if (parent.showUnselectedClose && (parent.showClose || item.showClose)) {
1675 textWidth -= item.closeRect.width + INTERNAL_SPACING;
1677 if (textWidth > 0) {
1678 Font gcFont = gc.getFont();
1679 gc.setFont(item.font == null ? parent.getFont() : item.font);
1680 if (item.shortenedText == null || item.shortenedTextWidth != textWidth) {
1681 item.shortenedText = shortenText(gc, item.getText(), textWidth);
1682 item.shortenedTextWidth = textWidth;
1684 Point extent = gc.textExtent(item.shortenedText, FLAGS);
1685 int textY = y + (height - extent.y) / 2;
1686 textY += parent.onBottom ? -1 : 1;
1687 gc.setForeground(parent.getForeground());
1688 gc.drawText(item.shortenedText, xDraw, textY, FLAGS);
1692 if (parent.showUnselectedClose && (parent.showClose || item.showClose)) drawClose(gc, item.closeRect, item.closeImageState);
1696 void fillRegion(GC gc, Region region) {
1697 // NOTE: region passed in to this function will be modified
1698 Region clipping = new Region();
1699 gc.getClipping(clipping);
1700 region.intersect(clipping);
1701 gc.setClipping(region);
1702 gc.fillRectangle(region.getBounds());
1703 gc.setClipping(clipping);
1707 Color getFillColor() {
1708 if (fillColor == null) {
1709 fillColor = new Color(parent.getDisplay(), CLOSE_FILL);
1714 private Font getChevronFont(Display display) {
1715 if (chevronFont == null) {
1716 Point dpi = display.getDPI();
1717 int fontHeight = 72 * CHEVRON_FONT_HEIGHT / dpi.y;
1718 FontData fd = parent.getFont().getFontData()[0];
1719 fd.setHeight(fontHeight);
1720 chevronFont = new Font(display, fd);
1726 * Return true if given start color, the cache of highlight colors we have
1727 * would match the highlight colors we'd compute.
1729 boolean isSelectionHighlightColorsCacheHit(Color start) {
1731 if(selectionHighlightGradientColorsCache == null)
1734 //this case should never happen but check to be safe before accessing array indexes
1735 if(selectionHighlightGradientColorsCache.length < 2)
1738 Color highlightBegin = selectionHighlightGradientColorsCache[0];
1739 Color highlightEnd = selectionHighlightGradientColorsCache[selectionHighlightGradientColorsCache.length - 1];
1741 if(! highlightBegin.equals(start))
1744 //Compare number of colours we have vs. we'd compute
1745 if(selectionHighlightGradientColorsCache.length != parent.tabHeight)
1748 //Compare existing highlight end to what it would be (selectionBackground)
1749 if(! highlightEnd.equals(parent.selectionBackground))
1755 void setSelectionHighlightGradientColor(Color start) {
1757 //Set to null to match all the early return cases.
1758 //For early returns, don't realloc the cache, we may get a cache hit next time we're given the highlight
1759 selectionHighlightGradientBegin = null;
1764 //don't bother on low colour
1765 if (parent.getDisplay().getDepth() < 15)
1768 //don't bother if we don't have a background gradient
1769 if(parent.selectionGradientColors.length < 2)
1772 //OK we know its a valid gradient now
1773 selectionHighlightGradientBegin = start;
1775 if(! isSelectionHighlightColorsCacheHit(start))
1776 createSelectionHighlightGradientColors(start); //if no cache hit then compute new ones
1779 String shortenText(GC gc, String text, int width) {
1780 return useEllipses()
1781 ? shortenText(gc, text, width, ELLIPSIS)
1782 : shortenText(gc, text, width, ""); //$NON-NLS-1$
1785 String shortenText(GC gc, String text, int width, String ellipses) {
1786 if (gc.textExtent(text, FLAGS).x <= width) return text;
1787 int ellipseWidth = gc.textExtent(ellipses, FLAGS).x;
1788 int length = text.length();
1789 TextLayout layout = new TextLayout(parent.getDisplay());
1790 layout.setText(text);
1791 int end = layout.getPreviousOffset(length, SWT.MOVEMENT_CLUSTER);
1793 text = text.substring(0, end);
1794 int l = gc.textExtent(text, FLAGS).x;
1795 if (l + ellipseWidth <= width) {
1798 end = layout.getPreviousOffset(end, SWT.MOVEMENT_CLUSTER);
1801 return end == 0 ? text.substring(0, 1) : text + ellipses;
1804 void updateCurves () {
1805 //Temp fix for Bug 384743
1806 if (this.getClass().getName().equals("org.eclipse.e4.ui.workbench.renderers.swt.CTabRendering")) return;
1807 int tabHeight = parent.tabHeight;
1808 if (tabHeight == lastTabHeight) return;
1809 if (parent.onBottom) {
1810 int d = tabHeight - 12;
1811 curve = new int[]{0,13+d, 0,12+d, 2,12+d, 3,11+d, 5,11+d, 6,10+d, 7,10+d, 9,8+d, 10,8+d,
1813 12+d,6, 13+d,6, 15+d,4, 16+d,4, 17+d,3, 19+d,3, 20+d,2, 22+d,2, 23+d,1};
1815 curveIndent = curveWidth/3;
1817 int d = tabHeight - 12;
1818 curve = new int[]{0,0, 0,1, 2,1, 3,2, 5,2, 6,3, 7,3, 9,5, 10,5,
1820 12+d,7+d, 13+d,7+d, 15+d,9+d, 16+d,9+d, 17+d,10+d, 19+d,10+d, 20+d,11+d, 22+d,11+d, 23+d,12+d};
1822 curveIndent = curveWidth/3;
1824 //this could be static but since values depend on curve, better to keep in one place
1825 topCurveHighlightStart = new int[] {
1832 //also, by adding in 'd' here we save some math cost when drawing the curve
1833 topCurveHighlightEnd = new int[] {
1836 12+d, 8+d, 13+d, 8+d,
1838 15+d, 10+d, 16+d, 10+d,
1839 17+d, 11+d, 18+d, 11+d, 19+d, 11+d,
1840 20+d, 12+d, 21+d, 12+d, 22+d, 12+d };
1845 * Return whether to use ellipses or just truncate labels
1847 boolean useEllipses() {
1848 return parent.simple;