/*******************************************************************************
* Copyright (c) 2000, 2016 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.forms.widgets;
import org.eclipse.swt.SWT;
import org.eclipse.swt.accessibility.ACC;
import org.eclipse.swt.accessibility.AccessibleAdapter;
import org.eclipse.swt.accessibility.AccessibleControlAdapter;
import org.eclipse.swt.accessibility.AccessibleControlEvent;
import org.eclipse.swt.accessibility.AccessibleEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.internal.forms.Messages;
/**
* A custom selectable control that can be used to control areas that can be
* expanded or collapsed.
* <p>
* This is an abstract class. Subclasses are responsible for rendering the
* control using decoration and hover decoration color. Control should be
* rendered based on the current expansion state.
* <p>
* <dl>
* <dt><b>Styles:</b></dt>
* <dd>None</dd>
* </dl>
*
* @since 3.0
*/
public abstract class ToggleHyperlink extends AbstractHyperlink {
protected int innerWidth;
protected int innerHeight;
protected boolean hover;
private boolean expanded;
private Color decorationColor;
private Color hoverColor;
/**
* Creates a control in a provided composite.
*
* @param parent
* the parent
* @param style
* the style
*/
public ToggleHyperlink(Composite parent, int style) {
super(parent, style);
Listener listener = e -> {
switch (e.type) {
case SWT.MouseEnter:
hover = true;
redraw();
break;
case SWT.MouseExit:
hover = false;
redraw();
break;
case SWT.KeyDown:
onKeyDown(e);
break;
}
};
addListener(SWT.MouseEnter, listener);
addListener(SWT.MouseExit, listener);
addListener(SWT.KeyDown, listener);
addHyperlinkListener(new HyperlinkAdapter() {
@Override
public void linkActivated(HyperlinkEvent e) {
setExpanded(!isExpanded());
}
});
initAccessible();
}
/**
* Sets the color of the decoration.
*
* @param decorationColor
*/
public void setDecorationColor(Color decorationColor) {
this.decorationColor = decorationColor;
}
/**
* Returns the color of the decoration.
*
* @return decoration color
*/
public Color getDecorationColor() {
return decorationColor;
}
/**
* Sets the hover color of decoration. Hover color will be used when mouse
* enters the decoration area.
*
* @param hoverColor
* the hover color to use
*/
public void setHoverDecorationColor(Color hoverColor) {
this.hoverColor = hoverColor;
}
/**
* Returns the hover color of the decoration.
*
* @return the hover color of the decoration.
* @since 3.1
*/
public Color getHoverDecorationColor() {
return hoverColor;
}
/**
* Returns the hover color of the decoration.
*
* @return the hover color of the decoration.
* @deprecated use <code>getHoverDecorationColor</code>
* @see #getHoverDecorationColor()
*/
@Deprecated
public Color geHoverDecorationColor() {
return hoverColor;
}
/**
* Computes the size of the control.
*
* @param wHint
* width hint
* @param hHint
* height hint
* @param changed
* if true, flush any saved layout state
*/
@Override
public Point computeSize(int wHint, int hHint, boolean changed) {
int width = innerWidth + 2 * marginWidth;
int height = innerHeight + 2 * marginHeight;
if (wHint != SWT.DEFAULT)
width = wHint;
if (hHint != SWT.DEFAULT)
height = hHint;
Rectangle trim = computeTrim(0, 0, width, height);
return new Point(trim.width, trim.height);
}
/**
* Returns the expansion state of the toggle control. When toggle is in the
* normal (downward) state, the value is <samp>true </samp>. Collapsed
* control will return <samp>false </samp>.
*
* @return <samp>false </samp> if collapsed, <samp>true </samp> otherwise.
*/
public boolean isExpanded() {
return expanded;
}
/**
* Sets the expansion state of the twistie control
*
* @param expanded the expansion state
*/
public void setExpanded(boolean expanded) {
this.expanded = expanded;
redraw();
}
private void initAccessible() {
getAccessible().addAccessibleListener(new AccessibleAdapter() {
@Override
public void getHelp(AccessibleEvent e) {
e.result = getToolTipText();
}
@Override
public void getName(AccessibleEvent e) {
e.result = Messages.ToggleHyperlink_accessibleName;
}
@Override
public void getDescription(AccessibleEvent e) {
getName(e);
}
});
getAccessible().addAccessibleControlListener(
new AccessibleControlAdapter() {
@Override
public void getChildAtPoint(AccessibleControlEvent e) {
Point testPoint = toControl(new Point(e.x, e.y));
if (getBounds().contains(testPoint)) {
e.childID = ACC.CHILDID_SELF;
}
}
@Override
public void getLocation(AccessibleControlEvent e) {
Rectangle location = getBounds();
Point pt = toDisplay(new Point(location.x, location.y));
e.x = pt.x;
e.y = pt.y;
e.width = location.width;
e.height = location.height;
}
@Override
public void getSelection (AccessibleControlEvent e) {
if (ToggleHyperlink.this.getSelection())
e.childID = ACC.CHILDID_SELF;
}
@Override
public void getFocus (AccessibleControlEvent e) {
if (ToggleHyperlink.this.getSelection())
e.childID = ACC.CHILDID_SELF;
}
@Override
public void getChildCount(AccessibleControlEvent e) {
e.detail = 0;
}
@Override
public void getRole(AccessibleControlEvent e) {
e.detail = ACC.ROLE_TREE;
}
@Override
public void getState(AccessibleControlEvent e) {
e.detail = ToggleHyperlink.this.isExpanded()
? ACC.STATE_EXPANDED
: ACC.STATE_COLLAPSED;
}
@Override
public void getValue(AccessibleControlEvent e) {
if (e.childID == ACC.CHILDID_SELF) {
String name = Messages.ToggleHyperlink_accessibleName;
if (getParent() instanceof ExpandableComposite) {
name = Messages.ToggleHyperlink_accessibleColumn+((ExpandableComposite)getParent()).getText();
int index = name.indexOf('&');
if (index != -1) {
name = name.substring(0, index) + name.substring(index + 1);
}
}
e.result = name;
}
}
});
}
// set bogus childIDs on link activation to ensure state is read on expand/collapse
@Override
void triggerAccessible() {
getAccessible().setFocus(getAccessibleChildID());
}
private int getAccessibleChildID() {
return ToggleHyperlink.this.isExpanded() ? 1 : 2;
}
private void onKeyDown(Event e) {
if (e.keyCode==SWT.ARROW_RIGHT) {
// expand if collapsed
if (!isExpanded()) {
handleActivate(e);
}
e.doit=false;
}
else if (e.keyCode==SWT.ARROW_LEFT) {
// collapse if expanded
if (isExpanded()) {
handleActivate(e);
}
e.doit=false;
}
}
}