/* ****************************************************************************** * Copyright (c) 2006-2012 XMind Ltd. and others. * * This file is a part of XMind 3. XMind releases 3 and * above are dual-licensed under the Eclipse Public License (EPL), * which is available at http://www.eclipse.org/legal/epl-v10.html * and the GNU Lesser General Public License (LGPL), * which is available at http://www.gnu.org/licenses/lgpl.html * See http://www.xmind.net/license.html for details. * * Contributors: * XMind Ltd. - initial API and implementation *******************************************************************************/ package org.xmind.ui.viewers; import java.util.ArrayList; import java.util.List; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.jface.util.SafeRunnable; import org.eclipse.jface.util.Util; import org.eclipse.jface.viewers.IOpenListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.OpenEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; public class MButton extends Viewer { /** * Style bit: Create control with default behaviours, i.e. showing text, * showing image. */ public static final int NORMAL = 0; /** * Style bit: Don't show text. */ public static final int NO_TEXT = 1; /** * Style bit: Don't show image. */ public static final int NO_IMAGE = 1 << 1; /** * Style bit: Don't show spinner arrows. */ public static final int NO_ARROWS = 1 << 2; private static final boolean DRAWS_FOCUS = Util.isMac(); protected static final int MARGIN = DRAWS_FOCUS ? 4 : 1; protected static final int CORNER_SIZE = 5; protected static final int BORDER = (CORNER_SIZE + 1) / 2; protected static final int FOCUS_CORNER_SIZE = CORNER_SIZE - 2; protected static final int FOCUS_BORDER = (FOCUS_CORNER_SIZE + 1) / 2; protected static final int IMAGE_TEXT_SPACING = 3; protected static final int CONTENT_ARROW_SPACING = 4; protected static final int ARROW_WIDTH = 7; protected static final int ARROW_HEIGHT = 4; protected static final int ARROWS_SPACING = 2; protected static final String ELLIPSIS = "..."; //$NON-NLS-1$ private Composite control; private int style; private String text = null; private Image image = null; private Color textForeground = null; private Color textBackground = null; private boolean hovered = false; private boolean pressed = false; private boolean forceFocus = false; private Point textSize = null; private Point imageSize = null; private int leftPadding = BORDER; private int rightPadding = BORDER; private int topPadding = BORDER; private int bottomPadding = BORDER; private List<IOpenListener> openListeners = null; /* * Caches: */ private Point cachedTextSize = null; private Point cachedImageSize = null; private String appliedText = null; private Rectangle bounds = null; private Rectangle contentArea = null; private Point arrowLoc = null; private Rectangle imgArea = null; private Rectangle textArea = null; /** * Constructs a new instance of this class given its parent and a style * value describing its behavior and appearance. * * @param parent * a composite control which will be the parent of the new * instance (cannot be null) * @param style * the style of control to construct * * @see #NORMAL * @see #NO_TEXT * @see #NO_IMAGE * @see #NO_ARROWS */ public MButton(Composite parent, int style) { this.style = checkStyle(style, NORMAL, NORMAL, NO_TEXT, NO_IMAGE) | checkStyle(style, SWT.NONE, NO_ARROWS); this.control = new Canvas(parent, SWT.DOUBLE_BUFFERED) { public Point computeSize(int wHint, int hHint, boolean changed) { checkWidget(); if (changed) clearCaches(); Point imageSize = getImageSize(); Point textSize = getTextSize(); boolean hasArrows = hasArrows(); int width; if (wHint != SWT.DEFAULT) { width = Math.max(wHint, MARGIN * 2); } else { width = MARGIN * 2 + imageSize.x + textSize.x + leftPadding + rightPadding; if (hasArrows) { width += ARROW_WIDTH + CONTENT_ARROW_SPACING; } if (imageSize.x != 0 && textSize.x != 0) { width += IMAGE_TEXT_SPACING; } // if (hasArrows && (imageSize.x != 0 || textSize.x != 0)) { // width += CONTENT_ARROW_SPACING; // } } int minHeight = MARGIN * 2 + Math.max(imageSize.y, textSize.y) + topPadding + bottomPadding; if (hasArrows) { minHeight = Math.max(minHeight, ARROW_HEIGHT * 2 + ARROWS_SPACING); } int height = minHeight; Rectangle trim = computeTrim(0, 0, width, height); return new Point(trim.width, trim.height); } }; hookControl(control); } protected void hookControl(Control control) { Listener listener = new Listener() { public void handleEvent(Event event) { switch (event.type) { case SWT.Paint: paint(event.gc, event.display); break; case SWT.Resize: clearCaches(); break; case SWT.MouseDown: if (event.button == 1) handleMousePress(); break; case SWT.MouseUp: if (event.button == 1) handleMouseRelease(); break; case SWT.MouseEnter: handleMouseEnter(); break; case SWT.MouseExit: handleMouseExit(); break; case SWT.KeyDown: handleKeyPress(event); break; case SWT.FocusIn: handleFocusIn(); break; case SWT.FocusOut: handleFocusOut(); } } }; control.addListener(SWT.Paint, listener); control.addListener(SWT.Resize, listener); control.addListener(SWT.MouseDown, listener); control.addListener(SWT.MouseUp, listener); control.addListener(SWT.MouseEnter, listener); control.addListener(SWT.MouseExit, listener); control.addListener(SWT.KeyDown, listener); control.addListener(SWT.FocusIn, listener); control.addListener(SWT.FocusOut, listener); } protected void handleMousePress() { if (!getControl().isEnabled()) return; setHovered(true); setPressed(true); getControl().setFocus(); fireOpen(); } protected void handleMouseRelease() { if (!getControl().isEnabled()) return; setPressed(false); } protected void handleMouseEnter() { if (!getControl().isEnabled()) return; setHovered(true); } protected void handleMouseExit() { if (!getControl().isEnabled()) return; setHovered(false); } protected void handleFocusIn() { if (!getControl().isEnabled()) return; refreshControl(); } protected void handleFocusOut() { if (!getControl().isEnabled()) return; setPressed(false); refreshControl(); } protected void handleKeyPress(Event e) { if (!getControl().isEnabled()) return; int keyCode = e.keyCode; int stateMask = e.stateMask; if (SWTUtils.matchKeyCode(keyCode, SWT.TAB)) { if (SWTUtils.matchState(stateMask, SWT.SHIFT)) { getControl().traverse(SWT.TRAVERSE_TAB_PREVIOUS); } else if (SWTUtils.matchState(stateMask, 0)) { getControl().traverse(SWT.TRAVERSE_TAB_NEXT); } } else if (SWTUtils.matchKey(stateMask, keyCode, 0, SWT.CR)) { fireOpen(); } } public void addOpenListener(IOpenListener listener) { if (openListeners == null) openListeners = new ArrayList<IOpenListener>(); openListeners.add(listener); } public void removeOpenListener(IOpenListener listener) { if (openListeners == null) return; openListeners.remove(listener); } protected void fireOpen(final OpenEvent event) { if (openListeners == null) return; for (final Object l : openListeners.toArray()) { SafeRunner.run(new SafeRunnable() { public void run() throws Exception { ((IOpenListener) l).open(event); } }); } } protected void fireOpen() { fireOpen(new OpenEvent(this, getSelection())); } public Control getControl() { return control; } protected int getStyle() { return style; } public String getText() { return text; } public Image getImage() { return image; } public boolean hasText() { return text != null && (style & NO_TEXT) == 0; } public boolean hasImage() { return image != null && (style & NO_IMAGE) == 0; } protected boolean hasArrows() { return (style & NO_ARROWS) == 0; } public Color getTextForeground() { return textForeground; } public Color getTextBackground() { return textBackground; } public void setTextForeground(Color c) { if (c == this.textForeground || (c != null && c.equals(this.textForeground))) return; this.textForeground = c; refreshControl(); } public void setTextBackground(Color c) { if (c == this.textBackground || (c != null && c.equals(this.textBackground))) return; this.textBackground = c; refreshControl(); } public boolean isHovered() { return hovered; } public boolean isPressed() { return pressed; } public boolean isForceFocus() { return forceFocus; } public void setHovered(boolean hovered) { if (hovered == this.hovered) return; this.hovered = hovered; refreshControl(); } public void setPressed(boolean pressed) { if (pressed == this.pressed) return; this.pressed = pressed; refreshControl(); } public void setForceFocus(boolean focused) { if (focused == this.forceFocus) return; this.forceFocus = focused; refreshControl(); } public void setText(String text) { if (text == this.text || (text != null && text.equals(this.text))) return; this.text = text; cachedTextSize = null; clearCaches(); refreshControl(); } public void setImage(Image image) { if (image == this.image) return; this.image = image; cachedImageSize = null; clearCaches(); refreshControl(); } public Point getTextSize() { if (textSize != null) return textSize; if (cachedTextSize == null) { cachedTextSize = calcTextSize(); } return cachedTextSize; } public Point getImageSize() { if (imageSize != null) return imageSize; if (cachedImageSize == null) { cachedImageSize = calcImageSize(); } return cachedImageSize; } public void setTextSize(Point size) { if (size == this.textSize || (size != null && size.equals(this.textSize))) return; this.textSize = size; cachedTextSize = null; refreshControl(); } public void setImageSize(Point size) { if (size == this.imageSize || (size != null && size.equals(this.imageSize))) return; this.imageSize = size; cachedImageSize = null; refreshControl(); } protected Point calcTextSize() { String string = getText(); if (!hasText()) { if ((style & NO_TEXT) != 0) return new Point(0, 0); string = "X"; //$NON-NLS-1$ } Point size; GC gc = new GC(getControl().getDisplay()); try { gc.setFont(getControl().getFont()); size = gc.stringExtent(string); } finally { gc.dispose(); } if (size.x == 0 && hasText()) size.x = 5; return size; } protected Point calcImageSize() { Point size = new Point(0, 0); if (hasImage()) { Rectangle bounds = image.getBounds(); size.x = Math.max(size.x, bounds.width); size.y = Math.max(size.y, bounds.height); } return size; } public void setEnabled(boolean enabled) { getControl().setEnabled(enabled); refreshControl(); } public boolean isEnabled() { return getControl().isEnabled(); } public String getAppliedText() { if (appliedText == null) { buildCaches(); } return appliedText; } protected void clearCaches() { appliedText = null; bounds = null; contentArea = null; arrowLoc = null; imgArea = null; textArea = null; } protected void buildCaches() { bounds = control.getClientArea(); bounds.x += MARGIN; bounds.y += MARGIN; bounds.width -= MARGIN * 2; bounds.height -= MARGIN * 2; int x1 = bounds.x + leftPadding; int y1 = bounds.y + topPadding; int w1 = bounds.width - (leftPadding + rightPadding); int h1 = bounds.height - (topPadding + bottomPadding); boolean hasArrows = hasArrows(); if (hasArrows) { arrowLoc = new Point(x1 + w1 + rightPadding / 2 - ARROW_WIDTH, y1 + (h1 - ARROW_HEIGHT * 2 - ARROWS_SPACING) / 2 - 1); } contentArea = new Rectangle(x1, y1, w1 - (hasArrows ? ARROW_WIDTH + CONTENT_ARROW_SPACING : 0), h1); boolean hasImage = hasImage(); boolean hasText = hasText(); if (hasImage) { if (hasText) { Point imgSize = getImageSize(); imgArea = new Rectangle(x1, y1, imgSize.x, h1); } else { imgArea = contentArea; } } if (hasText) { if (hasImage) { int w = imgArea.width + IMAGE_TEXT_SPACING; textArea = new Rectangle(imgArea.x + w, y1, contentArea.width - w, h1); } else { textArea = contentArea; } int maxTextWidth = textArea.width; Point textSize = getTextSize(); if (textSize.x > maxTextWidth) { GC gc = new GC(getControl().getDisplay()); try { gc.setFont(getControl().getFont()); appliedText = getSubString(gc, text, maxTextWidth - gc.stringExtent(ELLIPSIS).x) + ELLIPSIS; } finally { gc.dispose(); } } else { appliedText = text; } } } protected void paint(GC gc, Display display) { if (bounds == null) buildCaches(); gc.setAntialias(SWT.ON); gc.setTextAntialias(SWT.ON); int x, y, w, h; boolean focused = getControl().isFocusControl() || isForceFocus(); boolean hasBackgroundAndBorder = pressed || hovered || focused; if (hasBackgroundAndBorder) { // draw control background gc.setBackground(getBorderBackground(display)); gc.fillRoundRectangle(bounds.x, bounds.y, bounds.width, bounds.height, CORNER_SIZE, CORNER_SIZE); } if (focused) { // draw focused content background x = contentArea.x - FOCUS_BORDER; y = contentArea.y - FOCUS_BORDER; w = contentArea.width + FOCUS_BORDER * 2; h = contentArea.height + FOCUS_BORDER * 2; gc.setBackground(getRealTextBackground(display)); gc.fillRoundRectangle(x, y, w, h, FOCUS_CORNER_SIZE, FOCUS_CORNER_SIZE); } boolean hasImage = hasImage(); boolean hasText = hasText(); if (hasImage) { Rectangle clipping = gc.getClipping(); if (clipping == null || clipping.intersects(imgArea)) { // draw image Point imgSize = getImageSize(); x = imgArea.x + (imgArea.width - imgSize.x) / 2; y = imgArea.y + (imgArea.height - imgSize.y) / 2; gc.setClipping(imgArea); gc.drawImage(image, x, y); gc.setClipping(clipping); } } if (hasText) { Rectangle clipping = gc.getClipping(); if (clipping == null || clipping.intersects(textArea)) { // draw text String text = getAppliedText(); gc.setFont(getControl().getFont()); Point ext = gc.stringExtent(text); // if (hasImage) { x = textArea.x; // } else { // x = textArea.x + (textArea.width - ext.x) / 2; // } y = textArea.y + (textArea.height - ext.y) / 2; gc.setClipping(textArea); gc.setForeground(getRealTextForeground(display)); gc.drawString(text, x, y, true); gc.setClipping(clipping); } } // draw arrows if (hasArrows() && arrowLoc != null) { gc.setBackground(Display.getCurrent().getSystemColor( SWT.COLOR_WIDGET_NORMAL_SHADOW)); x = arrowLoc.x + ARROW_WIDTH / 2; y = arrowLoc.y; int x1 = arrowLoc.x - 1; int y1 = arrowLoc.y + ARROW_HEIGHT + 1; int x2 = arrowLoc.x + ARROW_WIDTH; gc.fillPolygon(new int[] { x, y, x1, y1, x2, y1 }); y += ARROW_HEIGHT * 2 + ARROWS_SPACING + 1; x1 = arrowLoc.x; y1 += ARROWS_SPACING; gc.fillPolygon(new int[] { x, y, x2, y1, x1 - 1, y1 }); } // draw border if (focused) { x = bounds.x; y = bounds.y; w = bounds.width; h = bounds.height; if (DRAWS_FOCUS) { gc.drawFocus(x - MARGIN + 1, y - MARGIN + 1, w + MARGIN * 2 - 2, h + MARGIN * 2 - 2); } else { gc.setForeground(getBorderForeground(display, focused)); gc.drawRoundRectangle(x, y, w, h, CORNER_SIZE, CORNER_SIZE); } } } private Color getRealTextForeground(Display display) { if (!getControl().isEnabled()) return display.getSystemColor(SWT.COLOR_GRAY); if (textForeground != null) return textForeground; return display.getSystemColor(SWT.COLOR_LIST_FOREGROUND); } private Color getRealTextBackground(Display display) { if (textBackground != null) return textBackground; return display.getSystemColor(SWT.COLOR_LIST_BACKGROUND); } private Color getBorderBackground(Display display) { if (pressed) return display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND); return display.getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW); } private Color getBorderForeground(Display display, boolean focused) { if (focused) return display.getSystemColor(SWT.COLOR_WIDGET_BORDER); return display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW); } protected static int checkStyle(int style, int defaultValue, int... bits) { for (int bit : bits) { int s = style & bit; if (s != 0) return s; } return defaultValue; } protected static String getSubString(GC gc, String string, int maxWidth) { Point ext = gc.stringExtent(string); if (ext.x <= maxWidth || string.length() == 0) return string; return getSubString(gc, string.substring(0, string.length() - 1), maxWidth); } public Object getInput() { return null; } public ISelection getSelection() { return new StructuredSelection(this); } public void refresh() { } public void refreshControl() { getControl().redraw(); } public void setInput(Object input) { } public void setSelection(ISelection selection, boolean reveal) { } public void setPaddings(int leftPadding, int topPadding, int rightPadding, int bottomPadding) { this.leftPadding = leftPadding; this.topPadding = topPadding; this.rightPadding = rightPadding; this.bottomPadding = bottomPadding; refreshControl(); } public void setHorizontalPaddings(int leftPadding, int rightPadding) { this.leftPadding = leftPadding; this.rightPadding = rightPadding; refreshControl(); } public void setVerticalPaddings(int topPadding, int bottomPadding) { this.topPadding = topPadding; this.bottomPadding = bottomPadding; refreshControl(); } public int getLeftPadding() { return leftPadding; } public int getTopPadding() { return topPadding; } public int getRightPadding() { return rightPadding; } public int getBottomPadding() { return bottomPadding; } }