/* * Button.java * Transform * * Copyright (c) 2001-2010 Flagstone Software Ltd. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Flagstone Software Ltd. nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package com.flagstone.transform.button; import java.io.IOException; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import java.util.Set; import com.flagstone.transform.MovieTypes; import com.flagstone.transform.coder.Coder; import com.flagstone.transform.coder.Context; import com.flagstone.transform.coder.Copyable; import com.flagstone.transform.coder.SWFDecoder; import com.flagstone.transform.coder.SWFEncodeable; import com.flagstone.transform.coder.SWFEncoder; import com.flagstone.transform.coder.SWFFactory; import com.flagstone.transform.datatype.Blend; import com.flagstone.transform.datatype.ColorTransform; import com.flagstone.transform.datatype.CoordTransform; import com.flagstone.transform.exception.IllegalArgumentRangeException; import com.flagstone.transform.filter.Filter; /** * <p> * ButtonShape identifies the shape that is drawn when a button is in a * particular state. Shapes can be drawn for each of three button states, Over, * Up and Down allowing simple animations to be created when a button is * clicked. * </p> * * <p> * A shape is also used to define active area of the button. When defining the * active area the outline of the shape defines the boundary of the area, the * shape itself is not displayed. The button will only respond to mouse events * when the cursor is placed inside the active area. * </p> * * <p> * The order in which shapes are displayed is controlled by the layer number. As * with the Flash Player's display list shapes on a layer with a higher number * are displayed in front of ones on a layer with a lower number. A coordinate * and color transform can also be applied to each shape to change its * appearance when it is displayed when the button enters the specified state. * </p> * * @see DefineButton * @see DefineButton2 */ public final class ButtonShape implements SWFEncodeable, Copyable<ButtonShape> { /** Format string used in toString() method. */ private static final String FORMAT = "ButtonShape: { state=%d;" + " identifier=%d; layer=%d; transform=%s; colorTransform=%s;" + " blend=%s; filters=%s}"; /** The button state that the shape represents. */ private int state; /** The unique identifier of the shape that will be displayed. */ private int identifier; /** The layer on which the shape is displayed. */ private int layer; /** The coordinate transform used to position the shape. */ private CoordTransform transform; /** The colour transform applied to the shape. */ private ColorTransform colorTransform; /** The set of filters applied to the shape. */ private List<Filter> filters; /** The mode used to blend the shape with its background. */ private Integer blend; /** Flag used when encoded to identify whether the blend is set. */ private transient boolean hasBlend; /** Flag used when encoded to identify whether filters are defined. */ private transient boolean hasFilters; /** * Creates and initialises a ButtonShape object using values encoded * in the Flash binary format. * * @param coder * an SWFDecoder object that contains the encoded Flash data. * * @param context * a Context object used to manage the decoders for different * type of object and to pass information on how objects are * decoded. * * @throws IOException * if an error occurs while decoding the data. */ public ButtonShape(final SWFDecoder coder, final Context context) throws IOException { final int bits = coder.readByte(); hasBlend = (bits & Coder.BIT5) != 0; hasFilters = (bits & Coder.BIT4) != 0; state = bits & Coder.NIB0; identifier = coder.readUnsignedShort(); layer = coder.readUnsignedShort(); transform = new CoordTransform(coder); if (context.get(Context.TYPE) != null && context.get(Context.TYPE) == MovieTypes.DEFINE_BUTTON_2) { colorTransform = new ColorTransform(coder, context); } if (hasFilters) { final SWFFactory<Filter> decoder = context.getRegistry().getFilterDecoder(); final int count = coder.readByte(); filters = new ArrayList<Filter>(count); for (int i = 0; i < count; i++) { decoder.getObject(filters, coder, context); } } else { filters = new ArrayList<Filter>(); } if (hasBlend) { blend = coder.readByte(); if (blend == 0) { blend = 1; } } else { blend = 0; } } /** * Creates am uninitialised ButtonShape object. */ public ButtonShape() { transform = CoordTransform.translate(0, 0); colorTransform = new ColorTransform(0, 0, 0, 0); filters = new ArrayList<Filter>(); blend = 0; } /** * Creates and initialises a ButtonShape object using the values copied * from another ButtonShape object. * * @param object * a ButtonShape object from which the values will be * copied. */ public ButtonShape(final ButtonShape object) { state = object.state; identifier = object.identifier; layer = object.layer; transform = object.transform; colorTransform = object.colorTransform; filters = new ArrayList<Filter>(object.filters); blend = object.blend; } /** * Get the list of states that the shape is displayed for. * @return the list of button states that define when the shape is * displayed. */ public Set<ButtonState> getState() { final Set<ButtonState> set = EnumSet.noneOf(ButtonState.class); if ((state & Coder.BIT0) != 0) { set.add(ButtonState.UP); } if ((state & Coder.BIT1) != 0) { set.add(ButtonState.OVER); } if ((state & Coder.BIT2) != 0) { set.add(ButtonState.DOWN); } if ((state & Coder.BIT3) != 0) { set.add(ButtonState.ACTIVE); } return set; } /** * Set the list of states that the shape is displayed for. * @param states the list of button states that define when the shape is * displayed. * @return this object. */ public ButtonShape setState(final Set<ButtonState> states) { for (final ButtonState buttonState : states) { switch (buttonState) { case UP: state |= Coder.BIT0; break; case OVER: state |= Coder.BIT1; break; case DOWN: state |= Coder.BIT2; break; case ACTIVE: state |= Coder.BIT3; break; default: throw new IllegalArgumentException(); } } return this; } /** * Add the state to the list of states that the shape is displayed for. * @param buttonState the state that defines when the shape is displayed. * @return this object. */ public ButtonShape addState(final ButtonState buttonState) { switch (buttonState) { case UP: state |= Coder.BIT0; break; case OVER: state |= Coder.BIT1; break; case DOWN: state |= Coder.BIT2; break; case ACTIVE: state |= Coder.BIT3; break; default: throw new IllegalArgumentException(); } return this; } /** * Get the unique identifier of the shape that this Button applies to. * * @return the unique identifier of the shape. */ public int getIdentifier() { return identifier; } /** * Sets the unique identifier of the DefineShape, DefineShape2 or * DefineShape3 object that defines the appearance of the button when it is * in the specified state(s). * * @param uid * the unique identifier of the shape object that defines the * shape's appearance. Must be in the range 1..65535. * @return this object. */ public ButtonShape setIdentifier(final int uid) { if ((uid < 1) || (uid > Coder.USHORT_MAX)) { throw new IllegalArgumentRangeException( 1, Coder.USHORT_MAX, uid); } identifier = uid; return this; } /** * Get the layer that the button will be displayed on. * * @return the layer that the shape is displayed on. */ public int getLayer() { return layer; } /** * Sets the layer in the display list that the shape will be displayed on. * * @param aNumber * the number of the layer in the display list where the shape is * drawn. Must be in the range 1..65535. * @return this object. */ public ButtonShape setLayer(final int aNumber) { if ((aNumber < 1) || (aNumber > Coder.USHORT_MAX)) { throw new IllegalArgumentRangeException( 1, Coder.USHORT_MAX, aNumber); } layer = aNumber; return this; } /** * Get the coordinate transform that will be applied to the button. * * @return the coordinate transform that is applied to the shape. */ public CoordTransform getTransform() { return transform; } /** * Sets the coordinate transform that will be applied to the shape to change * it's appearance. * * @param matrix * a CoordTransform object that will be applied to the shape. * Must not be null. * @return this object. */ public ButtonShape setTransform(final CoordTransform matrix) { if (matrix == null) { throw new IllegalArgumentException(); } transform = matrix; return this; } /** * Get the colour transform that will be applied to the button. * * Note that the colour transform will only be used if the ButtonShape is * added to a DefineButton2 object. * * @return the colour transform that is applied to the shape. */ public ColorTransform getColorTransform() { return colorTransform; } /** * Sets the colour transform that will be applied to the shape to change * it's colour. * * IMPORTANT: The colour transform is only used in DefineButton2 objects. * * @param cxform * a ColorTransform object that will be applied to the shape. * Must not be null, even if the ButtonShape will be added to a * DefineButton object. * @return this object. */ public ButtonShape setColorTransform(final ColorTransform cxform) { if (cxform == null) { throw new IllegalArgumentException(); } colorTransform = cxform; return this; } /** * Add a Filter to the list of Filters that will be applied to the shape. * @param filter a Filter to apply to the button shape. * @return this object. */ public ButtonShape add(final Filter filter) { if (filter == null) { throw new IllegalArgumentException(); } filters.add(filter); return this; } /** * Get the list of Filters that will be applied to the shape. * @return the list of filters. */ public List<Filter> getFilters() { return filters; } /** * Set the list of Filters that will be applied to the shape. * @param list a list of Filter objects. * @return this object. */ public ButtonShape setFilters(final List<Filter> list) { if (list == null) { throw new IllegalArgumentException(); } filters = list; return this; } /** * Get the Blend that defines how the shape is blended with background * shapes that make up the button. * @return the Blend mode. */ public Blend getBlend() { return Blend.fromInt(blend); } /** * Set the Blend that defines how the shape is blended with background * shapes that make up the button. * @param mode the Blend mode for this shape. * @return this object. */ public ButtonShape setBlend(final Blend mode) { blend = mode.getValue(); return this; } /** {@inheritDoc} */ public ButtonShape copy() { return new ButtonShape(this); } /** {@inheritDoc} */ @Override public String toString() { return String.format(FORMAT, state, identifier, layer, transform, colorTransform, blend, filters); } /** {@inheritDoc} */ public int prepareToEncode(final Context context) { hasBlend = blend != Blend.NULL.getValue(); hasFilters = true ^ filters.isEmpty(); // CHECKSTYLE IGNORE MagicNumberCheck FOR NEXT 1 LINES int length = 5 + transform.prepareToEncode(context); if (context.get(Context.TYPE) != null && context.get(Context.TYPE) == MovieTypes.DEFINE_BUTTON_2) { length += colorTransform.prepareToEncode(context); } if (hasFilters) { length += 1; for (Filter filter : filters) { length += filter.prepareToEncode(context); } } if (hasBlend) { length += 1; } return length; } /** {@inheritDoc} */ @SuppressWarnings("PMD.NPathComplexity") public void encode(final SWFEncoder coder, final Context context) throws IOException { int bits = 0; bits |= hasBlend ? Coder.BIT5 : 0; bits |= hasFilters ? Coder.BIT4 : 0; bits |= state; coder.writeByte(bits); coder.writeShort(identifier); coder.writeShort(layer); transform.encode(coder, context); if (context.get(Context.TYPE) != null && context.get(Context.TYPE) == MovieTypes.DEFINE_BUTTON_2) { colorTransform.encode(coder, context); } if (hasFilters) { coder.writeByte(filters.size()); for (Filter filter : filters) { filter.encode(coder, context); } } if (hasBlend) { coder.writeByte(blend); } } }