// Slider.java
// By Ned Etcode
// Copyright 1995, 1996 Netscape Communications Corp. All rights reserved.

package netscape.application;

import netscape.util.*;


/** View subclass that implements a "slider," an analog control device that
  * allows the user to drag a knob to select the Slider's value. As the
  * Slider changes its value, it sends a command to its Target.
  */
public class Slider extends View implements Component {
    Target      target;
    Border      border = BezelBorder.loweredBezel();
    Image       backgroundImage, knobImage;
    Color       backgroundColor = Color.gray;
    String      command;
    int         value, minValue, maxValue, sliderX, knobHeight,
                grooveHeight, clickOffset, imageDisplayStyle;
    boolean     enabled = true;

    static Vector    _fieldDescription;

    private static final int  SLIDER_KNOB_WIDTH = 6;

    final static String         displayNameArray[] = { "Centered", "Tiled",
                                                       "Scaled" };
    final static int            displayValueArray[] = { Image.CENTERED,
                                                        Image.TILED,
                                                        Image.SCALED };

    static final String         TARGET_KEY = "target",
                                BACKGROUND_IMAGE_KEY = "backgroundImage",
                                KNOB_IMAGE_KEY = "knobImage",
                                BACKGROUNDC_KEY = "backgroundColor",
                                COMMAND_KEY = "command",
                                VALUE_KEY = "value",
                                MINVALUE_KEY = "minValue",
                                MAXVALUE_KEY = "maxValue",
                                KNOB_HEIGHT_KEY = "knobHeight",
                                GROOVE_HEIGHT_KEY = "grooveHeight",
                                BORDER_KEY = "border",
                                ENABLED_KEY = "enabled",
                                IMAGEDISP_KEY = "imageDisplayStyle";


    /* constructors */

    /** Constructs a Slider with origin (<b>0</b>, <b>0</b>) and zero
      * width and height.
      */
    public Slider() {
        this(0, 0, 0, 0);
    }

    /** Constructs a Slider with bounds <B>rect</B>.
      */
    public Slider(Rect rect) {
        this(rect.x, rect.y, rect.width, rect.height);
    }

    /** Constructs a Slider with bounds
      * (<B>x</B>, <B>y</B>, <B>width</B>, <B>height</B>).
      */
    public Slider(int x, int y, int width, int height) {
        super(x, y, width, height);

        knobHeight = 13;
        grooveHeight = 8;

        minValue = 0;
        maxValue = 255;

        setValue(0);
    }

    private static int parseInt(String s) {
        try {
            return Integer.parseInt(s);
        } catch (NumberFormatException e) {
            return 0;
        }
    }

/* attributes/actions */

    /** Overridden to return the Slider's minimum size.
      */
    public Size minSize() {
        if (_minSize != null) {
            return new Size(_minSize);
        }

        return new Size(knobWidth() + 2,
                (knobHeight > grooveHeight ? knobHeight : grooveHeight));
    }

    /** Sets the Slider's minimum and maximum values.  Modifies the Slider's
      * current value to fit within this new range (if outside of the range).
      */
    public void setLimits(int minValue, int maxValue) {
        int     realValue;

        if (minValue >= maxValue) {
            return;
        }

        this.minValue = minValue;
        this.maxValue = maxValue;

        /* clip */
        if (value < minValue) {
            value = minValue;
        } else if (value > maxValue) {
            value = maxValue;
        }

        /* position knob and redraw */
        realValue = value;
        if (value > 0) {
            value--;
        } else {
            value++;
        }
        setValue(realValue);
    }

    /** Returns the Slider's minimum value.
      * @see #setLimits
      */
    public int minValue() {
        return minValue;
    }

    /** Returns the Slider's maximum value.
      * @see #setLimits
      */
    public int maxValue() {
        return maxValue;
    }

    /** Sets the Image displayed by the Slider as its "knob."  If set,
      * the Slider will display this Image rather than draw a bezeled
      * knob.
      */
    public void setKnobImage(Image anImage) {
        if (anImage == knobImage) {
            return;
        }

        knobImage = anImage;
        if (anImage != null) {
            knobHeight = anImage.height();
        }

        /* reposition knob and redraw */
        setValue(value);
    }

    /** Returns the Image displayed by the Slider as its "knob."
      * @see #setKnobImage
      */
    public Image knobImage() {
        return knobImage;
    }

    /** Sets the Color the Slider displays within its groove if it has no
      * image.
      */
    public void setBackgroundColor(Color aColor) {
        if (backgroundColor != null) {
            backgroundColor = aColor;
        }
    }

    /** Returns the Color the Slider displays within its groove.
      * @see #setBackgroundColor
      */
    public Color backgroundColor() {
        return backgroundColor;
    }

    /** Sets the Image the Slider displays within its groove.
      */
    public void setImage(Image anImage) {
        backgroundImage = anImage;
    }

    /** Returns the Image the Slider displays within its groove.
      * @see #setImage
      */
    public Image image() {
        return backgroundImage;
    }

    /** Tells the Slider how to display its Image (<b>Image.CENTERED</b>,
      * <b>Image.TILED</b>, or <b>Image.SCALED</b>).
      * @see #setImage
      */
    public void setImageDisplayStyle(int aStyle) {
        if (aStyle != Image.CENTERED && aStyle != Image.TILED &&
            aStyle != Image.SCALED) {
            throw new InconsistencyException("Unknown image display style: " +
                aStyle);
        }

        imageDisplayStyle = aStyle;
        draw();
    }

    /** Returns the style the Slider uses to display its Image.
      * @see #setImageDisplayStyle
      */
    public int imageDisplayStyle() {
        return imageDisplayStyle;
    }

    /** Sets the Slider's Target, the object the Slider notifies when the
      * user changes its value.
      * @see Target
      */
    public void setTarget(Target aTarget) {
        target = aTarget;
    }

    /** Returns the Slider's Target.
      * @see #setTarget
      */
    public Target target() {
        return target;
    }

    /** Sets the command the Slider sends to its Target.
      * @see #setTarget
      */
    public void setCommand(String command) {
        this.command = command;
    }

    /** Returns the command the Slider sends to its Target.
      * @see #setCommand
      */
    public String command() {
        return command;
    }

    /** Called by the Slider to send its command to its Target.
      * @see #setTarget
      */
    public void sendCommand() {
        if (target != null) {
            target.performCommand(command, this);
        }
    }

    /** Sets the Slider's "enabled" state and then calls the Slider's
      * <B>draw()</B> method to redraw it.  A disabled Slider does not
      * respond to mouse clicks or drags.
      */
    public void setEnabled(boolean enabled) {
        if (enabled != this.enabled) {
            this.enabled = enabled;

            draw();
        }
    }

    /** Returns <B>true</b> is the Slider is enabled.
      * @see #setEnabled
      */
    public boolean isEnabled() {
        return enabled;
    }

    /** Returns the width of the Slider's knob.  Override when subclassing
      * if you want to draw a special knob.
      */
    public int knobWidth() {
        if (knobImage != null) {
            return knobImage.width();
        }

        return SLIDER_KNOB_WIDTH;
    }

    /** Sets the Slider's knob height.
      */
    public void setKnobHeight(int anInt) {
        if (anInt > 0) {
            knobHeight = anInt;
        }
    }

    /** Returns the Slider's knob height.
      * @see #setKnobHeight
      */
    public int knobHeight() {
        return knobHeight;
    }

    /** Sets the Slider's groove height.
      */
    public void setGrooveHeight(int anInt) {
        if (anInt > 0) {
            grooveHeight = anInt;
        }
    }

    /** Returns the Slider's groove height.
      * @see #setKnobHeight
      */
    public int grooveHeight() {
        return grooveHeight;
    }

    /** Sets the Border the Slider draws around its groove.
      */
    public void setBorder(Border newBorder) {
        if (newBorder == null)
            newBorder = EmptyBorder.emptyBorder();

        border = newBorder;
        setValue(value);
    }

    /** Returns the Border the Slider draws around its groove.
      * @see #setBorder
      */
    public Border border() {
        return border;
    }

    void redrawView(int oldSliderX) {
        Rect    tmpRect;

        if (sliderX == oldSliderX) {
            return;
        }

        if (sliderX < oldSliderX) {
            tmpRect = Rect.newRect(sliderX, 0,
                                   oldSliderX - sliderX + knobWidth(),
                                   bounds.height);
        } else {
            tmpRect = Rect.newRect(oldSliderX, 0,
                                   sliderX - oldSliderX + knobWidth(),
                                   bounds.height);
        }

        draw(tmpRect);

        Rect.returnRect(tmpRect);
    }

    /** Sets the Slider's value and redraws the Slider.  If less than
      * <b>minValue()</b> or greater than <b>maxValue()</b>, does nothing.
      */
    public void setValue(int aValue) {
        Rect    updateRect;
        float   normalizedMaxValue;
        int     oldX, normalizedValue;

        if (aValue < minValue || aValue > maxValue) {
            return;
        }

        if (value != aValue) {
            value = aValue;

            oldX = sliderX;

            normalizedValue = value - minValue;
            normalizedMaxValue = (maxValue - minValue) * 1.0f;
            sliderX = (int)((normalizedValue / normalizedMaxValue) *
                        (bounds.width - knobWidth()));

            redrawView(oldX);
        }
    }

    /** Returns the Slider's value.
      * @see #setValue
      */
    public int value() {
        return value;
    }

    /** Draws the Slider's groove, which includes its Border and contents.
      * You never call this method directly, but should override it to
      * implement custom groove drawing.
      */
    public void drawViewGroove(Graphics g) {
        Rect    tmpRect;
        int     yOffset;

        yOffset = (bounds.height - grooveHeight) / 2;
        tmpRect = Rect.newRect(0, yOffset, bounds.width, grooveHeight);

        border.drawInRect(g, tmpRect);
        border.computeInteriorRect(tmpRect, tmpRect);

        if (backgroundImage != null) {
            g.pushState();
            g.setClipRect(tmpRect);
            backgroundImage.drawWithStyle(g, tmpRect, imageDisplayStyle);
            g.popState();
        } else {
            if (!enabled) {
                g.setColor(Color.lightGray);
            } else {
                g.setColor(backgroundColor);
            }

            g.fillRect(tmpRect);
        }

        Rect.returnRect(tmpRect);
    }

    /** Returns a Rect containing the Slider's knob.
      */
    public Rect knobRect() {
        int     y;

        if (knobImage != null) {
            y = (bounds.height - knobImage.height()) / 2;
        } else {
            y = (bounds.height - grooveHeight) / 2 -
                (knobHeight - grooveHeight) / 2;
        }
        return Rect.newRect(sliderX, y, knobWidth(), knobHeight);
    }

    /** Draws the Slider's knob.  You never call this method directly, but
      * should override it to implement custom knob drawing.
      */
    public void drawViewKnob(Graphics g) {
        Rect    knobRect;

        knobRect = knobRect();
        if (knobImage != null) {
            knobImage.drawAt(g, knobRect.x, knobRect.y);
        } else {
            BezelBorder.raisedButtonBezel().drawInRect(g, knobRect);
            g.setColor(Color.lightGray);
            g.fillRect(knobRect.x + 2, knobRect.y + 2, knobRect.width - 4,
                       knobRect.height - 4);
        }
        Rect.returnRect(knobRect);
    }

    /** Draws the Slider.  Calls <b>drawViewGroove()</b> and
      * <b>drawViewKnob()</b>.
      * @see #drawViewGroove
      * @see #drawViewKnob
      */
    public void drawView(Graphics g) {
        drawViewGroove(g);
        drawViewKnob(g);
    }

    int _positionFromPoint(int point) {
        int     knobWidth;

        knobWidth = knobWidth();
        point -= knobWidth / 2;

        if (point < 0) {
            point = 0;
        } else if (point > bounds.width - knobWidth) {
            point = bounds.width - knobWidth;
        }

        return point;
    }

    void _moveSliderTo(int point) {
        int     oldX;

        oldX = sliderX;

        sliderX = _positionFromPoint(point);
        value = (int)((((maxValue - minValue) * 1.0) /
                            (float)(bounds.width - knobWidth())) * sliderX) +
                                                                    minValue;

        redrawView(oldX);
    }

    /** Overridden to reposition the Slider's knob when resized.
      */
    public void didSizeBy(int deltaWidth, int deltaHeight) {
        setValue(value);
        super.didSizeBy(deltaWidth, deltaHeight);
    }

    /** Overridden to implement knob dragging and send the Slider's command
      * to its Target.
      * @see #sendCommand
      */
    public boolean mouseDown(MouseEvent event) {
        Rect    knobRect;

        if (!enabled) {
            return false;
        }

        knobRect = knobRect();
        if (knobRect.contains(event.x, event.y)) {
            clickOffset = _positionFromPoint(event.x) - sliderX;
        } else {
            clickOffset = 0;
            _moveSliderTo(event.x);
        }

        sendCommand();

        return true;
    }

    /** Overridden to implement knob dragging and send the Slider's command
      * to its Target.
      * @see #sendCommand
      */
    public void mouseDragged(MouseEvent event) {
        _moveSliderTo(event.x - clickOffset);

        sendCommand();
    }



/* archiving */


    /** Describes the Slider class' information.
     * @see Codable#describeClassInfo
     */
    public void describeClassInfo(ClassInfo info) {
        super.describeClassInfo(info);

        info.addClass("netscape.application.Slider", 1);
        info.addField(TARGET_KEY, OBJECT_TYPE);
        info.addPublicField(BACKGROUND_IMAGE_KEY, OBJECT_TYPE);
        info.addPublicField(KNOB_IMAGE_KEY, OBJECT_TYPE);
        info.addPublicField(BACKGROUNDC_KEY, OBJECT_TYPE);
        info.addPublicField(BORDER_KEY, OBJECT_TYPE);
        info.addPublicField(COMMAND_KEY, STRING_TYPE);
        info.addPublicField(VALUE_KEY, INT_TYPE);
        info.addPublicField(MINVALUE_KEY, INT_TYPE);
        info.addPublicField(MAXVALUE_KEY, INT_TYPE);
        info.addPublicField(KNOB_HEIGHT_KEY, INT_TYPE);
        info.addPublicField(GROOVE_HEIGHT_KEY, INT_TYPE);
        info.addPublicField(IMAGEDISP_KEY, INT_TYPE);
        info.addPublicField(ENABLED_KEY, BOOLEAN_TYPE);
    }

    /** Encodes the Slider instance.
     * @see Codable#encode
     */
    public void encode(Encoder encoder) throws CodingException {
        super.encode(encoder);

        encoder.encodeObject(TARGET_KEY, (Codable)target);
        encoder.encodeObject(BACKGROUND_IMAGE_KEY, backgroundImage);
        encoder.encodeObject(KNOB_IMAGE_KEY, knobImage);
        encoder.encodeObject(BACKGROUNDC_KEY, backgroundColor);

        encoder.encodeString(COMMAND_KEY, command);

        encoder.encodeInt(VALUE_KEY, value);
        encoder.encodeInt(MINVALUE_KEY, minValue);
        encoder.encodeInt(MAXVALUE_KEY, maxValue);
        encoder.encodeInt(KNOB_HEIGHT_KEY, knobHeight);
        encoder.encodeInt(GROOVE_HEIGHT_KEY, grooveHeight);

        if (border instanceof EmptyBorder)
            encoder.encodeObject(BORDER_KEY, null);
        else
            encoder.encodeObject(BORDER_KEY, border);

        encoder.encodeBoolean(ENABLED_KEY, enabled);
        encoder.encodeInt(IMAGEDISP_KEY, imageDisplayStyle);
    }

    /** Decodes the Slider instance.
     * @see Codable#decode
     */
    public void decode(Decoder decoder) throws CodingException {
        super.decode(decoder);

        target = (Target)decoder.decodeObject(TARGET_KEY);
        backgroundImage = (Image)decoder.decodeObject(BACKGROUND_IMAGE_KEY);
        knobImage = (Image)decoder.decodeObject(KNOB_IMAGE_KEY);
        backgroundColor = (Color)decoder.decodeObject(BACKGROUNDC_KEY);

        command = decoder.decodeString(COMMAND_KEY);

        value = decoder.decodeInt(VALUE_KEY);
        minValue = decoder.decodeInt(MINVALUE_KEY);
        maxValue = decoder.decodeInt(MAXVALUE_KEY);
        knobHeight = decoder.decodeInt(KNOB_HEIGHT_KEY);
        grooveHeight = decoder.decodeInt(GROOVE_HEIGHT_KEY);

        setBorder((Border)decoder.decodeObject(BORDER_KEY));

        enabled = decoder.decodeBoolean(ENABLED_KEY);
        imageDisplayStyle = decoder.decodeInt(IMAGEDISP_KEY);
    }

    /** Finishes the Slider instance decoding.  Initializes the Slider's value.
     * @see Codable#finishDecoding
     */
    public void finishDecoding() throws CodingException {
        int     realValue;

        super.finishDecoding();

        realValue = value;
        if (value > 0) {
            value--;
        } else {
            value++;
        }
        setValue(realValue);
    }



/* components */


    /** @private */
    public void describeBuilderInfo(BuilderInfo info) {
        info.addFieldInfo(KNOB_IMAGE_KEY, BuilderInfo.IMAGE);
        info.addFieldInfo(BACKGROUND_IMAGE_KEY, BuilderInfo.IMAGE,
                          BuilderInfo.ALTERNATE_KEY);
        info.addFieldInfo(BACKGROUNDC_KEY, BuilderInfo.COLOR);
        info.addFieldInfo(BORDER_KEY, BuilderInfo.BORDER);

        info.addEventInfo(TARGET_KEY, COMMAND_KEY);

        info.addFieldEnumeration(IMAGEDISP_KEY, displayNameArray,
                                 displayValueArray, BuilderInfo.POPUP);
    }

    /** @private */
    public void builderEncode(Encoder encoder) throws CodingException {
        encoder.encodeObject(TARGET_KEY, (Codable)target);
        encoder.encodeObject(BACKGROUND_IMAGE_KEY, backgroundImage);
        encoder.encodeObject(KNOB_IMAGE_KEY, knobImage);
        encoder.encodeObject(BACKGROUNDC_KEY, backgroundColor);

        encoder.encodeString(COMMAND_KEY, command);

        encoder.encodeInt(VALUE_KEY, value);
        encoder.encodeInt(MINVALUE_KEY, minValue);
        encoder.encodeInt(MAXVALUE_KEY, maxValue);
        encoder.encodeInt(KNOB_HEIGHT_KEY, knobHeight);
        encoder.encodeInt(GROOVE_HEIGHT_KEY, grooveHeight);

        encoder.encodeObject(BORDER_KEY, border);

        encoder.encodeBoolean(ENABLED_KEY, enabled);
        encoder.encodeInt(IMAGEDISP_KEY, imageDisplayStyle);
    }

    /** @private */
    public void builderDecode(Decoder decoder, String changedField) throws
                                                            CodingException {
        backgroundImage = (Image)decoder.decodeObject(BACKGROUND_IMAGE_KEY);
        setKnobImage((Image)decoder.decodeObject(KNOB_IMAGE_KEY));
        backgroundColor = (Color)decoder.decodeObject(BACKGROUNDC_KEY);

        target = (Target)decoder.decodeObject(TARGET_KEY);
        command = decoder.decodeString(COMMAND_KEY);

        if (changedField.equals(BORDER_KEY)) {
            setBorder((Border)decoder.decodeObject(BORDER_KEY));
        } else if (changedField.equals(KNOB_HEIGHT_KEY)) {
            setKnobHeight(decoder.decodeInt(KNOB_HEIGHT_KEY));
        } else if (changedField.equals(GROOVE_HEIGHT_KEY)) {
            setGrooveHeight(decoder.decodeInt(GROOVE_HEIGHT_KEY));
        } else if (changedField.equals(IMAGEDISP_KEY)) {
            setImageDisplayStyle(decoder.decodeInt(IMAGEDISP_KEY));
        } else if (changedField.equals(MINVALUE_KEY) ||
                   changedField.equals(MAXVALUE_KEY)) {
            setLimits(decoder.decodeInt(MINVALUE_KEY),
                      decoder.decodeInt(MAXVALUE_KEY));
        } else if (changedField.equals(VALUE_KEY)) {
            setValue(decoder.decodeInt(VALUE_KEY));
        } else if (changedField.equals(ENABLED_KEY)) {
            setEnabled(decoder.decodeBoolean(ENABLED_KEY));
        }
    }
}
