1  /*
     2   * Copyright the original author or authors.
     3   * 
     4   * Licensed under the MOZILLA PUBLIC LICENSE, Version 1.1 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   * 
     8   *      http://www.mozilla.org/MPL/MPL-1.1.html
     9   * 
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  import org.as2lib.app.exec.Call;
    18  import org.as2lib.env.event.impulse.AbstractImpulse;
    19  import org.as2lib.env.event.impulse.Impulse;
    20  import org.as2lib.env.event.impulse.FrameImpulseListener;
    21  import org.as2lib.env.except.FatalException;
    22  import org.as2lib.env.except.IllegalArgumentException;
    23  import org.as2lib.env.reflect.ReflectUtil;
    24  import org.as2lib.util.ArrayUtil;
    25  
    26  /**
    27   * {@code FrameImpulse} is a implementation of {@link Impulse} for a impulse
    28   * that gets executed at a the Frame {@code onEnterFrame} event.
    29   * 
    30   * <p>{@code FrameImpulse} supports static methods for easy connecting to a
    31   * FrameImpulse.
    32   * 
    33   * Note: Those methods can not be named in the same way as the public methods
    34   * are named because of a restriction in Macromedias compiler.
    35   * 
    36   * Example:
    37   * <code>
    38   *   import org.as2lib.app.exec.Executable;
    39   *   import org.as2lib.env.event.impulse.FrameImpulseListener;
    40   *   import org.as2lib.app.exec.FrameImpulse;
    41   *   
    42   *   class com.domain.FrameTracer implements FrameImpulseListener {
    43   *   
    44   *      private var prefix:String;
    45   *      
    46   *      private var postfix:String;
    47   *      
    48   *      public function FrameTracer(prefix:String, postfix:String) {
    49   *      	this.prefix = prefix;
    50   *      	this.postfix = postfix;
    51   *      	FrameImpulse.getInstance().addFrameImpulseListener(this);
    52   *      }
    53   *      
    54   *      public function onFrameImpulse(impulse:FrameImpulse):Void {
    55   *      	trace(prefix+_root._currentframe+postfix);
    56   *      }
    57   *   }
    58   *   
    59   * </code>
    60   * 
    61   * @author Martin Heidegger
    62   * @version 1.5
    63   */
    64  class org.as2lib.env.event.impulse.FrameImpulse extends AbstractImpulse implements Impulse {
    65  	
    66  	/** Holder for the static instance */
    67  	private static var instance:FrameImpulse;
    68  	
    69  	/**
    70  	 * Getter for a instance of a FrameImpulse.
    71  	 * 
    72  	 * <p>Generates a new FrameImpulse if no FrameImpulse has been set.
    73  	 * 
    74  	 * @return {@code FrameImpulse} instance.
    75  	 */
    76  	public static function getInstance(Void):FrameImpulse {
    77  		if(!instance) instance = new FrameImpulse();
    78  		return instance;
    79  	}
    80  	
    81  	/** Holder for the timeline to the FrameImpulse */
    82  	private var timeline:MovieClip;
    83  	
    84  	/** 
    85  	 * Flag if the timeline is generated and should be destroyed after
    86  	 * replacement.
    87  	 */
    88  	private var timelineIsGenerated:Boolean;
    89  	
    90  	/** Broadcaster for connected FrameImpulseListener's */
    91  	private var frameImpulseBroadcaster:Object;
    92  	
    93  	/**
    94  	 * Creates a new FrameImpulse instance.
    95  	 * 
    96  	 * @param timeline Timeline to be used - see: {@link #setTimeline}
    97  	 */
    98  	private function FrameImpulse(timeline:MovieClip) {
    99  		frameImpulseBroadcaster = new Object();
   100  		AsBroadcaster.initialize(frameImpulseBroadcaster);
   101  		setTimeline(timeline);
   102  	}
   103  		
   104  	/**
   105  	 * Sets a new Timeline as main timeline for the MovieClip.
   106  	 * 
   107  	 * @param timeline Timeline to be used for the frame event.
   108  	 * @throws IllegalArgumentException if onEnterFrame has already been used in the timeline.
   109  	 */
   110  	public function setTimeline(timeline:MovieClip):Void {
   111  		var e:Object = execBroadcaster;
   112  		var i:Object = impulseBroadcaster;
   113  		var f:Object = frameImpulseBroadcaster;
   114  		var that:Impulse = this;
   115  		if (timeline != null) {
   116  			if (timeline.onEnterFrame === undefined) {
   117  				
   118  				if (this.timeline) {
   119  					if(timelineIsGenerated) {
   120  						this.timeline.removeMovieClip();
   121  					}
   122  					delete this.timeline.onEnterFrame;
   123  					timelineIsGenerated = false;
   124  				}
   125  				
   126  				this.timeline = timeline;
   127  				timeline.onEnterFrame = function() {
   128  					e.broadcastMessage("execute", that);
   129  					i.broadcastMessage("onImpulse", that);
   130  					f.broadcastMessage("onFrameImpulse", that);
   131  				};
   132  			} else {
   133  				throw new IllegalArgumentException("onEnterFrame method in "
   134  												   +timeline
   135  												   +" has already been overwritten, its not possible to use it as Timeline for a FrameImpulse",
   136  												   this,
   137  												   arguments);
   138  			}
   139  		} else {
   140  			timeline = null;
   141  			getTimeline();
   142  		}
   143  	}
   144  	
   145  	/**
   146  	 * Getter for the currently listening timeline.
   147  	 * 
   148  	 * <p>This method creates a new timeline in root and listenes to it if no
   149  	 * timeline has been set.
   150  	 * 
   151  	 * @return Timeline that is currently used
   152  	 * @throws FatalExeception if a Timeline could not be generated on the fly.
   153  	 */
   154  	public function getTimeline(Void):MovieClip {
   155  		if (!timeline) {
   156  			var name:String = ReflectUtil.getUnusedMemberName(_root);
   157  			if (!name) {
   158  				throw new FatalException("Could not get a free instance name with"
   159  				                        +" ObjectUtil.getUnusedChildName(_root),"
   160  				                        +" to create a listenercontainer.",
   161  				                        this,
   162  				                        arguments);
   163  			}
   164  			var mc:MovieClip = _root.createEmptyMovieClip(name,
   165  														  _root.getNextHighestDepth());
   166  			if (mc) {
   167  				setTimeline(mc);
   168  			} else {
   169  				throw new FatalException("Could not generate a timeline for "
   170  										 +"impulse generation", this, arguments);
   171  			}
   172  			var timelineIsGenerated = true;
   173  		}
   174  		return timeline;
   175  	}
   176  	
   177  	/**
   178  	 * Method to add any supported listener to the FrameImpulse.
   179  	 * 
   180  	 * <p>Adds a listener to the FrameImpulse. The listener will be informed on
   181  	 * each frame change.
   182  	 * 
   183  	 * <p>Example:
   184  	 * <code>
   185  	 *   import org.as2lib.env.event.impulse.Impulse;
   186  	 *   import org.as2lib.env.event.impulse.FrameImpulse;
   187  	 *  
   188  	 *   function test(impulse:Impulse) {
   189  	 *     trace("Test called: "+impulse+" at "+getTimer()+"ms");
   190  	 *   }
   191  	 * 
   192  	 *   var impulse:Impulse = FrameImpulse.getInstance();
   193  	 *   impulse.addListener(new Call(this, test));
   194  	 * </code>
   195  	 * 
   196  	 * <p>Note: If a certain listener implements more than one supported event it
   197  	 * will listen to all of them at one execution (execute, onFrameImpulse,
   198  	 * onImpulse).
   199  	 * 
   200  	 * @param listener to be added.
   201  	 * @throws IllegalArgumentException if the listener doesn't match any type.
   202  	 */
   203  	public function addListener(listener):Void {
   204  		var added:Boolean = true;
   205  		try {
   206  			super.addListener(listener);
   207  		} catch(e:org.as2lib.env.except.IllegalArgumentException) {
   208  			added = false;
   209  		}
   210  		if (listener instanceof FrameImpulseListener) {
   211  			frameImpulseBroadcaster.addListener(listener);
   212  			added = true;
   213  		}
   214  		if (!added) {
   215  			throw new IllegalArgumentException("Passed listener "+listener+" does not match type 'Executable', 'ImpulseListener' or 'FrameImpuseListener'", this, arguments);
   216  		}
   217  	}
   218  	
   219  	/**
   220  	 * Methode to add a {@link FrameImpulseListener} as listener to the FrameImpulse. 
   221  	 * 
   222  	 * <p>Some parts of the code get better readable if you use a complete
   223  	 * clear name like "onFrameImpulse" to define your code. With
   224  	 * {@code .addFrameImpulseListener} you can add a listener that specially
   225  	 * listens only to this naming of the same event that will be executed as
   226  	 * "onImpulse" or "execute".
   227  	 * 
   228  	 * <p>Example:
   229  	 * 
   230  	 * <p>Listener:
   231  	 * <code>
   232  	 *   import org.as2lib.env.event.impulse.FrameImpulseListener;
   233  	 *   import org.as2lib.env.event.impulse.FrameImpulse;
   234  	 *   
   235  	 *   class TraceTimeImpulseListener implements FrameImpulseListener {
   236  	 *     public function onFrameImpulse(impulse:FrameImpulse):Void {
   237  	 *       trace("Frameimpulse executed at "+getTimer());
   238  	 *     }
   239  	 *   }
   240  	 * </code>
   241  	 * 
   242  	 * <p>Usage:
   243  	 * <code>
   244  	 *   import org.as2lib.env.event.impulse.FrameImpulse;
   245  	 *   
   246  	 *   var impulse:FrameImpulse = FrameImpulse.getInstance();
   247  	 *   impulse.addFrameImpulseListener(new TraceTimeImpulseListener());
   248  	 * </code>
   249  	 * 
   250  	 * @param listener Listener to be added.
   251  	 */
   252  	public function addFrameImpulseListener(listener:FrameImpulseListener):Void {
   253  		addListener(listener);
   254  	}
   255  	
   256  	/**
   257  	 * Removes a listener of any type that might be added.
   258  	 * 
   259  	 * @param listener Listener to be removed.
   260  	 * @throws IllegalArgumentException if you pass a listener that is of a
   261  	 *         illegal type.
   262  	 */
   263  	public function removeListener(listener):Void {
   264  		var notRemoved:Boolean = false;
   265  		try {
   266  			super.removeListener(listener);
   267  		} catch (e:org.as2lib.env.except.IllegalArgumentException) {
   268  			notRemoved = true;
   269  		}
   270  		if (listener instanceof FrameImpulseListener) {
   271  			frameImpulseBroadcaster.removeListener(listener);
   272  			notRemoved = false;
   273  		}
   274  		if (notRemoved) {
   275  			throw new IllegalArgumentException("Passed listener "+listener+" does not match type 'Executable', 'ImpulseListener' or 'FrameImpuseListener'", this, arguments);
   276  		}
   277  	}
   278  	
   279  	/**
   280   	 * Removes a {@link FrameImpulseListener} from listening to the events.
   281  	 * 
   282  	 * <p>The passed listener will be removed from listening to any event
   283  	 * (not only to from listening to {@code onFrameImpulse}).
   284  	 * 
   285  	 * @param listener Listener to be removed.
   286  	 */
   287  	public function removeFrameImpulseListener(listener:FrameImpulseListener):Void {
   288  		removeListener(listener);
   289  	}
   290  	
   291  	/**
   292  	 * Getter for the list of all added listeners.
   293  	 * 
   294  	 * <p>This method returns a list of all listeners added with eighter
   295  	 * {@link #connectExecutable}, {@link #addListener}
   296  	 * {@link #addImpulseListener} or {@link #addFrameImpulseListener}
   297  	 * 
   298  	 * @return List that contains all added listeners.
   299  	 */
   300  	public function getAllListeners(Void):Array {
   301  		return super.getAllListeners().concat(getAllFrameImpulseListeners());
   302  	}
   303  	
   304  	/**
   305  	 * Getter for the list of all added {@link FrameImpulseListener}s.
   306  	 * 
   307  	 * @return List that contains all added {@link FrameImpulseListener}s.
   308  	 */
   309  	public function getAllFrameImpulseListeners(Void):Array {
   310  		return frameImpulseBroadcaster._listeners.concat();
   311  	}
   312  	
   313  	/**
   314  	 * Removes all added listeners from listening to the FrameImpulse.
   315  	 * 
   316  	 * @throws IllegalArgumentException if the 
   317  	 */
   318  	public function removeAllListeners(Void):Void {
   319  		super.removeAllListeners();
   320  		removeAllFrameImpulseListeners();
   321  	}
   322  	
   323  	/**
   324  	 * Returns {@code true} if passed-in {@code listener} has been added.
   325  	 * 
   326  	 * @param listener the listener to check whether it has been added
   327  	 * @return {@code true} if the {@code listener} has been added
   328  	 */
   329  	public function hasListener(listener):Boolean {
   330  		if (hasFrameImpulseListener(listener)
   331  			|| super.hasListener(listener)) {
   332  			return true;
   333  		}
   334  		return false;
   335  	}
   336  	
   337  	/**
   338  	 * Adds a list of {@link FrameImpulseListener}s as listener to the events.
   339  	 * 
   340  	 * @param listeners List of all listeners to add.
   341  	 * @throws IllegalArgumentException if one listener didn't match to any listener type.
   342  	 * @see #addListener
   343  	 */
   344  	public function addAllFrameImpulseListeners(listeners:Array):Void {
   345  		for (var i:Number=0; i<listeners.length; i++) {
   346  			addListener(listeners[i]);
   347  		}
   348  	}
   349  	
   350  	/**
   351  	 * Removes all added {@link FrameImpulseListener}s from listening to any event.
   352  	 */
   353  	public function removeAllFrameImpulseListeners(Void):Void {
   354  		// As its possible that a frameimpulselistener was added as executable
   355  		// listener they have to be removed one by one.
   356  		var c:Call = new Call(this, removeListener);
   357  		c.forEach(frameImpulseBroadcaster._listeners);
   358  	}
   359  	
   360  	/**
   361  	 * Checks if a certain {@link FrameImpulseListener} has been added as
   362  	 * listener.
   363  	 * 
   364  	 * @param listener Listener to be checked if it has been added.
   365  	 * @return {@code true} if the certain listener has been added.
   366  	 */
   367  	public function hasFrameImpulseListener(listener:FrameImpulseListener):Boolean {
   368  		return ArrayUtil.contains(frameImpulseBroadcaster._listeners, listener); 
   369  	}
   370  	
   371  }