Skip to main content
Participating Frequently
January 11, 2006
Question

Failure to draw palette

  • January 11, 2006
  • 7 replies
  • 918 views
After several hours attempting to add a progress palette to my scripts, I've concluded that there is no way to get the palette to completely display. The box is drawn, as is the Cancel button, but the background, static text and progress bar are not show, and the Cancel button is not sensitive to clicks.

I've tried inserting hide() and show(), insert waits, etc. to no avail. I suspect that the redraw thread isn't even being run while my onClick command function is running.

Is there a method that will force redraw that I can put in my processing loop? How do I make the palette have focus so the button click will work?

Is the thread model for scripting documented anywhere?

Perhaps it would be better for Adobe and its customers if "Extend"Script were open sourced?
This topic has been closed for replies.

7 replies

Known Participant
January 14, 2006
Thanks Jack, very helpful.

Andrew
Participating Frequently
January 14, 2006
Oops, my typo, the line should read

var command = d.commands.readln();
Participating Frequently
January 14, 2006
Hi Andrew,
See the example below.
Since this uses scheduleTask, there are a few observations:
1. The worker function needs to process piecemeal, a unit of
work per call - therefore it can't store state in local vars.
Instead, use properties of the task object.
2. scheduleTask seems to be limited to a minimum of 100ms
between schedules. Often this is fast enough if there is Photoshop
processing per step; If not then do a small loop per worker call.
3. The state mechanism can be used for more states than just "run",
letting you embed a FSM in the worker function.

myCommand = new MenuElement("command", "Process Commands", "at the end of
", "myProcessCommands");
myCommand.onSelect = function() {
withProgress("Process Commands", function(d)
{
switch (d.state){
case "init":
d.commands = new File("Z:/photo/_demoted.txt");
d.commands.open("r");
d.progress.maxvalue = d.commands.length;
break;
case "run":
var command = readln();
d.setText(command);
d.setValue(d.commands.tell());
if (d.commands.eof) {d.progress.cancelled = true;}
else remoteEval("photoshop", command);
break;
case "close":
d.commands.close();
break;
}
}
);
}

//Call photoshop with script to execute, synchronous
function remoteEval (target, body)
{
startTargetApplication(target);
var bt = new BridgeTalk;
bt.target = target;
bt.body = body;
bt.onError = function(err) {throw new Error (parseInt (err.headers ["Error-Code"]), err.body);}
var result = null;
bt.onResult = function(res) {result = res.body;}
//var timeout = new Date().getTime() + 60000; // 60 second timeout
bt.send();
//if (new Date().getTime() > timeout) throw new Error("Call to " + target + " timed out - " + body);
while(!result){
bt.pump();
$.sleep(100); // sleep 100ms waiting for result
}
return result;
}
Known Participant
January 14, 2006
Hi Jack

This looks very interesting but I am having trouble getting it to work for me. Could you give an example that uses the main functionality.

Thanks

Andrew
Participating Frequently
January 14, 2006
Bob,
Thanks for the example. I adapted the technique to a general "withProgress" mechanism, see below. I appreciate your timely replies to our queries.

withProgress.tasks = new Array();

//Execute worker function with progress bar
//In worker(task):
// switch on task.state ("init", "run", ..., "close")
// task.setText(text) -- sets text string in progress box
// task.setValue(number) -- sets position of progress bar
// task.progress.maxvalue -- initialize to maximum value
// task.cancelled -- set true to terminate task
// the programmer can add properties to task, e.g. task.index, an iteration variable
function withProgress(title,worker)
{
//registers task and return task number (insurance, often only one task running)
function register(task){
for (var i = 0; i < withProgress.tasks.length; i++){
if (!withProgress.tasks) {withProgress.tasks = task; return i;}
}
return (withProgress.tasks.push(task)-1);
}
var d = new Window("palette", title, [500,300,1000,400]);
d.progressText = d.add( "statictext",[10,0,500,20], "" );
d.progressText.justify = "center";
d.progress = d.add( "progressbar",[20,40,500,50] );
d.progress.value = 0;

d.cancelBtn = d.add( "button",[210,60,290,80], "Cancel" );
d.cancelled = false; // caller checks this and terminates
d.cancelBtn.onClick = function(){this.parent.cancelled = true; this.parent.close();}

d.setValue = function(val){this.progress.value = val;}
d.setText = function(txt){this.progressText.text = txt;}
d.show();

//task mixin on this object
d.taskN = register(d);
d.state = "init";
d.worker = worker;
d.taskID = app.scheduleTask("withProgressContinuation(" + d.taskN + ");",0,true);
return d;
}

function withProgressContinuation(taskN)
{
function terminate(taskN){
var d = withProgress.tasks[taskN];
d.close();
app.cancelTask(d.taskID);
withProgress.tasks[taskN] = null;
}
try {
var d = withProgress.tasks[taskN];
if (d.cancelled) d.state = "close";
switch (d.state){
case "init":
d.worker(d);
d.state = "run";
//falls through
default:
d.worker(d);
break;
case "close":
d.worker(d);
terminate(taskN);
break;
}
} catch (err) {
terminate(taskN);
alert(err);
throw new Error (parseInt (err.headers ["Error-Code"]), err.body);}
}
Known Participant
January 11, 2006
OOPS...

that example was too long...

Here's the bottom part of the example:

// test code, creates a menu in Bridge, sets the onSelect handler.
// creates a folder /my documents/copyTemp - this is the only place it copies to...
// open a page with files in Bridge, select the menu
// it will copy all of the files in that folder, or if you selected a couple
// it will copy the selected files.
BgFileCopy.execute = function() {
try {
$.level = 1;
var files = getBridgeFiles();
var copier = BgFileCopy.factory();
var toFolder = new Folder( "~/my documents/copyTemp" );
toFolder = verifyFolder( toFolder, true );
for ( var i = 0; i < files.length; i++ ) {
var from = files[ i ];
var to = new File( toFolder.absoluteURI + "/" + from.name );
copier.add( from, to );
}
copier.setCloseOnEmpty();
} catch ( e ) {
alert( e );
}
}

var menuItem = createMenu( "command", "BgFileCopy Test", "at the end of Tools", "tools/fct", BgFileCopy.execute );
try {
ScriptManager.reportLoading( BgFileCopy.scriptInfo );
} catch ( e ) {
}

}
Known Participant
January 11, 2006
Jack,

This is a problem with Bridge in that when a script is running full speed, all of the redraws (bridge main window included) are not executed. Eventually the bridge window itself will go white, giving the impression that things have locked up.

That said, well after I wrote the Import from Camera script (which suffers from this malaise), I figured out a way around this.

The basic method is to use scheduled tasks. Each iteration of your script will be a separate execution of a scheduled task. You have to give it a 50-100ms or so between iterations to give Bridge a chance to update things.

Here is a example implementing a background file copy. Hope it helps.
Since then I've come up with easier implementations. First don't use a recurring task, reschedule the task on each iteration until you're out of iterations. That cleans things up a lot.

Bob
Adobe Workflow Scripting

#target bridge

if ( BridgeTalk.appName == "bridge" ) {
var BgFileCopy = {};
try {
BgFileCopy.scriptInfo = new ScriptManager.ScriptInfo();
BgFileCopy.scriptInfo.set( "name", "Background File Copy" );
BgFileCopy.scriptInfo.set( "author", "Bob Stucky" );
BgFileCopy.scriptInfo.set( "company", "Adobe Systems, Inc." );
BgFileCopy.scriptInfo.set( "copyright", "Copyright (c) 2004-2005 Adobe Systems Incorporated. All rights reserved" );
BgFileCopy.scriptInfo.set( "version", "0.1.1" );
BgFileCopy.scriptInfo.set( "date", "08-03-2005" );
BgFileCopy.scriptInfo.set( "website", "http://www.adobe.com" );
} catch ( e ) {
}

// background file copy for scripting

BgFileCopy._bgProcessRunning = false;
BgFileCopy._bgTaskId = "";
BgFileCopy._array = new Array();
BgFileCopy._cycle = 500;
BgFileCopy._nextId = 0;
BgFileCopy._ticks = 0;

///
/// method: BgFileCopy.bgProcess()
/// brief: Execute a background copy cycle
/// This is a primitive for internal use only. This method is the master process
///
BgFileCopy._bgProcess = function( ) {
try {
BgFileCopy._ticks++;
var a = new Array();
for ( var i = 0; i < BgFileCopy._array.length; i++ ) {
var copier = BgFileCopy._array[ i ];
if ( !copier.isEmpty ) {
copier._executeBackground();
a.push( copier );
} else if ( !copier.closeOnEmpty ) {
a.push( copier );
}
}
BgFileCopy._array = a;
if ( a.length == 0 ) {
app.cancelTask( BgFileCopy._bgTaskId );
BgFileCopy._bgProcessRunning = false;
}
} catch ( e ) {
alert( e );
}
// app.document.status = "BG Process: " + BgFileCopy.ticks++;
}

BgFileCopy._addBgProcess = function( object ) {
try {
BgFileCopy._array.push( object );
if ( !BgFileCopy._bgProcessRunning ) {
BgFileCopy._bgTaskId = app.scheduleTask( "BgFileCopy._bgProcess()", BgFileCopy._cycle, true );
BgFileCopy._bgProcessRunning = true;
}
} catch ( e ) {
alert( e );
}
}
BgFileCopy.factory = function( onComplete, onTick, onError ) {
var t = new BgFileCopy.FileCopier( BgFileCopy._nextId++, onComplete, onTick, onError );
BgFileCopy._addBgProcess( t );
return t;
}
BgFileCopy.toString = function(){
return "[Object BgFileCopy]";
}
///
/// class: BgFileCopy.CopySet
/// brief: Utility class that links two files, on a copyFrom, one a copyTo file.
/// During execution, a copier will copy files as a set, "from" one file "to" the other.
///
BgFileCopy.CopySet = function( from, to ) {
this.from = from;
this.to = to;
}
///
/// toString() everyone should have one
///
BgFileCopy.CopySet.prototype.toString = function() {
return "[Object BgFileCopy.CopySet]";
}
/// class: BgFileCopy.CopyError
/// brief: wrapper object to carry error information back to the calling process
///
BgFileCopy.CopyError = function( fileCopier, err, from, to ) {
this.fileCopier = fileCopier;
this.error = err;
this.from = from;
this.to = to;
}
///
/// class: BgFileCopy.FileCopier
/// brief: the workhorse class that executes the copy
/// Do not use the constructor, use the BgFileCopy.factory method to obtain one of these
/// The FileCopier object will not start executing until you call its execute method
///
BgFileCopy.FileCopier = function( id, onComplete, onTick, onError ) {
this.id = id;
this.onComplete = onComplete || BgFileCopy.onComplete;
this.onTick = onTick || BgFileCopy.onTick;
this.onError = onError || BgFileCopy.onError;
this.executing = false;
this.acceptNewFiles = true;
this.rewindOnError = false;
this.closeOnEmpty = false;
this.isEmpty = false;
this.error = "";
this.errored = false;
this.buffer = new FIFOBuffer();
this.copied = new Array();
this.fileCount = 0;
this.current = 0;
}
BgFileCopy.FileCopier.prototype.toString = function() {
return "[Object BgFileCopy.FileCopier]";
}
///
/// method: BgFileCopy.FileCopier.add
/// brief: adds a CopySet of files to the copy buffer;
/// throws: an exception if the copier is flagged to not accept new sets of files
///
BgFileCopy.FileCopier.prototype.add = function( from, to ) {
if ( this.acceptNewFiles ) {
this.buffer.push( new BgFileCopy.CopySet( from, to ) );
this.isEmpty = false;
this.fileCount++;
return true;
}
throw "BgFileCopy.FileCopier not accepting new files to copy";
}
///
/// method: BgFileCopier.FileCopier.setRewindOnError /// brief: sets a flag rewind on an error condition
///
BgFileCopy.FileCopier.prototype.setRewindOnError = function( boo ) {
this.rewindOnError = boo;
}
///
/// method: BgFileCopier.FileCopier.rewind
/// brief: removes all copied files. Use if there is an error condition and you want all the
/// files copied befor the error to be deleted
///
BgFileCopy.FileCopier.prototype.rewind = function() {
for ( var i = 0; i < this.copied.length; i++ ) {
this.copied[ i ].remove();
}
}
BgFileCopy.FileCopier.prototype.setCloseOnEmpty = function() {
this.closeOnEmpty = true;
this.acceptNewFiles = false;
}
///
/// method: BgFileCopier.FileCopier._copy
/// brief: The primitive method called by BgFileCopier's master process to execute the copy
///
BgFileCopy.FileCopier.prototype._executeBackground = function() {
var set = this.buffer.pop();
if ( set != undefined ) {
if ( this.onTick ) {
this.onTick( set, ++this.current, this.fileCount );
}
if ( set.from.copy( set.to ) ) {
this.copied.push( set.to );
return;
} else {
this.error = set.from.error;
this.errored = true;
if ( this.onError != undefined ) {
this.onError( new BgFileCopy.CopyError( this, this.error, set.from, set.to ) );
}
if ( this.rewindOnError ) {
this.rewind();
}
return;
}
} else {
if ( this.closeOnEmpty ) {
if ( this.onComplete ) {
this.onComplete();
}
this.isEmpty = true;
}

}
}
///
/// method: BgFileCopier.onComplete /// brief: default onComplete handler
///
BgFileCopy.onComplete = function() {
app.document.status = "Copy Complete";
}
///
/// method: BgFileCopier.onTick /// brief: default onTick handler
///
BgFileCopy.onTick = function( set, current, fileCount ) {
app.document.status = "Copying: " + decodeURI( set.from.name ) + " To: " + decodeURI( set.to.name );
}
///
/// method: BgFileCopier.onErr /// brief: default onError handler
///
BgFileCopy.onError = function( copyError ) {
app.document.status = copyError.error;
}
// test code, creates a menu in Bridge, sets the onSelect handler.
// creates a folder /my documents/copyTemp - this is the only place it copies to...
// open a page with files in Bridge, select the menu
// it will copy all of the files in that folder, or if you selected a couple
// it will copy the selected files.
BgFileCopy.execute = function() {
try {
$.level = 1;
var files = getBridgeFiles();
var copier = BgFileCopy.factory();
var toFolder = new Folder( "~/my documents/copyTemp" );
toFolder = verifyFolder( toFolder, true );
for ( var i = 0; i < files.length; i++ ) {
var from = files[ i ];
var to =