1 /*******************************************************************************
2  * Copyright (c) 2000, 2016 IBM Corporation and others.
3  *
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/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.swt.custom;
15 
16 
17 import org.eclipse.swt.*;
18 import org.eclipse.swt.graphics.*;
19 import org.eclipse.swt.widgets.*;
20 
21 /**
22  * Instances of this class implement a Composite that lays out its
23  * children and allows programmatic control of the layout. It draws
24  * a separator between the left and right children which can be dragged
25  * to resize the right control.
26  * CBanner is used in the workbench to layout the toolbar area and
27  * perspective switching toolbar.
28  * <p>
29  * Note that although this class is a subclass of <code>Composite</code>,
30  * it does not make sense to set a layout on it.
31  * </p>
32  * <dl>
33  * <dt><b>Styles:</b></dt>
34  * <dd>NONE</dd>
35  * <dt><b>Events:</b></dt>
36  * <dd>(None)</dd>
37  * </dl>
38  * <p>
39  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
40  * </p>
41  *
42  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
43  *
44  * @since 3.0
45  * @noextend This class is not intended to be subclassed by clients.
46  */
47 
48 public class CBanner extends Composite {
49 
50 	Control left;
51 	Control right;
52 	Control bottom;
53 
54 	boolean simple = true;
55 
56 	int[] curve = new int[0];
57 	int curveStart = 0;
58 	Rectangle curveRect = new Rectangle(0, 0, 0, 0);
59 	int curve_width = 5;
60 	int curve_indent = -2;
61 
62 	int rightWidth = SWT.DEFAULT;
63 	int rightMinWidth = 0;
64 	int rightMinHeight = 0;
65 	Cursor resizeCursor;
66 	boolean dragging = false;
67 	int rightDragDisplacement = 0;
68 	Listener listener;
69 
70 	static final int OFFSCREEN = -200;
71 	static final int BORDER_BOTTOM = 2;
72 	static final int BORDER_TOP = 3;
73 	static final int BORDER_STRIPE = 1;
74 	static final int CURVE_TAIL = 200;
75 	static final int BEZIER_RIGHT = 30;
76 	static final int BEZIER_LEFT = 30;
77 	static final int MIN_LEFT = 10;
78 	static int BORDER1 = SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW;
79 
80 
81 /**
82  * Constructs a new instance of this class given its parent
83  * and a style value describing its behavior and appearance.
84  * <p>
85  * The style value is either one of the style constants defined in
86  * class <code>SWT</code> which is applicable to instances of this
87  * class, or must be built by <em>bitwise OR</em>'ing together
88  * (that is, using the <code>int</code> "|" operator) two or more
89  * of those <code>SWT</code> style constants. The class description
90  * lists the style constants that are applicable to the class.
91  * Style bits are also inherited from superclasses.
92  * </p>
93  *
94  * @param parent a widget which will be the parent of the new instance (cannot be null)
95  * @param style the style of widget to construct
96  *
97  * @exception IllegalArgumentException <ul>
98  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
99  * </ul>
100  * @exception SWTException <ul>
101  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
102  * </ul>
103  *
104  */
CBanner(Composite parent, int style)105 public CBanner(Composite parent, int style) {
106 	super(parent, checkStyle(style));
107 	super.setLayout(new CBannerLayout());
108 	resizeCursor = getDisplay().getSystemCursor(SWT.CURSOR_SIZEWE);
109 
110 	listener = e -> {
111 		switch (e.type) {
112 			case SWT.Dispose:
113 				onDispose(e); break;
114 			case SWT.MouseDown:
115 				onMouseDown (e.x, e.y); break;
116 			case SWT.MouseExit:
117 				onMouseExit(); break;
118 			case SWT.MouseMove:
119 				onMouseMove(e.x, e.y); break;
120 			case SWT.MouseUp:
121 				onMouseUp(); break;
122 			case SWT.Paint:
123 				onPaint(e.gc); break;
124 			case SWT.Resize:
125 				onResize(); break;
126 		}
127 	};
128 	int[] events = new int[] {SWT.Dispose, SWT.MouseDown, SWT.MouseExit, SWT.MouseMove, SWT.MouseUp, SWT.Paint, SWT.Resize};
129 	for (int event : events) {
130 		addListener(event, listener);
131 	}
132 }
bezier(int x0, int y0, int x1, int y1, int x2, int y2, int x3, int y3, int count)133 static int[] bezier(int x0, int y0, int x1, int y1, int x2, int y2, int x3, int y3, int count) {
134 	// The parametric equations for a Bezier curve for x[t] and y[t] where  0 <= t <=1 are:
135 	// x[t] = x0+3(x1-x0)t+3(x0+x2-2x1)t^2+(x3-x0+3x1-3x2)t^3
136 	// y[t] = y0+3(y1-y0)t+3(y0+y2-2y1)t^2+(y3-y0+3y1-3y2)t^3
137 	double a0 = x0;
138 	double a1 = 3*(x1 - x0);
139 	double a2 = 3*(x0 + x2 - 2*x1);
140 	double a3 = x3 - x0 + 3*x1 - 3*x2;
141 	double b0 = y0;
142 	double b1 = 3*(y1 - y0);
143 	double b2 = 3*(y0 + y2 - 2*y1);
144 	double b3 = y3 - y0 + 3*y1 - 3*y2;
145 
146 	int[] polygon = new int[2*count + 2];
147 	for (int i = 0; i <= count; i++) {
148 		double t = (double)i / (double)count;
149 		polygon[2*i] = (int)(a0 + a1*t + a2*t*t + a3*t*t*t);
150 		polygon[2*i + 1] = (int)(b0 + b1*t + b2*t*t + b3*t*t*t);
151 	}
152 	return polygon;
153 }
checkStyle(int style)154 static int checkStyle (int style) {
155 	return SWT.NONE;
156 }
157 /*
158 * This class was not intended to be subclassed but this restriction
159 * cannot be enforced without breaking backward compatibility.
160 */
161 //protected void checkSubclass () {
162 //	String name = getClass ().getName ();
163 //	int index = name.lastIndexOf ('.');
164 //	if (!name.substring (0, index + 1).equals ("org.eclipse.swt.custom.")) {
165 //		SWT.error (SWT.ERROR_INVALID_SUBCLASS);
166 //	}
167 //}
168 /**
169 * Returns the Control that appears on the bottom side of the banner.
170 *
171 * @return the control that appears on the bottom side of the banner or null
172 *
173 * @exception SWTException <ul>
174 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
175 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
176 * </ul>
177 *
178 * @since 3.0
179 */
getBottom()180 public Control getBottom() {
181 	checkWidget();
182 	return bottom;
183 }
184 @Override
getClientArea()185 public Rectangle getClientArea() {
186 	return new Rectangle(0, 0, 0, 0);
187 }
188 
189 /**
190 * Returns the Control that appears on the left side of the banner.
191 *
192 * @return the control that appears on the left side of the banner or null
193 *
194 * @exception SWTException <ul>
195 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
196 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
197 * </ul>
198 *
199 * @since 3.0
200 */
getLeft()201 public Control getLeft() {
202 	checkWidget();
203 	return left;
204 }
205 
206 /**
207 * Returns the Control that appears on the right side of the banner.
208 *
209 * @return the control that appears on the right side of the banner or null
210 *
211 * @exception SWTException <ul>
212 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
213 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
214 * </ul>
215 *
216 * @since 3.0
217 */
getRight()218 public Control getRight() {
219 	checkWidget();
220 	return right;
221 }
222 /**
223  * Returns the minimum size of the control that appears on the right of the banner.
224  *
225  * @return the minimum size of the control that appears on the right of the banner
226  *
227  * @since 3.1
228  */
getRightMinimumSize()229 public Point getRightMinimumSize() {
230 	checkWidget();
231 	return new Point(rightMinWidth, rightMinHeight);
232 }
233 /**
234  * Returns the width of the control that appears on the right of the banner.
235  *
236  * @return the width of the control that appears on the right of the banner
237  *
238  * @since 3.0
239  */
getRightWidth()240 public int getRightWidth() {
241 	checkWidget();
242 	if (right == null) return 0;
243 	if (rightWidth == SWT.DEFAULT) {
244 		Point size = right.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
245 		return size.x;
246 	}
247 	return rightWidth;
248 }
249 /**
250  * Returns <code>true</code> if the CBanner is rendered
251  * with a simple, traditional shape.
252  *
253  * @return <code>true</code> if the CBanner is rendered with a simple shape
254  *
255  * @since 3.0
256  */
getSimple()257 public boolean getSimple() {
258 	checkWidget();
259 	return simple;
260 }
onDispose(Event event)261 void onDispose(Event event) {
262 	removeListener(SWT.Dispose, listener);
263 	notifyListeners(SWT.Dispose, event);
264 	event.type = SWT.None;
265 
266 	resizeCursor = null;
267 	left = null;
268 	right = null;
269 	bottom = null;
270 }
onMouseDown(int x, int y)271 void onMouseDown (int x, int y) {
272 	if (curveRect.contains(x, y)) {
273 		dragging = true;
274 		rightDragDisplacement = curveStart - x + curve_width - curve_indent;
275 	}
276 }
onMouseExit()277 void onMouseExit() {
278 	if (!dragging) setCursor(null);
279 }
onMouseMove(int x, int y)280 void onMouseMove(int x, int y) {
281 	if (dragging) {
282 		Point size = getSize();
283 		if (!(0 < x && x < size.x)) return;
284 		rightWidth = Math.max(0, size.x - x - rightDragDisplacement);
285 		if (rightMinWidth == SWT.DEFAULT) {
286 			Point minSize = right.computeSize(rightMinWidth, rightMinHeight);
287 			rightWidth = Math.max(minSize.x, rightWidth);
288 		} else {
289 			rightWidth = Math.max(rightMinWidth, rightWidth);
290 		}
291 		layout(false);
292 		return;
293 	}
294 	if (curveRect.contains(x, y)) {
295 		setCursor(resizeCursor);
296 	} else {
297 		setCursor(null);
298 	}
299 }
onMouseUp()300 void onMouseUp () {
301 	dragging = false;
302 }
onPaint(GC gc)303 void onPaint(GC gc) {
304 //	 Useful for debugging paint problems
305 //	{
306 //	Point size = getSize();
307 //	gc.setBackground(getDisplay().getSystemColor(SWT.COLOR_GREEN));
308 //	gc.fillRectangle(-10, -10, size.x+20, size.y+20);
309 //	}
310 	if (left == null && right == null) return;
311 	Point size = getSize();
312 	Color border1 = getDisplay().getSystemColor(BORDER1);
313 	if (bottom != null) {
314 		int y = bottom.getBounds().y - BORDER_STRIPE - 1;
315 		gc.setForeground(border1);
316 		gc.drawLine(0, y, size.x, y);
317 	}
318 	if (left == null || right == null) return;
319 	int[] line1 = new int[curve.length+6];
320 	int index = 0;
321 	int x = curveStart;
322 	line1[index++] = x + 1;
323 	line1[index++] = size.y - BORDER_STRIPE;
324 	for (int i = 0; i < curve.length/2; i++) {
325 		line1[index++]=x+curve[2*i];
326 		line1[index++]=curve[2*i+1];
327 	}
328 	line1[index++] = x + curve_width;
329 	line1[index++] = 0;
330 	line1[index++] = size.x;
331 	line1[index++] = 0;
332 
333 	Color background = getBackground();
334 
335 	if (getDisplay().getDepth() >= 15) {
336 		// Anti- aliasing
337 		int[] line2 = new int[line1.length];
338 		index = 0;
339 		for (int i = 0; i < line1.length/2; i++) {
340 			line2[index] = line1[index++]  - 1;
341 			line2[index] = line1[index++];
342 		}
343 		int[] line3 = new int[line1.length];
344 		index = 0;
345 		for (int i = 0; i < line1.length/2; i++) {
346 			line3[index] = line1[index++] + 1;
347 			line3[index] = line1[index++];
348 		}
349 		RGB from = border1.getRGB();
350 		RGB to = background.getRGB();
351 		int red = from.red + 3*(to.red - from.red)/4;
352 		int green = from.green + 3*(to.green - from.green)/4;
353 		int blue = from.blue + 3*(to.blue - from.blue)/4;
354 		Color color = new Color(red, green, blue);
355 		gc.setForeground(color);
356 		gc.drawPolyline(line2);
357 		gc.drawPolyline(line3);
358 		color.dispose();
359 
360 		// draw tail fading to background
361 		int x1 = Math.max(0, curveStart - CURVE_TAIL);
362 		gc.setForeground(background);
363 		gc.setBackground(border1);
364 		gc.fillGradientRectangle(x1, size.y - BORDER_STRIPE, curveStart-x1+1, 1, false);
365 	} else {
366 		// draw solid tail
367 		int x1 = Math.max(0, curveStart - CURVE_TAIL);
368 		gc.setForeground(border1);
369 		gc.drawLine(x1, size.y - BORDER_STRIPE, curveStart+1, size.y - BORDER_STRIPE);
370 	}
371 
372 	// draw border
373 	gc.setForeground(border1);
374 	gc.drawPolyline(line1);
375 }
376 
onResize()377 void onResize() {
378 	updateCurve(getSize().y);
379 }
380 /**
381 * Set the control that appears on the bottom side of the banner.
382 * The bottom control is optional.  Setting the bottom control to null will remove it from
383 * the banner - however, the creator of the control must dispose of the control.
384 *
385 * @param control the control to be displayed on the bottom or null
386 *
387 * @exception SWTException <ul>
388 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
389 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
390 *    <li>ERROR_INVALID_ARGUMENT - if the bottom control was not created as a child of the receiver</li>
391 * </ul>
392 *
393 * @since 3.0
394 */
setBottom(Control control)395 public void setBottom(Control control) {
396 	checkWidget();
397 	if (control != null && control.getParent() != this) {
398 		SWT.error(SWT.ERROR_INVALID_ARGUMENT);
399 	}
400 	if (bottom != null && !bottom.isDisposed()) {
401 		Point size = bottom.getSize();
402 		bottom.setLocation(OFFSCREEN - size.x, OFFSCREEN - size.y);
403 	}
404 	bottom = control;
405 	layout(false);
406 }
407 /**
408  * Sets the layout which is associated with the receiver to be
409  * the argument which may be null.
410  * <p>
411  * Note: No Layout can be set on this Control because it already
412  * manages the size and position of its children.
413  * </p>
414  *
415  * @param layout the receiver's new layout or null
416  *
417  * @exception SWTException <ul>
418  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
419  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
420  * </ul>
421  */
422 @Override
setLayout(Layout layout)423 public void setLayout (Layout layout) {
424 	checkWidget();
425 	return;
426 }
427 
428 /**
429 * Set the control that appears on the left side of the banner.
430 * The left control is optional.  Setting the left control to null will remove it from
431 * the banner - however, the creator of the control must dispose of the control.
432 *
433 * @param control the control to be displayed on the left or null
434 *
435 * @exception SWTException <ul>
436 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
437 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
438 *    <li>ERROR_INVALID_ARGUMENT - if the left control was not created as a child of the receiver</li>
439 * </ul>
440 *
441 * @since 3.0
442 */
setLeft(Control control)443 public void setLeft(Control control) {
444 	checkWidget();
445 	if (control != null && control.getParent() != this) {
446 		SWT.error(SWT.ERROR_INVALID_ARGUMENT);
447 	}
448 	if (left != null && !left.isDisposed()) {
449 		Point size = left.getSize();
450 		left.setLocation(OFFSCREEN - size.x, OFFSCREEN - size.y);
451 	}
452 	left = control;
453 	layout(false);
454 }
455 /**
456 * Set the control that appears on the right side of the banner.
457 * The right control is optional.  Setting the right control to null will remove it from
458 * the banner - however, the creator of the control must dispose of the control.
459 *
460 * @param control the control to be displayed on the right or null
461 *
462 * @exception SWTException <ul>
463 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
464 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
465 *    <li>ERROR_INVALID_ARGUMENT - if the right control was not created as a child of the receiver</li>
466 * </ul>
467 *
468 * @since 3.0
469 */
setRight(Control control)470 public void setRight(Control control) {
471 	checkWidget();
472 	if (control != null && control.getParent() != this) {
473 		SWT.error(SWT.ERROR_INVALID_ARGUMENT);
474 	}
475 	if (right != null && !right.isDisposed()) {
476 		Point size = right.getSize();
477 		right.setLocation(OFFSCREEN - size.x, OFFSCREEN - size.y);
478 	}
479 	right = control;
480 	layout(false);
481 }
482 /**
483  * Set the minimum height of the control that appears on the right side of the banner.
484  *
485  * @param size the minimum size of the control on the right
486  *
487  * @exception SWTException <ul>
488  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
489  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
490  *    <li>ERROR_INVALID_ARGUMENT - if the size is null or the values of size are less than SWT.DEFAULT</li>
491  * </ul>
492  *
493  * @since 3.1
494  */
setRightMinimumSize(Point size)495 public void setRightMinimumSize(Point size) {
496 	checkWidget();
497 	if (size == null || size.x < SWT.DEFAULT || size.y < SWT.DEFAULT) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
498 	rightMinWidth = size.x;
499 	rightMinHeight = size.y;
500 	layout(false);
501 }
502 /**
503  * Set the width of the control that appears on the right side of the banner.
504  *
505  * @param width the width of the control on the right
506  *
507  * @exception SWTException <ul>
508  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
509  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
510  *    <li>ERROR_INVALID_ARGUMENT - if width is less than SWT.DEFAULT</li>
511  * </ul>
512  *
513  * @since 3.0
514  */
setRightWidth(int width)515 public void setRightWidth(int width) {
516 	checkWidget();
517 	if (width < SWT.DEFAULT) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
518 	rightWidth = width;
519 	layout(false);
520 }
521 /**
522  * Sets the shape that the CBanner will use to render itself.
523  *
524  * @param simple <code>true</code> if the CBanner should render itself in a simple, traditional style
525  *
526  * @exception SWTException <ul>
527  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
528  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
529  * </ul>
530  *
531  * @since 3.0
532  */
setSimple(boolean simple)533 public void setSimple(boolean simple) {
534 	checkWidget();
535 	if (this.simple != simple) {
536 		this.simple = simple;
537 		if (simple) {
538 			curve_width = 5;
539 			curve_indent = -2;
540 		} else {
541 			curve_width = 50;
542 			curve_indent = 5;
543 		}
544 		updateCurve(getSize().y);
545 		layout(false);
546 		redraw();
547 	}
548 }
updateCurve(int height)549 void updateCurve(int height) {
550 	int h = height - BORDER_STRIPE;
551 	if (simple) {
552 		curve = new int[] {0,h, 1,h, 2,h-1, 3,h-2,
553 								   3,2, 4,1, 5,0,};
554 	} else {
555 		curve = bezier(0, h+1, BEZIER_LEFT, h+1,
556 							 curve_width-BEZIER_RIGHT, 0, curve_width, 0,
557 							 curve_width);
558 	}
559 }
560 }
561