Here is a mini-Framework that I am currently using for constructing ScriptUI applications.
Code: Select all//
// GenericUI is the core class for this framework.
//
GenericUI = function() {};
GenericUI.prototype.ftn = undefined; // callback for actual processing
GenericUI.prototype.title = "Generic UI"; // the window title
GenericUI.prototype.winRect = { // the rect for the window
x: 200,
y: 200,
w: 100,
h: 200
};
GenericUI.prototype.notesSize = 100; // the height of the Notes text panel
// set to 0 to disable
GenericUI.prototype.documentation = "some documentation for this script";
GenericUI.prototype.iniFile = undefined; // the name of the ini file used for
// this script. Set to 'undefined' to
// disable ini processing
//
// createWindow constructs a window with a documentation panel and a app panel
// and 'Process' and 'Cancel' buttons. 'createPanel' (implemented by the app
// script) is invoked by this method to create the app panel.
//
GenericUI.prototype.createWindow = function(ini) {
var self = this;
var wrect = self.winRect;
function rectToBounds(r) {
return[r.x, r.y, r.x+r.w, r.y+r.h]
};
var win = new Window('dialog', self.title, rectToBounds(wrect));
win.mgr = this; // save a ref to the UI manager
var xOfs = 10;
var yy = 10;
if (self.notesSize) {
// define the notes panel (if needed) and insert the documentation text
var docPnl = win.add('panel',
[xOfs, yy, wrect.w-xOfs, self.notesSize+10],
'Notes:');
docPnl.add('statictext',
[10,10,docPnl.bounds.width-10,self.notesSize-20],
self.documentation,
{multiline:true});
yy += self.notesSize + 10;
}
// Now, create the application panel
win.appPnl = win.add('panel', [xOfs, yy, wrect.w-xOfs, wrect.h - 60]);
// and call the application callback function with the ini object
self.createPanel(win.appPnl, ini);
// Create the Process/Cancel buttons
var btnY = wrect.h - 40;
var btnW = 90;
var btnOfs = (wrect.w - (2 * btnW)) / 3;
win.process = win.add('button',
[btnOfs,btnY,btnOfs+btnW,btnY+20],
'Process');
win.cancel = win.add('button',
[wrect.w-btnOfs-btnW,btnY,wrect.w-btnOfs,btnY+20],
'Cancel');
win.defaultElement = win.process;
// And now the callback for the process button.
// We ignore the Cancel button.
win.process.onClick = function() {
try {
// validate the contents of the window
var rc = this.parent.validate();
if (!rc) {
// if there was a terminal problem with the validation,
// close up the window
this.parent.close(2);
}
} catch (e) {
alert(e.toSource());
}
}
// Point to the validation
win.validate = GenericUI.validate;
return win;
};
// a simple utility function for copying properties from one object to another
GenericUI.prototype.copyObject = function(obj) {
var self = this;
for (var idx in obj) {
if (typeof obj[idx] != "function") {
self[idx] = obj[idx];
}
}
};
//
// exec runs the ui and the application callback
// doc is the document to operate on (optional)
// if noUI is true, the window is not open. The runtime parameters
// are taken from the ini file.
//
GenericUI.prototype.exec = function(doc, noUI) {
var self = this;
// read the ini file (if present)
var ini = self.readIni();
var opts;
if (noUI) {
// if we don't want a UI, just use the ini object
opts = ini;
} else {
// create window
var win = self.createWindow(ini);
// run the window and return the parameters mapped from the window
opts = self.run(win);
}
// $.level = 1; debugger;
// if we got options back, we can do some processing
if (opts) {
var argz = [];
if (doc) { // make the doc the first parameter if we have one
argz.push(doc);
}
argz.push(opts);
// if we have an application callback function, run it
if (self.ftn) {
self.ftn(argz[0], argz[1]);
} else {
// if there is no callback, just pop up the options
alert(listProps(opts));
}
}
};
//
// the run method 'show's the window. If it ran successfully, the options
// returned are written to an ini file (if one has been specified
//
GenericUI.prototype.run = function(win) {
var self = this;
win.show();
if (win.opts) {
self.writeIni(win.opts);
}
return win.opts;
};
//
// errorPrompt is used in window/panel validation. It pops up a 'confirm'
// with the prompt 'str'. If the user selects 'Yes', the 'confirm' is closed
// and the user is returned to the window for further interaction. If the user
// selects 'No', the 'confirm' is closed, the window is closed, and the script
// terminates.
//
GenericUI.errorPrompt = function(str) {
return confirm(str + "\r\rContinue?", false);
};
//
// 'validate' is called by the win.process.onClick method to validate the
// contents of the window. To validate the window, we call the application
// defined 'validatePanel' method. 'validate' returns 'true', 'false', or
// an options object with the values collected from the application panel.
// If 'true' is returned, this means that there was a problem with validation
// but the user wants to continue. If 'false' is returned, there was a problem
// with validation and the user wants to stop. If an object is returned, the
// window is closed and processing continues based on the options values
//
GenericUI.validate = function() {
var win = this;
var mgr = win.mgr;
try {
var res = mgr.validatePanel(win.appPnl);
if (typeof res == 'boolean') {
return res;
}
win.opts = res;
win.close(1);
return true;
} catch (e) {
alert(e.toSource());
return false;
}
};
//
// readIni
// writeIni
// Methods for reading and writing ini files in this framework. The actual
// reading and writing is implemented by Stdlib functions and only occurs
// if an ini file has been specified
//
// These can be replaced with other storage mechanisms such as Rob Stucky's
// ScriptStore class.
//
GenericUI.prototype.readIni = function() {
var self = this;
var ini = undefined;
if (self.iniFile) {
var file = new File(self.iniFile);
if (file.exists) {
try {
Stdlib;
} catch (e) {
alert("Stdlib.readIniFile is not available. Either included it " +
"in this script or replace it with something comparable.");
return undefined;
}
ini = Stdlib.readIniFile(file);
}
}
return ini;
};
GenericUI.prototype.writeIni = function(ini) {
var self = this;
if (ini && self.iniFile) {
try {
Stdlib;
} catch (e) {
alert("Stdlib.writeIniFile is not available. Either included it " +
"in this script or replace it with something comparable.");
return undefined;
}
Stdlib.writeIniFile(self.iniFile, ini);
}
};
//
// createPanel returns a panel specific to this app
// win is the window into which the panel to be inserted
// ini is an object containing default values for the panel
//
GenericUI.prototype.createPanel = function(win, ini) {};
//
// validatePanel returns
// - an object representing the gather input
// - true if there was an error, but continue gathering input
// - false if there was an error and terminate
//
GenericUI.prototype.validatePanel = function() {};
-X
mini-Framework for UI scripts
mini-Framework for UI scripts
Here is an example of how the framework would be used.
Code: Select all// The Sample class is where the actual processing for this script
// is implemented
Sample = function() {};
// Here is our application callback
Sample.process = function(opts) {
alert("In Sample.process:" + opts.toSource()); // all we do is alert
};
//
// This is the class that contains our options for this script
//
SampleUIOptions = function() {
var self = this;
self.source = '';
self.outf = '';
};
//
// SampleUI is our UI class
//
SampleUI = function() {};
// make it a subclass of GenericUI
SampleUI.prototype = new GenericUI();
// Point the callback function
SampleUI.prototype.ftn = Sample.process;
SampleUI.prototype.title = "Sample UI"; // our window title
SampleUI.prototype.winRect = { // the size of our window
x: 200,
y: 200,
w: 420,
h: 220
};
SampleUI.prototype.notesSize = 50; // The height of our Notes panel
SampleUI.prototype.documentation =
"some documentation for our Sample Application";
SampleUI.prototype.iniFile = "~/Sample.ini"; // our ini file name
// Here is where we create the components of our panel
SampleUI.prototype.createPanel = function(pnl, ini) {
var xOfs = 10;
var yy = 10;
// for our panel, we have a source directory input
var xx = xOfs;
pnl.add('statictext', [xx,yy,xx+110,yy+20], 'Source Directory:');
xx += 110;
pnl.source = pnl.add('edittext', [xx,yy,xx+220,yy+20],
new Folder("~").fsName);
xx += 225;
pnl.sourceBrowse = pnl.add('button', [xx,yy,xx+30,yy+20], '...');
yy += 40;
xx = xOfs;
// and a target directory input
pnl.add('statictext', [xx,yy,xx+110,yy+20], 'Target Directory:');
xx += 110;
pnl.outf = pnl.add('edittext', [xx,yy,xx+220,yy+20],
new Folder("~").fsName);
xx += 225;
pnl.outfBrowse = pnl.add('button', [xx,yy,xx+30,yy+20], '...');
// now specify the callbacks for our controls
pnl.sourceBrowse.onClick = function() {
try {
var pnl = this.parent;
var f = Stdlib.selectFolder("Select a Source folder", pnl.source.text);
if (f) {
pnl.source.text = decodeURI(f.fsName);
if (!pnl.outf.text) {
pnl.outf.text = pnl.source.text;
}
}
} catch (e) {
alert(e);
}
}
pnl.outfBrowse.onClick = function() {
var pnl = this.parent;
var f;
var def = pnl.outf.text;
if (!def && pnl.source.text) {
def = pnl.source.text;
}
f = Stdlib.selectFolder("Select a Destination Folder", def);
if (f) {
pnl.outf.text = decodeURI(f.fsName);
}
}
if (ini) {
// if we have an ini object
if (ini.source) {
pnl.source.text = ini.source; // get the source directory
}
if (ini.outf) {
pnl.outf.text = ini.outf; // get the target directory
}
}
// return the panel object
return pnl;
};
// just use the default errorPrompt
SampleUI.prototype.errorPrompt = GenericUI.errorPrompt;
//
// code for validating our panel
//
SampleUI.prototype.validatePanel = function(pnl) {
var self = this;
var opts = new SampleUIOptions(); // our options object
// A source directory must be specified and must exist
var f;
if (pnl.source.text) {
f = new Folder(pnl.source.text);
}
if (!f || !f.exists) {
return self.errorPrompt("Source folder not found");
}
opts.source = f.fsName;
// A target directory must be specified and either must already
// exist or we need to be able to create now
if (pnl.outf.text) {
f = new Folder(pnl.outf.text);
if (!f.exists) {
if (!f.create()) {
return self.errorPrompt("Unable to create target folder");
}
}
}
if (!f || !f.exists) {
return self.errorPrompt("Target folder not found");
}
opts.outf = f.fsName;
// return our valid options (if we made it this far)
return opts;
};
//
// This version uses the current doc and uses the ini options instead of
// opening a window
//
// Sample.main = function() {
// var doc = undefined;
// if (app.documents.length) {
// doc = app.documents;
// }
// var ui = new SampleUI();
// ui.exec(doc, true);
// };
// This version collects options via a window
Sample.main = function() {
var ui = new SampleUI();
ui.exec();
};
Sample.main();
-X
Code: Select all// The Sample class is where the actual processing for this script
// is implemented
Sample = function() {};
// Here is our application callback
Sample.process = function(opts) {
alert("In Sample.process:" + opts.toSource()); // all we do is alert
};
//
// This is the class that contains our options for this script
//
SampleUIOptions = function() {
var self = this;
self.source = '';
self.outf = '';
};
//
// SampleUI is our UI class
//
SampleUI = function() {};
// make it a subclass of GenericUI
SampleUI.prototype = new GenericUI();
// Point the callback function
SampleUI.prototype.ftn = Sample.process;
SampleUI.prototype.title = "Sample UI"; // our window title
SampleUI.prototype.winRect = { // the size of our window
x: 200,
y: 200,
w: 420,
h: 220
};
SampleUI.prototype.notesSize = 50; // The height of our Notes panel
SampleUI.prototype.documentation =
"some documentation for our Sample Application";
SampleUI.prototype.iniFile = "~/Sample.ini"; // our ini file name
// Here is where we create the components of our panel
SampleUI.prototype.createPanel = function(pnl, ini) {
var xOfs = 10;
var yy = 10;
// for our panel, we have a source directory input
var xx = xOfs;
pnl.add('statictext', [xx,yy,xx+110,yy+20], 'Source Directory:');
xx += 110;
pnl.source = pnl.add('edittext', [xx,yy,xx+220,yy+20],
new Folder("~").fsName);
xx += 225;
pnl.sourceBrowse = pnl.add('button', [xx,yy,xx+30,yy+20], '...');
yy += 40;
xx = xOfs;
// and a target directory input
pnl.add('statictext', [xx,yy,xx+110,yy+20], 'Target Directory:');
xx += 110;
pnl.outf = pnl.add('edittext', [xx,yy,xx+220,yy+20],
new Folder("~").fsName);
xx += 225;
pnl.outfBrowse = pnl.add('button', [xx,yy,xx+30,yy+20], '...');
// now specify the callbacks for our controls
pnl.sourceBrowse.onClick = function() {
try {
var pnl = this.parent;
var f = Stdlib.selectFolder("Select a Source folder", pnl.source.text);
if (f) {
pnl.source.text = decodeURI(f.fsName);
if (!pnl.outf.text) {
pnl.outf.text = pnl.source.text;
}
}
} catch (e) {
alert(e);
}
}
pnl.outfBrowse.onClick = function() {
var pnl = this.parent;
var f;
var def = pnl.outf.text;
if (!def && pnl.source.text) {
def = pnl.source.text;
}
f = Stdlib.selectFolder("Select a Destination Folder", def);
if (f) {
pnl.outf.text = decodeURI(f.fsName);
}
}
if (ini) {
// if we have an ini object
if (ini.source) {
pnl.source.text = ini.source; // get the source directory
}
if (ini.outf) {
pnl.outf.text = ini.outf; // get the target directory
}
}
// return the panel object
return pnl;
};
// just use the default errorPrompt
SampleUI.prototype.errorPrompt = GenericUI.errorPrompt;
//
// code for validating our panel
//
SampleUI.prototype.validatePanel = function(pnl) {
var self = this;
var opts = new SampleUIOptions(); // our options object
// A source directory must be specified and must exist
var f;
if (pnl.source.text) {
f = new Folder(pnl.source.text);
}
if (!f || !f.exists) {
return self.errorPrompt("Source folder not found");
}
opts.source = f.fsName;
// A target directory must be specified and either must already
// exist or we need to be able to create now
if (pnl.outf.text) {
f = new Folder(pnl.outf.text);
if (!f.exists) {
if (!f.create()) {
return self.errorPrompt("Unable to create target folder");
}
}
}
if (!f || !f.exists) {
return self.errorPrompt("Target folder not found");
}
opts.outf = f.fsName;
// return our valid options (if we made it this far)
return opts;
};
//
// This version uses the current doc and uses the ini options instead of
// opening a window
//
// Sample.main = function() {
// var doc = undefined;
// if (app.documents.length) {
// doc = app.documents;
// }
// var ui = new SampleUI();
// ui.exec(doc, true);
// };
// This version collects options via a window
Sample.main = function() {
var ui = new SampleUI();
ui.exec();
};
Sample.main();
-X
mini-Framework for UI scripts
Here's the code extract from Stdlib for file I/O and ini file handling. Note that the ini read/write functions will only process string/boolean/number properties. If your ini objects have nested objects, you need to manually flatten or map them to an intermediate object format.
This extract appears complete, but I haven't tested it.
Code: Select allStdlib = function Stdlib() {};
isCS3 = function() { return version.match(/^10\./); };
isCS2 = function() { return version.match(/^9\./); };
isCS = function() { return version.match(/^8\./); };
isPS7 = function() { return version.match(/^7\./); };
String.prototype.trim = function() {
return this.replace(/^[\s]+|[\s]+$/g, '');
};
throwFileError = function(f, msg) {
if (msg == undefined) msg = '';
throw msg + '\"' + f + "\": " + f.error + '.';
};
//
// Return a File or Folder object given one of:
// A File or Folder Object
// A string literal or a String object that refers to either
// a File or Folder
//
Stdlib.convertFptr = function(fptr) {
var f;
if (fptr.constructor == String) {
f = File(fptr);
} else if (fptr instanceof File || fptr instanceof Folder) {
f = fptr;
} else {
throw "Bad file \"" + fptr + "\" specified.";
}
return f;
};
Stdlib.writeToFile = function(fptr, str, encoding) {
var file = Stdlib.convertFptr(fptr);
file.open("w") || throwFileError(file, "Unable to open output file ");
if (encoding) {
file.encoding = encoding;
}
if (isPS7() && encoding == 'BINARY') {
file.lineFeed = 'unix';
var pos = 0;
var cr = '\r';
var next;
while ((next = str.indexOf(cr, pos)) != -1) {
file.write(str.substring(pos, next));
file.lineFeed = 'mac';
file.write(cr);
file.lineFeed = 'unix';
pos = next + 1;
}
if (pos < str.length) {
file.write(str.substring(pos));
}
} else {
file.write(str);
}
file.close();
};
Stdlib.readFromFile = function(fptr, encoding) {
var file = Stdlib.convertFptr(fptr);
file.open("r") || throwFileError("Unable to open input file ");
if (encoding) {
file.encoding = encoding;
}
var str = file.read();
file.close();
return str;
};
Stdlib.toIniString = function(obj) {
var str = '';
for (var idx in obj) {
if (idx.charAt(0) == '_') { // private stuff
continue;
}
var val = obj[idx];
if (typeof val == "string" ||
typeof val == "number" ||
typeof val == "boolean") {
str += (idx + ": " + val.toString() + "\n");
}
}
return str;
};
Stdlib.fromIniString = function(str, obj) {
if (!obj) {
obj = {};
}
var lines = str.split(/\r|\n/);
var rexp = new RegExp(/([^:]+):(.*)$/);
for (var i = 0; i < lines.length; i++) {
var line = lines.trim();
if (!line || line.charAt(0) == '#') {
continue;
}
var ar = rexp.exec(line);
if (!ar) {
alert("Bad line in config file: \"" + line + "\"");
return;
}
obj[ar[1].trim()] = ar[2].trim();
}
return obj;
};
Stdlib.readIniFile = function(fptr, obj) {
if (!obj) {
obj = {};
}
fptr = Stdlib.convertFptr(fptr);
if (!fptr.exists) {
return obj;
}
var str = Stdlib.readFromFile(fptr);
return Stdlib.fromIniString(str, obj);
};
Stdlib.writeIniFile = function(fptr, obj, header) {
var str = (header != undefined) ? header : '';
str += Stdlib.toIniString(obj);
Stdlib.writeToFile(fptr, str);
};
-X
This extract appears complete, but I haven't tested it.
Code: Select allStdlib = function Stdlib() {};
isCS3 = function() { return version.match(/^10\./); };
isCS2 = function() { return version.match(/^9\./); };
isCS = function() { return version.match(/^8\./); };
isPS7 = function() { return version.match(/^7\./); };
String.prototype.trim = function() {
return this.replace(/^[\s]+|[\s]+$/g, '');
};
throwFileError = function(f, msg) {
if (msg == undefined) msg = '';
throw msg + '\"' + f + "\": " + f.error + '.';
};
//
// Return a File or Folder object given one of:
// A File or Folder Object
// A string literal or a String object that refers to either
// a File or Folder
//
Stdlib.convertFptr = function(fptr) {
var f;
if (fptr.constructor == String) {
f = File(fptr);
} else if (fptr instanceof File || fptr instanceof Folder) {
f = fptr;
} else {
throw "Bad file \"" + fptr + "\" specified.";
}
return f;
};
Stdlib.writeToFile = function(fptr, str, encoding) {
var file = Stdlib.convertFptr(fptr);
file.open("w") || throwFileError(file, "Unable to open output file ");
if (encoding) {
file.encoding = encoding;
}
if (isPS7() && encoding == 'BINARY') {
file.lineFeed = 'unix';
var pos = 0;
var cr = '\r';
var next;
while ((next = str.indexOf(cr, pos)) != -1) {
file.write(str.substring(pos, next));
file.lineFeed = 'mac';
file.write(cr);
file.lineFeed = 'unix';
pos = next + 1;
}
if (pos < str.length) {
file.write(str.substring(pos));
}
} else {
file.write(str);
}
file.close();
};
Stdlib.readFromFile = function(fptr, encoding) {
var file = Stdlib.convertFptr(fptr);
file.open("r") || throwFileError("Unable to open input file ");
if (encoding) {
file.encoding = encoding;
}
var str = file.read();
file.close();
return str;
};
Stdlib.toIniString = function(obj) {
var str = '';
for (var idx in obj) {
if (idx.charAt(0) == '_') { // private stuff
continue;
}
var val = obj[idx];
if (typeof val == "string" ||
typeof val == "number" ||
typeof val == "boolean") {
str += (idx + ": " + val.toString() + "\n");
}
}
return str;
};
Stdlib.fromIniString = function(str, obj) {
if (!obj) {
obj = {};
}
var lines = str.split(/\r|\n/);
var rexp = new RegExp(/([^:]+):(.*)$/);
for (var i = 0; i < lines.length; i++) {
var line = lines.trim();
if (!line || line.charAt(0) == '#') {
continue;
}
var ar = rexp.exec(line);
if (!ar) {
alert("Bad line in config file: \"" + line + "\"");
return;
}
obj[ar[1].trim()] = ar[2].trim();
}
return obj;
};
Stdlib.readIniFile = function(fptr, obj) {
if (!obj) {
obj = {};
}
fptr = Stdlib.convertFptr(fptr);
if (!fptr.exists) {
return obj;
}
var str = Stdlib.readFromFile(fptr);
return Stdlib.fromIniString(str, obj);
};
Stdlib.writeIniFile = function(fptr, obj, header) {
var str = (header != undefined) ? header : '';
str += Stdlib.toIniString(obj);
Stdlib.writeToFile(fptr, str);
};
-X
mini-Framework for UI scripts
Here's a new rev of GenericUI: http://ps-scripts.cvs.sourceforge.net/* ... vision=1.3 ... vision=1.3". This embeds the ini file IO code. There are also some minor changes to the framework itself. I'll be posting a tutorial for this at a later date.
-X
-X
mini-Framework for UI scripts
Here's the docs for GenericUI along with GenericUI.jsx and SampleUI.jsx
Feedback is more than welcome.
Thanks to Trevor Morris for some donated code and to Mike Hale for proof-reading an earlier rev.
-X
Feedback is more than welcome.
Thanks to Trevor Morris for some donated code and to Mike Hale for proof-reading an earlier rev.
-X