| /******************************************************************************* |
| * Copyright (c) 2000, 2016 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.swt.custom; |
| |
| |
| import org.eclipse.swt.*; |
| import org.eclipse.swt.graphics.*; |
| import org.eclipse.swt.widgets.*; |
| |
| /** |
| * Instances of this class implement a Composite that lays out its |
| * children and allows programmatic control of the layout. It draws |
| * a separator between the left and right children which can be dragged |
| * to resize the right control. |
| * CBanner is used in the workbench to layout the toolbar area and |
| * perspective switching toolbar. |
| * <p> |
| * Note that although this class is a subclass of <code>Composite</code>, |
| * it does not make sense to set a layout on it. |
| * </p><p> |
| * <dl> |
| * <dt><b>Styles:</b></dt> |
| * <dd>NONE</dd> |
| * <dt><b>Events:</b></dt> |
| * <dd>(None)</dd> |
| * </dl> |
| * <p> |
| * IMPORTANT: This class is <em>not</em> intended to be subclassed. |
| * </p> |
| * |
| * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> |
| * |
| * @since 3.0 |
| * @noextend This class is not intended to be subclassed by clients. |
| */ |
| |
| public class CBanner extends Composite { |
| |
| Control left; |
| Control right; |
| Control bottom; |
| |
| boolean simple = true; |
| |
| int[] curve = new int[0]; |
| int curveStart = 0; |
| Rectangle curveRect = new Rectangle(0, 0, 0, 0); |
| int curve_width = 5; |
| int curve_indent = -2; |
| |
| int rightWidth = SWT.DEFAULT; |
| int rightMinWidth = 0; |
| int rightMinHeight = 0; |
| Cursor resizeCursor; |
| boolean dragging = false; |
| int rightDragDisplacement = 0; |
| Listener listener; |
| |
| static final int OFFSCREEN = -200; |
| static final int BORDER_BOTTOM = 2; |
| static final int BORDER_TOP = 3; |
| static final int BORDER_STRIPE = 1; |
| static final int CURVE_TAIL = 200; |
| static final int BEZIER_RIGHT = 30; |
| static final int BEZIER_LEFT = 30; |
| static final int MIN_LEFT = 10; |
| static int BORDER1 = SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW; |
| |
| |
| /** |
| * Constructs a new instance of this class given its parent |
| * and a style value describing its behavior and appearance. |
| * <p> |
| * The style value is either one of the style constants defined in |
| * class <code>SWT</code> which is applicable to instances of this |
| * class, or must be built by <em>bitwise OR</em>'ing together |
| * (that is, using the <code>int</code> "|" operator) two or more |
| * of those <code>SWT</code> style constants. The class description |
| * lists the style constants that are applicable to the class. |
| * Style bits are also inherited from superclasses. |
| * </p> |
| * |
| * @param parent a widget which will be the parent of the new instance (cannot be null) |
| * @param style the style of widget to construct |
| * |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> |
| * </ul> |
| * @exception SWTException <ul> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> |
| * </ul> |
| * |
| */ |
| public CBanner(Composite parent, int style) { |
| super(parent, checkStyle(style)); |
| super.setLayout(new CBannerLayout()); |
| resizeCursor = getDisplay().getSystemCursor(SWT.CURSOR_SIZEWE); |
| |
| listener = e -> { |
| switch (e.type) { |
| case SWT.Dispose: |
| onDispose(e); break; |
| case SWT.MouseDown: |
| onMouseDown (e.x, e.y); break; |
| case SWT.MouseExit: |
| onMouseExit(); break; |
| case SWT.MouseMove: |
| onMouseMove(e.x, e.y); break; |
| case SWT.MouseUp: |
| onMouseUp(); break; |
| case SWT.Paint: |
| onPaint(e.gc); break; |
| case SWT.Resize: |
| onResize(); break; |
| } |
| }; |
| int[] events = new int[] {SWT.Dispose, SWT.MouseDown, SWT.MouseExit, SWT.MouseMove, SWT.MouseUp, SWT.Paint, SWT.Resize}; |
| for (int i = 0; i < events.length; i++) { |
| addListener(events[i], listener); |
| } |
| } |
| static int[] bezier(int x0, int y0, int x1, int y1, int x2, int y2, int x3, int y3, int count) { |
| // The parametric equations for a Bezier curve for x[t] and y[t] where 0 <= t <=1 are: |
| // x[t] = x0+3(x1-x0)t+3(x0+x2-2x1)t^2+(x3-x0+3x1-3x2)t^3 |
| // y[t] = y0+3(y1-y0)t+3(y0+y2-2y1)t^2+(y3-y0+3y1-3y2)t^3 |
| double a0 = x0; |
| double a1 = 3*(x1 - x0); |
| double a2 = 3*(x0 + x2 - 2*x1); |
| double a3 = x3 - x0 + 3*x1 - 3*x2; |
| double b0 = y0; |
| double b1 = 3*(y1 - y0); |
| double b2 = 3*(y0 + y2 - 2*y1); |
| double b3 = y3 - y0 + 3*y1 - 3*y2; |
| |
| int[] polygon = new int[2*count + 2]; |
| for (int i = 0; i <= count; i++) { |
| double t = (double)i / (double)count; |
| polygon[2*i] = (int)(a0 + a1*t + a2*t*t + a3*t*t*t); |
| polygon[2*i + 1] = (int)(b0 + b1*t + b2*t*t + b3*t*t*t); |
| } |
| return polygon; |
| } |
| static int checkStyle (int style) { |
| return SWT.NONE; |
| } |
| /* |
| * This class was not intended to be subclassed but this restriction |
| * cannot be enforced without breaking backward compatibility. |
| */ |
| //protected void checkSubclass () { |
| // String name = getClass ().getName (); |
| // int index = name.lastIndexOf ('.'); |
| // if (!name.substring (0, index + 1).equals ("org.eclipse.swt.custom.")) { |
| // SWT.error (SWT.ERROR_INVALID_SUBCLASS); |
| // } |
| //} |
| /** |
| * Returns the Control that appears on the bottom side of the banner. |
| * |
| * @return the control that appears on the bottom side of the banner or null |
| * |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| * |
| * @since 3.0 |
| */ |
| public Control getBottom() { |
| checkWidget(); |
| return bottom; |
| } |
| @Override |
| public Rectangle getClientArea() { |
| return new Rectangle(0, 0, 0, 0); |
| } |
| |
| /** |
| * Returns the Control that appears on the left side of the banner. |
| * |
| * @return the control that appears on the left side of the banner or null |
| * |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| * |
| * @since 3.0 |
| */ |
| public Control getLeft() { |
| checkWidget(); |
| return left; |
| } |
| |
| /** |
| * Returns the Control that appears on the right side of the banner. |
| * |
| * @return the control that appears on the right side of the banner or null |
| * |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| * |
| * @since 3.0 |
| */ |
| public Control getRight() { |
| checkWidget(); |
| return right; |
| } |
| /** |
| * Returns the minimum size of the control that appears on the right of the banner. |
| * |
| * @return the minimum size of the control that appears on the right of the banner |
| * |
| * @since 3.1 |
| */ |
| public Point getRightMinimumSize() { |
| checkWidget(); |
| return new Point(rightMinWidth, rightMinHeight); |
| } |
| /** |
| * Returns the width of the control that appears on the right of the banner. |
| * |
| * @return the width of the control that appears on the right of the banner |
| * |
| * @since 3.0 |
| */ |
| public int getRightWidth() { |
| checkWidget(); |
| if (right == null) return 0; |
| if (rightWidth == SWT.DEFAULT) { |
| Point size = right.computeSize(SWT.DEFAULT, SWT.DEFAULT, false); |
| return size.x; |
| } |
| return rightWidth; |
| } |
| /** |
| * Returns <code>true</code> if the CBanner is rendered |
| * with a simple, traditional shape. |
| * |
| * @return <code>true</code> if the CBanner is rendered with a simple shape |
| * |
| * @since 3.0 |
| */ |
| public boolean getSimple() { |
| checkWidget(); |
| return simple; |
| } |
| void onDispose(Event event) { |
| removeListener(SWT.Dispose, listener); |
| notifyListeners(SWT.Dispose, event); |
| event.type = SWT.None; |
| |
| resizeCursor = null; |
| left = null; |
| right = null; |
| bottom = null; |
| } |
| void onMouseDown (int x, int y) { |
| if (curveRect.contains(x, y)) { |
| dragging = true; |
| rightDragDisplacement = curveStart - x + curve_width - curve_indent; |
| } |
| } |
| void onMouseExit() { |
| if (!dragging) setCursor(null); |
| } |
| void onMouseMove(int x, int y) { |
| if (dragging) { |
| Point size = getSize(); |
| if (!(0 < x && x < size.x)) return; |
| rightWidth = Math.max(0, size.x - x - rightDragDisplacement); |
| if (rightMinWidth == SWT.DEFAULT) { |
| Point minSize = right.computeSize(rightMinWidth, rightMinHeight); |
| rightWidth = Math.max(minSize.x, rightWidth); |
| } else { |
| rightWidth = Math.max(rightMinWidth, rightWidth); |
| } |
| layout(false); |
| return; |
| } |
| if (curveRect.contains(x, y)) { |
| setCursor(resizeCursor); |
| } else { |
| setCursor(null); |
| } |
| } |
| void onMouseUp () { |
| dragging = false; |
| } |
| void onPaint(GC gc) { |
| // Useful for debugging paint problems |
| // { |
| // Point size = getSize(); |
| // gc.setBackground(getDisplay().getSystemColor(SWT.COLOR_GREEN)); |
| // gc.fillRectangle(-10, -10, size.x+20, size.y+20); |
| // } |
| if (left == null && right == null) return; |
| Point size = getSize(); |
| Color border1 = getDisplay().getSystemColor(BORDER1); |
| if (bottom != null) { |
| int y = bottom.getBounds().y - BORDER_STRIPE - 1; |
| gc.setForeground(border1); |
| gc.drawLine(0, y, size.x, y); |
| } |
| if (left == null || right == null) return; |
| int[] line1 = new int[curve.length+6]; |
| int index = 0; |
| int x = curveStart; |
| line1[index++] = x + 1; |
| line1[index++] = size.y - BORDER_STRIPE; |
| for (int i = 0; i < curve.length/2; i++) { |
| line1[index++]=x+curve[2*i]; |
| line1[index++]=curve[2*i+1]; |
| } |
| line1[index++] = x + curve_width; |
| line1[index++] = 0; |
| line1[index++] = size.x; |
| line1[index++] = 0; |
| |
| Color background = getBackground(); |
| |
| if (getDisplay().getDepth() >= 15) { |
| // Anti- aliasing |
| int[] line2 = new int[line1.length]; |
| index = 0; |
| for (int i = 0; i < line1.length/2; i++) { |
| line2[index] = line1[index++] - 1; |
| line2[index] = line1[index++]; |
| } |
| int[] line3 = new int[line1.length]; |
| index = 0; |
| for (int i = 0; i < line1.length/2; i++) { |
| line3[index] = line1[index++] + 1; |
| line3[index] = line1[index++]; |
| } |
| RGB from = border1.getRGB(); |
| RGB to = background.getRGB(); |
| int red = from.red + 3*(to.red - from.red)/4; |
| int green = from.green + 3*(to.green - from.green)/4; |
| int blue = from.blue + 3*(to.blue - from.blue)/4; |
| Color color = new Color(getDisplay(), red, green, blue); |
| gc.setForeground(color); |
| gc.drawPolyline(line2); |
| gc.drawPolyline(line3); |
| color.dispose(); |
| |
| // draw tail fading to background |
| int x1 = Math.max(0, curveStart - CURVE_TAIL); |
| gc.setForeground(background); |
| gc.setBackground(border1); |
| gc.fillGradientRectangle(x1, size.y - BORDER_STRIPE, curveStart-x1+1, 1, false); |
| } else { |
| // draw solid tail |
| int x1 = Math.max(0, curveStart - CURVE_TAIL); |
| gc.setForeground(border1); |
| gc.drawLine(x1, size.y - BORDER_STRIPE, curveStart+1, size.y - BORDER_STRIPE); |
| } |
| |
| // draw border |
| gc.setForeground(border1); |
| gc.drawPolyline(line1); |
| } |
| |
| void onResize() { |
| updateCurve(getSize().y); |
| } |
| /** |
| * Set the control that appears on the bottom side of the banner. |
| * The bottom control is optional. Setting the bottom control to null will remove it from |
| * the banner - however, the creator of the control must dispose of the control. |
| * |
| * @param control the control to be displayed on the bottom or null |
| * |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * <li>ERROR_INVALID_ARGUMENT - if the bottom control was not created as a child of the receiver</li> |
| * </ul> |
| * |
| * @since 3.0 |
| */ |
| public void setBottom(Control control) { |
| checkWidget(); |
| if (control != null && control.getParent() != this) { |
| SWT.error(SWT.ERROR_INVALID_ARGUMENT); |
| } |
| if (bottom != null && !bottom.isDisposed()) { |
| Point size = bottom.getSize(); |
| bottom.setLocation(OFFSCREEN - size.x, OFFSCREEN - size.y); |
| } |
| bottom = control; |
| layout(false); |
| } |
| /** |
| * Sets the layout which is associated with the receiver to be |
| * the argument which may be null. |
| * <p> |
| * Note: No Layout can be set on this Control because it already |
| * manages the size and position of its children. |
| * </p> |
| * |
| * @param layout the receiver's new layout or null |
| * |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| */ |
| @Override |
| public void setLayout (Layout layout) { |
| checkWidget(); |
| return; |
| } |
| |
| /** |
| * Set the control that appears on the left side of the banner. |
| * The left control is optional. Setting the left control to null will remove it from |
| * the banner - however, the creator of the control must dispose of the control. |
| * |
| * @param control the control to be displayed on the left or null |
| * |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * <li>ERROR_INVALID_ARGUMENT - if the left control was not created as a child of the receiver</li> |
| * </ul> |
| * |
| * @since 3.0 |
| */ |
| public void setLeft(Control control) { |
| checkWidget(); |
| if (control != null && control.getParent() != this) { |
| SWT.error(SWT.ERROR_INVALID_ARGUMENT); |
| } |
| if (left != null && !left.isDisposed()) { |
| Point size = left.getSize(); |
| left.setLocation(OFFSCREEN - size.x, OFFSCREEN - size.y); |
| } |
| left = control; |
| layout(false); |
| } |
| /** |
| * Set the control that appears on the right side of the banner. |
| * The right control is optional. Setting the right control to null will remove it from |
| * the banner - however, the creator of the control must dispose of the control. |
| * |
| * @param control the control to be displayed on the right or null |
| * |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * <li>ERROR_INVALID_ARGUMENT - if the right control was not created as a child of the receiver</li> |
| * </ul> |
| * |
| * @since 3.0 |
| */ |
| public void setRight(Control control) { |
| checkWidget(); |
| if (control != null && control.getParent() != this) { |
| SWT.error(SWT.ERROR_INVALID_ARGUMENT); |
| } |
| if (right != null && !right.isDisposed()) { |
| Point size = right.getSize(); |
| right.setLocation(OFFSCREEN - size.x, OFFSCREEN - size.y); |
| } |
| right = control; |
| layout(false); |
| } |
| /** |
| * Set the minimum height of the control that appears on the right side of the banner. |
| * |
| * @param size the minimum size of the control on the right |
| * |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * <li>ERROR_INVALID_ARGUMENT - if the size is null or the values of size are less than SWT.DEFAULT</li> |
| * </ul> |
| * |
| * @since 3.1 |
| */ |
| public void setRightMinimumSize(Point size) { |
| checkWidget(); |
| if (size == null || size.x < SWT.DEFAULT || size.y < SWT.DEFAULT) SWT.error(SWT.ERROR_INVALID_ARGUMENT); |
| rightMinWidth = size.x; |
| rightMinHeight = size.y; |
| layout(false); |
| } |
| /** |
| * Set the width of the control that appears on the right side of the banner. |
| * |
| * @param width the width of the control on the right |
| * |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * <li>ERROR_INVALID_ARGUMENT - if width is less than SWT.DEFAULT</li> |
| * </ul> |
| * |
| * @since 3.0 |
| */ |
| public void setRightWidth(int width) { |
| checkWidget(); |
| if (width < SWT.DEFAULT) SWT.error(SWT.ERROR_INVALID_ARGUMENT); |
| rightWidth = width; |
| layout(false); |
| } |
| /** |
| * Sets the shape that the CBanner will use to render itself. |
| * |
| * @param simple <code>true</code> if the CBanner should render itself in a simple, traditional style |
| * |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| * |
| * @since 3.0 |
| */ |
| public void setSimple(boolean simple) { |
| checkWidget(); |
| if (this.simple != simple) { |
| this.simple = simple; |
| if (simple) { |
| curve_width = 5; |
| curve_indent = -2; |
| } else { |
| curve_width = 50; |
| curve_indent = 5; |
| } |
| updateCurve(getSize().y); |
| layout(false); |
| redraw(); |
| } |
| } |
| void updateCurve(int height) { |
| int h = height - BORDER_STRIPE; |
| if (simple) { |
| curve = new int[] {0,h, 1,h, 2,h-1, 3,h-2, |
| 3,2, 4,1, 5,0,}; |
| } else { |
| curve = bezier(0, h+1, BEZIER_LEFT, h+1, |
| curve_width-BEZIER_RIGHT, 0, curve_width, 0, |
| curve_width); |
| } |
| } |
| } |