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.ui.forms.widgets; 15 import org.eclipse.swt.SWT; 16 import org.eclipse.swt.accessibility.ACC; 17 import org.eclipse.swt.accessibility.AccessibleAdapter; 18 import org.eclipse.swt.accessibility.AccessibleControlAdapter; 19 import org.eclipse.swt.accessibility.AccessibleControlEvent; 20 import org.eclipse.swt.accessibility.AccessibleEvent; 21 import org.eclipse.swt.graphics.Color; 22 import org.eclipse.swt.graphics.Point; 23 import org.eclipse.swt.graphics.Rectangle; 24 import org.eclipse.swt.widgets.Composite; 25 import org.eclipse.swt.widgets.Event; 26 import org.eclipse.swt.widgets.Listener; 27 import org.eclipse.ui.forms.events.HyperlinkAdapter; 28 import org.eclipse.ui.forms.events.HyperlinkEvent; 29 import org.eclipse.ui.internal.forms.Messages; 30 31 /** 32 * A custom selectable control that can be used to control areas that can be 33 * expanded or collapsed. 34 * <p> 35 * This is an abstract class. Subclasses are responsible for rendering the 36 * control using decoration and hover decoration color. Control should be 37 * rendered based on the current expansion state. 38 * </p> 39 * <dl> 40 * <dt><b>Styles:</b></dt> 41 * <dd>None</dd> 42 * </dl> 43 * 44 * @since 3.0 45 */ 46 public abstract class ToggleHyperlink extends AbstractHyperlink { 47 protected int innerWidth; 48 protected int innerHeight; 49 protected boolean hover; 50 private boolean expanded; 51 private Color decorationColor; 52 private Color hoverColor; 53 /** 54 * Creates a control in a provided composite. 55 * 56 * @param parent 57 * the parent 58 * @param style 59 * the style 60 */ ToggleHyperlink(Composite parent, int style)61 public ToggleHyperlink(Composite parent, int style) { 62 super(parent, style); 63 Listener listener = e -> { 64 switch (e.type) { 65 case SWT.MouseEnter: 66 hover = true; 67 redraw(); 68 break; 69 case SWT.MouseExit: 70 hover = false; 71 redraw(); 72 break; 73 case SWT.KeyDown: 74 onKeyDown(e); 75 break; 76 } 77 }; 78 addListener(SWT.MouseEnter, listener); 79 addListener(SWT.MouseExit, listener); 80 addListener(SWT.KeyDown, listener); 81 addHyperlinkListener(new HyperlinkAdapter() { 82 @Override 83 public void linkActivated(HyperlinkEvent e) { 84 setExpanded(!isExpanded()); 85 } 86 }); 87 initAccessible(); 88 } 89 90 /** 91 * Sets the color of the decoration. 92 * 93 * @param decorationColor color to set 94 */ setDecorationColor(Color decorationColor)95 public void setDecorationColor(Color decorationColor) { 96 this.decorationColor = decorationColor; 97 } 98 /** 99 * Returns the color of the decoration. 100 * 101 * @return decoration color 102 */ getDecorationColor()103 public Color getDecorationColor() { 104 return decorationColor; 105 } 106 /** 107 * Sets the hover color of decoration. Hover color will be used when mouse 108 * enters the decoration area. 109 * 110 * @param hoverColor 111 * the hover color to use 112 */ setHoverDecorationColor(Color hoverColor)113 public void setHoverDecorationColor(Color hoverColor) { 114 this.hoverColor = hoverColor; 115 } 116 /** 117 * Returns the hover color of the decoration. 118 * 119 * @return the hover color of the decoration. 120 * @since 3.1 121 */ getHoverDecorationColor()122 public Color getHoverDecorationColor() { 123 return hoverColor; 124 } 125 126 /** 127 * Returns the hover color of the decoration. 128 * 129 * @return the hover color of the decoration. 130 * @deprecated use <code>getHoverDecorationColor</code> 131 * @see #getHoverDecorationColor() 132 */ 133 @Deprecated geHoverDecorationColor()134 public Color geHoverDecorationColor() { 135 return hoverColor; 136 } 137 /** 138 * Computes the size of the control. 139 * 140 * @param wHint 141 * width hint 142 * @param hHint 143 * height hint 144 * @param changed 145 * if true, flush any saved layout state 146 */ 147 @Override computeSize(int wHint, int hHint, boolean changed)148 public Point computeSize(int wHint, int hHint, boolean changed) { 149 int width = innerWidth + 2 * marginWidth; 150 int height = innerHeight + 2 * marginHeight; 151 if (wHint != SWT.DEFAULT) 152 width = wHint; 153 if (hHint != SWT.DEFAULT) 154 height = hHint; 155 156 Rectangle trim = computeTrim(0, 0, width, height); 157 return new Point(trim.width, trim.height); 158 } 159 /** 160 * Returns the expansion state of the toggle control. When toggle is in the 161 * normal (downward) state, the value is <samp>true </samp>. Collapsed 162 * control will return <samp>false </samp>. 163 * 164 * @return <samp>false </samp> if collapsed, <samp>true </samp> otherwise. 165 */ isExpanded()166 public boolean isExpanded() { 167 return expanded; 168 } 169 /** 170 * Sets the expansion state of the twistie control 171 * 172 * @param expanded the expansion state 173 */ setExpanded(boolean expanded)174 public void setExpanded(boolean expanded) { 175 this.expanded = expanded; 176 redraw(); 177 } initAccessible()178 private void initAccessible() { 179 getAccessible().addAccessibleListener(new AccessibleAdapter() { 180 @Override 181 public void getHelp(AccessibleEvent e) { 182 e.result = getToolTipText(); 183 } 184 @Override 185 public void getName(AccessibleEvent e) { 186 e.result = Messages.ToggleHyperlink_accessibleName; 187 } 188 @Override 189 public void getDescription(AccessibleEvent e) { 190 getName(e); 191 } 192 }); 193 getAccessible().addAccessibleControlListener( 194 new AccessibleControlAdapter() { 195 @Override 196 public void getChildAtPoint(AccessibleControlEvent e) { 197 Point testPoint = toControl(new Point(e.x, e.y)); 198 if (getBounds().contains(testPoint)) { 199 e.childID = ACC.CHILDID_SELF; 200 } 201 } 202 @Override 203 public void getLocation(AccessibleControlEvent e) { 204 Rectangle location = getBounds(); 205 Point pt = toDisplay(new Point(location.x, location.y)); 206 e.x = pt.x; 207 e.y = pt.y; 208 e.width = location.width; 209 e.height = location.height; 210 } 211 @Override 212 public void getSelection (AccessibleControlEvent e) { 213 if (ToggleHyperlink.this.getSelection()) 214 e.childID = ACC.CHILDID_SELF; 215 } 216 217 @Override 218 public void getFocus (AccessibleControlEvent e) { 219 if (ToggleHyperlink.this.getSelection()) 220 e.childID = ACC.CHILDID_SELF; 221 } 222 @Override 223 public void getChildCount(AccessibleControlEvent e) { 224 e.detail = 0; 225 } 226 @Override 227 public void getRole(AccessibleControlEvent e) { 228 e.detail = ACC.ROLE_TREE; 229 } 230 @Override 231 public void getState(AccessibleControlEvent e) { 232 e.detail = ToggleHyperlink.this.isExpanded() 233 ? ACC.STATE_EXPANDED 234 : ACC.STATE_COLLAPSED; 235 } 236 @Override 237 public void getValue(AccessibleControlEvent e) { 238 if (e.childID == ACC.CHILDID_SELF) { 239 String name = Messages.ToggleHyperlink_accessibleName; 240 if (getParent() instanceof ExpandableComposite) { 241 name = Messages.ToggleHyperlink_accessibleColumn+((ExpandableComposite)getParent()).getText(); 242 int index = name.indexOf('&'); 243 if (index != -1) { 244 name = name.substring(0, index) + name.substring(index + 1); 245 } 246 } 247 e.result = name; 248 } 249 } 250 }); 251 } 252 // set bogus childIDs on link activation to ensure state is read on expand/collapse 253 @Override triggerAccessible()254 void triggerAccessible() { 255 getAccessible().setFocus(getAccessibleChildID()); 256 } getAccessibleChildID()257 private int getAccessibleChildID() { 258 return ToggleHyperlink.this.isExpanded() ? 1 : 2; 259 } 260 onKeyDown(Event e)261 private void onKeyDown(Event e) { 262 if (e.keyCode==SWT.ARROW_RIGHT) { 263 // expand if collapsed 264 if (!isExpanded()) { 265 handleActivate(e); 266 } 267 e.doit=false; 268 } 269 else if (e.keyCode==SWT.ARROW_LEFT) { 270 // collapse if expanded 271 if (isExpanded()) { 272 handleActivate(e); 273 } 274 e.doit=false; 275 } 276 } 277 } 278