mini-Framework for UI scripts

Discussion of the xtools Toolkit

Moderators: Tom, Kukurykus

xbytor

mini-Framework for UI scripts

Post by xbytor »

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
xbytor

mini-Framework for UI scripts

Post by xbytor »

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
xbytor

mini-Framework for UI scripts

Post by xbytor »

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
xbytor

mini-Framework for UI scripts

Post by xbytor »

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
xbytor

mini-Framework for UI scripts

Post by xbytor »

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