﻿#target bridge


/*-------------------------------------------------------------------------------------------------------------- 
	ADOBE SYSTEMS INCORPORATED
	Copyright 2006 - Adobe Systems Incorporated
	All Rights Reserved
 
	If this file is provided by Adobe in modifiable form (e.g., source code or JavaScript), then you may modify this file.  
	You may use and distribute this file, in modified or unmodified form, provided that you must reproduce the above
	acknowledgement and this notice with all copies of the file.
 
	THIS SOFTWARE IS PROVIDED BY ADOBE SYSTEMS INCORPORATED “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 ADOBE BE LIABLE TO YOU 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.
--------------------------------------------------------------------------------------------------------------*/

/* WasScriptLibrary.jsx
	Author: 		Bob Stucky
	Description:	A standard library of classes and functions that are pretty handy for bridge scripting
	Version:		2.0.38
	Dated:		11/15/2006
*/

/*  
@@@BUILDINFO@@@ WasScriptLibrary.jsx 2.0.0.38 1.0000  15-Nov-2006 
*/
//$.level = 0;
WasLib = {};
///
/// Standard translated strings
///
WasLib.strings = {};
WasLib.strings.folder = localize( "$$$/bsl/folder=Folder" );
WasLib.strings.file = localize( "$$$/bsl/file=File" );
WasLib.strings.fileMustExist = localize( "$$$/bsl/fileMustExist=The File Object Must Point to an Existing File" );
WasLib.strings.notCreated = localize( "$$$/bsl/createError=could not be created" );
WasLib.strings.shortDays = [ localize( "$$$/bsl/sun=Sun" ), localize( "$$$/bsl/mon=Mon" ), localize( "$$$/bsl/tue=Tue" ), localize( "$$$/bsl/wed=Wed" ), localize( "$$$/bsl/thu=Thu" ), localize( "$$$/bsl/fri=Fri" ), localize( "$$$/bsl/sat=Sat" ) ];
WasLib.strings.longDays = [ localize( "$$$/bsl/sunday=Sunday" ), localize( "$$$/bsl/monday=Monday" ), localize( "$$$/bsl/tuesday=Tuesday" ), localize( "$$$/bsl/wednesday=Wednesday" ), localize( "$$$/bsl/thursday=Thursday" ), localize( "$$$/bsl/friday=Friday" ), localize( "$$$/bsl/saturday=Saturday" ) ];
WasLib.strings.shortMonths = [ localize( "$$$/bsl/jan=Jan" ), localize( "$$$/bsl/feb=Feb" ), localize( "$$$/bsl/mar=Mar" ), localize( "$$$/bsl/apr=Apr" ), localize( "$$$/bsl/may=May" ), localize( "$$$/bsl/jun=Jun" ), localize( "$$$/bsl/jul=Jul" ), localize( "$$$/bsl/aug=Aug" ), localize( "$$$/bsl/sep=Sep" ), localize( "$$$/bsl/oct=Oct" ), localize( "$$$/bsl/nov=Nov" ), localize( "$$$/bsl/dec=Dec" ) ];
WasLib.strings.longMonths = [ localize( "$$$/bsl/january=January" ), localize( "$$$/bsl/febr=February" ), localize( "$$$/bsl/march=March" ), localize( "$$$/bsl/april=April" ), localize( "$$$/bsl/may2=May" ), localize( "$$$/bsl/june=June" ), localize( "$$$/bsl/july=July" ), localize( "$$$/bsl/augu=August" ), localize( "$$$/bsl/septem=September" ), localize( "$$$/bsl/octo=October" ), localize( "$$$/bsl/novem=November" ), localize( "$$$/bsl/decem=December" ) ];
WasLib.strings.alert = localize( "$$$/bsly/alert=Alert" );
WasLib.strings.noShowAgain = localize( "$$$/bsl/noShow=Do not show this message again" );
WasLib.strings.cancel =  localize( "$$$/bsl/canx=Cancel" )
WasLib.strings.OK = localize( "$$$/bsl/ok=OK" );
WasLib.strings.errorLoadingPrefs = localize( "$$$/bsl/errorLoadingPrefs=Error loading Preferences" );
WasLib.strings.prefsFileNotSet = localize( "$$$/bsl/prefsFileNotSet=Invalid Preferences File" );
WasLib.strings.prefsNotSave = localize( "$$$/bsl/prefsNotSaves=Preferences File Not Found" );
WasLib.strings.psMenu = localize( "$$$/bsl/Photoshop=Photoshop" );
WasLib.strings.aiMenu = localize( "$$$/bsl/Illustrator=Illustrator" );
WasLib.strings.idMenu = localize( "$$$/bsl/InDesign=InDesign" );
WasLib.strings.glMenu = localize( "$$$/bsl/GoLive=GoLive" );
WasLib.strings.userCancelled = localize( "$$$/bsl/userCancelled=User Cancelled This Operation" );
WasLib.strings.noValidFiles = localize( "$$$/bsl/noValidFiles=No Valid Files Were Selected" );
WasLib.strings.progress = localize( "$$$/bsl/progress=Progress" );
WasLib.strings.complete = localize( "$$$/bsl/complete=Complete" );
WasLib.strings.starting= localize( "$$$/bls/starting=Starting..." );
WasLib.strings.operationComplete = localize( "$$$/bsl/opComplete=Complete" );
WasLib.strings.errorLongRunning = localize( "$$$/bsl/errLongRun=An error ocurred " );

///
/// 	Classes
///

///
///	Handy Dandy log object
/// 	usage:
/// 		var log = new WasLib.Log( pathOrFile, true );
/// 		log.writeln( "This is my first log entry" );
///		log.close();
///
	WasLib.Log = function( logFile, includeDate ) {
		this.includeDate = typeof( includeDate ) == "boolean" ? includeDate : true;
		this.file = new File( logFile ); // note passing a file object or path to constructor yields a file
		this.file.parent.verify(); // this will ensure the folder containing the file exists
		if ( this.file.exists ) {
			this.file.open( "e" );
			while ( !this.file.eof ) {
				this.logText += this.file.read( 1 );
			}
		} else {
			this.file.open( "w" );
		}
		this.file.writeln( WasLib.Log.getDate() + ": Log Session Opened");
	}
	WasLib.Log.getDate = function() { // static method to get the standard date format for the log
		var d = new Date();
		if ( ( d.getYear() - 100 ) < 10 ) {
			var yearStr = "0" + new String( ( d.getYear() - 100 ) );
		} else {
			var yearStr = new String ( ( d.getYear() - 100 ) );
		}
		var ds = ( d.getMonth() + 1 ) + "/" + d.getDate() + "/" + yearStr + " " + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds();
		return ds;
	 }
	WasLib.Log.prototype.write = function( txt ) {
		txt = txt == undefined ? "" : txt; // write nothing if nothing passed in (instead of "undefined")
		this.file.write( txt );
	}
	WasLib.Log.prototype.writeln = function( txt ) {
		txt = txt == undefined ? "" : txt;
		if ( this.includeDate ) {
			if ( txt == "" ) {
				this.file.writeln( txt );
			} else {
				this.file.writeln( WasLib.Log.getDate() + ": " + txt );
			}
		} else {
			this.file.writeln( txt );
		}
	}
	WasLib.Log.prototype.writeFormatted = function( array ) {
		if ( this.includeDate ) {
			this.file.write( WasLib.Log.getDate() + ": " );
		} 
		for ( var i = 0; i < array.length; i++ ) {
			this.file.write( array[ i ] + "\t" );
		}
		this.file.writeln();
	}
	WasLib.Log.prototype.close = function() {
		this.file.writeln( WasLib.Log.getDate() +  ": Log Session Closed" );
		this.file.writeln();
		this.file.close();
	}
///
/// 	Stringbuffer
/// 	much, MUCH, faster way of building large strings than by normal concatenation
/// 	usage:
/// 		var sb = new WasLib.StringBuffer();
///		sb.append( "The rain " );
/// 		sb.append( "in Spain..." );
///		var finalString = sb.toString();
///
	WasLib.StringBuffer = function() {
		this.array = new Array();
		this.length = 0;
	}
///	note this is not done as a subclass of array as the length property 
///	would be calc'd using the array's methods, and would not be correct
	WasLib.StringBuffer.prototype.toString = function( sep ) {
		if ( sep ) { // for example, use a newline char to have each snippet on its own line
			return this.array.join( sep );
		} else {
			return this.array.join( "" );
		}
	}
	WasLib.StringBuffer.prototype.append = function( x ) {
		this.push( x );
		this.length += x.length;
	}
	WasLib.StringBuffer.prototype.clear = function() {
		this.array = new Array();
		this.length = 0;
	}
	WasLib.StringBuffer.prototype.concat = function( sBuffer ) {
		this.array = this.array.concat( sBuffer.array );
		this.length += sBuffer.length;
	}
///
///	Hashtable
///	handy way to store name-value pairs - is serializable via toSource() and can be used with BridgeTalk
///	usage
///		var ht = new WasLib.Hashtable();
///		ht.putValue( "Fred", "Flintstone" );
///		var out = ht.getValue( "Fred" );
///
///	for serialization: 
///	ht1 = eval( ht.toSource() );
///	out = ht1.getValue( "Fred" );
///  ht1 is an exact copy of ht, but it is a copy, NOT a reference to the same hashtable
///
WasLib.Hashtable = function( caseSensitive ) {
	this.caseSensitive = caseSensitive || false;
	this.keyList = new Array( 0 );
	this.keyIndex = {};
	this.count = 0;
}
WasLib.Hashtable.prototype._getKey = function( name ) {
	return this.caseSensitive ? new String( "htx%_" +  name ) : new String( "htx%_" +  name ).toLowerCase();
}
WasLib.Hashtable.prototype.clone = function() {
	var t = new WasLib.Hashtable( this.caseSensitive );
	for ( var i = 0; i < this.keyList.length; i++ ) {
		t.putValue( this.keyList[ i ], this.getValue( this.keyList[ i ] ) );
	}
	return t;
}
WasLib.Hashtable.prototype.merge = function( ht ) {
	for ( var i = 0; i < ht.keyList.length; i++ ) {
		this.putValue( ht.keyList[ i ], ht.getValue( ht.keyList[ i ] ) );
	}
}
WasLib.Hashtable.prototype.isUniqueKey = function( key ) {
	return this.keyIndex[ key ]  == undefined;
}
WasLib.Hashtable.prototype.putValue = function( name, value ) {
	if ( ( value instanceof Boolean ) || ( typeof value == "boolean" ) ) {
		value = value ? "true" : "false";
	}
	var key = this._getKey( name );
	this[ key ] = value;
	if ( this.isUniqueKey( key ) ) {
		this.keyIndex[ key ] = this.keyList.length;
		this.keyList.push( name );
		this.count++;
	} else {
		this.keyList[ this.keyIndex[ key ] ] = name;
	}
}			
WasLib.Hashtable.prototype.keys = function()  {
	return this.keyList;
}
WasLib.Hashtable.prototype.getValue = function( name ) {
	var val = this[ this._getKey( name ) ];
	if ( ( val == "true" ) || ( val == "false" ) ) {
		val = eval( val );
	}
	return val;
}
WasLib.Hashtable.prototype.getCount = function()  {
	return this.count;
}
WasLib.Hashtable.prototype.remove = function( name )  {
	if ( this.getValue( name ) )  {
		var key = this._getKey( name );
		var idx = this.keyIndex[ key ];
		this.keyIndex[ key ] = undefined;
		this[ key ] = undefined;
		this.keyList.splice( idx, 1 );
		this.count--;
	}
}
WasLib.Hashtable.prototype.toString = function() {
	return "[object WasLib.Hashtable] " + this.keyList;
}///
/// 	Preferences 
///	Subclass of Hashtable - saves a Hashtable to a file, and reconstitutes it again
///	usage:
///		var prefs = new WasLib.Preferences( fileObjectOrPath );
///		prefs.putValue( "filterString", "JPG,JPEG,TIF,TIFF" );
///		prefs.save();
///		var newPrefs = new WasLib.Preferences( fileObjectOrPath ); // note same path as the first one!
///		newPrefs.load();
///		var filterString = newPrefs.getValue( "filterString" );
///
	WasLib.Preferences = function( file, caseSensitive ) {
		this.ancestor = WasLib.Hashtable;
		this.caseSensitive = caseSensitive || false;
		this.keyList = new Array( 0 );
		this.keyIndex = {};
		this.count = 0;
		this.file = new File( file ); 
		this.file.parent.verify();
		this.wasLoaded = false;
	}
	WasLib.Preferences.prototype = new WasLib.Hashtable();

	WasLib.Preferences.prototype.load = function() {
		var oldDebug = $.level;
		$.level = 0;
		if ( this.file ) {
			if ( this.file.exists ) {
//				try {
					this.file.open( "r" );
					var buf = this.file.read();
					this.file.close();
					var obj = eval( buf );
					this.merge( obj );
//				} catch ( e ) {
//					throw WasLib.strings.errorLoadingPrefs + ": " + e;
//				}
			} else {
				throw WasLib.strings.prefsNotSaved;
			}
		} else {
			throw WasLib.strings.prefsFileNotSet;
		}
		$.level = oldDebug;
		this.wasLoaded = true;
	}
	WasLib.Preferences.prototype.save = function() {
		if ( this.file ) {
			this.file.parent.verify();
			this.file.open( "w" );
			this.file.write( this.toSource() );
			this.file.close();
		} else {
			throw WasLib.strings.prefsFileNotSet;
		}
	}
	WasLib.Preferences.prototype.toString = function() {
		return "[Object WasLib.Preferences] " + this.file.fsName;
	}
///
/// FIFOBuffer
///	obviously you can use an array, but this is just easier...
///
	WasLib.FIFOBuffer = function() {
		this.ancestor = Array();
	}
	WasLib.FIFOBuffer.prototype = new Array();
	WasLib.FIFOBuffer.prototype.pop = function() {
		return this.shift();
	}
	WasLib.FIFOBuffer.prototype.peek = function() {
		return this[ 0 ];
	}
	WasLib.FIFOBuffer.prototype.toString = function() {
		return "[Object WasLib.FIFOBuffer]" + this.toString();
	}
///
///	KeyedArrayList
///
///	put in a key and a value, it stores the value in an array, and ensures the array contains only unique values.
///	Only 1 array will be created for each key. This is handy when you're storing data in a number of arrays. With
///	this class, you can "name" the array with a key value, and reference it that way.
///	If sorted == true, the arrays will be sorted alphabetically ascending.
///
	WasLib.KeyedArrayList = function( sorted ) {
		this.sorted = sorted || false;
		this.collection = new  WasLib.Hashtable( true );
	}
	WasLib.KeyedArrayList.prototype.putValue = function( key, value ) {
		if ( this.collection.isUniqueKey( key ) ) {
			var ar = new Array();
			ar.push( value );
			this.collection.put( key, ar );
		} else {
			ar = this.collection.get( key );
			for ( i = 0; i < ar.length; i++ ) {
				if ( ar[ i ] == value ) {
					return;
				}
			}
			ar.push( value );
			if ( this.sorted ) {
				ar.sort();
			}
		}
	}
	WasLib.KeyedArrayList.prototype.getArray = function( key ) {
		return this.collection.getValue( key );
	}
	WasLib.KeyedArrayList.prototype.getKeys = function() {
		return this.collection.keyList;
	}

///
///	AlertNoShow - simple alert box with a "Do not show again" check box
///	access the result of the show again by the instance's noShow property
///	usage:
///		if ( myGlobalSettings.getValue( "showThisError" ); { // WasLib.Preferences object to see what the stored state was, if true, then show the message
///			var a = new WasLib.AlertNoShow( "An Error occurred, you are so hosed...", true ); // the true will put an OK and Cancel button on the alert
///			if ( a.show() ) {
///				if they got here, OK was hit
///			} else {
///				if they got here, Cancel was hit
///			}
///			myScriptGlobalSettings.putValue( "showThisError", a.showAgain );
///			myScriptGlobalSettings.save();
///		}
	WasLib.AlertNoShow = function( msg, cancel ) {
		this.ancestor = Window;
		this.dialog = new Window( "dialog", WasLib.strings.alert );
		var txt = this.dialog.add( "statictext", undefined, msg, { multiline: true } );

		var notice = this.dialog.add( "checkbox", undefined, WasLib.strings.noShowAgain );
		notice.obj = this;
		this.showAgain = true;
		notice.onClick = function() {
			this.obj.showAgain = !this.value;
		}
		notice.value = !this.showAgain;
		var group = this.dialog.add( "group" );
		if ( cancel ) {
			var cancel = group.add( "button", undefined, WasLib.strings.cancel );
			cancel.dlg = this.dialog;
			cancel.wrapper = this;
			cancel.alignment = "right";
			cancel.obj = this.dialog;
			cancel.onClick = function() {
				this.obj.close( 2 );
				
			}
		}
		var ok = group.add( "button", undefined, WasLib.strings.OK );
		ok.alignment = "right";
		ok.obj = this.dialog;
		ok.onClick = function() {
			this.obj.close( 1 );
		}
	}
	WasLib.AlertNoShow.prototype.show = function() {
		this.dialog.center();
		return this.dialog.show() == 1 ?  true : false;
	}
///
/// 	Folder.verify()
///	ensures a Folder object, and the complete path to that folder exists. If you don't use the heck out of this one, you're stupid
/// 	usage: 
/// 		var f = new Folder( path );
/// 		f.verify(); - the folder pointed to by path will now exist or an error will be thrown
///
	Folder.prototype.verify = function() {
		if ( !this.exists ) { // if this folder doesn't exist
			var oFolder = new Folder( this.absoluteURI ); // make a new folder object (copy) to play with
			var stk = new Array();
			while ( !oFolder.exists ) { // we know this first one will not exist
				stk.push( oFolder ); // so push this folder onto the stack
					oFolder = new Folder( oFolder.path ); // and then check to see if the parent folder exists, if so, stop this loop
			}
			stk2 = new Array(); // this is to remove created folders if one of the subsequent folder creations fails - leave nothing behind
			while ( stk.length > 0 ) { // while there's still stuff in the stack
				oFolder = stk.pop(); // pop off the last one, create it, push it onto the error recovery stack if it works, if it doesn't unwind what we've done
				if ( oFolder.create() ) {
					stk2.push( oFolder );
				} else {
					while ( stk2.length > 0 ) {
						stk2.pop.remove();
					}
					throw WasLib.strings.folder + " " + decodeURI( folder.name ) + " " + WasLib.strings.notCreated;
				} 
			}
		}
	}
///
///	Extensions to File class
///

///
///	gets the extension of a file from the file name, option to NOT decode the file name
///
	File.prototype.getExtension = function( noDecode ) {
		var idx = noDecode ? this.name.lastIndexOf( "." ) :  decodeURIComponent( this.name ).lastIndexOf( "." );
		var ext = undefined;
		if ( idx > -1 ) {
			if ( noDecode ) {
				ext = this.name.substr( ( idx + 1 ) );
			} else {
				ext = decodeURIComponent( this.name ).substr( ( idx + 1 ) );
			}
		}
		return ext;
	}
///
/// gets the file name without the extension
///
	File.prototype.nameWithoutExtension = function( noDecode ) {
		var fName = noDecode ? this.name :  decodeURIComponent( this.name );
		var idx = fName.lastIndexOf( "." ); // could be more than 1 . in the file name
		if ( idx > -1 ) {
			fName = fName.substr( 0, idx );
		}
		return fName;
	}	
///
/// sets the file extension to a given string, note File Object must exist in the file system. This renames the actual disk file
///
	File.prototype.setExtension = function( ext ) {
		if ( this.exists ) {
			var newName = this.nameWithoutExtension( true ) + "." + ext;
			this.rename( newName );
		} else {
			throw WasLib.strings.fileMustExist;
		}
	}
///
///	File.getFileWithExtension - 
///
	File.prototype.getFileWithExtension = function( ext ) {
		var fName = this.nameWithoutExtension();
		return new File( this.path + "/" + fName + "." + ext );
	}
///
/// adds "(n)" to a file object's name until it is a unique file name
///
	File.prototype.uniquify = function() {
		var sq = 1;
		var oldName = this.nameWithoutExtension( true );
		var oldExt = this.getExtension( true );
		var aFile = this;
		while ( aFile.exists ) {
			aFile = new File( aFile.path + "/" + oldName + "(" + sq++ + ")." + oldExt );
		}
		return aFile;
	}
///
/// provide an extension or mac file type, or list of same. It will return true if the file is of that type
///
	File.prototype.isFileType = function( ex )  {
		if ( ! ex || ex == null ) {	return true;		}
		var ex = ex.toUpperCase();
		ext = this.getExtension();
		if ( !ext || ( ext == null ) ) {
			ext = "";
		}
		ext = WasLib.trim(ext);

		// this is tricky now because OS X uses extensions
		// so if the extension can be used to determine type,
		// use it, otherwise try the type
		if (ex.indexOf( ext.toUpperCase() ) > -1) return true;
		// only try this on mac
		if ( File.fs == "Macintosh" ) {
			ext = ( this.type == '????' ) ? ext : this.type;
			return ex.indexOf( ext.toUpperCase() ) > -1;
		}
		// will be false at this point
		return false;
	}
///
///	Date.format()
///
	Date.prototype._getMonth = function( mo, longName ) {
		if ( longName ) {
			return WasLib.strings.longDays[ mo ];
		} else {
			return WasLib.strings.shortDays[ mo ];
		}		
	}
	Date.prototype._getDay = function( d, longName ) {
		if ( longName ) {
			return WasLib.strings.longMonths[ d ];
		} else {
			return WasLib.strings.shortMonths[ d ];
		}		
	}
	Date.prototype._formatterDateString = function( ch, length) {
		var ds = "";
		switch( ch ) {
			case "Y" : {
				ds = new String( this.getFullYear() );
				if ( length > 4 ) {
					ds = "               ".substr( 0, ( length - 4 ) ) + ds;
				} else {
					ds = ds.substr( ( 4 - length ), length );	
				}
				return ds;
			}
			case "M" : {
				if ( length == 3 ) {
					return this._getMonth( this.getMonth(), false );
				} else if ( length > 3 ) {
					return this._getMonth( this.getMonth(), true );
				} else {
					ds = new String( this.getMonth() + 1 );
					if ( ds.length  == 1 ) {
						ds = "0" + ds;
					}
				}
				break;
			}
			case "D" : {
				if ( length == 3 ) {
					return this._getDay( this.getDay(), false );
				} else if ( length > 3 ) {
					return this._getDay( this.getDay(), true );
				} else {
					ds = new String( this.getDate() );
					if ( ds.length  == 1 ) {
						ds = "0" + ds;
					}
				}
				break;
			}
			case "h" : {
				ds =  new String( this.getHours() + 1 );
				if ( ds.length  == 1 ) {
					ds = "0" + ds;
				}
				break;
			}
			case "m" : { 
				 ds =  new String( this.getMinutes() );
				if ( ds.length  == 1 ) {
					ds = "0" + ds;
				}
				break;
			}
			case "s" : { 
				var ds = new String( this.getSeconds() );
				if ( ds.length  == 1 ) {
					ds = "0" + ds;
				}
				break;
			}
			case "S" : { 
				var ds = new String( this.getMilliseconds() );
				if ( ds.length  == 1 ) {
					ds = "0" + ds;
				}
				break;
			}
			case "Z" : {
				return getCharsAfter( this.toString(), "GMT" );
				break;
			}
			case "z" : {
				var s = getCharsAfter( this.toString(), "GMT" );
				var a = s.substr( 1,2 ) + ":" + s.substr( 3,2 );
				return a;
			}
		}
		if ( length > 2 ) {
			ds = "000000000000000".substr( 0, ( length - 2 ) ) + ds;
		} else {
			ds = ds.substr( ( 2 - length ), length );	
		}
		return ds;
	}
	Date.prototype.format = function( fmt, token, out ) {
		if ( !out ) {
			out = "";
		}
		if ( fmt.length == 0 ) {
			if ( token.length > 0 ) {
				out += this._formatterDateString( token[ 0 ], token.length );
			}
			return out;
		}
		if ( token != undefined ) { // we're in a second or higher iteration
			var ch = fmt[ 0 ]; // next char in the format
			var fmt = fmt.substr( 1 ); // stuff after char 1 of the format string
			if ( token.length > 0 ) { // there's something in token...
				var tch = token[ 0 ];
				if ( tch == ch ) { // it's another copy of the existing token
					token += ch;
					return this.format( fmt, token, out );
				} 
				if ( "ZzYMDhmsS".indexOf( ch ) > -1 ) { // it's a new token character (different from existing token)
					out += this._formatterDateString( tch, token.length );
					token = ch;
					return this.format( fmt, token, out );
				} else { // it's not a format token character
					if ( token.length > 0 ) {
						out += this._formatterDateString( tch, token.length );
					}
					out += ch;
					return this.format( fmt, "", out );
				}
			} else { // token is empty 
				if ( "ZzYMDhmsS".indexOf( ch ) > -1 ) { //it's a token char
					return this.format( fmt, ch, out );
				} else { // not a token char
					out += ch;
					return this.format( fmt, token, out );
				}
			}
		} else {
			return this.format( fmt, "", out );
		}
		return out;
	}
///
/// String Functions
///
	WasLib.equalsIgnoreCase = function( str, str1 )  {
		return str.toLowerCase() == str1.toLowerCase();
	}
	String.prototype.equalsIgnoreCase = function( str1 ) {
		return WasLib.equalsIgnoreCase( this, str1 );
	}
	WasLib.ltrim = function( str )  {
		return new String( str ).replace( /^\s+/ , "" );
	}
	String.prototype.ltrim = function() {
		return WasLib.ltrim( this );
	}
	WasLib.rtrim = function( str )  {
		return new String( str ).replace( /\s+$/ , "" );
	}
	String.prototype.rtrim = function() {
		return WasLib.rtrim( this );
	}
	WasLib.trim = function( st )  {
		var str = new String( st );
		str = str.replace( /^\s+/ , "" );
		str = str.replace( /\s+$/ , "" );
		return str
	}
	String.prototype.trim = function() {
		return WasLib.trim( this );
	}
	WasLib.endsWith = function( str, end )  {
		return ( str.substr( ( str.length - end.length ), end.length ) == end );
	}
	String.prototype.endsWith = function( end ) {
		return WasLib.endsWith( this, end );
	}
	WasLib.startsWith = function( str, start )  {
		return ( str.substr( 0, start.length ) == start );
	}
	String.prototype.starsWith = function( start ) {
		return WasLib.startsWith( this, start );
	}
	WasLib.contains = function( str, ch )  {
		return ( new String( str ).indexOf( ch ) > -1 );
	}
	String.prototype.contains = function( ch ) {
		return WasLib.contains( this, ch );
	}
	WasLib.getCharsBefore = function( str, ch )  {
		s = new String( WasLib.trim( str ) );
		var idx = s.indexOf( ch );
		if (idx == -1 )  {
			return s.toString();
		} else {
			return s.substr( 0, idx );		
		}
	}
	String.prototype.getCharsBefore = function( ch ) {
		return WasLib.getCharsBefore( this, ch );
	}
	WasLib.getCharsAfter = function( str, ch )  {
		s = new String( WasLib.trim( str ) );
		var idx = s.indexOf( ch );
		if (idx == -1 )  {
			return "";
		} else {
			return s.substr( ( idx + ch.length ) );		
		}
	}
	String.prototype.getCharsAfter = function( ch ) {
		return WasLib.getCharsAfter( this, ch );
	}
///
/// 	standard file type lists
/// 	these are not all inclusive lists, but do cover most of the possibilities for PS and AI
///	use these with the File.isFileType() method
///	if ( myFile.isFileType( WasLib.fileFilters.getValue( "Photoshop Openable" ) ) ) {
///		[Do something really cool here because it CAN be opened by PS]
///	}
///
	WasLib.fileFilterPath = Folder.userData + "/Adobe/Scripting/fileFilterPreferences.jsx";
	WasLib.fileFilters = new WasLib.Preferences( WasLib.fileFilterPath );
	try {
		WasLib.fileFilters.load();
	} catch( e ) {
		WasLib.fileFilters.putValue( "All", "" );
		WasLib.fileFilters.putValue( "Photoshop", "PSD,PDD,8BPS,JPEG,JPG,GIF," );
		WasLib.fileFilters.putValue( "Photoshop Openable", "PSD,PDD,JPEG,JPG,JPE,8BPS,GIF,BMP,RLE,DIB,TIF,CRW,NEF,RAF,ORF," +
			"CIN,SDPX,DPX,FIDO,GIF,EPS,PS,EPSF,FLM,PSB,EXR,AI3,AI4,AI5,AI6,AI7,AI8,AI,PCX,PDF,PDP," +
			"PCD,RAW,PICT,PCT,PIC,PXR,PNG,HRR,RGBE,XYZE,TGA,VDA,ICB,VST,TIF,TIFF,WBM,WBMP,DNG,SCT,PBM," );
		WasLib.fileFilters.putValue( "PageMaker", "PM6,PM6,P65,PMD,ALD6," );
		WasLib.fileFilters.putValue( "InDesign",  "INDD,IDD3,IDD4,INDT," );
		WasLib.fileFilters.putValue( "PDF", "pdf,PDF," );
		WasLib.fileFilters.putValue( "Illustrator", "AI,AIT," );
		WasLib.fileFilters.putValue( "Illustrator Openable",  "AI,AIT,PDF,DXF,BMP,RLE,CGM,CDR,EPS,EPSF,PS,EMF,FH?," +
			"GIF,JPEG,JPG,JPE,PCD,PCT,PIC,RTF,DOC,PCX,PSD,PDD,PXR,PNG,SVG,SVGZ,TGA,VDA,ICB,VST,TXT,TIF,TIFF,WMF," );
		WasLib.fileFilters.putValue( "Contact Sheet", "PNG,JPG,JPEG,TIF,TIFF,GIF,AI,PSD,PDF,EPS,EPSF" );
		WasLib.fileFilters.putValue( "GoLive", "HTML,HTM,CSS,JS" );
		WasLib.fileFilters.putValue( "Script", "JSX" );
		WasLib.fileFilters.save();
	}
	WasLib.fileFilterPanel = function( panel, filterChangeCallback ) {
	// filterChangeCallback is a JS function that takes a file filter string and the name of the filter as as args - in that code
	// you get a new set of files and set the settings to use or point to the filter. It is fired when the user selects a saved filter or changes a filter
	// if a filter is changed but not saved, the name of the filter is a localized version of "Custom";
		panel.filterPanel = panel.add( "panel", undefined, localize( "$$$/WAS/rb/dfsfilters=File Type Filters" ) );
		panel.filterPanel.setFilterName = function( name ) {
		    	var filter = WasLib.fileFilters.getValue( filter );
		    	if ( filter ) {
		       	for ( var i = 0; i < this.panel.filterDrop.items.length; i++ ) {
		       		if ( this.panel.filterDrop.items[ i ].text == name ) {
		       			this.panel.filterDrop.items[ i ].selected = true;
		       		}
		       		this.panel.filterEdit.text = filter;
					this.panel.removeFilterButton.enabled = true;
					this.panel.saveFilterButton.enabled = true;
					this.panel.filterChangeCallback( name, this.panel.filterEdit.text );
			    	}
		    	}	
		}
		panel.filterPanel.setCustomFilter = function( filter ) {
		    	this.panel.filterEdit.text = filter;
	       	for ( var i = 0; i < this.panel.filterDrop.items.length; i++ ) {
	       		if ( this.panel.filterDrop.items[ i ].text == WasLib.strings.theWordCustom ) {
	       			this.panel.filterDrop.items[ i ].selected = true;
	       		}
		    	}
			this.panel.removeFilterButton.enabled = false
			this.panel.saveFilterButton.enabled = true;
			this.panel.filterChangeCallback( WasLib.strings.theWordCustom, this.panel.filterEdit.text );
		}
		WasLib.strings.theWordCustom = localize( "$$$/WAS/db/custom=Custom" );
		panel.filterPanel.filterChangeCallback = filterChangeCallback;
		panel.filterPanel.alignment = "fill";
		panel.filterPanel.alignChildren = "top";
		panel.filterPanel.orientation = "row";
		var fGroup = panel.filterPanel.add( "group" );
		fGroup.orientation = "column";
		var dropGroup = fGroup.add( "group" );
		var fButtonGroup = fGroup.add( "group" );
		panel.filterDrop = dropGroup.add( "dropdownlist" );
		panel.filterDrop.alignment = "fill";
//		panel.filterDrop.preferredSize = [140,20];
		panel.filterDrop.panel = panel;
		panel.saveFilterButton = fButtonGroup.add( "button", undefined, localize( "$$$/bsl/filters/save=Save" ) );
		panel.saveFilterButton.helpTip = localize( "$$$/WAS/rb/dfsSaveFilter=Save a Custom File Filter. Type in the Edit Box to the right to enable this button" );
		panel.removeFilterButton = fButtonGroup.add( "button", undefined, localize( "$$$/bsl/filters/rem=Remove " ) );
		panel.removeFilterButton.helpTip = localize( "$$$/WAS/rb/dfsRemoveFilter=Remove Selected Custom File Filter" );
		panel.saveFilterButton.enabled = false;
		panel.saveFilterButton.panel = panel;
		panel.removeFilterButton.panel = panel;
		panel.removeFilterButton.enabled = false;
		panel.filterEdit = panel.filterPanel.add( "edittext", undefined, undefined, {multiline:true} );
		panel.filterEdit.preferredSize = [200,60];
		panel.filterEdit.panel = panel;
		panel.removeFilterButton.onClick = function() {
			if ( this.panel.filterDrop.selection ) {
				WasLib.fileFilters.remove( this.panel.filterDrop.selection.text );
				WasLib.fileFilters.save();
				this.panel.filterDrop.removeAll();
			     	DialogUtilities.setupDropdown( this.panel.filterDrop, WasLib.fileFilters, "Contact Sheet" );
				this.panel.filterDrop.add( "item", WasLib.strings.theWordCustom );
			     	this.panel.saveFilterButton.enabled = false;
			     	this.enabled = false;
			}
		}
		panel.saveFilterButton.onClick = function() {
			var tempName = Window.prompt( localize( "$$$/WAS/IC/enterNameForFilter=Enter a name for your filter:" ) );
			if ( tempName && !isEmpty( trim( tempName ) ) ) {
				var checker = WasLib.fileFilters.getValue( tempName );
				if ( checker ) {
					if ( !confirm( '"' + tempName + '" ' + localize( "$$$/WAS/rb/overwriteFilter=exists, overwrite?" ) ) ) {
						return;
					}
				}
				WasLib.fileFilters.putValue( tempName, this.panel.filterEdit.text );
				this.panel.filterDrop.removeAll();
			     	DialogUtilities.setupDropdown( this.panel.filterDrop, WasLib.fileFilters, "Contact Sheet" );
				this.panel.filterDrop.add( "item", WasLib.strings.theWordCustom );
			}
		}
		panel.filterEdit.onChange = function() {
			WasLib.fileFilters.putValue( WasLib.strings.theWordCustom, this.text );
			WasLib.fileFilters.save();
			this.panel.filterChangeCallback( WasLib.strings.theWordCustom, this.text );
		}
		panel.filterEdit.onChanging = function() {
			WasLib.fileFilters.putValue( WasLib.strings.theWordCustom, this.text );
			if ( this.panel.filterDrop.selection ) {
				if ( this.panel.filterDrop.selection.text != WasLib.strings.theWordCustom ) {
			       	for ( var i = 0; i < this.panel.filterDrop.items.length; i++ ) {
			       		if ( this.panel.filterDrop.items[ i ].text == WasLib.strings.theWordCustom ) {
			       			this.panel.filterDrop.items[ i ].selected = true;
			       		}
				    	}
		 		}
			} else {
		       	for ( var i = 0; i < panel.filterDrop.items.length; i++ ) {
		       		if ( panel.filterDrop.items[ i ].text == WasLib.strings.theWordCustom ) {
		       			panel.filterDrop.items[ i ].selected = true;
		       		}
			    	}
			}
			this.panel.saveFilterButton.enabled = true;
		}
		panel.filterDrop.onChange = function() {
			if ( this.selection ) {
		     		this.panel.filterEdit.text = WasLib.fileFilters.getValue( this.selection.text );
				this.panel.removeFilterButton.enabled = ( this.selection.text != WasLib.strings.theWordCustom );
				this.panel.saveFilterButton.enabled = ( WasLib.strings.theWordCustom == this.selection.text );
				this.panel.filterChangeCallback( this.selection.text, this.panel.filterEdit.text );
			}
		}
		WasLib.setupDropdown( panel.filterDrop, WasLib.fileFilters );
		return panel.filterPanel;

	}
///	
///	Utility Functions
/// 
	WasLib.decimalToHex = function( d ) {
		var s = "00" + Number (d).toString (16);
		return s.substr (s.length - 2);	
	}
	WasLib.hexToDecimal = function( hex ) {
		return parseInt( hex, 16);
	}
/// functionName - setupDropdown 	- adds items to a dropdown or list boc
///
/// Description		If you store the choices for a ScriptUI dropdown in a Hashtable, this ditty
///				will take the dropdown list and Hashtable, and populate the list
/// arguments 		- list - ScriptUI list object (dropdown or list box )
///				- table = a hashtable - keys from the hashtable will be the items added
///				- selectedValue - if present will cause the key with the passed value to be the selected item
///				- selectedIndex - if present will cause the item with this index to be selected, note only works if
///                                                   selectedValue is passed as undefined, and will select the ADDED item by it's
///                                                   order in the hashtable. It will NOT select the list.item[ selectedIndex ]
/// returns		- Nothing
///
	WasLib.setupDropdown = function( list, table, selectedText, selectedIndex ) {
	    // ipaterso Watson 1465932 Empty dropdown list before populating...
	    list.removeAll();
		for ( var i in table.keyList ) {
			var item = list.add( "item", table.keyList[ i ]  );
			if ( selectedText ) {
				if ( table.keyList[ i ]  == selectedText ) {
					item.selected = true;
				}
			} else if ( selectedIndex != undefined ) {
				if ( i == selectedIndex ) {
					item.selected = true;
				}
			}
		}
	}
///
/// getGuid - if you ever needed a unique ID for something, this will do it 99.99% of the time.
///
	WasLib.getGuid = function() {
			return ( new String( new Number( new Date() ) ) +":" + Math.round( 1000000 * Math.random() ) +  ":" + Math.round( 1000000 * Math.random() ) + ":" + Math.round( 1000000 * Math.random() ) );	
	}
///
/// cullFolders - takes an array of file/folder objects and culls out the Folder objects
///
	WasLib.cullFolders = function( array ) {
		if ( array ) {
			var files = new Array();
			for ( var i in array ) { // get rid of any Folder objects in the file array
				// Part of Watson 1462673: I believe it's better to use the URI than fsName when working with VC assets
				if ( File( array[ i ].absoluteURI ) instanceof File ) {
					files.push( array[ i ] );
				}
			}
			return files;
		} else {
			return undefined;
		}
	}
///
/// WasLib.getBridgeFiles and WasLib.getBridgeThumbnails
///
/// 	These functions exist just cuz everything you do in Bridge Scripting has something to do with figuring out
///	 what files are selected in Bridge.
///	this function will return the selected thumbnails, or if nothing is selected, all of the visible thumbnails in the 
///	active bridge window. It calls app.preflightFile(), and work seamlessly with VersionCue.
///	Optionally, you can specify a set of extensions ( "jpg, jpeg, psd,..." ) as a mask, and it will return only files of 
///	that type. If files are skipped (not contained in the mask), it will flip up a warning with a do not show again option.
///	For standard masks, use the WasLib.fileFilters Hashtable.
///	If the user selects "Cancel" in the warning, these functions will return undefined. If they select nothing or nothing fits the parameters
///	of the script, it will return an empty array. Not that you use the undefined return condition to determine if the user cancelled.
///
///	Example:
///	var thumbs = WasLib.getBridgeThumbnails();
///	or
///	var files = WasLib.getBridgeFiles();
/// 	or
///	var files = WasLib.getBridgeFiles( WasLib.fileFilters.getValue( "Photoshop Openable" ) ); //gets only files openable by PS
///	if ( !files ) {
///		alert ( "User Cancelled this Operation" );
///	}
///	for ( var i in file ) {
///		// do your magic down here
///	}
	WasLib.thumbnailsToFiles = function( thumbs ) {
		if ( thumbs ) {
		    // Watson 1462703 (ipaterso) we assume we've already called acquirePhysicalFiles before this method is called-
		    // only client using this method is getBridgeFiles, which calls getBridgeThumbnails, which calls acquirePhysicalFiles
			var files = new Array();
			for ( var i in thumbs ) {
				var nextThumbnail = thumbs[i];
				if ( nextThumbnail != undefined && nextThumbnail.exists && nextThumbnail.spec != undefined ) {
				    files.push(File(nextThumbnail.spec.absoluteURI));
				}
			}
			return files;
		}
		return thumbs;
	}
	
	WasLib.getBridgeFilesOnlyIgnoringFolders = function( mask ) {
		return WasLib.thumbnailsToFiles( WasLib.getBridgeThumbnails( mask, true ) );
	}
	
	WasLib.getBridgeFiles = function( mask ) {
		return WasLib.thumbnailsToFiles( WasLib.getBridgeThumbnails( mask, false ) );
	}
	
	WasLib.preflightThumbnails = function( thumbs ) {
		app.acquirePhysicalFiles( thumbs ); // gurarantee that VC or other remote nodes exist
	}
	
	WasLib.getBridgeThumbnails = function( mask, ignoreFolders )  {	
	    // Follow-up to fixing 1468146- fix invalid logic in this method.
	    // NB Second expression in the if statement was typographically incorrect (thumnail for thumbnail)
	    // and the expression only evaluated to true by chance... when the typo was corrected, the
	    // logic was incorrect and I have removed it for that reason
		if ( !app.document ) {
			return new Array(); // no active document
		} else {
		    // Conditional logic to either take the active selection, or everything visible
		    // Don't repeatedly acquire app.document.selections.length
		    var cachedSelLength = app.document.selectionLength;
			var source = cachedSelLength == 0 ? app.document.visibleThumbnails : app.document.selections;
			var thumbs = new Array();
			var userSelectedBadFile = false;
			var userSelectedNoFile = ( cachedSelLength == 0 );
			if ( mask ) {
				for ( var i in source ) {
					switch ( source[ i ].type ) {
						case "alias" : {
							var t = source[ i ].resolve();
							// Check initially that the first resolve didn't lead to bad Thumbnail
							if ( typeof(t) == "undefined" || !t ) {
								break;
							}
							while( t.type == "alias" ) {
								var t = t.resolve();
								if ( typeof(t) == "undefined" || !t ) {
									break;
								}
							}
							if ( typeof(t) == "undefined" || !t ) {
								continue;
							} 
							
							if ( t.type == "file" ) {
								if ( t.spec.isFileType( mask ) ) {
									thumbs.push( t );
								} else {
									userSelectedBadFile = true;
								}
							} else {
							    if(!ignoreFolders) {
								    thumbs.push( t );
								}
							}
						}
						break;
						
						case "file" : {
							if ( source[ i ].spec.isFileType( mask ) ) {
								thumbs.push( source[ i ] );
							} else {
								userSelectedBadFile = true;
							}
						}
						break;
						
						case "folder" : {
							if(!ignoreFolders) {
							    thumbs.push( source[ i ] );
							}
						}
						break;
					}
				}
			} else {
			    // Code path for when no mask was supplied, so ignoreFolders is ignored in this case
				for ( var i in source ) {
					if ( source[ i ].type == "alias" ) {
							var t = source[ i ].resolve();
							while( t.type == "alias" ) {
								var t = t.resolve();
								if ( !t ) {
									break;
								}
							}
							if ( !t ) {
								continue;
							} 
							thumbs.push( t );
					} else if ( source[ i ].type == "file" || source[ i ].type == "folder" ) {
						thumbs.push( source[ i ] );
					}
				}
			}
			var goAhead = true;
			if ( userSelectedNoFile ) {
				if ( !app.preferences.WasLib_ShowNoSelWarning) { // and if they haven't asked to not see it again...
					var alert = new WasLib.AlertNoShow( localize("$$$/bslt/noFileSel=When you do not select any files, All files in the active Bridge document are processed. Click Cancel if this is not what you want." ), true );
					var goAhead = alert.show(); // warn them that files will be skipped, allow them to cancel
					app.preferences.WasLib_ShowNoSelWarning = !alert.showAgain; // if they don't want to be bugged, remember it
				}
			}
			if ( userSelectedBadFile ) { // if they selected a file that didn't fit the mask
				if ( !app.preferences.WasLib_ShowBadFileWarning) { // and if they haven't asked to not see it again...
					var alert = new WasLib.AlertNoShow( localize("$$$/bslt/badFile=Selected Files that are not of the correct type will be skipped" ), true );
					var goAhead = alert.show(); // warn them that files will be skipped, allow them to cancel
					app.preferences.WasLib_ShowBadFileWarning = !alert.showAgain; // if they don't want to be bugged, remember it
				}
			}
			if ( goAhead ) {
				try {
					app.acquirePhysicalFiles( thumbs ); // new API for downloading VC files, preflight is now deprecated
				} catch ( e ) {
				}
				return thumbs;
			} else {
				return undefined; // this signals scripts that the user cancelled the operation - an empty selection will return an empty array
			}
		} 
	}
///
/// createAppMenus - creates the PS, AI, and ID menus for all WAS Scripts
///
	WasLib.createAppMenus = function() {
		try {
			try {
				WasLoader.createMenu( "menu", WasLoader.strings.aiMenu, "after tools/ps", "tools/ai" );
			} catch ( e ) {
				var psServicesMenuExists = MenuElement.find ('Tools/PhotoshopServices') != null;
				WasLoader.createMenu( "menu", WasLoader.strings.aiMenu, psServicesMenuExists ? '-after Tools/PhotoshopServices-' : '-at the end of Tools', "tools/ai" );
			}
			WasLoader.createMenu( "menu", WasLoader.strings.idMenu, "after tools/ai", "tools/id" );
		} catch ( e ) {
			alert( localize( "$$$/WAS/wsl/installProb=There is a problem with your installation of the Workflow Automation Scripts. Please re-install." ) );
		}
	}
///
/// MenuElement.create bug workaround
///
	WasLib.createMenu = function( type, text, loc, id ) {
		var menu = MenuElement.find( id );
		if ( !menu ) {
			menu = MenuElement.create( type, text, loc, id );
		}
		return menu;
	}


///
///	Development Utilities to be removed, eventually
///
	WasLib.resetPrefs = function() {
		var ar = new Array();
		ar.push( "WasLib_ShowBadFileWarning" );
		ar.push( "WasLib_ShowNoSelWarning" );
		app.preferences.clear( ar );
		return "Preferences Reset";
	}

	WasLib.ProgressErrorPalette = function( title ) {
		this.paletteTitle = title || localize( "$$$/WAS/Library/stdErrMessageTitle=Encountered Errors During Processing" );
		this.palette = new Window( "palette", title );
		this.palette.location = [100,100];
		this.palette.orientation = "column";
		this.stat = this.palette.add( "statictext", undefined, localize( "$$$/WAS/Library/stdErrMessage=An Error ocurred while processing the listed files:" ) );
		this.list = this.palette.add( "listbox",undefined, undefined, {multiselect:false} );
		this.list.preferredSize = [280,100];
		var props = {};
		props.readonly = false;
		props.multiline = true;
		this.showDetail = this.palette.add( "checkbox", undefined, localize( "$$$/WAS/Library/showDetail=Show Details" ) );
		this.detail = this.palette.add( "edittext",undefined, undefined, props );
		this.showDetail.value = false;
		this.detail.visible = false;
		this.detail.preferredSize = [280,100];
		this.showDetail.detail = this.detail;
		this.errList = new  WasLib.Hashtable( true );
		this.list.errList = this.errList;
		this.list.detail = this.detail;
		this.list.onChange = function() {
	//		alert( this.selection.text );
			if ( this.selection ) {
				this.detail.text = this.errList.getValue( this.selection.text );
			}
		}
		this.showDetail.onClick = function() {
			this.detail.visible = this.value;
		}

	}
	WasLib.ProgressErrorPalette.prototype.show = function() {
		this.palette.show();
	}
	WasLib.ProgressErrorPalette.prototype.close = function() {
		this.palette.close();
	}
	WasLib.ProgressErrorPalette.prototype.hide = function() {
		this.palette.hide();
	}
	WasLib.ProgressErrorPalette.prototype.updateList = function() {
		this.list.removeAll();
		var lastItem = undefined;
		for ( var i = 0; i < this.errList.keyList.length; i++ ) {
			lastItem = this.list.add( "item", this.errList.keyList[ i ] );
		}
		lastItem.selected = true;
	}
	WasLib.ProgressErrorPalette.prototype.addError = function( key, err ) {
		this.errList.putValue( key, err );
		this.updateList();	
	}
///
/// This ditty checks to see if a target app is ready to work with bridgetalk, 
/// you supply it a target and a callbacks. One for a ready target app, one for a 
/// not ready target app. It will send a BridgeTalk message to the target, which will
/// start the app, if it's not already. If the app responds, then we have a good app.
/// If it doesn't respond, then something's wrong, app not installed correctly, modal dialog
/// showing, something....
/// Usage:
///		IF YOU ARE CALLING THIS FROM AN INSTANCE OF A CLASS YOU MUST SEND THE INSTANCE AS A CONTEXT VARIABLE
///		TO THE LAUNCH CALL AND TAKE THE INSTANCE BACK IN THE CALLBACK. IF YOU DON'T, 
///		YOU WON'T BE ABLE TO REFERENCE THE INSTANCE
///
///		myObject.prototype.myCallback = function( context, success, target ) {
///			if ( success ) {
///				// the app is launched and ready to run BT messages - no dialogs up, etc
///			} else { 
///				// the app is not launched or is busy, and won't take a bt message now
///			}
///		WasLib.AppLauncher.launch( "indesign", this.myCallback, this );
///
///		IF YOU ARE NOT CALLING THIS FROM AN INSTANCE OF A CLASS, OMIT THE CONTEXT
///		myCallback = function( success, target ) {
///			if ( success ) {
///				// the app is launched and ready to run BT messages - no dialogs up, etc
///			} else { 
///				// the app is not launched or is busy, and won't take a bt message now
///			}
///		WasLib.AppLauncher.launch( "indesign", myCallback );
///
///	  
	WasLib.AppLauncher = {};
	WasLib.AppLauncher.array = new Array();
	WasLib.AppLauncher.processID = null;
	WasLib.AppLauncher.running = false;
	WasLib.AppLauncher.pause = false;
	WasLib.AppLauncher.id = 0;
	WasLib.AppLauncher.launchTimeout = 2 * 60 * 1000; /* 2 minutes */
	WasLib.AppLauncher.launchCycle = 1000; /* launch and check launch status every 1000ms */
	WasLib.AppLauncher.launch = function( target, callback, context ) {
		if ( !BridgeTalk.isRunning( target ) ) {
			var launcher = new WasLib.AppLauncher.Launcher( target, callback, context );
			WasLib.AppLauncher.array.push( launcher );
			WasLib.AppLauncher.processId = app.scheduleTask( "WasLib.AppLauncher._$launch();", 50, false );
		} else {
			if ( context ) {
				callback.call( context, true, target );
			} else {
				callback( true, target );
			}
		}
	}
	WasLib.AppLauncher._$launch = function() {
		var ar = new Array();
		for ( var i = 0; i < WasLib.AppLauncher.array.length; i++ ) {
			if ( !WasLib.AppLauncher.array[ i ].killed ) {
				WasLib.AppLauncher.array[ i ].launch();
				ar.push( WasLib.AppLauncher.array[ i ] );
			} 
		}
		WasLib.AppLauncher.array = ar;
		if ( WasLib.AppLauncher.array.length > 0 ) { // if there's still tasks out there, reschedle, if not, it dies
			WasLib.AppLauncher.processId = app.scheduleTask( "WasLib.AppLauncher._$launch();", WasLib.AppLauncher.launchCycle, false );
		}
	}
	WasLib.AppLauncher.Launcher = function( target, callback, context ) {
		this.id = WasLib.AppLauncher.id++;
		this.target = target;
		this.callback = callback;
		this.context = context;
		this.calledback = false;
		this.launched = false;
		this.t1 = new Date();
		this.killed = false;
	}
	WasLib.AppLauncher.Launcher.prototype.timedOut = function() {
		return ( ( new Date() - this.t1 ) > WasLib.AppLauncher.launchTimeout );
	}
	WasLib.AppLauncher.Launcher.prototype.resultHandler = function() {
		if ( this.context ) {
			this.callback.call( this.context, true, this.target );
		} else {
			this.callback( true, this.target );
		}
	}
	WasLib.AppLauncher.Launcher.prototype.kill = function() {
		this.killed = true;
	}
	WasLib.AppLauncher.Launcher.prototype.launch = function() {
		if ( this.timedOut() ) {
			this.kill();
			if ( this.context ) {
				this.callback.call( this.context, false, this.target );
			} else {
				this.callback( false, this.target );
			}
		} else {
			if ( !this.launched ) {
				BridgeTalk.launch( this.target, "background" );
				this.launched = true;
			} 
			if ( BridgeTalk.isRunning( this.target ) && ( !this.calledback ) ) {
				this.calledback = true;
				this.kill();
				if ( this.context ) {
					this.callback.call( this.context, true, this.target );
				} else {
					this.callback( true, this.target );
				}
			}
		}
	}	
///
//---------------------------------------------------------
// 	Class Name - BridgeTalkIterator - 
//	Sends multiple bridgetalk messages, one after another, with an option
//	for a progress bar to be shown during the iteration. Progress bar
//	has an "n" of "total" status line( n / total ) for each iteration.
//  handled errors in individual units of the iteration, and reports back
//	error information.
// 	BridgeTalkIterator handles the progress bar itself, You do NOTHING
// 	In the iteration, you CAN (don't know why you would) send messages to different
//	target applications.
//
//  Usage:
//	getScript = function( file ) [
//		...code to generate a script based upon a passed file name
//	}
//	var target = "photoshop";
//	var showProgress = true;
//	var notifyComplete = false;
//	var progressTextLine = "Doing Something Now";
//	var progressWindowTitle = "Doing Something";
//	var bti = new BridgeTalkIterator( showProgress, progressTextLine, progressWindowTitle, notifyComplete );
//	for ( var i = 0; i < files.length; i++ ) {
//		var scp = getString( files[ i ] );
//		bti.add( target, scp );
//	}
//	bti.send();
//
	WasLib.BridgeTalkIterator = function( showProgress, text, title, notifyComplete ) {
		this.text = text;
		this.title = title;
		this.showProgress = showProgress;
		this.notifyComplete = notifyComplete;
		this.progress = undefined;
		this.messageBuffer = new WasLib.FIFOBuffer();
		this.firstMessageSent = false;
		this.errored = false;
		this.errorArray = new Array();
		this.count = 1;
		this.total = 0;
	}
	WasLib.BridgeTalkIterator.prototype.addMessage = function( target, script, fileName ) {
		this.target = target;
		var bt = new BridgeTalk();
		bt.target = target;
		bt.body = script;
		bt.fileName = decodeURI( fileName );
		this.messageBuffer.push( bt );
		this.total++;
	}
	WasLib.BridgeTalkIterator.prototype.increment = function() {
		if ( this.count < this.total ) {
			this.count++;
		}
	}
	WasLib.BridgeTalkIterator.prototype.send = function() {
		if ( this.showProgress ) {
			if ( !this.progress ) {
				this.progress = WasLib.progressDialog( this.text, this.title, this.messageBuffer.length, true, WasLib.strings.cancel );
				var txt = this.count + " / " + this.total;
				this.progress.setStatus( txt );
			} 
		}
		WasLib.AppLauncher.launch( this.target, this._sendPrimitive, this );
	}
	WasLib.BridgeTalkIterator.prototype._sendPrimitive = function( context, success, target ) {
		if ( !success ) {
			if ( this.showProgress ) {
				if ( this.progress ) {
					this.progress.close();
				} 
			}
			alert( this.target + " " + localize( "$$$/WAS/wsl/appNotReady=is not ready to receive messages. It may have a dialog box showing, or may not be installed correctly." ) );
			return;
		}
		if ( this.messageBuffer.length > 0 ) {
			var bt = this.messageBuffer.pop();
			bt.wrapper = this;
			bt.onResult = function( btObj ) {
				this.wrapper.increment();
				if ( this.wrapper.showProgress ) {
					var txt = this.wrapper.count + " / " + this.wrapper.total;
					this.wrapper.progress.setStatus( txt );
					this.wrapper.progress.increment();
				}
				this.wrapper.send();
			}
			bt.onError = function( btObj ) {
				this.wrapper.errorArray.push( this.wrapper.count );
				this.wrapper.increment();
				if ( !this.wrapper.errored ) {
					this.wrapper.errorPalette = new WasLib.ProgressErrorPalette( localize( "$$$/WAS/Library/stdErrMessageTitle2es=Encountered Errors During Processing" ) );
//					AdobeLibrary1.palette = this.wrapper.errorPalette;
					this.wrapper.errorPalette.show();
				}
				this.wrapper.errored = true;
				var errName = localize("$$$/WAS/Lib/iterationWord=Iteration " ) +  (this.wrapper.count - 1 )
				if ( this.fileName ) {
					errName = this.fileName;
				} 
				var errMessage = localize( "$$$/WAS/Lib/errFailureOnFile=The error message was: " ) + "\r\n\r\n" + btObj.body;
				this.wrapper.errorPalette.addError( errName, errMessage );
				if ( this.wrapper.showProgress ) {
					var txt = errName;	
					this.wrapper.progress.setText( txt );
					app.beep();
					$.sleep( 1500 );
					var txt = this.wrapper.count + " / " + this.wrapper.total;
					this.wrapper.progress.setStatus( txt );
					this.wrapper.progress.increment();
				}
				this.wrapper.send();
			}
			if ( this.showProgress ) {
				if ( !this.progress.cancelled ) {
					bt.send();
					this.firstMessageSent = true;
				}
			} else {
				bt.send();
				this.firstMessageSent = true;
			}
		} else {
			if ( this.showProgress ) {
				this.progress.setComplete();
				this.progress.close();
			}
			if ( this.notifyComplete ) {
				alert( AdobeLibraryStrings.operationComplete );
			}
		}
	}
//---------------------------------------------------------
// 	Class Name - BridgeTalkLongProcess - 
//	Designed to implement a progress bar during a long running process on the target application
//	It will flip up a progress window (palette), but it is up to YOUR script that executes on the 
//  target application to send back status messages to make the progress bar do anything.
//  
// 	To send back an status message, embed a function call in your long running target script.
//  the call is: sendBackStatus( increment, status, text );
//	increment is the value you want the progress bar to take, 0-100, 100 being complete.
//  status is the status line text. The progress palette has 2 lines of text, one on top of
// 		the other. This parameter affects the SECOND line. Useful for reporting what file name
//		is being operated on, etc. Consider it a detail information line for the progress palette
//  text is the FIRST line. Normally you wouldn't change this (but might want to in certain processes,
//    	it can be left out if you don't want to change.
//	
//	The funtion sendBackStatus() is generated using a call to the target application PRIOR to executing your
// 	script. The function is generated and then prepended to your script. In your script, DO NOT define your
//  own function named sendBackStatus(), it will render this object inoperable.
//
//	A Detailed example of the useage of this object is in ContactSheet_ID.jsx
//
//  Usage:
//	getScript = function( file ) [
//		var scp = "sendBackStatus( 0, 'Photoshop has accepted the task' ); \n" +
//			scp+= "... code to do something...; \n" +
//			scp+= "sendBackStatus( 10, 'We are 10% done' ); \n" +
//			scp+= "...more code to do something; \n" +
//			scp+= "sendBackStatus( 20, 'We are 20% done', 'Is this cool or what?' ); \n";
//			... more code....
//	}
//	var target = "photoshop";
//	var showProgress = true;
//	var notifyComplete = false;
//	var progressTextLine = "Doing Something Now";
//	var progressWindowTitle = "Doing Something";
//  var switchTargetOnCompletion = false;
//	var bt = new BridgeTalkLongProcess( showProgress, progressTextLine, progressWindowTitle, notifyComplete, target, getScript(), switchTargetOnCompletion );
//	bt.send();
//
	WasLib.BridgeTalkLongProcess = function( showProgress, text, title, notifyComplete, target, script, switchTargetOnCompletion ) {
		this.script = script;
		this.target = target;
		this.replyScript = "";
		this.progress = undefined;
		this.text = text;
		this.title = title;
		this.showProgress = showProgress;
		this.notifyComplete = notifyComplete
		this.progress = undefined;
		this.errored = false;
		this.errorArray = new Array();
		this.switchTargetOnCompletion = switchTargetOnCompletion;
	}
	WasLib.BridgeTalkLongProcess.instances = new WasLib.Hashtable();
	WasLib.BridgeTalkLongProcess.factory = function( showProgress, text, title, notifyComplete, target, script, switchTargetOnCompletion) {
		BridgeTalk.onReceive = function( msg ) {
			switch( msg.type ) {
				case "BridgeTalkLongProcess" : {
					var obj = eval( msg.body );
					WasLib.BridgeTalkLongProcess.updateProgress( obj );
					break;
				}
				default : { 
					app.lastSender = msg.sender; 	
					app.displayDialogs = 'all'; 	
					var retval =  eval( '$.level = 0; app.synchronousMode = false;' + msg.body ); 
					app.displayDialogs = 'all'; 	
					app.synchronousMode = false; 	
					return retval; 
				}
			}
		}
		var guid = WasLib.getGuid();
		var instance = new WasLib.BridgeTalkLongProcess( showProgress, text, title, notifyComplete, target, script, switchTargetOnCompletion);
		instance.replyScript = "sendBackStatus = function( increment, status, text, truncate, action ) { \n";
		instance.replyScript += "var obj = {}; \n";
		instance.replyScript += "obj.increment = increment; \n";
		instance.replyScript += "obj.status = status \n";
		instance.replyScript += "obj.text = text; \n";
		instance.replyScript += "obj.truncate = truncate; \n";
		instance.replyScript += "obj.action = action; \n";
		instance.replyScript += "obj.instanceId = \"" + guid + "\"; \n";
		instance.replyScript += "var bt = new BridgeTalk(); \n";
		instance.replyScript += "bt.body = obj.toSource(); \n";
		instance.replyScript += "bt.type = \"BridgeTalkLongProcess\"; \n";
		instance.replyScript += "bt.target = \"bridge\"; \n";
		instance.replyScript += "bt.send();";
		instance.replyScript += "}\n";	
		instance.id = guid;
		this.instances.putValue( guid, instance );
		return instance;
	}
	WasLib.BridgeTalkLongProcess.updateProgress = function( obj ) {
		var instance = WasLib.BridgeTalkLongProcess.instances.getValue( obj.instanceId );
		if ( instance ) {
			if ( obj.increment ) {
				instance.progress.setValue( obj.increment );
			}
			if ( obj.status ) {
				instance.progress.setStatus( obj.status, obj.truncate, obj.action );
			}
			if ( obj.text ) {
				instance.progress.setText( obj.text );
			}
		}
	} 
	WasLib.BridgeTalkLongProcess.prototype.send = function() {
		if ( this.showProgress ) { 
			this.progress = WasLib.progressDialog( this.text, this.title, 100, true, 'OK' );
			this.progress.setStatus( WasLib.strings.starting );			
		}
		WasLib.AppLauncher.launch( this.target, this._sendPrimitive, this );
	}
	WasLib.BridgeTalkLongProcess.prototype._sendPrimitive = function( context, success, target ) {
		if ( !success ) {
			if ( this.showProgress ) { 
				this.progress.close();
			}
			alert( this.target + " " + localize( "$$$/WAS/wsl/appNotReadyLP=is not ready to receive messages. It may have a dialog box showing, or may not be installed correctly." ) );
			return;
		}
		this.bt = new BridgeTalk();
		this.bt.target = this.target;
		this.bt.body = this.replyScript + this.script;
		this.bt.wrapper = this;
		
		this.bt.onResult = function( btObj ) {
			if ( this.wrapper.showProgress ) {
				this.wrapper.progress.close();
			}
			if ( this.wrapper.notifyComplete ) {
				alert ( WasLib.strings.operationComplete );
			}
			// Watson 1455566: if we have been asked to switch from source app (Bridge) to
			// target app (which might be InDesign), execute this switch
			if(this.wrapper.switchTargetOnCompletion) {
				BridgeTalk.bringToFront(this.wrapper.target);
			}
		}
		this.bt.onError = function( btObj ) {
			alert( WasLib.strings.errorLongRunning + ": " + btObj.body );
		}
		this.bt.send();
	}

//---------------------------------------------------------
// functionName - progressDialog	- creates a progress PALETTE...
// Description 
// arguments		- text - the first line of 2 text lines for user info
// 	 				- title - the window title of the palette
//					- max - the max number of units for the progress bar
//					- autoClose - whether to automatically close on completion
//					- btnText - the text of the ONE button on the palette
//	returns - a reference to the progress palette - NOTE: it SHOWS the palette for you
	WasLib.progressDialog = function( text, title, max, autoClose, btnText ) {
		this.buttonText = btnText || WasLib.strings.cancel;
		this.windowTitle =  title || WasLib.strings.progress;
		this.maxValue = max || 100;
		var d = new Window( "palette", this.windowTitle, [500,300,820,440] );
		this.d = d;
		d.staticText = d.add( "statictext",[10,0,290,20], text );
		d.staticText.justify = "center";
		d.statusText = d.add( "statictext",[10,30,290,50], "" );
		d.statusText.justify = "center";
		d.statusText2 = d.add( "statictext",[10,50,290,70], "" );
		d.statusText2.justify = "center";
		d.progress = d.add( "progressbar",[ 20,80,280,90] );
		d.cancelBtn = d.add( "button",[120,100,200,120], this.buttonText );
		d.progress.maxvalue = this.maxValue;
		d.progress.value = 0;
		d.cancelled = false;
		d.cancelBtn.onClick = function() {
			this.parent.cancelled = ( this.parent.progress.value < this.parent.progress.maxvalue );
			this.parent.close();
		}
		d.onClose = function() {
		}
		d.setValue = function( val ) {
			var numValue = parseInt( val );
			if ( isNaN( numValue ) ) {
				throw "Whoops";
			}
			this.progress.value = numValue;
			if ( this.progress.value >= this.progress.maxvalue ) {
				if ( autoClose ) {
					this.setText( WasLib.strings.complete );
					this.cancelBtn.text = WasLib.strings.OK;
					$.sleep( 1500 );
					this.close();
				} else {
					this.setText( WasLib.strings.complete );
					this.cancelBtn.text = WasLib.strings.OK;
				}
			}
		}
		d.setComplete = function() {
			this.setValue( this.progress.maxvalue );
		}
		d.increment = function() {
			this.setValue( ++d.progress.value );
		}
		d.setText = function( txt ) {
			this.staticText.text = this.checkStringWidth( txt );
		}
		d.setStatus = function( txt, truncate, action ) {
			if ( truncate ) {
				this.statusText.text = this.checkStringWidth( txt, action );
				this.statusText2.text = "";
			} else {
				this.splitString( txt );
			}
		}
		d.splitString = function( str, action) {
			var w = this.stringWidth( str );
			if ( w > 280 ) {
				var idx = Math.round( str.length / 2 ) - 5;
				idx = str.indexOf( " ", idx );
				if ( idx == -1 ) {
					this.statusText.text = this.checkStringWidth( str, action )
					this.statusText2.text = "";
				} else {
					this.statusText.text = str.substring( 0, idx );
					this.statusText2.text = str.substr( idx );
				}
			} else {
				this.statusText.text = str;
			}
		}
		d.stringWidth = function( str ) {
			var temp = this.add( "statictext", undefined, str );
			var w = temp.preferredSize.width + 2; // there's a 2 pixel bug in the call
			this.remove( temp );
			return w;
		}
		d.checkStringWidth = function( txt, action ) {
			if ( action && ( action != null ) ) {
				var prefix = action + ": ";
			} else {
				var prefix = "";
			}
			var w = this.stringWidth( prefix + txt );
			var res = prefix + txt;
			if ( w >= 280 ) {
				var idx = Math.round( ( ( ( w - 280 ) * res.length ) / w ) + 3 );
				res = prefix + "..." + txt.substr( idx );
			}
			return res;
		}
		d.show();
		return d;
	}

"Bridge Scripting Library Loaded";