/*
* Event.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;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.flagstone.transform.action.Action;
import com.flagstone.transform.action.ActionData;
import com.flagstone.transform.coder.Context;
import com.flagstone.transform.coder.SWFDecoder;
import com.flagstone.transform.coder.SWFEncoder;
import com.flagstone.transform.coder.SWFFactory;
/**
* <p>
* EventHandler is used to define the actions that a movie clip or button will
* execute in response to a particular event. Handlers for movie clips are
* defined when the movie clip is added to the display list using Place2 or
* Place3 objects while handlers for buttons are added when the button is
* created.
* </p>
*
* @see Event
*/
@SuppressWarnings("PMD.CyclomaticComplexity")
public final class EventHandler implements Action {
/** Format string used in toString() method. */
private static final String FORMAT = "EventHandler: { events=%s;"
+ " key=%s; actions=%s}";
/** Version of Flash that supports the extended event model. */
private static final int EVENTS_VERSION = 6;
/** Number of bits to shift key code for encoding with event flags. */
private static final int KEY_OFFSET = 9;
/** Bit mask for key field. */
private static final int KEY_MASK = 0xFE00;
/** Bit mask for key field. */
private static final int EVENT_MASK = 0x01FF;
/** The number of different types of event supported by buttons. */
private static final int NUM_BUTTON_EVENTS = 9;
/** The number of different types of event supported by movie clips. */
private static final int NUM_CLIP_EVENTS = 19;
/** Bit mask for accessing bit 0 of the composite event code. */
private static final int BIT0 = 1;
/** Bit mask for accessing bit 1 of the composite event code. */
private static final int BIT1 = 2;
/** Bit mask for accessing bit 2 of the composite event code. */
private static final int BIT2 = 4;
/** Bit mask for accessing bit 3 of the composite event code. */
private static final int BIT3 = 8;
/** Bit mask for accessing bit 4 of the composite event code. */
private static final int BIT4 = 16;
/** Bit mask for accessing bit 5 of the composite event code. */
private static final int BIT5 = 32;
/** Bit mask for accessing bit 6 of the composite event code. */
private static final int BIT6 = 64;
/** Bit mask for accessing bit 7 of the composite event code. */
private static final int BIT7 = 128;
/** Bit mask for accessing bit 8 of the composite event code. */
private static final int BIT8 = 256;
/** Bit mask for accessing bit 9 of the composite event code. */
private static final int BIT9 = 512;
/** Bit mask for accessing bit 10 of the composite event code. */
private static final int BIT10 = 1024;
/** Bit mask for accessing bit 11 of the composite event code. */
private static final int BIT11 = 2048;
/** Bit mask for accessing bit 12 of the composite event code. */
private static final int BIT12 = 4096;
/** Bit mask for accessing bit 13 of the composite event code. */
private static final int BIT13 = 8192;
/** Bit mask for accessing bit 14 of the composite event code. */
private static final int BIT14 = 16384;
/** Bit mask for accessing bit 15 of the composite event code. */
private static final int BIT15 = 32768;
/** Bit mask for accessing bit 16 of the composite event code. */
private static final int BIT16 = 65536;
/** Bit mask for accessing bit 17 of the composite event code. */
private static final int BIT17 = 131072;
/** Bit mask for accessing bit 18 of the composite event code. */
private static final int BIT18 = 262144;
/** Table mapping a movie event to a code. */
private static final Map<Event, Integer> CLIP_CODES;
/** Table mapping a push button event to a code. */
private static final Map<Event, Integer> BUTTON_CODES;
/** Table mapping a menu button event to a code. */
private static final Map<Event, Integer> MENU_CODES;
/** Table mapping a code to a movie event. */
private static final Map<Integer, Event> CLIP_EVENTS;
/** Table mapping a code to a push button event. */
private static final Map<Integer, Event> BUTTON_EVENTS;
/** Table mapping a code to a menu button event. */
private static final Map<Integer, Event> MENU_EVENTS;
static {
CLIP_CODES = new LinkedHashMap<Event, Integer>();
CLIP_CODES.put(Event.LOAD, BIT0);
CLIP_CODES.put(Event.ENTER_FRAME, BIT1);
CLIP_CODES.put(Event.UNLOAD, BIT2);
CLIP_CODES.put(Event.MOUSE_MOVE, BIT3);
CLIP_CODES.put(Event.MOUSE_DOWN, BIT4);
CLIP_CODES.put(Event.MOUSE_UP, BIT5);
CLIP_CODES.put(Event.KEY_DOWN, BIT6);
CLIP_CODES.put(Event.KEY_UP, BIT7);
CLIP_CODES.put(Event.DATA, BIT8);
CLIP_CODES.put(Event.INITIALIZE, BIT9);
CLIP_CODES.put(Event.PRESS, BIT10);
CLIP_CODES.put(Event.RELEASE, BIT11);
CLIP_CODES.put(Event.RELEASE_OUT, BIT12);
CLIP_CODES.put(Event.ROLL_OVER, BIT13);
CLIP_CODES.put(Event.ROLL_OUT, BIT14);
CLIP_CODES.put(Event.DRAG_OVER, BIT15);
CLIP_CODES.put(Event.DRAG_OUT, BIT16);
CLIP_CODES.put(Event.KEY_PRESS, BIT17);
CLIP_CODES.put(Event.CONSTRUCT, BIT18);
CLIP_EVENTS = new LinkedHashMap<Integer, Event>();
CLIP_EVENTS.put(BIT0, Event.LOAD);
CLIP_EVENTS.put(BIT1, Event.ENTER_FRAME);
CLIP_EVENTS.put(BIT2, Event.UNLOAD);
CLIP_EVENTS.put(BIT3, Event.MOUSE_MOVE);
CLIP_EVENTS.put(BIT4, Event.MOUSE_DOWN);
CLIP_EVENTS.put(BIT5, Event.MOUSE_UP);
CLIP_EVENTS.put(BIT6, Event.KEY_DOWN);
CLIP_EVENTS.put(BIT7, Event.KEY_UP);
CLIP_EVENTS.put(BIT8, Event.DATA);
CLIP_EVENTS.put(BIT9, Event.INITIALIZE);
CLIP_EVENTS.put(BIT10, Event.PRESS);
CLIP_EVENTS.put(BIT11, Event.RELEASE);
CLIP_EVENTS.put(BIT12, Event.RELEASE_OUT);
CLIP_EVENTS.put(BIT13, Event.ROLL_OVER);
CLIP_EVENTS.put(BIT14, Event.ROLL_OUT);
CLIP_EVENTS.put(BIT15, Event.DRAG_OVER);
CLIP_EVENTS.put(BIT16, Event.DRAG_OUT);
CLIP_EVENTS.put(BIT17, Event.KEY_PRESS);
CLIP_EVENTS.put(BIT18, Event.CONSTRUCT);
BUTTON_CODES = new LinkedHashMap<Event, Integer>();
BUTTON_CODES.put(Event.ROLL_OVER, BIT0);
BUTTON_CODES.put(Event.ROLL_OUT, BIT1);
BUTTON_CODES.put(Event.PRESS, BIT2);
BUTTON_CODES.put(Event.RELEASE, BIT3);
BUTTON_CODES.put(Event.DRAG_OUT, BIT4);
BUTTON_CODES.put(Event.DRAG_OVER, BIT5);
BUTTON_CODES.put(Event.RELEASE_OUT, BIT6);
BUTTON_EVENTS = new LinkedHashMap<Integer, Event>();
BUTTON_EVENTS.put(BIT0, Event.ROLL_OVER);
BUTTON_EVENTS.put(BIT1, Event.ROLL_OUT);
BUTTON_EVENTS.put(BIT2, Event.PRESS);
BUTTON_EVENTS.put(BIT3, Event.RELEASE);
BUTTON_EVENTS.put(BIT4, Event.DRAG_OUT);
BUTTON_EVENTS.put(BIT5, Event.DRAG_OVER);
BUTTON_EVENTS.put(BIT6, Event.RELEASE_OUT);
MENU_CODES = new LinkedHashMap<Event, Integer>();
MENU_CODES.put(Event.ROLL_OVER, BIT0);
MENU_CODES.put(Event.ROLL_OUT, BIT1);
MENU_CODES.put(Event.PRESS, BIT2);
MENU_CODES.put(Event.RELEASE, BIT3);
MENU_CODES.put(Event.RELEASE_OUT, BIT4);
MENU_CODES.put(Event.DRAG_OVER, BIT7);
MENU_CODES.put(Event.DRAG_OUT, BIT8);
MENU_EVENTS = new LinkedHashMap<Integer, Event>();
MENU_EVENTS.put(BIT0, Event.ROLL_OVER);
MENU_EVENTS.put(BIT1, Event.ROLL_OUT);
MENU_EVENTS.put(BIT2, Event.PRESS);
MENU_EVENTS.put(BIT3, Event.RELEASE);
MENU_EVENTS.put(BIT4, Event.RELEASE_OUT);
MENU_EVENTS.put(BIT7, Event.DRAG_OVER);
MENU_EVENTS.put(BIT8, Event.DRAG_OUT);
}
/** The events that the handler responds to. */
private Set<Event> events;
/** The code representing keyboard shortcut for the handler. */
private int key;
/** The actions executed by the handler when the event occurs. */
private List<Action> actions;
/** The composite event code for all events this handler responds to. */
private transient int eventCode;
/** The number of bytes used to encode the handler. */
private transient int length;
/** The offset in bytes to the next handler, if any, to be decoded. */
private transient int offset;
/**
* Creates and initialises a EventHandler object using values
* encoded in the Flash binary format.
*
* @param value
* is decoded by and it is dependent on the parent object. If
* it is a Place2 or Place3 object then the event handler is for
* a movie clip and the value represents the the set of events
* that the handler responds to. If the parent object is a
* button then the value is the length in bytes of the encoded
* actions executed by the handler.
*
* @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 EventHandler(final int value, final SWFDecoder coder,
final Context context) throws IOException {
int field;
events = EnumSet.noneOf(Event.class);
if (context.contains(Context.TYPE)
&& context.get(Context.TYPE) == MovieTypes.DEFINE_BUTTON_2) {
length = value;
final int eventKey = coder.readUnsignedShort();
eventCode = eventKey & EVENT_MASK;
key = (eventKey & KEY_MASK) >> KEY_OFFSET;
if (context.contains(Context.MENU_BUTTON)) {
for (int i = 0; i < NUM_BUTTON_EVENTS; i++) {
field = eventCode & (1 << i);
if (MENU_EVENTS.containsKey(field)) {
events.add(MENU_EVENTS.get(field));
}
}
} else {
for (int i = 0; i < NUM_BUTTON_EVENTS; i++) {
field = eventCode & (1 << i);
if (field != 0 && BUTTON_EVENTS.containsKey(field)) {
events.add(BUTTON_EVENTS.get(field));
}
}
}
} else {
eventCode = value;
length = coder.readInt();
if ((eventCode & CLIP_CODES.get(Event.KEY_PRESS)) != 0) {
key = coder.readByte();
length -= 1;
}
for (int i = 0; i < NUM_CLIP_EVENTS; i++) {
field = eventCode & (1 << i);
if (field != 0 && CLIP_EVENTS.containsKey(field)) {
events.add(CLIP_EVENTS.get(field));
}
}
}
actions = new ArrayList<Action>();
final SWFFactory<Action> decoder = context.getRegistry()
.getActionDecoder();
if (decoder == null) {
if (length != 0) {
actions.add(new ActionData(coder.readBytes(new byte[length])));
}
} else {
coder.mark();
while (coder.bytesRead() < length) {
decoder.getObject(actions, coder, context);
}
coder.unmark();
}
}
/**
* Creates a ClipEvent object that with a list of actions that will be
* executed when a particular event occurs.
*
* @param event
* the set of Events that the handler will respond to.
* @param list
* the list of actions that will be executed when the specified
* event occurs.
*/
public EventHandler(final Set<Event> event, final List<Action> list) {
setEvents(event);
setActions(list);
}
/**
* Creates an EventHandler object that defines the list of actions that
* will be executed when a particular event occurs or when the specified
* key is pressed.
*
* @param event
* the set of Events that the handler will respond to.
* @param character
* the ASCII code for the key pressed on the keyboard.
* @param list
* the list of actions that will be executed when the specified
* event occurs. Must not be null.
*/
public EventHandler(final Set<Event> event,
final int character, final List<Action> list) {
setEvents(event);
setKey(character);
setActions(list);
}
/**
* Creates and initialises a EventHandler object using the values
* copied from another EventHandler object.
*
* @param object
* a EventHandler object from which the values will be
* copied.
*/
public EventHandler(final EventHandler object) {
events = object.events;
key = object.key;
actions = new ArrayList<Action>(object.actions);
}
/**
* Get the value that is encoded to represent the set of events that the
* handler responds to.
*
* NOTE: This method is only used by Place2 and Place3 objects to encode
* EventHandlers for movie clips. It should not be used.
*
* @return the value representing the set of encoded events.
*/
public int getEventCode() {
return eventCode;
}
/**
* Get the set of events that the handler responds to.
* @return a set of Events.
*/
public Set<Event> getEvents() {
return events;
}
/**
* Set the events that the handler responds to.
* @param set the set of Events for the handler.
*/
public void setEvents(final Set<Event> set) {
events = set;
}
/**
* Get the code for the key that triggers the event when pressed. The
* code is typically the ASCII code for standard western keyboards.
*
* @return the ASCII code for the key that triggers the event.
*/
public int getKey() {
return key;
}
/**
* Sets the code for the key that triggers the event when pressed. The code
* is typically the ASCII code for standard western keyboards.
*
* @param code
* the ASCII code for the key that triggers the event.
*/
public void setKey(final int code) {
key = code;
}
/**
* Get the list of actions that are executed by the movie clip.
*
* @return the actions executed by the handler.
*/
public List<Action> getActions() {
return actions;
}
/**
* Sets the list of actions that are executed by the handler in response
* to specified event(s).
*
* @param list
* the array of actions that will be executed when the specified
* event occurs. Must not be null.
*/
public void setActions(final List<Action> list) {
if (list == null) {
throw new IllegalArgumentException();
}
actions = list;
}
/**
* Adds an action to the list of actions.
*
* @param anAction
* an action object. Must not be null.
* @return this object.
*/
public EventHandler add(final Action anAction) {
if (anAction == null) {
throw new IllegalArgumentException();
}
actions.add(anAction);
return this;
}
/** {@inheritDoc} */
@Override
public EventHandler copy() {
return new EventHandler(this);
}
@Override
public String toString() {
return String.format(FORMAT, events, key, actions);
}
/** {@inheritDoc} */
@Override
public int prepareToEncode(final Context context) {
//CHECKSTYLE:OFF
eventCode = 0;
if (context.contains(Context.TYPE)
&& context.get(Context.TYPE) == MovieTypes.DEFINE_BUTTON_2) {
if (context.contains(Context.MENU_BUTTON)) {
for (Event event : events) {
eventCode |= MENU_CODES.get(event);
}
} else {
for (Event event : events) {
eventCode |= BUTTON_CODES.get(event);
}
}
length = 4;
for (final Action action : actions) {
length += action.prepareToEncode(context);
}
if (context.contains(Context.LAST)) {
offset = -2;
} else {
offset = length - 2;
}
} else {
for (Event event : events) {
eventCode |= CLIP_CODES.get(event);
}
if (context.get(Context.VERSION) >= EVENTS_VERSION) {
length = 8;
} else {
length = 6;
}
offset = (eventCode & CLIP_CODES.get(Event.KEY_PRESS)) == 0 ? 0 : 1;
for (final Action action : actions) {
offset += action.prepareToEncode(context);
}
length += offset;
}
return length;
//CHECKSTYLE:ON
}
/** {@inheritDoc} */
@Override
public void encode(final SWFEncoder coder, final Context context)
throws IOException {
if (Constants.DEBUG) {
coder.mark();
}
if (context.contains(Context.TYPE)
&& context.get(Context.TYPE) == MovieTypes.DEFINE_BUTTON_2) {
coder.writeShort(offset + 2);
coder.writeShort((key << KEY_OFFSET) | eventCode);
} else {
if (context.get(Context.VERSION) >= EVENTS_VERSION) {
coder.writeInt(eventCode);
} else {
coder.writeShort(eventCode);
}
coder.writeInt(offset);
if ((eventCode & CLIP_CODES.get(Event.KEY_PRESS)) != 0) {
coder.writeByte(key);
}
}
for (final Action action : actions) {
action.encode(coder, context);
}
if (Constants.DEBUG) {
coder.check(length);
coder.unmark();
}
}
}