view dwtx/draw2d/Clickable.d @ 98:95307ad235d9

Added Draw2d code, still work in progress
author Frank Benoit <benoit@tionex.de>
date Sun, 03 Aug 2008 00:52:14 +0200
parents
children
line wrap: on
line source

/*******************************************************************************
 * 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
 * Port to the D programming language:
 *     Frank Benoit <benoit@tionex.de>
 *******************************************************************************/
module dwtx.draw2d.Clickable;

import dwt.dwthelper.utils;

import dwtx.dwtxhelper.Collection;

import dwtx.draw2d.geometry.Rectangle;
import dwtx.draw2d.Figure;
import dwtx.draw2d.ActionListener;
import dwtx.draw2d.ActionEvent;
import dwtx.draw2d.ChangeListener;
import dwtx.draw2d.ClickableEventHandler;
import dwtx.draw2d.IFigure;
import dwtx.draw2d.ButtonModel;
import dwtx.draw2d.ChangeEvent;
import dwtx.draw2d.Graphics;
import dwtx.draw2d.ToggleModel;
import dwtx.draw2d.StackLayout;
import dwtx.draw2d.ColorConstants;
import dwtx.draw2d.ButtonBorder;


/**
 * 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
    : Figure
{

private static const 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 const 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 const int STYLE_TOGGLE = STYLE_TOGGLE_FLAG;

/**
 * An action is performed every time the mouse is released.
 */
public static const 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 const int REPEAT_FIRING  = 1;

/**
 * Observes the model for action and state changes.
 *
 * @see ActionListener
 * @see ChangeListener
 */
/+static+/ interface ModelObserver
    : ActionListener, ChangeListener
{ }

private ClickableEventHandler eventHandler;

private ButtonModel model;

private ModelObserver modelObserver;

private void instanceInit(){
    init();
    setRequestFocusEnabled(true);
    setFocusTraversable(true);
}

/**
 * Constructs a Clickable with no contents.
 */
public this() {
    instanceInit();
}

/**
 * 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 this(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 this(IFigure contents, int style) {
    instanceInit();
    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.classinfo, cast(Object)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.classinfo, cast(Object)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 class() 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.classinfo);
    while (listeners.hasNext())
        (cast(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.classinfo);
    while (listeners.hasNext())
        (cast(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 is 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 bool isRolloverEnabled() {
    return (flags & ROLLOVER_ENABLED_FLAG) !is 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 bool 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 bool isStyle(int style) {
    return ((style & flags) is 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.classinfo, cast(Object)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.classinfo, cast(Object)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(cast(IFigure)getChildren().get(0));
    add(contents);
}

/**
 * @see dwtx.draw2d.IFigure#setEnabled(bool)
 */
public void setEnabled(bool value) {
    if (isEnabled() is 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 !is null)
        unhookEventHandler(eventHandler);
    eventHandler = h;
    if (eventHandler !is 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 !is null) {
        this.model.removeChangeListener(modelObserver);
        this.model.removeActionListener(modelObserver);
        modelObserver = null;
    }
    this.model = model;
    if (model !is 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(bool value) {
    if (isRolloverEnabled() is 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(bool 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) !is 0) {
        setFlag(STYLE_BUTTON_FLAG, true);
        if (!(null !is cast(ButtonBorder)getBorder() ))
            setBorder(new ButtonBorder());
        setOpaque(true);
    } else {
        setFlag(STYLE_BUTTON_FLAG, false);
        setOpaque(false);
    }

    if ((style & STYLE_TOGGLE) !is 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 is null)
        return;
    removeMouseListener(handler);
    removeMouseMotionListener(handler);
    removeChangeListener(handler);
}

}