Mini Kabibi Habibi
//
// LatteUI.jsx - John Peterson
// (c) 2005-2007 Adobe Systems Inc. All Rights Reserved
//
// This file has tools to translate Eve dialog descriptions generated by
// the Latte application into ScriptUI resources.
//
/*
@@@BUILDINFO@@@ LatteUI.jsx 1.0.0.1
*/
// Debug string class optionally displays results as they're added.
function DbgString()
{
this.data = "";
this.debug = true;
}
DbgString.prototype.toString = function () { return this.data; }
DbgString.prototype.add = function ( str )
{
if (this.debug)
$.write(str);
this.data += str.replace(/\n/g, " ");
}
function tabs(num)
{
s = "";
for (i = 0; i < num; ++i)
s += "\t";
return s;
}
// Given an Eve dialog description in the string "src", return
// a ScriptUI resource description string.
function translateLatte(src, srcFilePath, debug_mode)
{
// Build translation tables xxx['Latte'] = "ScriptUI";
var nouns = new Array();
nouns['dialog'] = "dialog";
nouns['palette'] = "palette";
nouns['group'] = "Group";
nouns['frame'] = "Panel";
nouns['cluster'] = "Panel";
nouns['edit_text'] = "EditText";
nouns['static_text'] = "StaticText";
nouns['button'] = "Button";
nouns['checkbox'] = "Checkbox";
nouns['popup_menu'] = "DropDownList";
nouns['popup'] = "DropDownList";
nouns['picture_button'] = "IconButton";
nouns['image'] = "Image";
nouns['user_item'] = "Group";
nouns['listbox'] = "ListBox";
nouns['progress_bar'] = "Progressbar";
nouns['picture'] = "Image";
nouns['slider'] = "Slider";
nouns['radio'] = "RadioButton";
var aligns = new Array();
aligns['align_top'] = 'top';
aligns['align_bottom'] = 'bottom';
aligns['align_fill'] = 'fill';
aligns['align_left'] = 'left';
aligns['align_right'] = 'right';
aligns['place_row'] = 'row';
aligns['place_column'] = 'column';
var margindex = new Array();
margindex['top'] = 1;
margindex['left'] = 0;
margindex['right'] = 2;
margindex['bottom'] = 3;
var props = new Array();
props['child_horizontal'] = 'alignChildren';
props['child_vertical'] = 'alignChildren';
props['placement'] = 'orientation';
props['horizontal'] = 'alignment';
props['vertical'] = 'alignment';
props['multiline'] = 'properties: { multiline';
props['name'] = 'text';
// These are handled as special cases
props['view_id'] = 'view_id';
props['width'] = 'width';
props['height'] = 'height';
props['margin_top'] = 'margins';
props['margin_left'] = 'margins';
props['margin_right'] = 'margins';
props['margin_bottom'] = 'margins';
props['resource_id'] = 'image';
// OK, sometimes there are things script UI has that Latte can't
// express, like "creation properties". We use the item_id field
// to specify these, because item_id is available in all Latte controls
// and it's not used by ScriptUI.
props['item_id'] = 'CR_PROP_HACK';
props['text_trim'] = 'TT_PROP_HACK';
var srcnoun, noun, params, term, rest, stmt;
var view_id = 0;
var level = 0;
var resultString = new DbgString();
resultString.debug = debug_mode;
while (src)
{
// Pull off 'noun(params)' and either the ';' or the '{' following
// Known evil bug: If the parameter contains quoted parenthesis, the match fails.
// e.g.: "text: 'this is (a problem)'" <--- Parens in quotes
// RE below from Mihai Nita fixes the above limitation, but runs too slow
// stmt = src.match(/\s*(\w+)\s*\(((?:(?:\w+\s*:\s*\S+)|(?:\w+\s*:\s*'[^']+')|,|\s*)+)\)\s*([;{])\s*([\s\S]*)/);
stmt = src.match(/\s*(\w*)\(([^)]*)\)\s*([;{])\s*([\s\S]*)/)
if (stmt)
{
srcnoun = stmt[1]; // class (edit_text, group, etc.)
params = stmt[2].split(/,\s*/); // Make a list of "xyz: abc" properties
term = stmt[3]; // "{" or ";"
rest = stmt[4]; // The rest of the Eve text after stmt
noun = nouns[srcnoun];
if (! noun)
{
alert("Unknown noun: " + srcnoun, "Conversion", true);
return null;
}
// Parse the parameter list
var i, j, outputParams = "";
var sizeParam = false, width = 20, height = 20; // Default sizes
var marginVals = [0, 0, 0, 0]; // left, top, right, bottom
var marginParam = false;
var alignmentParam = false;
var alignmentVals = ['',''];
var mval;
viewname = "view" + view_id++;
for (i in params)
{
// p[0]=name,p[1]=value
// var p = params[i].split(/\s*:\s*/); // Uh, sorry, no. Param might have a ':' in it!
var colonpos = params[i].search(/\s*:\s*/);
var colonsize= params[i].match(/\s*:\s*/)[0].length;
var p = [params[i].slice(0,colonpos), params[i].slice(colonpos+colonsize)];
if (props[p[0]])
{
var left, right, quotes;
left = props[p[0]];
if (aligns[p[1]])
right = aligns[p[1]];
else
right = p[1];
// Handle special cases
switch (left)
{
case 'CR_PROP_HACK':
left = 'properties';
// Extract the properties from the surrouncing single quotes
right = p[1].match(/'([^']*)'/)[1];
// We can't put the "ok" in "name:ok" in single quotes, otherwise Latte
// gags on it. So we look for "name" as a special case, and re-wrap it. Ugh.
var nameArg = right.match(/name\s*:\s*(\S*)/);
if (nameArg)
right = "name: '" + nameArg[1] + "'";
right = '{' + right + '}';
break;
case 'TT_PROP_HACK':
left = 'properties';
// Extract the properties from the surrouncing single quotes
right = p[1].match(/'([^']*)'/)[1];
// We can't put the "ok" in "truncate:middle" in single quotes, otherwise Latte
// gags on it. So we look for "truncate" as a special case, and re-wrap it. Ugh.
var nameArg = right.match(/truncate\s*:\s*(\S*)/);
if (nameArg)
right = "truncate: '" + nameArg[1] + "'";
right = '{' + right + '}';
break;
case 'view_id': viewname = p[1].match(/'(\w*)'/)[1];
break;
case 'width': width = p[1].match(/\D*(\d*)/)[1];
sizeParam = true;
break;
case 'alignment':
alignmentParam = true;
if (p[0] == 'horizontal')
alignmentVals[0] = right;
if (p[0] == 'vertical')
alignmentVals[1] = right;
break;
case 'height': height = p[1].match(/\D*(\d*)/)[1]; // add default width
sizeParam = true;
break;
case 'image': if (noun == "Image")
{
right = p[1].match(/\s*'([^']+)'/)[1];
if ((right[0] != '/') && srcFilePath)
right = srcFilePath + '/' + right;
}
else
continue; // Ignored for menus, etc.
break;
case 'margins': // Convert margin_top/left/etc into an index into marginVals
marginVals[margindex[p[0].slice(7)]] = eval(p[1].match(/\D*(\d*)/)[1]);
marginParam = true;
break;
// Multiline needs a "properties: { ... }" wrapper
case 'properties: { multiline':
right = p[1].match(/.*(true|false)/)[1] + " }";
break;
}
// Only add quotes if it's not a number, a size, true|false,
// has properties { .. }, or already has a quote
if (right.match(/^\[.*/)
|| right.match(/^\d+$/)
|| right.match(/^'[^']*'/)
|| right.match(/^true|^false/)
|| left.match(/^properties.*/))
quotes = ""
else
quotes = "'"
// Only add to output if not a special case
if (! p[0].match(/^view_id|^width|^height|^horizontal|^vertical|^margin_\w+/))
{
if (outputParams.length > 0)
outputParams += ", ";
outputParams += left + ": " + quotes + right + quotes;
}
}
}
if ((sizeParam || marginParam || alignmentParam) && (outputParams.length > 0))
outputParams += ", ";
// If we have a size, output it here (we've captured height & width by now)
if (sizeParam)
outputParams += "size: [" + width.toString() + ", " + height.toString() + "]";
if (marginParam)
outputParams += "margins: [" + marginVals[0] + ", " + marginVals[1] + ", " + marginVals[2] + ", " + marginVals[3] + "]";
// Collect horizontal / vertical into an array pair if both are provided.
if (alignmentParam)
{
if (alignmentVals[0] == '')
outputParams += "alignment:'" + alignmentVals[1] + "'";
else
if (alignmentVals[1] == '')
outputParams += "alignment:'" + alignmentVals[0] + "'";
else
outputParams += "alignment:['" +alignmentVals[0] + "','" + alignmentVals[1] + "']";
}
var isGroup = (srcnoun == 'group'
|| srcnoun = 'cluster'
|| srcnoun == 'frame'
|| srcnoun == 'palette'
|| srcnoun == 'dialog');
// Treat empty groups like other (non-group) nouns
if (isGroup && term == ';')
isGroup = false;
resultString.add(tabs(level));
if ((noun != "dialog") && (noun != "palette"))
resultString.add( viewname + ": " );
resultString.add(noun);
if (isGroup)
{
resultString.add("\n" + tabs(level) + "{\n");
level++;
if (outputParams.length > 0)
{
resultString.add(tabs(level) + outputParams)
}
}
else
{
resultString.add("{ " + outputParams + " }")
}
if (outputParams.length > 0)
{
if (rest[0] != '}')
resultString.add(",");
resultString.add("\n");
}
while ((rest[0] == "}") && (level > 0))
{
level--;
resultString.add( tabs(level) + "}" );
// If we hit the closing "}", grab everything after it.
if (rest[0] == "}")
rest = rest.match(/\s*\}\s*([\s\S]*)/)[1];
if ((rest[0] != "}") && (level > 0))
resultString.add(",");
resultString.add("\n");
}
} // if (stmt)
// if (level < 0)
// alert("Too many close braces?","",true);
if (! stmt)
{
if (level > 0)
while(--level)
resultString.add(tabs(level) +"}\n");
resultString.add("}\n"); // level 0
src = null;
}
else
src = rest;
} // while
return resultString.toString();
}
// Traverse the window structure to find a named control.
// WARNING: Controls better not have names that collide with other
// JavaScript object parameters. F'rinstance, a name like "length"
// would be Really Bad. Maybe you want to stick underscores or something
// similarly ugly on your view names? You have been warned.
function findControlRoutine( root, name )
{
var i, c;
if (root.type == 'ListItem')
return null;
if (root[name])
return root[name];
else
if (root.children)
for (i = 0; i < root.children.length; i++)
{
c = findControlRoutine( root.children[i], name );
if (c)
return c;
}
return null;
}
function findControlMethod( name )
{
return findControlRoutine( this, name );
}
/*
// Why isn't this part of the JS Array class?
function remove(key)
{
var i;
for (i in this)
if (this[i] == key)
this.splice(i,1);
}
*/
function latteUI(filename, debug_mode)
{
var res;
// Check to see if a latte description was passed in instead of just a filename
if ((filename.match(/^palette\s*[(]|^dialog\s*[(]/)) && (!filename.match(/.+[.]exv$/)))
res = translateLatte(filename, null, debug_mode);
else
{
var f = new File(filename);
f.open('r');
res = translateLatte(f.read(), f.path, debug_mode);
f.close();
Folder.current = g_StackScriptFolderPath; // Work around bug 2505978
}
if (! res)
{
alert("Unable to read or process latte file");
return null;
}
var w = new Window(res);
// match our dialog background color to the host application
w.findControl = findControlMethod;
return w;
}
// Should really be a const, need to figure out how to make it a library
var kCanceled = 2;
//-----------------------------------------------------