blob: 381d1b006f4f47567d09d4f497d3d9b2437c6f8a [file] [log] [blame]
/*
* Copyright (c) 2015 The Eclipse Foundation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Yatta Solutions - [466264] initial API and implementation
*/
package org.eclipse.oomph.internal.ui;
import org.eclipse.oomph.ui.UIUtil;
import org.eclipse.oomph.util.ObjectUtil;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
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.layout.GridLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import java.util.ArrayList;
import java.util.List;
/**
* Special button that does not draw any borders on hover/down coming from the OS.
*
* @author Andreas Scharf
*/
public class FlatButton extends Canvas implements Listener, PaintListener
{
private static final int[] LISTENER_TYPES = { SWT.MouseEnter, SWT.MouseExit, SWT.MouseDown, SWT.MouseUp, SWT.MouseMove, SWT.KeyDown, SWT.Traverse,
SWT.FocusIn, SWT.FocusOut };
private static final Cursor CURSOR_HAND = UIUtil.getDisplay().getSystemCursor(SWT.CURSOR_HAND);
private static final int DEFAULT_ICON_TEXT_GAP = 5;
protected static final Color COLOR_DEFAULT_FOCUS_FOREGROUND = UIPlugin.getColor(180, 180, 180);
protected static final Color COLOR_DEFAULT_DISABLED_BACKGROUND = UIPlugin.getColor(238, 238, 238);
protected static final Color COLOR_DEFAULT_DISABLED_FOREGROUND = UIPlugin.getColor(170, 170, 170);
private final List<SelectionListener> selectionListeners = new ArrayList<SelectionListener>();
private final int buttonStyle;
private boolean hover;
private Color hoverColor = UIUtil.getEclipseThemeColor();
private Color disabledBackgroundColor = COLOR_DEFAULT_DISABLED_BACKGROUND;
private Color disabledForegroundColor = COLOR_DEFAULT_DISABLED_FOREGROUND;
private Color internalBackgroundColor;
private Color focusForegroundColor = COLOR_DEFAULT_FOCUS_FOREGROUND;
private Image image;
private String text;
private int cornerWidth;
private int iconTextGap = DEFAULT_ICON_TEXT_GAP;
private int alignment = SWT.LEFT;
private boolean listenersPaused;
private boolean mouseDown;
private boolean mouseLockedInBounds;
private boolean showButtonDownState = true;
public FlatButton(Composite parent, int buttonStyle)
{
super(parent, SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED);
this.buttonStyle = buttonStyle;
setLayout(new GridLayout(1, false));
setCursor(CURSOR_HAND);
hookListeners();
}
@Override
public void setEnabled(boolean enabled)
{
super.setEnabled(enabled);
setCursor(enabled ? CURSOR_HAND : null);
}
public String getText()
{
return text;
}
public void setText(String text)
{
if (this.text != text)
{
this.text = text;
redraw();
}
}
public Image getImage()
{
return image;
}
public void setImage(Image image)
{
if (this.image != image)
{
this.image = image;
redraw();
}
}
public int getAlignment()
{
return alignment;
}
public void setAlignment(int alignment)
{
switch (alignment)
{
case SWT.LEFT:
case SWT.CENTER:
case SWT.RIGHT:
// Do nothing.
break;
default:
throw new IllegalArgumentException("Alignment must be one of SWT.LEFT, SWT.CENTER, SWT.RIGHT");
}
if (this.alignment != alignment)
{
this.alignment = alignment;
redraw();
}
}
public int getCornerWidth()
{
return cornerWidth;
}
public void setCornerWidth(int cornerWidth)
{
if (this.cornerWidth != cornerWidth)
{
this.cornerWidth = cornerWidth;
redraw();
}
}
@Override
public Color getBackground()
{
if (internalBackgroundColor == null)
{
return super.getBackground();
}
return internalBackgroundColor;
}
@Override
public void setBackground(Color color)
{
if (internalBackgroundColor != color)
{
internalBackgroundColor = color;
redraw();
}
}
public Color getHoverColor()
{
return hoverColor;
}
public void setHoverColor(Color hoverColor)
{
if (this.hoverColor != hoverColor)
{
this.hoverColor = hoverColor;
redraw();
}
}
public Color getDisabledForegroundColor()
{
return disabledForegroundColor;
}
public void setDisabledForegroundColor(Color color)
{
if (!ObjectUtil.equals(disabledForegroundColor, color))
{
disabledForegroundColor = color;
redraw();
}
}
protected void setDisabledBackgroundColor(Color color)
{
if (!ObjectUtil.equals(disabledBackgroundColor, color))
{
disabledBackgroundColor = color;
redraw();
}
}
protected Color getDisabledBackgroundColor()
{
return disabledBackgroundColor;
}
public Color getFocusForegroundColor()
{
return focusForegroundColor;
}
public void setFocusForegroundColor(Color focusForegroundColor)
{
this.focusForegroundColor = focusForegroundColor;
}
public boolean isShowButtonDownState()
{
return showButtonDownState;
}
public void setShowButtonDownState(boolean showButtonDownState)
{
if (this.showButtonDownState != showButtonDownState)
{
this.showButtonDownState = showButtonDownState;
redraw();
}
}
public int getIconTextGap()
{
return iconTextGap;
}
public void setIconTextGap(int iconTextGap)
{
if (this.iconTextGap != iconTextGap)
{
this.iconTextGap = iconTextGap;
Composite parent = getParent();
if (parent != null)
{
parent.layout();
}
}
}
public void addSelectionListener(SelectionListener listener)
{
selectionListeners.add(listener);
}
public void removeSelectionListener(SelectionListener listener)
{
selectionListeners.remove(selectionListeners);
}
public boolean isHover()
{
return hover;
}
@Override
public Point computeSize(int wHint, int hHint, boolean changed)
{
Point size = null;
if (wHint != SWT.DEFAULT && hHint != SWT.DEFAULT)
{
size = new Point(wHint, hHint);
}
else
{
size = getTotalSize();
}
if (wHint != SWT.DEFAULT)
{
size.x = wHint;
}
if (hHint != SWT.DEFAULT)
{
size.y = hHint;
}
// Extend the size to be able to visualize button down state.
size.x += 1;
size.y += 1;
return size;
}
protected Point getTotalSize()
{
int width = 0;
int height = 0;
if (text != null)
{
GC gc = new GC(this);
Point textSize;
try
{
textSize = gc.textExtent(text);
}
finally
{
gc.dispose();
}
width += textSize.x;
if (image != null)
{
width += iconTextGap;
}
height += textSize.y;
}
if (image != null)
{
Rectangle imgBounds = image.getBounds();
width += imgBounds.width;
int heightDiff = imgBounds.height - height;
if (heightDiff > 0)
{
height += heightDiff;
}
}
return new Point(width, height);
}
protected void hookListeners()
{
for (int type : LISTENER_TYPES)
{
addListener(type, this);
}
addPaintListener(this);
}
protected void unhookListeners()
{
removePaintListener(this);
for (int type : LISTENER_TYPES)
{
removeListener(type, (Listener)this);
}
}
@Override
public void dispose()
{
unhookListeners();
super.dispose();
}
/**
* Called when this button should paint itself.
*
* Subclasses may override.
*/
protected void drawContent(PaintEvent e)
{
GC gc = e.gc;
Point totalSize = getTotalSize();
Rectangle clientArea = getClientArea();
int startX = 0;
switch (getAlignment())
{
case SWT.CENTER:
startX = (clientArea.x + clientArea.width - totalSize.x) / 2;
break;
case SWT.RIGHT:
startX = clientArea.x + clientArea.width - totalSize.x;
break;
}
if (showButtonDownState && mouseLockedInBounds)
{
startX++;
}
if (image != null)
{
Rectangle imgBounds = image.getBounds();
int imgY = (clientArea.y + clientArea.height - imgBounds.height) / 2;
if (showButtonDownState && mouseLockedInBounds)
{
imgY++;
}
drawImage(gc, startX, imgY);
startX += imgBounds.width;
if (text != null)
{
startX += iconTextGap;
}
}
if (text != null)
{
Point textExtent = gc.textExtent(text);
int textY = (clientArea.y + clientArea.height - textExtent.y) / 2;
if (showButtonDownState && mouseLockedInBounds)
{
textY += 1;
}
drawText(gc, startX, textY);
}
}
protected void drawText(GC gc, int x, int y)
{
if (isEnabled())
{
if (isHover() || isFocusControl() && !listenersPaused && hoverColor != null)
{
gc.setForeground(hoverColor);
}
}
else if (disabledForegroundColor != null)
{
gc.setForeground(disabledForegroundColor);
}
gc.drawText(text, x, y, true);
}
protected void drawImage(GC gc, int x, int y)
{
gc.drawImage(image, x, y);
}
@Override
public int getStyle()
{
return buttonStyle;
}
@Override
protected void checkSubclass()
{
// Allow subclassing.
}
public void handleEvent(final Event event)
{
switch (event.type)
{
case SWT.MouseEnter:
onMouseEnter(event);
break;
case SWT.MouseUp:
onMouseUp(event);
break;
case SWT.MouseExit:
onMouseExit(event);
break;
case SWT.MouseDown:
onMouseDown(event);
break;
case SWT.MouseMove:
mouseMoveInternal(event);
break;
case SWT.KeyDown:
if (event.character == SWT.CR || event.character == SWT.SPACE)
{
notifySelectionListeners(null);
}
break;
case SWT.Traverse:
event.doit = true;
break;
case SWT.FocusIn:
onFocusIn(event);
break;
case SWT.FocusOut:
// Async exec to ensure that focus transfer is completed
UIUtil.asyncExec(this, new Runnable()
{
public void run()
{
onFocusOut(event);
}
});
break;
}
if (!isDisposed())
{
redraw();
}
}
/**
* Called when the button loses focus.
*
* @param event The associated {@link Event}, may be null.
*/
protected void onFocusOut(Event event)
{
// Subclasses may override.
}
/**
* Called when the button gains focus.
*
* @param event The associated {@link Event}, may be null.
*/
protected void onFocusIn(Event event)
{
// Subclasses may override.
}
private void mouseMoveInternal(Event event)
{
if (mouseDown)
{
Rectangle bounds = getBounds();
boolean inBounds = event.x >= 0 && event.x <= bounds.width && event.y >= 0 && event.y <= bounds.height;
if (inBounds != mouseLockedInBounds)
{
mouseLockedInBounds = inBounds;
if (inBounds)
{
onMouseEnter(event);
}
else
{
onMouseExit(event);
}
}
}
}
protected void onMouseDown(Event event)
{
mouseDown = true;
mouseLockedInBounds = true;
}
protected void onMouseExit(Event event)
{
setHover(false);
}
protected void onMouseUp(Event event)
{
mouseLockedInBounds = false;
mouseDown = false;
setHover(true);
notifySelectionListeners(new SelectionEvent(event));
}
/**
* Called when the hover status of this button changes.
*/
protected void onHover()
{
// Do nothing.
}
protected void onMouseEnter(Event event)
{
setHover(true);
}
private void setHover(boolean hover)
{
if (this.hover != hover)
{
this.hover = hover;
onHover();
}
}
private void notifySelectionListeners(SelectionEvent event)
{
if (!isEnabled() || listenersPaused)
{
return;
}
Rectangle r = getBounds();
// Event could be null if the keyboard was used
if (event == null || event.x >= 0 && event.x <= r.width && event.y >= 0 && event.y <= r.height && !selectionListeners.isEmpty())
{
for (SelectionListener listener : selectionListeners)
{
try
{
listener.widgetSelected(event);
}
catch (Exception ex)
{
UIPlugin.INSTANCE.log(ex);
}
}
}
}
protected boolean isListenersPaused()
{
return listenersPaused;
}
protected void setListenersPaused(boolean pause)
{
listenersPaused = pause;
}
public final void paintControl(PaintEvent e)
{
Rectangle clientBounds = getClientArea();
GC gc = e.gc;
gc.setAntialias(SWT.ON);
int clientX = clientBounds.x;
int clientY = clientBounds.y;
int clientWidth = clientBounds.width - 1;
int clientHeight = clientBounds.height - 1;
if (showButtonDownState && mouseLockedInBounds)
{
clientX++;
clientY++;
}
clearBackground(gc, clientBounds);
drawBackground(gc, clientX, clientY, clientWidth, clientHeight, 0, 0);
drawContent(e);
if (isHover())
{
drawHoverState(gc, clientX, clientY, clientWidth, clientHeight);
}
if (isFocusControl())
{
drawFocusState(gc, clientX, clientY, clientWidth, clientHeight);
}
}
/**
* Draws the focus state of this button. This method will be called last, meaning
* that everything is painted <b>on top</b> of anything else.
*
* @param gc The {@link GC} to draw with
* @param x The x coordinate
* @param x The y coordinate
* @param width The width to visualize
* @param height The height to visualize
*/
protected void drawFocusState(GC gc, int x, int y, int width, int height)
{
if (focusForegroundColor != null)
{
Color oldForeground = gc.getForeground();
gc.setForeground(focusForegroundColor);
gc.drawRoundRectangle(x, y, width, height, cornerWidth, cornerWidth);
gc.setForeground(oldForeground);
}
}
private void clearBackground(GC gc, Rectangle bounds)
{
Composite parent = getParent();
Color parentBackground = null;
do
{
parentBackground = parent.getBackground();
parent = parent.getParent();
} while (parentBackground == null && parent.getParent() != null);
if (parentBackground != null)
{
Color oldBG = gc.getBackground();
gc.setBackground(parentBackground);
gc.fillRectangle(bounds);
gc.setBackground(oldBG);
}
}
protected void drawHoverState(GC gc, int x, int y, int width, int height)
{
// Subclasses may override to draw a hover state.
}
@Override
public void drawBackground(GC gc, int x, int y, int width, int height, int offsetX, int offsetY)
{
if (isEnabled())
{
if (internalBackgroundColor != null)
{
gc.setBackground(internalBackgroundColor);
gc.fillRoundRectangle(x, y, width, height, cornerWidth, cornerWidth);
}
}
else if (disabledBackgroundColor != null)
{
gc.setBackground(disabledBackgroundColor);
gc.fillRoundRectangle(x, y, width, height, cornerWidth, cornerWidth);
}
}
}