/*
 * Java-Gnome Bindings Library
 *
 * Copyright 1998-2004 the Java-Gnome Team, all rights reserved.
 *
 * The Java-Gnome bindings library is free software distributed under
 * the terms of the GNU Library General Public License version 2.
 */

package org.gnu.gtk;

import java.util.Vector;

import org.gnu.glib.EventMap;
import org.gnu.glib.EventType;
import org.gnu.glib.GObject;
import org.gnu.glib.Type;
import org.gnu.gtk.event.AdjustmentEvent;
import org.gnu.gtk.event.AdjustmentListener;
import org.gnu.glib.Handle;

/**
 * This object stores geometry information, such as upper and lower bounds, step
 * and page increments, and the size of a page. Other objects use an Adjustment
 * object to store their internal dimension settings. It also is passed as an
 * argument to specify geometry.
 * 
 * @see SpinButton
 * @see Range
 * @see HScrollBar
 * @see VScrollBar
 * @see HScale
 * @see VScale
 */
public class Adjustment extends GtkObject {

    /**
     * The list of objects interested in focus events.
     */
    private Vector adjustmentListeners = null;

    /**
     * Construct a new Adjustment from a handle to a native resource.
     */
    public Adjustment(Handle handle) {
        super(handle);
    }

    /**
     * Internal static factory method to be used by Java-Gnome only.
     */
    public static Adjustment getAdjustment(Handle handle) {
        if (handle == null)
            return null;

        Adjustment obj = (Adjustment) GObject.getGObjectFromHandle(handle);

        if (obj == null)
            obj = new Adjustment(handle);

        return obj;
    }

    /**
     * Constructs a new adjustment
     * 
     * @param value
     *            The initial value.
     * @param lower
     *            The minimum value.
     * @param upper
     *            The maximum value.
     * @param stepIncrement
     *            The step increment.
     * @param pageIncrement
     *            The page increment.
     * @param pageSize
     *            The page size.
     */
    public Adjustment(double value, double lower, double upper,
            double stepIncrement, double pageIncrement, double pageSize) {
        super(gtk_adjustment_new(value, lower, upper, stepIncrement,
                pageIncrement, pageSize));
    }

    /**
     * Sets the GtkAdjustment value.
     */
    public void setValue(double value) {
        gtk_adjustment_set_value(getHandle(), value);
    }

    /**
     * Gets the current value of the adjustment.
     * 
     * @return The current value of the adjustment.
     */
    public double getValue() {
        return gtk_adjustment_get_value(getHandle());
    }

    /**
     * Gets the minimum value of the adjustment
     * 
     * @return The minimum value of the adjustment.
     */
    public double getLower() {
        return getLower(getHandle());
    }

    /**
     * Gets the maximum value of the adjustment. Note that values will be
     * restricted by <code>upper - page-size</code> if the page-size property
     * is nonzero.
     * 
     * @return The maximum value of the adjustment.
     */
    public double getUpper() {
        return getUpper(getHandle());
    }

    /**
     * Gets the step increment of the adjustment.
     * 
     * @return The step increment of the adjustment.
     */
    public double getStepIncrement() {
        return getStepIncrement(getHandle());
    }

    /**
     * Gets the page increment of the adjustment.
     * 
     * @return The page increment of the adjustment.
     */
    public double getPageIncrement() {
        return getPageIncrement(getHandle());
    }

    /**
     * Gets the page size of the adjustment. Note that the page-size is
     * irrelevant and should be set to zero if the adjustment is used for a
     * simple scalar value, e.g. in a SpinButton.
     * 
     * @return The page size of the adjustment.
     */
    public double getPageSize() {
        return getPageSize(getHandle());
    }

    /**
     * Updates the GtkAdjustment value to ensure that the range between lower
     * and upper is in the current page (ie between value and value + pageSize).
     * If the range is larger than the page size, then only the start of it will
     * be in the current page. A "changed" signal will be emitted if the value
     * is changed.
     */
    public void clampPage(double lower, double upper) {
        gtk_adjustment_clamp_page(getHandle(), lower, upper);
    }

    /***************************************************************************
     * Event Handling
     **************************************************************************/

    public Class getEventListenerClass(String signal) {
        Class cls = evtMap.getEventListenerClass(signal);
        if (cls == null)
            cls = super.getEventListenerClass(signal);
        return cls;
    }

    public EventType getEventType(String signal) {
        EventType et = evtMap.getEventType(signal);
        if (et == null)
            et = super.getEventType(signal);
        return et;
    }

    private static EventMap evtMap = new EventMap();
    static {
        addEvents(evtMap);
    }

    private static void addEvents(EventMap anEvtMap) {
        anEvtMap.addEvent("value_changed", "handleValueChanged",
                AdjustmentEvent.Type.VALUE_CHANGED, AdjustmentListener.class);
        anEvtMap.addEvent("changed", "handleChanged",
                AdjustmentEvent.Type.CHANGED, AdjustmentListener.class);
    }

    /**
     * Register an object to handle spin events.
     * 
     * @see org.gnu.gtk.event.SpinListener
     */
    public void addListener(AdjustmentListener listener) {
        // Don't add the listener a second time if it is in the Vector.
        int i = findListener(adjustmentListeners, listener);
        if (i == -1) {
            if (null == adjustmentListeners) {
                evtMap.initialize(this, AdjustmentEvent.Type.VALUE_CHANGED);
                evtMap.initialize(this, AdjustmentEvent.Type.CHANGED);
                adjustmentListeners = new Vector();
            }
            adjustmentListeners.addElement(listener);
        }
    }

    /**
     * Removes a listener
     * 
     * @see #addListener(AdjustmentListener)
     */
    public void removeListener(AdjustmentListener listener) {
        int i = findListener(adjustmentListeners, listener);
        if (i > -1)
            adjustmentListeners.remove(i);
        if (0 == adjustmentListeners.size()) {
            evtMap.uninitialize(this, AdjustmentEvent.Type.VALUE_CHANGED);
            evtMap.uninitialize(this, AdjustmentEvent.Type.CHANGED);
            adjustmentListeners = null;
        }
    }

    protected void fireAdjustmentEvent(AdjustmentEvent event) {
        if (null == adjustmentListeners)
            return;
        int size = adjustmentListeners.size();
        int i = 0;
        while (i < size) {
            AdjustmentListener al = (AdjustmentListener) adjustmentListeners
                    .elementAt(i);
            al.adjustmentEvent(event);
            i++;
        }
    }

    private void handleValueChanged() {
        fireAdjustmentEvent(new AdjustmentEvent(this,
                AdjustmentEvent.Type.VALUE_CHANGED));
    }

    public void handleChanged() {
        fireAdjustmentEvent(new AdjustmentEvent(this,
                AdjustmentEvent.Type.CHANGED));
    }

    /**
     * Give us a way to locate a specific listener in a Vector.
     * 
     * @param list
     *            The Vector of listeners to search.
     * @param listener
     *            The object that is to be located in the Vector.
     * @return Returns the index of the listener in the Vector, or -1 if the
     *         listener is not contained in the Vector.
     */
    protected static int findListener(Vector list, Object listener) {
        if (null == list || null == listener)
            return -1;
        return list.indexOf(listener);
    }

    /**
     * Retrieve the runtime type used by the GLib library.
     */
    public static Type getType() {
        return new Type(gtk_adjustment_get_type());
    }

    public void setLower(double lower) {
        setDoubleProperty("lower", lower);
    }

    public void setPageIncrement(double pageIncrement) {
        setDoubleProperty("page-increment", pageIncrement);
    }

    public void setPageSize(double pageSize) {
        setDoubleProperty("page-size", pageSize);
    }

    public void setStepIncrement(double stepIncrement) {
        setDoubleProperty("step-increment", stepIncrement);
    }

    public void setUpper(double upper) {
        setDoubleProperty("upper", upper);
    }

    native static final protected double getLower(Handle cptr);

    native static final protected double getUpper(Handle cptr);

    native static final protected double getStepIncrement(Handle cptr);

    native static final protected double getPageIncrement(Handle cptr);

    native static final protected double getPageSize(Handle cptr);

    native static final protected int gtk_adjustment_get_type();

    native static final protected Handle gtk_adjustment_new(double value,
            double lower, double upper, double stepIncrement,
            double pageIncrement, double pageSize);

    native static final protected void gtk_adjustment_changed(Handle adjustment);

    native static final protected void gtk_adjustment_value_changed(
            Handle adjustment);

    native static final protected void gtk_adjustment_clamp_page(
            Handle adjustment, double lower, double upper);

    native static final protected double gtk_adjustment_get_value(
            Handle adjustment);

    native static final protected void gtk_adjustment_set_value(
            Handle adjustment, double value);

}
