/******************************************************************************* * 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 * Izzet Safer (isafer@ca.ibm.com) - patch (see Bugzilla #250505) *******************************************************************************/ package org.eclipse.ui.forms.widgets; import org.eclipse.core.runtime.ListenerList; import org.eclipse.swt.SWT; import org.eclipse.swt.accessibility.ACC; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.graphics.GC; 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.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.ui.forms.events.HyperlinkEvent; import org.eclipse.ui.forms.events.IHyperlinkListener; import org.eclipse.ui.internal.forms.widgets.FormsResources; /** * This is the base class for custom hyperlink widget. It is responsible for * processing mouse and keyboard events, and converting them into unified * hyperlink events. Subclasses are responsible for rendering the hyperlink in * the client area. * <p> * <dl> * <dt><b>Styles:</b></dt> * <dd>None</dd> * </dl> * * @since 3.0 */ public abstract class AbstractHyperlink extends Canvas { private boolean hasFocus; boolean paintFocus=true; /* * Armed link is one that will activate on a mouse up event, i.e. * it has received a mouse down and mouse still on top of it. */ private boolean armed; private ListenerList<IHyperlinkListener> listeners; /** * Amount of the margin width around the hyperlink (default is 1). */ protected int marginWidth = 1; /** * Amount of the margin height around the hyperlink (default is 1). */ protected int marginHeight = 1; /** * Creates a new hyperlink in the provided parent. * * @param parent * the control parent * @param style * the widget style */ public AbstractHyperlink(Composite parent, int style) { super(parent, style); addListener(SWT.KeyDown, e -> { if (e.character == '\r') { handleActivate(e); } }); addPaintListener(e -> paint(e)); addListener(SWT.Traverse, e -> { switch (e.detail) { case SWT.TRAVERSE_PAGE_NEXT: case SWT.TRAVERSE_PAGE_PREVIOUS: case SWT.TRAVERSE_ARROW_NEXT: case SWT.TRAVERSE_ARROW_PREVIOUS: case SWT.TRAVERSE_RETURN: e.doit = false; return; } e.doit = true; }); Listener listener = e -> { switch (e.type) { case SWT.FocusIn: hasFocus = true; handleEnter(e); break; case SWT.FocusOut: hasFocus = false; handleExit(e); break; case SWT.DefaultSelection: handleActivate(e); break; case SWT.MouseEnter: handleEnter(e); break; case SWT.MouseExit: handleExit(e); break; case SWT.MouseDown: handleMouseDown(e); break; case SWT.MouseUp: handleMouseUp(e); break; case SWT.MouseMove: handleMouseMove(e); break; } }; addListener(SWT.MouseEnter, listener); addListener(SWT.MouseExit, listener); addListener(SWT.MouseDown, listener); addListener(SWT.MouseUp, listener); addListener(SWT.MouseMove, listener); addListener(SWT.FocusIn, listener); addListener(SWT.FocusOut, listener); setCursor(FormsResources.getHandCursor()); } /** * Adds the event listener to this hyperlink. * * @param listener * the event listener to add */ public void addHyperlinkListener(IHyperlinkListener listener) { if (listeners == null) listeners = new ListenerList<>(); listeners.add(listener); } /** * Removes the event listener from this hyperlink. * * @param listener * the event listener to remove */ public void removeHyperlinkListener(IHyperlinkListener listener) { if (listeners == null) return; listeners.remove(listener); } /** * Returns the selection state of the control. When focus is gained, the * state will be <samp>true </samp>; it will switch to <samp>false </samp> * when the control looses focus. * * @return <code>true</code> if the widget has focus, <code>false</code> * otherwise. */ public boolean getSelection() { return hasFocus; } /** * Called when hyperlink is entered. Subclasses that override this method * must call 'super'. */ protected void handleEnter(Event e) { redraw(); if (listeners == null) return; HyperlinkEvent he = new HyperlinkEvent(this, getHref(), getText(), e.stateMask); for (IHyperlinkListener listener : listeners) { listener.linkEntered(he); } } /** * Called when hyperlink is exited. Subclasses that override this method * must call 'super'. */ protected void handleExit(Event e) { // disarm the link; won't activate on mouseup armed = false; redraw(); if (listeners == null) return; HyperlinkEvent he = new HyperlinkEvent(this, getHref(), getText(), e.stateMask); for (IHyperlinkListener listener : listeners) { listener.linkExited(he); } } /** * Called when hyperlink has been activated. Subclasses that override this * method must call 'super'. */ protected void handleActivate(Event e) { // disarm link, back to normal state armed = false; if (listeners == null) return; setCursor(FormsResources.getBusyCursor()); HyperlinkEvent he = new HyperlinkEvent(this, getHref(), getText(), e.stateMask); for (IHyperlinkListener listener : listeners) { listener.linkActivated(he); } if (!isDisposed()) { setCursor(FormsResources.getHandCursor()); triggerAccessible(); } } void triggerAccessible() { getAccessible().setFocus(ACC.CHILDID_SELF); } /** * Sets the object associated with this hyperlink. Concrete implementation * of this class can use if to store text, URLs or model objects that need * to be processed on hyperlink events. * * @param href * the hyperlink object reference */ public void setHref(Object href) { setData("href", href); //$NON-NLS-1$ } /** * Returns the object associated with this hyperlink. * * @see #setHref * @return the hyperlink object reference */ public Object getHref() { return getData("href"); //$NON-NLS-1$ } /** * Returns the textual representation of this hyperlink suitable for showing * in tool tips or on the status line. * * @return the hyperlink text */ public String getText() { return getToolTipText(); } /** * Paints the hyperlink as a reaction to the provided paint event. * * @param gc * graphic context */ protected abstract void paintHyperlink(GC gc); /** * Paints the control as a reaction to the provided paint event. * * @param e * the paint event */ protected void paint(PaintEvent e) { GC gc = e.gc; Rectangle clientArea = getClientArea(); if (clientArea.width == 0 || clientArea.height == 0) return; paintHyperlink(gc); if (paintFocus && hasFocus) { Rectangle carea = getClientArea(); gc.setForeground(getForeground()); gc.drawFocus(0, 0, carea.width, carea.height); } } private void handleMouseDown(Event e) { if (e.button != 1) return; // armed and ready to activate on mouseup armed = true; } private void handleMouseUp(Event e) { if (!armed || e.button != 1) return; Point size = getSize(); // Filter out mouse up events outside // the link. This can happen when mouse is // clicked, dragged outside the link, then // released. if (e.x < 0) return; if (e.y < 0) return; if (e.x >= size.x) return; if (e.y >= size.y) return; handleActivate(e); } private void handleMouseMove(Event e) { // disarm link if we move out of bounds if (armed) { Point size = getSize(); armed = (e.x >= 0 && e.y >= 0 && e.x < size.x && e.y < size.y); } } @Override public void setEnabled (boolean enabled) { boolean needsRedraw = enabled != getEnabled(); super.setEnabled(enabled); if (needsRedraw) redraw(); } }