root/dwt/custom/CTabFolder.d

Revision 315:349b8c12e243, 150.0 kB (checked in by Frank Benoit <benoit@tionex.de>, 2 months ago)

Sync dwt/custom with dwt-linux

Line 
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 dwt.custom.CTabFolder;
14
15 import dwt.DWT;
16 import dwt.DWTException;
17 import dwt.accessibility.ACC;
18 import dwt.accessibility.Accessible;
19 import dwt.accessibility.AccessibleAdapter;
20 import dwt.accessibility.AccessibleControlAdapter;
21 import dwt.accessibility.AccessibleControlEvent;
22 import dwt.accessibility.AccessibleEvent;
23 import dwt.events.SelectionAdapter;
24 import dwt.events.SelectionEvent;
25 import dwt.events.SelectionListener;
26 import dwt.graphics.Color;
27 import dwt.graphics.Font;
28 import dwt.graphics.FontData;
29 import dwt.graphics.GC;
30 import dwt.graphics.Image;
31 import dwt.graphics.Point;
32 import dwt.graphics.RGB;
33 import dwt.graphics.Rectangle;
34 import dwt.graphics.Region;
35 import dwt.widgets.Composite;
36 import dwt.widgets.Control;
37 import dwt.widgets.Display;
38 import dwt.widgets.Event;
39 import dwt.widgets.Layout;
40 import dwt.widgets.Listener;
41 import dwt.widgets.Menu;
42 import dwt.widgets.MenuItem;
43 import dwt.widgets.TypedListener;
44 import dwt.custom.CTabItem;
45 import dwt.custom.CTabFolder2Listener;
46 import dwt.custom.CTabFolderListener;
47 import dwt.custom.CTabFolderLayout;
48 import dwt.custom.CTabFolderEvent;
49
50 import dwt.dwthelper.utils;
51 import tango.util.Convert;
52 static import tango.text.convert.Utf;
53
54 /**
55  *
56  * Instances of this class implement the notebook user interface
57  * metaphor.  It allows the user to select a notebook page from
58  * set of pages.
59  * <p>
60  * The item children that may be added to instances of this class
61  * must be of type <code>CTabItem</code>.
62  * <code>Control</code> children are created and then set into a
63  * tab item using <code>CTabItem#setControl</code>.
64  * </p><p>
65  * Note that although this class is a subclass of <code>Composite</code>,
66  * it does not make sense to set a layout on it.
67  * </p><p>
68  * <dl>
69  * <dt><b>Styles:</b></dt>
70  * <dd>CLOSE, TOP, BOTTOM, FLAT, BORDER, SINGLE, MULTI</dd>
71  * <dt><b>Events:</b></dt>
72  * <dd>Selection</dd>
73  * <dd>"CTabFolder2"</dd>
74  * </dl>
75  * <p>
76  * Note: Only one of the styles TOP and BOTTOM
77  * may be specified.
78  * </p><p>
79  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
80  * </p>
81  *
82  * @see <a href="http://www.eclipse.org/swt/snippets/#ctabfolder">CTabFolder, CTabItem snippets</a>
83  * @see <a href="http://www.eclipse.org/swt/examples.php">DWT Example: CustomControlExample</a>
84  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
85  */
86
87 public class CTabFolder : Composite {
88
89     /**
90      * marginWidth specifies the number of pixels of horizontal margin
91      * that will be placed along the left and right edges of the form.
92      *
93      * The default value is 0.
94      */
95     public int marginWidth = 0;
96     /**
97      * marginHeight specifies the number of pixels of vertical margin
98      * that will be placed along the top and bottom edges of the form.
99      *
100      * The default value is 0.
101      */
102     public int marginHeight = 0;
103
104     /**
105      * A multiple of the tab height that specifies the minimum width to which a tab
106      * will be compressed before scrolling arrows are used to navigate the tabs.
107      *
108      * NOTE This field is badly named and can not be fixed for backwards compatibility.
109      * It should not be capitalized.
110      *
111      * @deprecated This field is no longer used.  See setMinimumCharacters(int)
112      */
113     public int MIN_TAB_WIDTH = 4;
114
115     /**
116      * Color of innermost line of drop shadow border.
117      *
118      * NOTE This field is badly named and can not be fixed for backwards compatibility.
119      * It should be capitalized.
120      *
121      * @deprecated drop shadow border is no longer drawn in 3.0
122      */
123     public static RGB borderInsideRGB;
124     /**
125      * Color of middle line of drop shadow border.
126      *
127      * NOTE This field is badly named and can not be fixed for backwards compatibility.
128      * It should be capitalized.
129      *
130      * @deprecated drop shadow border is no longer drawn in 3.0
131      */
132     public static RGB borderMiddleRGB;
133     /**
134      * Color of outermost line of drop shadow border.
135      *
136      * NOTE This field is badly named and can not be fixed for backwards compatibility.
137      * It should be capitalized.
138      *
139      * @deprecated drop shadow border is no longer drawn in 3.0
140      */
141     public static RGB borderOutsideRGB;
142
143     /* sizing, positioning */
144     int xClient, yClient;
145     bool onBottom = false;
146     bool single = false;
147     bool simple = true;
148     int fixedTabHeight = DWT.DEFAULT;
149     int tabHeight;
150     int minChars = 20;
151
152     /* item management */
153     CTabItem items[];
154     int firstIndex = -1; // index of the left most visible tab.
155     int selectedIndex = -1;
156     int[] priority;
157     bool mru = false;
158     Listener listener;
159
160     /* External Listener management */
161     CTabFolder2Listener[] folderListeners;
162     // support for deprecated listener mechanism
163     CTabFolderListener[] tabListeners;
164
165     /* Selected item appearance */
166     Image selectionBgImage;
167     Color[] selectionGradientColors;
168     int[] selectionGradientPercents;
169     bool selectionGradientVertical;
170     Color selectionForeground;
171     Color selectionBackground;  //selection fade end
172     Color selectionFadeStart;
173
174     Color selectionHighlightGradientBegin = null;  //null is no highlight
175     //Although we are given new colours all the time to show different states (active, etc),
176     //some of which may have a highlight and some not, we'd like to retain the highlight colours
177     //as a cache so that we can reuse them if we're again told to show the highlight.
178     //We are relying on the fact that only one tab state usually gets a highlight, so only
179     //a single cache is required. If that happens to not be true, cache simply becomes less effective,
180     //but we don't leak colours.
181     Color[] selectionHighlightGradientColorsCache = null;  //null is a legal value, check on access
182
183     /* Unselected item appearance */
184     Image bgImage;
185     Color[] gradientColors;
186     int[] gradientPercents;
187     bool gradientVertical;
188     bool showUnselectedImage = true;
189
190     static Color borderColor;
191
192     // close, min/max and chevron buttons
193     bool showClose = false;
194     bool showUnselectedClose = true;
195
196     Rectangle chevronRect;
197     int chevronImageState = NORMAL;
198     bool showChevron = false;
199     Menu showMenu;
200
201     bool showMin = false;
202     Rectangle minRect;
203     bool minimized = false;
204     int minImageState = NORMAL;
205
206     bool showMax = false;
207     Rectangle maxRect;
208     bool maximized = false;
209     int maxImageState = NORMAL;
210
211     Control topRight;
212     Rectangle topRightRect;
213     int topRightAlignment = DWT.RIGHT;
214
215     // borders and shapes
216     int borderLeft = 0;
217     int borderRight = 0;
218     int borderTop = 0;
219     int borderBottom = 0;
220
221     int highlight_margin = 0;
222     int highlight_header = 0;
223
224     int[] curve;
225     int[] topCurveHighlightStart;
226     int[] topCurveHighlightEnd;
227     int curveWidth = 0;
228     int curveIndent = 0;
229
230     // when disposing CTabFolder, don't try to layout the items or
231     // change the selection as each child is destroyed.
232     bool inDispose = false;
233
234     // keep track of size changes in order to redraw only affected area
235     // on Resize
236     Point oldSize;
237     Font oldFont;
238
239     // internal constants
240     static const int DEFAULT_WIDTH = 64;
241     static const int DEFAULT_HEIGHT = 64;
242     static const int BUTTON_SIZE = 18;
243
244     static const int[] TOP_LEFT_CORNER = [0,6, 1,5, 1,4, 4,1, 5,1, 6,0];
245
246     //TOP_LEFT_CORNER_HILITE is laid out in reverse (ie. top to bottom)
247     //so can fade in same direction as right swoop curve
248     static const int[] TOP_LEFT_CORNER_HILITE = [5,2, 4,2, 3,3, 2,4, 2,5, 1,6];
249
250     static const int[] TOP_RIGHT_CORNER = [-6,0, -5,1, -4,1, -1,4, -1,5, 0,6];
251     static const int[] BOTTOM_LEFT_CORNER = [0,-6, 1,-5, 1,-4, 4,-1, 5,-1, 6,0];
252     static const int[] BOTTOM_RIGHT_CORNER = [-6,0, -5,-1, -4,-1, -1,-4, -1,-5, 0,-6];
253
254     static const int[] SIMPLE_TOP_LEFT_CORNER = [0,2, 1,1, 2,0];
255     static const int[] SIMPLE_TOP_RIGHT_CORNER = [-2,0, -1,1, 0,2];
256     static const int[] SIMPLE_BOTTOM_LEFT_CORNER = [0,-2, 1,-1, 2,0];
257     static const int[] SIMPLE_BOTTOM_RIGHT_CORNER = [-2,0, -1,-1, 0,-2];
258     static const int[] SIMPLE_UNSELECTED_INNER_CORNER = [0,0];
259
260     static const int[] TOP_LEFT_CORNER_BORDERLESS = [0,6, 1,5, 1,4, 4,1, 5,1, 6,0];
261     static const int[] TOP_RIGHT_CORNER_BORDERLESS = [-7,0, -6,1, -5,1, -2,4, -2,5, -1,6];
262     static const int[] BOTTOM_LEFT_CORNER_BORDERLESS = [0,-6, 1,-6, 1,-5, 2,-4, 4,-2, 5,-1, 6,-1, 6,0];
263     static const int[] BOTTOM_RIGHT_CORNER_BORDERLESS = [-7,0, -7,-1, -6,-1, -5,-2, -3,-4, -2,-5, -2,-6, -1,-6];
264
265     static const int[] SIMPLE_TOP_LEFT_CORNER_BORDERLESS = [0,2, 1,1, 2,0];
266     static const int[] SIMPLE_TOP_RIGHT_CORNER_BORDERLESS= [-3,0, -2,1, -1,2];
267     static const int[] SIMPLE_BOTTOM_LEFT_CORNER_BORDERLESS = [0,-3, 1,-2, 2,-1, 3,0];
268     static const int[] SIMPLE_BOTTOM_RIGHT_CORNER_BORDERLESS = [-4,0, -3,-1, -2,-2, -1,-3];
269
270     static const int SELECTION_FOREGROUND = DWT.COLOR_LIST_FOREGROUND;
271     static const int SELECTION_BACKGROUND = DWT.COLOR_LIST_BACKGROUND;
272     static const int BORDER1_COLOR = DWT.COLOR_WIDGET_NORMAL_SHADOW;
273     static const int FOREGROUND = DWT.COLOR_WIDGET_FOREGROUND;
274     static const int BACKGROUND = DWT.COLOR_WIDGET_BACKGROUND;
275     static const int BUTTON_BORDER = DWT.COLOR_WIDGET_DARK_SHADOW;
276     static const int BUTTON_FILL = DWT.COLOR_LIST_BACKGROUND;
277
278     static const int NONE = 0;
279     static const int NORMAL = 1;
280     static const int HOT = 2;
281     static const int SELECTED = 3;
282     static const RGB CLOSE_FILL;
283
284     static const int CHEVRON_CHILD_ID = 0;
285     static const int MINIMIZE_CHILD_ID = 1;
286     static const int MAXIMIZE_CHILD_ID = 2;
287     static const int EXTRA_CHILD_ID_COUNT = 3;
288
289 static this(){
290     borderInsideRGB  = new RGB (132, 130, 132);
291     borderMiddleRGB  = new RGB (143, 141, 138);
292     borderOutsideRGB = new RGB (171, 168, 165);
293     CLOSE_FILL = new RGB(252, 160, 160);
294 }
295
296 /**
297  * Constructs a new instance of this class given its parent
298  * and a style value describing its behavior and appearance.
299  * <p>
300  * The style value is either one of the style constants defined in
301  * class <code>DWT</code> which is applicable to instances of this
302  * class, or must be built by <em>bitwise OR</em>'ing together
303  * (that is, using the <code>int</code> "|" operator) two or more
304  * of those <code>DWT</code> style constants. The class description
305  * lists the style constants that are applicable to the class.
306  * Style bits are also inherited from superclasses.
307  * </p>
308  *
309  * @param parent a widget which will be the parent of the new instance (cannot be null)
310  * @param style the style of widget to construct
311  *
312  * @exception IllegalArgumentException <ul>
313  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
314  * </ul>
315  * @exception DWTException <ul>
316  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
317  * </ul>
318  *
319  * @see DWT#TOP
320  * @see DWT#BOTTOM
321  * @see DWT#FLAT
322  * @see DWT#BORDER
323  * @see DWT#SINGLE
324  * @see DWT#MULTI
325  * @see #getStyle()
326  */
327 public this(Composite parent, int style) {
328     chevronRect = new Rectangle(0, 0, 0, 0);
329     minRect = new Rectangle(0, 0, 0, 0);
330     maxRect = new Rectangle(0, 0, 0, 0);
331     topRightRect = new Rectangle(0, 0, 0, 0);
332     super(parent, checkStyle (parent, style));
333     super.setLayout(new CTabFolderLayout());
334     int style2 = super.getStyle();
335     oldFont = getFont();
336     onBottom = (style2 & DWT.BOTTOM) !is 0;
337     showClose = (style2 & DWT.CLOSE) !is 0;
338 //  showMin = (style2 & DWT.MIN) !is 0; - conflicts with DWT.TOP
339 //  showMax = (style2 & DWT.MAX) !is 0; - conflicts with DWT.BOTTOM
340     single = (style2 & DWT.SINGLE) !is 0;
341     borderLeft = borderRight = (style & DWT.BORDER) !is 0 ? 1 : 0;
342     borderTop = onBottom ? borderLeft : 0;
343     borderBottom = onBottom ? 0 : borderLeft;
344     highlight_header = (style & DWT.FLAT) !is 0 ? 1 : 3;
345     highlight_margin = (style & DWT.FLAT) !is 0 ? 0 : 2;
346     //set up default colors
347     Display display = getDisplay();
348     selectionForeground = display.getSystemColor(SELECTION_FOREGROUND);
349     selectionBackground = display.getSystemColor(SELECTION_BACKGROUND);
350     borderColor = display.getSystemColor(BORDER1_COLOR);
351     updateTabHeight(false);
352
353     initAccessible();
354
355     // Add all listeners
356     listener = new class() Listener {
357         public void handleEvent(Event event) {
358             switch (event.type) {
359                 case DWT.Dispose:          onDispose(event); break;
360                 case DWT.DragDetect:       onDragDetect(event); break;
361                 case DWT.FocusIn:          onFocus(event);  break;
362                 case DWT.FocusOut:         onFocus(event);  break;
363                 case DWT.KeyDown:          onKeyDown(event); break;
364                 case DWT.MouseDoubleClick: onMouseDoubleClick(event); break;
365                 case DWT.MouseDown:        onMouse(event);  break;
366                 case DWT.MouseEnter:       onMouse(event);  break;
367                 case DWT.MouseExit:        onMouse(event);  break;
368                 case DWT.MouseMove:        onMouse(event); break;
369                 case DWT.MouseUp:          onMouse(event); break;
370                 case DWT.Paint:            onPaint(event);  break;
371                 case DWT.Resize:           onResize();  break;
372                 case DWT.Traverse:         onTraverse(event); break;
373                 default:
374             }
375         }
376     };
377
378     int[] folderEvents = [
379         DWT.Dispose,
380         DWT.DragDetect,
381         DWT.FocusIn,
382         DWT.FocusOut,
383         DWT.KeyDown,
384         DWT.MouseDoubleClick,
385         DWT.MouseDown,
386         DWT.MouseEnter,
387         DWT.MouseExit,
388         DWT.MouseMove,
389         DWT.MouseUp,
390         DWT.Paint,
391         DWT.Resize,
392         DWT.Traverse,
393     ];
394     for (int i = 0; i < folderEvents.length; i++) {
395         addListener(folderEvents[i], listener);
396     }
397 }
398 static int checkStyle (Composite parent, int style) {
399     int mask = DWT.CLOSE | DWT.TOP | DWT.BOTTOM | DWT.FLAT | DWT.LEFT_TO_RIGHT | DWT.RIGHT_TO_LEFT | DWT.SINGLE | DWT.MULTI;
400     style = style & mask;
401     // TOP and BOTTOM are mutually exclusive.
402     // TOP is the default
403     if ((style & DWT.TOP) !is 0) style = style & ~DWT.BOTTOM;
404     // SINGLE and MULTI are mutually exclusive.
405     // MULTI is the default
406     if ((style & DWT.MULTI) !is 0) style = style & ~DWT.SINGLE;
407     // reduce the flash by not redrawing the entire area on a Resize event
408     style |= DWT.NO_REDRAW_RESIZE;
409     //TEMPORARY CODE
410     /*
411      * The default background on carbon and some GTK themes is not a solid color
412      * but a texture.  To show the correct default background, we must allow
413      * the operating system to draw it and therefore, we can not use the
414      * NO_BACKGROUND style.  The NO_BACKGROUND style is not required on platforms
415      * that use double buffering which is true in both of these cases.
416      */
417     String platform = DWT.getPlatform();
418     if ("carbon"==platform || "gtk"==platform) return style; //$NON-NLS-1$ //$NON-NLS-2$
419
420     //TEMPORARY CODE
421     /*
422      * In Right To Left orientation on Windows, all GC calls that use a brush are drawing
423      * offset by one pixel.  This results in some parts of the CTabFolder not drawing correctly.
424      * To alleviate some of the appearance problems, allow the OS to draw the background.
425      * This does not draw correctly but the result is less obviously wrong.
426      */
427     if ((style & DWT.RIGHT_TO_LEFT) !is 0) return style;
428     if ((parent.getStyle() & DWT.MIRRORED) !is 0 && (style & DWT.LEFT_TO_RIGHT) is 0) return style;
429
430     return style | DWT.NO_BACKGROUND;
431 }
432 static void fillRegion(GC gc, Region region) {
433     // NOTE: region passed in to this function will be modified
434     Region clipping = new Region();
435     gc.getClipping(clipping);
436     region.intersect(clipping);
437     gc.setClipping(region);
438     gc.fillRectangle(region.getBounds());
439     gc.setClipping(clipping);
440     clipping.dispose();
441 }
442 /**
443  *
444  * Adds the listener to the collection of listeners who will
445  * be notified when a tab item is closed, minimized, maximized,
446  * restored, or to show the list of items that are not
447  * currently visible.
448  *
449  * @param listener the listener which should be notified
450  *
451  * @exception IllegalArgumentException <ul>
452  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
453  * </ul>
454  *
455  * @exception DWTException <ul>
456  *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
457  *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
458  * </ul>
459  *
460  * @see CTabFolder2Listener
461  * @see #removeCTabFolder2Listener(CTabFolder2Listener)
462  *
463  * @since 3.0
464  */
465 public void addCTabFolder2Listener(CTabFolder2Listener listener) {
466     checkWidget();
467     if (listener is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
468     // add to array
469     CTabFolder2Listener[] newListeners = new CTabFolder2Listener[folderListeners.length + 1];
470     SimpleType!(CTabFolder2Listener).arraycopy(folderListeners, 0, newListeners, 0, folderListeners.length);
471     folderListeners = newListeners;
472     folderListeners[folderListeners.length - 1] = listener;
473 }
474 /**
475  * Adds the listener to the collection of listeners who will
476  * be notified when a tab item is closed.
477  *
478  * @param listener the listener which should be notified
479  *
480  * @exception IllegalArgumentException <ul>
481  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
482  * </ul>
483  * @exception DWTException <ul>
484  *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
485  *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
486  * </ul>
487  *
488  * @see CTabFolderListener
489  * @see #removeCTabFolderListener(CTabFolderListener)
490  *
491  * @deprecated use addCTabFolder2Listener(CTabFolder2Listener)
492  */
493 public void addCTabFolderListener(CTabFolderListener listener) {
494     checkWidget();
495     if (listener is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
496     // add to array
497     CTabFolderListener[] newTabListeners = new CTabFolderListener[tabListeners.length + 1];
498     SimpleType!(CTabFolderListener).arraycopy(tabListeners, 0, newTabListeners, 0, tabListeners.length);
499     tabListeners = newTabListeners;
500     tabListeners[tabListeners.length - 1] = listener;
501     // display close button to be backwards compatible
502     if (!showClose) {
503         showClose = true;
504         updateItems();
505         redraw();
506     }
507 }
508 /**
509  * Adds the listener to the collection of listeners who will
510  * be notified when the user changes the receiver's selection, by sending
511  * it one of the messages defined in the <code>SelectionListener</code>
512  * interface.
513  * <p>
514  * <code>widgetSelected</code> is called when the user changes the selected tab.
515  * <code>widgetDefaultSelected</code> is not called.
516  * </p>
517  *
518  * @param listener the listener which should be notified when the user changes the receiver's selection
519  *
520  * @exception IllegalArgumentException <ul>
521  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
522  * </ul>
523  * @exception DWTException <ul>
524  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
525  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
526  * </ul>
527  *
528  * @see SelectionListener
529  * @see #removeSelectionListener
530  * @see SelectionEvent
531  */
532 public void addSelectionListener(SelectionListener listener) {
533     checkWidget();
534     if (listener is null) {
535         DWT.error(DWT.ERROR_NULL_ARGUMENT);
536     }
537     TypedListener typedListener = new TypedListener(listener);
538     addListener(DWT.Selection, typedListener);
539     addListener(DWT.DefaultSelection, typedListener);
540 }
541 void antialias (int[] shape, RGB lineRGB, RGB innerRGB, RGB outerRGB, GC gc){
542     // Don't perform anti-aliasing on Mac and WPF because the platform
543     // already does it.  The simple style also does not require anti-aliasing.
544     if (simple || "carbon".equals(DWT.getPlatform()) || "wpf".equals(DWT.getPlatform())) return; //$NON-NLS-1$
545     // Don't perform anti-aliasing on low resolution displays
546     if (getDisplay().getDepth() < 15) return;
547     if (outerRGB !is null) {
548         int index = 0;
549         bool left = true;
550         int oldY = onBottom ? 0 : getSize().y;
551         int[] outer = new int[shape.length];
552         for (int i = 0; i < shape.length/2; i++) {
553             if (left && (index + 3 < shape.length)) {
554                 left = onBottom ? oldY <= shape[index+3] : oldY >= shape[index+3];
555                 oldY = shape[index+1];
556             }
557             outer[index] = shape[index] + (left ? -1 : +1);
558             index++;
559             outer[index] = shape[index];
560             index++;
561         }
562         RGB from = lineRGB;
563         RGB to = outerRGB;
564         int red = from.red + 2*(to.red - from.red)/3;
565         int green = from.green + 2*(to.green - from.green)/3;
566         int blue = from.blue + 2*(to.blue - from.blue)/3;
567         Color color = new Color(getDisplay(), red, green, blue);
568         gc.setForeground(color);