/******************************************************************************* * Copyright (c) 2000, 2005 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.draw2d; import java.util.Iterator; import org.eclipse.draw2d.geometry.Rectangle; /** * A Clickable responds to mouse clicks in some way (determined by a ClickBehavior) and * fires action events. No visual appearance of feedback is offered. Depends on a model * holder and an event handler which understands the model and updates the model * accordingly. {@link ButtonModel} is used by default. Any figure can be set as contents * to a Clickable. Clickable->EventHandler->Model->ModelObserver->Listeners of actions. */ public class Clickable extends Figure { private static final int ROLLOVER_ENABLED_FLAG = Figure.MAX_FLAG << 1, STYLE_BUTTON_FLAG = Figure.MAX_FLAG << 2, STYLE_TOGGLE_FLAG = Figure.MAX_FLAG << 3; /** * The highest reserved flag used by this class */ protected static int MAX_FLAG = STYLE_TOGGLE_FLAG; /** * Style constant that defines a push button. The button will be pressed when the mouse is * pressed on top of it. The button will be released when the mouse is released or is move * off of the button. * */ public static final int STYLE_BUTTON = STYLE_BUTTON_FLAG; /** * Style constant that defines a toggle button. The button will toggle between 2 states * when the mouse is clicked on the button. */ public static final int STYLE_TOGGLE = STYLE_TOGGLE_FLAG; /** * An action is performed every time the mouse is released. */ public static final int DEFAULT_FIRING = 0; /** * Firing starts as soon as the mouse is pressed on this Clickable, and keeps firing at * prefixed intervals until the mouse is released. */ public static final int REPEAT_FIRING = 1; /** * Observes the model for action and state changes. * * @see ActionListener * @see ChangeListener */ static interface ModelObserver extends ActionListener, ChangeListener { } private ClickableEventHandler eventHandler; private ButtonModel model; private ModelObserver modelObserver; { init(); setRequestFocusEnabled(true); setFocusTraversable(true); } /** * Constructs a Clickable with no contents. */ public Clickable() { } /** * Constructs a Clickable whose contents are provided as input. The content figure * occupies the entire region of the Clickable. * * @param contents The content figure */ public Clickable(IFigure contents) { this(contents, 0); } /** * Constructs a Clickable whose contents are provided as input. The content figure * occupies the entire region of the Clickable. Sets the style to the given <i>style</i> * (either {@link #STYLE_BUTTON} or {@link #STYLE_TOGGLE}). * * @param contents The content figure * @param style The button style */ public Clickable(IFigure contents, int style) { setContents(contents); setStyle(style); } /** * Adds the given listener to the list of action listeners of this Figure. Listener is * called whenever an action is performed. * * @param listener The ActionListener to be added * @since 2.0 */ public void addActionListener(ActionListener listener) { addListener(ActionListener.class, listener); } /** * Adds the given listener to the list of state change listeners of this figure. A * ChangeListener is informed if there is any state change in the model requiring action * by the listener. * * @param listener The ChangeListener to be added * @since 2.0 */ public void addChangeListener(ChangeListener listener) { addListener(ChangeListener.class, listener); } /** * Returns a newly created ButtonModel as the default model to be used by this Clickable * based on the button style. * * @return The model to be used by default * @since 2.0 */ protected ButtonModel createDefaultModel() { if (isStyle(STYLE_TOGGLE)) return new ToggleModel(); else return new ButtonModel(); } /** * Returns a newly created event handler for this Clickable and its model. * * @return The event handler * @since 2.0 */ protected ClickableEventHandler createEventHandler() { return new ClickableEventHandler(); } /** * Returns a newly created model observer which listens to the model, and fires any action * or state changes. A ModelObserver holds both an action listener and a state change * listener. * * @return The newly created model observer * @since 2.0 */ protected ModelObserver createModelObserver() { return new ModelObserver() { public void actionPerformed(ActionEvent action) { fireActionPerformed(); } public void handleStateChanged(ChangeEvent change) { fireStateChanged(change); } }; } /** * Fires an action performed event. * * @since 2.0 */ public void doClick() { fireActionPerformed(); } /** * Called when there has been an action performed by this Clickable, which is determined * by the model. Notifies all ActionListener type listeners of an action performed. * * @since 2.0 */ protected void fireActionPerformed() { ActionEvent action = new ActionEvent(this); Iterator listeners = getListeners(ActionListener.class); while (listeners.hasNext()) ((ActionListener)listeners.next()) //Leave newline for debug stepping .actionPerformed(action); } /** * Called when there has been a change of state in the model of this clickable. Notifies * all ChangeListener type listeners of the state change. * * @param modelChange The ChangeEvent * @since 2.0 */ protected void fireStateChanged(ChangeEvent modelChange) { ChangeEvent change = new ChangeEvent(this, modelChange.getPropertyName()); Iterator listeners = getListeners(ChangeListener.class); while (listeners.hasNext()) ((ChangeListener)listeners.next()) //Leave newline for debug stepping .handleStateChanged(change); } /** * Returns the behavior model used by this Clickable. * * @return The model used by this Clickable * @since 2.0 */ public ButtonModel getModel() { return model; } /** * Adds the given ClickableEventHandler to this clickable. A ClickableEventHandler * should be a MouseListener, MouseMotionListener, ChangeListener, KeyListener, and * FocusListener. * * @param handler The new event handler * @since 2.0 */ protected void hookEventHandler(ClickableEventHandler handler) { if (handler == null) return; addMouseListener(handler); addMouseMotionListener(handler); addChangeListener(handler); addKeyListener(handler); addFocusListener(handler); } /** * Initializes this Clickable by setting a default model and adding a clickable event * handler for that model. * * @since 2.0 */ protected void init() { setModel(createDefaultModel()); setEventHandler(createEventHandler()); } /** * Returns <code>true</code> if rollover feedback is enabled. * * @return <code>true</code> rollover feedback is enabled * @since 2.0 */ public boolean isRolloverEnabled() { return (flags & ROLLOVER_ENABLED_FLAG) != 0; } /** * Returns <code>true</code> if this Clickable is in a selected state. The model is the * one which holds all this state based information. * * @return <code>true</code> if this Clickable is in a selected state * @since 2.0 */ public boolean isSelected() { return getModel().isSelected(); } /** * Returns <code>true</code> if this Clickable's style is the same as the passed style. * * @param style The style to be checked * @return <code>true</code> if this Clickable's style is the same as the passed style * @since 2.0 */ public boolean isStyle(int style) { return ((style & flags) == style); } /** * If this Clickable has focus, this method paints a focus rectangle. * * @param graphics Graphics handle for painting */ protected void paintBorder(Graphics graphics) { super.paintBorder(graphics); if (hasFocus()) { graphics.setForegroundColor(ColorConstants.black); graphics.setBackgroundColor(ColorConstants.white); Rectangle area = getClientArea(); if (isStyle(STYLE_BUTTON)) graphics.drawFocus(area.x, area.y, area.width, area.height); else graphics.drawFocus(area.x, area.y, area.width - 1, area.height - 1); } } /** * Paints the area of this figure excluded by the borders. Induces a (1,1) pixel shift in * the painting if the mouse is armed, giving it the pressed appearance. * * @param graphics Graphics handle for painting * @since 2.0 */ protected void paintClientArea(Graphics graphics) { if (isStyle(STYLE_BUTTON) && (getModel().isArmed() || getModel().isSelected())) { graphics.translate(1, 1); graphics.pushState(); super.paintClientArea(graphics); graphics.popState(); graphics.translate(-1, -1); } else super.paintClientArea(graphics); } /** * Removes the given listener from the list of ActionListener's of this Clickable. * * @param listener Listener to be removed from this figure * @since 2.0 */ public void removeActionListener(ActionListener listener) { removeListener(ActionListener.class, listener); } /** * Removes the given listener from the list of ChangeListener's of this clickable. * * @param listener Listener to be removed from this figure * @since 2.0 */ public void removeChangeListener(ChangeListener listener) { removeListener(ChangeListener.class, listener); } /** * Sets the Figure which is the contents of this Clickable. This Figure occupies the * entire clickable region. * * @param contents Contents of the clickable * @since 2.0 */ protected void setContents(IFigure contents) { setLayoutManager(new StackLayout()); if (getChildren().size() > 0) remove((IFigure)getChildren().get(0)); add(contents); } /** * @see org.eclipse.draw2d.IFigure#setEnabled(boolean) */ public void setEnabled(boolean value) { if (isEnabled() == value) return; super.setEnabled(value); getModel().setEnabled(value); setChildrenEnabled(value); } /** * Sets the event handler which interacts with the model to determine the behavior of this * Clickable. * * @param h Event handler for this clickable * @since 2.0 */ public void setEventHandler(ClickableEventHandler h) { if (eventHandler != null) unhookEventHandler(eventHandler); eventHandler = h; if (eventHandler != null) hookEventHandler(eventHandler); } /** * Determines how this clickable is to fire notifications to its listeners. In the default * firing method ({@link #DEFAULT_FIRING}), an action is performed every time the mouse * is released. In the repeat firing method ({@link #REPEAT_FIRING}), firing starts as * soon as it is pressed on this clickable, and keeps firing at prefixed intervals until * the mouse is released. * * @param type Type of firing * @since 2.0 */ public void setFiringMethod(int type) { getModel().setFiringBehavior(type); } /** * Sets the model to be used by this clickable for its state and behavior determination. * This clickable removes any observers from the previous model before adding new ones to * the new model. * * @param model The new model of this Clickable * @since 2.0 */ public void setModel(ButtonModel model) { if (this.model != null) { this.model.removeChangeListener(modelObserver); this.model.removeActionListener(modelObserver); modelObserver = null; } this.model = model; if (model != null) { modelObserver = createModelObserver(); model.addActionListener(modelObserver); model.addChangeListener(modelObserver); } } /** * Enables or disables rollover feedback of this figure. Generally used in conjunction * with the model to determine if feedback is to be shown. * * @param value The rollover state to be set * @since 2.0 */ public void setRolloverEnabled(boolean value) { if (isRolloverEnabled() == value) return; setFlag(ROLLOVER_ENABLED_FLAG, value); repaint(); } /** * Sets the selected state of this Clickable. Since the model is responsible for all state * based information, it is informed of the state change. Extending classes can choose * selection information, if they do not represent any selection. * * @param value New selected state of this clickable. * @see #isSelected() * @since 2.0 */ public void setSelected(boolean value) { getModel().setSelected(value); } /** * Sets this Clickable's style to the passed value, either {@link #STYLE_BUTTON} or * {@link #STYLE_TOGGLE}. * * @param style The button style * @since 2.0 */ public void setStyle(int style) { if ((style & STYLE_BUTTON) != 0) { setFlag(STYLE_BUTTON_FLAG, true); if (!(getBorder() instanceof ButtonBorder)) setBorder(new ButtonBorder()); setOpaque(true); } else { setFlag(STYLE_BUTTON_FLAG, false); setOpaque(false); } if ((style & STYLE_TOGGLE) != 0) { setFlag(STYLE_TOGGLE_FLAG, true); setModel(createDefaultModel()); } } /** * Removes the given ClickableEventHandler containing listeners from this Clickable. * * @param handler The event handler to be removed * @since 2.0 */ protected void unhookEventHandler(ClickableEventHandler handler) { if (handler == null) return; removeMouseListener(handler); removeMouseMotionListener(handler); removeChangeListener(handler); } }