// generated from config/js/traction.properties on Wed Sep 01 17:53:44 EDT 2010

//------------------------------------------------------------------------
// traction.Traction

var Traction = {};
function _get_url_param(name,def) {
  return (document.fm) ? document.fm[name].value : def;
}

//------------------------------------------------------------------------
// traction.admin.DigestState

Traction.DigestState = Class.create();
Traction.DigestState.prototype = {
  hour: -1,
  minute: -1,
  frequencyCode: -1,
  enabled: true,
  initialize: function(hour, minute, frequencyCode, enabled) {
    this.hour = hour;
    this.minute = minute;
    this.frequencyCode = frequencyCode;
    this.enabled = enabled;
  },
  getHour: function() {
    if (this.hour == 0) {
      return 12;
    }
    if (this.hour > 12) {
      return (this.hour - 12);
    }
    return this.hour;
  },
  getAMPM: function() {
    if (this.hour >= 12) {
      return "PM";
    }
    return "AM";
  },
  serialize: function(number) {
    var propprefix = (typeof(number) == "number") ? (number + "_") : "";
    var str =
      propprefix + "hours=" + this.hour + "," +
      propprefix + "minutes=" + this.minute + "," +
      propprefix + "enabled=" + (this.enabled ? "true" : "false") + "," +
      propprefix + "digestFreq=" + this.frequencyCode;
    return str;
  }
};
Traction.UserDigestState = Class.create();
Traction.UserDigestState.prototype = Object.extend(new Traction.DigestState(),{
  initialize: function(hour, minute, frequencyCode, enabled, email, styleCode) {
    this.hour = hour;
    this.minute = minute;
    this.frequencyCode = frequencyCode;
    this.enabled = enabled;
    this.email = email;
    this.styleCode = styleCode;
  },
  serialize: function(number) {
    var propprefix = (typeof(number) == "number") ? (number + "_") : "";
    var str =
      propprefix + "hours=" + this.hour + "," +
      propprefix + "minutes=" + this.minute + "," +
      propprefix + "enabled=" + (this.enabled ? "true" : "false") + "," +
      propprefix + "digestFreq=" + this.frequencyCode + "," +
      propprefix + "digestEmails=" + this.email + "," +
      propprefix + "digestHtmls=" + this.styleCode;
    return str;
  }
});

//------------------------------------------------------------------------
// traction.admin.InternetAddress

Traction.InternetAddress = Class.create();
Traction.InternetAddress.prototype = {
  ip: null,
  notes: null,
  valid: true,
  removed: false,
  initialize: function(ip, notes, valid) {
    this.ip = ip;
    this.notes = notes;
    this.valid = valid;
  },
  serialize: function(number) {
    var propprefix = (typeof(number) == "number") ? (number + "_") : "";
    var str =
      propprefix + "addr=" + this.ip + "," +
      propprefix + "notes=" + this.notes + ",";
    return str;
  }
};

//------------------------------------------------------------------------
// traction.admin.MailAliasRule

Traction.MailAliasRule = Class.create();
Traction.MailAliasRule.prototype = {
  recipient: null,
  labelProjectName: null,
  labelName: null,
  removed: false,
  initialize: function(recipient, labelProjectName, labelName) {
    this.recipient = recipient;
    this.labelProjectName = labelProjectName;
    this.labelName = labelName;
  },
  serialize: function(number) {
    var propprefix = (typeof(number) == "number") ? (number + "_") : "";
    var str =
      propprefix + "project=" + this.labelProjectName + "," +
      propprefix + "type=topic," +
      propprefix + "value=" + this.labelName + ",";
    return str;
  }
};

//------------------------------------------------------------------------
// traction.admin.MailHeaderMatcher

Traction.MailHeaderMatcher = Class.create();
Traction.MailHeaderMatcher.prototype = {
  header: null,
  match: null,
  labelProjectName: null,
  labelName: null,
  removed: false,
  initialize: function(header, match, labelProjectName, labelName) {
    this.header = header;
    this.match = match;
    this.labelProjectName = labelProjectName;
    this.labelName = labelName;
  },
  serialize: function(number) {
    var propprefix = (typeof(number) == "number") ? (number + "_") : "";
    var str =
      propprefix + "header=" + this.header + "," +
      propprefix + "match=" + this.match + "," +
      propprefix + "label.project=" + this.labelProjectName + "," +
      propprefix + "label.type=topic," +
      propprefix + "label.value=" + this.labelName + ",";
    return str;
  }
};

//------------------------------------------------------------------------
// traction.admin.Permission

Traction.Permission = {
  RECLASSIFY: 'R',
  COMMENT: 'C',
  EDIT: 'c',
  DELETE: 'X',
  RENAME: 'n',
  LOCK: 'k',
  VERSION: 'V',
  CANCEL_CHECKOUT: 'y',
  CANCEL_LOCK: 'Y',
  STEAL_LOCK: 'Z'
};

//------------------------------------------------------------------------
// traction.admin.PermissionSet

Traction.PermissionSet = Class.create();
Traction.PermissionSet.prototype = {
  
  initialize: function(permstr) {
    this.permstr = permstr;
  },
  has: function(permchar) {
    return (this.permstr != null && this.permstr.indexOf(permchar) != -1);
  }
};

//------------------------------------------------------------------------
// traction.admin.Permissions

Traction.Permissions = {
  initialize: function() {},
  
  map: new Array(),
  
  store: function(tractionId, perms) {
    var permobj = perms;
    if (typeof permobj == "string") {
      permobj = new Traction.PermissionSet(permobj);
    }
    this.map[tractionId] = permobj;
  },
  
  get: function(tractionId) {
    return this.map[tractionId];
  },
  
  has: function(tractionId, perm) {
    var perms = this.get(tractionId);
    return (perms && perms != null && perms.has(perm));
  }
};

//------------------------------------------------------------------------
// traction.admin.ProjectTemplateInfo

Traction.ProjectTemplateInfo = Class.create();
Traction.ProjectTemplateInfo.prototype = {
  name: null,
  displayName: null,
  description: null,
  hasSettings: false,
  editable: false,
  initialize: function(name, displayName, description, hasSettings, editable) {
    this.name = name;
    this.displayName = displayName;
    this.description = description;
    this.hasSettings = hasSettings;
    this.editable = editable;
  }
};

//------------------------------------------------------------------------
// traction.admin.StandardMailEntryRoutingRule

Traction.StandardMailEntryRoutingRule = Class.create();
Traction.StandardMailEntryRoutingRule.prototype = {
  header: null,
  regex: null,
  project: null,
  removed: false,
  initialize: function(header, regex, project) {
    this.header  = header;
    this.regex   = regex;
    this.project = project;
  },
  serialize: function(number) {
    var propprefix = (typeof(number) == "number") ? (number + "_") : "";
    var str =
      propprefix + "header=" + this.header + "," +
      propprefix + "regex=" + this.regex + "," +
      propprefix + "project=" + this.project;
    return str;
  }
};

//------------------------------------------------------------------------
// traction.atom.ID

Traction.ID = Class.create();
Traction.ID.parse = function(str) {
  if (str == null) {
    str = "";
  } else {
    str = str.trim();
  }
  str = decode_url_parameter(str);
  Debug.println("Traction.ID.parse( ", str, " )");
  
  var absolute = new RegExp("^([^\\s\\\"_\\?,\\.!#;:@\\$%\\^&\\*=~/\\\\\\|\\(\\)\\[\\]\\{\\}\\'`<>\\+\\-]*[^\\s\\\"_\\?,\\.!#;:@\\$%\\^&\\*=~/\\\\\\|\\(\\)\\[\\]\\{\\}\\'`<>\\+\\-0-9])(\\d+)((\\.|@)(\\d+)|(\\$)(.+))?$");
  
  var relative = new RegExp("^(@(\\d+)|\\$(.+))$");
  var groups = relative.exec(str);
  if (groups != null) {
    if (typeof(groups[2]) != "undefined") {
      return new Traction.ID("", Traction.ID.CURRENT_ENTRY, "@", groups[2]);
    } else { // typeof(groups[3]) != "undefined"
      return new Traction.ID("", Traction.ID.CURRENT_ENTRY, "$", groups[3]);
    }
  } else {
    groups = absolute.exec(str);
    if (groups != null) {
      if (typeof(groups[3]) != "undefined") {
	if (typeof(groups[4]) != "undefined") {
	  return new Traction.ID(groups[1], groups[2], groups[4], groups[5]);
	} else { // typeof(groups[6]) != "undefined"
	  return new Traction.ID(groups[1], groups[2], groups[6], groups[7]);
	}
      } else {
	return new Traction.ID(groups[1], groups[2]);
      }
    } else {
      return null;
    }
  }
};
Traction.ID.prototype = {
  initialize: function(project, entry, separator, id) {
    this.project = project;
    this.entry = entry;
    this.id = (typeof(id) == "undefined") ? Traction.ID.NO_ITEM : id;
    this.separator = (typeof(separator) == "undefined") ? ((this.id == Traction.ID.NO_ITEM) ? "" : ".") : separator;
  },
  debug: function() {
    Debug.println("project="+this.project+" entry="+this.entry+" id="+this.id+" sep="+this.separator + " str="+this.toString());
  },
  toString: function() {
    return this.project + ((this.entry != Traction.ID.CURRENT_ENTRY) ? this.entry : "") + this.separator + ((this.id != Traction.ID.NO_ITEM) ? this.id : "");
  },
  removeItem: function() {
    return new Traction.ID(this.project, this.entry);
  }
};
Traction.ID.CURRENT_ENTRY = -1;
Traction.ID.NO_ITEM       = -1;
Traction.ID.test = function() {
  Traction.ID.parse("Woohoo43").debug();
  Traction.ID.parse("Woohoo43.00").debug();
  Traction.ID.parse("Woohoo43.03").debug();
  Traction.ID.parse("Woohoo43$important").debug();
  Traction.ID.parse("Woohoo43$7").debug();
  Traction.ID.parse("Woohoo43@2").debug();
  Traction.ID.parse("notatractionid").debug();
};

//------------------------------------------------------------------------
// traction.String

if (!String.prototype.endsWith) {
  String.prototype.endsWith = function(str) {
    return this.length >= str.length && this.substring(this.length - str.length) == str;
  };
}
if (!String.prototype.startsWith) {
  String.prototype.startsWith = function(str) {
    return this.length >= str.length && this.substring(0,str.length) == str;
  };
}
if (!String.prototype.trim) {
  String.prototype.trim = function() { // taken from shared.js
    return new String(this).replace(/^\s+/,'').replace(/\s+$/,'');
  };
}
if (!String.prototype.ltrim) {
  String.prototype.ltrim = function() { // taken from shared.js
    return new String(this).replace(/^\s+/,'');
  };
}
if (!String.prototype.rtrim) {
  String.prototype.rtrim = function() { // taken from shared.js
    return new String(this).replace(/\s+$/,'');
  };
}
if (!String.prototype.caseInsensitiveCompare) {
  String.prototype.caseInsensitiveCompare = function(other) {
    var a = this.toLowerCase();
    var b = (other != null) ? other.toLowerCase() : null;
    return (al == bl) ? 0 : (al < bl) ? -1 : 1;
  };
}
if (!String.prototype.removeURLParameter) {
  
  String.prototype.removeURLParameter = function(paramname) {
    var param_assignment = new RegExp("&" + paramname + "=[^&]*");
    var newurl = new String(this);
    while (newurl.match(param_assignment)) {
      newurl = this.replace(param_assignment, "");
    }
    return newurl;
  };
}
if (!String.prototype.cleanrs) {
  
  String.prototype.cleanrs = function() {
    var re = /\s*\[*(.*)/;
    return this.replace(re, "$1");
  };
}
if (!String.prototype.countChars) {
  
  String.prototype.countChars = function(c) {
    var start = -1;
    var ret = 0;
    while ((start = this.indexOf(c,start+c.length)) != -1) {
      ret++;
    }
    return ret;
  };
}
if (!String.prototype.unescape_fromattribute) {
  String.prototype.unescape_fromattribute = function() {
    var dq = new RegExp( "&quot;", "g" );
    var str = this.replace(dq, "\"");
    var lt = new RegExp( "&lt;", "g" );
    str = str.replace(lt, "\<");
    var gt = new RegExp( "&gt;", "g" );
    str = str.replace(gt, "\>");
    return str;
  };
}
if (!String.prototype.escape_commas) {
  
  String.prototype.escape_commas = function() {
    var bs = new RegExp( "\\\\", "g" );
    var str = this.replace(bs, "\\\\");
    var cm = new RegExp( ",", "g" );
    str = str.replace(cm, "\\,");
    return str;
  };
}
if (!String.prototype.escape_forattribute) {
  
  String.prototype.escape_forattribute = function() {
    var dq = new RegExp( "\"", "g" );
    var str = this.replace(dq, "&quot;");
    var lt = new RegExp( "\<", "g" );
    str = str.replace(lt, "&lt;");
    var gt = new RegExp( "\>", "g" );
    str = str.replace(gt, "&gt;");
    return str;
  };
}
if (!String.prototype.escape_char) {
  String.prototype.escape_char = function(c) {
    var bs = new RegExp( "\\\\", "g" );
    var str = this.replace(bs, "\\\\"); // escape escapes
    var cm = new RegExp( c, "g" );
    str = str.replace(cm, "\\"+c); // escape char
    return str;
  };
}
if (!String.prototype.unescape_char) {
  String.prototype.unescape_char = function(c) {
    var cm = new RegExp( "\\\\"+c, "g" );
    var str = this.replace(cm, c); // unescape char
    var bs = new RegExp( "\\\\\\\\", "g" );
    str = str.replace(bs, "\\"); // unescape escapes
    return str;  
  };
}
if (!String.prototype.escape_singlequotes) {
  
  String.prototype.escape_singlequotes = function() {
    return this.escape_char("'");
  };
}
if (!String.prototype.unescape_singlequotes) {
  String.prototype.unescape_singlequotes = function() {
    return this.unescape_char("'");
  };
}
if (!String.prototype.encode_url) {
  String.prototype.encode_url = function() {
    try {
      return encodeURI(this); // not IE 5
    } catch (xcp) {
      return escape(this);
    }
  };
}
if (!String.prototype.decode_url) {
  String.prototype.decode_url = function() {
    try {
      return decodeURI(this); // not IE 5
    } catch (xcp) {
      return unescape(this);
    }
  };
}
if (!String.prototype.encode_url_parameter) {
  String.prototype.encode_url_parameter = function() {
    try {
      return encodeURIComponent(this); // not IE 5
    } catch (xcp) {
      return escape(this);
    }
  };
}
if (!String.prototype.decode_url_parameter) {
  String.prototype.decode_url_parameter = function() {
    try {
      return decodeURIComponent(this); // not IE 5
    } catch (xcp) {
      return unescape(this);
    }
  };
}
Traction.StringUtil = {
  
  replaceClassName: function(element, oldclass, newclass) {
    var classname = (element != null) ? element.className : null;
    if (classname == null) {
      return;
    }
    var newclassname = " " + classname + " ";
    var old = new RegExp( "\\s+" + oldclass + "\\s+", "g" );
    newclassname = newclassname.replace(old, " " + newclass + " ").trim();
    element.className = newclassname;
  },
  nopx: function(size) {
    return parseInt(size.replace("px", ""));
  },
  px: function(size) {
    return size + "px";
  }
};

//------------------------------------------------------------------------
// traction.util.Rect

var Rect = Class.create();
Rect.copy = function(rect) {
  return new Rect(rect.x, rect.y, rect.w, rect.h);
};
Rect.prototype = {
  initialize: function(x,y,w,h) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
  },
  isPointInside: function(x,y) {
    return (this.x <= x && x <= this.x + this.w &&
	    this.y <= y && y <= this.y + this.h);
  },
  isMouseInside: function(event) {
    var pt = Util.getMousePosition(event);
    return this.isPointInside(pt.x, pt.y);
  },
  equals: function(other) {
    return (this.x == other.x &&
	    this.y == other.y &&
	    this.w == other.w &&
	    this.h == other.h);
  },
  x2: function() { return this.x + this.w; },
  y2: function() { return this.y + this.h; },
  print: function() { return "("+this.x+","+this.y+")["+this.w+","+this.h+"]"; }
};

//------------------------------------------------------------------------
// traction.util.Util

var Util = {};

Util.getMousePosition = function(event) {
  if (event == null) return { x:-1, y:-1 };
  if (event.pageX || event.pageY) { // safari, firefox
    return {
      x: event.pageX,
      y: event.pageY
    };
  }
  else if (document.documentElement && (document.documentElement.scrollLeft || document.documentElement.scrollTop)) { // ie with doctype
    return {
      x: event.clientX + document.documentElement.scrollLeft,
      y: event.clientY + document.documentElement.scrollTop
    };
  }
  else { // ie no doctype
    return {
      x: event.clientX + document.body.scrollLeft,
      y: event.clientY + document.body.scrollTop
    };    
  }
};

Util.getElementBounds = function(elm) {
  var forTopAndHeight = elm;
  if (elm != null && elm.tagName == "TR") {
    forTopAndHeight = elm.firstChild;
    while (forTopAndHeight.tagName != "TD") {
      forTopAndHeight = forTopAndHeight.nextSibling;
    }
  }
  return new Rect(getleft(elm),
		  gettop(forTopAndHeight),
		  outer_width(elm),
		  outer_height(forTopAndHeight));
};

Util.getViewportBounds = function() {
  if (window.innerWidth) { // safari, firefox
    var iw = window.innerWidth;
    var ih = window.innerHeight;
    if (window.scrollMaxX) ih -= 20;
    if (window.scrollMaxY) iw -= 20;
      return new Rect(window.pageXOffset,
		      window.pageYOffset,
		      iw,
		      ih);
  }
  else if (document.documentElement && document.documentElement.clientWidth) { // ie with doctype
    return new Rect(document.documentElement.scrollLeft,
		    document.documentElement.scrollTop,
		    document.documentElement.clientWidth,
		    document.documentElement.clientHeight);
  }
  else { // ie no doctype
    return new Rect(document.body.scrollLeft,
		    document.body.scrollTop,
		    document.body.clientWidth,
		    document.body.clientHeight);
  }
};
Util.getDocumentBounds = function() {
  if (document.documentElement && document.documentElement.clientWidth) { // ie with doctype
    return new Rect(0,0,
		    document.documentElement.scrollWidth,
		    document.documentElement.scrollHeight);
  }
  else { // ie no doctype
    return new Rect(0,0,
		    document.body.scrollWidth,
		    document.body.scrollHeight);
  }
};

Util.cvtToRelativeCoords = function(elm,pt) {
  var bounds = Util.getElementBounds(elm);
  var left = nopx(elm.style.left);
  var top = nopx(elm.style.top);
  return { x: pt.x + (left - bounds.x), y: pt.y + (top - bounds.y) };
};
Util.keepInBounds = function(rect, bounds) {
  var ret = Rect.copy(rect);
  if (rect.x2() > bounds.x2()) {
    ret.x -= (rect.x2() - bounds.x2());
  }
  if (rect.y2() > bounds.y2()) {
    ret.y -= (rect.y2() - bounds.y2());
  }
  return ret;
};

Util.getServerRelativeURL = function(url) {
  var ret = url;
  var slashSlash = url.indexOf("//");
  if (slashSlash != -1) {
    var slash = url.indexOf("/", slashSlash+2);
    if (slash != -1) {
      ret = url.substring(slash);
    }
  }
  return ret;
};

Util.hasStylesheet = function(stylesheet) {
  stylesheet = Util.getServerRelativeURL(stylesheet);
  var s = document.styleSheets;
  for (var i=0; i<s.length; i++) {
    if (s[i].href != null && Util.getServerRelativeURL(s[i].href).indexOf(stylesheet) != -1) {
      return true;
    }
  }
  return false;  
};
Util.isVisible = function(elm) {
  if (!elm) return false;
  while (elm) {
    if (elm.style && elm.style.visibility != "hidden") {
      return false;
    }
    elm = elm.parentNode;
  }
  return true;
};
Util.isDisplayed = function(elm) {
  if (!elm) return false;
  while (elm) {
    if (elm.style && elm.style.display == "none") {
      return false;
    }
    elm = elm.parentNode;
  }
  return true;
};
Util.nopx = function(size) {
  if (typeof(size) == "number") {
    return size;
  }
  return parseInt(size.replace("px",""));
};
Util.px = function(size) {
  return size+"px";
};

Util.ColorParser = function(color_string) {
    this.ok = false;
    if (color_string.charAt(0) == '#') { // remove # if any
        color_string = color_string.substr(1,6);
    }
    color_string = color_string.replace(/ /g,'');
    color_string = color_string.toLowerCase();
    var simple_colors = {
        aliceblue: 'f0f8ff',
        antiquewhite: 'faebd7',
        aqua: '00ffff',
        aquamarine: '7fffd4',
        azure: 'f0ffff',
        beige: 'f5f5dc',
        bisque: 'ffe4c4',
        black: '000000',
        blanchedalmond: 'ffebcd',
        blue: '0000ff',
        blueviolet: '8a2be2',
        brown: 'a52a2a',
        burlywood: 'deb887',
        cadetblue: '5f9ea0',
        chartreuse: '7fff00',
        chocolate: 'd2691e',
        coral: 'ff7f50',
        cornflowerblue: '6495ed',
        cornsilk: 'fff8dc',
        crimson: 'dc143c',
        cyan: '00ffff',
        darkblue: '00008b',
        darkcyan: '008b8b',
        darkgoldenrod: 'b8860b',
        darkgray: 'a9a9a9',
        darkgreen: '006400',
        darkkhaki: 'bdb76b',
        darkmagenta: '8b008b',
        darkolivegreen: '556b2f',
        darkorange: 'ff8c00',
        darkorchid: '9932cc',
        darkred: '8b0000',
        darksalmon: 'e9967a',
        darkseagreen: '8fbc8f',
        darkslateblue: '483d8b',
        darkslategray: '2f4f4f',
        darkturquoise: '00ced1',
        darkviolet: '9400d3',
        deeppink: 'ff1493',
        deepskyblue: '00bfff',
        dimgray: '696969',
        dodgerblue: '1e90ff',
        feldspar: 'd19275',
        firebrick: 'b22222',
        floralwhite: 'fffaf0',
        forestgreen: '228b22',
        fuchsia: 'ff00ff',
        gainsboro: 'dcdcdc',
        ghostwhite: 'f8f8ff',
        gold: 'ffd700',
        goldenrod: 'daa520',
        gray: '808080',
        green: '008000',
        greenyellow: 'adff2f',
        honeydew: 'f0fff0',
        hotpink: 'ff69b4',
        indianred : 'cd5c5c',
        indigo : '4b0082',
        ivory: 'fffff0',
        khaki: 'f0e68c',
        lavender: 'e6e6fa',
        lavenderblush: 'fff0f5',
        lawngreen: '7cfc00',
        lemonchiffon: 'fffacd',
        lightblue: 'add8e6',
        lightcoral: 'f08080',
        lightcyan: 'e0ffff',
        lightgoldenrodyellow: 'fafad2',
        lightgrey: 'd3d3d3',
        lightgreen: '90ee90',
        lightpink: 'ffb6c1',
        lightsalmon: 'ffa07a',
        lightseagreen: '20b2aa',
        lightskyblue: '87cefa',
        lightslateblue: '8470ff',
        lightslategray: '778899',
        lightsteelblue: 'b0c4de',
        lightyellow: 'ffffe0',
        lime: '00ff00',
        limegreen: '32cd32',
        linen: 'faf0e6',
        magenta: 'ff00ff',
        maroon: '800000',
        mediumaquamarine: '66cdaa',
        mediumblue: '0000cd',
        mediumorchid: 'ba55d3',
        mediumpurple: '9370d8',
        mediumseagreen: '3cb371',
        mediumslateblue: '7b68ee',
        mediumspringgreen: '00fa9a',
        mediumturquoise: '48d1cc',
        mediumvioletred: 'c71585',
        midnightblue: '191970',
        mintcream: 'f5fffa',
        mistyrose: 'ffe4e1',
        moccasin: 'ffe4b5',
        navajowhite: 'ffdead',
        navy: '000080',
        oldlace: 'fdf5e6',
        olive: '808000',
        olivedrab: '6b8e23',
        orange: 'ffa500',
        orangered: 'ff4500',
        orchid: 'da70d6',
        palegoldenrod: 'eee8aa',
        palegreen: '98fb98',
        paleturquoise: 'afeeee',
        palevioletred: 'd87093',
        papayawhip: 'ffefd5',
        peachpuff: 'ffdab9',
        peru: 'cd853f',
        pink: 'ffc0cb',
        plum: 'dda0dd',
        powderblue: 'b0e0e6',
        purple: '800080',
        red: 'ff0000',
        rosybrown: 'bc8f8f',
        royalblue: '4169e1',
        saddlebrown: '8b4513',
        salmon: 'fa8072',
        sandybrown: 'f4a460',
        seagreen: '2e8b57',
        seashell: 'fff5ee',
        sienna: 'a0522d',
        silver: 'c0c0c0',
        skyblue: '87ceeb',
        slateblue: '6a5acd',
        slategray: '708090',
        snow: 'fffafa',
        springgreen: '00ff7f',
        steelblue: '4682b4',
        tan: 'd2b48c',
        teal: '008080',
        thistle: 'd8bfd8',
        tomato: 'ff6347',
        turquoise: '40e0d0',
        violet: 'ee82ee',
        violetred: 'd02090',
        wheat: 'f5deb3',
        white: 'ffffff',
        whitesmoke: 'f5f5f5',
        yellow: 'ffff00',
        yellowgreen: '9acd32'
    };
    for (var key in simple_colors) {
        if (color_string == key) {
            color_string = simple_colors[key];
        }
    }
    var color_defs = [
        {
            re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,
            example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],
            process: function (bits){
                return [
                    parseInt(bits[1]),
                    parseInt(bits[2]),
                    parseInt(bits[3])
                ];
            }
        },
        {
            re: /^(\w{2})(\w{2})(\w{2})$/,
            example: ['#00ff00', '336699'],
            process: function (bits){
                return [
                    parseInt(bits[1], 16),
                    parseInt(bits[2], 16),
                    parseInt(bits[3], 16)
                ];
            }
        },
        {
            re: /^(\w{1})(\w{1})(\w{1})$/,
            example: ['#fb0', 'f0f'],
            process: function (bits){
                return [
                    parseInt(bits[1] + bits[1], 16),
                    parseInt(bits[2] + bits[2], 16),
                    parseInt(bits[3] + bits[3], 16)
                ];
            }
        }
    ];
    for (var i = 0; i < color_defs.length; i++) {
        var re = color_defs[i].re;
        var processor = color_defs[i].process;
        var bits = re.exec(color_string);
        if (bits) {
            channels = processor(bits);
            this.r = channels[0];
            this.g = channels[1];
            this.b = channels[2];
            this.ok = true;
        }
    }
    this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r);
    this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g);
    this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b);
    this.toRGB = function () {
        return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')';
    }
    this.toHex = function () {
        var r = this.r.toString(16);
        var g = this.g.toString(16);
        var b = this.b.toString(16);
        if (r.length == 1) r = '0' + r;
        if (g.length == 1) g = '0' + g;
        if (b.length == 1) b = '0' + b;
        return '#' + r + g + b;
    }
    this.getHelpXML = function () {
        var examples = new Array();
        for (var i = 0; i < color_defs.length; i++) {
            var example = color_defs[i].example;
            for (var j = 0; j < example.length; j++) {
                examples[examples.length] = example[j];
            }
        }
        for (var sc in simple_colors) {
            examples[examples.length] = sc;
        }
        var xml = document.createElement('ul');
        xml.setAttribute('id', 'rgbcolor-examples');
        for (var i = 0; i < examples.length; i++) {
            try {
                var list_item = document.createElement('li');
                var list_color = new Util.ColorParser(examples[i]);
                var example_div = document.createElement('div');
                example_div.style.cssText =
                        'margin: 3px; '
                        + 'border: 1px solid black; '
                        + 'background:' + list_color.toHex() + '; '
                        + 'color:' + list_color.toHex()
                ;
                example_div.appendChild(document.createTextNode('test'));
                var list_item_value = document.createTextNode(
                    ' ' + examples[i] + ' -> ' + list_color.toRGB() + ' -> ' + list_color.toHex()
                );
                list_item.appendChild(example_div);
                list_item.appendChild(list_item_value);
                xml.appendChild(list_item);
            } catch(e){}
        }
        return xml;
    }
};
Util.getRGBFromString = function(color_string) {
  var color = new Util.ColorParser(color_string);
  if (color.ok) { // 'ok' is true when the parsing was a success
    return [ color.r, color.g, color.b ];
  }
  else {
    return null;
  }
};
Util.EditApps = { 
  Word: { 
    name: "Word.Application",
    failKey: "cfi_direct_open_failure_message_msword",
    failText: "Could not open Microsoft Word.  Microsoft Word may not be installed on your system, or you may need to change your Internet Explorer security settings to allow execution of ActiveX controls.",
    open: function(obj, url) {
      obj.Visible = true;
      obj.Documents.Open(url);
    }
  },
  Excel: { 
    name: "Excel.Application",
    failKey: "cfi_direct_open_failure_message_msexcel",
    failText: "Could not open Microsoft Excel.  Microsoft Excel may not be installed on your system, or you may need to change your Internet Explorer security settings to allow execution of ActiveX controls.",
    open: function(obj, url) {
      obj.Visible = true;
      obj.Workbooks.Open(url);
    }
  },
  Powerpoint: { 
    name: "PowerPoint.Application",
    failKey: "cfi_direct_open_failure_message_mspowerpoint",
    failText: "Could not open Microsoft PowerPoint.  Microsoft PowerPoint may not be installed on your system, or you may need to change your Internet Explorer security settings to allow execution of ActiveX controls.",
    open: function(obj, url) {
      obj.Visible = true;
      obj.Presentations.Open(url);
    }
  }
};

Util.editDocument = function(app, url) {
  var obj, error = false;
  try {
    obj = new ActiveXObject(app.name);
    if (obj != null) {
      app.open(obj, url);
    } else {
      error = true;
    }
  }
  catch (e) {
    error = true;
  }
  if (error) {
    if (confirm(i18n(app.failKey, app.failText))) {
      showClientSecuritySettingsHelp();
    }
  }
};

//------------------------------------------------------------------------
// traction.util.Cookie

 
Util.Cookie = Class.create();
Util.Cookie.getCookie = function(name, isSafelyEncoded) { // static factory method
  var nameeq = name+'=';
  var cookies = document.cookie.split(';');
  for (var i=0; i<cookies.length; i++) {
    var cur = cookies[i].trim();
    if (cur.startsWith(nameeq)) {
      var value = cur.substring(nameeq.length);
      if (!isSafelyEncoded) value = decode_url_parameter(value);
      return new Util.Cookie(name, value, isSafelyEncoded);
    }
  }
  return null;
}
Util.Cookie.prototype = { // instance methods
  name: null,
  value: null,
  isSafelyEncoded: null,
  initialize: function(name, value, isSafelyEncoded) {
    this.name = name;
    this.value = value;
    this.isSafelyEncoded = isSafelyEncoded;
  },
  savePermanent: function() {
    this.save(5*365*24*60*60); // 5 years
  },
  saveSession: function() {
    this.save(0);
  },
  save: function(seconds) {
    var expires = "";
    if (seconds != 0) {
      var date = new Date();
      date.setTime(date.getTime()+(seconds*1000));
      expires = "; expires="+date.toGMTString();
    }
    var value = this.value;
    if (!this.isSafelyEncoded) value = encode_url_parameter(value);
    document.cookie = this.name+"="+value+expires+"; path=/";
  },
  expire: function() {
    this.save(-1);
  }
};

//------------------------------------------------------------------------
// traction.util.State

var State = {};
State.COOKIE_NAME = "state";
State.data_ = null;
State.LABLE_DELIM = "ld";
State.load = function() {
  var cookie = Util.Cookie.getCookie(State.COOKIE_NAME, true);
  if (cookie != null) {
    State.decode(cookie.value);
  } else {
    State.data_ = new Array();
  }
};
State.save = function() {
  var cookie = new Util.Cookie(State.COOKIE_NAME, State.encode(), true);
  cookie.savePermanent();  
};
State.safe = function(str) {
  return str.encode_url_parameter().escape_singlequotes(); 
};
State.unsafe = function(str) { 
  return str.unescape_singlequotes().decode_url_parameter(); 
};
State.decode = function(val) {
  var data;
  if (val) {
    try {
      val = "("+val+")";
      data = eval(val);
      for (var key in data) {
	if (key != "extend") {
	  data[key] = State.unsafe(data[key]);
	}
      }
    } 
    catch (e) {
    }
    if (!data || data == null) {
      data = new Array();
    }
  } else {
    data = new Array();
  }
  State.data_ = data;
};
State.encode = function() {
  var data = State.data();
  var str = "";
  if (data != null) {
    for (var key in data) {
      if (key != "extend") {
	var val = data[key];
	if (val != null && val != "") {
	  if (str) {
	    str += ",";
	  } else {
	    str = "{";
	  }
	  str += key + ":'" + State.safe(val) + "'";
	}
      }
    }
  }
  if (str) {
    str += "}";
  }
  return str;
};
State.get = function(key,def) {
  var val = State.data()[key];
  if (!val || val == null) {
    val = def;
  }
  return val;
};
State.put = function(key, val) {
  State.data()[key] = val;
};
State.putAndSave = function(key, val) {
  State.put(key,val);
  State.save();
};
State.data = function() {
  if (State.data_ == null) {
    State.load();
  }
  return State.data_;
};

//------------------------------------------------------------------------
// traction.atom.Label

Traction.Label = Class.create();
Traction.Label.delim = State.get(State.LABLE_DELIM, ":");
Traction.Label.setDelimeter = function(delim) {
  if (Traction.Label.delim != delim) {
    Traction.Label.delim = delim;
    State.putAndSave(State.LABLE_DELIM,delim);
  }
};

Traction.Label.parse = function(str, contextProj) {
  var proj = null;
  var label = null;
  str = str.replace(/([^\\])\"/g,"$1");
  str = str.replace(/\\\"/g,"\"");
  var cc = str.indexOf('::');
  var c;
  if (cc != -1) {
    c = str.indexOf(':',cc+2);
    if (c != -1) {
      proj = str.substring(cc+2,c);
    } else {
      proj = str.substring(cc+2);
      return new Traction.Label(proj, null, -1, null);  // <<<<<<<<<<<<<<<<<<<<
    }
  }
  else {
    c = str.indexOf(':');
  }
  if (c != -1) {
    label = str.substring(c+1);
  } else {
    label = str;
  }
  if (proj == null && typeof(contextProj) != "undefined" && contextProj != null) {
    proj = contextProj;
  }
  if (proj != null) {
    var projColon = proj + ":";
    if (label.indexOf(projColon) == 0) {
      label = label.substring(projColon.length);
    }
  }
  var ret = new Traction.Label(proj, label, -1, null);
  if (str.trim().startsWith("+")) {
    ret.isNew = true;
  }
  return ret;  // <<<<<<<<<<<<<<<<<<<<
};
Traction.Label.splitQuoted = function(str) {
  var parts = str.split(' ');
  var newparts = new Array();
  var curpart = "";
  var isComplete = true;
  for (var i=0; i<parts.length; i++) {
    var cur = parts[i];
    var quoteCount = 0;
    var quoteIndex = -1;
    while ((quoteIndex = cur.indexOf('"',quoteIndex+1)) != -1) {
      if (quoteIndex == 0 || cur.charAt(quoteIndex-1) != '\\') {
	quoteCount++;
      }
    }
    if (quoteCount % 2 != 0) {
      isComplete = !isComplete;
    }
    if (curpart) {
      curpart += " ";
    }
    curpart += cur;
    if (isComplete) {
      newparts.push(curpart);
      curpart = "";
    }
  }
  if (!isComplete) {
    newparts.push(curpart);
  }
  return newparts;
};

Traction.Label.parselist = function(str, contextProj) {
  var parts = Traction.Label.splitQuoted(str);
  var labels = new Array();
  for (var i=0; i<parts.length; i++) {
    if (parts[i].trim()) {
      var cur = Traction.Label.parse(parts[i], contextProj);
      if (cur != null) {
	labels.push(cur);
      }
    }
  }
  return labels;
};

Traction.Label.fromArray = function(arr) {
  return new Traction.Label(arr.p, arr.ln, arr.id, arr.i);
};

Traction.Label.createNewLabel = function(project, label) {
  var label = new Traction.Label(project, label, -1, null);
  label.isNew = true;
  return label;
};
Traction.Label.arrayToRS = function(labels) {
  var str = "";
  if (labels && labels != null) {
    for (var i=0; i<labels.length; i++) {
      if (str) str += ' ';
      str += labels[i].getRapidSelectorFormat();
    }
  }
  return str;
};
Traction.Label.prototype = {
  
  isNew: false,
  initialize: function(project, label, id, icon) {
    this.project = project;
    this.label = label;
    this.id = id;
    this.icon = icon;
  },
  getRapidSelectorFormat: function() {
    var rs = this.label;
    if (rs.indexOf(' ') != -1) {
      rs = '"' + rs + '"';
    }
    rs = ":"+rs;
    if (this.project != null && this.project.trim() != "") {
      rs = "::"+this.project+rs;
    }
    if (this.isNew) {
      rs = "+" + rs;
    }
    return rs;
  },
  
  getDisplayName: function(project) {
    if (this.project && this.project != null) {
      var colon = Traction.Label.delim;
      if (project && project != null) {
	if (project.toLowerCase() == this.project.toLowerCase()) {
	  return this.label;
	} else {
	  return colon + this.project + colon + this.label;
	}
      } else {
	return colon + this.project + colon + this.label;
      }
    }
    else {
      return this.label;
    }
  },
  toString: function() {
    return "[Label "+this.getRapidSelectorFormat()+"]";
  },
  dump: function() {
    return "{p:"+this.project+", ln:"+this.label+", id:"+this.id+", i:"+this.icon+"}";
  }
};
Traction.Label.debug = function() {
  Traction.Label.parselist("::woohoo:news ::woohoo:bug:open ::woohoo:\"hey there\" \"::woohoo:hey there\" \"::woohoo:hey there \\\"dude\\\"\" ::woohoo :news news");
  Traction.Label.parselist("news");
  Traction.Label.parselist("::woohoo:news :news\"");
  Traction.Label.parselist("::woohoo:news :news\\\"");
};

//------------------------------------------------------------------------
// traction.contextmenu.items.AbstractItem

Traction.AbstractItem = function() {};
Traction.AbstractItem.prototype = {
  insert: function(submenu,styler) {
    this._insert(submenu,styler);
  },
  _insert: function(submenu,styler) {
    this.styler = styler;
    this.TR = document.createElement("TR");
    this.TD = document.createElement("TD");
    this.A = document.createElement("DIV");
    if (this.icon != null) {
      this.IMG = document.createElement("IMG");
      this.TD.appendChild(this.IMG);
    }
    this.TD.appendChild(this.A);
    this.TR.appendChild(this.TD);
    submenu.getTBODY().appendChild(this.TR);
    this.style_setup();
    this.TR.onmouseover = this.hover.bindAsEventListener(this);
    this.TR.onmouseout = this.unhover.bindAsEventListener(this);
    this.TD.onclick = this.execute.bindAsEventListener(this);
    if (submenu.options.parentMenu == null) {
      this.TD.style.whiteSpace = "nowrap";
    }
    Events.kill(this.TR, Events.ContextMenu);
  },
  style_setup: function() {
    this.styler.style_abstract_setup(this);
  },
  style_on: function() {
    this.styler.style_abstract_on(this);
  },
  style_off: function() {
    this.styler.style_abstract_off(this);
  },
  hover: function(event) {
    this.style_on();
  },
  unhover: function(event) {
    this.style_off();
  },
  execute: function(event) {
    alert("AbstractItem.execute");
  },
  hideSubMenuNow: function() {
    if (this.menu != null) this.menu.hideNow();
  },
  remove: function() {
    this.TR.style.display = "none";
  }
};

//------------------------------------------------------------------------
// traction.contextmenu.items.LabelItem

Traction.LabelItem = Class.create();
Traction.LabelItem.prototype = Object.extend(new Traction.AbstractItem(), {
  initialize: function(label) {
      this.label = label;
      this.icon = null;
  },
  style_setup: function() {
      this.styler.style_label_setup(this);
  },
  style_on: function() {
      this.styler.style_label_on(this);
  },
  style_off: function() {
      this.styler.style_label_off(this);
  },
  execute: function(event) {}
});

//------------------------------------------------------------------------
// traction.contextmenu.styles.PlainMenuStyle

Traction.PlainMenuStyle = Class.create();
Traction.PlainMenuStyle.prototype = {
  initialize: function(options) {
    this.setOptions(options);
  },
  setOptions: function(options) {
    this.options = Object.extend({
      scroller_border_color: "#666",
      scroller_hover_color: "#333",
      scroller_background_color: "#fff",
      scroller_height: 14,
      menu_foreground_color: "#000",
      menu_border_color: "#666",
      menu_drop_shadow_1_color: "#ddd",
      menu_drop_shadow_2_color: "#aaa",
      menu_font_family: "Tahoma, Verdana, Arial, Helvetica, sans-serif;",
      menu_font_size: "8pt",
      menu_background_color: "#fff",
      menu_opacity: 100, 
      item_hover_foreground_color: "#000",
      item_hover_background_color: "#aaa",
      item_hover_border_color: "#666",
      label_background_color: "#eee",
      submenu_image_src: "/images/submenu.gif"
	  }, options || {});
  },
  style_setup: function(item) {
    with (item.TD) {
      style.backgroundColor = "transparent";
      style.cursor = ua("css_cursor_property_pointer_value", "pointer");;
    }
    var icon = item.icon;
    var label = item.label
    if (item.icon != null) {
      with (item.IMG) {
	src = icon;
	border = "";
	style["float"] = "left";
	style.position = "absolute";
	style.left = "5px";
	style.paddingTop = "3px";
      }
    }
    with (item.A) {
      innerHTML = label;
      style.padding = "4px 15px 4px 29px";
      style.display = "block";
      style.color = this.options.menu_foreground_color;
      style.fontFamily = this.options.menu_font_family;
      style.fontSize = this.options.menu_font_size;
      style.lineHeight = "14px";      
    }
  },
  
  style_abstract_setup: function(item) {
    this.style_setup(item);
      with (item.A) {
	style.backgroundColor = "transparent";
	style.paddingLeft = "5px";
      }    
  },
  style_abstract_on: function(item) {
    with (item.A) {
      style.color = this.options.item_hover_foreground_color;
      style.backgroundColor = this.options.item_hover_background_color;
    }
  },
  style_abstract_off: function(item) {
    with (item.A) {
      style.color = this.options.menu_foreground_color;
      style.backgroundColor = "transparent";
    }
  },
  
  style_label_setup: function(item) {
    this.style_setup(item);
    with (item.TD) {
      style.backgroundColor = this.options.label_background_color;
      style.cursor = "default";
    }
  },
  style_label_on: function(item) {
    with (item.A) {
      style.textDecoration = "none";
    }
  },
  style_label_off: function(item) {},
  
  style_action_setup: function(item) {
    this.style_setup(item);
  },
  style_action_on: function(item) {
    this.style_abstract_on(item);
  },
  style_action_off: function(item) {
    this.style_abstract_off(item);
  },  
  
  style_subitem_setup: function(item) {
    this.style_setup(item);
    item.img = document.createElement("IMG");
    with (item.img) {
      src = this.options.submenu_image_src;
      border = "";
      style["float"] = "right";
      style.position = "absolute";
      style.right = "5px";
	style.padding = "2px 3px 0px 0px";
    }    
    item.A.appendChild(item.img);
  },
  style_subitem_on: function(item) {
    this.style_abstract_on(item);
  },
  style_subitem_off: function(item) {
    this.style_abstract_off(item);
  },
  
  style_ajaxitem_setup: function(item) {
    item.progress = document.createElement("IMG");
    with (item.progress) {
      src = "/images/modern/px.gif";
      style.width = "16px";
      style.height = "16px";
      style["float"] = "right";
      style.position = "absolute";
      style.right = "13px";
      style.padding = "0";
      style.marginTop = "-1px";
      style.border = "none";
    }
    this.style_subitem_setup(item);
    item.A.appendChild(item.progress);
  },
  style_ajaxitem_on: function(item) {
    this.style_subitem_on(item);
  },
  style_ajaxitem_off: function(item) {
    this.style_subitem_off(item);
  }
}

//------------------------------------------------------------------------
// traction.contextmenu.styles.DarkMenuStyle

Traction.DarkMenuStyle = Class.create();
Traction.DarkMenuStyle.prototype = Object.extend(new Traction.PlainMenuStyle(), {
  initialize: function() {
      this.setOptions({
	    scroller_border_color: "#000",
	    scroller_hover_color: "#ccc",
	    scroller_background_color: "#000",
	    scroller_height: 14,
	    menu_foreground_color: "#fff",
	    menu_border_color: "#000",
	    menu_drop_shadow_1_color: "#444",
	    menu_drop_shadow_2_color: "#111",
	    menu_font_family: "Tahoma, Verdana, Arial, Helvetica, sans-serif;",
	    menu_font_size: "8pt",
	    menu_background_color: "#000",
	    menu_opacity: 92, 
	    item_hover_foreground_color: "#000",
	    item_hover_background_color: "#ccc",
	    item_hover_border_color: "#000",
	    label_background_color: "#222",
	    submenu_image_src: "/images/submenu_grey.gif"
	    });
    },
    
    style_label_setup: function(item) {
      this.style_setup(item);
      with (item.TD) {
	style.backgroundColor = this.options.label_background_color;
	style.cursor = "default";
	style.fontWeight = "bold";
      }
    },
    style_label_on: function(item) {
      with (item.A) {
	style.textDecoration = "none";
      }
    },
    style_label_off: function(item) {}
  });

//------------------------------------------------------------------------
// traction.contextmenu.styles.OfficeMenuStyle

Traction.OfficeMenuStyle = Class.create();
Traction.OfficeMenuStyle.prototype = Object.extend(new Traction.PlainMenuStyle(), {
  initialize: function() {
    this.setOptions({
      scroller_border_color: "#002d96",
      scroller_hover_color: "#ffeec2",
      scroller_background_color: "#fff",
      scroller_height: 14,
      menu_foreground_color: "#000",
      menu_border_color: "#002d96",
      menu_drop_shadow_1_color: "#ddd",
      menu_drop_shadow_2_color: "#aaa",
      menu_font_family: "Tahoma, Verdana, Arial, Helvetica, sans-serif;",
      menu_font_size: "8pt",
      menu_background_color: "#fff",
      menu_opacity: 100, 
      item_hover_foreground_color: "#000",
      item_hover_background_color: "#ffeec2",
      item_hover_border_color: "#000080",
      label_background_color: "#e3efff"
    });
  },
  style_setup: function(item) {
    with (item.TD) {
      style.background = "url(/images/menugradient.gif) repeat-y top left";
      style.backgroundColor = "transparent";
      style.cursor = ua("css_cursor_property_pointer_value", "pointer");
    }
    var icon = item.icon;
    var label = item.label
    if (item.icon != null) {
      with (item.IMG) {
	src = icon;
	border = "";
	style["float"] = "left";
	style.position = "absolute";
	style.left = "3px";
	style.paddingTop = "3px";
      }
    }
    with (item.A) {
      innerHTML = label;
      style.padding = "4px 15px 4px 29px";
      style.display = "block";
      style.color = this.options.menu_foreground_color;
      style.fontFamily = this.options.menu_font_family;
      style.fontSize = this.options.menu_font_size;
      style.lineHeight = "14px";
    }
  },
  
  style_label_setup: function(item) {
    this.style_setup(item);
    with (item.TD) {
      style.backgroundColor = this.options.label_background_color;
      style.cursor = "default";
      style.fontWeight = "bold";
    }
    item.A.style.color = "#333";
  },
  
  style_abstract_setup: function(item) {
    this.style_setup(item);
      with (item.A) {
	style.backgroundColor = "transparent";
	style.paddingLeft = "5px";
      }    
  },
  style_abstract_on: function(item) {
    with (item.A) {
      style.padding = "2px 13px 2px 27px";
      style.margin = "1px";
      style.border = "1px solid "+this.options.item_hover_border_color;
      style.backgroundColor = this.options.item_hover_background_color;
      style.color = this.options.item_hover_foreground_color;
    }
  },
  style_abstract_off: function(item) {
    with (item.A) {
      style.padding = "4px 15px 4px 29px";
      style.margin = "0";
      style.border = "none";
      style.backgroundColor = "transparent";
      style.color = this.options.menu_foreground_color;
    }
  }
  });

//------------------------------------------------------------------------
// traction.contextmenu.styles.DefaultMenuStyle


Traction.DefaultMenuStyle = new Traction.OfficeMenuStyle();


//------------------------------------------------------------------------
// traction.contextmenu.styles.LightBlueMenuStyle

Traction.LightBlueMenuStyle = Class.create();
Traction.LightBlueMenuStyle.prototype = Object.extend(new Traction.PlainMenuStyle(), {
  initialize: function() {
      this.setOptions({
      scroller_border_color: "#a9bef5",
      scroller_hover_color: "#c5e4ff",
      scroller_background_color: "#f1f8ff",
      scroller_height: 14,
      menu_foreground_color: "#000",
      menu_border_color: "#a9bef5",
      menu_drop_shadow_1_color: "#c3bfc2",
      menu_drop_shadow_2_color: "#7b7e6d",
      menu_font_family: "Tahoma, Verdana, Arial, Helvetica, sans-serif;",
      menu_font_size: "8pt",
      menu_background_color: "#f1f8ff",
      menu_opacity: 100, 
      item_hover_foreground_color: "#000",
      item_hover_background_color: "#c5e4ff",
      item_hover_border_color: "#c5e4ff",
      label_background_color: "#fff"
	    });
    },
    
    style_label_setup: function(item) {
      this.style_setup(item);
      with (item.TD) {
	style.backgroundColor = this.options.label_background_color;
	style.cursor = "default";
	style.fontWeight = "bold";
      }
      item.A.style.color = "#444";
    },
    style_label_on: function(item) {
      with (item.A) {
	style.textDecoration = "none";
      }
    },
    style_label_off: function(item) {}
  });

//------------------------------------------------------------------------
// traction.ui.Fader

Traction.Fader = Class.create();
Traction.Fader.disabled = false;
Traction.Fader.prototype = {
  initialize: function(elements, options) {
    this.setOptions(options);
    if (elements.length) {
      this.elements = elements;
    } else {
      this.elements = [ elements ];
    }
    this.reset();
  },
  setOptions: function(options) {
    this.options = Object.extend({
      delay    : 0,   // delay before starting the fade
      start    : 100, // starting opacity (0-100)
      finish   : 0,   // ending opacity (0-100)
      step     : -10, // must be a factor of (start - finish)
      interval : 20,  // time in ms between each step, total = interval*(start-finish)/step
      nofade   : [],  // elements that should not be faded, but should show/hide
      display_none_at_finish: false
	  }, options || {});
  },
  reset: function() {
    if (this.timer) clearTimeout(this.timer);
    this.current = this.options.start;
    this.setOpacity(this.current);
  },
  show: function() {
    if (this.options.step < 0) this.reset();
    this.setVisibility('visible');
  },
  hide: function() {
    this.setVisibility('hidden');
    if (this.options.display_none_at_finish) {
      this.setDisplay("none");
    }
  },
  isReset: function() {
    return (this.current == this.options.start);
  },
  fade: function() {
    if (Traction.Fader.disabled) {
      this.setOpacity(100);
      var func = (this.options.step < 0) ? this.hide : this.show; 
      this.current = this.options.finish;
      this.timer = setTimeout(func.bind(this), this.options.delay);      
    } else {
      if (this.timer) clearTimeout(this.timer);
      if (this.isFinished()) { 
	if (this.options.step < 0) this.hide(); else this.show(); 
	return;
      }
      if (this.isReset() && this.options.delay > 0) {
	this.current += this.options.step; // we'll miss the first step, shhh.. don't tell anyone!
	this.timer = setTimeout(this.fade.bind(this), this.options.delay);      
	return;
      }
      this.setOpacity(this.current);
      this.current += this.options.step;
      this.timer = setTimeout(this.fade.bind(this), this.options.interval);
    }
  },
  isFinished: function() {
    return ( (this.options.step < 0) 
	     ? this.current <= this.options.finish 
	     : this.current >= this.options.finish );
  },
  isVisible: function() {
    return (this.elements[0].style.visibility == 'visible');
  },
  setVisibility: function(visibility) {
    for (var i=0; i<this.elements.length; i++) {
      this.elements[i].style.visibility = visibility;
    }
    for (var i=0; i<this.options.nofade.length; i++) {
      this.options.nofade[i].style.visibility = visibility;
    }    
  },
  setDisplay: function(display) {
    for (var i=0; i<this.elements.length; i++) {
      this.elements[i].style.display = display;
    }
    for (var i=0; i<this.options.nofade.length; i++) {
      this.options.nofade[i].style.display = display;
    }    
  },
  setOpacity: function(opacity) {
    opacity = (opacity == 100) ? 99.999 : opacity;
    for (var i=0; i<this.elements.length; i++) {
      this.elements[i].style.filter = "alpha(opacity:"+opacity+")";
      this.elements[i].style.opacity = opacity/100 ;
    }
  }
}

//------------------------------------------------------------------------
// traction.ui.Scroller

Traction.Scroller = Class.create();
Traction.Scroller.prototype = {
  
  initialize: function(element, options) {
    this.setOptions(options);
    this.element = element;
    this.uparrow = document.createElement("DIV");
    this.downarrow = document.createElement("DIV");
    this.updatePosition(1000);
    this.style_arrow(this.uparrow, false);
    this.style_arrow(this.downarrow, true);
    document.body.appendChild(this.uparrow);
    document.body.appendChild(this.downarrow);
  },
  setOptions: function(options) {
    this.options = Object.extend({
      arrowBackground  : "#fff", 
      arrowBorderColor : "#ddd",
      arrowDropShadow  : true,
      arrowDropShadow1 : "#ddd",
      arrowDropShadow2 : "#aaa",
      arrowHoverColor  : "#ffeec2",
      arrowHeight      : 10,   // # of px the up/down arrows should be
      step             : 30,   // # of px to move at each interval
      interval         : 40,   // time in ms between each step, velocity=step/interval px/ms
      onHover          : null, // callback when either arrow is hovered 
      onUnhover        : null  // callback when either arrow is unhovered
	  }, options || {});
  },
  up: function() { return this.uparrow; },
  down: function() { return this.downarrow; },
  style_hover: function(arrow) {
    arrow.style.backgroundColor = this.options.arrowHoverColor;
  },
  style_unhover: function(arrow) {
    arrow.style.backgroundColor = this.options.arrowBackground;
  },  
  style_visibility: function(arrow, main, isVisible) {
    var disp = (isVisible) ? "" : "none";
    if (!isVisible && arrow.style.display != disp) {
      if (this.options.onUnhover != null) this.options.onUnhover(null, true);
    }
    arrow.style.display = disp;
    if (!isVisible && main != null) {
      this.style_unhover(main);
    }
  },
  style_arrow: function(arrow, isDown) {
    with (arrow) {
      style.left   = px(this.bounds.x);
      style.position = "absolute";
      style.zIndex = 100;
      style.padding = "0px";
    }
    var img = document.createElement("IMG");
    with (img) {
      src = (isDown) ? "/images/dropdown.gif" : "/images/dropup.gif";
      border = "";
      style.margin = "auto";
      style.verticalAlign = "middle";
      style.marginTop = px(this.options.arrowHeight / 2 - 2);
    }
    var maindiv;
    if (this.options.arrowDropShadow) {
      var inner1 = document.createElement("DIV");
      var inner2 = document.createElement("DIV");
      inner1.appendChild(inner2);
      arrow.appendChild(inner1);
      arrow.style.borderRight = "1px solid "+this.options.arrowDropShadow1;
      inner1.style.borderRight = "1px solid "+this.options.arrowDropShadow2;
      inner1.style.backgroundColor = "transparent";
      inner2.style.backgroundColor = "transparent";
      if (isDown) {
	arrow.style.borderBottom = "1px solid "+this.options.arrowDropShadow1;
	inner1.style.borderBottom = "1px solid "+this.options.arrowDropShadow2;
      }
      inner2.appendChild(img);
      maindiv = inner2;
    } else {
      arrow.appendChild(img);
      maindiv = arrow;
    }
    maindiv.style.cursor = ua("css_cursor_property_pointer_value", "pointer");
    maindiv.style.border = "1px solid "+this.options.arrowBorderColor;      
    maindiv.style.backgroundColor = this.options.arrowBackground;
    maindiv.style.textAlign = "center";
    maindiv.style.verticalAlign = "middle";
    maindiv.style.width  = px(this.bounds.w - 4);
    maindiv.style.height = px(this.options.arrowHeight);
    if (isDown) {
      maindiv.style.borderTop = "none";
    } else {
      maindiv.style.borderBottom = "none";
    }
    if (isDown) {
      img.onmouseover = this.onHoverDown.bindAsEventListener(this);
      img.onmouseout = this.onUnhoverDown.bindAsEventListener(this);
      maindiv.onmouseover = this.onHoverDown.bindAsEventListener(this);
      maindiv.onmouseout = this.onUnhoverDown.bindAsEventListener(this);
    } else {
      img.onmouseover = this.onHoverUp.bindAsEventListener(this);
      img.onmouseout = this.onUnhoverUp.bindAsEventListener(this);
      maindiv.onmouseover = this.onHoverUp.bindAsEventListener(this);
      maindiv.onmouseout = this.onUnhoverUp.bindAsEventListener(this);
    }
    maindiv.onclick = this.onClick.bindAsEventListener(this);
    if (isDown) {
      this.downmain = maindiv;
    } else {
      this.upmain = maindiv;
    }
  },
  updatePosition: function(height) {
    this.height = height;
    this.bounds = Util.getElementBounds(this.element);
    this.uparrow.style.top    = px(this.bounds.y-1); // -1 fudge for IE
    this.uparrow.style.left   = px(this.bounds.x);
    this.downarrow.style.top  = px(this.bounds.y + (height > this.bounds.h ? this.bounds.h : height) - this.options.arrowHeight);    
    this.downarrow.style.left = px(this.bounds.x);
    this.uparrow.style.width = px(this.bounds.w);
    this.downarrow.style.width = px(this.bounds.w);
    this.setOffset(0);
  },
  isTopVisible:    function() { return (this.offset == 0); },
  isBottomVisible: function() { return (this.offset + this.height >= this.bounds.h); },
  getOffset: function() {
    return this.offset;
  },
  setOffset: function(offset) {
    if (offset < 0) {
      offset = 0;
    }
    if (this.height > this.bounds.h) {
      offset = 0;
      this.element.style.clip = "rect(0px,"+this.bounds.w+"px,"+this.bounds.h+"px,0px)";
    } else {
      if (offset > this.bounds.h - this.height) {
	offset = this.bounds.h - this.height;
      }
      this.element.style.clip = "rect("+offset+"px,"+this.bounds.w+"px,"+(this.height + offset)+"px,0px)";
      this.element.style.top = px(this.bounds.y - offset);
    }
    this.offset = offset;
    this.style_visibility(this.uparrow, this.upmain, !this.isTopVisible());
    this.style_visibility(this.downarrow, this.downmain, !this.isBottomVisible());
  },
   
  scroll: function(direction) {
    this.direction = direction;
    this.scroll_();
  },
  scroll_: function() {
    if (this.timer) clearTimeout(this.timer);
    this.setOffset(this.getOffset() + (this.direction * this.options.step));
    if (this.direction > 0) {
      if (this.isBottomVisible()) return;
    } else {
      if (this.isTopVisible()) return;
    }
    this.timer2 = setTimeout(this.scroll_wakeup_.bind(this), 1);
  },
  scroll_wakeup_: function() {
    this.timer = setTimeout(this.scroll_.bind(this), this.options.interval);
  },
  stop: function() {
    if (this.timer2) clearTimeout(this.timer2); // fixes Server18955.04
    if (this.timer) clearTimeout(this.timer);
  },
  onClick: function(event) {
    this.wasOnClick = true;
    this.stop();
    this.setOffset(this.getOffset() + (this.direction * (this.height - 2*this.options.arrowHeight - 15))); // 15 is fudge so you don't loose context
    return false;
  },
  onHoverUp: function(event) {
    if (this.wasOnClick) return;
    this.style_hover(this.upmain);
    this.scroll(-1*(event.shiftKey ? 3 : 1));
    if (this.options.onHover != null) this.options.onHover(event);
  },
  onHoverDown: function(event) {
    if (this.wasOnClick) return;
    this.style_hover(this.downmain);
    this.scroll(1*(event.shiftKey ? 3 : 1));
    if (this.options.onHover != null) this.options.onHover(event);
  },
  onUnhoverUp: function(event) {
    this.wasOnClick = false;
    this.style_unhover(this.upmain);
    this.stop();
    if (this.options.onUnhover != null) this.options.onUnhover(event);
  },
  onUnhoverDown: function(event) {
    this.wasOnClick = false;
    this.style_unhover(this.downmain);
    this.stop();
    if (this.options.onUnhover != null) this.options.onUnhover(event);
  }
};

//------------------------------------------------------------------------
// traction.contextmenu.SubMenu

Traction.SubMenu = Class.create();
Traction.SubMenu.prototype = {
  initialize: function(options) {
    this.setOptions(options);
    this.unlock();
  },
  setOptions: function(options) {
    this.options = Object.extend({
      isSubMenu  : false,
      parentMenu : null,
      menuItem   : null,
      name       : "untitled",
      styler     : Traction.DefaultMenuStyle
	  }, options || {});
  },
  build: function(items) {
    this.items = items;
    var isFirstTime = (this.div == null);
    if (isFirstTime) {
      this.div = document.createElement("DIV");
      var styleropts = this.options.styler.options;
      with (this.div) {
	style.position = "absolute";
	style.zIndex = 100;
	style.display = "block";
	style.backgroundColor = styleropts.menu_background_color;
	style.padding = "0px";
	style.fontFamily = "Arial, Helvetica, sans-serif;";
	style.fontSize = "8pt";
      }
      this.divA = document.createElement("DIV");
      this.div2 = document.createElement("DIV");
      this.div.appendChild(this.divA);
      this.divA.appendChild(this.div2);
      this.setOnLeftSide(false);
    } else {
      this.div2.innerHTML = "";
      this.table = null;
      this.tbody = null;
    }
    for (var i=0; i<items.length; i++) {
      items[i].insert(this, this.options.styler);
    }
    if (isFirstTime) {
      document.body.appendChild(this.div);
      var styleropts = this.options.styler.options;
      this.scroller = new Traction.Scroller(this.div, 
	{
	    height: 0, 
	    arrowBorderColor: styleropts.scroller_border_color, 
	    arrowBackground: styleropts.scroller_background_color, 
	    arrowHoverColor: styleropts.scroller_hover_color, 
	    arrowHeight: styleropts.scroller_height,
	    arrowDropShadow: true, 
	    arrowDropShadow1: styleropts.menu_drop_shadow_1_color, 
	    arrowDropShadow2: styleropts.menu_drop_shadow_2_color, 
	    onHover: this.lock.bind(this),
	    onUnhover: this.unlock.bind(this)
	});
      var op = styleropts.menu_opacity;
      this.fader = new Traction.Fader( [ this.scroller.up(), this.div, this.scroller.down() ],
				       (this.options.parentMenu != null) 
				       ? { step: -op, interval: op, start: op } // immediate shutoff for submenus
				       : { start: op }
				       );
    }
  },
  setOnLeftSide: function(onLeft) {
    var styleropts = this.options.styler.options;
    this.div2.style.border = "1px solid "+styleropts.menu_border_color; // blue border
    this.divA.style.borderBottom = "1px solid "+styleropts.menu_drop_shadow_2_color; // dark drop-shadow
    this.div.style.borderBottom = "1px solid "+styleropts.menu_drop_shadow_1_color; // light drop-shadow
    if (onLeft) {
      this.divA.style.borderRight = "none";
      this.div.style.borderRight = "none";
    } else {
      this.divA.style.borderRight = "1px solid "+styleropts.menu_drop_shadow_2_color; // dark drop-shadow
      this.div.style.borderRight = "1px solid "+styleropts.menu_drop_shadow_1_color; // light drop-shadow
    }
  },
  getDIV: function() {
    return this.div;
  },
  getTABLE: function() {
    if (this.table == null) {
      this.table = document.createElement("TABLE");
      this.table.cellSpacing = "0";
      this.table.cellPadding = "0";
      this.tbody = document.createElement("TBODY");
      this.table.appendChild(this.tbody);
      this.div.onmouseout = this.onmouseout.bindAsEventListener(this);
      this.div.onmouseover = this.onmouseover.bindAsEventListener(this);
      this.div2.appendChild(this.table);
    }
    return this.table;
  },
  getTBODY: function() {
    this.getTABLE();
    return this.tbody;
  },
  hide: function() {
    if (!this.locked) {
      if (this.options.parentMenu != null) this.options.parentMenu.unlock();
      if (this.options.menuItem != null) {
	this.options.menuItem.style_off();
      }
      this.hideSubMenusNow();
      this.fader.fade();
    }
  },
  hideSubMenusNow: function() {
    for (var i=0; i<this.items.length; i++) {
      this.items[i].hideSubMenuNow();
    }    
  },
  hideNow: function() {
    this.hideSubMenusNow();
    this.fader.hide();
    if (this.options.parentMenu != null) this.options.parentMenu.unlock();
    if (this.options.menuItem != null) {
      this.options.menuItem.style_off();
    }
  },
  showLoading: function(x,y) {
    this.build( [ new Traction.LabelItem(i18n("contextmenu_loading_placholder_text", "Loading...")) ] );
    this.show(x,y);
  },
  show: function(x,y) {
    if (this.hideTimer) clearTimeout(this.hideTimer);
    if (this.options.parentMenu != null) this.options.parentMenu.lock();
    if (this.options.menuItem != null) {
      this.options.menuItem.style_on();
    }
    var bounds = Util.getElementBounds(this.div);
    var view = Util.getViewportBounds();
    if (bounds.h > view.h) {
      y = view.y + 4;
    }
    else {
      var extra = y + bounds.h - view.y2();
      if (extra > 0) {
	y -= extra;
      }
    }
    var overX = (bounds.x + bounds.w) - (view.x + view.w);
    if (overX > 0) {
      x -= overX;
    }
    this.set_position(x,y,view.h-6);
    this.fader.show();
  },
  showAlignRight: function(x,y) {
    var bounds = Util.getElementBounds(this.div);
    this.show(x-bounds.w+2, y);
  },
  set_position: function(x, y, maxHeight) {
    with (this.div) {
      style.left = px(x);
      style.top = px(y);
      this.scroller.updatePosition(maxHeight);
    }    
  },
  onmouseout: function() {
    this.isMouseOver = false;
    if (!this.locked) {
      this.hideTimer = setTimeout(this.hide.bind(this), 200);
    }
  },
  onmouseover: function() {
    this.isMouseOver = true;
    if (this.options.parentMenu != null) this.options.parentMenu.lock();
    if (this.options.menuItem != null) {
      this.options.menuItem.style_on();
    }
    if (this.hideTimer) clearTimeout(this.hideTimer);
    this.fader.reset();
  },
  
  lock: function(event) { 
    this.locked = true; 
  },
  unlock: function(event, isInside) { 
    var isInside = isInside || this.isMouseOver;
    if (event && event != null) {
      isInside = Util.getElementBounds(this.div).isMouseInside(event);
    }
    if (this.locked && !isInside) {
      this.locked = false; 
      this.hide();
    }
    this.locked = false; 
  },
  isVisible: function() {
    return this.fader != null && this.fader.isVisible(); 
  },
  execute: function(event, action, item) {
    var str = "";
    for (var i=0; i<this.items.length; i++) {
      var actionName = (this.items[i].action != null) ? this.items[i].action.action : null;
      str += actionName + "\n";
      if (action == actionName) {
	this.items[i].execute(event);
	break;
      }
    }
  }
};

//------------------------------------------------------------------------
// traction.contextmenu.items.SubItem

Traction.SubItem = Class.create();
Traction.SubItem.prototype = Object.extend(new Traction.AbstractItem(), {
  initialize: function(label, icon, items) {
      this.label = label;
      this.icon = icon;
      this.items = items;
      if (this.icon == '') {
	this.icon = null;
      }
    },
    insert: function(submenu, styler) {
      this._insert(submenu, styler);
      this.parent = submenu;    
    },
    style_setup: function() {
      this.styler.style_subitem_setup(this);
    },
    style_on: function() {
      this.styler.style_subitem_on(this);
    },
    style_off: function() {
      this.styler.style_subitem_off(this);
    },
    hover: function(event) {
      this.style_on();
      this._SubItem_hover(event);
    },
    _SubItem_hover: function(event) {
      if (this.menu == null) {
	this.menu = new Traction.SubMenu( { isSubMenu: true, parentMenu: this.parent, menuItem: this, name: this.label, styler: this.styler } );
	this.menu.build(this.items);
      }
      var rect = Util.getElementBounds(this.TD);
      var view = Util.getViewportBounds();
      var spaceOnRight = view.x2() - rect.x2();
      var overlapMenuBy = 0;
      var requiredSpaceOnRight = 80;
      if (spaceOnRight >= requiredSpaceOnRight) {
	this.menu.setOnLeftSide(false);
	this.menu.show(rect.x + rect.w - overlapMenuBy, rect.y + 1);
      } else {
	this.menu.setOnLeftSide(true);
	this.menu.showAlignRight(rect.x - 1, rect.y);
      }
    },
    unhover: function(event) {
      this.style_off();
      this._SubItem_unhover();
    },
    _SubItem_unhover: function(event) {
      if (this.menu != null) {
	this.menu.hide();
      }
    },
    execute: function(event) {
      this.hover(event);
    }    
});

//------------------------------------------------------------------------
// traction.contextmenu.items.AjaxItem

var AjaxItemCache = new Array();
Traction.AjaxItem = Class.create();
Traction.AjaxItem.prototype = Object.extend(new Traction.SubItem(), {
  style_setup: function() {
      this.styler.style_ajaxitem_setup(this);
  },
  style_on: function() {
    this.styler.style_ajaxitem_on(this);
  },
  style_off: function() {
    this.styler.style_ajaxitem_off(this);
  },
  hover: function(event) {
      this.style_on();
      if (this.ajaxInProgress) return;
      var state = { event: event };
      if (this.items != null) {
	this._SubItem_hover(event);
      } else {
	var cached = AjaxItemCache[this.poststring()]; // check cache
	if (cached) {
	  this.ajaxInProgress = true;
	  this.done(cached, state);
	} else {
	  this.ajaxInProgress = true;
	  this.xmlrpc = xmlpost_async(FORM_ACTION_READ_WRITE, this.poststring(), this.progress, false, this.done.bind(this), state, -1);
	}
      }
  },
  poststring: function() {
      alert('override poststring() in AjaxItem subclass');
      var poststring = "";
      poststring = fm_append(poststring, "type=ajaxrpc");
      poststring = fm_append(poststring, "method=hello");
      return poststring; 
  },
    unhover: function(event) {
      this.style_off();
      this._SubItem_unhover(event);
      if (this.ajaxInProgress && this.xmlrpc && 
	  (this.xmlrpc.readyState == 0 || this.xmlrpc.readyState == 1)) {
	xmlprogress(-1, this.progress);
	this.xmlrpc.abort();
	this.items = null;
	this.ajaxInProgress = false;
      }
      this.ajaxInProgress = false;
    },
    done: function(responseText, state) {      
      AjaxItemCache[this.poststring()] = responseText; // cache this
      if (responseText) {
	this.items = this.getItems(responseText, state);
	if (this.ajaxInProgress) {
	  this._SubItem_hover(state.event);
	}
      }
      this.ajaxInProgress = false;
    },
    getItems: function(responseText, state) {
      alert('override getItems() in AjaxItem subclass');
    }
});

//------------------------------------------------------------------------
// traction.atom.LabelCache

Traction.LabelCache = {};

Traction.LabelCache.getLabels = function(project, callback, state, requireAnyPermission) {
  var poststring = "";
  poststring = fm_append(poststring, "type=ajaxrpc");
  poststring = fm_append(poststring, "method=GetLabelsWithIcons");
  poststring = fm_append(poststring, "p0="+project);
  if (requireAnyPermission) {
    poststring = fm_append(poststring, "p1="+requireAnyPermission);
  }
  var mystate = { callback: callback, state: state, poststring: poststring, project: project };
  var cached = AjaxItemCache[poststring]; // check cache
  if (cached) {
    this.getLabels_done(cached, mystate);
  } else {
    xmlpost_async(FORM_ACTION_READ_WRITE, poststring, null, false, this.getLabels_done.bind(this), mystate, -1);    
  }
};
Traction.LabelCache.getLabels_done = function(responseText, state) {
  if (responseText) {
    AjaxItemCache[state.poststring] = responseText;
    var responseArray = eval(responseText);
    var labelArray = new Array();
    for (var i=0; i<responseArray.length; i++) {
      labelArray[i] = Traction.Label.fromArray(responseArray[i]);
      if (!labelArray[i].project || labelArray[i].project == null) {
	labelArray[i].project = state.project; // fix the project
      }
    }
    state.callback(labelArray, state.state);
  }
};
Traction.LabelCache.getProjects = function(permission, callback, state) {
  var poststring = "";
  poststring = fm_append(poststring, "type=ajaxrpc");
  poststring = fm_append(poststring, "method=getProjects");
  poststring = fm_append(poststring, "p0="+permission);
  var mystate = { callback: callback, state: state, poststring: poststring };
  var cached = AjaxItemCache[poststring]; // check cache
  if (cached) {
    this.getProjects_done(cached, mystate);
  } else {
    xmlpost_async(FORM_ACTION_READ_WRITE, poststring, null, false, this.getProjects_done.bind(this), mystate, -1);    
  }  
};
Traction.LabelCache.getProjects_done = function(responseText, state) {
  if (responseText) {
    AjaxItemCache[state.poststring] = responseText;
    var responseArray = eval(responseText);
    state.callback(responseArray, state.state);
  }
};

//------------------------------------------------------------------------
// traction.atom.Project

Traction.Project = Class.create();
Traction.Project.prototype = {
  initialize: function(name, id, displayName, activityCount) {
    this.name = name;
    this.id = id;
    if (typeof(displayName) == "undefined") {
      this.displayName = name;
    } else {
      this.displayName = displayName;
    }
    if (typeof(activityCount) == "undefined") {
      this.activityCount = -1;
    } else {
      this.activityCount = activityCount;
    }
  },
  debug: function() {
    Debug.println(name, " / ", id, " / ", displayName);
  },
  getID: function() {
    return this.id;
  },
  getName: function() {
    return this.name;
  },
  getDisplayName: function() {
    return this.displayName;
  },
  getActivityCount: function() {
    return this.activityCount;
  },
  toString: function() {
    return this.name + " / " + this.id + " / " + this.displayName;
  },
  copy: function() {
    return new Traction.Project(this.name, this.id, this.displayName, this.activityCount);
  }
};
Traction.Project.fromFastFilter = function(navModFilter, navModDisplayName, navModCount) {
  var idFromProjectFilter = new RegExp("^\\+p:([0-9]+)$");
  var matchGroups = idFromProjectFilter.exec(navModFilter);
  if (matchGroups != null) {
    return new Traction.Project(navModDisplayName, matchGroups[1], navModDisplayName, navModCount);
  }
  return null;
};

//------------------------------------------------------------------------
// traction.contextmenu.actions.AbstractAction

Traction.AbstractAction = function() {};
Traction.AbstractAction.prototype = {
  execute: function(event, item) {}
};

//------------------------------------------------------------------------
// traction.contextmenu.actions.Action

Traction.Action = Class.create();
Traction.Action.prototype = Object.extend(new Traction.AbstractAction(),{
  initialize: function(opttype, action, formtarget, opentargetjs, windowUrl, windowName, windowFeatures, javascriptToEval) {
      this.opttype = opttype;
      this.action = action;
      this.formtarget = formtarget;
      this.opentargetjs = opentargetjs;
      this.windowUrl = windowUrl;
      this.windowName = windowName;
      this.windowFeatures = windowFeatures;
      this.javascriptToEval = javascriptToEval;
  },
  execute: function(event, item) {
      processAction(this.opttype, this.action, this.formtarget, this.opentargetjs, this.windowUrl, this.windowName, eval(this.windowFeatures), this.javascriptToEval);
      if (itemMenu != null) { itemMenu.hide(); itemMenu.clearCache(); }
  }
});

//------------------------------------------------------------------------
// traction.util.AjaxRPC

Traction.AjaxRPC = {
  
  poststring: function(method, params, curitem, curentry) {
      var poststring = "";
      poststring = fm_append(poststring, "type=ajaxrpc");
      poststring = fm_append(poststring, "method="+method);
      if (params && params != null) {
	for (var i=0; i<params.length; i++) {
	  poststring = fm_append(poststring, "p"+i+"="+encode_url_parameter(params[i]));
	}
      }
      if (curitem && curitem != null) {
	poststring = fm_append(poststring, "curitem="+curitem);    
      }
      if (curentry && curentry != null) {
	poststring = fm_append(poststring, "curentry="+curentry);    
      }
      return poststring;
  }
};

//------------------------------------------------------------------------
// traction.contextmenu.actions.Collect

Traction.Collect = Class.create();
Traction.Collect.prototype = Object.extend(new Traction.AbstractAction(),{
  initialize: function(title, isItem) {
      this.title = title;
      this.isItem = isItem;
  },
  execute: function(event, item) {
      var poststring = Traction.AjaxRPC.poststring("CollectAdd", [ this.title, "true" ], (this.isItem ? curItem : null), (this.isItem ? null : curItem));
      xmlpost_async(FORM_ACTION_READ_WRITE, poststring, null, true, this.done.bind(this), null);
      if (itemMenu != null) { itemMenu.hide(); itemMenu.clearCache(); }
  },
  done: function(responseText, state) {
      if (responseText == null) {
	failureServerUnavailable();
      } else if (responseText == "") {
	failureUnauthorized();
      } else {
	var error = findErrorAndFeedback(responseText);
	if (error[0] != null && error[0] != "" && error[0] != "undefined") {
	  alert(error[0]);
	} else {
	  eval(responseText);
	}
      }
    },
  failed: function() {
      alert(i18n("contextmenu_action_generic_failure_message", "Sorry, but the operation failed."));
  }
});

//------------------------------------------------------------------------
// traction.contextmenu.actions.CustomAction

Traction.CustomAction = Class.create();
Traction.CustomAction.prototype = Object.extend(new Traction.AbstractAction(), {
  initialize: function(id, requireconfirm) {
      this.id = id;
      this.requireconfirm = requireconfirm;
  },
  execute: function(event, item) {
      processCustomActionInline(this.id, this.requireconfirm);
      if (itemMenu != null) { itemMenu.hide(); itemMenu.clearCache(); }
  }
});

//------------------------------------------------------------------------
// traction.contextmenu.actions.Reclassify

Traction.Reclassify = Class.create();
Traction.Reclassify.prototype = Object.extend(new Traction.AbstractAction(), {
  
  initialize: function(remLabels, addLabels, removeAfterExecute, requireConfirmation) {
      this.remLabels = this.normalize(remLabels);
      this.addLabels = this.normalize(addLabels);
      this.removeAfterExecute = removeAfterExecute;
      this.requireConfirmation = requireConfirmation;
      Debug.println("Reclassify add ["+this.addLabels+"] rem ["+this.remLabels+"]");
  },
  
  normalize: function(labels) {
      if (labels == null) {
	return '';
      } else {
	if (labels instanceof Array) {
	  var ret = "";
	  for (var i=0; i<labels.length; i++) {
	    if (i > 0) ret += ' ';
	    ret += labels[i].getRapidSelectorFormat();
	  }
	  return ret;
	}
	else {
	  return labels;
	}
      }
  },
  execute: function(event, item) {
      if (this.requireConfirmation) {
	var origtypeparam = "";
	if (document.fm.type && document.fm.type.value) {
	  origtypeparam = "&origtype=" + document.fm.type.value;
	}
	var extras = "";
	extras += "&addlabels=" + encode_url_parameter(this.addLabels);
	extras += "&remlabels=" + encode_url_parameter(this.remLabels);
	extras += "&curitem=" + decode_url_parameter(curItem);
	extras += origtypeparam;
	openDialog("reclassifydialog",
		   "modern_insertlabel_window",
		   "",
		   extras,
		   DIALOG_FLAG_MULTIPLE_WINDOWS);	
      } else {
	var poststring = "";
	poststring = fm_append(poststring, "type=ajaxrpc");
	poststring = fm_append(poststring, "method=reclassify");
	poststring = fm_append(poststring, "proj="+_get_url_param("proj", "*"));
	poststring = fm_append(poststring, "p0="+encode_url_parameter(this.remLabels));
	poststring = fm_append(poststring, "p1="+encode_url_parameter(this.addLabels));
	poststring = fm_append(poststring, "curitem="+curItem);
	var state = { menuItem: item, curItem: curItem };
	xmlpost_async(FORM_ACTION_READ_WRITE, poststring, null, true, this.done.bind(this), state);
      }
      if (itemMenu != null) { itemMenu.hide(); itemMenu.clearCache(); }
  },
  done: function(responseText, state) {
      labels_refresh(responseText, 
		     [ encode_url_parameter(state.curItem), this.failed.bind(this) ]);
      if (typeof(removeAfterExecute) != "undefined" && removeAfterExecute != null) {
	state.menuItem.remove();
      }
  },
  failed: function() {
      alert(i18n("contextmenu_action_generic_failure_message", "Sorry, but the operation failed."));
  }
});

//------------------------------------------------------------------------
// traction.contextmenu.actions.RemoveLabel

Traction.RemoveLabel = Class.create();
Traction.RemoveLabel.prototype = Object.extend(new Traction.AbstractAction(), {
  initialize: function(label) {
      this.label = label;
  },
  execute: function(event, item) {
      var poststring = "";
      poststring = fm_append(poststring, "type=ajaxrpc");
      poststring = fm_append(poststring, "method=reclassify");
      poststring = fm_append(poststring, "p0="+this.label);
      poststring = fm_append(poststring, "curitem="+curItem);
      var state = { menuItem: item, curItem: curItem };
      xmlpost_async(FORM_ACTION_READ_WRITE, poststring, null, true, this.done.bind(this), state);
      if (itemMenu != null) { itemMenu.hide(); itemMenu.clearCache(); }
  },
  done: function(responseText, state) {
      labels_refresh(responseText, 
		     [ encode_url_parameter(state.curItem), this.failed.bind(this) ]);
      state.menuItem.remove();
  },
  failed: function() {
      alert(i18n("contextmenu_action_generic_failure_message", "Sorry, but the operation failed."));
  }
});

//------------------------------------------------------------------------
// traction.contextmenu.actions.StatusChange

Traction.StatusChange = Class.create();

Traction.StatusChange.exec = function(operationName, targets, opttype, triggerElement) {
  var change = new Traction.StatusChange(operationName, targets, triggerElement);
  change.execute(null, null, triggerElement);
};
Traction.StatusChange.prototype = Object.extend(new Traction.AbstractAction(), {
  
  initialize: function(operationName, targets, triggerElement) {
      this.targets = this.normalize(targets);
      this.operationName = operationName;
      this.triggerElement = triggerElement;
      Debug.println(this.operationName+" ["+this.targets+"]");
  },
  
  normalize: function(targets) {
      if (targets == null) {
	return '';
      } else {
	if (targets instanceof Array) {
	  var ret = "";
	  for (var i=0; i<targets.length; i++) {
	    if (i > 0) ret += ',';
	    ret += targets;
	  }
	  return ret;
	}
	else {
	  return targets;
	}
      }
  },
  execute: function(event, item) {
      var poststring = "";
      poststring = fm_append(poststring, "type=ajaxrpc");
      poststring = fm_append(poststring, "method=changeStatus");
      poststring = fm_append(poststring, "p0="+this.operationName);
      poststring = fm_append(poststring, "entryid="+encode_url_parameter(this.targets));
      var state = { menuItem: item, curItem: curItem };
      xmlpost_async(FORM_ACTION_READ_WRITE, poststring, null, true, this.done.bind(this), state);
      if (itemMenu != null) { itemMenu.hide(); itemMenu.clearCache(); }
  },
  done: function(responseText, state) {
      var data;
      eval("data = " + responseText);
      if (!data["success"]) {
	var errorMessage = data["errorMessage"];
	switch (data["errorType"]) {
	case "namingConflict":
	  if (data["canSeeName"] && data["editActionURL"] != null) {
	    if (confirm(errorMessage + "\n\n" +
			i18n("changestatus_fix_naming_conflict_confirmation_message",
			     "You can edit the article to fix the naming conflict.\n\nClick OK if you want to edit the article now.\nClick Cancel otherwise."))) {
	      document.location.href = data["editActionURL"];
	    }
	  }
	  break;
        default:
	  alert(errorMessage);
	}
      }
      else {
	if (this.targets.indexOf(",") == -1 && this.triggerElement != null) {
	  var tid = Traction.ID.parse(this.targets);
	  var poststring = "";
	  poststring = fm_append(poststring, "type=entrycontrols_");
	  poststring = fm_append(poststring, "proj=" + encode_url_parameter(tid.project));
	  poststring = fm_append(poststring, "rec=" + tid.entry);
	  poststring = fm_append(poststring, "operation=" + this.operationName);
	  xmlpost_async(FORM_ACTION_READ_ONLY, poststring, null, true, this.updateEntryControls.bind(this));
	}
	else {
	}
      }
  },
  updateEntryControls: function(responseText) {
      var elm = this.triggerElement;
      while (elm != null) {
	if (elm.nodeName.toLowerCase() == "div" && elm.className.indexOf("entrycontrols") >= 0) {
	  elm = elm.parentNode; // up to TD
	  break;
	}
	elm = elm.parentNode;
      }
      elm.innerHTML = responseText;
  },
  failed: function() {
      alert(i18n("contextmenu_action_generic_failure_message", "Sorry, but the operation failed."));
  }
});

//------------------------------------------------------------------------
// traction.Events

var Events = {
  MouseMove: "mousemove",
  MouseDown: "mousedown",
  MouseUp: "mouseup",
  MouseOver: "mouseover",
  MouseOut: "mouseout",
  ContextMenu: "contextmenu",
  Click: "click",
  Submit: "submit",
  Resize: "resize",
  Change: "change",
  Focus: "focus",
  Drag: "drag",
  DragStart: "dragstart",
  DragInit: "draginit",
  DragMove: "dragmove",
  DragEnd: "dragend",
  Load: "load",
  Unload: "unload",
  SelectStart: "selectstart",
  KeyPress: "keypress",
  KeyUp: "keyup",
  KeyDown: "keydown",
  DOMchanged: "_domchanged",
  
  attach: function(obj, name, scope, handler) {
    this.insert(-1, obj, name, scope, handler);
  },
  attachCustom: function(name, scope, handler) {
    this.attach(null, name, scope, handler);
  },
  kill: function(obj, name) {
    if (obj == null) {
      obj = window;
    }
    obj["on"+name] = function() { return false; }
    obj[name+"_Handlers"] = null;
  },
  cancel: function(event) {
    if (event) {
      if (event.stopPropagation) event.stopPropagation();
      event.cancelBubble = true;
    }
  },
  
  insert: function(index, obj, name, scope, handler) {
    if (obj == null) obj = window;
    var handlers = obj[name+"_Handlers"];
    if (handlers == null) {
      handlers = new Events.Handlers();
      obj[name+"_Handlers"] = handlers;
      if (obj.addEventListener) {
	obj.addEventListener(name, handlers.dispatch.bindAsEventListener(handlers), false);
      } else if (obj.attachEvent) {
	obj.attachEvent("on" + name, handlers.dispatch.bindAsEventListener(handlers));
      } else {
	obj["on" + name] = handlers.dispatch.bindAsEventListener(handlers);
      }
    }
    handlers.insert(index, scope, handler);
  },
  
  fire: function(obj, name, event) {
    if (obj == null) obj = window;
    var handlers = obj[name+"_Handlers"];
    if (handlers != null) {
      handlers.dispatch(event);
    }
  },
  fireCustom: function(name) {
    this.fire(null, name, null);
  }
  
  
};
Events.Handlers = Class.create();
Events.Handlers.prototype = {
  initialize: function() {
    this.handlers = new Array();
  },
  insert: function(index, scope, handler) {
    var func = handler.bindAsEventListener(scope);
    if (index == -1 || index >= this.handlers.length) {
      this.handlers.push(func);
    }
    else {
      for (var i=this.handlers.length; i > index; i--) {
	this.handlers[i] = this.handlers[i-1];
      }
      this.handlers[index] = func;
    }
  },
  dispatch: function(event) {
    var ret = true;
    for (var i=0; i<this.handlers.length; i++) {
      ret &= this.handlers[i](event);
    }
    if (!ret) {
      if (event) {
	if (event.stopPropagation) event.stopPropagation();
	event.cancelBubble = true;
      }
    }
    return ret;
  }
};

//------------------------------------------------------------------------
// traction.util.CookiePrefs


Util.CookiePrefs = {
  NAVFRAME_SCALE_FONT: "nsf",
  NAVFRAME_SORT_BY:    "nso",
  TINYMCE_CONTROL_SET_MODE:  "tmm",
  cookie_: null,
  map_: {},
  cookie: function() {
    if (Util.CookiePrefs.cookie_ == null) {
      Util.CookiePrefs.cookie_ = Util.Cookie.getCookie("cp", false);
      if (Util.CookiePrefs.cookie_ == null) {
	Util.CookiePrefs.cookie_ = new Util.Cookie("cp", "", false);
      }
      Util.CookiePrefs.map_ = Util.CookiePrefs.value2map(Util.CookiePrefs.cookie_.value);
    }
    return Util.CookiePrefs.cookie_;
  },
  map: function() {
    Util.CookiePrefs.cookie();
    return Util.CookiePrefs.map_;
  },
  set: function(name, value) {
    Util.CookiePrefs.map()[name] = value;
    var c = Util.CookiePrefs.cookie();
    c.value = Util.CookiePrefs.map2value(Util.CookiePrefs.map_);
    c.savePermanent();
  },
  get: function(name, defvalue) {
    var ret = Util.CookiePrefs.map()[name];
    return ret ? ret : defvalue;
  },
  value2map: function(value) {
    var map = {};
    if (value) {
      var pairs = value.split(',');
      for (var i=0; i<pairs.length; i++) {
	var pair = pairs[i];
	pair = unescape_char(pair, ',');
	var eq = pair.indexOf('=');
	if (eq >= 0) {
	  map[pair.substring(0,eq)] = pair.substring(eq+1);
	} else {
	  map[pair] = "";
	}
      }
    }
    return map;
  },
  map2value: function(map) {
    var value = "";
    if (map) {
      for (key in map) {
	if (key == "extend") continue; // ignore function added by Prototype
	if (value != "") {
	  value += ",";
	}
	value += escape_char(key+"="+map[key], ',');
      }
    }
    return value;
  }
};

//------------------------------------------------------------------------
// traction.util.Date


Date.prototype.formatDate = function (input,time) {
  var daysLong =    ["Sunday", "Monday", "Tuesday", "Wednesday", 
		     "Thursday", "Friday", "Saturday"];
  var daysShort =   ["Sun", "Mon", "Tue", "Wed", 
		     "Thu", "Fri", "Sat"];
  var monthsShort = ["Jan", "Feb", "Mar", "Apr",
		     "May", "Jun", "Jul", "Aug", "Sep",
		     "Oct", "Nov", "Dec"];
  var monthsLong =  ["January", "February", "March", "April",
		     "May", "June", "July", "August", "September",
		     "October", "November", "December"];
  var switches = { // switches object
    a : function () {
      return date.getHours() > 11? "pm" : "am";
    },
    A : function () {
      return (this.a().toUpperCase ());
    },
    B : function (){
      var off = (date.getTimezoneOffset() + 60)*60;
      var theSeconds = (date.getHours() * 3600) + 
      (date.getMinutes() * 60) + 
      date.getSeconds() + off;
      var beat = Math.floor(theSeconds/86.4);
      if (beat > 1000) beat -= 1000;
      if (beat < 0) beat += 1000;
      if ((String(beat)).length == 1) beat = "00"+beat;
      if ((String(beat)).length == 2) beat = "0"+beat;
      return beat;
    },
    c : function () {
      return (this.Y() + "-" + this.m() + "-" + this.d() + "T" + 
	      this.h() + ":" + this.i() + ":" + this.s() + this.P());
    },
    d : function () {
      var j = String(this.j());
      return (j.length == 1 ? "0"+j : j);
    },
    D : function () {
      return daysShort[date.getDay()];
    },
    F : function () {
      return monthsLong[date.getMonth()];
    },
    g : function () {
      return date.getHours() > 12? date.getHours()-12 : date.getHours();
    },
    G : function () {
      return date.getHours();
    },
    h : function () {
      var g = String(this.g());
      return (g.length == 1 ? "0"+g : g);
    },
    H : function () {
      var G = String(this.G());
      return (G.length == 1 ? "0"+G : G);
    },
    i : function () {
      var min = String (date.getMinutes ());
      return (min.length == 1 ? "0" + min : min);
    },
    I : function () {
      var noDST = new Date ("January 1 " + this.Y() + " 00:00:00");
      return (noDST.getTimezoneOffset () == 
	      date.getTimezoneOffset () ? 0 : 1);
    },
    j : function () {
      return date.getDate();
    },
    l : function () {
      return daysLong[date.getDay()];
    },
    L : function () {
      var Y = this.Y();
      if (         
	  (Y % 4 == 0 && Y % 100 != 0) ||
	  (Y % 4 == 0 && Y % 100 == 0 && Y % 400 == 0)
	  ) {
	return 1;
      } else {
	return 0;
      }
    },
    m : function () {
      var n = String(this.n());
      return (n.length == 1 ? "0"+n : n);
    },
    M : function () {
      return monthsShort[date.getMonth()];
    },
    n : function () {
      return date.getMonth()+1;
    },
    N : function () {
      var w = this.w();
      return (w == 0 ? 7 : w);
    },
    O : function () {
      var os = Math.abs(date.getTimezoneOffset());
      var h = String(Math.floor(os/60));
      var m = String(os%60);
      h.length == 1? h = "0"+h:1;
      m.length == 1? m = "0"+m:1;
      return date.getTimezoneOffset() < 0 ? "+"+h+m : "-"+h+m;
    },
    P : function () {
      var O = this.O();
      return (O.substr(0, 3) + ":" + O.substr(3, 2));
    },      
    r : function () {
      var r; // result
      r = this.D() + ", " + this.d() + " " + this.M() + " " + this.Y() +
      " " + this.H() + ":" + this.i() + ":" + this.s() + " " + this.O();
      return r;
    },
    s : function () {
      var sec = String (date.getSeconds ());
      return (sec.length == 1 ? "0" + sec : sec);
    },        
    S : function () {
      switch (date.getDate ()) {
	case  1: return ("st"); 
	case  2: return ("nd"); 
	case  3: return ("rd");
	case 21: return ("st"); 
	case 22: return ("nd"); 
	case 23: return ("rd");
	case 31: return ("st");
	default: return ("th");
      }
    },
    t : function () {
      var daysinmonths = [null,31,28,31,30,31,30,31,31,30,31,30,31];
      if (this.L()==1 && this.n()==2) return 29; // ~leap day
      return daysinmonths[this.n()];
    },
    U : function () {
      return Math.round(date.getTime()/1000);
    },
    w : function () {
      return date.getDay();
    },
    W : function () {
      var DoW = this.N ();
      var DoY = this.z ();
      var daysToNY = 364 + this.L () - DoY;
      if (daysToNY <= 2 && DoW <= (3 - daysToNY)) {
	return 1;
      }
      if (DoY <= 2 && DoW >= 5) {
	return new Date (this.Y () - 1, 11, 31).formatDate ("W");
      }
      var nyDoW = new Date (this.Y (), 0, 1).getDay ();
      nyDoW = nyDoW != 0 ? nyDoW - 1 : 6;
      if (nyDoW <= 3) { // First day of the year is a Thursday or earlier
	return (1 + Math.floor ((DoY + nyDoW) / 7));
      } else {  // First day of the year is a Friday or later
	return (1 + Math.floor ((DoY - (7 - nyDoW)) / 7));
      }
    },
    y : function () {
      var y = String(this.Y());
      return y.substring(y.length-2,y.length);
    },        
    Y : function () {
      if (date.getFullYear) {
	var newDate = new Date("January 1 2001 00:00:00 +0000");
	var x = newDate .getFullYear();
	if (x == 2001) {              
	  return date.getFullYear();
	}
      }
      var x = date.getYear();
      var y = x % 100;
      y += (y < 38) ? 2000 : 1900;
      return y;
    },
    z : function () {
      var t = new Date("January 1 " + this.Y() + " 00:00:00");
      var diff = date.getTime() - t.getTime();
      return Math.floor(diff/1000/60/60/24);
    },
    Z : function () {
      return (date.getTimezoneOffset () * -60);
    }        
  }
  function getSwitch(str) {
    if (switches[str] != undefined) {
      return switches[str]();
    } else {
      return str;
    }
  }
  var date;
  if (time) {
    var date = new Date (time);
  } else {
    var date = this;
  }
  var formatString = input.split("");
  var i = 0;
  while (i < formatString.length) {
    if (formatString[i] == "\\") {
      formatString.splice(i,1);
    } else {
      formatString[i] = getSwitch(formatString[i]);
    }
    i++;
  }
  return formatString.join("");
}
Date.DATE_ATOM    = "Y-m-d\\TH:i:sP";
Date.DATE_ISO8601 = "Y-m-d\\TH:i:sO";
Date.DATE_RFC2822 = "D, d M Y H:i:s O";
Date.DATE_W3C     = "Y-m-d\\TH:i:sP";

//------------------------------------------------------------------------
// traction.util.Finder


Util.Finder = Class.create();
Util.Finder.prototype = {
  columns: new Array(),
  request: null,
  lastRequestVal: null,
  finderNode: null,
  lastKeyWasLeftArrow: false,
  lastKeyWasRightArrow: false,
  initialize: function(options) {
    this.options = {
      rootDirectory: "/",
      finderID: "finder",
      listIDRoot: "filelist",
      onChooseFile: null,
      throbberImage: null
    }
    Object.extend(this.options, options || {});
    if (!this.options.formField) {
      Debug.println("No form field for Finder!");
      return;
    }
    if (!this.options.finderID ||
	!(this.finderNode = document.getElementById(this.options.finderID))) {
      Debug.println("No finder root node for Finder!");
      return;
    }
    Debug.println("Finder Container = ", this.finderNode.nodeName, " (id = '", this.options.finderID, "')");
    var i;
    i = 0;
    Debug.println("listIDRoot: '", this.options.listIDRoot, "'");
    var fileList = document.getElementById(this.options.listIDRoot + i);
    while (fileList != null) {
      this.saveColumn(fileList, fileList.nodeName == "SELECT");
      i ++;
      fileList = document.getElementById(this.options.listIDRoot + i);
    }
  },
  saveColumn: function(column, isFolder) {
    this.columns[this.columns.length] = column;
    if (isFolder) {
      Events.attach(column, "change", this, this.onChooseFile);
    }
  },
  addColumn: function(newColumnHTML, id, isFolder, testVal) {
    Debug.println("Testing '", testVal, "' and '", this.lastRequestVal, "':  equal? ", (testVal == this.lastRequestVal));
    if (testVal != this.lastRequestVal) {
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    var dummyContainer = document.createElement("SPAN");
    var appendTo;
    var finderDIV = getFirstChildByName(this.finderNode, "DIV");
    if (isFolder) {
      appendTo = finderDIV;
    } else {
      appendTo = this.finderNode.firstChild.firstChild.firstChild.nextSibling;
    }
    appendTo.appendChild(dummyContainer);
    dummyContainer.innerHTML = newColumnHTML;
    finderDIV.scrollLeft = finderDIV.scrollWidth;
    var column = document.getElementById(id);
    this.saveColumn(column, isFolder);
  },
  abortPreviousRequest: function(curval) {
    this.lastRequestVal = curval;
    if (this.request != null && this.request.readyState == 1) {
      try {
	Debug.println("Aborting previous finder folder listing / file preview request.");
	this.request.abort();
	this.request = null;
      } catch (e) {
	alert(e);
      }
    }
  },
  
  onChooseFile: function(event) {
    var fileList = (typeof(event.target) == "undefined") ? event.srcElement : event.target;
    Debug.println("onChange for ", fileList.name);
    if (this.lastKeyWasLeftArrow || this.lastKeyWasRightArrow) { // left arrow
      if (event.stopPropagation) event.stopPropagation();
      event.cancelBubble = true;
      return false;
    }
    Debug.println("onChooseFile for file listing ", fileList.name, ".");
    var selectedFile = fileList.options[fileList.selectedIndex];
    this.options.formField.value = selectedFile.value;
    this.abortPreviousRequest(selectedFile.value);
    var isFolder = (selectedFile.className.indexOf("folder") != -1);
    Debug.println("Removing columns to the right of the file listing where ", selectedFile.value, " was selected.");
    var newIdx = parseInt(fileList.name.substring(this.options.listIDRoot.length), 10) + 1;
    for (var i = this.columns.length - 1; i >= newIdx; i --) {
      var removeMe = getParentByName(this.columns[i], "SPAN");
      removeMe.parentNode.removeChild(removeMe);
      Debug.println("Removed ", this.columns[i].id, "'s containing SPAN.");
    }
    this.columns.length = newIdx;
    if (this.options.onChooseFile != null) {
      this.options.onChooseFile(selectedFile.value);
    }
    Debug.println("Retrieving directory listing or file preview for selected value '", selectedFile.value, "'.");
    var url = FORM_ACTION_READ_ONLY + "?type=findercolumn_&root=" + encode_url_parameter(this.options.rootDirectory) + "&url=" + encode_url_parameter(fileList.value) + "&fileListName=" + encode_url_parameter(this.options.listIDRoot) + "&columnID=" + (parseInt(fileList.name.substring(this.options.listIDRoot.length), 10) + 1);
    this.request = xmlget_async(url, this.options.throbberImage, (this.options.throbberImage == null),
				this.onChooseFile_wakeup.bind(this), [ isFolder, newIdx, selectedFile.value ], -1);
  },
  
  onChooseFile_wakeup: function(responseText, args) {
    var isFolder = args[0];
    var newIdx = args[1];
    var testVal = args[2];
    var f = this;
    setTimeout(function() {
		 f.addColumn.bind(f)(responseText, f.options.listIDRoot + newIdx, isFolder, testVal);
	       }, 100);
  },
  jumpTo: function(listNum) {
    var list = document.getElementById(this.options.listIDRoot + listNum);
    if (list != null) {
      Debug.println("Jumping to list ", listNum);
      list.focus();
    }
  },
  eatEvent: function(event) {
    Debug.println("eating " + event);
    if (event.stopPropagation) event.stopPropagation();
    event.cancelBubble = true;
    return false;
  },
  onKeyDown: function(event) {
    Debug.println("onKeyDown");
    var code = keyCode2(event);
    switch (code) {
    case 37:
    case 39:
      this.lastKeyWasLeftArrow  = (code == 37);
      this.lastKeyWasRightArrow = (code == 39);
      if (event.stopPropagation) event.stopPropagation();
      event.cancelBubble = true;
      return false;
    default:
      return true;
    }
  },
  onKeyUp: function(event) {
    Debug.println("onKeyUp");
    var code = keyCode2(event);
    switch (code) {
    case 37:
    case 39:
      if (event.stopPropagation) event.stopPropagation();
      event.cancelBubble = true;
      return false;
    default:
      return true;
    }
  },
  onKeyPress: function(event) {
    Debug.println("onKeyPress");
    var code = keyCode2(event);
    var fileList = (typeof(event.target) == "undefined") ? event.srcElement : event.target;
    var listNum = parseInt(fileList.name.substring(this.options.listIDRoot.length), 10);
    if (code == 37) { // left arrow
      this.jumpTo(listNum - 1);
      if (event.stopPropagation) event.stopPropagation();
      event.cancelBubble = true;
      this.lastKeyWasLeftArrow  = false;
      this.lastKeyWasRightArrow = false;
      return false;
    }
    else if (code == 39) { // right arrow
      this.jumpTo(listNum + 1);
      if (event.stopPropagation) event.stopPropagation();
      event.cancelBubble = true;
      this.lastKeyWasLeftArrow  = false;
      this.lastKeyWasRightArrow = false;
      return false;
    }
    return true;
  }
};

//------------------------------------------------------------------------
// traction.util.PingServer

Util.PingServer = Class.create();
Util.PingServer.STATUS_UNKNOWN     = -1;
Util.PingServer.STATUS_UNREACHABLE = 0;
Util.PingServer.STATUS_ONLINE      = 1;
Util.PingServer.prototype = {
  
  
  throbber: null,
  throbberState: null,
  
  statusArea: null,
  
  delay: 1000,
  
  maxAttempts: 1,
  
  failedPingCallbackFunction: null,
  
  finalCallbackFunction: null,
  
  finalCallbackArgs: null,
  
  skipWarnings: false,
  
  
  lastPingTime: 0,
  
  attempts: 0,
  
  serverStatus: Util.PingServer.STATUS_UNKNOWN,
  
  initialize: function(throbber, statusArea,
		       delay, maxAttempts, skipWarnings,
		       failedPingCallbackFunction, finalCallbackFunction, finalCallbackArgs) {
    this.throbber   = throbber;
    this.statusArea = statusArea;
    this.delay        = delay;
    this.maxAttempts  = maxAttempts;
    this.skipWarnings = skipWarnings;
    this.failedPingCallbackFunction = failedPingCallbackFunction;
    this.finalCallbackFunction      = finalCallbackFunction;
    this.finalCallbackArgs          = finalCallbackArgs;
  },
  reset: function() {
    this.attempts     = 0;
    this.lastPingTime = 0;
    this.serverStatus = Util.PingServer.STATUS_UNKNOWN;
  },
  start: function() {
    this.startThrobber();
    if (this.statusArea != null) {
      this.statusArea.innerHTML = "";
    }
    this.reset();
    this.pingServer();
  },
  saveThrobberSource: function() {
    if (this.throbber.src.indexOf("/images/loading_on.gif") != -1) {
      this.throbberState = this.throbber.src.replace("_on", "_off");
      return null;
    }
    this.throbberState = this.throbber.src;
    return "/images/loading_on.gif";
  },
  stopThrobber: function() {
    if (this.throbber == null) {
      return;
    }
    this.throbber.src = this.throbberState;
  },
  startThrobber: function() {
    if (this.throbber == null) {
      return;
    }
    var newSrc = this.saveThrobberSource();
    if (newSrc != null) {
      this.throbber.src = newSrc;
    }
  },
  pingServer: function() {
    var actualDelay = (new Date()).getTime() - this.lastPingTime;
    if (actualDelay < this.delay) {
      var pinger = this;
      setTimeout(function() {
		   pinger.pingServer();
		 },
		 this.delay - actualDelay + 250);// be conservative and require at least a 250ms wait time.
      return;
    }
    this.lastPingTime = (new Date()).getTime();
    xmlget_async("/images/pixel.gif&cacheid=" + (Math.floor(Math.random() * 1000000000)),
		 null,
		 false, 
		 this.pingServer_wakeup.bind(this),
		 null,
		 -1);
    this.attempts ++;
  },
  pingServer_wakeup: function(content) {
    if (wasUnreachable(content)) {
      this.serverStatus = Util.PingServer.STATUS_UNREACHABLE;
      if (this.attempts < this.maxAttempts) {
	this.pingServer();
	if (this.failedPingCallbackFunction) {
	  this.failedPingCallbackFunction();
	}
      } else {
	if (!this.skipWarnings) {
	  failureServerUnavailable();
	}
	this.stopThrobber();
	this.finalCallbackFunction(false, this.finalCallbackArgs);
      }
    } else {
      this.serverStatus = Util.PingServer.STATUS_ONLINE;
      this.finalCallbackFunction(true, this.finalCallbackArgs);
    }
  }
};

//------------------------------------------------------------------------
// traction.util.URL

Util.URL = Class.create();
Util.URL.prototype = {
  
  URL_COMPONENTS: null,
  scheme: "http",
  authority: {
    raw: null,
    host: null,
    port: 0
  },
  path: null,
  query: {
    raw: null,
    params: null
  },
  fragment: null,
  relative: false,
  initialize: function(urlspec) {
    this.URL_COMPONENTS = new RegExp("^(([^:/?#]+):)?(\\/\\/([^\\/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?");
    var parseResult = this.URL_COMPONENTS.exec(urlspec);
    this.scheme        = parseResult[2];
    this.authority.raw = parseResult[4];
    this.path          = parseResult[5];
    this.query.raw     = parseResult[7];
    this.fragment      = parseResult[9];
    if (this.authority.raw != null) {
      var portColon = this.authority.raw.indexOf(":");
      if (portColon != -1) {
	try {
	  this.authority.port = parseInt(this.authority.raw.substring(portColon + 1), 10);
	} catch (xcp) {
	}
	this.authority.host = this.authority.raw.substring(0, portColon);;
      } else {
	this.authority.host = this.authority.raw;
      }
      if (this.authority.port <= 0) {
	if (this.scheme == "https") {
	  this.authority.port = 443;
	} else {
	  this.authority.port = 80;
	}
      }
    }
    this.query.params = new Array();
    if (this.query.raw) {
      var nvList = this.query.raw.split(/&/);
      for (var i = 0; i < nvList.length; i ++) {
	this._setParamNameValuePair(nvList[i]);
      }
    }
  },
  _setParamNameValuePair: function(nextParam) {
    var eq = nextParam.indexOf("=");
    var name;
    var value;
    if (eq != -1) {
      name = nextParam.substring(0, eq);
      if (eq != nextParam.length) {
	value = nextParam.substring(eq + 1, nextParam.length);
      } else {
	value = "";
      }
    } else {
      name = nextParam;
      value = "";
    }
    name = decode_url_parameter(name);
    value = decode_url_parameter(value);
    var existingType = typeof(this.query.params[name]);
    if (existingType != "undefined" && existingType != "function") {
      this.query.params[name] += "," + decode_url_parameter(value);
    } else {
      this.query.params[name] = value;
    }
  },
  toString: function() {
    var str = "";
    str += "scheme: " + this.scheme + "\n";
    str += "host: " + this.authority.host + "\n";
    str += "port: " + this.authority.port + "\n";
    str += "path: " + this.path;
    for (var pn in this.query.params) {
      if (pn != "extend") {
	str += "\n  " + pn + ": " + this.query.params[pn];
      }
    }
    return str;
  }
};

//------------------------------------------------------------------------
// traction.util.SearchExpression

Util.SearchExpression = Class.create();
Util.SearchExpression.prototype = {
  search: null,
  find: null,
  parser: null,
  
  initialize: function(url) {
    this.search = url.query.params["search"];
    this.find   = url.query.params["find"];
  },
  getPrefixExpression: function() {
    return this.find;
  },
  getInfixExpression: function() {
    if (this.search == null && this.find != null) {
      this._computeInfix();
    }
    return this.search;
  },
  _computeInfix: function() {
    if (this.parser == null) {
      this.parser = new Util.FindExpressionParser();
    }
    this.search = this.parser.parse(this.find).getSearchExpression();
  }
};
Util.FindExpressionParser = Class.create();
Util.FindExpressionParser.prototype = {
  tokenizer: null,
  abstractSyntaxTree: null,
  lastToken: null,
  mode: -1,
  cursor: null,
  openParens: 0,
  acceptTypes: [ true, true, true, true, true, true ],
  inUse: false,
  initialize: function() {
    this.inUse = false;
  },
  parse: function(inputString) {
    if (this.inUse) {
      var fn = this.parse.bind(this);
      setTimeout(function() {
		   fn(inputString);
		 }, 100);
    }
    this.inUse = true;
    var abstractSyntaxTree = null;
    try {
      this.tokenizer = new Util.FindExpressionParser.Tokenizer(inputString);
      this.lastToken = this.tokenizer.nextToken();
      this.openParens = 0;
      abstractSyntaxTree = new Util.FindExpressionParser.AbstractSyntaxTree();
      this.acceptTypes = [ false, true, false, false, false, false ];
      while (this.lastToken.type != Util.FindExpressionParser.Token.TYPE_END_OF_INPUT) { // while more tokens
	var nextToken = this.tokenizer.nextToken();
	if (this.lastToken.type == Util.FindExpressionParser.Token.TYPE_LPAREN &&
	    nextToken.type == Util.FindExpressionParser.Token.TYPE_OPERAND) {
	  if (nextToken.text.match(/^[ \"_?\.!#;:@$%^&*\=\~\/\\|\(\)\[\]\{\}\<\>\+\-]+$/)) {
	    nextToken.type = Util.FindExpressionParser.Token.TYPE_OPERATOR;
	  }
	  else if (!nextToken.text.match(/[ \"?\,\.!#;:@$%^&*\=\~\/\\|\(\)\[\]\{\}\'\`\<\>\+\-]+/)) {
	    nextToken.type = Util.FindExpressionParser.Token.TYPE_OPERATOR;
	  }
	}
	switch (this.lastToken.type) { // switch on this.lastToken.type
	case Util.FindExpressionParser.Token.TYPE_BEGINNING_OF_INPUT:
	  if (nextToken.type == Util.FindExpressionParser.Token.TYPE_LPAREN) {
	    this.openParens ++;
	    abstractSyntaxTree.startExpression();
	  }
	  else {
	    throw ("Left parenthesis required to begin expression.  Instead found \"" + nextToken.text + "\".");
	  }
	  break;
	case Util.FindExpressionParser.Token.TYPE_LPAREN:
	  if (nextToken.type == Util.FindExpressionParser.Token.TYPE_OPERATOR) {
	    abstractSyntaxTree.addOperator(nextToken);
	  }
	  else {
	    throw ("Operator token required after left parenthesis to begin prefix expression.  Instead found \"" + nextToken.text + "\".");
	  }
	  break;
	case Util.FindExpressionParser.Token.TYPE_OPERATOR:
	  if (nextToken.type == Util.FindExpressionParser.Token.TYPE_OPERAND) {
	    abstractSyntaxTree.addOperand(nextToken);
	  }
	  else if (nextToken.type == Util.FindExpressionParser.Token.TYPE_LPAREN) {
	    this.openParens ++;
	    abstractSyntaxTree.startExpression();
	  }
	  else {
	    throw ("Operand token required after operator token.  Instead found \"" + nextToken.text + "\"");
	  }
	  break;
	case Util.FindExpressionParser.Token.TYPE_OPERAND:
	  if (nextToken.type == Util.FindExpressionParser.Token.TYPE_OPERAND) {
	    abstractSyntaxTree.addOperand(nextToken);
	  }
	  else if (nextToken.type == Util.FindExpressionParser.Token.TYPE_LPAREN) {
	    this.openParens ++;
	    abstractSyntaxTree.startExpression();
	  }
	  else if (nextToken.type == Util.FindExpressionParser.Token.TYPE_RPAREN) {
	    this.openParens --;
	    abstractSyntaxTree.endExpression();
	  }
	  else {
	    throw ("Another operand token or parenthesis token required after operator token.  Instead found \"" + nextToken.text + "\" after '" + this.lastToken.text + "'..");
	  }
	  break;
	case Util.FindExpressionParser.Token.TYPE_RPAREN:
	  if (nextToken.type == Util.FindExpressionParser.Token.TYPE_LPAREN) {
	    if (this.openParens >= 1) {
	      this.openParens ++;
	      abstractSyntaxTree.startExpression();
	    } else {
	      throw ("Final right parenthesis came too soon.");
	    }
	  }
	  else if (nextToken.type == Util.FindExpressionParser.Token.TYPE_RPAREN) {
	    if (this.openParens > 0) {
	      this.openParens --;
	      abstractSyntaxTree.endExpression();
	    } else {
	      throw ("Found unbalanced right parenthesis.");
	    }
	  }
	  else if (nextToken.type == Util.FindExpressionParser.Token.TYPE_OPERAND) {
	    abstractSyntaxTree.addOperand(nextToken);
	  }
	  else if (nextToken.type == Util.FindExpressionParser.Token.TYPE_END_OF_INPUT) {
	    if (this.openParens > 0) {
	      throw ("Found unbalanced left parenthesis (end of input came too soon).");
	    }
	  }
	  else {
	    throw ("Another operand token or another parenthesis token required after right parenthesis.  Instead found \"" + nextToken.text + "\".");
	  }
	  break;
	}
	this.lastToken = nextToken;
      }
    } catch (anything) {
      this.inUse = false;
      Debug.println(anything);
      throw anything;
    }
    this.inUse = false;
    return abstractSyntaxTree;
  }
};
Util.FindExpressionParser.Tokenizer = Class.create();
Util.FindExpressionParser.Tokenizer.prototype = {
  inputString: "",
  scanPosition: -1,
  len: 0,
  _next: null,
  initialize: function(inputString) {
    Debug.println("Tokenizing \"", inputString, "\"");
    this._next = new Util.FindExpressionParser.Token("(beginning of input)", Util.FindExpressionParser.Token.TYPE_BEGINNING_OF_INPUT);
    this.scanPosition = 0;
    this.len = (inputString != null) ? inputString.length : 0;
    this.inputString = inputString;
  },
  nextToken: function() {
    var ret = this._next;
    this._findNextToken();
    return ret;
  },
  _findNextToken: function() {
    var found = false;
    while (!found && this.scanPosition < this.len) {
      var c = this._nextChar();
      switch (c) {
      case "(":
	found = true;
	this._next = new Util.FindExpressionParser.Token(c, Util.FindExpressionParser.Token.TYPE_LPAREN);
	break;
      case ")":
	found = true;
	this._next = new Util.FindExpressionParser.Token(c, Util.FindExpressionParser.Token.TYPE_RPAREN);
	break;
      case " ":
	found = false;
	break;
      default:
	found = true;
	var str = new String(c);
	var stop = false;
	var quoted = false;
	while (!stop && this._hasMoreInput()) {
	  c = this._nextChar();
	  if (c == "\"") {
	    quoted = !quoted;
	    str += c;
	  }
	  else if (c == " ") {
	    if (quoted) {
	      str += c;
	    } else {
	      stop = true;
	    }
	  }
	  else if (c == "(" || c == ")") {
	    if (quoted) {
	      str += c;
	    } else {
	      this.scanPosition --; // have to roll back cursor, unlike in the case of spaces
	      stop = true;
	    }
	  }
	  else {
	    str += c;
	  }
	}
	this._next = new Util.FindExpressionParser.Token(str, Util.FindExpressionParser.Token.TYPE_OPERAND);
      }
    }
    if (!found) {
      this._next = new Util.FindExpressionParser.Token("(end of input)", Util.FindExpressionParser.Token.TYPE_END_OF_INPUT);
    }
    Debug.println("Token \"", this._next.text, "\"");
  },
  _hasMoreInput: function() {
    return (this.scanPosition < this.len);
  },
  _nextChar: function() {
    var ret = this.inputString.charAt(this.scanPosition);
    this.scanPosition ++;
    return ret;
  }
};
Util.FindExpressionParser.Token = Class.create();
Util.FindExpressionParser.Token.prototype = {
  text: null,
  type: -1,
  initialize: function(text, type) {
    this.text = text;
    this.type = type;
  }
};
Util.FindExpressionParser.Token.TYPE_BEGINNING_OF_INPUT = 0;
Util.FindExpressionParser.Token.TYPE_LPAREN             = 1;
Util.FindExpressionParser.Token.TYPE_RPAREN             = 2;
Util.FindExpressionParser.Token.TYPE_OPERATOR           = 3;
Util.FindExpressionParser.Token.TYPE_OPERAND            = 4;
Util.FindExpressionParser.Token.TYPE_END_OF_INPUT       = 5;
Util.FindExpressionParser.AbstractSyntaxTree = Class.create();
Util.FindExpressionParser.AbstractSyntaxTree.prototype = {
  root: null,
  cursor: null,
  
  initialize: function() {
    this.root = new Util.FindExpressionParser.AbstractSyntaxTreeNode(Util.FindExpressionParser.AbstractSyntaxTreeNode.TYPE_ROOT);
    this.cursor = this.root;
  },
  
  startExpression: function() {
    var newExpression = new Util.FindExpressionParser.AbstractSyntaxTreeNode(Util.FindExpressionParser.AbstractSyntaxTreeNode.TYPE_CONTAINER);
    this.cursor.addChild(newExpression);
    this.cursor = newExpression;
  },
  
  endExpression: function() {
    this.cursor = this.cursor.parent;
  },
  
  addOperand: function(operandToken) {
    var newOperand = new Util.FindExpressionParser.AbstractSyntaxTreeNode(Util.FindExpressionParser.AbstractSyntaxTreeNode.TYPE_OPERAND, operandToken.text);
    this.cursor.addChild(newOperand);
  },
  
  addOperator: function(operatorToken) {
    this.cursor.type = Util.FindExpressionParser.AbstractSyntaxTreeNode.TYPE_OPERATOR;
    this.cursor.text = operatorToken.text;
  },
  getSearchExpression: function() {
    return this.root.visitInfix();
  }
};
Util.FindExpressionParser.AbstractSyntaxTreeNode = Class.create();
Util.FindExpressionParser.AbstractSyntaxTreeNode.prototype = {
  type: -1,
  text: null,
  children: null,
  parent: null,
  
  indexInParentList: -1,
  initialize: function(type, text) {
    this.type = type;
    this.text = text;
    this.children = new Array();
  },
  addChild: function(node) {
    this.children.push(node);
    node.parent = this;
  },
  visitInfix: function() {
    var ret = "";
    switch (this.type) {
    case Util.FindExpressionParser.AbstractSyntaxTreeNode.TYPE_ROOT:
      ret = "(" + this.children[0].visitInfix() + ")";
      break;
    case Util.FindExpressionParser.AbstractSyntaxTreeNode.TYPE_OPERATOR:
      if (this.children.length == 1) {
	ret = this.text;
	if (this.children[0].type == Util.FindExpressionParser.AbstractSyntaxTreeNode.TYPE_OPERATOR) {
	  ret += this.children[0].visitInfix();
	} else {
	  ret += "(" + this.children[0].visitInfix() + ")";
	}
      }
      else if (this.children.length == 2) {
	ret = "(" + this.children[0].visitInfix() + " " + this.text + " " + this.children[1].visitInfix() + ")";
      }
      else {
	throw ("Cannot form infix expression for an operator that has more than 2 operands.");
      }
      break;
    case Util.FindExpressionParser.AbstractSyntaxTreeNode.TYPE_OPERAND:
      if (this.isLabelOperand()) {
	ret += ":" + this.text;
      } else {
	ret += this.text;
      }
      break;
    }
    return ret;
  },
  isLabelOperand: function() {
    var p = this.parent;
    while (p != null && p.type != Util.FindExpressionParser.AbstractSyntaxTreeNode.TYPE_OPERATOR) {
      p = p.parent;
    }
    if (p != null) {
      if (p.text == "c" || p.text == "C" || p.text == "s" || p.text == "t" || p.text == "n") {
	return false;
      }
      return true;
    }
    return false;
  }
};
Util.FindExpressionParser.AbstractSyntaxTreeNode.TYPE_ROOT      = -1;
Util.FindExpressionParser.AbstractSyntaxTreeNode.TYPE_CONTAINER = 0;
Util.FindExpressionParser.AbstractSyntaxTreeNode.TYPE_OPERATOR  = 1;
Util.FindExpressionParser.AbstractSyntaxTreeNode.TYPE_OPERAND   = 2;

//------------------------------------------------------------------------
// traction.contextmenu.ContextMenu

Traction.ContextMenu = Class.create();
Traction.ContextMenu.prototype = {
  initialize: function(options) {
    this.setOptions(options);
    this.cache = new Array();
    this.root = new Traction.SubMenu({name: "*root*", styler: this.options.styler});
    this.isLoaded = false;
  },
  setOptions: function(options) {
    this.options = Object.extend({
      styler : Traction.DefaultMenuStyle
	  }, options || {});
  },
  load: function(callback) {
    this.show_(null, callback, false, false);
  },
  show: function(event) {
    this.show_(event, null, true, true);
  },
  show_: function(event, callback, makeVisible, doAsync) {
    var pt = Util.getMousePosition(event);
    var state = { pt: pt, curItem: curItem, makeVisible: makeVisible, callback: callback }
    if (cm.items != null) {
      this.build(cm.items, state);
    } else {
      var cached = this.cache[curItem.toLowerCase()];
      if (cached) {
	this.show_callback(cached, state);
      }
      else {
	if (this.xmlrpc != null && this.xmlrpc.readyState == 1) {
	  this.xmlrpc.abort();
	}
	var url = FORM_ACTION_READ_ONLY + "?type=ajaxcontextmenu&curtype="+((document.fm && document.fm.type) ? document.fm.type.value : "single")+"&curitem="+curItem;
	var tid = Traction.ID.parse(curItem);
	if (tid != null) {
	  url += "&proj="+encode_url_parameter(tid.project);
	}
	if (makeVisible) {
	  this.root.showLoading(state.pt.x-10, state.pt.y-10);
	}
	if (doAsync) {
	  this.xmlrpc = xmlget_async(url, null, true, this.show_callback.bind(this), state);
	} 
	else {
	  var responseText = xmlget(url, null);	  
	  this.show_callback(responseText, state)
	}
      }
    }
  },
  show_callback: function(responseText, state) {
    if (responseText == null) {
      failureServerUnavailable();
      this.hide();
    } else if (responseText == "") {
      failureUnauthorized();
      this.hide();
    } else {
      var error = findErrorAndFeedback(responseText);
      if (error[0] != null && error[0] != "" && error[0] != "undefined") {
	alert(error[0]);
	this.hide();
      } else {
	this.cache[state.curItem.toLowerCase()] = responseText;
	var items = eval(responseText);
	this.build(items, state);
      }
    }
    this.xmlrpc = null; // probably good to clear this, even if it's unncessary
  },
  build: function(items, state) {
    this.root.build(items);
    this.isLoaded = true;
    if (state.makeVisible) {
      this.root.show(state.pt.x-10, state.pt.y-10);
    }
    if (state.callback != null) {
      state.callback();
    }
  },
  hide: function() {
    this.root.hide();
  },
  hideNow: function() {
    this.root.hideNow();
  },
  isVisible: function() {
    return this.root.isVisible();
  },
  execute: function(event, action, item) {
    var me = this;
    this.load( function() { 
		 me.root.execute(event, action, item);
	       } );
  }
};

//------------------------------------------------------------------------
// traction.contextmenu.ItemHoverRight

Traction.ItemHoverRight = Class.create();
Traction.ItemHoverRight.prototype = {
  initialize: function(options) {
    if (typeof(options) == "undefined") {
      return;
    }
    this.setOptions(options);
    this.rect = new Rect(0,0,0,0);
    this.parts = {
      top:    this.style_part(document.createElement("DIV")),
      right:  this.style_part(document.createElement("DIV")),
      bottom: this.style_part(document.createElement("DIV")),
      left:   this.style_part(document.createElement("DIV")),
      middle: this.style_part(document.createElement("DIV")),
      itemid: this.style_part(document.createElement("DIV"))
    };
    this.build_parts();
    if (document.body) {
      document.body.appendChild(this.parts.top);
      document.body.appendChild(this.parts.right);
      document.body.appendChild(this.parts.bottom);
      document.body.appendChild(this.parts.left);
      document.body.appendChild(this.parts.middle);
      document.body.appendChild(this.parts.itemid);
    }
  },
  setOptions: function(options) {
    this.options = Object.extend({
      onMenuTabHover   : null,
      onMenuTabUnhover : null,
      onMenuTabClick   : null
	  }, options || {});
  },
  isValid: function() {
    return (this.parts.top    != null &&
	    this.parts.right  != null &&
	    this.parts.bottom != null &&
	    this.parts.middle != null &&
	    this.parts.left   != null);
  },
  gif_or_png: function() {
    return (ua("supports_png_transparency", "true") == "true") ? 'png' : 'gif';
  },
  create_middle: function() {
    var canReclassify = cm.perms.has(Traction.Permission.RECLASSIFY);
    var canComment = Traction.Permissions.has(cm.curItem, Traction.Permission.COMMENT);
    var canEdit = Traction.Permissions.has(cm.curItem, Traction.Permission.EDIT);
    var exactEntry = cm.exactEntry;
    Debug.println(cm.curItem+" - "+canComment);
    var mid = "";
    mid += '<table cellspacing="0" cellpadding="0" id="hoverctrls"><tr><td width="6"><img src="/images/hover/btn-lt.'+this.gif_or_png()+'" width="6" height="12"></td><td style="background: url(/images/hover/btn-mid.'+this.gif_or_png()+')">';
    if (!this.exactEntry && canReclassify) {
      mid += '<a onclick="cm.execute(event,\'Reclassify\',curItem); return true;" href="javascript:void(0)">' + i18n("hovermenu_label_link_text", "label") + '</a> &bull; ';
    }
    if (!this.exactEntry && canComment) {
      mid += '<a onclick="cm.execute(event,\'Comment\',curItem); return true;" href="javascript:void(0)">' + i18n("hovermenu_comment_link_text", "comment") + '</a> &bull; ';
    }
    if (canEdit) {
      mid += '<a onclick="cm.execute(event,\'' + (this.exactEntry ? 'UpdateExact' : 'Update') + '\',curItem); return true;" href="javascript:void(0)">' + i18n("hovermenu_edit_link_text", "edit") + '</a> &bull; ';
    }
    mid += '<a onclick="cm.show(event,curItem); return true;" href="javascript:void(0)">' + i18n("hovermenu_more_link_text", "more") + '<img border="0" style="margin: 0 0 1px 2px" src="/images/dropdownmask.gif"></a>';
    mid += '</td><td width="6"><img src="/images/hover/btn-rt.'+this.gif_or_png()+'" width="6" height="12"></td></tr></table>';    
    this.parts.middle.innerHTML = mid;
    with (this.parts.middle) {
      style.fontFamily = "Arial"; 
      style.fontSize = "7pt";
      style.color = "#fff";
    }
  },
  build_parts: function() {
    this.parts.top.innerHTML = '<table width="100%" cellpadding="0" cellspacing="0"><tr><td><image src="/images/tl.gif"></td><td style="border-top: 2px solid #ccc;"><img src="/images/modern/px.gif"></td><td style="border-top: 2px solid #ccc; border-left: 1px solid #ccc; background-color: #eee;"><img src="/images/modern/px.gif" width="5" height="1"></td><td><image src="/images/tr.gif"></td></tr></table>';
    this.parts.right.innerHTML = '<img style="padding: 5px 0 0 1px;" src="/images/dropdown.gif">';
    this.parts.bottom.innerHTML = '<table width="100%" cellpadding="0" cellspacing="0"><tr><td><image src="/images/bl.gif"></td><td style="border-bottom: 2px solid #ccc;"><img src="/images/modern/px.gif"></td><td style="border-bottom: 2px solid #ccc; border-left: 1px solid #ccc; background-color: #eee;"><img src="/images/modern/px.gif" width="5" height="1"></td><td><img src="/images/br.gif" width="6" height="6"></td></tr></table>';
    this.parts.left.innerHTML = '<img src="/images/modern/px.gif">';
    this.create_middle();
    this.tr = this.parts.top.getElementsByTagName("IMG")[3];
    this.br = this.parts.bottom.getElementsByTagName("IMG")[3];
    this.trtd = this.parts.top.getElementsByTagName("TD")[2];
    this.brtd = this.parts.bottom.getElementsByTagName("TD")[2];
    with (this.parts.right) {
      style.border = "solid #ccc";
      style.borderWidth = "0px 2px 0px 1px";
      style.backgroundColor = "#eee";
      style.cursor = ua("css_cursor_property_pointer_value", "pointer");
      title = i18n("contextmenu_dropdown_title_tip", "Show Menu");
    }
    with (this.parts.left) {
      style.borderLeft = "2px solid #ccc";
    }
    with (this.parts.itemid) {
      style.zIndex = 19;
      style.backgroundColor = "transparent";
      style.fontFamily = "Arial"; 
      style.fontSize = "7pt";
      style.color = "#999";
    }
    if (this.isValid()) {
      with (this.parts) {
	right.onmouseover = this.menuTabHover.bindAsEventListener(this);
	right.onmouseout = this.menuTabUnhover.bindAsEventListener(this);
	right.onclick = this.menuTabClick.bindAsEventListener(this);
	right.oncontextmenu = this.menuTabClick.bindAsEventListener(this);
      }
      this.fader = new Traction.Fader( [ this.parts.top, 
					 this.parts.right, 
					 this.parts.bottom, 
					 this.parts.left ],
				       { start: 0, finish: 100, step: 5, nofade: [ this.parts.itemid, this.parts.middle ] });    
    }
  },
  style_part: function(div) {
    with (div) {
      style.zIndex = 20;
      style.visibility = "hidden";
      style.display = "";
      style.position = "absolute";
    }
    return div;
  },
  isMouseInside: function(event) {
    return rect.isMouseInside(event);
  },
  setBounds: function(rect) {
    if (!this.rect.equals(rect)) {
      this.rect = rect;
      this.fader.reset();
    }
  },
  setItemId: function(itemid, exactEntry) {
    if (typeof(exactEntry) != "undefined") {
      this.exactEntry = exactEntry;
    }
    if (this.itemid != itemid) {
      this.itemid = itemid;
      this.create_middle();
    }
  },
  isVisible: function() {
    return (this.parts.top.style.visibility != "hidden");
  },
  hide: function() {
    this.fader.reset();
    with (this.parts) {
      top.style.visibility = "hidden";
      right.style.visibility = "hidden";
      bottom.style.visibility = "hidden";
      middle.style.visibility = "hidden";
      left.style.visibility = "hidden";
      itemid.style.visibility = "hidden";
    }    
  },
  getMargins: function() {
    return { top: -4, right: 1, bottom: -4, left: 4 }; 
  },
  getThicks: function() {
    return { top: 6, right: 9, bottom: 6, left: 2 };
  },
  getItemIdExtra: function() {
    return 0;
  },
  getExtraPx: function() {
    return 0;
  },
  show: function(event) {
    var top = this.rect.y;
    var left = this.rect.x;
    var width = this.rect.w;
    var height = this.rect.h;
    var margin = this.getMargins();
    var thick = this.getThicks();
    var selwidth = width + margin.left + margin.right + thick.left + thick.right + 3; // 3px for right borders
    var selheight = height + margin.top + margin.bottom;
    var idstr = "";
    var entrystr = "";
    if (this.itemid != null) {
      var period = this.itemid.lastIndexOf('.');
      idstr = (period != -1) ? this.itemid.substring(period+1) : "00";
      entrystr = (period != -1) ? this.itemid.substring(0,period) : this.itemid;
    }
    Debug.println(": "+left+","+top+" ("+width+"x"+height+") ["+idstr+"]");
    this.style_tab_unhover();
    with (this.parts.top) {
      style.left = px(left - margin.left - thick.left);
      style.top = px(top - thick.top - margin.top);
      style.width = px(selwidth);
      style.height = px(thick.top);
    }
    this.size_table_(this.parts.top, 6, selwidth, thick);
    with (this.parts.bottom) {
      style.left = px(left - margin.left - thick.left);
      style.top = px(top + height + margin.bottom);
      style.width = px(selwidth);
      style.height = px(thick.bottom);
    }
    var midbounds = Util.getElementBounds(this.parts.middle);
    this.size_table_(this.parts.bottom, 6, selwidth, thick);
    with (this.parts.right) {
      style.left = px(left + width + margin.right + this.getExtraPx());
      style.top = px(top - margin.top);
      style.width = px(thick.right);
      style.height = px(selheight);
    }
    with (this.parts.left) {
      style.left = px(left - margin.left - thick.left);
      style.top = px(top - margin.top);
      style.width = px(thick.left);
      style.height = px(selheight);
    }    
    with (this.parts.middle) {
      style.left = px(left  + (width/2) - (midbounds.w/2));
      Debug.println("checking for "+entrystr+"c");
      if (idstr == "00" && document.getElementById(entrystr.toLowerCase()+"c") == null) {
	style.top = px(top - (midbounds.h/2) - 1);
      } else {
	style.top = px(top + height - (midbounds.h/2) + 1);
      }
    }    
    if (idstr != "") {
      if (idstr == "00") {
	this.parts.itemid.innerHTML = "";
      } else {
	this.parts.itemid.innerHTML = '<span style="padding: 1px" title="' +
	(new MessageFormat(i18n("contextmenu_itemid_title_tip",
				"Link to this paragraph directly using '{0}'"))).format(this.itemid) +
	'">'+idstr+'</span>';
      }
      var itemid_bounds = Util.getElementBounds(this.parts.itemid);
      with (this.parts.itemid) {
	style.left = px(left + width - itemid_bounds.w + 1 + this.getItemIdExtra());
	style.top = px(top + height - itemid_bounds.h + 1);
      }
    }
    this.fader.fade();
  },
  size_table_: function(div, imgsz, selwidth, thick) {
    var tables = div.getElementsByTagName('TABLE');
    if (tables.length > 0) {
      var rows = tables[0].getElementsByTagName('TR');
      if (rows.length > 0) {
	var tds = rows[0].getElementsByTagName('TD');
	if (tds.length == 4) {
	  tds[0].style.width = px(imgsz);
 	  tds[1].style.width = px(selwidth - 2*imgsz);
 	  tds[2].style.width = px(thick.right - imgsz + 2);
	  tds[3].style.width = px(imgsz);
	  tds[0].style.height = px(imgsz - 2);
	  tds[1].style.height = px(imgsz - 2);
	  tds[2].style.height = px(imgsz - 2);
	  tds[3].style.height = px(imgsz - 2);
	}
      }
    }
  },
  menuTabHover: function(event) {
    this.style_tab_hover(event);
    if (this.options.onMenuTabHover) {
      this.options.onMenuTabHover(event);
    }
  },
  menuTabUnhover: function(event) {
    this.style_tab_unhover(event);
    if (this.options.onMenuTabUnhover) {
      this.options.onMenuTabUnhover(event);
    }
  },
  style_tab_hover: function(event) {
    with (this.parts.right) {
      style.backgroundColor = "#ffeec2";
    }
    this.tr.src = "/images/tr_h.gif";
    this.br.src = "/images/br_h.gif";
    this.trtd.style.backgroundColor = "#ffeec2";
    this.brtd.style.backgroundColor = "#ffeec2";
  },
  style_tab_unhover: function(event) {
    with (this.parts.right) {
      style.backgroundColor = "#eee";      
    }
    this.tr.src = "/images/tr.gif";
    this.br.src = "/images/br.gif";
    this.trtd.style.backgroundColor = "#eee";
    this.brtd.style.backgroundColor = "#eee";    
  },
  menuTabClick: function(event) {
    if (this.options.onMenuTabClick) {
      this.options.onMenuTabClick(event);
      return false;
    } else {
      return true;
    }
  }
};

//------------------------------------------------------------------------
// traction.contextmenu.ItemHoverLeft

Traction.ItemHoverLeft = Class.create();
Traction.ItemHoverLeft.prototype = Object.extend(new Traction.ItemHoverRight(), {
  build_parts: function() {
    this.parts.top.innerHTML = '<table width="100%" cellpadding="0" cellspacing="0"><tr><td><image src="/images/tl1.gif"></td><td style="border-top: 2px solid #ccc; border-right: 1px solid #ccc; background-color: #eee;"><img src="/images/modern/px.gif" width="5" height="1"></td><td style="border-top: 2px solid #ccc;"><img src="/images/modern/px.gif"></td><td><image src="/images/tr1.gif"></td></tr></table>';
    this.parts.right.innerHTML = '<img src="/images/modern/px.gif">';
    this.parts.bottom.innerHTML = '<table width="100%" cellpadding="0" cellspacing="0"><tr><td><image src="/images/bl1.gif"></td><td style="border-bottom: 2px solid #ccc; border-right: 1px solid #ccc; background-color: #eee;"><img src="/images/modern/px.gif" width="5" height="1"></td><td style="border-bottom: 2px solid #ccc;"><img src="/images/modern/px.gif"></td><td><img src="/images/br1.gif" width="6" height="6"></td></tr></table>';
    this.parts.left.innerHTML = '<img style="padding: 5px 0 0 1px;" src="/images/dropdown.gif">';
    this.create_middle();
    this.tr = this.parts.top.getElementsByTagName("IMG")[0];
    this.br = this.parts.bottom.getElementsByTagName("IMG")[0];
    this.trtd = this.parts.top.getElementsByTagName("TD")[1];
    this.brtd = this.parts.bottom.getElementsByTagName("TD")[1];
    with (this.parts.left) {
      style.border = "solid #ccc";
      style.borderWidth = "0px 1px 0px 2px";
      style.backgroundColor = "#eee";
      style.cursor = ua("css_cursor_property_pointer_value", "pointer");
      title = i18n("contextmenu_dropdown_title_tip", "Show Menu");
    }
    with (this.parts.right) {
      style.borderRight = "2px solid #ccc";
    }
    with (this.parts.itemid) {
      style.zIndex = 19;
      style.backgroundColor = "transparent";
      style.fontFamily = "Arial"; 
      style.fontSize = "7pt";
      style.color = "#999";
    }
    if (this.isValid()) {
      with (this.parts) {
	left.onmouseover = this.menuTabHover.bindAsEventListener(this);
	left.onmouseout = this.menuTabUnhover.bindAsEventListener(this);
	left.onclick = this.menuTabClick.bindAsEventListener(this);
	left.oncontextmenu = this.menuTabClick.bindAsEventListener(this);
      }
      this.fader = new Traction.Fader( [ this.parts.top, 
					 this.parts.right, 
					 this.parts.bottom,
					 this.parts.left ],
				       { start: 0, finish: 100, step: 5, nofade: [ this.parts.itemid, this.parts.middle ] });    
    }
  },
  getMargins: function() {
    return { top: -4, right: 4, bottom: -4, left: 5 }; 
  },
  getThicks: function() {
    return { top: 6, right: 2, bottom: 6, left: 9 };
  },
  getItemIdExtra: function() {
    return 5;
  },
  getExtraPx: function() {
    return 1;
  },
  size_table_: function(div, imgsz, selwidth, thick) {
    var tables = div.getElementsByTagName('TABLE');
    if (tables.length > 0) {
      var rows = tables[0].getElementsByTagName('TR');
      if (rows.length > 0) {
	var tds = rows[0].getElementsByTagName('TD');
	if (tds.length == 4) {
	  tds[0].style.width = px(imgsz);
 	  tds[1].style.width = px(thick.left - imgsz + 2);
 	  tds[2].style.width = px(selwidth - 2*imgsz);
	  tds[3].style.width = px(imgsz);
	  tds[0].style.height = px(imgsz - 2);
	  tds[1].style.height = px(imgsz - 2);
	  tds[2].style.height = px(imgsz - 2);
	  tds[3].style.height = px(imgsz - 2);
	}
      }
    }
  },
  style_tab_hover: function(event) {
    with (this.parts.left) {
      style.backgroundColor = "#ffeec2";
    }
    this.tr.src = "/images/tl_h1.gif";
    this.br.src = "/images/bl_h1.gif";
    this.trtd.style.backgroundColor = "#ffeec2";
    this.brtd.style.backgroundColor = "#ffeec2";
  },
  style_tab_unhover: function(event) {
    with (this.parts.left) {
      style.backgroundColor = "#eee";      
    }
    this.tr.src = "/images/tl1.gif";
    this.br.src = "/images/bl1.gif";
    this.trtd.style.backgroundColor = "#eee";
    this.brtd.style.backgroundColor = "#eee";    
  }
  });

//------------------------------------------------------------------------
// traction.contextmenu.ItemMenu

Traction.ItemMenu = Class.create();
Traction.ItemMenu.prototype = {
  didSetupUnhover: false,
  initialize: function(options) {
    this.setOptions(options);
  },
  bindUnhover: function(id) {
    var div = $(id);
    if (div && div != null) {
      Events.attach(div, Events.MouseOver, this, this.unhover);
    }
  },
  setupUnhover: function() {
    if (!this.didSetupUnhover) {
      this.bindUnhover('left');
      this.bindUnhover('right');
      this.bindUnhover('middletop');    
      this.didSetupUnhover = true;
    }
  },
  setOptions: function(options) {
    this.options = Object.extend({
      showHover   : 1,
      effects : 1,
      styler : Traction.DefaultMenuStyle
	  }, options || {});
    if (this.options.effects == 0) {
      Traction.Fader.disabled = true;
      Traction.ZoomExpander.disabled = true;
    }
    switch (this.options.showHover) {
    case -1:
      this.selector = new Traction.ItemHoverLeft( { onMenuTabClick: this.showMenu.bind(this) } );
      break;
    case 0:
      this.selector = null;
      break;
    default:
    case 1:
      this.selector = new Traction.ItemHoverRight( { onMenuTabClick: this.showMenu.bind(this) } );
      break;
    case 2:
      this.selector = new Traction.ItemHoverNone( { } );
      break;
    }
    Events.attach(window, Events.Resize, this, this.onresize);
    Events.attachCustom(Events.DOMchanged, this, this.ondomchange);
  },
  setFocus: function(item, elm, bounds) {
    if (item != null) {
	this.item = item;
	this.elm = elm;
	this.bounds = bounds;
    } else {
      this.item = null;
      this.elm = null;
      this.bounds = null;
    }
  },
  ondomchange: function(event) {
    if (this.selector != null && this.item != null && this.elm != null) {
      this.hide();
    }
  },
  onresize: function(event) {
    if (this.selector != null && this.selector.isVisible() && // selector is showing
	this.item != null && this.elm != null) {
      this.bounds = this.compute_bounds_rect_(this.item, this.elm);
      this.show(event);
    }
  },
  hover: function(event, item, elm, exactEntry) {
    var bounds = this.compute_bounds_rect_(item, elm);
    if (bounds.isMouseInside(event)) {
      this.setFocus(item, elm, bounds);
      this.show(event, exactEntry);
      if (event.stopPropagation) event.stopPropagation();
      event.cancelBubble = true;
      return false;
    } else {
      Debug.println("Mouse not inside, ignoring hover event");
      this.unhover();
    }
    return true;
  },
  unhover: function() {
    this.setFocus(null);
    this.hide();
  },
  isTitleItem: function(item) {
    return item.endsWith('.00');
  },
  compute_bounds_rect_: function(item, elm) {
    var TR = elm;
    var top = 0;
    var left = 0;
    var width = 0;
    var height = 0;
    var isVanillaDIV = false;
    var eltBounds = Util.getElementBounds(elm);
    if (TR.tagName == "SPAN") {
      TD = TR;
      height = outer_height(TD);
    }
    else if (TR.tagName == "DIV" && TR.className == "commentdetails") {
      var div = TR;
      TD = getParentByName(TR, "TD");
      var tdbounds = Util.getElementBounds(TD);
      height = tdbounds.h;
      if (div.nextSibling) {
	var divbounds = Util.getElementBounds(div);
	height -= ((tdbounds.y + tdbounds.h) - (divbounds.y + divbounds.h));
      }
    }
    else if (elm.tagName == "TR")  {
      if (TR == null) {
	Debug.println("Couldn't find element to focus.");
	return;
      }
      var TD = TR.getElementsByTagName("TD")[0];
      var lastTR = null;
      if (TD != null && TD.className.startsWith("searchhit")) {
	var entrytable = getParentByName(TD, "TABLE");
	var TRs = entrytable.getElementsByTagName("TR");
	TR = TRs[0];
	lastTR = TRs[TRs.length-1];
	TD = TR.getElementsByTagName("TD")[0];
      }
    } else {
      isVanillaDIV = true;
      TD = TR;
      height = outer_height(TD);      
    }
    Debug.println(TD);
    top = gettop(TD);
    left = getleft(TD);
    width = outer_width(TD);
    if (this.isTitleItem(item)) {
      top += 1;
      if (TD.className == "smallPortletEntry") {
      }
      else if (TD.className.endsWith("PortletEntryTitle")) {
	if (TD.className == "mediumPortletEntryTitle") {
	} else {
	  var entrytable = getParentByName(TD, "TABLE");
	  var TRs = entrytable.getElementsByTagName("TR");
	  var i;
	  for (i=0; i<TRs.length; i++) {
	    if (TRs[i] == TR) break;
	  }
	  for (i++; i<TRs.length; i++) {
	    if (TRs[i].className.endsWith("PortletEntryItem")) {
	    } else {
	      lastTR = TRs[i];
	      break;
	    }
	  }
	}
      }
      else {
	if (lastTR == null) {
	  var entrytable = getParentByName(TD, "TABLE");
	  var TRs = entrytable.getElementsByTagName("TR");
	  for (var i=0; i<TRs.length; i++) {
	    if (TRs[i].className == "entryitem") {
	      lastTR = TRs[i];
	    }
	  }
	}
      }
      if (lastTR != null) {
	var firstTD = getFirstChildByName(lastTR, "TD"); // lastTR.firstChild;
	height = gettop(firstTD) + outer_height(firstTD) - top;
      }
    }
    if (height == 0) {
      height = outer_height(TD);
      var hasComments = false;
      var TABLEs = TD.getElementsByTagName("TABLE");
      for (var i=0; i<TABLEs.length; i++) {
	if (TABLEs[i].className == "commentouter" || TABLEs[i].className == "comment_ commentouter" ) {
	  if (Util.isDisplayed(TABLEs[i])) {
	    height = gettop(TABLEs[i]) - top;
	    hasComments = true;
	  }
	  break;
	}
      }
      if (!hasComments) {
	var FORMs = TD.getElementsByTagName("FORM");
	for (var i=0; i<FORMs.length; i++) {
	  if (FORMs[i].className == "entryaddcomment") {
	    height = gettop(FORMs[i]) - top - 3;
	    break;
	  }
	}
      }
    }    
    if (elm.tagName == "SPAN") {
      top -= 4;
      height += 8;
      var td = getParentByName(elm, "TD");
      if (td != null && td.className != null && td.className.indexOf("commentinner") != -1 ) {
	width = outer_width(td) - 9;
      } else if ( elm.className == "quotecontent") { // top title of transcluded comment target
	var ptd = getParentByName( elm, "TD");
	if ( ptd != null ) {
	  var p  = Util.getElementBounds(ptd);
	  top    = p.y - 4;
	  height = p.h + 8;
	  left   = p.x;
	  width  = p.w - 9;
	}
      } else {
	var entryDiv = getParentByName(elm, "DIV");
	if (entryDiv != null && entryDiv.className != null && entryDiv.className == "entry") {
	  width = outer_width(entryDiv) + 4;
	  height = outer_height(entryDiv) + 10;
	  left -= 2;
	  top -= 4;
	}
      } 
      left += 1;
    } else if (isVanillaDIV) {
      top -= 4;
      height += 8;
      left -= 3;
      width += 6;
    }
    return new Rect(left, top, width, height);
  },
  show: function(event, exactEntry) {
    if (this.item == null) return;
    if (this.selector != null) {
      this.setupUnhover();
      this.selector.setBounds(this.bounds);
      this.selector.setItemId(this.item, exactEntry);
      this.selector.show(event);
    }
  },
  showMenu: function(event) {
    setItem(this.item);
    if (this.contextMenu == null) {
      this.contextMenu = new Traction.ContextMenu({ styler: this.options.styler});
    }
    this.contextMenu.show(event);
  },
  hide: function() {
    if (this.contextMenu != null) {
      this.contextMenu.hideNow();
    }
    if (this.selector != null) {
      this.selector.hide();
    }
  },
  
  execute: function(event, action, item) {
    setItem(item);
    if (this.contextMenu == null) {
      this.contextMenu = new Traction.ContextMenu({ styler: this.options.styler});
    }
    this.contextMenu.execute(event, action, item);
  },
  clearCache: function() {
    if (this.contextMenu != null) {
      this.contextMenu.cache[curItem.toLowerCase()] = null;
    }
  },
  isMenuVisible: function() {
    return (this.contextMenu != null && this.contextMenu.isVisible());
  }
};

//------------------------------------------------------------------------
// traction.contextmenu.cm

var itemMenu = null;

var cm = {
  
  init: function(options) {
    cm.options = options;
    cm.perms = new Traction.PermissionSet(options.perms);
    if (ua_safe("operation_aborted_bug", false)) {
      Events.attach(window, Events.Load, cm, cm.init_);
      Debug.println("operation_aborted_bug, waiting");
    } else {
      Debug.println("no operation_aborted_bug, initializing");
      cm.init_();
    }
  },
  init_: function() {
    Debug.println("******************** cm.init_ ********************");
    itemMenu = new Traction.ItemMenu(cm.options);      
  },
  
  build: function(items) {
    this.items = items;
  },
  
  focus: function(item) {
    curItem = item;
    this.curItem = item;
  },
  
  show: function(event, item) {
    cm.focus(item);
    if (itemMenu != null) itemMenu.showMenu(event);
  },
  
  event: function(event, item, perms) {
    if (perms) Traction.Permissions.store(item, perms);
    cm.focus(item);
    if (showNormalContextMenu(event) || itemMenu == null) {
      return true;
    } else {
      itemMenu.showMenu(event);
      return false;
    }  
  },
  
  execute: function(event, action, item) {
    if (itemMenu != null) itemMenu.execute(event, action, item);
  },
  lock: function() {
    this.locked = true;
  },
  unlock: function() {
    this.locked = false;
  },
  isLocked: function() {
    return this.locked;
  },
  
  isMenuVisible: function() {
    return (itemMenu != null && itemMenu.isMenuVisible());
  },
  
  hover: function(event, item, elm, perms, exactEntry) {
    if (perms) Traction.Permissions.store(item, perms);
    if (!cm.isMenuVisible() && !cm.isLocked()) {
      cm.focus(item);
      if (itemMenu != null) itemMenu.hover(event, item, elm, exactEntry);
    }
  },
  
  hide: function() {
    if (itemMenu != null) itemMenu.hide();
  }
};

//------------------------------------------------------------------------
// traction.contextmenu.ItemHoverNone

Traction.ItemHoverNone = Class.create();
Traction.ItemHoverNone.prototype = Object.extend(new Traction.ItemHoverRight(), {
  build_parts: function() {
    this.parts.top.innerHTML = '<table width="100%" cellpadding="0" cellspacing="0"><tr><td><image src="/images/tl.gif"></td><td style="border-top: 2px solid #ccc;"><img src="/images/modern/px.gif" width="5" height="1"></td><td style="border-top: 2px solid #ccc;"><img src="/images/modern/px.gif"></td><td><image src="/images/tr1.gif"></td></tr></table>';
    this.parts.right.innerHTML = '<img src="/images/modern/px.gif">';
    this.parts.bottom.innerHTML = '<table width="100%" cellpadding="0" cellspacing="0"><tr><td><image src="/images/bl.gif"></td><td style="border-bottom: 2px solid #ccc;"><img src="/images/modern/px.gif" width="5" height="1"></td><td style="border-bottom: 2px solid #ccc;"><img src="/images/modern/px.gif"></td><td><img src="/images/br1.gif" width="6" height="6"></td></tr></table>';
    this.parts.left.innerHTML = '<img src="/images/modern/px.gif">';
    this.create_middle();
    this.tr = this.parts.top.getElementsByTagName("IMG")[0];
    this.br = this.parts.bottom.getElementsByTagName("IMG")[0];
    this.trtd = this.parts.top.getElementsByTagName("TD")[1];
    this.brtd = this.parts.bottom.getElementsByTagName("TD")[1];
    with (this.parts.left) {
      style.borderLeft = "2px solid #ccc";
    }
    with (this.parts.right) {
      style.borderRight = "2px solid #ccc";
    }
    with (this.parts.itemid) {
      style.zIndex = 19;
      style.backgroundColor = "transparent";
      style.fontFamily = "Arial"; 
      style.fontSize = "7pt";
      style.color = "#999";
    }
    if (this.isValid()) {
      this.fader = new Traction.Fader( [ this.parts.top, 
					 this.parts.right, 
					 this.parts.bottom, 
					 this.parts.left ],
				       { start: 0, finish: 100, step: 5, nofade: [ this.parts.itemid, this.parts.middle ] });    
    }
  },
  getMargins: function() {
    return { top: -4, right: 4, bottom: -4, left: 4 }; 
  },
  getThicks: function() {
    return { top: 6, right: 2, bottom: 6, left: 2 };
  },
  getItemIdExtra: function() {
    return 5;
  },
  getExtraPx: function() {
    return 1;
  },
  size_table_: function(div, imgsz, selwidth, thick) {
    var tables = div.getElementsByTagName('TABLE');
    if (tables.length > 0) {
      var rows = tables[0].getElementsByTagName('TR');
      if (rows.length > 0) {
	var tds = rows[0].getElementsByTagName('TD');
	if (tds.length == 4) {
	  tds[0].style.width = px(imgsz);
 	  tds[1].style.width = "";
 	  tds[2].style.width = "";
	  tds[3].style.width = px(imgsz);
	  tds[0].style.height = px(imgsz - 2);
	  tds[1].style.height = px(imgsz - 2);
	  tds[2].style.height = px(imgsz - 2);
	  tds[3].style.height = px(imgsz - 2);
	}
      }
    }
  },
  style_tab_hover: function(event) {},
  style_tab_unhover: function(event) {}
  });

//------------------------------------------------------------------------
// traction.contextmenu.items.ActionItem

Traction.ActionItem = Class.create();
Traction.ActionItem.prototype = Object.extend(new Traction.AbstractItem(), {
  initialize: function(label, icon, action) {
      this.label = label;
      this.icon = icon;
      this.action = action;
      if (this.icon == '') {
	this.icon = null;
      }
  },
  style_setup: function() {
    this.styler.style_action_setup(this);
  },
  style_on: function() {
    this.styler.style_action_on(this);
  },
  style_off: function() {
    this.styler.style_action_off(this);
  },
  execute: function(event) {
      this.action.execute(event, this);
  }
});

//------------------------------------------------------------------------
// traction.contextmenu.items.RecentItems

Traction.RecentItems = Class.create(); // singleton
Traction.RecentItems.prototype = {
  COOKIE_NAME: "recent",
  MAX_LABELS: 7,
  MAX_PROJECTS: 5,
  MAX_COLLECTIONS: 5,
  MAX_SECTIONS: 5,
  initialize: function() {
    this.load();
  },
  addProject: function(project) {
    this.add(this.projects, project, this.MAX_PROJECTS);
  },
  addLabel: function(project, label, icon) {
    this.add(this.labels, 
	     {
	         p:  project,
		 ln: label,
		 i:  (icon == 'undefined') ? null : icon
	     },
	     this.MAX_LABELS
	     );
  },
  addSection: function(project, group, section, addlabels, confirm) {
    this.add(this.sections,
	     {
	         p: project,
		 g: group,
		 s: section,
		 a: addlabels,
		 c: confirm ? '1' : '0'
	     },
	     this.MAX_SECTIONS);
  },
  addCollection: function(collection) {
    this.add(this.collections, collection, this.MAX_COLLECTIONS);
  },
  add: function(arr, obj, max) {
    for (var i=0; i<arr.length; i++) {
      if (this.itemequals(arr[i], obj)) {
	for (var j=i; j<arr.length-1; j++) {
	  arr[j] = arr[j+1];
	}
	arr.length -= 1;
	break;
      }
    }
    var stop = (arr.length < max) ? arr.length : max-1;
    for (var i=stop; i>0; i--) {
      arr[i] = arr[i-1];
    }
    arr[0] = obj;
  },
  itemequals: function(x, y) {
    if (typeof x == 'string') {
      return x==y;
    } else {
      for (property in x) {
	if (x[property] != y[property]) return false;
      }
      return true;
    }
  },
  getProjects: function() { return this.projects; },
  getLabels: function() { return this.labels; },
  getCollections: function() { return this.collections; },
  getSections: function() { return this.sections; },
  load: function() {
    var cookie = Util.Cookie.getCookie(this.COOKIE_NAME, true);
    if (cookie != null) {
      this.decode(cookie.value);
    }
    if (this.projects == null) {
      this.projects = new Array();
    }
    if (this.labels == null) {
      this.labels = new Array();
    }
    if (this.collections == null) {
      this.collections = new Array();
    }
    if (this.sections == null) {
      this.sections = new Array();
    }
  },
  save: function() {
    var cookie = new Util.Cookie(this.COOKIE_NAME, this.encode(), true);
    cookie.savePermanent();
  },
  encode: function() {
    var str = "{p:[";
    for (var i=0; i<this.projects.length; i++) {
      if (i!=0) str+=',';
      str += "'"+this.safe(this.projects[i])+"'";
    }
    str += "],l:[";
    for (var i=0; i<this.labels.length; i++) {
      if (i!=0) str+=',';
      var label = this.labels[i];
      str += "{ln:'"+this.safe(label.ln)+
	((label.i != null) ? "',i:'"+this.safe(label.i) : '')+
	"',p:'"+this.safe(label.p)+"'}";
    }
    str += "],s:[";
    for (var i=0; i<this.sections.length; i++) {
      if (i!=0) str+=',';
      var section = this.sections[i];
      str += 
	"{p:'"+this.safe(section.p)+
	"',g:'"+this.safe(section.g)+
	"',s:'"+this.safe(section.s)+
	"',a:'"+this.safe(section.a)+
	"',c:'"+this.safe(section.c)+"'}";
    }
    str += "],c:[";
    for (var i=0; i<this.collections.length; i++) {
      if (i!=0) str+=',';
      str += "'"+this.safe(this.collections[i])+"'";
    }
    str += "]}";
    return str;
  },
  safe: function(str) { return escape_singlequotes(encode_url_parameter(str)); },
  unsafe: function(str) { return decode_url_parameter(unescape_singlequotes(str)); },
  decode: function(str) {
    try {
      if (str != null) {
	var obj;
	try {
	  str = "("+str+")"; // for some reason, firefox wants ()
	  obj = eval(str);
	} catch (e) { Debug.println(e); throw e;}
	this.projects = obj.p;
 	this.labels = obj.l;
	this.collections = obj.c;
	this.sections = obj.s;
	if (this.projects != null) {
	  for (var i=0; i<this.projects.length; i++) {
	    this.projects[i] = this.unsafe(this.projects[i]);
	  }
	}
	if (this.labels != null) {
	  for (var i=0; i<this.labels.length; i++) {
	    this.labels[i].p = this.unsafe(this.labels[i].p);
	    this.labels[i].ln = this.unsafe(this.labels[i].ln);
	    if (this.labels[i].i == "null" || typeof(this.labels[i].i) == "undefined") {
	      this.labels[i].i = null;
	    }
	    else if (this.labels[i].i != null) {
	      this.labels[i].i = this.unsafe(this.labels[i].i);
	    }
	  }
	}
	if (this.sections != null) {
	  for (var i=0; i<this.sections.length; i++) {
	    this.sections[i].p = this.unsafe(this.sections[i].p);
	    this.sections[i].g = this.unsafe(this.sections[i].g);
	    this.sections[i].s = this.unsafe(this.sections[i].s);
	    this.sections[i].a = this.unsafe(this.sections[i].a);
	    this.sections[i].c = this.unsafe(this.sections[i].c);
	  }
	}
	if (this.collections != null) {
	  for (var i=0; i<this.collections.length; i++) {
	    this.collections[i] = this.unsafe(this.collections[i]);
	  }
	}
      }
    } catch (e) { 
      Debug.println("Decoding... "+str);
      Debug.println("Exception in Traction.RecentItems.decode(): "+e);
    }
  }
};
Traction.RecentItems.instance_ = null; // singleton
Traction.RecentItems.getInstance = function() {
  if (Traction.RecentItems.instance_ == null) {
    Traction.RecentItems.instance_ = new Traction.RecentItems();
  }
  return Traction.RecentItems.instance_;
};

//------------------------------------------------------------------------
// traction.contextmenu.items.AddLabelItem

Traction.AddLabelItem = Class.create();
Traction.AddLabelItem.prototype = Object.extend(new Traction.ActionItem(), {
  initialize: function(project, labelname, icon, label) {
      this.project = project;
      this.labelname = labelname;
      this.icon = icon;
      this.label = label ? label : labelname;
      var rs = new Traction.Label(project,labelname).getRapidSelectorFormat();
      this.action = new Traction.Reclassify(null, rs);
  },
  execute: function(event) {
      var recent = Traction.RecentItems.getInstance();
      recent.addProject(this.project);
      recent.addLabel(this.project, this.labelname, this.icon);
      recent.save();
      this.action.execute(event, this);
  }
});

//------------------------------------------------------------------------
// traction.contextmenu.items.AddLabelsProject

Traction.AddLabelsProject = Class.create();
Traction.AddLabelsProject.prototype = Object.extend(new Traction.AjaxItem(), {
  initialize: function(project) {
      this.project = project;
      this.label = project;
  },
  poststring: function() {
      var poststring = "";
      poststring = fm_append(poststring, "type=ajaxrpc");
      poststring = fm_append(poststring, "method=GetLabelsWithIcons");
      poststring = fm_append(poststring, "p0="+this.project);
      return poststring;      
  },
  getItems: function(responseText, state) {
      var ret = [];
      var labels = eval(responseText);
      for (var i=0; i<labels.length; i++) {
	ret[i] = new Traction.AddLabelItem(this.project, labels[i].ln, labels[i].i);
      }
      return ret;
    }
});

//------------------------------------------------------------------------
// traction.contextmenu.items.AddLabelsChoose

Traction.AddLabelsChoose = Class.create();
Traction.AddLabelsChoose.prototype = Object.extend(new Traction.AjaxItem(), {
    poststring: function() {
      var poststring = "";
      poststring = fm_append(poststring, "type=ajaxrpc");
      poststring = fm_append(poststring, "method=GetProjectNames");
      poststring = fm_append(poststring, "p0=reclassify");
      return poststring;
    }, 
    getItems: function(responseText, state) {
      var ret = [];
      var projects = eval(responseText);
      for (var i=0; i<projects.length; i++) {
	ret[i] = new Traction.AddLabelsProject(projects[i]);
      }
      return ret;
    }
});

//------------------------------------------------------------------------
// traction.contextmenu.items.AddLabels

Traction.AddLabels = Class.create();
Traction.AddLabels.prototype = Object.extend(new Traction.SubItem(), {
  initialize: function(label, icon, hasCurrentProjectReclassify) {
      this.label = label;
      this.icon = icon;
      if (this.icon == '') {
	this.icon = null;
      }
      this.hasCurrentProjectReclassify = hasCurrentProjectReclassify;
    },
  hover: function(event) {
      this.styler.style_subitem_on(this);
      this.items = new Array();
      if (this.hasCurrentProjectReclassify) {
	var tractionId = Traction.ID.parse(decode_url_parameter(curItem));
	if (tractionId != null) {
	  this.items.push(new Traction.AddLabelsProject(tractionId.project));
	}
      }
      var recent = Traction.RecentItems.getInstance();
      var labels = recent.getLabels();
      if (labels.length > 0) {
	this.items.push(new Traction.LabelItem(i18n("contextmenu_recent_labels_heading", "Recent Labels")));
	for (var i=0; i<labels.length; i++) {
	  this.items.push(new Traction.AddLabelItem(labels[i].p, labels[i].ln, labels[i].i, ":"+labels[i].p+":"+labels[i].ln));
	}
      }
      var projects = recent.getProjects();
      if (projects.length > 0) {
	this.items.push(new Traction.LabelItem(i18n("contextmenu_recent_projects_heading", "Recent Projects")));
	for (var i=0; i<projects.length; i++) {
	  this.items.push(new Traction.AddLabelsProject(projects[i]));
	}
      }
      this.items.push(new Traction.LabelItem(i18n("contextmenu_all_projects_heading", "All Projects")));
      this.items.push(new Traction.AddLabelsChoose(i18n("contextmenu_tearoff_choose_option", "Choose"),
						   "/images/modern/icons/ic_reclassify.gif"));
      this._SubItem_hover(event);
  }
});

//------------------------------------------------------------------------
// traction.contextmenu.items.AddToSection

Traction.AddToSection = Class.create();
Traction.AddToSection.prototype = Object.extend(new Traction.SubItem(), {
  initialize: function(label, icon) {
      this.label = label;
      this.icon = icon;
      if (this.icon == '') {
	this.icon = null;
      }
    },
  hover: function(event) {
      this.styler.style_subitem_on(this);
      this.items = new Array();
      var tractionId = Traction.ID.parse(decode_url_parameter(curItem));
      if (tractionId != null) {
	this.items.push(new Traction.AddToSectionProject(tractionId.project));
      }
      var recent = Traction.RecentItems.getInstance();
      var sections = recent.getSections();
      if (sections.length > 0) {
	this.items.push(new Traction.LabelItem(i18n("contextmenu_recent_sections_heading", "Recent Sections")));
	for (var i=0; i<sections.length; i++) {
	  this.items.push(new Traction.AddToSectionItem(sections[i].p, sections[i].g, sections[i].s, 
							sections[i].p+"/"+sections[i].g+"/"+sections[i].s,
							sections[i].l, sections[i].c == '1'));
	}
      }
      var projects = recent.getProjects();
      if (projects.length > 0) {
	this.items.push(new Traction.LabelItem(i18n("contextmenu_recent_projects_heading", "Recent Projects")));
	for (var i=0; i<projects.length; i++) {
	  this.items.push(new Traction.AddToSectionProject(projects[i]));
	}
      }
      this.items.push(new Traction.LabelItem(i18n("contextmenu_all_projects_heading", "All Projects")));
      this.items.push(new Traction.AddToSectionProject(null));
      this.items.push(new Traction.AddToSectionChoose(i18n("contextmenu_tearoff_choose_option", "Choose"),
						      "/images/modern/icons/ic_customize.gif"));
      this._SubItem_hover(event);
  }
});

//------------------------------------------------------------------------
// traction.contextmenu.items.AddToSectionChoose

Traction.AddToSectionChoose = Class.create();
Traction.AddToSectionChoose.prototype = Object.extend(new Traction.AjaxItem(), {
    poststring: function() {
      var poststring = "";
      poststring = fm_append(poststring, "type=ajaxrpc");
      poststring = fm_append(poststring, "method=GetProjectNames");
      poststring = fm_append(poststring, "p0=access");
      return poststring;
    }, 
    getItems: function(responseText, state) {
      var ret = [];
      var projects = eval(responseText);
      for (var i=0; i<projects.length; i++) {
	ret[i] = new Traction.AddToSectionProject(projects[i]);
      }
      return ret;
    }
});

//------------------------------------------------------------------------
// traction.contextmenu.items.AddToSectionItem

Traction.AddToSectionItem = Class.create();
Traction.AddToSectionItem.prototype = Object.extend(new Traction.ActionItem(), {
  initialize: function(project, group, section, label, addlabels, confirm) {
      this.project = project;
      this.group = group;
      this.section = section;
      this.label = (label != null) ? label : section;
      this.addlabels = addlabels;
      this.confirm = confirm;
      this.action = new Traction.Reclassify(null, addlabels, false, confirm=="1");
  },
  execute: function(event) {
      var recent = Traction.RecentItems.getInstance();
      if (this.project && this.project != null) {
	recent.addProject(this.project);
      }
      recent.save();
      this.action.execute(event, this);
  }
});

//------------------------------------------------------------------------
// traction.contextmenu.items.AddToSectionProject

Traction.AddToSectionProject = Class.create();
Traction.AddToSectionProject.prototype = Object.extend(new Traction.AjaxItem(), {
  initialize: function(project) {
      this.project = project;
      this.label = (project != null) ? project : i18n("Front_Page", "Front Page");
  },
  poststring: function() {
      var poststring = "";
      poststring = fm_append(poststring, "type=ajaxrpc");
      poststring = fm_append(poststring, "method=GetAddToSections");
      if (this.project != null) {
	poststring = fm_append(poststring, "p0="+this.project);
      }
      return poststring;      
  },
  getItems: function(responseText, state) {
      var ret = [];
      var results = eval(responseText);
      if (!results || !results.length) {
	ret[0] = new Traction.LabelItem(i18n("None", "None"));
      } else {
	if (results.length <= 1) {
	  for (var i=0; i<results.length; i++) {
	    var group = results[i];
	    var groupkey  = group.g;
	    var groupname = group.n;
	    if (!groupname) groupname = groupkey;
	    var grouplist = group.l;
	    var groupitems = [];
	    if (groupname) {
	      ret[ret.length] = new Traction.LabelItem(groupname);
	    }
	    for (var j=0; j<grouplist.length; j++) {
	      var section = grouplist[j];
	      ret[ret.length] = new Traction.AddToSectionItem(this.project, groupname, section.s, null, section.a, section.c);
	    }
	  }
	}
	else {
	  for (var i=0; i<results.length; i++) {
	    var group = results[i];
	    var groupkey  = group.g;
	    var groupname = group.n;
	    if (!groupname) groupname = groupkey;
	    var grouplist = group.l;
	    var groupitems = [];
	    for (var j=0; j<grouplist.length; j++) {
	      var section = grouplist[j];
	      groupitems[j] = new Traction.AddToSectionItem(this.project, groupname, section.s, null, section.a, section.c);
	    }
	    if (groupitems.length > 0) {
	      ret[i] = new Traction.SubItem(groupname, null, groupitems);
	    }
	  }
	}
      }
      return ret;
  }
});

//------------------------------------------------------------------------
// traction.contextmenu.items.CollectAdd

Traction.CollectAdd = Class.create();
Traction.CollectAdd.prototype = Object.extend(new Traction.SubItem(), {
  initialize: function(label, icon, isItem, currentCollection) {
      this.SubItem_initialize(label, icon, null);
      this.isItem = isItem;
      this.currentCollection = currentCollection;
  },
  execute: function(event) {
      this.hover(event);
    },
  hover: function(event) {
      this.styler.style_subitem_on(this);
      this.items = new Array();
      if (this.hasCurrentProjectReclassify) {
	var tractionId = Traction.ID.parse(decode_url_parameter(curItem));
	if (tractionId != null) {
	  this.items.push(new Traction.AddLabelsProject(tractionId.project));
	}
      }
      var collections = recent.getCollections();
      if (collections.length > 0) {
	this.items.push(new Traction.LabelItem(i18n("contextmenu_recent_projects_heading", "Recent Projects")));
	for (var i=0; i<projects.length; i++) {
	  this.items.push(new Traction.AddLabelsProject(projects[i]));
	}
      }
      this.items.push(new Traction.LabelItem(i18n("contextmenu_all_projects_heading", "All Projects")));
      this.items.push(new Traction.AddLabelsChoose(i18n("contextmenu_tearoff_choose_option", "Choose"),
						   "/images/modern/icons/ic_reclassify.gif"));
      this._SubItem_hover(event);
  }
}, "SubItem");

//------------------------------------------------------------------------
// traction.contextmenu.items.CollectTitle

Traction.CollectTitle = Class.create();
Traction.CollectTitle.prototype = Object.extend(new Traction.ActionItem(), {
  initialize: function(title, isItem) {
      this.ActionItem_initialize(title, "/images/modern/icons/ic_collector.gif", new Traction.Collect(title, isItem));
      this.title = title;
  },
  execute: function(event) {
      var recent = Traction.RecentItems.getInstance();
      recent.addCollection(this.title);
      recent.save();
      this.action.execute(event, this);
  }
}, "ActionItem");

//------------------------------------------------------------------------
// traction.contextmenu.items.CollectChoose

Traction.CollectChoose = Class.create();
Traction.CollectChoose.prototype = Object.extend(new Traction.AjaxItem(), {
    initialize: function(label, icon, isItem) {
      this.AjaxItem_initialize(label, icon, null);
      this.isItem = isItem;
    },
    poststring: function() {
      return Traction.AjaxRPC.poststring("GetCollectionNames");
    }, 
    getItems: function(responseText, state) {
      var ret = [];
      var collections = eval(responseText);
      for (var i=0; i<collections.length; i++) {
	ret[i] = new Traction.CollectTitle(collections[i], this.isItem);
      }
      return ret;
    }
}, "AjaxItem");

//------------------------------------------------------------------------
// traction.contextmenu.items.CollectCreate

Traction.CollectCreate = Class.create();
Traction.CollectCreate.prototype = Object.extend(new Traction.ActionItem(), {
  initialize: function(isItem) {
      this.ActionItem_initialize(i18n("contextmenu_new_collection_option", "New Collection") + "...", null, null);
      this.isItem = isItem;
  },
  execute: function(event) {
      var title = prompt(i18n("contextmenu_new_collection_prompt_message", "Type a Collection Name:"), "");
      if (title) {
	var recent = Traction.RecentItems.getInstance();
	recent.addCollection(title);
	recent.save();
	new Traction.Collect(title, this.isItem).execute(event, this);
      }
    }
}, "ActionItem");

//------------------------------------------------------------------------
// traction.contextmenu.items.CollectRoot

Traction.CollectRoot = Class.create();
Traction.CollectRoot.prototype = Object.extend(new Traction.SubItem(), {
  initialize: function(label, icon, isItem, currentCollection) {
      this.SubItem_initialize(label, icon, null);
      this.isItem = isItem;
      this.currentCollection = currentCollection;
  },
  execute: function(event) {
      this.hover(event);
    },
  hover: function(event) {
      this.styler.style_subitem_on(this);
      this.items = new Array();
      if (this.currentCollection != null) {
	this.items.push(new Traction.CollectTitle(this.currentCollection, this.isItem));
      }
      var recent = Traction.RecentItems.getInstance();
      var collections = recent.getCollections();
      if (collections.length > 0) {
	this.items.push(new Traction.LabelItem(i18n("contextmenu_recent_collections_heading", "Recent Collections")));
	for (var i=0; i<collections.length; i++) {
	  this.items.push(new Traction.CollectTitle(collections[i], this.isItem));
	}
      }
      this.items.push(new Traction.LabelItem(i18n("contextmenu_all_collections_heading", "All Collections")));
      this.items.push(new Traction.CollectChoose(i18n("contextmenu_tearoff_choose_option", "Choose"),
						 "/images/modern/icons/ic_collector.gif", this.isItem));
      this.items.push(new Traction.CollectCreate(this.isItem));
      this._SubItem_hover(event);
  }
}, "SubItem");

//------------------------------------------------------------------------
// traction.edit.Edit


Traction.Edit = {
  
  AUTOSAVE_INTERVAL_MS: 20000,
  
  AUTOSAVE_FIELD_NAMES: {
    "edit_title": false,
    "edit_content": true,
    "edit_project": false,
    "edit_labels": false,
    "edit_attachments": false,
    "edit_verbs": false,
    "edit_targets": false,
    "edit_sections_encoded": false,
    "edit_names_encoded": false,
    "edit_email_header_volume": false,
    "edit_email_format": false,
    "edit_entrystyle": false,
    "edit_edit_desc": false,
    "edit_email_from": false,
    "edit_email_to": false,
    "edit_email_cc": false,
    "edit_email_bcc": false,
    "edit_textonly": false,
    "edit_email_include_links": false
  },
  
  USER_PREF_USE_RICH_TEXT: true,
  
  NAME_STATE_DATA_VIEW_TYPE: "entrynames",
  
  projname2naminginfo: new Array(),
  
  projname2publishinfo: new Array(),
  
  projname2addattachments: new Array(),
  
  projname2entrystyle: new Array()
};

//------------------------------------------------------------------------
// traction.edit.dialogs.Dialogs

Traction.Edit.Dialogs = { };

//------------------------------------------------------------------------
// traction.edit.dialogs.Attachments


Traction.Edit.Dialogs.Attachments = {
  
  attachmentData: new Array(),
  
  attachmentIDs: new Array(),
  
  originalAttachmentData: new Array(),
  
  originalAttachmentIDs: new Array(),
  
  garbageData: new Array(),
  
  garbageIDs: new Array(),
  
  selectedAttachments: new Array(),
  
  attachmentSelector: null,
  
  startAttachmentNumber: 1,
  
  showMoreRowsClickCount: 0,
  
  renumberFiles: false,
  
  
  initView: function(forConfirmation) {
    Traction.Edit.Dialogs.Attachments.initAttachmentData();
    if (forConfirmation) {
      Traction.Edit.Dialogs.Attachments.updateOpener();
      setTimeout(function() {
		   window.close();
		 }, 1000);
    } else {
      Traction.Edit.Dialogs.Attachments.attachmentSelector = document.fm.selectattachments;
      Traction.Edit.Dialogs.Attachments.attachmentsSelected();
      Traction.Edit.Dialogs.Attachments.initRemoveButton();
      if (Traction.Edit.Dialogs.Attachments.attachmentSelector) {
	Traction.Edit.Dialogs.Attachments.attachmentSelector.disabled = false;
      }
      Traction.Edit.Dialogs.Attachments.initClickHandlers();
    }
  },
  initClickHandlers: function() {
    for (var i = 0; i < Traction.Edit.Dialogs.Attachments.attachmentIDs.length; i ++) {
      var row = document.getElementById("attach"+i);
      row.onclick = Traction.Edit.Dialogs.Attachments.attachRowClick.bind(row);
      var chk = document.getElementById("attach"+i+"chk");
      chk.onclick = Traction.Edit.Dialogs.Attachments.attachChkClick.bind(chk);
    }
  },
  
  initAttachmentSelector: function() {
    var newOptionIdx = 0;
    for (var i = 0; i < Traction.Edit.Dialogs.Attachments.attachmentIDs.length; i ++) {
      var attachment = Traction.Edit.Dialogs.Attachments.attachmentData[Traction.Edit.Dialogs.Attachments.attachmentIDs[i]];
      var row = document.getElementById("attach"+i);
      if (attachment.removed) {
	row.style.display = "none";
      } else {
	newOptionIdx++;
	var number = attachment.number+".";
	var td = getFirstChildByName(row,"TD");
	if (td.innerHTML != number) {
	  td.innerHTML = number;
	}
	if (Traction.Edit.Dialogs.Attachments.isSelected(attachment)) {
	  row.className = "selected";
	} else {
	  row.className = (newOptionIdx % 2 == 0) ? "even" : "odd";
	}
      }
    }  
  },
  initRemoveButton: function() {
    var removebutton = document.getElementById("remove");
    if (removebutton == null) {
      return;
    }
    if (Traction.Edit.Dialogs.Attachments.selectedAttachments.length == 0) {
      removebutton.disabled = true;
    } else {
      removebutton.disabled = false;
    }
  },
  
  onUnload: function() {
    closeIfReady();
  },
  
  attachmentsSelected: function() {
    Traction.Edit.Dialogs.Attachments.selectedAttachments = new Array();
    for (var i = 0; i < Traction.Edit.Dialogs.Attachments.attachmentIDs.length; i ++) {
      var attachment = Traction.Edit.Dialogs.Attachments.attachmentData[Traction.Edit.Dialogs.Attachments.attachmentIDs[i]];
      if (!attachment.removed) {
	var chk = document.getElementById("attach"+i+"chk");
	if (chk.checked) {
	  Traction.Edit.Dialogs.Attachments.selectedAttachments[Traction.Edit.Dialogs.Attachments.selectedAttachments.length] = attachment.localfname;
	}
      }
    }
    Traction.Edit.Dialogs.Attachments.initRemoveButton();
  },
  
  updateDescription: function() {
    if (Traction.Edit.Dialogs.Attachments.selectedAttachments.length == 1) {
      Traction.Edit.Dialogs.Attachments.attachmentData[Traction.Edit.Dialogs.Attachments.selectedAttachments[0]].desc = document.fm.description.value;
      document.fm.datachange.value = "true";
    }
  },
  removeAttachment: function(localfname) {
    var index = 0;
    for (var i=0; i<Traction.Edit.Dialogs.Attachments.attachmentIDs.length; i++) {
      if (Traction.Edit.Dialogs.Attachments.attachmentIDs[i] == localfname) {
	index = i;
	break;
      }
    }
    var attachment = Traction.Edit.Dialogs.Attachments.attachmentData[localfname];
    var row = document.getElementById("attach"+index);
    if (row) {
      row.className = "selected";
    }
    Traction.Edit.Dialogs.Attachments.selectedAttachments = new Array();
    Traction.Edit.Dialogs.Attachments.selectedAttachments[0] = localfname;
    var removed = Traction.Edit.Dialogs.Attachments.removeSelectedAttachments();
    if (!removed) {
      Traction.Edit.Dialogs.Attachments.initAttachmentSelector();
    }
  },
  
  removeSelectedAttachments: function() {
    if (Traction.Edit.Dialogs.Attachments.selectedAttachments.length == 0 ||
	!confirm(i18n("editattachments_remove_attachments_confirmation_message"))) {
      return false;
    }
    var count = parseInt(document.fm.count.value);
    for (var i = 0; i < Traction.Edit.Dialogs.Attachments.selectedAttachments.length; i ++) {
      var file = Traction.Edit.Dialogs.Attachments.attachmentData[Traction.Edit.Dialogs.Attachments.selectedAttachments[i]];
      file.removed = true;
      count --;
      if (!file.isref) {
	document.fm.mustsubmit.value = "true";
	document.fm.datachange.value = "true";
      } else {
	document.fm.datachange.value = "true";
      }
    }
    document.fm.count.value = "" + count;
    var next = Traction.Edit.Dialogs.Attachments.startAttachmentNumber;
    for (var i = 0; i < Traction.Edit.Dialogs.Attachments.attachmentIDs.length; i ++) {
      var file = Traction.Edit.Dialogs.Attachments.attachmentData[Traction.Edit.Dialogs.Attachments.attachmentIDs[i]];
      if (!file.removed && (Traction.Edit.Dialogs.Attachments.renumberFiles || !file.isref)) {
	file.number = next;
	next ++;
      }
    }
    Traction.Edit.Dialogs.Attachments.selectedAttachments = new Array();
    Traction.Edit.Dialogs.Attachments.initAttachmentSelector();
    Traction.Edit.Dialogs.Attachments.initRemoveButton();
    return true;
  },
  
  uploadFiles: function() {
    document.fm.filecollection_action.value = "adduploads";
    document.fm.datachange.value = "true";
    Traction.Edit.Dialogs.Attachments.serializeAttachmentData();
    xmlprogress(1, "throbber");
    document.fm.submit();
  },
  
  showMoreUploadRows: function() {
    try {
      Traction.Edit.Dialogs.Attachments.showMoreRowsClickCount ++;
      var cur = document.getElementById("put_expand"+Traction.Edit.Dialogs.Attachments.showMoreRowsClickCount);
      if (cur != null) {
	cur.style.display = "block";
      }
      if (Traction.Edit.Dialogs.Attachments.showMoreRowsClickCount == 4) {
	document.getElementById("morerows").style.display = "none";
      }
    } catch (e) {
      alert(i18n("cfi_more_rows_not_supported_message", "Sorry, but this is not supported by your browser."));
      document.fm.more.disabled = true;
    }
  },
  FILE_INPUT_CONTROL_NAMES: [ "document.fm.file00",
			      "document.fm.file01",
			      "document.fm.file02",
			      "document.fm.file03",
			      "document.fm.file04",
			      "document.fm.file05",
			      "document.fm.file06",
			      "document.fm.file07",
			      "document.fm.file08",
			      "document.fm.file09",
			      "document.fm.file10",
			      "document.fm.file11",
			      "document.fm.file12",
			      "document.fm.file13",
			      "document.fm.file14" ],
  requireUpload: false,
  testRequireUpload: function() {
    Traction.Edit.Dialogs.Attachments.requireUpload = false;
    for (var i = 0; !Traction.Edit.Dialogs.Attachments.requireUpload && i < Traction.Edit.Dialogs.Attachments.FILE_INPUT_CONTROL_NAMES.length; i ++) {
      var fileName = eval(Traction.Edit.Dialogs.Attachments.FILE_INPUT_CONTROL_NAMES[i] + ".value");
      Traction.Edit.Dialogs.Attachments.requireUpload = (fileName != "");
    }
    if (Traction.Edit.Dialogs.Attachments.requireUpload) {
      document.fm.mustsubmit.value = "true";
    }
  },
  getNextAttachmentNumber: function() {
    var next;
    if (Traction.Edit.Dialogs.Attachments.attachmentIDs.length > 0) {
      next = Traction.Edit.Dialogs.Attachments.attachmentData[Traction.Edit.Dialogs.Attachments.attachmentIDs[Traction.Edit.Dialogs.Attachments.attachmentIDs.length - 1]].number + 1;
      if (next < Traction.Edit.Dialogs.Attachments.startAttachmentNumber) {
	next = Traction.Edit.Dialogs.Attachments.startAttachmentNumber;
      }
    } else {
      next = Traction.Edit.Dialogs.Attachments.startAttachmentNumber;
    }
    return next;
  },
  cancelAttachmentEdit: function() {
    if (Traction.Edit.Dialogs.Attachments.requireUpload &&
	!confirm(i18n("editattachments_cancel_without_uploading_confirmation_message",
		      "Are you sure you want to cancel without uploading the files you've chosen?"))) {
      return;
    }
    var cancelledAttachmentData = Traction.Edit.Dialogs.Attachments.attachmentData;
    var cancelledAttachmentIDs = Traction.Edit.Dialogs.Attachments.attachmentIDs;
    Traction.Edit.Dialogs.Attachments.initOriginalAttachmentData();
    Traction.Edit.Dialogs.Attachments.attachmentData = Traction.Edit.Dialogs.Attachments.originalAttachmentData;
    Traction.Edit.Dialogs.Attachments.attachmentIDs = Traction.Edit.Dialogs.Attachments.originalAttachmentIDs;
    var nextIdx = Traction.Edit.Dialogs.Attachments.attachmentIDs.length;
    for (var i = 0; i < cancelledAttachmentIDs.length; i ++) {
      var id = cancelledAttachmentIDs[i];
      if (Traction.Edit.Dialogs.Attachments.attachmentData[id] == null) {
	document.fm.mustsubmit.value = "true";
	Traction.Edit.Dialogs.Attachments.attachmentData[id] = cancelledAttachmentData[id].copy();
	Traction.Edit.Dialogs.Attachments.attachmentData[id].removed = true;
	Traction.Edit.Dialogs.Attachments.attachmentIDs[nextIdx] = id;
	nextIdx ++;
      }
    }
    if (document.fm.mustsubmit.value == "false") {
      window.close();
    } else {
      document.fm.filecollection_action.value = "collectgarbage";
      Traction.Edit.Dialogs.Attachments.serializeAttachmentData();
      readyToClose = true;
      xmlprogress(1, "throbber");
      document.fm.submit();
    }
  },
  finalizeAttachmentEdit: function() {
    if (document.fm.mustsubmit.value == "true") {
      document.fm.confirmation.value = "true";
      Traction.Edit.Dialogs.Attachments.serializeAttachmentData();
      if (Traction.Edit.Dialogs.Attachments.requireUpload) {
	document.fm.filecollection_action.value = "finalize";
      } else {
	document.fm.filecollection_action.value = "collectgarbage";
	readyToClose = true;
	Traction.Edit.Dialogs.Attachments.updateOpener();
      }
      xmlprogress(1, "throbber");
      Debug.println("Must submit attachments changes (action = ", document.fm.filecollection_action.value, ").");
      document.fm.submit();
    } else if (document.fm.datachange.value == "true") {
      Debug.println("No submission necessary; just serialize and update opener.");
      Traction.Edit.Dialogs.Attachments.serializeAttachmentData();
      Traction.Edit.Dialogs.Attachments.updateOpener();
      window.close();
    } else {
      window.close();
    }
  },
  
  updateOpener: function() {
    Debug.println("updateOpener(", "\"", document.fm.dialogname.value, "\",\n",
		  "\"", document.fm.filecollection_files.value, "\",\n",
		  parseInt(document.fm.count.value, 10), ")");
    window.opener.closeAttachmentsDialog(document.fm.dialogname.value, document.fm.filecollection_files.value, parseInt(document.fm.count.value, 10),
					 null, Traction.Edit.Dialogs.Attachments.getFlatDataList());
  },
  getFlatDataList: function() {
    var ret = new Array();
    for (var i = 0; i < Traction.Edit.Dialogs.Attachments.attachmentIDs.length; i ++) {
      ret.push(Traction.Edit.Dialogs.Attachments.attachmentData[Traction.Edit.Dialogs.Attachments.attachmentIDs[i]]);
    }
    return ret;
  },
  serializeAttachmentData: function() {
    Traction.Edit.Dialogs.Attachments.initGarbageData();
    var newgarbage = "";
    for (var i = 0; i < Traction.Edit.Dialogs.Attachments.garbageIDs.length; i ++) {
      var file = Traction.Edit.Dialogs.Attachments.garbageData[Traction.Edit.Dialogs.Attachments.garbageIDs[i]];
      file.number = i;
      if (i != 0) {
	newgarbage += ",";
      }
      newgarbage += file.serialize(i);
    }
    var nextGarbageIdx = Traction.Edit.Dialogs.Attachments.garbageIDs.length;
    var newfiles = "";
    var nextAttachmentIdx = 0;
    var lastNumber = 0;
    for (var i = 0; i < Traction.Edit.Dialogs.Attachments.attachmentIDs.length; i++) {
      var file = Traction.Edit.Dialogs.Attachments.attachmentData[Traction.Edit.Dialogs.Attachments.attachmentIDs[i]];
      if (file.removed) {
	if (!file.isref) {
	  if (nextGarbageIdx != 0) {
	    newgarbage += ",";
	  }
	  file.number = nextGarbageIdx;
	  newgarbage = newgarbage + file.serialize(nextGarbageIdx);
	  nextGarbageIdx ++;
	}
      } else {
	if (nextAttachmentIdx != 0) {
	  newfiles = newfiles + ",";
	}
	newfiles = newfiles + file.serialize(nextAttachmentIdx);
	nextAttachmentIdx ++;
	lastNumber = file.number;
      }
    }
    lastNumber ++;
    document.fm.filecollection_files.value = newfiles;
    document.fm.filecollection_garbage.value = newgarbage;
  },
  isSelected: function(attachment) {
    for (var i=0; i<Traction.Edit.Dialogs.Attachments.selectedAttachments.length; i++) {
      if (Traction.Edit.Dialogs.Attachments.selectedAttachments[i] == attachment.localfname) {
	return true;
      }
    }
    return false;
  },
  attachRowClick: function() {
    var index = this.id.substring(6);
    var chk = document.getElementById("attach"+index+"chk");
    chk.checked = !chk.checked;
    Traction.Edit.Dialogs.Attachments.attachmentsSelected();
    Traction.Edit.Dialogs.Attachments.initAttachmentSelector();
  },
  attachChkClick: function() {
    this.checked = !this.checked;
  }
};

Traction.Edit.Dialogs.Attachments.AttachmentData = Class.create();
Traction.Edit.Dialogs.Attachments.AttachmentData.prototype = {
  desc: null,
  fname: null,
  isref: null,
  localfname: null,
  mimetype: null,
  number: null,
  removed: false,
  id: null,
  iconInfo: {},
  initialize: function(desc_, fname_, isref_, localfname_, mimetype_, number_, iconInfo_) {
    this.desc       = desc_;
    this.fname      = fname_;
    this.isref      = isref_;
    this.localfname = localfname_;
    this.mimetype   = mimetype_;
    this.number     = number_;
    this.removed    = false;
    this.id         = localfname_;
    this.iconInfo   = iconInfo_
  },
  
  serialize: function(num) {
    var propprefix = num + "_";
    var str =
      propprefix + "desc="       + escape_commas(this.desc)       + "," +
      propprefix + "fname="      + escape_commas(this.fname)      + "," +
      propprefix + "isref="      + new String(this.isref)         + "," +
      propprefix + "localfname=" + escape_commas(this.localfname) + "," +
      propprefix + "mimetype="   + escape_commas(this.mimetype)   + "," +
      propprefix + "number="     + this.number;
    return str;
  },
  
  copy: function() {
    var newdata = new Traction.Edit.Dialogs.Attachments.AttachmentData(this.desc, this.fname, this.isref, this.localfname, this.mimetype, this.number, this.iconInfo);
    newdata.removed = this.removed;
    return newdata;
  }
};

//------------------------------------------------------------------------
// traction.edit.dialogs.ChooseName


Traction.Edit.Dialogs.ChooseName = {
  nameChangeCount: 0,
  init: function() {
    Traction.Edit.Dialogs.ChooseName.initAutoCompletion();
    Traction.Edit.Dialogs.ChooseName.onPropertyChange();
    document.fm.name.focus();
  },
  initAutoCompletion: function() {
    eac_setup(document.fm.name);
    eac_HighlightMatches = false;
    EAC_TYPE_COMPLETER = "pagenamecompleter";
    eac_accept = Traction.Edit.Dialogs.ChooseName.PageNameCompleter.acceptMatch.bind(Traction.Edit.Dialogs.ChooseName.PageNameCompleter);
    document.fm.name.onfocus = Traction.Edit.Dialogs.ChooseName.PageNameCompleter.fetchMatches.bindAsEventListener(this);
    EAC_SEND_EMPTY = false;
    Events.attachCustom("eacbeforerequest", Traction.Edit.Dialogs.ChooseName.PageNameCompleter, Traction.Edit.Dialogs.ChooseName.PageNameCompleter.beforeEACRequest);
    Events.attach(document.fm.name, Events.KeyUp, Traction.Edit.Dialogs.ChooseName, Traction.Edit.Dialogs.ChooseName.onNameChange);
  },
  onCancelClick: function() {
    window.close();
  },
  onOKClick: function() {
    if (window.opener.closeChooseNameDialog(document.fm.proj.value.substring(2),
					    (document.fm.oldname ? document.fm.oldname.value : null),
					    document.fm.name.value.trim(),
					    (document.fm.alreadyused.value == "true"))) {
      setTimeout(function() {
		   window.close();
		 }, 100);
    }
    return false;
  },
  onProjectChange: function() {
    Debug.println("Traction.Edit.Dialogs.ChooseName.onProjectChange");
    eac_hide();
    Traction.Edit.Dialogs.ChooseName.onPropertyChange();
    Traction.Edit.Dialogs.ChooseName.PageNameCompleter.fetchMatches();
  },
  onPropertyChange: function() {
    Debug.println("Traction.Edit.Dialogs.ChooseName.onPropertyChange");
    Traction.Edit.Dialogs.ChooseName.getTractionAtomPreview();
    document.fm.ok.disabled = (document.fm.name.value.trim() == "");
  },
  getTractionAtomPreview: function() {
    if (document.fm.name.value.trim() == "") {
      Traction.Edit.Dialogs.ChooseName.getTractionAtomPreview_wakeup.bind(this)("(" +
								i18n("choosename_preview_placeholder_text",
								     "Choose a name and project to see a preview.") +
								")" +
								"<!--[[status=spaces]]-->");
      return;
    }
    var url = FORM_ACTION_READ_ONLY + "?type=atomsnippet_";
    Debug.println("Retrieving atomsnippet_ for named page ", document.fm.name.value.trim(), " in scope ",
		  document.fm.proj.options[document.fm.proj.selectedIndex].text,
		  "...");
    url += "&name=" + encode_url_parameter(document.fm.name.value.trim()) +
      "&proj=" + encode_url_parameter(document.fm.proj.value.substring(2));
    xmlget_async(url, null, true, Traction.Edit.Dialogs.ChooseName.getTractionAtomPreview_wakeup.bind(this));
  },
  getTractionAtomPreview_wakeup: function(responseText) {
    var nameStatus;
    var findNameStatus = responseText.indexOf("<!--[[status=");
    if (findNameStatus != -1) {
      nameStatus = responseText.substring(findNameStatus + 13, responseText.indexOf("]]", findNameStatus));
      Debug.println("nameStatus ", nameStatus);
    }
    else {
      nameStatus = "unrequested"; // if we can't tell for sure, assume unrequested
    }
    document.fm.alreadyused.value = (nameStatus == "unrequested" || nameStatus == "unassigned" || nameStatus == "unknown") ? "false" :
      ((document.fm.edit_edit_original_id != null && nameStatus == document.fm.edit_edit_original_id.value) ? "false" : "true");
    Debug.println("Name status ", nameStatus, " (", document.fm.alreadyused.value, ")");
    document.getElementById("statusmessage").innerHTML = responseText;
  },
  onNameChange: function() {
    Debug.println("Traction.Edit.Dialogs.ChooseName.onNameChange");
    Traction.Edit.Dialogs.ChooseName.nameChangeCount ++;
    var myCount = Traction.Edit.Dialogs.ChooseName.nameChangeCount;
    setTimeout(function() {
		 Traction.Edit.Dialogs.ChooseName.onNameChange_wakeup(myCount);
	       },
	       250);
  },
  onNameChange_wakeup: function(myCount) {
    if (myCount == Traction.Edit.Dialogs.ChooseName.nameChangeCount) {
      Traction.Edit.Dialogs.ChooseName.onPropertyChange();
      Traction.Edit.Dialogs.ChooseName.getTractionAtomPreview();
    }
  }
};
Traction.Edit.Dialogs.ChooseName.PageNameCompleter = {
  fetchMatches: function() {
    Traction.Edit.Dialogs.ChooseName.PageNameCompleter.beforeEACRequest();
    eac_Element = document.fm.name;
    var q = document.fm.name.value.trim();
    if (q != "") {
      Debug.println("Firing pagenamecompleter search.");
      var url = FORM_ACTION_READ_ONLY + "?type=" + EAC_TYPE_COMPLETER +
        "&q=" + encode_url_parameter(q) + eac_Extras;
      var throbber = eac_Element.nextSibling;
      eac_xmlget = xmlget_async(url, throbber, (throbber == null), eac_callback, [ eac_Element, null ], -1);
    }
  },
  acceptMatch: function(index) {
    if (eac_DIV != null && eac_DIV.style.display != "none") {
      document.fm.name.value   = eac_hits[index][0] ? eac_hits[index][0] : eac_hits[index][1];
      Debug.println("Accepted '", eac_hits[index][2], "' as the match for a ", EAC_TYPE_COMPLETER, " search (assigned to ", eac_hits[index][1], ").");
      eac_hide();
      Traction.Edit.Dialogs.ChooseName.onPropertyChange();
    }
  },
  beforeEACRequest: function() {
    eac_Extras = "&proj=" + encode_url_parameter(document.fm.proj.value.substring(2));
    Debug.println("Setting eac_Extras = '", eac_Extras, "'");
  }
};

//------------------------------------------------------------------------
// traction.edit.dialogs.Names


Traction.Edit.Dialogs.Names = {
  nameStates: null,
  nameRowContainer: null,
  nameindex2selected: null,
  
  initHasConflicts: false,
  
  existingTitleNameAlreadyUsed: false,
  init: function() {
    closeChooseNameDialog = Traction.Edit.Dialogs.Names.onChooseName;
    Traction.Edit.Dialogs.Names.initNameStates();
    Traction.Edit.Dialogs.Names.initNameRows();
    Traction.Edit.Dialogs.Names.resetSelection();
    Traction.Edit.Dialogs.Names.initNameSelector();
    Traction.Edit.Dialogs.Names.onToggleLinkByNames();
    this.initHasConflicts = Traction.Edit.Dialogs.Names.hasNewConflicts();
  },
  initNameRows: function() {
    Traction.Edit.Dialogs.Names.nameRowContainer = document.getElementById("name-row-container");
    for (var i = 0; i < Traction.Edit.Dialogs.Names.nameStates.length; i ++) {
      Traction.Edit.Dialogs.Names.addNameRow(Traction.Edit.Dialogs.Names.nameStates[i], i);
    }
  },
  addNameRow: function(nameState, nameIndex) {
    Debug.println("Adding name for ", (nameState.newname ? nameState.newname : nameState.curname), " (index ", nameIndex, ")");
    var nameRow = document.createElement("TR");
    nameRow.id = "name" + nameIndex;
    nameRow.valign = "middle";
    var nameCell = document.createElement("TD");
    nameCell.className = "name";
    var nameDIV = document.createElement("DIV");
    nameDIV.className = "curname";
    var displayNameText = document.createTextNode(nameState.newname ? nameState.newname : nameState.curname);
    nameDIV.appendChild(displayNameText);
    var existingNameDIV = document.createElement("DIV");
    existingNameDIV.className = "oldname";
    existingNameDIV.style.display = (nameState.newname ? "" : "none");
    var existingNameText = document.createTextNode("(" + (new MessageFormat(i18n("formerly_named_article_warning", "Was \"{0}\""))).format(nameState.curname) + ")");
    existingNameDIV.appendChild(existingNameText);
    var projDIV = document.createElement("DIV");
    projDIV.className = "projectname";
    if (nameState.project == "*") {
      projDIV.innerHTML = "(" + i18n("editnames_global_project_indicator", "Global") + ")";
    } else {
      if (document.fm.proj.value != nameState.project) {
	projDIV.innerHTML = nameState.project;
      }
    }
    var checkboxCell = document.createElement("TD");
    checkboxCell.width = "1%";
    checkboxCell.className = "checkbox";
    var nameChk = document.createElement("INPUT");
    nameChk.type = "checkbox";
    nameChk.id = "name" + nameIndex + "chk";
    checkboxCell.appendChild(nameChk);
    nameCell.appendChild(nameDIV);
    nameCell.appendChild(existingNameDIV);
    nameCell.appendChild(projDIV);
    nameRow.appendChild(nameCell);
    nameRow.appendChild(checkboxCell);
    Traction.Edit.Dialogs.Names.nameRowContainer.appendChild(nameRow);
    setTimeout(function() {
		 Debug.println("Adding handlers for ", nameIndex);
		 nameRow.onclick = Traction.Edit.Dialogs.Names.onNameRowClick.bind(nameRow);
		 nameChk.onclick = Traction.Edit.Dialogs.Names.onNameCheckboxClick.bind(nameChk);
	       }, 100);
  },
  resetSelection: function() {
    Traction.Edit.Dialogs.Names.nameindex2selected = new Array(Traction.Edit.Dialogs.Names.nameStates.length);
    for (var i = 0; i < Traction.Edit.Dialogs.Names.nameindex2selected.length; i ++) {
      Traction.Edit.Dialogs.Names.nameindex2selected[i] = false;
    }
  },
  
  initNameSelector: function() {
    var newOptionIdx = 0;
    var selectedCount = 0;
    var firstSelected = -1;
    for (var i = 0; i < Traction.Edit.Dialogs.Names.nameStates.length; i ++) {
      var nameState = Traction.Edit.Dialogs.Names.nameStates[i];
      var row = document.getElementById("name"+i);
      if (nameState.removed) {
	row.style.display = "none";
      } else {
	newOptionIdx ++;
	if (Traction.Edit.Dialogs.Names.nameindex2selected[i]) {
	  selectedCount ++;
	  if (firstSelected == -1) {
	    firstSelected = i;
	  }
	  className = "selected";
	} else {
	  className = ((newOptionIdx % 2 == 0) ? "even" : "odd");
	}
	if (nameState.alreadyUsed) {
	  className += " conflict";
	}
	row.className = className;
	var titleDIV = getElementsByClassFromRootNode("curname", row, "DIV")[0];
	if (nameState.isTitle) {
	  titleDIV.id = "titlename";
	} else {
	  titleDIV.removeAttribute("id");
	}
      }
    }
    document.fm.add.disabled = (!document.fm.linkbynames.checked);
    document.fm.maketitle.disabled = ((selectedCount != 1) ||
				      Traction.Edit.Dialogs.Names.nameStates[firstSelected].isTitle ||
				      (Traction.Edit.Dialogs.Names.nameStates[firstSelected].project == "*") ||
				      (Traction.Edit.Dialogs.Names.nameStates[firstSelected].project != document.fm.proj.value));
    document.fm.remove.disabled = ((selectedCount == 0) ||
				   Traction.Edit.Dialogs.Names.forwardLinkNameIsSelected());
    document.fm.rename.disabled = ((selectedCount != 1) ||
				   (Traction.Edit.Dialogs.Names.nameStates[firstSelected].number == -1));
    document.fm.showhistory.disabled  = (selectedCount != 1);
    var notOkay = (document.fm.linkbynames.checked &&
		   (Traction.Edit.Dialogs.Names.getTitle() == null));
    document.fm.ok.disabled = notOkay;
    show_default(notOkay, document.getElementById("okdisabledwarning"));
  },
  onNameRowClick: function() {
    Debug.println("onNameRowClick for ", this.id, "...");
    var nameIndex = parseInt(this.id.substring(4), 10);
    var chk = document.getElementById("name"+nameIndex+"chk");
    chk.checked = !chk.checked;
    Traction.Edit.Dialogs.Names.nameindex2selected[nameIndex] = chk.checked;
    Traction.Edit.Dialogs.Names.initNameSelector();
  },
  onNameCheckboxClick: function() {
    Debug.println("onNameCheckboxClick for ", this.id, "...");
    this.checked = !this.checked;
  },
  onMakeTitleClick: function() {
    var selIdx = Traction.Edit.Dialogs.Names.getFirstSelectedIndex();
    if (selIdx != -1) {
      for (var i = 0; i < Traction.Edit.Dialogs.Names.nameStates.length; i ++) {
	Traction.Edit.Dialogs.Names.nameStates[i].isTitle = false;
      }
      var nameState = Traction.Edit.Dialogs.Names.nameStates[selIdx];
      nameState.isTitle = true;
      Traction.Edit.Dialogs.Names.initNameSelector(); // set class on this name to make it styled like the title
    }
  },
  onAddClick: function() {
    openChooseNameDialog("", // no dialog name
			 document.fm.edit_edit_original_id ? document.fm.edit_edit_original_id.value : null, // edit target ID, if present
			 document.fm.proj.value, // the project selected in the editor when the dialog was opened
			 null, // no current (initial) name, we're adding a new name here
			 null, // no new (changed) name, we're adding a new name here
			 null); // no extra params
  },
  onRenameClick: function() {
    var selIdx = Traction.Edit.Dialogs.Names.getFirstSelectedIndex();
    if (selIdx != -1) {
      var nameState = Traction.Edit.Dialogs.Names.nameStates[selIdx];
      openChooseNameDialog("", // no dialog name
			   document.fm.edit_edit_original_id ? document.fm.edit_edit_original_id.value : null, // edit target ID, if present
			   (nameState.project != null ? nameState.project : "*"), // the ID of the project in which the new name belongs, or * if it's global
			   nameState.curname, // the current (the initial, from the server when the entry edit started) name
			   nameState.newname, // if there's a newname field, this name has been marked to be changed
			   null); // no extra params
    }
  },
  onRemoveClick: function() {
    var selIdx = Traction.Edit.Dialogs.Names.getFirstSelectedIndex();
    if (selIdx != -1) {
      for (var i = 0; i < Traction.Edit.Dialogs.Names.nameindex2selected.length; i ++) {
	if (Traction.Edit.Dialogs.Names.nameindex2selected[i]) {
	  Traction.Edit.Dialogs.Names.nameStates[i].removed = true; // flag this for removal
	}
      }
      Traction.Edit.Dialogs.Names.resetSelection();
      Traction.Edit.Dialogs.Names.initNameSelector(); // hide this one, and reshuffle odd/even colorings
    }
  },
  onShowHistoryClick: function() {
    var selIdx = Traction.Edit.Dialogs.Names.getFirstSelectedIndex();
    if (selIdx != -1) {
      var nameState = Traction.Edit.Dialogs.Names.nameStates[selIdx];
      do_window_open(FORM_ACTION_READ_ONLY + "?type=pagenamehistory&proj=" + nameState.project + "&name=" + encode_url_parameter(nameState.curname));
    }
  },
  onToggleLinkByNames: function() {
    if (document.fm.linkbynames.checked && Traction.Edit.Dialogs.Names.getTitle() == null && document.fm.title && (trim(document.fm.title.value) != "")) {
      var newIdx = Traction.Edit.Dialogs.Names.nameStates.length;
      Traction.Edit.Dialogs.Names.nameStates[newIdx] = new Traction.Edit.Dialogs.Names.NameState(-1, document.fm.title.value, null, document.fm.proj.value, false, true, Traction.Edit.Dialogs.Names.existingTitleNameAlreadyUsed);
      Traction.Edit.Dialogs.Names.addNameRow(Traction.Edit.Dialogs.Names.nameStates[newIdx], newIdx);
    }
    Traction.Edit.Dialogs.Names.initNameSelector();
  },
  onCancelClick: function() {
    if (!Traction.Edit.Dialogs.Names.checkConflicts(Traction.Edit.Dialogs.Names.hasExistingConflicts(),
					i18n("editnames_cancel_with_conflicts_confirmation_message",
					     "If you don't edit this list of names, it will still contain names that are " +
					     "already assigned to other articles (those originally appearing in red text).")));
    window.close();
  },
  
  hasExistingConflicts: function() {
    return Traction.Edit.Dialogs.Names.initHasConflicts;
  },
  
  hasNewConflicts: function() {
    var conflicts = false;
    if (document.fm.linkbynames.checked) {
      for (var i = 0; i < Traction.Edit.Dialogs.Names.nameStates.length; i ++) {
	if (!Traction.Edit.Dialogs.Names.nameStates[i].removed &&
	    Traction.Edit.Dialogs.Names.nameStates[i].alreadyUsed) {
	  conflicts = true;
	  break;
	}
      }
    }
    return conflicts;
  },
  checkConflicts: function(hasConflicts, confirmationMessage) {
    if (hasConflicts) {
      return confirm(confirmationMessage + "\n\n" +
		     i18n("editnames_conflicts_confirmation_options",
			  "Click OK to accept this list of names, including the names assigned to other articles.  " +
			  "Before submitting this article, you must resolve the naming conflicts by editing the articles " +
			  "to which the names in question are assigned, removing or changing names as necessary.\n\n" +
			  "Click Cancel to continue editing the names here.  You must remove or change the names that are " +
			  "already assigned to other articles to resolve conflicts that you don't plan on resolving by " +
			  "removing or changing names assigned to other articles as necessary."));
    }
    return true;
  },
  onOKClick: function() {
    var hasConflicts = Traction.Edit.Dialogs.Names.hasNewConflicts();
    if (!Traction.Edit.Dialogs.Names.checkConflicts(hasConflicts,
					    i18n("editnames_accept_with_conflicts_confirmation_message",
						 "The names appearing in red text are already assigned to other articles."))) {
      return false;
    }
    window.opener.closeNamesDialog(document.fm.dialogname ? document.fm.dialogname.value : "",
				   document.fm.linkbynames.checked,
				   Traction.Edit.Dialogs.Names.getNameSerialization(),
				   Traction.Edit.Dialogs.Names.getTitle(),
				   Traction.Edit.Dialogs.Names.getCount(),
				   hasConflicts,
				   true);
    setTimeout(function() {
		 window.close();
	       }, 100);
    return false;
  },
  onChooseName: function(project, oldName, newName, alreadyUsed) {
    if (oldName != null) {
      var existingNameState = null;
      var idx = -1;
      for (var i = 0; i < Traction.Edit.Dialogs.Names.nameStates.length; i ++) {
	if (Traction.Edit.Dialogs.Names.nameStates[i].curname == oldName) {
	  existingNameState = Traction.Edit.Dialogs.Names.nameStates[i];
	  idx = i;
	  break;
	}
      }
      if (existingNameState) {
	existingNameState.alreadyUsed = alreadyUsed;
	var nameRow = document.getElementById("name" + idx);
	var existingNameDIV = getElementsByClassFromRootNode("oldname", nameRow, "DIV")[0];
	if (existingNameState.curname.toLowerCase() == newName.toLowerCase()) {
	  existingNameState.newname = null; // this name is being renamed back to the name it was.  it's no longer a rename.
	  show_block(false, existingNameDIV);
	} else {
	  existingNameState.newname = newName;
	  show_block(true, existingNameDIV);
	}
	getElementsByClassFromRootNode("curname", nameRow, "DIV")[0].innerHTML = newName;
      }
    }
    else {
      var newIndex = Traction.Edit.Dialogs.Names.nameStates.length;
      var newNameState = new Traction.Edit.Dialogs.Names.NameState(-1, newName, null, project, false, false, alreadyUsed);
      for (var i = 0; i < Traction.Edit.Dialogs.Names.nameStates.length; i ++) {
	if (!Traction.Edit.Dialogs.Names.nameStates[i].removed && Traction.Edit.Dialogs.Names.nameStates[i].isSameName(newNameState)) {
	  alert(i18n("editnames_name_already_present_warning_message", "The name you have chosen is already in the list of names to use for this entry.  Please choose another name."));
	  return false;
	}
      }
      Traction.Edit.Dialogs.Names.nameStates[newIndex] = newNameState;
      Traction.Edit.Dialogs.Names.addNameRow(newNameState, newIndex);
      Traction.Edit.Dialogs.Names.initNameSelector();
    }
    return true;
  },
  getFirstSelectedIndex: function() {
    for (var i = 0; i < Traction.Edit.Dialogs.Names.nameindex2selected.length; i ++) {
      if (Traction.Edit.Dialogs.Names.nameindex2selected[i]) {
	return i;
      }
    }
    return -1;
  },
  forwardLinkNameIsSelected: function() {
    if (document.fm.forwardlinkname && document.fm.forwardlinkproj) {
      for (var i = 0; i < Traction.Edit.Dialogs.Names.nameindex2selected.length; i ++) {
	if (Traction.Edit.Dialogs.Names.nameStates[i].curname.toLowerCase() == document.fm.forwardlinkname.value.toLowerCase() &&
	    Traction.Edit.Dialogs.Names.nameStates[i].project == document.fm.forwardlinkproj.value &&
	    Traction.Edit.Dialogs.Names.nameindex2selected[i]) {
	  return true;
	}
      }
    }
    return false;
  },
  getNameSerialization: function() {
    var s = "";
    for (var num = 0; num < Traction.Edit.Dialogs.Names.nameStates.length; num ++) {
      if (num > 0) {
	s += ",";
      }
      s += Traction.Edit.Dialogs.Names.nameStates[num].serialize(num);
    }
    return s;
  },
  getTitle: function() {
    for (var i = 0; i < Traction.Edit.Dialogs.Names.nameStates.length; i ++) {
      if (Traction.Edit.Dialogs.Names.nameStates[i].isTitle && !Traction.Edit.Dialogs.Names.nameStates[i].removed) {
	return (Traction.Edit.Dialogs.Names.nameStates[i].newname ? Traction.Edit.Dialogs.Names.nameStates[i].newname : Traction.Edit.Dialogs.Names.nameStates[i].curname);
      }
    }
    return null;
  },
  getCount: function() {
    var count = 0;
    for (var i = 0; i < Traction.Edit.Dialogs.Names.nameStates.length; i ++) {
      if (!Traction.Edit.Dialogs.Names.nameStates[i].removed) {
	count ++;
      }
    }
    return count;
  }
};

Traction.Edit.Dialogs.Names.NameState = Class.create();
Traction.Edit.Dialogs.Names.NameState.prototype = {
  number: -1,
  curname: null,
  newname: null,
  project: -1,
  isTitle: false,
  removed: false,
  alreadyUsed: false,
  initialize: function(number, curname, newname, project, removed, isTitle, alreadyUsed) {
    this.number      = number;
    this.curname     = curname;
    this.newname     = newname;
    this.project     = project;
    this.removed     = removed;
    this.isTitle     = isTitle;
    this.alreadyUsed = alreadyUsed;
  },
  
  serialize: function(num) {
    var propprefix = num + "_";
    var str =
      propprefix + "number="  + this.number + "," +
      propprefix + "curname=" + escape_commas(this.curname) + "," +
      propprefix + "project=" + this.project + "," +
      propprefix + "removed=" + this.removed + "," +
      propprefix + "istitle=" + this.isTitle + "," +
      propprefix + "alreadyused=" + this.alreadyUsed;
    if (this.newname) {
      str += "," + propprefix + "newname=" + escape_commas(this.newname);
    }
    return str;
  },
  
  copy: function() {
    var newdata = new Traction.Edit.Dialogs.Names.NameState(this.number, this.name, this.project, this.removed, this.isTitle, this.alreadyUsed);
    return newdata;
  },
  isSameName: function(otherNameState) {
    var ret = false;
    if (this.project == otherNameState.project) {
      var n1 = (this.newname)           ? this.newname.toLowerCase()           : this.curname.toLowerCase();
      var n2 = (otherNameState.newname) ? otherNameState.newname.toLowerCase() : otherNameState.curname.toLowerCase();
      ret = (n1 == n2);
    }
    return ret;
  }
};

//------------------------------------------------------------------------
// traction.edit.Util

Traction.Edit.Util = {
  
  autoSaveDisabled: function(formElm) {
    return ((formElm.edit_save_draft_auto == null) ||
	    (Traction.Edit.AUTOSAVE_INTERVAL_MS < 0) ||
	    (ua("supports_xmlhttp", "false") != "true"));
  },
  getAttachmentsUrlParameters: function(formElm) {
    return "&filecollection_files=" + (formElm.edit_attachments != null ? encode_url_parameter(formElm.edit_attachments.value) : "") +
      "&isedit=" + new String(formElm.edit_edit_id != null) +
      "&filecollection_next_file_number=" + (formElm.attachmentstartnumber != null ? formElm.attachmentstartnumber.value : 1);
  },
  getSectionsURLParameters: function(formElm) {
    if (formElm && formElm.edit_sections_encoded) {
      return "&sections=" + encode_url_parameter(formElm.edit_sections_encoded.value);
    }
    return "";
  },
  getProjectURLParameter: function(formElm) {
    if (formElm && formElm.edit_project) {
      var val = formElm.edit_project.value;
      if (val.indexOf("::") == 0) {
	val = val.substring(2);
      }
      if (val == "") {
	val = "*";
      }
      return "&proj=" + encode_url_parameter(val);
    }
    return "&proj=*";
  },
  getEditTargetURLParameter: function(formElm) {
    if (document.fm.edit_edit_id) {
      return "&curentry=" + encode_url_parameter(document.fm.edit_edit_id.value);
    }
    return "";
  }
};

//------------------------------------------------------------------------
// traction.ui.Select


Traction.Select = Class.create();
Traction.Select.registry = new Array();
Traction.Select.MODE_SELECTOR_NORMAL = 1;
Traction.Select.MODE_HIDDEN_NORMAL   = 2;
Traction.Select.MODE_HIDDEN_ESCAPED  = 3;
Traction.Select.MODE_HIDDEN_BOTH     = 4;
Traction.Select.add = function(id, text, value) { Traction.Select.registry[id].add(text, value); };
Traction.Select.insert = function(id, index, text, value) { Traction.Select.registry[id].insert(index, text, value); };
Traction.Select.moveup = function(id) { Traction.Select.registry[id].moveup(); };
Traction.Select.movedown = function(id) { Traction.Select.registry[id].movedown(); };
Traction.Select.isEmpty = function(id) { Traction.Select.registry[id].isEmpty(); };
Traction.Select.removeSelected = function(id, confirm) { Traction.Select.registry[id].removeSelected(confirm); };
Traction.Select.selectToText = function(id) { Traction.Select.registry[id].selectToText(); };
Traction.Select.setOptions = function(id, options, selectedOptions) { Traction.Select.registry[id].setOptions(options, selectedOptions); };
Traction.Select.currentIndex = function(id) { Traction.Select.registry[id].currentIndex() };
Traction.Select.currentText = function(id) { Traction.Select.registry[id].currentText(); };
Traction.Select.currentValue = function(id) { Traction.Select.registry[id].currentValue() };
Traction.Select.currentOption = function(id) { Traction.Select.registry[id].currentOption() };
Traction.Select.selectNone = function(id) { Traction.Select.registry[id].selectNone(); };
Traction.Select.selectAll = function(id) { Traction.Select.registry[id].selectAll(); };
Traction.Select.removeAll = function(id) { Traction.Select.registry[id].removeAll(); };
Traction.Select.indexOfText = function(id, text) { Traction.Select.registry[id].indexOfText(text) };
Traction.Select.indexOfValue = function(id, value) { Traction.Select.registry[id].indexOfValue(value); };
Traction.Select.selected2array = function(id) { Traction.Select.registry[id].selected2array(); };
Traction.Select.selectOption = function(id, text, value, insertIndex, byValue) { Traction.Select.registry[id].selectOption(text, value, insertIndex, byValue); };
Traction.Select.selectOptions = function(id, values) { Traction.Select.registry[id].selectOptions(texts, values, insertIndex, byValue); };
Traction.Select.cutAndPasteSelected = function(id, destinationSelector) { Traction.Select.registry[id].cutAndPasteSelected(destinationSelector); };
Traction.Select.copySelectedTo = function(id, destinationSelector, duplicatesok) { Traction.Select.registry[id].copySelectedTo(); };
Traction.Select.pasteFromSelected = function(id, sourceSelector, duplicatesok) { Traction.Select.registry[id].paseFromSelected(); };

Traction.Select.prototype = {
  
  selector: null,
  
  configOptions: null,
  
  initialize: function(id, configOptions) {
    this.selector = document.getElementById(id);
    this.configOptions = configOptions;
    if (configOptions.submitMode == Traction.Select.MODE_HIDDEN_NORMAL ||
	configOptions.submitMode == Traction.Select.MODE_HIDDEN_ESCAPED ||
	configOptions.submitMode == Traction.Select.MODE_HIDDEN_BOTH) {
      if (typeof(configOptions.customEventName) != "undefined" &&
	  configOptions.customEventName != null) {
	Events.attachCustom(configOptions.customEventName, this, this.prepare_for_submit);
      }
      else {
	var standardEventName;
	if (typeof(configOptions.standardEventName) != "undefined" &&
	    configOptions.standardEventName != null) {
	  standardEventName = configOptions.standardEventName;
	} else {
	  standardEventName = Events.Submit;
	}
	var eventTarget = (typeof(configOptions.eventTarget) != "undefined" && configOptions.eventTarget != null) ?
	  configOptions.eventTarget : this.selector.form;
	Events.attach(this.selector.form, standardEventName, this, this.prepare_for_submit);
      }
    }
  },
  
  create_hidden_fields: function() {
    var opts = this.selector.options;
    var sz = opts.length;
    var str;
    var escStr;
    if (sz > 0) {
      str = opts[0].value;
      escStr = escape_commas(opts[0].value)
      for (var i = 1; i < sz; i ++) {
	str += "," + opts[i].value;
	escStr += "," + escape_commas(opts[i].value);
      }
    } else {
      escStr = str = "";
    }
    if (this.configOptions.submitMode == Traction.Select.MODE_HIDDEN_NORMAL ||
	this.configOptions.submitMode == Traction.Select.MODE_HIDDEN_BOTH) {
      var field = document.createElement("INPUT");
      field.type  = "hidden";
      field.name  = this.configOptions.fieldName;
      field.value = str;
      this.selector.parentNode.appendChild(field);
    }
    if (this.configOptions.submitMode == Traction.Select.MODE_HIDDEN_ESCAPED ||
	this.configOptions.submitMode == Traction.Select.MODE_HIDDEN_BOTH) {
      var escField = document.createElement("INPUT");
      escField.type  = "hidden";
      escField.name  = this.configOptions.fieldName + "__escaped";
      escField.value = escStr;
      this.selector.parentNode.appendChild(escField);
    }
  },
  
  prepare_for_submit: function() {
    this.create_hidden_fields();
    this.selectNone();
  },
  
  add: function(text, value, selected, duplicatesOK) {
    var index = this.indexOfValue(value);
    if (index == -1 || duplicatesOK) {
      this.selector[this.selector.length] = (value) ? new Option(text, value) : new Option(text);
      if (selected) {
	this.selector[this.selector.length - 1].selected = true;
      }
    }
    else if (selected && index >= 0) {
      this.selector[index].selected = true;
    }
  },
  
  insert: function(index, text, value, initiallySelected) {
    var opts = this.selector.options;
    if (index > opts.length) {
      index = opts.length;
    } else {
      for (var i = opts.length; i > index; i --) {
	opts[i] = new Option(opts[i - 1].text);
	if (opts[i - 1].selected) {
	  opts[i].selected = true;
	  opts[i - 1].selected = false;
	}
      }
    }
    opts[index] = value ? new Option(text, value) : new Option(text);
    if (initiallySelected) {
      opts[index].selected = true;
    }
  },
  
  isEmpty: function() {
    return (this.selector.options.length == 0)
  },
  
  moveup: function() {
    var opts = this.selector.options;
    var tmpvalue, tmptext, tmpsel;
    var ret = false;
    for(var i = 1; i < opts.length; i ++) {
      if (opts[i].selected) {
	if (opts[i].selected && opts[i - 1].selected) {
	  continue; // don't bother switching them...
	}
	tmptext  = opts[i].text;
	tmpvalue = opts[i].value;
	tmpsel   = opts[i].selected;
	tmpcls   = opts[i].className;
	tmpstyle = opts[i].style.cssText;
	opts[i].text = opts[i + 1].text;
	opts[i].value = opts[i + 1].value;
	opts[i].selected = opts[i + 1].selected;
	opts[i].className = opts[i + 1].className;
	opts[i].style.cssText = opts[i + 1].style.cssText;
	opts[i + 1].text = tmptext;
	opts[i + 1].value = tmpvalue;
	opts[i + 1].selected = tmpsel;
	opts[i + 1].className = tmpcls;
	opts[i + 1].style.cssText = tmpstyle;
	ret = true;
      }
    }
    return ret;
  },
  
  movedown: function() {
    var opts = this.selector.options;
    var tmpvalue, tmptext, tmpsel;
    var ret = false;
    for(var i = (opts.length - 2); i >= 0; i --) {
      if (opts[i].selected) {
	if (opts[i].selected && opts[i + 1].selected) {
	  continue; // don't bother switching them...
	}
	tmptext  = opts[i].text;
	tmpvalue = opts[i].value;
	tmpsel   = opts[i].selected;
	opts[i].text = opts[i + 1].text;
	opts[i].value = opts[i + 1].value;
	opts[i].selected = opts[i + 1].selected;
	opts[i + 1].text = tmptext;
	opts[i + 1].value = tmpvalue;
	opts[i + 1].selected = tmpsel;
	ret = true;
      }
    }
    return ret;
  },
  
  removeSelected: function(confirm) {
    var opts = this.selector.options; 
    var i = 0;
    while(i < opts.length) {
      if (opts[i].selected) {
	opts[i] = null;
      } else {
	i ++;
      }
    }
  },
  
  selectToText: function() {
    var opts = this.selector.options;
    var sz = opts.length;
    var val = "";
    for (var i = 0; i < sz; i ++) {
      if (val != "") {
	val += ",";
      }
      val += escape_commas(opts[i].value);
    }
    return val;
  },
  
  setOptions: function(newOptions) {
    if (this.selector) {
      var opts = this.selector.options;
      opts.length = 0;
      if (newOptions) {
	var sz = newOptions.length;
	for (var i = 0; i < sz; i ++) {
	  opts[opts.length] = newOptions[i];
	}
      }
    }
  },
  
  currentIndex: function() {
    if (!this.isEmpty()) {
      return this.selector.selectedIndex;
    } else {
      return -1;
    }
  },
  
  currentText: function() {
    var index = this.currentIndex();
    if (index != -1) {
      return this.selector.options[index].text;
    } else {
      return "";  // ie5 is much happier with this
    }
  },
  
  currentValue: function() {
    var index = this.currentIndex();
    if (index != -1) {
      return this.selector.options[index].value;
    } else {
      return "";  // ie5 is much happier with this
    }
  },
  
  currentOption: function() {
    var index = this.currentIndex();
    if (index != -1) {
      return this.selector.options[index];
    } else {
      return null;
    }
  },
  
  selectNone: function() {
    var opts = this.selector.options;
    var sz = opts.length;
    for (var i = 0; i < sz; i ++) {
      opts[i].selected = false;
    }
  },
  
  selectAll: function() {
    var opts = this.selector.options;
    var sz = opts.length;
    for (var i = 0; i < sz; i ++) {
      opts[i].selected = true;
    }
  },
  
  removeAll: function() {
    this.selector.options.length = 0;
  },
  
  indexOfText: function(text) {
    var opts = this.selector.options;
    var sz = opts.length;
    for (var i = 0; i < sz; i ++) {
      if (text == opts[i].text) {
	return i;
      }
    }
    return -1;
  },
  
  indexOfValue: function(value) {
    var opts = this.selector.options;
    var sz = opts.length;
    for (var i = 0; i < sz; i ++) {
      if (value == opts[i].value) {
	return i;
      }
    }
    return -1;
  },
  
  selected2array: function() {
    var opts = this.selector.options;
    var sz = opts.length;
    var ret = new Array();
    for (var i = 0; i < sz; i ++) {
      if (opts[i].selected) {
	ret.push(opts[i].text);
      }
    }
    return ret;
  },
  
  selectOptions: function(texts, values, insertIndex, byValue) {
    for (var listIdx = 0; listIdx < (byValue ? values.length : texts.length); listIdx ++) {
      var i = byValue ? this.indexOfValue(values[listIdx]) : this.indexOfText(texts[listIdx]);
      if (i == -1) {
	if (insertIndex >= 0) {
	  this.insert(insertIndex, text, value);
	  this.selector.selectedIndex = insertIndex;
	}
      } else {
	this.selector.selectedIndex = i;
      }
    }
  },
  
  selectOption: function(text, value, insertIndex, byValue) {
    var i = byValue ? this.indexOfValue(value) : this.indexOfText(text);
    if (i == -1) {
      if (insertIndex >= 0) {
	this.insert(insertIndex, text, value);
	this.selector.selectedIndex = insertIndex;
      }
    } else {
      this.selector.selectedIndex = i;
    }
  },
  
  cutAndPasteSelectedTo: function(destinationSelector) {
    var opts = this.selector.options;
    var i = 0; 
    var ret = false;
    destinationSelector.selectNone();
    while (i < opts.length) {
      if (opts[i].selected) {
	this.add(destinationSelector.selector.options, opts[i].text, opts[i].value);
	opts[i] = null;
	ret = true;
      } else {
	i ++;
      }
    }
    return ret;
  },
  
  copySelectedTo: function(destinationSelector, duplicatesOK) {
    var srcopts = this.selector.options;
    var destopts = destinationSelector.selector.options;
    destinationSelector.selectNone();  // we're going to modify the selection, so start clean
    for (var i = 0; i < srcopts.length; i ++) {
      if (srcopts[i].selected) {
	destinationSelector.add(srcopts[i].text, srcopts[i].value, true, duplicatesOK);
      }
    }
  },
  
  pasteFromSelected: function(sourceSelector, duplicatesOK) {
    var srcopts = sourceSelector.selector.options;
    var destopts = this.selector.options;
    this.selectNone();  // we're going to modify the selection, so start clean
    for (var i = 0; i < srcopts.length; i ++) {
      if (srcopts[i].selected) {
	this.add(srcopts[i].text, srcopts[i].value, true, duplicatesOK);
      }
    }
  }
};

//------------------------------------------------------------------------
// traction.edit.InlineComment

var inlinecomment_ = {};
inlinecomment_.focusForm = null;
inlinecomment_.unloadEventTrapped = false;
inlinecomment_.formCount = 0;
inlinecomment_.registry = new Array();
inlinecomment_.nextTabIndex = 110;
inlinecomment_.supportAutoRestore = true;

inlinecomment_.registerForm = function(formID, targetPublished, randomVal, autoFocus) {
  if (window.onbeforeunload != inlinecomment_.preventAccidentalLoss) {
    window.onbeforeunload = inlinecomment_.preventAccidentalLoss;
  }
  Debug.comment.println("Registering comment form with id = '", formID, "'");
  inlinecomment_.registry[formID] = new Traction.InlineCommentForm(document.getElementById(formID), targetPublished, randomVal, autoFocus);
  inlinecomment_.formCount ++;
};
inlinecomment_.unregisterForm = function(icf) {
  Debug.comment.println("Unregistering comment form with id = '", icf.formElm.id, "'");
  if (inlinecomment_.registry[icf.formElm.id]) {
    inlinecomment_.registry[icf.formElm.id].alreadySubmitted = true;;
    inlinecomment_.registry[icf.formElm.id] = null;
    inlinecomment_.formCount --;
  }
};
inlinecomment_.unregisterFormID = function(formID) {
  Debug.comment.println("Unregistering comment form with id = '", formID, "'");
  inlinecomment_.registry[formID] = null;
}
inlinecomment_.preventAccidentalLoss = function() {
  if (inlinecomment_.formCount == 0) {
    return;
  }
  Traction.Edit.TinyMce.saveRichText(true, true);
  var hasInProgressComments = false;
  var autoSaveDisabled;
  for (formID in inlinecomment_.registry) {
    if (formID == "extend") {
      continue;
    }
    autoSaveDisabled = Traction.Edit.Util.autoSaveDisabled(inlinecomment_.registry[formID].formElm);
    break;
  }
  for (formID in inlinecomment_.registry) {
    if (formID == "extend") {
      continue;
    }
    var icf = inlinecomment_.registry[formID];
    if (icf && icf.formElm) {
      var textarea = icf.formElm["edit_content"];
      if (textarea != null) {
	if (!(trim(textarea.value) == "" || // no content / no plain text content
	      (Traction.Edit.TinyMce.useRichText() && Traction.Edit.TinyMce.isHTMLWhiteSpace(textarea.value))) && // no rich text content
	    (autoSaveDisabled || icf.formStateChanged(false))) { // needs autosave
	  hasInProgressComments = true;
	  Debug.edit.println("Found open in-progress comment whose autosave is not up to date ",
			     icf.formElm.id);
	  break;
	}
      }
    }
  }
  if (window.event &&
      document.activeElement &&
      document.activeElement.tagName == "A" &&
      document.activeElement.href) {
    var triggerHref = document.activeElement.href.trim().toLowerCase();
    if ((triggerHref.indexOf("javascript:") == 0) &&
	(triggerHref.indexOf("'_top'") == -1)) {
      return;
    }
  }
  if (!hasInProgressComments) {
    return; // don't return a value, or else the confirmation dialog pops up unnecessarily.
  }
  return i18n("comment_alert_message_confirm_discard_open_comments_page_navigation",
	      "You have started typing a comment but have not posted it.\n\n" +
	      "If you navigate to another page, you will lose any comments you have started.");
};
inlinecomment_.waitforload_onsubmit = function() {
  if (inlinecomment_.focusForm == null) {
    return false;
  }
  return inlinecomment_.registry[inlinecomment_.focusForm.id].isValidComment();
};
Traction.InlineCommentForm = Class.create();
Traction.InlineCommentForm.updateProjectInfo = function(formElm) {
  inlinecomment_.registry[formElm.id].updateProjectInfo();
};
Traction.InlineCommentForm.submit = function(formElm) {
  inlinecomment_.registry[formElm.id].submit();
};
Traction.InlineCommentForm.close = function(formElm) {
  inlinecomment_.registry[formElm.id].close();
};
Traction.InlineCommentForm.cancel = function(formElm, restoreForm) {
  inlinecomment_.registry[formElm.id].cancel(restoreForm);
};
Traction.InlineCommentForm.openCommentView = function(formElm, actionURL, actionURLLoadDraft) {
  inlinecomment_.registry[formElm.id].openCommentView(actionURL, actionURLLoadDraft);
};
Traction.InlineCommentForm.onPublishStatusChange = function(formElm) {
  inlinecomment_.registry[formElm.id].onPublishStatusChange();
};
Traction.InlineCommentForm.onProjectChange = function(formElm) {
  inlinecomment_.registry[formElm.id].onProjectChange();
};
Traction.InlineCommentForm.expandContentArea = function(formId) {
  inlinecomment_.registry[formId].expandContentArea();
};
Traction.InlineCommentForm.prototype = {
  
  formElm: null,
  labelSelector: null,
  projectSelector: null,
  requireLabel: false,
  requireVisitorName: false,
  requireVisitorEmail: false,
  encodedTargetID: null,
  autoSaveInProgress: false,
  autoSaveReady: false,
  autoSaveData: new Array(),
  autoSaveWaiting: false,
  alreadySubmitted: false,
  submitInProgress: false,
  operationWaiting: null,
  targetPublished: false,
  initialize: function(formElement, targetPublished, randomVal, autoFocus) {
    closeAttachmentsDialog = Traction.Edit.StandardView.prototype.onEditAttachments;
    Debug.comment.println("Initialize comment form " + formElement.id);
    this.formElm = formElement;
    this.labelSelector   = new Traction.Select(formElement["edit_labels0"].id,
					       { submitMode: Traction.Select.MODE_NORMAL });
    this.projectSelector = new Traction.Select(formElement["edit_project"].id,
					       { submitMode: Traction.Select.MODE_NORMAL });
    var requireLabelField = formElement["comment_label_force_choice"]
    this.requireLabel    = requireLabelField ? (requireLabelField.value == "true") : false;
    this.showLabelSelector = (this.labelSelector.selector.parentNode.style.display != "none");
    var requireVisitorNameField = formElement["comment_force_visitor_name"];
    this.requireVisitorName = requireVisitorNameField ? (requireVisitorNameField.value == "true") : false;
    var requireVisitorEmailField = formElement["comment_force_visitor_email"];
    this.requireVisitorEmail = requireVisitorEmailField ? (requireVisitorEmailField.value == "true") : false;
    this.encodedTargetID = this.formElm.id.substring(0, this.formElm.id.length - 3);
    this.targetPublished = targetPublished;
    this.onProjectChange();
    this.initRichText(randomVal, autoFocus);
    var t = this;
    var postButton = this.formElm["add comment"];
    if (postButton != null) {
      postButton.onclick = this.submit.bind(this);
    }
    var restoreForm = (this.formElm["entryaddcomment"].value == "true");
    var cancelLink = document.getElementById(this.encodedTargetID + "cancel");
    if (cancelLink) {
      cancelLink.onclick = function() { t.cancel(restoreForm); return false; };
    }
    var expandLink = document.getElementById(this.encodedTargetID + "expandlink");
    if (expandLink) {
      expandLink.onclick = function() { t.expandContentArea(); return false; };
    }
    this.projectSelector.selector.onchange = this.onProjectChange.bind(this);
    this.formElm["publish_on_submit_chk"].onchange = this.onPublishStatusChange.bind(this);
    var fn = this.setupAutoSave.bind(this);
    setTimeout(function() {
		 fn();
	       }, 100);
  },
  
  initRichText: function(randomVal, autoFocus) {
    Debug.richtext.println("inlinecomment_.initRichText(\"" + this.encodedTargetID + "\")");
    if (!Traction.Edit.TinyMce.useRichText()) {
      Debug.richtext.println("inlinecomment_ not using rich text");
      return;
    }
    var contentFieldId = this.getTinyMceEditorId(randomVal);
    var locale = i18n("core_locale", "en");
    var docs_locale = (locale == "ja" ? "en" : locale);
    var options = {
      mode: "exact",
      elements: contentFieldId,
      language : "en",
      docs_language: docs_locale,
      theme_advanced_resizing: true,
      theme_advanced_resizing_min_height: 200,
      theme_advanced_resizing_use_cookie: false,
      theme_advanced_resize_horizontal: false,
      plugins : "safari,table,compacttablecontrols,alignmentselect,styledformatselect,tractionimage,tractionlink,tractionwidget,iespell,contextmenu,paste,directionality,fullscreen,noneditable,contextmenuextras,controlsetmodes,compactpastecontrols",
      setup: Traction.Edit.TinyMce.setupTinyMce,
      width: "100%",
      height: "250"
    };
    if (autoFocus) {
      options.auto_focus = contentFieldId;
    }
    Traction.Edit.TinyMce.initRichText(options);
  },
  editorId: null,
  getTinyMceEditorId: function(randomVal) {
    if (!this.editorId) {
      this.editorId = this.encodedTargetID + "ac";
      if (randomVal) {
	this.editorId += "_" + randomVal;
      }
    }
    return this.editorId;
  },
  getTinyMceEditor: function() {
    return tinyMCE.getInstanceById(this.getTinyMceEditorId());
  },
  clearRichTextField: function() {
    var ed = this.getTinyMceEditor();
    if (ed) {
      ed.getBody().innerHTML = "";
    }
  },
  
  hasUninitializedRichText: function() {
    if (typeof(tinyMCE) == "undefined" ||
	tinyMCE == null) {
      return false;
    }
    if (!Traction.Edit.TinyMce.useRichText()) {
      return false;
    }
    var ed = this.getTinyMceEditor();
    return !ed || (ed.getDoc() == null) || (ed.getBody() == null) || (ed.formElement == null);
  },
  expandContentArea: function() {
    var height = outer_height(this.formElm["edit_content"]);
    this.formElm["edit_content"].style.height = px(height*2);
    notifyDOMchanged();
  },
  setupAutoSave: function() {
    this.autoSaveInProgress = false;
    if (Traction.Edit.Util.autoSaveDisabled(this.formElm)) {
      return;
    }
    var popupLink = document.getElementById(this.encodedTargetID + "ow");
    if (popupLink) {
      show_inline(true, popupLink);
    }
    if (!this.autoSaveReady) {
      if (this.hasUninitializedRichText()) {
	Debug.comment.println("DELAYING auto save setup due to unfinished TinyMCE loading.");
	var tryagain = this.setupAutoSave.bind(this);
	setTimeout(function() {
		     tryagain();
		   },
		   250);
	return;
      }
      if (Traction.Edit.AUTOSAVE_INTERVAL_MS < 5000) {
	Traction.Edit.AUTOSAVE_INTERVAL_MS = 5000;
      }
      this.onBeforeAutoSaveTest();
      this.formStateChanged(true);
      this.autoSaveReady = true;
    }
    var fn = this.autoSave.bind(this);
    setTimeout(function() {
		 fn();
	       },
	       Traction.Edit.AUTOSAVE_INTERVAL_MS);
    Debug.edit.println("Auto save set up for inline comment on ",
		       this.encodedTargetID,
		       " at ",
		       (Traction.Edit.AUTOSAVE_INTERVAL_MS / 1000), "s interval.");
  },
  updateProjectInfo: function() {
    var poststring = "";
    var projName = this.projectSelector.currentValue();
    poststring = fm_append(poststring, "type=ajaxrpc");
    poststring = fm_append(poststring, "method=getProjectCommentInfo");
    poststring = fm_append(poststring, "p0=" + encode_url_parameter(projName));
    xmlpost_async(FORM_ACTION_READ_WRITE, poststring, null, true, this._updateProjectInfo_wakeup.bind(this), projName);
  },
  _updateProjectInfo_wakeup: function(content, projName) {
    if (wasUnauthorized(content)) {
      failureUnauthorized();
      this.resetProject();
      return false; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    if (wasUnreachable(content)) {
      failureServerUnavailable();
      this.resetProject();
      return false; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    var error = findErrorAndFeedback(content)[0];
    if (error != null && error != "" && error != "undefined") {
      alert(error);
      this.resetProject();
      return false; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    var projectCommentInfo = eval("(" + content + ")");
    Debug.comment.println("getProjectCommentInfo response: ", content);
    var newLabels = projectCommentInfo["labels"];
    var defaultLabel = projectCommentInfo["comment_label_default"];
    var requireLabel = projectCommentInfo["comment_label_force_choice"];
    var showLabelSelector = projectCommentInfo["comment_label_show_selector"];
    this.requireVisitorName = projectCommentInfo["comment_force_visitor_name"];
    this.requireVisitorEmail = projectCommentInfo["comment_force_visitor_email"];
    if (typeof(Traction.Edit.projname2publishinfo[projName]) || Traction.Edit.projname2publishinfo[projName] == null) {
      Traction.Edit.projname2publishinfo[projName] = projectCommentInfo["pubDraftInfo"];
      Debug.comment.println(projName, ": { supported: ", Traction.Edit.projname2publishinfo[projName].supported,
			    ", byDefault: ", Traction.Edit.projname2publishinfo[projName].byDefault,
			    ", canChoose: ", Traction.Edit.projname2publishinfo[projName].canChoose,
			    ", publishOwnPermission: ", Traction.Edit.projname2publishinfo[projName].publishOwnPermission,
			    ", publishPermission: ", Traction.Edit.projname2publishinfo[projName].publishPermission, " }");
    }
    var blankOption = this.labelSelector.selector.options[0];
    var newOptions = new Array();
    newOptions[0] = blankOption;
    if (defaultLabel == "") {
      newOptions[0].selected = true;
    }
    var qualifierPrefix = this.projectSelector.currentValue() + ":";
    for (var i = 0; i < newLabels.length; i ++) {
      var val;
      if (newLabels[i].indexOf(" ") != -1) {
	val = qualifierPrefix + "\"" + newLabels[i] + "\"";
      } else {
	val = qualifierPrefix + newLabels[i];
      }
      newOptions[i + 1] = new Option(newLabels[i], val);
      if (newLabels[i] == defaultLabel) {
	newOptions[i + 1].selected = true;
      }
    }
    this.labelSelector.setOptions(newOptions);
    this.showLabelSelector = showLabelSelector;
    this.labelSelector.selector.parentNode.style.display = showLabelSelector ? "" : "none";
    this.requireLabel = requireLabel;
    return true;
  },
  onProjectChange: function() {
    this.updateProjectInfo();
    this.onPublishStatusDirty();
  },
  onPublishStatusChange: function() {
    var pubInfo = Traction.Edit.projname2publishinfo[this.projectSelector.currentValue()];
    if (pubInfo != null) {
      Debug.comment.println("onPublishStatusChange");
      if (pubInfo.supported && pubInfo.publishOwnPermission && this.targetPublished) {
	this.formElm.edit_publish_on_submit.value = this.formElm.publish_on_submit_chk.checked ? "true" : "false";
	this.formElm.publish_on_submit_default.value = "false";
	show_default(true, getParentByName(this.formElm.edit_publish_on_submit, "SPAN"));
      } else {
	show_default(false, getParentByName(this.formElm.edit_publish_on_submit, "SPAN"));
	this.formElm.edit_publish_on_submit.value = "false";
	this.formElm.publish_on_submit_chk.checked = false;
      }
    }
    else {
      var fn = this.onPublishStatusChange.bind(this);
      setTimeout(function() {
		   Debug.comment.println("Waiting for update to project comment info before onPublishStatusChange");
		   fn();
		 }, 250);
    }
  },
  
  onPublishStatusDirty: function() {
    if (this.formElm.edit_publish_on_submit) {
      var pubInfo = Traction.Edit.projname2publishinfo[this.projectSelector.currentValue()];
      if (pubInfo != null) {
	Debug.comment.println("onPublishStatusDirty");
	if (pubInfo.supported && pubInfo.publishOwnPermission && this.targetPublished) {
	  if (pubInfo.canChoose) {
	    show_default(true, getParentByName(this.formElm.edit_publish_on_submit, "SPAN"));
	    if (this.formElm.publish_on_submit_default.value == "true") {
	      this.formElm.publish_on_submit_chk.checked = pubInfo.byDefault;
	      this.formElm.edit_publish_on_submit.value = pubInfo.byDefault ? "true" : "false";
	    }
	  }
	  else {
	    show_default(false, getParentByName(this.formElm.edit_publish_on_submit, "SPAN"));
	    if (this.formElm.publish_on_submit_default.value == "true") {
	      this.formElm.publish_on_submit_chk.checked = pubInfo.byDefault;
	      this.formElm.edit_publish_on_submit.value = pubInfo.byDefault ? "true" : "false";
	    }
	  }
	}
	else {
	  show_default(false, getParentByName(this.formElm.edit_publish_on_submit, "SPAN"));
	  this.formElm.publish_on_submit_chk.checked = false;
	  this.formElm.edit_publish_on_submit.value = "false";
	}
      }
      else {
	var fn = this.onPublishStatusDirty.bind(this);
	setTimeout(function() {
		     Debug.comment.println("Waiting for update to project comment info before onPublishStatusDirty");
		     fn();
		   }, 250);
      }
    }
  },
  resetProject: function() {
    var proj = this.labelSelector.selector.options[1].value;
    this.projectSelector.selectValue(proj.substring(0, proj.indexOf(":", 2)));
  },
  go: function(execFunction) {
    if (this.autoSaveInProgress) {
      this.operationWaiting = execFunction;
    } else {
      execFunction();
    }
  },
  submit: function() {
    this.go(this._submit.bind(this));
  },
  _submit: function() {
    this.submitInProgress = true;
    this.formElm.edit_action.value = "submit";
    var inlinesubmit = (this.formElm["inlinecomment"].value == "true" &&
			ua("supports_xmlhttp", "false") == "true");
    if (inlinesubmit) {
      this._submitInline();
    } else {
      inlinecomment_.focusForm = this.formElm;
      var tmp_wflos = waitforload_onsubmit;
      waitforload_onsubmit = inlinecomment_.waitforload_onsubmit;
      go("Custom Action", this.formElm);
      waitforload_onsubmit = tmp_wflos;
      inlinecomment_.focusForm = null;
    }
  },
  close: function() {
    var textarea = this.formElm["edit_content"];
    if (textarea == null) {
      return; // what were they closing?
    }
    Traction.Edit.TinyMce.saveRichText(true, true);
    var OKtoclose;
    if (!(trim(textarea.value) == "" || // no content / no plain text content
	  (Traction.Edit.TinyMce.useRichText() && Traction.Edit.TinyMce.isHTMLWhiteSpace(textarea.value)))) { // no rich text content
      OKtoclose = confirm(i18n("comment_alert_message_confirm_close",
			       "Are you sure you want to close this comment?"));
    } else {
      OKtoclose = true;
    }
    if (!OKtoclose) {
      return;
    }
    commentform_remove( this.encodedTargetID );
    inlinecomment_.unregisterForm(this);
  },
  draftInProgress: function() {
    return (this.formElm.edit_working_draft_id &&
	    this.formElm.edit_delete_draft_on_cancel &&
	    this.formElm.edit_delete_draft_ids);
  },
  cancel: function(restoreForm) {
    this.submitInProgress = true;
    restoreForm &= inlinecomment_.supportAutoRestore;
    Debug.xmlhttp.println("InlineCommentForm.cancel(", restoreForm, ") (form.id = ", this.formElm.id, ")");
    var textarea = this.formElm["edit_content"];
    if (textarea == null) {
      return; // what were they closing?
    }
    Traction.Edit.TinyMce.saveRichText(true, true);
    var OKtoclose;
    var doCancel;
    if (this.draftInProgress() &&
	confirm(i18n("editentry_alert_message_delete_draft_on_cancel_confirmation",
		     "Do you want to delete the draft you're working on?\n\n" +
		     "Press OK to delete this draft.\nPress Cancel to keep it."))) {
      this.formElm.edit_action.value = "deletedraft";
      this.formElm.edit_delete_draft_ids.value = this.formElm.edit_working_draft_id.value;
      this.formElm.edit_delete_draft_on_cancel.value = "true";
      OKtoclose = true;
      doCancel = true;
    }
    else if (!(trim(textarea.value) == "" || // no content / no plain text content
	       (Traction.Edit.TinyMce.useRichText() && Traction.Edit.TinyMce.isHTMLWhiteSpace(textarea.value)))) { // no rich text content
      OKtoclose = confirm(i18n("comment_alert_message_confirm_close",
			       "Are you sure you want to close this comment?"));
      doCancel = !this.draftInProgress();
      if (this.draftInProgress()) {
	this.formElm.edit_delete_draft_ids.value = "";
	this.formElm.edit_delete_draft_on_cancel.value = "false";
      }
    }
    else {
      OKtoclose = true;
      doCancel = false;
    }
    if (!OKtoclose) {
      this.submitInProgress = false;
      return;
    }
    if (doCancel) {
      var img = document.getElementById(this.formElm.id.toLowerCase() + "loading");
      if (img) {
	img.src = "/images/loading_on.gif";
      }
      this.formElm.edit_action.value = "cancel";
      var poststring = fm_submit(this.formElm);
      poststring = fm_append(poststring, "rs=type+cancelconfirmation");
      if (document.fm && document.fm.proj) {
	poststring = fm_append(poststring, "entry_project=" + encode_url_parameter(document.fm.proj.value));
	poststring = fm_append(poststring, "stickyparams=entry_project");
      }
      xmlpost_async(FORM_ACTION_READ_WRITE, poststring, img, false, this.confirmCancellation.bind(this), restoreForm);
    }
    else {
      Debug.edit.println("Removing inline comment form.");
      this.remove();
      this.submitInProgress = false;
      if (restoreForm) {
	commentform_insert("entry", this.encodedTargetID, "&entryaddcomment=true");
      }
    }
  },
  remove: function() {
    commentform_remove(this.encodedTargetID);
    inlinecomment_.unregisterForm(this);
  },
  confirmCancellation: function(content, restoreForm) {
    Debug.edit.println("Arrived in confirmSubmission.");
    var error = null;
    if (wasUnauthorized(content)) {
      error = i18n("xmlrequest_unauthorized_alert_message",
		   "You are not authorized to perform the requested action with your current credentials.  " +
		   "Please open another browser window, sign in, and then try the action again.");
    } else if (wasUnreachable(content)) {
      error = i18n("xmlrequest_failed_alert_message",
		   "The request failed because the server could not be reached.  Please ensure that your " +
		   "network connection has not been interrupted and that the server is still online.");
    } else {
      var findError = findErrorAndFeedback(content);
      if (findError[0] != null && findError[0] != "" && findError[0] != "undefined") {
	error = findError[0];
      } else {
	var container = getParentByName(this.formElm, "DIV");
	var opttype = (this.formElm.entryaddcomment.value == "true") ? "entry" : "item";
	commentform_remove(this.encodedTargetID);
	inlinecomment_.unregisterForm(this);
	if (restoreForm) {
	  commentform_insert(opttype, this.encodedTargetID, "&entryaddcomment=true");
	}
      }
    }
    if (error != null) {
      var container = document.getElementById(this.encodedTargetID + "commenterror");
      if (container != null) {
	show_block(true, container);
	container.innerHTML = error;
      }
      var cbutton = this.formElm["add comment"];
      if ( cbutton != null ) {
	cbutton.disabled = false;
      }
    }
    this.submitInProgress = false;
  },
  openCommentView: function(actionURL, actionURLLoadDraft) {
    Debug.edit.println("Opening comment view from inline comment for ", this.formElm.id);
    if (!Traction.Edit.Util.autoSaveDisabled(this.formElm)) {
      this.onBeforeAutoSaveTest();
      if (this.shouldAutoSave()) {
	Debug.edit.println("Auto saving...");
	this.submitInProgress = true; // sort of...
	this.autoSaveInProgress = true;
	this.setAutoSaveStatusMessage(i18n("editentry_autosave_in_progress_status_message",
					   "Autosave in progress..."));
	var args = {
	  dateString: (new Date()).formatDate("g:i:s a"),
	  actionURL: actionURLLoadDraft
	};
	xmlpost_async(FORM_ACTION_READ_WRITE,
		      this.getAutoSaveURL(),
		      document.getElementById(this.encodedTargetID + "loading"),
		      true,
		      this.openCommentViewAutoSave_wakeup.bind(this),
		      args);
      } else {
	Debug.edit.println("Auto save skipped.");
	if (this.formElm.edit_working_draft_id) {
	  Debug.edit.println("Loading working draft.");
	  eval((new MessageFormat(actionURLLoadDraft)).format(this.formElm.edit_working_draft_id.value));
	} else {
	  eval(actionURL.substring(11));
	}
	this.alreadySubmitted = true; // sort of; shuts off autosave, which is good.
      }
    }
  },
  openCommentViewAutoSave_wakeup: function(content, args) {
    Debug.edit.println("Auto save returned.");
    var ok;
    if (wasUnauthorized(content)) {
      this.setAutoSaveStatusMessage((new MessageFormat(i18n("editentry_autosave_failed_status_message",
							    "Autosave FAILED at {0}"))).format(args.dateString) +
				    ":  " +
				    i18n("editentry_autosave_failed_unauthorized_message",
					 "unauthorized."));
      failureUnauthorized();
      ok = false;
    } else if (wasUnreachable(content)) {
      this.setAutoSaveStatusMessage((new MessageFormat(i18n("editentry_autosave_failed_status_message",
							    "Autosave FAILED at {0}"))).format(args.dateString) +
				    ":  " +
				    i18n("editentry_autosave_failed_server_unreachable_message",
					 "server unreachable."));
      failureServerUnavailable();
      ok = false;
    } else {
      var error = findErrorAndFeedback(content);
      if (error[0] != null && error[0] != "" && error[0] != "undefined") {
	this.setAutoSaveStatusMessage((new MessageFormat(i18n("editentry_autosave_failed_status_message",
							      "Autosave FAILED at {0}"))).format(args.dateString) +
				      ":  " +
				      error[0]);
	ok = false;
      } else {
	ok = true;
	var dff = document.getElementById(this.encodedTargetID + "draftformfields");
	if (dff) {
	  dff.innerHTML = content;
	}
	this.setAutoSaveStatusMessage((new MessageFormat(i18n("editentry_autosave_success_status_message",
							      "Autosaved at {0}."))).format(args.dateString));
      }
    }
    if (ok) {
      this.alreadySubmitted = true; // sort of; shuts off autosave.
      var actionURL = args.actionURL.substring(11);
      var sourceForm = this.formElm;
      setTimeout(function() {
		   Debug.edit.println("Loading working draft.");
		   eval((new MessageFormat(actionURL)).format(sourceForm.edit_working_draft_id.value));
		 }, 100);
    }
    this.submitInProgress = false;
    this.autoSaveInProgress = false;
    this.alreadySubmitted = true;
  },
  _submitInline: function() {
    Debug.xmlhttp.println("InlineCommentForm._submitInline() (form.id = ", this.formElm.id, ")");
    if (!this.isValidComment()) {
      return false;
    }
    var cbutton = this.formElm["add comment"];
    if ( cbutton != null ) {
      cbutton.disabled = true;
    }
    var img = document.getElementById(this.formElm.id.toLowerCase() + "loading");
    if (img) {
      img.src = "/images/loading_on.gif";
    }
    var poststring = fm_submit(this.formElm);
    poststring = fm_append(poststring, "rs={0}+type+comment_");
    if (document.fm && document.fm.proj) {
      poststring = fm_append(poststring, "entry_project=" + encode_url_parameter(document.fm.proj.value));
      poststring = fm_append(poststring, "stickyparams=entry_project");
    }
    xmlpost_async(FORM_ACTION_READ_WRITE, poststring, img, false, this.confirmSubmission.bind(this));
    return true;
  },
  isValidComment: function() {
    if ( Traction.Edit.TinyMce.useRichText() ) {
      Traction.Edit.TinyMce.cleanUpAndSave(this.getTinyMceEditor(), false);
    } else {
      try {
	if (this.formElm.edit_simplify_on_submit.value != "false") {
	  this.formElm.edit_simplify_on_submit.value = "false";
	  this.formElm.edit_format.value = "text";
	}
      } catch (xcp) {  }
    }
    var valid = this.checkProject() && this.checkContent() && this.checkLabel() && this.checkVisitorInfo();
    if (!valid) {
      return false;
    }
    if (Traction.Edit.TinyMce.useRichText() && this.formElm.edit_attach_urls != null) {
      var imgurls = getExternalImageURLs(document.getElementById(this.getTinyMceEditorId() + "_ifr").contentWindow.document);
      if (imgurls == null) {
	return false;
      }
      this.formElm.edit_attach_urls.value = imgurls;
    }
    return true;
  },
  checkProject: function() {
    if (this.projectSelector.currentValue() != "") {
      return true;
    } else {
      alert(i18n("comment_alert_message_submit_choose_project",
		 "Please select a project to which this comment should be posted."));
      return false;
    }
  },
  checkContent: function() {
    var textarea = this.formElm["edit_content"];
    if (Traction.Edit.TinyMce.useRichText()) {
      var s = new String(textarea.value);
      if (s == "<br />\n<br />") {
	textarea.value = "";
	this.clearRichTextField();
      }
    }
    if (trim(textarea.value) == "" || // no content / no plain text content
	(Traction.Edit.TinyMce.useRichText() && Traction.Edit.TinyMce.isHTMLWhiteSpace(textarea.value))) { // no rich text content
      alert(i18n("editformparser_error_message_no_title_and_content_comment",
		 "Please supply some content for this comment."));
      return false;
    } else {
      return true;
    }
  },
  checkLabel: function() {
    if (this.requireLabel && this.showLabelSelector && (this.labelSelector.currentValue() == "")) {
      alert((new MessageFormat(i18n("editentry_alert_message_submit_comment_choose_label",
				    "Comments posted to the {0} project require a label.  Please choose a label from the label list."))
	     .format(this.projectSelector.currentText())));
      this.labelSelector.selector.focus();
      return false;
    }
    return true;
  },
  checkVisitorInfo: function() {
    if (IS_VISITOR) {
      if (this.requireVisitorName) {
	var visitorNameField = this.formElm["edit_property_author_name"];
	if (visitorNameField && !visitorNameField.value.trim().match(/.+/)) {
	  alert(i18n("editentry_alert_message_submit_comment_enter_name",
		     "You must enter your name with this comment."));
	  visitorNameField.focus();
	  return false;
	}
      }
      if (this.requireVisitorEmail) {
	var visitorEmailField = this.formElm["edit_property_author_email"];
	if (visitorEmailField && !visitorEmailField.value.trim().match(/^[^@]+(@[^@]+)?$/)) {
	  alert(i18n("editentry_alert_message_submit_comment_enter_email",
		     "You must enter an email address with this comment."));
	  visitorEmailField.focus();
	  return false;
	}
      }
    }
    return true;
  },
  focus: function() {
    var ed = this.getTinyMceEditor();
    if (ed) {
      ed.focus();
    }
  },
  confirmSubmission: function(content) {
    Debug.edit.println("Arrived in confirmSubmission.");
    var error = null;
    if (wasUnauthorized(content)) {
      error = i18n("xmlrequest_unauthorized_alert_message",
		   "You are not authorized to perform the requested action with your current credentials.  " +
		   "Please open another browser window, sign in, and then try the action again.");
    } else if (wasUnreachable(content)) {
      error = i18n("xmlrequest_failed_alert_message",
		   "The request failed because the server could not be reached.  Please ensure that your " +
		   "network connection has not been interrupted and that the server is still online.");
    } else {
      var findError = findErrorAndFeedback(content);
      if (findError[0] != null && findError[0] != "" && findError[0] != "undefined") {
	error = findError[0];
      } else {
	var container = getParentByName(this.formElm, "DIV");
	var displayNewForm = (this.formElm["entryaddcomment"].value == "true");
	var args = [ this.encodedTargetID, container, displayNewForm ];
	comment_insert(content, args);
	inlinecomment_.unregisterForm(this);
      }
    }
    if (error != null) {
      var container = document.getElementById(this.encodedTargetID + "commenterror");
      if (container != null) {
	show_block(true, container);
	container.innerHTML = error;
      }
      var cbutton = this.formElm["add comment"];
      if ( cbutton != null ) {
	cbutton.disabled = false;
      }
    }
    this.submitInProgress = false;
  },
  shouldAutoSave: function(url) {
    if (waitforload_preSubmit || waitforload_startedSubmit || this.submitInProgress) {
      return false;
    }
    if (this.autoSaveWaiting) {
      return true;
    }
    if ((this.formElm.edit_content == null || Traction.Edit.TinyMce.isHTMLWhiteSpace(this.formElm.edit_content.value)) &&
	(this.formElm.edit_title   == null || trim(this.formElm.edit_title.value) == "") &&
	(this.formElm.edit_labels  == null || trim(this.formElm.edit_labels.value) == "") &&
	(this.formElm.edit_verbs   == null || trim(this.formElm.edit_verbs.value) == "") &&
	(this.formElm.edit_attachments == null || trim(this.formElm.edit_attachments.value) == "") &&
	(this.formElm.edit_sections == null || trim(this.formElm.edit_sections_encoded.value) == "")) {
      Debug.edit.println("All of the core fields are blank.");
      return false;
    }
    return this.formStateChanged(true);
  },
  formStateChanged: function(saveData) {
    var ret = false;
    var newData = new Array();
    for (var fieldName in Traction.Edit.AUTOSAVE_FIELD_NAMES) {
      if (fieldName == "extend") continue;
      var field = this.formElm[fieldName];
      if (field) {
	newData[fieldName] = trim(field.value);
	if (Traction.Edit.AUTOSAVE_FIELD_NAMES[fieldName]) {
	  if (Traction.Edit.TinyMce.isHTMLWhiteSpace(newData[fieldName])) {
	    newData[fieldName] = "";
	  }
	  if (this.autoSaveData[fieldName] != newData[fieldName]) {
	    Debug.edit.println("Rich text field ",
			       fieldName,
			       " has changed from '",
			       this.autoSaveData[fieldName],
			       "' to '",
			       newData[fieldName], "'.");
	    ret = true;
	  }
	}
	else {
	  if (this.autoSaveData[fieldName] != newData[fieldName]) {
	    Debug.edit.println("Plain text field ",
			       fieldName,
			       " has changed from '",
			       this.autoSaveData[fieldName],
			       "' to '",
			       newData[fieldName], "'.");
	    ret = true;
	  }
	}
      }
    }
    if (saveData) {
      this.autoSaveData = newData;
    }
    Debug.edit.println("Auto save data now updated for ", this.encodedTargetID);
    return ret;
  },
  getAutoSaveURL: function() {
    var changeFields = {
      "edit_action": { o: this.formElm.edit_action.value, n: "savedraft" },
      "edit_save_draft_auto": { o: this.formElm.edit_save_draft_auto.value, n: "true" }
    };
    for (var fieldName in changeFields) {
      if (fieldName == "extend") continue;
      this.formElm[fieldName].value = changeFields[fieldName].n;
    }
    var url = fm_submit(this.formElm);
    for (var fieldName in changeFields) {
      if (fieldName == "extend") continue;
      this.formElm[fieldName].value = changeFields[fieldName].o;
    }
    return url + "&rs={0}+type+draftlist_";
  },
  onBeforeAutoSaveTest: function() {
    Traction.Edit.TinyMce.cleanUpAndSave(this.getTinyMceEditor(), true);
  },
  autoSave: function() {
    if (this.alreadySubmitted) {
      return;
    }
    Debug.edit.println("Arrived in autoSave...");
    if (this.formElm.edit_save_draft_auto) {
      Debug.edit.println("Auto saving...");
      this.onBeforeAutoSaveTest();
      if (this.shouldAutoSave()) {
	this.autoSaveInProgress = true;
	this.setAutoSaveStatusMessage(i18n("editentry_autosave_in_progress_status_message",
					   "Autosave in progress..."));
	xmlpost_async(FORM_ACTION_READ_WRITE,
		      this.getAutoSaveURL(),
		      document.getElementById(this.encodedTargetID + "loading"),
		      true,
		      this.autoSave_wakeup.bind(this),
		      (new Date()).formatDate("g:i:s a"));
      } else {
	Debug.edit.println("Auto save skipped.");
	this.setupAutoSave();
      }
    } else {
      Debug.edit.println("Required autosave field edit_save_draft_auto not present; shutting off autosave.");
    }
  },
  autoSave_wakeup: function(content, dateStr) {
    Debug.edit.println("Auto save returned.");
    var ok;
    if (wasUnauthorized(content)) {
      this.setAutoSaveStatusMessage((new MessageFormat(i18n("editentry_autosave_failed_status_message",
							    "Autosave FAILED at {0}"))).format(dateStr) +
				    ":  " +
				    i18n("editentry_autosave_failed_unauthorized_message",
					 "unauthorized."));
      ok = false;
    } else if (wasUnreachable(content)) {
      this.setAutoSaveStatusMessage((new MessageFormat(i18n("editentry_autosave_failed_status_message",
							    "Autosave FAILED at {0}"))).format(dateStr) +
				    ":  " +
				    i18n("editentry_autosave_failed_server_unreachable_message",
					 "server unreachable."));
      ok = false;
    } else {
      var error = findErrorAndFeedback(content);
      if (error[0] != null && error[0] != "" && error[0] != "undefined") {
	this.setAutoSaveStatusMessage((new MessageFormat(i18n("editentry_autosave_failed_status_message",
							      "Autosave FAILED at {0}"))).format(dateStr) +
				      ":  " +
				      error[0]);
	ok = false;
      } else {
	ok = true;
	var dff = document.getElementById(this.encodedTargetID + "draftformfields");
	if (dff) {
	  dff.innerHTML = content;
	}
	this.setAutoSaveStatusMessage((new MessageFormat(i18n("editentry_autosave_success_status_message",
							      "Autosaved at {0}."))).format(dateStr));
      }
    }
    this.autoSaveWaiting = !ok;
    this.setupAutoSave();
    if (this.operationWaiting) {
      this.operationWaiting();
    }
  },
  setAutoSaveStatusMessage: function(msg) {
    var statusContainer = document.getElementById(this.encodedTargetID + "autosavestatus");
    if (statusContainer != null) {
      statusContainer.innerHTML = msg;
    }
  }
};

//------------------------------------------------------------------------
// traction.edit.StandardView


Traction.Edit.StandardView = Class.create();

Traction.Edit.StandardView.TINY_MCE_OPTIONS = { };
Traction.Edit.StandardView.prototype = {
  
  onsubmitfunctions: new Array(),
  
  fieldValidationInfo: new Array(),
  
  action: "",
  
  contentFieldRegistry: new Array(),
  
  canAddAttachments: false,
  
  initialLinkByNames: false,
  
  authorIsEditing: false,
  
  autoSaveInProgress: false,
  
  autoSaveReady: false,
  
  autoSaveData: new Array(),
  
  autoSaveWaiting: false,
  
  workingWithDrafts: false,
  
  operationWaiting: null,
  
  standardFunctionsInitialized: false,
  
  nameValidationWaiting: false,
  
  nameValidationInProgress: false,
  
  nameUpdateInProgress: false,
  
  onNamesDirtyTries: 0,
  
  namesDialogWaiting: false,
  
  onEditNamesClickTries: 0,
  
  targetPublished: true,
  
  initialize: function(baseUrl) {
    Traction.Edit.TinyMce.initRichText(Traction.Edit.StandardView.TINY_MCE_OPTIONS);
    this.projname2naminginfo = Traction.Edit.projname2naminginfo;
    this.projname2publishinfo = Traction.Edit.projname2publishinfo;
    this.projname2addattachments = Traction.Edit.projname2addattachments;
    this.projname2entrystyle = Traction.Edit.projname2entrystyle;
    YAHOO.util.Event.addListener(window, "load", function() { this.init() }, null, this);
    YAHOO.util.Event.addListener(window, "load", function() { this.setBaseUrl(baseUrl) }, null, this);
    YAHOO.util.Event.addListener(window, "load", waitforload_ready);
    YAHOO.util.Event.addListener(window, "load", this.initDragAndDropTractionId, null, this);
  },
  
  init: function() {
    this.initAutoCompleteAddresses();
    this.initResizers();
    this.registerContentContainers();
    this.initStandardFunctions();
    this.initOnSubmitFunctions();
    this.updateDraftControlsDisplay();
    this.onChangeDraft();
    this.setupForwardLinkName();
    this.onProjectChange();
    if (typeof(this.init_labellist) != "undefined") {
      this.init_labellist();
    }
    if (typeof(this.getInitialAttachmentData) == "function") {
      var data = this.getInitialAttachmentData();
      this.updateAttachmentsDisplay(data.length, data);
    }
    var nameCount = 1;
    try {
      nameCount = parseInt(document.getElementById("namecount").innerHTML, 10);
    } catch (xcp) { }
    this.onNameStateChange(nameCount != 0);
    window.onbeforeunload = this.preventAccidentalClose.bind(this);
    this.setupAutoSave();
    if (document.fm.edit_title) {
      document.fm.edit_title.focus();
    }
  },
  initDragAndDropTractionId: function() {
    tid_setup_drop(document.getElementById('edit_content'));
  },
  
  registerContentContainers: function() {
    this.contentFieldRegistry.push(document.fm.edit_content);
  },
  
  initAutoCompleteAddresses: function() {
    if (document.fm.edit_email_to != null) {
      eac_setup(document.fm.edit_email_to);
    }
    if (document.fm.edit_email_cc != null) {
      eac_setup(document.fm.edit_email_cc);
    }
    if (document.fm.edit_email_bcc != null) {
      eac_setup(document.fm.edit_email_bcc);
    }
  },
  
  eac_accept: function(index) {
    if (eac_DIV != null && eac_DIV.style.display != "none") {
      eac_Element.value = eac_prefix + unescape_fromattribute(eac_hits[index][2]) + ", " + eac_suffix;
      eac_hide();
    }
  },
  getTinyMceEditor: function() {
    return tinyMCE.getInstanceById("edit_content");
  },
  hasUninitializedRichText: function() {
    if (typeof(tinyMCE) == "undefined" ||
	tinyMCE == null) {
      return false;
    }
    if (!Traction.Edit.TinyMce.useRichText()) {
      return false;
    }
    var ed = this.getTinyMceEditor();
    return !ed || (ed.getDoc() == null) || (ed.getBody() == null) || (ed.formElement == null);
  },
  
  setupAutoSave: function() {
    this.autoSaveInProgress = false;
    if (Traction.Edit.Util.autoSaveDisabled(document.fm)) {
      return;
    }
    var ef = this;
    if (!this.autoSaveReady) {
      if (this.hasUninitializedRichText()) {
	Debug.edit.println("DELAYING auto save setup due to unfinished TinyMCE loading.");
	setTimeout(function() {
		     ef.setupAutoSave();
		   },
		   250);
	return;
      }
      if (this.AUTOSAVE_INTERVAL_MS < 5000) {
	this.AUTOSAVE_INTERVAL_MS = 5000;
      }
      this.onBeforeAutoSaveTest();
      this.formStateChanged(true);
      this.autoSaveWaiting = false;
      this.autoSaveReady = true;
    }
    setTimeout(function() {
		 ef.autoSave();
	       },
	       Traction.Edit.AUTOSAVE_INTERVAL_MS);
    Debug.edit.println("Auto save set up for ", (Traction.Edit.AUTOSAVE_INTERVAL_MS / 1000), "s interval.");
  },
  
  setupForwardLinkName: function() {
    if (document.fm.edit_forward_link_name != null &&
	document.fm.edit_forward_link_name.value != "") {
      document.fm.link_by_names_default.value = "false";
    }
  },
  updateAttachmentsDisplay: function(count, attDataList) {
    var attachmentInfoDisplay = document.getElementById("attachments_display");
    if (attachmentInfoDisplay) {
      show_default((count > 0), attachmentInfoDisplay);
      var attachmentsUL = document.getElementById("attachments_list");
      if (attachmentsUL) {
	var removeChild = attachmentsUL.firstChild;
	while (removeChild != null) {
	  var nextChild = removeChild.nextSibling;
	  attachmentsUL.removeChild(removeChild);
	  removeChild = nextChild;
	}
	var next = 0;
	var displayedCount = 0;
	while (next < attDataList.length && displayedCount < 3) {
	  var curData = attDataList[next];
	  if (!curData.removed) {
	    var newLI = document.createElement("LI");
	    var fileIcon = document.createElement("IMG");
	    fileIcon.src = curData.iconInfo["imageurl"];
	    fileIcon.width = curData.iconInfo["width"];
	    fileIcon.height = curData.iconInfo["height"];
	    fileIcon.className = "fileicon";
	    newLI.appendChild(fileIcon);
	    var fileName = document.createElement("SPAN");
	    fileName.className = "filename";
	    fileName.appendChild(document.createTextNode(truncateWithEllipses(curData.fname, 15)));
	    newLI.appendChild(fileName);
	    attachmentsUL.appendChild(newLI);
	    displayedCount ++;
	  }
	  next ++;
	}
      }
    }
  },
  
  
  initResizers: function() {
    TitleResizer = Class.create();
    TitleResizer.prototype = {
      initialize: function() {
      },
      resize: function() {
	var edit_title = document.fm.edit_title;
	var partialwidth = 0;
	var titletable = document.getElementById("titletable");
	var TDs = titletable.getElementsByTagName("TD");
	for (var i=0; i<TDs.length-1; i++) {
	  partialwidth += outer_width(TDs[i]);
	}
	var remaining = client_width(document.body) - partialwidth;
	edit_title.style.width = (remaining) + "px";
      }
    };
    LabelsResizer = Class.create();
    LabelsResizer.prototype = {
      initialize: function() {
      },
      resize: function() {
	var edit_labels = document.fm.edit_labels;
	var detailstable = document.getElementById("details");
	var TDs = detailstable.getElementsByTagName("TD");
	var occupied = outer_width(TDs[1]) + outer_width(TDs[3]) + outer_width(TDs[4]);
	var remaining = client_width(document.body) - occupied;
	edit_labels.style.width = remaining + "px";
      }
    };
    var registered = false;
    var tablewidth = 0;
    var titletable = document.getElementById("titletable");
    if (titletable != null) {
      var TDs = titletable.getElementsByTagName("TD");
      for (var i = 0; i < TDs.length; i ++) {
	tablewidth += outer_width(TDs[i]);
      }
    }
    if (tablewidth > (client_width(document.body) + 5)) { // 5 is space IE always puts there
      register_resizable(new TitleResizer());
      registered = true;
    }
    var detailstable = document.getElementById("details");
    if (detailstable != null) {
      TDs = detailstable.getElementsByTagName("TD");
      var outerTableWidth = outer_width(TDs[0]) + outer_width(TDs[4]);
      if (outerTableWidth > (client_width(document.body) + 5)) {
	register_resizable(new LabelsResizer());
	registered = true;
      }
    }
    if (registered) {
      window.onresize = handle_resize;
      handle_resize();
    }
  },
  
  
  onEditLabels: function(labelArray) {
    var set = "";
    if (labelArray != null) {
      for (var i=0; i<labelArray.length; i++) {
	set += labelArray[i] + " ";
      }
    }
    document.fm.edit_labels.value = set;
  },
  
  onEditRelationships: function(dialogname, verbsString, targetsString, count) {
    document.fm.edit_verbs.value = verbsString;
    document.fm.edit_targets.value = targetsString;
    try {
      var countspan = document.getElementById("relationshipcount");
      if (countspan != null) {
	countspan.innerHTML = new String(count);
      }
    } catch (xcp) { }
    var showWhenNone = document.getElementById("relationships_add_link");
    if (showWhenNone) {
      show_default((count == 0), showWhenNone);
    }
    var showWhenSome = document.getElementById("relationships_edit_link");
    if (showWhenSome) {
      show_default((count > 0), showWhenSome);
    }
  },
  
  onEditAttachments: function(dialogname, newAttachmentsString, count, fm, attDataList) {
    if (fm == null) {
      fm = document.fm;
    }
    fm.edit_attachments.value = newAttachmentsString;
    try {
      var countspan = document.getElementById("attachmentcount");
      if (countspan != null) {
	countspan.innerHTML = new String(count);
      }
    } catch (xcp) { }
    var showWhenNone = document.getElementById("attachments_add_link");
    if (showWhenNone) {
      show_default((count == 0), showWhenNone);
    }
    var showWhenSome = document.getElementById("attachments_edit_link");
    if (showWhenSome) {
      show_default((count > 0), showWhenSome);
    }
    if (this.updateAttachmentsDisplay) {
      this.updateAttachmentsDisplay(count, attDataList);
    }
  },
  
  onEditSections: function(dialogname, newSectionsString, count, fm) {
    if (fm == null) {
      fm = document.fm;
    }
    fm.edit_sections_encoded.value = newSectionsString;
    try {
      var countspan = document.getElementById("sectioncount");
      if (countspan != null) {
	countspan.innerHTML = count;
      }
    } catch (xcp) { }
    var showWhenNone = document.getElementById("sections_add_link");
    if (showWhenNone) {
      show_default((count == 0), showWhenNone);
    }
    var showWhenSome = document.getElementById("sections_edit_link");
    if (showWhenSome) {
      show_default((count > 0), showWhenSome);
    }
  },
  
  onEditChildren: function(dialogname, newChildrenString, count, fm) {
    if (fm == null) {
      fm = document.fm;
    }
    fm.edit_children_encoded.value = newChildrenString;
    try {
      var countspan = document.getElementById("childrencount");
      if (countspan != null) {
	countspan.innerHTML = count;
      }
    } catch (xcp) { }
    var showWhenNone = document.getElementById("children_add_link");
    if (showWhenNone) {
      show_default((count == 0), showWhenNone);
    }
    var showWhenSome = document.getElementById("children_edit_link");
    if (showWhenSome) {
      show_default((count > 0), showWhenSome);
    }
  },
  
  onEditNamesClick: function(dialogName, extraParams, tryAgain) {
    if (document.fm.edit_project.value == "") {
      alert(i18n("editentry_alert_message_edit_names_choose_project",
		 "Please select a project first."));
      setTimeout(function() {
		   document.fm.edit_project.focus();
		 }, 100);
      return;
    }
    if ((typeof(tryAgain) == "undefined" || !tryAgain) &&
	this.namesDialogWaiting) {
      return;
    }
    this.namesDialogWaiting = true;
    var ef = this;
    if (this.nameValidationWaiting ||
	this.nameValidationInProgress ||
	this.nameUpdateInProgress) {
      setTimeout(function() {
		   if (ef.onEditNamesClickTries < 120) {
		     if (ef.onEditNamesClickTries == 0) {
		       setWaitCursor(true);
		     }
		     ef.onEditNamesClickTries ++;
		     ef.onEditNamesClick(dialogName, extraParams, true);
		   } else {
		     setWaitCursor(false);
		     ef.namesDialogWaiting = false;
		     return;
		   }
		 }, 250);
      return;
    }
    this.onEditNamesClickTries = 0;
    this.namesDialogWaiting = false;
    setWaitCursor(false);
    openNamesDialog(document.fm.edit_link_by_names.value,
		    document.fm.edit_link_by_names_locked.value,
		    document.fm.edit_names_encoded.value,
		    document.fm.edit_forward_link_name.value,
		    document.fm.edit_forward_link_project.value,
		    (document.fm.edit_edit_original_id ? document.fm.edit_edit_original_id.value : null),
		    ((document.fm.edit_project.value == '') ? '*' : document.fm.edit_project.value),
		    document.fm.edit_title.value,
		    dialogName,
		    extraParams);
  },
  
  onEditNames: function(dialogname, linkByNames, newNamesString, newTitle, count, hasConflicts, fromDialog, fm) {
    if (fm == null) {
      fm = document.fm;
    }
    Debug.edit.println("onEditNames: hasConflicts = " + hasConflicts);
    if (fromDialog) {
      document.fm.edit_link_by_names.value = linkByNames ? "true" : "false";
      document.fm.link_by_names_default.value = "false";
      if (document.fm.edit_link_by_names_chk) {
	document.fm.edit_link_by_names_chk.checked = linkByNames;
      }
    }
    if (document.fm.edit_title && newTitle) {
      document.fm.edit_title.value = newTitle;
    }
    fm.edit_names_encoded.value = newNamesString;
    try {
      var countspan = document.getElementById("namecount");
      if (countspan != null) {
	countspan.innerHTML = linkByNames ? new String(count) : "0";
      }
    }
    catch (xcp) { }
    Debug.edit.println("Set name count to ", count);
    var nameLinkCells = getElementsByClass("names-link", document.getElementsByTagName("TD"));
    Debug.edit.println("Found names-link cells ", nameLinkCells.length);
    for (var i = 0; i < nameLinkCells.length; i ++) {
      if (hasConflicts) {
	replaceClassName(nameLinkCells[i], "", "names-link-conflict");
	Debug.edit.println("Adding names-link-conflict class to ", nameLinkCells[i]);
      } else {
	replaceClassName(nameLinkCells[i], "names-link-conflict", "");
	Debug.edit.println("Removing names-link-conflict class to ", nameLinkCells[i]);
      }
    }
    var showWhenNone = document.getElementById("names_add_link");
    if (showWhenNone) {
      show_default((count == 0), showWhenNone);
    }
    var showWhenSome = document.getElementById("names_edit_link");
    if (showWhenSome) {
      show_default((count > 0), showWhenSome);
    }
    this.onNameStateChange(linkByNames);
  },
  
  onChangeDraft: function() {
    if (document.fm.loaddraft == null) {
      return;
    }
    document.fm.loaddraft.disabled = (document.fm.draftlist.value == "");
    if (document.fm.deletedraft) {
      document.fm.deletedraft.disabled =
	(document.fm.draftlist.value == "") ||
	(document.fm.draftlist.options[document.fm.draftlist.selectedIndex].className.indexOf("draftoption") == -1);
    }
  },
  
  onNameStateChange: function(linkByNames) {
    if (document.fm.edit_names_encoded == null ||
	document.fm.edit_link_by_names == null) {
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    if (document.fm.edit_edit_id != null &&
	this.projname2naminginfo[document.fm.edit_project.value].required) {
      virtuallyDisable(!linkByNames, document.fm.edit_title);
    } else {
      var namingInfo = this.projname2naminginfo[document.fm.edit_project.value];
      if (document.fm.edit_link_by_names_chk) {
	document.fm.edit_link_by_names_chk.disabled = namingInfo.required;
      }
      if (document.fm.link_by_names_default.value == "true") {
	if (document.fm.edit_edit_id == null &&
	    document.fm.edit_comment_id == null &&
	    document.fm.edit_emailreply_id == null &&
	    (document.fm.edit_forward_link_name == null ||
	     document.fm.edit_forward_link_name.value == "")) {
	  if (document.fm.edit_link_by_names_chk) {
	    document.fm.edit_link_by_names_chk.checked = namingInfo.byDefault;
	  }
	  document.fm.edit_link_by_names.value = namingInfo.byDefault ? "true" : "false";
	}
      }
      else if (namingInfo.required) {
	if (document.fm.edit_link_by_names_chk) {
	  document.fm.edit_link_by_names_chk.checked = true;
	}
	document.fm.edit_link_by_names.value = "true";
      }
    }
  },
  
  onCancel: function() {
    Debug.edit.println("onCancel");
    if (document.fm.edit_working_draft_id &&
	document.fm.edit_delete_draft_on_cancel &&
	this.onDeleteWorkingDraft(i18n("editentry_alert_message_delete_draft_on_cancel_confirmation",
				       "Do you want to delete the draft you're working on?\n\n" +
				       "Press OK to delete this draft.\nPress Cancel to keep it."))) {
      document.fm.edit_delete_draft_on_cancel.value = "true";
    }
    document.fm.edit_action.value = "cancel";
    document.fm.rs0.value = "type cancelconfirmation";
    return true;
  },
  
  onSimplifyHTML: function() {
    Traction.Edit.TinyMce.cleanUpAndSave(this.getTinyMceEditor(), false);
    document.fm.edit_action.value = "simplifycontent";
    return true;
  },
  
  onSubmitToJournal: function() {
    if (!this.validateProject(i18n("editentry_alert_message_submit_choose_project",
				   "Please select a project to which this entry should be posted."))) {
      return false;
    }
    if (!this.validateTitleOptional(i18n("editentry_confirmation_message_no_title",
					 "Are you sure you want to submit this article with no title?"))) {
      return false;
    }
    if (!this.validateContent(i18n("editformparser_error_message_no_title_and_content_comment",
				   "Please supply some content for this comment."),
			      i18n("editformparser_error_message_no_title_and_content",
				   "Please supply a title and/or some content for the entry."))) {
      return false;
    }
    if (!this.validateAttachments(i18n("editentry_confirmation_message_no_add_attachments_permission_in_project", "You are attempting to add attachments to this entry, but you do not have Add Attachments permission in this project.  If you continue, your attachments will be discarded.\n\nPress OK to proceed anyway.\n\nPress Cancel to choose a different project and try again."),
				  i18n("editentry_confirmation_message_no_add_attachments_permission_in_any_project", "You are attempting to add attachments to this entry, but you do not have Add Attachments permission in any project.  If you continue, your attachments will be discarded.\n\nPress OK to proceed anyway.\n\nPress Cancel to stop submission."))) {
      return false;
    }
    if (!this.validateCustomFields()) {
      return false;
    }
    document.fm.edit_action.value = "submit";
    this.setupRSForSubmission();
    this.setupSimplifyOnSubmit();
    this.setupAutoCapture();
    return true;
  },
  
  onSaveDraft: function() {
    Traction.Edit.TinyMce.cleanUpAndSave(this.getTinyMceEditor(), false);
    document.fm.edit_action.value = "savedraft";
    return true;
  },
  
  onLoadDraft: function() {
    document.fm.edit_action.value = "loaddraft";
    document.fm.edit_load_draft_id.value = document.fm.draftlist.value;
    return true;
  },
  
  onLoadTemplate: function() {
    document.fm.edit_action.value = "loadtemplate";
    document.fm.edit_load_template_id.value = document.fm.draftlist.value;
    return true;
  },
  onDeleteSelectedDraft: function(confirmationMsg) {
    if (typeof(confirmationMsg) == "undefined" ||
	confirmationMsg == null) {
      confirmationMsg = new MessageFormat(i18n("editentry_alert_message_delete_selected_draft_confirmation",
					       "Are you sure you want to delete the draft {0}?")).
        format(document.fm.draftlist.options[document.fm.draftlist.selectedIndex].text);
    }
    if (confirm(confirmationMsg)) {
      Traction.Edit.TinyMce.cleanUpAndSave(this.getTinyMceEditor(), false);
      document.fm.edit_action.value = "deletedraft";
      document.fm.edit_delete_draft_ids.value = document.fm.draftlist.options[document.fm.draftlist.selectedIndex].value;
      return true;
    } else {
      return false;
    }
  },
  
  onDeleteWorkingDraft: function(confirmationMsg) {
    if (typeof(confirmationMsg) == "undefined" ||
	confirmationMsg == null) {
      confirmationMsg = i18n("editentry_alert_message_delete_draft_confirmation",
			     "Are you sure you want to delete the draft you're working on?");
    }
    if (confirm(confirmationMsg)) {
      document.fm.edit_action.value = "deletedraft";
      document.fm.edit_delete_draft_ids.value = document.fm.edit_working_draft_id.value;
      return true;
    } else {
      return false;
    }
  },
  
  onEmailReply: function() {
    if (!this.onSubmitToJournal()) {
      return false;
    }
    return true;
  },
  
  onDeleteAlternativeDrafts: function() {
    return confirm(i18n("alternative_drafts_cleanup_confirmation_message",
			"Are you absolutely sure that you want to delete these alternative drafts?"));
  },
  
  
  validateProject: function(msg) {
    if (document.fm.edit_project.value == "") {
      alert(msg);
      setTimeout(function() {
		   document.fm.edit_project.focus();
		 }, 10);
      return false;
    }
    return true;
  },
  
  validateTitleOptional: function(msg) {
    if (trim(document.fm.edit_title.value) == "") {
      if (!confirm(msg)) {
	setTimeout(function() {
		     document.fm.edit_title.focus();
		   }, 10);
	return false;
      }
    }
    return true;
  },
  
  validateTitleRequired: function(msg) {
    if (trim(document.fm.edit_title.value) == "") {
      alert(msg);
      setTimeout(function() {
		   document.fm.edit_title.focus();
		 }, 10);
      return false;
    }
    return true;
  },
  
  validateContent: function(commentMsg, entryMsg) {
    Traction.Edit.TinyMce.cleanUpAndSave(this.getTinyMceEditor(), false);
    var trimmedcontent = trim(document.fm.edit_content.value);
    if (document.fm.edit_comment_id != null) {
      if (trimmedcontent == "" || // no content / no plain text content
	  (Traction.Edit.TinyMce.useRichText() && Traction.Edit.TinyMce.isHTMLWhiteSpace(document.fm.edit_content.value))) { // no rich text content
	alert(commentMsg);
	return false;
      }
    } else {
      if (trim(document.fm.edit_title.value) == "" && // no title
	  (trimmedcontent == "" || // no content / no plain text content
	   (Traction.Edit.TinyMce.useRichText() && Traction.Edit.TinyMce.isHTMLWhiteSpace(document.fm.edit_content.value)))) { // no rich text content
	alert(entryMsg);
	return false;
      }
    }
    return true;
  },
  
  validateAttachments: function(msgThisProject, msgAllProjects) {
    if (document.fm.edit_attachments != null && // attachments field is present
	document.fm.edit_edit_id == null && // not necessary for edits
	document.fm.edit_attachments.value != "" && // attachments are present
	!this.projname2addattachments[document.fm.edit_project.value]) { // no permission in this project
      if (this.canAddAttachments) {
	return confirm(msgThisProject);
      } else {
	return confirm(msgAllProjects);
      }
    }
    return true;
  },
  validateCustomFields: function() {
    for (var i = 0; i < this.fieldValidationInfo.length; i ++) {
      if ((this.fieldValidationInfo[i].name != null) &&
	  (document.fm[this.fieldValidationInfo[i].name] != null) &&
	  (typeof(this.fieldValidationInfo[i].validationFunction) == "function")) {
	if (!this.fieldValidationInfo[i].validationFunction(document.fm[this.fieldValidationInfo[i].name],
							    this.fieldValidationInfo[i].validationErrorMessage)) {
	  return false;
	}
      }
    }
    return true;
  },
  checkNotEmpty: function(field, errorMsg) {
    if (field && trim(field.value) == "") {
      if (errorMsg) {
	alert(errorMsg);
      } else {
	alert((new MessageFormat(i18n("editentry_alert_message_submit_fill_in_custom_field",
				      "Please fill in a value for the {0} field."))).format(field.name));
      }
      field.focus();
      return false;
    }
    return true;
  },
  
  setupRSForSubmission: function() {
    var rs = "type submitconfirmation";
    if (document.fm.edit_comment_id != null) {
      rs = "{0} " + rs;
    } else if (document.fm.edit_edit_id != null) {
      rs = "{2} " + rs;
    } else if (document.fm.edit_emailreply_id != null) {
      rs = "{0} " + rs;
    }
    document.fm.rs0.value = rs;
  },
  
  setupSimplifyOnSubmit: function() {
    if (document.fm.edit_simplify_on_submit.value == "true") {
      document.fm.edit_simplify_on_submit.value = (document.fm.edit_format.value == "htmledit") ? "true" : "false";
    }
  },
  
  setupAutoCapture: function() {
    if (Traction.Edit.TinyMce.useRichText() && document.fm.edit_attach_urls != null) {
      var imgurls = Traction.Edit.TinyMce.getExternalImageUrls("edit_content");
      if (imgurls == null) {
	return false;
      }
      document.fm.edit_attach_urls.value = imgurls;
    }
  },
  
  
  onFormValidate: function() {
    Debug.edit.println("onFormValidate looking for onsubmitfunction for action '", this.action, "'");
    var submitfn = this.onsubmitfunctions[this.action];
    if (submitfn != null) {
      return submitfn();
    } else {
      return true; // no validation required for this action
    }
  },
  go: function(execFunction) {
    if (this.autoSaveInProgress ||
	this.nameValidationInProgress) {
      this.operationWaiting = execFunction;
    } else {
      execFunction();
    }
  },
  
  cancelEditSession: function() {
    this.go(this._cancelEditSession.bind(this));
  },
  _cancelEditSession: function() {
    this.action = "cancel";
    Debug.edit.println("Set this.action to '", this.action, "'");
    go("custom_action", document.fm, true, false);
  },
  
  simplifyHTML: function() {
    this.go(this._simplifyHTML.bind(this));
  },
  _simplifyHTML: function() {
    this.action = "simplifycontent";
    go("custom_action", document.fm, true, true);
  },
  
  submitToJournal: function() {
    this.go(this._submitToJournal.bind(this));
  },
  _submitToJournal: function() {
    this.action = "submit";
    go("custom_action", document.fm, true, true);
  },
  
  saveDraft: function() {
    this.go(this._saveDraft.bind(this));
  },
  _saveDraft: function() {
    if (document.fm.edit_save_draft_auto) {
      document.fm.edit_save_draft_auto.value = "false";
    }
    this.action = "savedraft";
    go("custom_action", document.fm, true, true);
  },
  
  loadDraft: function() {
    this.go(this._loadDraft.bind(this));
  },
  _loadDraft: function() {
    var isdraft = (document.fm.draftlist.options[document.fm.draftlist.selectedIndex].className.indexOf("draftoption") != -1);
    if (isdraft) {
      this.action = "loaddraft";
    } else {
      this.action = "loadtemplate";
    }
    go("custom_action", document.fm, true, true);
  },
  
  deleteSelectedDraft: function() {
    this.go(this._deleteSelectedDraft.bind(this));
  },
  _deleteSelectedDraft: function() {
    this.action = "deleteselecteddraft";
    go("custom_action", document.fm, true, true);
  },
  
  deleteWorkingDraft: function() {
    this.go(this._deleteWorkingDraft.bind(this));
  },
  _deleteWorkingDraft: function() {
    this.action = "deleteworkingdraft";
    go("custom_action", document.fm, true, true);
  },
  
  sendEmailReply: function() {
    this.go(this._sendEmailReply.bind(this));
  },
  _sendEmailReply: function() {
    this.action = "emailreply";
    go("custom_action", document.fm, true, true);
  },
  
  deleteAlternativeDrafts: function() {
    this.go(this._deleteAlternativeDrafts.bind(this));
  },
  _deleteAlternativeDrafts: function() {
    this.action = "deletealternativedrafts";
    go("custom_action", document.fm, true, true);
  },
  
  initStandardFunctions: function() {
    this.standardFunctionsInitialized = true;
    closeInsertLabelDialog = this.onEditLabels.bind(this);
    closeRelationshipsDialog = this.onEditRelationships.bind(this);
    closeAttachmentsDialog = this.onEditAttachments.bind(this);
    closeSectionsDialog = this.onEditSections.bind(this);
    closeChildrenDialog = this.onEditChildren.bind(this);
    closeNamesDialog = this.onEditNames.bind(this);
    waitforload_onsubmit = this.onFormValidate.bind(this);
    waitforload_getThrobber = this.getPingThrobber.bind(this);
    waitforload_getPingStatusArea = this.getPingStatusArea.bind(this);
  },
  
  initOnSubmitFunctions: function() {
    Debug.edit.println("initOnSubmitFunctions");
    this.onsubmitfunctions = {
      "cancel" : this.onCancel.bind(this),
      "submit" : this.onSubmitToJournal.bind(this),
      "savedraft" : this.onSaveDraft.bind(this),
      "loaddraft" : this.onLoadDraft.bind(this),
      "loadtemplate" : this.onLoadTemplate.bind(this),
      "deleteselecteddraft" : this.onDeleteSelectedDraft.bind(this),
      "deleteworkingdraft" : this.onDeleteWorkingDraft.bind(this),
      "simplifycontent" : this.onSimplifyHTML.bind(this),
      "emailreply" : this.onEmailReply.bind(this),
      "deletealternativedrafts" : this.onDeleteAlternativeDrafts.bind(this)
    };
    if (!this.standardFunctionsInitialized) {
      this.initStandardFunctions();
    }
  },
  updateDraftControlsDisplay: function() {
    var dff = document.getElementById("draftformfields");
    if (dff) {
      var container = dff.parentNode;
      while (container != null && container.tagName != "TR") {
	container = container.parentNode;
      }
      if (container) {
	show_default((trim(dff.innerHTML) != ""), container);
      }
    }
  },
  
  getPingThrobber: function() {
    return document.getElementById("edit_server_ping_throbber");
  },
  getPingStatusArea: function() {
    return document.getElementById("edit_server_ping_status");
  },
  
  
  checkCancel: function() {
  },
  
  setBaseUrl: function(base) {
    if (document.fm.baseurl != null && document.fm.baseurl.value == "") {
      if (base == null || base == "") {
	if (window.opener != null) {
	  document.fm.baseurl.value = "" + window.opener.location.href;
	}
      } else {
	document.fm.baseurl.value = base;
      }
    }
  },
  
  completeSubmission: function(newEntryUrl, id, inlineupdate, isupdate, newCommentTarget, parentEntryUrl, refreshOpener) {
    Debug.edit.println("completeSubmission(" +
		       "New URL: '" + newEntryUrl +"',\n" +
		       "ID: '" + id + "',\n" +
		       "Inline? " + inlineupdate + ",\n" +
		       "Update? " + isupdate + ",\n" +
		       "Target: '" + newCommentTarget + "',\n" +
		       "Parent Entry URL: '" + parentEntryUrl + "')");
    var result = {
      fm: document.fm,
      newEntryUrl: newEntryUrl,
      id: id,
      isupdate: isupdate,
      iscomment: this.hasCommentTarget(),
      parentEntryUrl: parentEntryUrl,
      refreshOpener: refreshOpener
    };
    if (hasNoOpener()) {
      this.completeSubmissionForNoOpener(result);
    }
    else if (hasUnrefreshableOpener()) {
      this.completeSubmissionForUnrefreshableOpener(result);
    }
    else {
      this.completeSubmissionUpdateOpener(result);
    }
    this.closeIfNoWarnings();
  },
  completeSubmissionForNoOpener: function(result) {
    if (this.confirmationHasWarnings()) {
      return;
    }
    window.location.href = result.newEntryUrl;
  },
  completeSubmissionForUnrefreshableOpener: function(result) {
    var navurl;
    if (this.hasBaseUrl()) {
      navurl = document.fm.baseurl.value;
    }
    else {
      navurl = (this.hasCommentTarget() && result.parentEntryUrl != null) ?
        (result.parentEntryUrl + "#" + result.id + "c") : result.newEntryUrl;
    }
    window.open(navurl);
  },
  completeSubmissionUpdateOpener: function(result) {
    window.opener.updateWithNewEntry(result);
  },
  closeIfNoWarnings: function() {
    if (!this.confirmationHasWarnings()) {
      closeWindowAfterDelay(1000);
    }
  },
  confirmationHasWarnings: function() {
    return (document.fm.hasalternativedrafts != null &&
	    document.fm.hasalternativedrafts.value == "true") ||
    (document.fm.hasgeneralwarnings != null &&
     document.fm.hasgeneralwarnings.value == "true");
  },
  
  completeInvisibleSubmission: function(baseurl) {
    if (baseurl == null || trim(baseurl) == "") {
      baseurl = "/traction";
    }
    if (hasNoOpener()) { // user navigated to this view in main window
      document.location.href = baseurl;
    }
    else if (hasUnrefreshableOpener()) { // launching window still open, but it's the collector
      window.open(baseurl);
    }
  },
  hasBaseUrl: function() {
    return (document.fm != null && document.fm.baseurl != null && trim(document.fm.baseurl.value) != "");
  },
  hasCommentTarget: function() {
    return (document.fm != null) && (document.fm.edit_comment_id != null);
  },
  
  completeCancellation: function() {
    if (hasNoOpener()) { // user navigated to this view in main window
      document.location.href = this.getCancelURL();
    } else if (hasUnrefreshableOpener()) { // launching window still open, but it's the collector
      window.open(this.getCancelURL());
    }
    closeWindowAfterDelay(1000);
  },
  getCancelURL: function() {
    if (document.fm != null && document.fm.cancelurl != null && document.fm.cancelurl.value != "") {
      return document.fm.cancelurl.value;
    }
    return "/traction";
  },
  
  preventAccidentalClose: function() {
    Traction.Edit.TinyMce.saveRichText(true, true);
    var requireConfirmation = false;
    var autoSaveDisabled = Traction.Edit.Util.autoSaveDisabled(document.fm);
    for (var n = 0; n < this.contentFieldRegistry.length; n ++) {
      var contentField = this.contentFieldRegistry[n];
      if (contentField &&
	  !Traction.Edit.TinyMce.isHTMLWhiteSpace(contentField.value) &&
	  (autoSaveDisabled || this.formStateChanged(false))) {
	requireConfirmation = true;
	Debug.edit.println("Found potentially dirty in-progress entry content field ",
			   contentField.name,
			   " whose autosave is not up to date ");
	break;
      }
    }
    if (window.event &&
	document.activeElement &&
	document.activeElement.tagName == "A" &&
	document.activeElement.href) {
      var triggerHref = document.activeElement.href.trim().toLowerCase();
      if ((triggerHref.indexOf("javascript:") == 0) &&
	  (triggerHref.indexOf("'_top'") == -1)) {
	return;
      }
    }
    if (!requireConfirmation) {
      return; // don't return a value, or else the confirmation dialog pops up unnecessarily.
    }
    return i18n("editentry_alert_message_confirm_discard_entry_page_navigation",
		"You have not yet posted or otherwise saved your work.\n\n" +
		"If you navigate to another page, you will lose any changes you have made.");
  },
  
  
  shouldAutoSave: function() {
    if (waitforload_preSubmit || waitforload_startedSubmit) {
      return false;
    }
    if (this.autoSaveWaiting) {
      return true;
    }
    if ((document.fm.edit_content               == null || Traction.Edit.TinyMce.isHTMLWhiteSpace(document.fm.edit_content.value)) &&
	(document.fm.edit_title                 == null || trim(document.fm.edit_title.value) == "") &&
	(document.fm.edit_labels                == null || trim(document.fm.edit_labels.value) == "") &&
	(document.fm.edit_verbs                 == null || trim(document.fm.edit_verbs.value) == "") &&
	(document.fm.edit_attachments           == null || trim(document.fm.edit_attachments.value) == "") &&
	(document.fm.edit_names_encoded         == null || trim(document.fm.edit_names_encoded.value) == "") &&
	(document.fm.edit_children_encoded      == null || trim(document.fm.edit_children_encoded.value) == "") &&
	(document.fm.edit_sections_encoded      == null || trim(document.fm.edit_sections_encoded.value) == "")) {
      return false;
    }
    return this.formStateChanged(true);
  },
  
  formStateChanged: function(saveData) {
    var ret = false;
    var newData = new Array();
    for (var fieldName in Traction.Edit.AUTOSAVE_FIELD_NAMES) {
      if (fieldName == "extend") continue;
      var field = document.fm[fieldName];
      if (field) {
	newData[fieldName] = trim(field.value);
	if (Traction.Edit.AUTOSAVE_FIELD_NAMES[fieldName]) {
	  if (Traction.Edit.TinyMce.isHTMLWhiteSpace(newData[fieldName])) {
	    newData[fieldName] = "";
	  }
	  if (this.autoSaveData[fieldName] != newData[fieldName]) {
	    ret = true;
	  }
	}
	else {
	  if (this.autoSaveData[fieldName] != newData[fieldName]) {
	    ret = true;
	  }
	}
      }
    }
    if (saveData) {
      this.autoSaveData = newData;
    }
    return ret;
  },
  
  getAutoSaveURL: function() {
    var changeFields = {
      "rs0": { o: document.fm.rs0.value, n: "type draftlist_" },
      "edit_action": { o: document.fm.edit_action.value, n: "savedraft" },
      "edit_save_draft_auto": { o: document.fm.edit_save_draft_auto.value, n: "true" }
    };
    for (var fieldName in changeFields) {
      if (fieldName == "extend") continue;
      document.fm[fieldName].value = changeFields[fieldName].n;
    }
    var url = fm_submit(document.fm);
    for (var fieldName in changeFields) {
      if (fieldName == "extend") continue;
      document.fm[fieldName].value = changeFields[fieldName].o;
    }
    return url;
  },
  
  onBeforeAutoSaveTest: function() {
    Traction.Edit.TinyMce.cleanUpAndSave(this.getTinyMceEditor(), true);
  },
  
  autoSave: function() {
    if (document.fm.edit_save_draft_auto) {
      if (this.workingWithDrafts) {
	Debug.edit.println("Skipping auto save because user is working with draft/template loading controls.");
	this.setupAutoSave();
	return;
      }
      this.onBeforeAutoSaveTest();
      if (this.shouldAutoSave()) {
	Debug.edit.println("Auto saving...");
	this.autoSaveInProgress = true;
	this.setAutoSaveStatusMessage(i18n("editentry_autosave_in_progress_status_message",
					   "Autosave in progress..."));
	xmlpost_async(FORM_ACTION_READ_WRITE,
		      this.getAutoSaveURL(),
		      document.getElementById("edit_server_ping_throbber"),
		      true,
		      this.autoSave_wakeup.bind(this),
		      (new Date()).formatDate("g:i:s a"));
      } else {
	Debug.edit.println("Auto save skipped.");
	this.setupAutoSave();
      }
    } else {
      Debug.edit.println("Required autosave field edit_save_draft_auto not present; shutting off autosave.");
    }
  },
  
  autoSave_wakeup: function(content, dateStr) {
    Debug.edit.println("Auto save returned.");
    var ok;
    if (wasUnauthorized(content)) {
      this.setAutoSaveStatusMessage((new MessageFormat(i18n("editentry_autosave_failed_status_message",
							    "Autosave FAILED at {0}"))).format(dateStr) +
				    ":  " +
				    i18n("editentry_autosave_failed_unauthorized_message",
					 "unauthorized."));
      ok = false;
    } else if (wasUnreachable(content)) {
      this.setAutoSaveStatusMessage((new MessageFormat(i18n("editentry_autosave_failed_status_message",
							    "Autosave FAILED at {0}"))).format(dateStr) +
				    ":  " +
				    i18n("editentry_autosave_failed_server_unreachable_message",
					 "server unreachable."));
      ok = false;
    } else {
      var error = findErrorAndFeedback(content);
      if (error[0] != null && error[0] != "" && error[0] != "undefined") {
	this.setAutoSaveStatusMessage((new MessageFormat(i18n("editentry_autosave_failed_status_message",
							      "Autosave FAILED at {0}"))).format(dateStr) +
				      ":  " +
				      error);
	ok = false;
      } else {
	ok = true;
	var dff = document.getElementById("draftformfields");
	if (dff) {
	  dff.innerHTML = content;
	  this.updateDraftControlsDisplay();
	}
	this.setAutoSaveStatusMessage((new MessageFormat(i18n("editentry_autosave_success_status_message",
									   "Autosaved at {0}."))).format(dateStr));
      }
    }
    this.autoSaveWaiting = !ok;
    this.setupAutoSave();
    if (this.operationWaiting) {
      this.operationWaiting();
    }
  },
  
  setAutoSaveStatusMessage: function(msg) {
    window.status = msg;
  },
  
  toggleWorkingWithDrafts: function(on) {
    this.workingWithDrafts = on;
  },
  
  onTitleChange: function() {
    this.onNamesDirty(false);
  },
  
  onLinkByNameStatusChange: function() {
    if (!document.fm.edit_link_by_names_chk.checked) {
      var nameCount = 0;
      try {
	nameCount = parseInt(document.getElementById("namecount").innerHTML, 10);
      } catch (xcp) { }
      if (nameCount >= 2) {
	var confirmationMsg = (new MessageFormat(i18n("editentry_confirmation_message_remove_all_names",
						      "This article currently has {0} names assigned to it. Turning off linking by name will remove these names.\n\n" +
						      "Click OK to turn off linking by name and remove all names from this article.\n" +
						      "Click Cancel leave linking by name turned on and leave the names as they are."))).format(new String(nameCount));
	if (!confirm(confirmationMsg)) {
	  document.fm.edit_link_by_names_chk.checked = true;
	  return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<
	}
      }
    }
    document.fm.edit_link_by_names.value = document.fm.edit_link_by_names_chk.checked ? "true" : "false";
    document.fm.link_by_names_default.value = "false";
    this.onNamesDirty(true);
  },
  
  onNamesDirty: function(inResponseToToggle) {
    if (document.fm.edit_names_encoded == null ||
	document.fm.edit_link_by_names == null) {
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    this.nameValidationWaiting = true;
    if (this.nameValidationInProgress) {
      if (this.onNamesDirtyTries < 120) {
	var ef = this;
	setTimeout(function() {
		     ef.onNamesDirtyTries += 2;
		     ef.onNamesDirty(inResponseToToggle);
		   }, 500);
      } else {
	this.nameValidationWaiting = false;
      }
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    try {
      this.onBeforeAutoSaveTest();
    } catch (xcp) {
      Debug.edit.println("Possibly expected error in onBeforeAutoSaveTest caught in onNamesDirty: ", xcp);
      if (this.onNamesDirtyTries < 120) {
	var ef = this;
	setTimeout(function() {
		     ef.onNamesDirtyTries ++;
		     ef.onNamesDirty(inResponseToToggle);
		   }, 250);
      } else {
	this.nameValidationWaiting = false;
      }
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    this.onNamesDirtyTries = 0;
    this.nameValidationInProgress = true;
    this.nameValidationWaiting = false;
    var changeFields = {
      "type": { o: document.fm.type.value, n: Traction.Edit.NAME_STATE_DATA_VIEW_TYPE },
      "edit_action": { o: document.fm.edit_action.value, n: "passthrough" }
    };
    var omitFields = { };
    if (document.fm.link_by_names_default.value == "true") {
      omitFields["edit_link_by_names"] = true;
    }
    for (var fieldName in changeFields) {
      if (fieldName == "extend") continue;
      document.fm[fieldName].value = changeFields[fieldName].n;
    }
    var postString = fm_submit(document.fm, omitFields);
    for (var fieldName in changeFields) {
      if (fieldName == "extend") continue;
      document.fm[fieldName].value = changeFields[fieldName].o;
    }
    xmlpost_async(FORM_ACTION_READ_ONLY, postString, true, true, this.onNamesClean.bind(this), [ inResponseToToggle ]);
  },
  
  onNamesClean: function(content, args) {
    this.nameUpdateInProgress = true;
    this.nameValidationInProgress = false;
    var inResponseToToggle = args[0];
    if (wasUnauthorized(content)) {
      if (inResponseToToggle) {
	if (document.fm.edit_link_by_names_chk) {
	  document.fm.edit_link_by_names_chk.checked = !document.fm.edit_link_by_names_chk.checked;
	}
      }
      failureUnauthorized();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    if (wasUnreachable(content)) {
      if (inResponseToToggle) {
	if (document.fm.edit_link_by_names_chk) {
	  document.fm.edit_link_by_names_chk.checked = !document.fm.edit_link_by_names_chk.checked;
	}
      }
      failureServerUnavailable();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    Debug.println("Name update data: ", content);
    var result;
    eval("result = " + content);
    this.updateNameConflictMessages(result.messages);
    this.onEditNames(null, result.linkByNames, result.encodedNames, result.title, result.nameCount, result.hasConflicts, false, document.fm);
    this.nameUpdateInProgress = false;
    if (this.operationWaiting) {
      this.operationWaiting();
    }
  },
  
  updateNameConflictMessages: function(messages) {
    var messageTypes = [ "error", "warning" ];
    for (var i = 0; i < messageTypes.length; i ++) {
      var messageContainer = document.getElementById("name-conflict-" + messageTypes[i]);
      if (messageContainer) {
	if (messages[messageTypes[i]]) {
	  messageContainer.innerHTML = messages[messageTypes[i]];
	  show_TR(true, messageContainer.parentNode);
	}
	else {
	  show_TR(false, messageContainer.parentNode);
	}
      }
    }
  },
  
  onProjectChange: function() {
    this.onAttachmentsStatusDirty();
    this.onPublishStatusDirty();
    this.onNamesDirty(false);
    if (document.fm.edit_edit_id == null &&
	(document.fm.entry_style_default != null && document.fm.edit_entrystyle != null)) {
      this.onEntryStyleStatusDirty();
    }
  },
  
  onPublishStatusChange: function() {
    document.fm.edit_publish_on_submit.value = document.fm.publish_on_submit_chk.checked ? "true" : "false";
    document.fm.publish_on_submit_default.value = "false";
    this.onNamesDirty(true);
  },
  
  onAttachmentsStatusDirty: function() {
    var attDIV = document.getElementById("attachments");
    if (attDIV) {
      show_block(this.projname2addattachments[document.fm.edit_project.value], attDIV);
    }
  },
  onEntryStyleChange: function() {
    document.fm.entry_style_default.value = "false";
  },
  onEntryStyleStatusDirty: function() {
    if (document.fm.entry_style_default.value == "true") {
      selectOptionByValue(document.fm.edit_entrystyle, this.projname2entrystyle[document.fm.edit_project.value]);
    }
  },
  
  onPublishStatusDirty: function() {
    if (document.fm.edit_publish_on_submit) {
      var pubInfo = this.projname2publishinfo[document.fm.edit_project.value];
      if (pubInfo) {
	if (this.targetPublished &&
	    pubInfo.supported &&
	    (pubInfo.publishPermission || (pubInfo.publishOwnPermission && this.authorIsEditing))) {
	  if (pubInfo.canChoose) {
	    show_default(true, document.getElementById("publish_data"));
	    if (document.fm.publish_on_submit_default.value == "true") {
	      document.fm.publish_on_submit_chk.checked = pubInfo.byDefault;
	      document.fm.edit_publish_on_submit.value = pubInfo.byDefault ? "true" : "false";
	    }
	  }
	  else {
	    show_default(false, document.getElementById("publish_data"));
	    if (document.fm.publish_on_submit_default.value == "true") {
	      document.fm.publish_on_submit_chk.checked = pubInfo.byDefault;
	      document.fm.edit_publish_on_submit.value = pubInfo.byDefault ? "true" : "false";
	    }
	  }
	}
	else {
	  show_default(false, document.getElementById("publish_data"));
	  if (this.targetPublished) {
	    if (document.fm.publish_on_submit_default.value == "true") {
	      document.fm.publish_on_submit_chk.checked = pubInfo.byDefault;
	      document.fm.edit_publish_on_submit.value = pubInfo.byDefault ? "true" : "false";
	    }
	  } else {
	    document.fm.publish_on_submit_chk.checked = false;
	    document.fm.edit_publish_on_submit.value = "false";
	  }
	}
      }
    }
  }
};

//------------------------------------------------------------------------
// traction.edit.tinymce.TinyMce


Traction.Edit.TinyMce = {
  TOKEN_TYPE_ATTRIBUTE_NAME: "traction_tokentype",
  DISPLAYNAME_ATTRIBUTE_NAME: "traction_displayname",
  RS_EXPRESSION_ATTRIBUTE_NAME: "traction_rs",
  IMAGE_TOKEN_DISPLAY_ATTRIBUTE_NAME: "traction_image_display",
  IMAGE_TOKEN_DISABLE_AUTOSCALE_ATTRIBUTE_NAME: "traction_image_disableautoscale",
  IMAGE_TOKEN_AUTOCAPTURE_ATTRIBUTE_NAME: "traction_image_autocapture",
  LINK_TOKEN_LINK_TYPE_ATTRIBUTE_NAME: "traction_link_type",
  LINK_TOKEN_REFERENCE_SHOW_TITLE_ATTRIBUTE_NAME: "traction_link_reference_showtitle",
  LINK_TOKEN_REFERENCE_SHOW_ID_ATTRIBUTE_NAME: "traction_link_reference_showid",
  LINK_TOKEN_NAME_REFERENCE_SHOW_NAME_ATTRIBUTE_NAME: "traction_wikilink_showname",
  INSERT_IMAGE_VIEW_TYPE: "insertimage2",
  INSERT_LINK_VIEW_TYPE: "insertlink2",
  INSERT_WIDGET_VIEW_TYPE: "insertwidget2",
  
  supported: function() {
    return (typeof(tinymce) != "undefined") && (typeof(ua) != "undefined") && (ua("supports_tinymce", "true") == "true");
  },
  
  DEFAULT_TINYMCE_OPTIONS: {
    mode : "textareas",
    language : "en",
    docs_language : i18n("core_locale", "en"),
    theme : "advanced",
    plugins : "safari,table,compacttablecontrols,alignmentselect,styledformatselect,tractionimage,tractionlink,tractionwidget,iespell,contextmenu,paste,directionality,fullscreen,noneditable,contextmenuextras,controlsetmodes,compactpastecontrols",
    extended_valid_elements: "p[*],pre[*],div[*],h1[*],h2[*],h3[*],h4[*],h5[*],h6[*],address[*],ol[*],ul[*],sl[*],dl[*],li[*],dt[*],dd[*],table[*],blockquote[*],center[*],tr[*],td[*],th[*],font[*],span[*],img[*],a[*],code,tt,samp,kbd,var,del,ins",
    add_unload_trigger: false,
    add_form_submit_trigger: false,
    theme_advanced_buttons1 : "styledformatselect,fontselect,fontsizeselect,|,forecolor,backcolor,|,bold,italic,underline,|,numlist,bullist,outdent,indent,blockquote,|,alignmentselect,|,|,modeadvanced",
    
    theme_advanced_buttons2 : "tractionlink,unlink,tractionimage,tractionwidget,iespell,|,hr,|,charmap,|,compacttablecontrols,|,cut,copy,compactpastecontrols,|,undo,redo,|,cleanup,removeformat,|,code,fullscreen,|,modesimple",
    theme_advanced_buttons3: "",
    theme_advanced_toolbar_location : "top",
    theme_advanced_toolbar_align : "left",
    theme_advanced_statusbar_location : "bottom",
    theme_advanced_resizing_vertical: false,
    theme_advanced_styles : "Normal=;Sample Code=code",
    table_styles : i18n("editentry_table_style_normal", "Normal") + "=normaltable;" + i18n("editentry_table_style_noborders", "No Borders")+"=nobordertable",
    gecko_spellcheck: true,
    element_format: "html",
    styledformatselect_options: "p_=;address_=;pre_=;samplecode=samplecode|code;h1_=;h2_=;h3_=;h4_=;h5_=;h6_=,div_=",
    convert_fonts_to_spans: false,
    setup: function(ed, ob) {
      Traction.Edit.TinyMce.setupTinyMce(ed, ob);
    },
    save_callback: function(elementId, html, bodyElm) { return Traction.Edit.TinyMce.cleanUpTrailingBRs(html); },
    content_css: "../../../html/css/edit/content.css", // w.r.t. /html/js/tinymce/
    formats: {
      samplecode: [
        { block: "pre", attributes: { "class": "code" } },
        { selector: "pre.code" }
      ],
      p_: { block: "p", attributes: { "class": "" } },
      address_: { block: "address", attributes: { "class": "" } },
      pre_: { block: "pre", attributes: {"class": "" } },
      h1_: { block: "h1", attributes: { "class": "" } },
      h2_: { block: "h2", attributes: { "class": "" } },
      h3_: { block: "h3", attributes: { "class": "" } },
      h4_: { block: "h4", attributes: { "class": "" } },
      h5_: { block: "h5", attributes: { "class": "" } },
      h6_: { block: "h6", attributes: { "class": "" } },
      div_: { block: "div", attributes: { "class": "" } }
    }
  },
  handleRemNode: function(e) {
    setTimeout(function() {
		 Traction.Edit.TinyMce.handleRemNode2(e);
	       }, 100);
  },
  handleRemNode2: function(e) {
    var ed = tinyMCE.activeEditor;
    if (e.target && e.target.nodeName == "P") {
      var pNode = e.target;
      var trs = ed.dom.getAttrib(e.target, Traction.Edit.TinyMce.RS_EXPRESSION_ATTRIBUTE_NAME, null);
      var parentP = ed.dom.getParent(ed.selection.getNode(), "P");
      if (parentP) {
	ed.dom.setAttrib(parentP, Traction.Edit.TinyMce.RS_EXPRESSION_ATTRIBUTE_NAME, trs);
      }
    }
  },
  
  handleNewBlock: function(ed, lastBlock, newBlock) {
    if (lastBlock && Traction.Edit.TinyMce.isHTMLWhiteSpace(lastBlock.innerHTML)) {
      setTimeout(function() {
		   Debug.richtext.println("Clearing out traction_rs from existing block.");
		   ed.dom.setAttrib(lastBlock, Traction.Edit.TinyMce.RS_EXPRESSION_ATTRIBUTE_NAME, "");
		 }, 100);
    } else {
      setTimeout(function() {
		   Debug.richtext.println("Clearing out traction_rs from new block.");
		   ed.dom.setAttrib(newBlock, Traction.Edit.TinyMce.RS_EXPRESSION_ATTRIBUTE_NAME, "");
		 }, 100);
    }
  },
  
  initRichText: function(options) {
    if (Traction.Edit.TinyMce.useRichText()) {
      Debug.richtext.println("initRichText with these options ", options);
      tinyMCE.init(Object.extend(Traction.Edit.TinyMce.DEFAULT_TINYMCE_OPTIONS, options || {}));
    }
  },
  useRichText: function() {
    var ret = Traction.Edit.TinyMce.supported() && Traction.Edit.USER_PREF_USE_RICH_TEXT;
    Debug.richtext.println("useRichText says: ", ret);
    return ret;
  },
  
  cleanUpAndSave: function(tinyMceEditor, quiet) {
    if (!tinyMceEditor) {
      Debug.richtext.println("Traction.Edit.TinyMce.cleanUpAndSave, no rich text editor to save.");
      return;
    }
    Debug.richtext.println("Traction.Edit.TinyMce.cleanUpAndSave for field '", tinyMceEditor.id, "' -&gt; ", "(", (quiet ? "quiet" : "normal") + ")");
    if (quiet) {
      Traction.Edit.TinyMce.triggerQuietSave(tinyMceEditor);
    } else {
      tinyMCE.triggerSave(true, true);
    }
  },
  saveRichText: function(skipCleanup, skipCallback) {
    if (!Traction.Edit.TinyMce.useRichText()) {
      return;
    }
    tinyMCE.triggerSave(skipCleanup, skipCallback);
  },
  
  getExternalImageUrls: function(fieldId) {
    if (!Traction.Edit.TinyMce.useRichText()) {
      return;
    }
    var doc = document.getElementById(fieldId + "_ifr").contentWindow.document
    var images = doc.getElementsByTagName("img");
    if (images == null || images.length == 0) {
      return "";
    }
    var root = getRootDocumentURL();
    var ret = "";
    for (var i = 0; i < images.length; i ++) {
      var snarfOK = images[i].getAttribute(Traction.Edit.TinyMce.IMAGE_TOKEN_AUTOCAPTURE_ATTRIBUTE_NAME);
      if (snarfOK == "false") {
	continue;
      }
      var src = images[i].getAttribute("src");
      if (src == null) {
	src = "";
      }
      if (src.toLowerCase().indexOf("file:") == 0) {
	var warningmsg = i18n("editentry_alert_message_file_url_image_warning_before",
			      "You have included an image in this document that is a reference to a file on your local filesystem (") +
	  src +
	  i18n("editentry_alert_message_file_url_image_warning_after",
	       ").  This image will NOT be automatically included when you submit this article.\n\n" +
	       "If you want help fixing this problem, press OK.");
	if (confirm(warningmsg)) {
	  window.open(i18n("help_view_newentry", "/share/tsi/help/html/Traction_Help_Guide.htm#Add_New_Article.htm"));
	  return null;
	}
	warningmsg = i18n("editentry_alert_message_file_url_image_warning_continuation_confirmation",
			  "If you want to continue and post this message as it is, press OK.\n\n" +
			  "If you want to cancel the post for now and fix the problem, press Cancel.");
	if (!confirm(warningmsg)) {
	  return null;
	}
      }
      var trs = images[i].getAttribute(Traction.Edit.TinyMce.RS_EXPRESSION_ATTRIBUTE_NAME);
      if ((trs == null || trs == "") && // no traction_rs attribute
	  (src.toLowerCase().indexOf("http:") == 0 || src.toLowerCase().indexOf("https:")) &&
	  (root != null && src.indexOf(root) != 0)) { // resource begins with http: or https:
	Debug.richtext.println("adding external image src \"" + src + "\"");
	if (ret != "") {
	  ret += ",";
	}
	ret += escape_commas(src);
      }
    }
    return ret;
  },
  
  isHTMLWhiteSpace: function(str) {
    if (str != null) {
      var newstr = new String(str);
      newstr = newstr.replace(/<img[^>]+>/gi, "IMG");
      newstr = newstr.replace(/<[^>]+>/g, "");
      newstr = newstr.replace(/(&nbsp;)/g, "");
      newstr = newstr.replace(/\s+/g,"");
      return (newstr == "");
    } else {
      return false; // null, not whitespace
    }
  },
  
  cleanUpTrailingBRs: function(bodyHTML) {
    bodyHTML = bodyHTML.replace(/(\s*<br>\s*)+$/gi, "");
    bodyHTML = bodyHTML.replace(/(\s*<br>\s*)+<\/(p|pre|h1|h2|h3|h4|h5|h6|div)>/gi, "</$2>");
    return bodyHTML;
  },
  cleanUpLICloseTags: function(doc) {
    var bodyHTML = new String(doc.body.innerHTML);
    bodyHTML = bodyHTML.replace(/\<\/li\>/gi, "");
    doc.body.innerHTML = bodyHTML;
  },
  triggerQuietSave: function(inst) {
    Debug.richtext.println("Quietly saving ", inst, " (", (inst ? inst.id : "no instance?"), ")");
    inst.save();
  },
  isRealImage: function(ed, imgElement) {
    return (ed.dom.getAttrib(imgElement, 'class').indexOf('mceItem') == -1) &&
      (ed.dom.getAttrib(imgElement, Traction.Edit.TinyMce.TOKEN_TYPE_ATTRIBUTE_NAME, "image") == "image");
  },
  addLinkContextMenuItem: function(contextMenu) {
    Debug.richtext.println("addLinkContextMenuItem...");
    var contextMenuItemProps = {
      title : i18n("tinymce_tooltip_link", "Insert/Edit Link"),
      icon: "link",
      cmd : Traction.Edit.TinyMce.Plugins.TractionLink.pluginObj.COMMAND,
      ui: true
    };
    contextMenu.add(contextMenuItemProps);
  },
  addImageContextMenuItem: function(contextMenu, pluginUrl) {
    Debug.richtext.println("addImageContextMenuItem...");
    var contextMenuItemProps = {
      title : i18n("tinymce_tooltip_image", "Insert/Edit Image"),
      icon_src: pluginUrl + "/../tractionimage" + Traction.Edit.TinyMce.Plugins.TractionImage.pluginObj.IMAGE_PATH,
      cmd : Traction.Edit.TinyMce.Plugins.TractionImage.pluginObj.COMMAND,
      ui: true
    };
    contextMenu.add(contextMenuItemProps);
  },
  addWidgetContextMenuItem: function(contextMenu, pluginUrl) {
    Debug.richtext.println("addWidgetContextMenuItem...");
    var contextMenuItemProps = {
      title : i18n("tinymce_tooltip_widget", "Insert/Edit Widget"),
      icon_src: pluginUrl + "/../tractionwidget" + Traction.Edit.TinyMce.Plugins.TractionWidget.pluginObj.IMAGE_PATH,
      cmd : Traction.Edit.TinyMce.Plugins.TractionWidget.pluginObj.COMMAND,
      ui: true
    };
    contextMenu.add(contextMenuItemProps);
  },
  canInsertOrEditLink: function(ed, selElement, collapsed) {
    return (selElement.nodeName != "IMG");
  },
  selectionIsLink: function(ed, selElement, collapsed) {
      var selElement = ed.selection.getNode();
      var parentLinkElement = ed.dom.getParent(selElement, "A");
      var linkElement;
      if (selElement.nodeName == "IMG") {
	return false;
      } else {
	return (selElement.nodeName == "A") ? selElement : parentLinkElement;
      }
  },
  canInsertOrEditImage: function(ed, selElement, collapsed) {
    return (collapsed && !Traction.Edit.TinyMce.selectionIsLink(ed, selElement, collapsed)) ||
      ((selElement.nodeName == "IMG") && Traction.Edit.TinyMce.isRealImage(ed, selElement));
  },
  selectionIsImage: function(ed, selElement, collapsed) {
    return !collapsed && (selElement.nodeName == "IMG") && Traction.Edit.TinyMce.isRealImage(ed, selElement);
  },
  canInsertOrEditWidget: function(ed, selElement, collapsed) {
    var realImage = Traction.Edit.TinyMce.isRealImage(ed, selElement);
    var isImage = (selElement.nodeName == "IMG");
    return (isImage && !realImage) || (!isImage && collapsed);
  },
  selectionIsWidget: function(ed, selElement, collapsed) {
    var realImage = Traction.Edit.TinyMce.isRealImage(ed, selElement);
    var isImage = (selElement.nodeName == "IMG");
    return !collapsed && isImage && !realImage;
  },
  getEditorNamespacedId: function(ed, id) {
    return ed.editorId + "_" + id;
  },
  setupTinyMce: function(ed, ob) {
    ed.onInsertPara.add(Traction.Edit.TinyMce.handleNewBlock);
    if (tinymce.isGecko) {
      ed.onInit.add(function() {
		      tinymce.dom.Event._add(ed.getBody(), 'DOMNodeRemoved', Traction.Edit.TinyMce.handleRemNode);
		    });
    }
    if (tinymce.isIE) {
      ed.onSetContent.add(Traction.Edit.TinyMce.setupTinyMceDragAndDrop);
    }
  },
  setupTinyMceDragAndDrop: function(ed, object) {
    Traction.Edit.TinyMce.DragAndDrop.setup(ed);
  }
};

//------------------------------------------------------------------------
// traction.edit.tinymce.dialogs.Dialogs

Traction.Edit.TinyMce.Dialogs = {
  getFormForEditor: function() {
    return tinyMCEPopup.editor.getElement().form;
  },
  updateAttachmentData: function(type, countField) {
    window.opener.closeAttachmentsDialog(type,
					 document.fm.filecollection_files.value,
					 parseInt(countField.value, 10),
					 Traction.Edit.TinyMce.Dialogs.getFormForEditor(),
					 Traction.Edit.Dialogs.Attachments.getFlatDataList());
  }
};

//------------------------------------------------------------------------
// traction.edit.tinymce.dialogs.Image



Traction.Edit.TinyMce.Dialogs.Image = {
  IMAGE_SOURCE_TYPE_URL: "url",
  IMAGE_SOURCE_TYPE_UPLOAD: "upload",
  IMAGE_SOURCE_TYPE_TRACTIONID: "tractionid",
  IMAGE_SOURCE_TYPE_URL_DYNAMIC: "urldynamic",
  
  onImageLoad: function() {
    Traction.Edit.TinyMce.Dialogs.Image.updateImageData();
  },
  
  onImageError: function() {
    Traction.Edit.TinyMce.Dialogs.Image.clearImageDimensions();
  },
  
  preloadImg: new Image(),
  
  
  insertSRC: null,
  
  insertRS: null,
  
  updateFired: false,
  
  initializePercentage: false,
  
  imgNode: null,
  registerWithTinyMcePopup: function() {
    Debug.richtext.println("registerWithTinyMcePopup");
    tinyMCEPopup.onInit.add(Traction.Edit.TinyMce.Dialogs.Image.init, Traction.Edit.TinyMce.Dialogs.Image);
  },
  
  
  init: function() {
    Traction.Edit.Dialogs.Attachments.initAttachmentData();
    this.initializeImgNode();
    this.initializeCoreFields();
    this.initializeReferenceFields();
    if (document.fm.lastuploaded.value == "true") {
      this.onUploadCompletion();
    } else {
      this.validateInputForExecution();
    }
  },
  
  onUploadCompletion: function() {
    var fm = Traction.Edit.TinyMce.Dialogs.getFormForEditor();
    if (fm == null) {
      alert(i18n("tinymce_insertimage_no_form_for_id_before", "Could not find a form associated with editor ID") +
	    " " + tinyMCEPopup.editor.id +
	    i18n("tinymce_insertimage_no_form_for_id_after", "."));
      return;
    }
    if (document.fm.addeduploads.value != "true") {
      if (document.getElementById("error") == null) {
	alert(i18n("tinymce_insertimage_no_upload_message", "No file was uploaded."));
      }
      document.fm.img_src_type.value = Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_UPLOAD;
      this.onSwitchImageSourceType();
      this.validateInputForExecution();
      return;
    }
    var newid = Traction.Edit.Dialogs.Attachments.attachmentIDs[Traction.Edit.Dialogs.Attachments.attachmentIDs.length - 1];
    document.fm.attach_src.options[selectIndexOfValue(document.fm.attach_src, newid)].selected = true;
    Traction.Edit.TinyMce.Dialogs.updateAttachmentData("insertimage2", document.fm.count);
    var src = this.correctAttachmentPath(newid);
    var tractionrs = this.getImageRSExpression();
    if (this.updateFired) {
      this.updateOpener(src, tractionrs);
      if (document.fm.closeonload.value == "true") {
	Traction.Edit.TinyMce.Dialogs.Image.safeClose();
      }
    }
    else {
      this.insertSRC = src;
      this.insertRS  = tractionrs;
    }
  },
  
  onCancelClick: function() {
    Traction.Edit.TinyMce.Dialogs.Image.safeClose();
  },
  
  onInsertClick: function() {
    if (!this.validateOpener()) {
      return;
    }
    document.fm.apply.disabled = true;
    this.insertImage(false);
  },
  
  onOKClick: function() {
    if (!this.validateOpener()) {
      return;
    }
    document.fm.apply.disabled = true;
    this.insertImage(true);
  },
  onPropertyChange: function() {
    this.validateInputForExecution();
  },
  
  onSwitchImageSourceType: function() {
    var type = document.fm.img_src_type.value;
    var urlblock = document.getElementById("image_by_url");
    var tidblock = document.getElementById("image_by_tractionid");
    var uplblock = document.getElementById("image_by_upload");
    var dynblock = document.getElementById("image_by_url_dynamic");
    if (urlblock != null) {
      var useDynamic = (type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_URL_DYNAMIC);
      var useURL = (type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_URL || useDynamic);
      show_TABLE(useURL, urlblock);
      if (useURL) {
	this.getRemoteImageData();
	show_TR(!useDynamic, document.getElementById("upload_remote_warning"));
	document.fm.autocapture.value = useDynamic ? "false" : "true";
      }
    }
    if (tidblock != null) {
      show_TABLE(type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_TRACTIONID, tidblock);
      if (type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_TRACTIONID) {
	this.getAttachmentImageData();
      }
    }
    if (uplblock != null) {
      show_TABLE(type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_UPLOAD, uplblock);
      if (type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_UPLOAD) {
	this.clearImageDimensions();
	this.getEmptyImageData();
      }
    }
  },
  
  onDimensionChange: function(e, field) {
    if (keyCode2(e) == 9) {
      return;
    }
    if (!document.fm.proportional.checked || this.preloadImg.width == 0) {
      return;
    }
    var newpct = this.updatePercentage(field);
    if (newpct != 0) {
      var iswidth = (field == null || field.toLowerCase() != "height");
      var dimfield = iswidth ? document.fm.height : document.fm.width;
      var newdim = (iswidth ? this.preloadImg.height : this.preloadImg.width) * (newpct / 100.0);
      dimfield.value = new String(Math.round(newdim));
    }
  },
  onDisplayChange: function() {
    var msg;
    if (document.fm.display.value == "inline") {
      msg = i18n("tinymce_display_selector_option_inline_description", "Display this image inline with text.");
    }
    else if (document.fm.display.value == "block") {
      msg = i18n("tinymce_display_selector_option_block_description", "Display this image on its own line.");
    }
    else {
      msg = i18n("tinymce_display_selector_option_auto_description", "Small images will be displayed inline, larger images will be displayed on their own line.");
    }
    document.getElementById("display_description").innerHTML = msg;
  },
  
  onPercentageChange: function() {
    var pct = 100;
    if (trim(document.fm.pct.value) != "") {
      try {
	var newpct = parseFloat(new String(document.fm.pct.value));
	if (!isNaN(newpct) && newpct >= 0 && (new String(newpct)).toLowerCase() != "infinity") {
	  pct = newpct;
	}
      } catch (xcp) { }
    }
    var w = Math.round((pct / 100.0) * this.preloadImg.width);
    var h = Math.round((pct / 100.0) * this.preloadImg.height);
    document.fm.width.value = new String(w);
    document.fm.height.value = new String(h);
  },
  
  
  initializeImgNode: function() {
    var ed = tinyMCEPopup.editor;
    var dom = ed.dom;
    this.imgNode = ed.selection.getNode();
    if (this.imgNode && this.imgNode.nodeName != "IMG") {
      this.imgNode = null;
    }
  },
  
  initializeCoreFields: function() {
    var ed = tinyMCEPopup.editor;
    var dom = ed.dom;
    var imgNode = this.imgNode;
    if (document.fm.alt.value == "") {
      document.fm.alt.value = imgNode ? dom.getAttrib(imgNode, "alt", "") : "";
    }
    var alignval = document.fm.lastalign != null ? document.fm.lastalign : dom.getAttrib(imgNode, "align", "");
    var alignidx = selectIndexOfValue(document.fm.align, alignval);
    if (alignidx != -1) {
      document.fm.align.options[alignidx].selected = true;
    }
    if (document.fm.width.value == "") {
      document.fm.width.value = imgNode ? dom.getAttrib(imgNode, "width") : "";
    }
    if (document.fm.height.value == "") {
      document.fm.height.value = imgNode ? dom.getAttrib(imgNode, "height") : "";
    }
    if (document.fm.border.value == "") {
      document.fm.border.value = imgNode ? dom.getAttrib(imgNode, "border") : "";
    }
  },
  
  initializeReferenceFields: function() {
    var ed = tinyMCEPopup.editor;
    var dom = ed.dom;
    var attachid = null;
    var entryid = null;
    if (document.fm.lastuploaded.value == "true") {
      if (Traction.Edit.Dialogs.Attachments.attachmentIDs.length != 0) {
	attachid = Traction.Edit.Dialogs.Attachments.attachmentIDs[Traction.Edit.Dialogs.Attachments.attachmentIDs.length - 1];
      }
    }
    else if (this.imgNode != null) {
      var tractionrs = dom.getAttrib(this.imgNode, Traction.Edit.TinyMce.RS_EXPRESSION_ATTRIBUTE_NAME, "");
      var rsimg_attachid = /^(\[\[\s*\/?image\s+[^\s]*@)([0-9]+)(.*\]\]$)/;
      if (tractionrs != null && trim(tractionrs) != "" && tractionrs.match(rsimg_attachid)) {
	var attachnum = parseInt(RegExp.$2, 10);
	for (var i = 0; i < Traction.Edit.Dialogs.Attachments.attachmentIDs.length; i ++) {
	  if (Traction.Edit.Dialogs.Attachments.attachmentData[Traction.Edit.Dialogs.Attachments.attachmentIDs[i]].number == attachnum) {
	    attachid = Traction.Edit.Dialogs.Attachments.attachmentIDs[i];
	    break;
	  }
	}
	if (attachid != null) {
	  attachid = Traction.Edit.Dialogs.Attachments.attachmentIDs[i];
	  var rsimg_entryid = /^(\[\[image\s+)([^@]+)(@.+\]\]$)/;
	  if (tractionrs.match(rsimg_entryid)) {
	    entryid = RegExp.$2;
	  }
	} else {
	  attachid = null;
	}
      }
    }
    if (attachid != null) {
      document.fm.attach_src.options[selectIndexOfValue(document.fm.attach_src, attachid)].selected = true;
      if (entryid != null) {
	document.fm.attach_entry.value = entryid;
      } else {
	document.fm.attach_entry.value = "";
      }
      document.fm.img_src_type.options[selectIndexOfValue(document.fm.img_src_type, Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_TRACTIONID)].selected = true;
      this.onSwitchImageSourceType();
      this.initializePercentage = (document.fm.lastuploaded.value == "false");
    } else {
      var argsrc = this.imgNode ? dom.getAttrib(this.imgNode, "src", "") : "";
      if (argsrc != null && argsrc != "") {
	if (argsrc.indexOf("../") == 0) {
	  argsrc = argsrc.substring(2);
	}
	document.fm.url_src.value = argsrc;
	if (tinyMCEPopup.getWindowArg("autocapture") == "false") {
	  document.fm.img_src_type.options[selectIndexOfValue(document.fm.img_src_type, Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_URL_DYNAMIC)].selected = true;
	  this.initializePercentage = (trim(document.fm.width.value) != "");
	} else {
	  document.fm.img_src_type.options[selectIndexOfValue(document.fm.img_src_type, Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_URL)].selected = true;
	  this.initializePercentage = true;
	}
	this.onSwitchImageSourceType();
      } else {
	document.fm.img_src_type.options[selectIndexOfValue(document.fm.img_src_type, Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_UPLOAD)].selected = true;
	this.onSwitchImageSourceType();
      }
    }
  },
  
  insertImage: function(closewindow) {
    var src = null;
    var tractionrs = null;
    var type = document.fm.img_src_type.value;
    switch (type) {
    case Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_URL:
    case Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_URL_DYNAMIC:
      if (trim(document.fm.url_src.value) == "") {
	alert(i18n("tinymce_insertimage_url_required_message", "Please enter the URL of an image to include."));
	document.fm.url_src.focus();
	return;
      }
      src = document.fm.url_src.value;
      if (type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_URL_DYNAMIC) {
	tractionrs = this.getImageRSExpression();
      }
      break;
    case Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_TRACTIONID:
      if (document.fm.attach_src.selectedIndex == 0) {
	alert(i18n("tinymce_insertimage_attachment_required_message", "Please choose an attached image to include."));
	document.fm.attach_src.focus();
	return;
      }
      src = this.correctAttachmentPath(new String(document.fm.attach_src.value));
      tractionrs = this.getImageRSExpression();
      break;
    case Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_UPLOAD:
      if (document.fm.file0.value == "") {
	alert(i18n("tinymce_insertimage_file_required_message", "Please choose a file to upload."));
	document.fm.file0.focus();
	return;
      }
      if (closewindow) {
	document.fm.closeonload.value = "true";
      }
      document.fm.rs0.value = "type insertimage2";
      document.fm.submit();
      return;
    }
    this.updateOpener(src, tractionrs);
    if (closewindow) {
      Traction.Edit.TinyMce.Dialogs.Image.safeClose();
    } else {
      Traction.Edit.TinyMce.Dialogs.Image.imgNode.focus()
    }
  },
  
  updateOpener: function(src, tractionrs) {
    if (src == null) {
      alert(i18n("tinymce_insertimage_general_failure_message", "Unable to insert an image."));
      return;
    }
    if (!window.opener || !tinyMCEPopup.editor) {
      return; // <<<<<<<<<<<<<<<<<<<
    }
    var alt = document.fm.alt.value;
    var border = document.fm.border.value;
    var width = document.fm.width.value;
    var height = document.fm.height.value;
    var align = document.fm.align.value;
    var type = document.fm.img_src_type.value;
    var useDynamic = (type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_URL_DYNAMIC);
    var useURL = (type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_URL || useDynamic);
    var autocapture;
    if (useURL) {
      autocapture = useDynamic ? "false" : "true";
    } else {
      autocapture = "false";
    }
    var NUMERIC = new RegExp("\\s*[0-9]+\\s*");
    var borderVal = (border.match(NUMERIC) ? parseInt(trim(border), 10) : "");
    var style = ("display: " + document.fm.display.value + ";") + (borderVal ? (" border: " + borderVal + "px solid black;") : "");
    var imageProps = {
      "src": src,
      "alt": alt,
      "border": borderVal,
      "width": (width.match(NUMERIC) ? parseInt(trim(width), 10) : ""),
      "height": (height.match(NUMERIC) ? parseInt(trim(height), 10) : ""),
      "align": align,
      "traction_tokentype": "image",
      "traction_rs": ((tractionrs != null) ? trim(tractionrs) : null),
      "traction_image_disableautoscale": (document.fm.disableautoscale.checked ? "true" : "false"),
      "traction_image_display": document.fm.display.value,
      "traction_image_autocapture": autocapture,
      "style": style
    };
    Debug.richtext.println("updateOpener using src='", src, "'");
    var ed = tinyMCEPopup.editor;
    var dom = ed.dom;
    imgNode = Traction.Edit.TinyMce.Dialogs.Image.imgNode;
    tinyMCEPopup.restoreSelection();
    if (tinymce.isWebKit) {
      ed.getWin().focus();
    }
    if (imgNode && imgNode.nodeName == "IMG") {
      ed.dom.setAttribs(imgNode, imageProps);
    } else {
      ed.execCommand('mceInsertContent', false, '<img id="__mce_tmp" />', {skip_undo : 1});
      ed.dom.setAttribs('__mce_tmp', imageProps);
      Traction.Edit.TinyMce.Dialogs.Image.imgNode = ed.dom.get('__mce_tmp');
      ed.dom.setAttrib('__mce_tmp', 'id', '');
      ed.undoManager.add();
    }
  },
  
  getImageRSExpression: function() {
    var rs = new String("[[ /image ");
    if (document.fm.img_src_type.value == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_URL_DYNAMIC) {
      rs += escape_singlequotes(document.fm.url_src.value);
    } else {
      if (document.fm.attach_entry != null && document.fm.attach_entry.value != "") {
	rs += document.fm.attach_entry.value;
      }
      rs += "@" + Traction.Edit.Dialogs.Attachments.attachmentData[document.fm.attach_src.value].number;
      if (trim(document.fm.alt.value) != "") {
	rs += " '" + escape_singlequotes(document.fm.alt.value) + "'";
      }
    }
    try {
      var dimw = new String(parseInt(document.fm.width.value, 10));
      var dimh = new String(parseInt(document.fm.height.value, 10));
      if (!isNaN(dimw) && !isNaN(dimh)) {
	var dimrs = dimw + "x" + dimh;
	try {
	  var dimb = new String(parseInt(document.fm.border.value));
	  if (!isNaN(dimb)) {
	    dimrs += "x" + dimb;
	  } else {
	    dimrs += "x0";
	  }
	} catch (xcp1) {
	  dimrs += "x0";
	}
	rs += " " + dimrs
	  }
    } catch (xcp2) {
    }
    var align = currentSelValue(document.fm.align);
    if (align != "") {
      rs += " align='" + align + "'";
    }
    rs += " ]]";
    return rs;
  },
  
  
  updateImageData: function() {
    var preview = document.getElementById("previewimg");
    if (Traction.Edit.TinyMce.Dialogs.Image.preloadImg.src == "") {
      if (preview != null) {
	preview.style.display = "none";
      }
    } else {
      if (this.initializePercentage) {
	this.initializePercentage = false;
	this.updatePercentage();
      } else {
	document.fm.width.value = Traction.Edit.TinyMce.Dialogs.Image.preloadImg.width;
	document.fm.height.value = Traction.Edit.TinyMce.Dialogs.Image.preloadImg.height;
	this.onPercentageChange();
      }
      if (preview != null) {
	preview.src = Traction.Edit.TinyMce.Dialogs.Image.preloadImg.src;
	preview.style.display = "";
	var newdims = scaleDimensions( [Traction.Edit.TinyMce.Dialogs.Image.preloadImg.width, Traction.Edit.TinyMce.Dialogs.Image.preloadImg.height], [300, 100] );
	preview.width = newdims[0];
	preview.height = newdims[1];
      }
    }
    if (this.insertSRC != null) {
      this.updateOpener(this.insertSRC, this.insertRS);
      this.insertSRC = this.insertRS = null;
      if (document.fm.closeonload.value == "true") {
	if (document.fm.closeonload.value == "true") {
	  Traction.Edit.TinyMce.Dialogs.Image.safeClose();
	}
      }
    }
    this.updateFired = true;
  },
  safeClose: function() {
    if (tinymce.isWebKit) {
      setTimeout(function() {
		   tinyMCEPopup.close();
		 }, 100);
    } else {
      tinyMCEPopup.close();
    }
  },
  addImageEventHandlers: function() {
    Events.attach(Traction.Edit.TinyMce.Dialogs.Image.preloadImg, "load",  Traction.Edit.TinyMce.Dialogs.Image, Traction.Edit.TinyMce.Dialogs.Image.onImageLoad);
    Events.attach(Traction.Edit.TinyMce.Dialogs.Image.preloadImg, "error", Traction.Edit.TinyMce.Dialogs.Image, Traction.Edit.TinyMce.Dialogs.Image.onImageError);
  },
  
  getRemoteImageData: function() {
    var url = document.fm.url_src.value;
    Traction.Edit.TinyMce.Dialogs.Image.preloadImg = new Image();
    Traction.Edit.TinyMce.Dialogs.Image.addImageEventHandlers();
    if (trim(url) != "") {
      var baseURL;
      try {
	baseURL = new tinymce.util.URI(tinymce.documentBaseURL);
	url = baseURL.toAbsolute(url);
      } catch (stupidIEError) {
      }
    } else {
      var preview = document.getElementById("previewimg");
      if (preview != null) {
	preview.style.display = "none";
      }
    }
    Traction.Edit.TinyMce.Dialogs.Image.preloadImg.src = url;
  },
  
  getAttachmentImageData: function() {
    var attachpath = new String(document.fm.attach_src.options[document.fm.attach_src.selectedIndex].value);
    Traction.Edit.TinyMce.Dialogs.Image.preloadImg = new Image();
    if (attachpath != "") {
      Traction.Edit.TinyMce.Dialogs.Image.addImageEventHandlers();
      Traction.Edit.TinyMce.Dialogs.Image.preloadImg.src = this.correctAttachmentPath(attachpath);
    } else {
      this.updateImageData();
    }
  },
  
  getEmptyImageData: function() {
    Traction.Edit.TinyMce.Dialogs.Image.preloadImg = new Image();
    this.updateImageData();
  },
  
  validateInputForExecution: function() {
    var type = document.fm.img_src_type.value;
    var validateproperties;
    if (type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_URL ||
	type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_URL_DYNAMIC) {
      if (trim(document.fm.url_src.value) == "") {
	this.toggleExecutionButtonDisabledState(true);
	return;
      }
      validateproperties = (document.fm.addeduploads.value == "true" ? "update" : tinyMCEPopup.getWindowArg('action')) == "update";
    } else if (type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_TRACTIONID) {
      if (document.fm.attach_src.value == "") {
	this.toggleExecutionButtonDisabledState(true);
	return;
      }
      validateproperties = (document.fm.addeduploads.value == "true" ? "update" : tinyMCEPopup.getWindowArg('action')) == "update";
    } else if (type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_UPLOAD) {
      if (document.fm.file0.value == "") {
	this.toggleExecutionButtonDisabledState(true);
	return;
      }
      validateproperties = false;
    }
    if (validateproperties) {
    }
    this.toggleExecutionButtonDisabledState(false);
  },
  toggleExecutionButtonDisabledState: function(disabled) {
    document.fm.apply.disabled = document.fm.ok.disabled = disabled;
  },
  validateOpener: function() {
    if (window.opener == null) {
      alert(i18n("tinymce_insertimage_no_opening_window",
		 "Nowhere to insert this image."));
      return false;
    }
    return true;
  },
  
  updatePercentage: function(field) {
    var newpct = 0;
    try {
      var iswidth = (typeof(field) == "undefined" || field == null || field.toLowerCase() != "height");
      var x = parseInt(iswidth ? document.fm.width.value : document.fm.height.value, 10);
      if (!isNaN(x) && x >= 0 || (new String(x)).toLowerCase() != "infinity") {
	newpct = 100.0 * (x / (iswidth ? this.preloadImg.width : this.preloadImg.height));
	document.fm.pct.value = new String(newpct);
      }
    } catch (xcp) { }
    return newpct;
  },
  
  clearImageDimensions: function() {
    document.fm.width.value = document.fm.height.value = "";
  },
  
  correctAttachmentPath: function(attachpath) {
    var origPath = attachpath;
    var bs = new RegExp( "\\\\", "g" );
    if (attachpath != "") {
      attachpath = attachpath.replace(bs, "/");
      var tempfilemarker = attachpath.indexOf("/drafts/");
      if (tempfilemarker != -1) {
	attachpath = "/db" + attachpath.substring(tempfilemarker);
      } else {
	attachpath = "/db/attachments/" + attachpath;
      }
    }
    Debug.richtext.println("Corrected '", origPath, "' to '", attachpath, "'");
    return attachpath;
  }
};

//------------------------------------------------------------------------
// traction.edit.tinymce.dialogs.Link


Traction.Edit.TinyMce.Dialogs.Link = {
  LINK_TYPE_EXTERNAL: "url", // Link by URL
  LINK_TYPE_TRACTIONID: "tractionid", // Link to an Article, Paragraph or Attachment
  LINK_TYPE_RS: "rs", // Link to a View
  LINK_TYPE_SHARE: "share", // Link to a Shared File or Folder
  LINK_TYPE_WIKINAME: "wikilink", // Link to a named page
  id2title: new Array(),
  acfieldname2achandler: new Array(),
  accordionLayout: null,
  warnOnShowTitleIDCheck: false,
  PROJECT_SHARED_FOLDER_ROOT: "/db/share/",
  wikiNameChangeCount: 0,
  ROUNDING_OPTIONS: {
    corners: 'tl br'
  },
  sharedFileFinder: null,
  linkByTractionIDPanelVisited: false,
  linkByNamePanelVisited: false,
  linkElement: null,
  registerWithTinyMcePopup: function() {
    Debug.edit.println("registering with tinyMCEPopup...");
    tinyMCEPopup.onInit.add(Traction.Edit.TinyMce.Dialogs.Link.init, Traction.Edit.TinyMce.Dialogs.Link);
  },
  
  init: function() {
    var ed = tinyMCEPopup.editor;
    this.linkElement = ed.dom.getParent(ed.selection.getNode(), "A");
    if (document.fm.final_token != null) {
      this.insertAndClose();
    } else {
      this.warnOnAutoLinkText = this.warnOnAutoLinkText ||
        ((document.fm.action.value == "edit") &&
         (document.fm.link_link_type.value == Traction.Edit.TinyMce.Dialogs.Link.LINK_TYPE_TRACTIONID) &&
         (document.fm.link_link_tractionid_showtitle.value == "true") && (document.fm.link_link_tractionid_showid.value == "true"));
      Traction.Edit.Dialogs.Attachments.initAttachmentData();
      this.initDND();
      this.initAutoCompletion();
      this.initProjectSharedFileFinder();
      this.initUI();
    }
  },
  initUI: function() {
    var ACCORDION_OPTIONS = {
      panelHeight: 300,
      collapsedBg: "#edf3fe",
      expandedBg: "#edf3fe",
      collapsedTextColor: "#000",
      expandedTextColor: "#000",
      borderColor: "#adb3be",
      hoverBg: "#3268b5",
      hoverTextColor: "#fff",
      onShowTab: this.onSelectLinkType.bind(this),
      onHideTab: this.onDeselectLinkType.bind(this),
      onLoadShowTab: this.linktype2tabindex(document.fm.link_link_type.value)
    };
    this.accordionLayout = new Rico.Accordion( $("linktypes"), ACCORDION_OPTIONS);
    if ($("tractionid_lookup")) {
      Rico.Corner.round( $("tractionid_lookup"), this.ROUNDING_OPTIONS );
    }
    if ($("share_lookup")) {
      Rico.Corner.round( $("share_lookup"), this.ROUNDING_OPTIONS );
    }
    document.fm.style.display = "";
    document.fm.linktext.focus();
    this.accordionLayout.options.onShowTab(this.accordionLayout.lastExpandedTab);
  },
  initAutoCompletion: function() {
    if (document.fm.link_link_tractionid_lookup_text) {
      eac_setup(document.fm.link_link_tractionid_lookup_text);
      document.fm.link_link_tractionid_lookup_text.onfocus = this.onFocusAutoCompleterField.bindAsEventListener(this);
    }
    eac_setup(document.fm.link_link_tractionid);
    document.fm.link_link_tractionid.onfocus = this.onFocusAutoCompleterField.bindAsEventListener(this);
    if (document.fm.link_link_share_url_lookup_sharefilename) {
      eac_setup(document.fm.link_link_share_url_lookup_sharefilename);
      document.fm.link_link_share_url_lookup_sharefilename.onfocus = this.onFocusAutoCompleterField.bindAsEventListener(this);
    }
    eac_setup(document.fm.link_link_share_url_textfield);
    document.fm.link_link_share_url_textfield.onfocus = this.onFocusAutoCompleterField.bindAsEventListener(this);
    document.fm.link_link_share_url_textfield.onchange = this.onChangeSharedFilePath.bindAsEventListener(this);
    eac_setup(document.fm.link_link_wikiname);
    Events.attach(document.fm.link_link_wikiname, Events.KeyUp, this, this.onWikiNameChange);
    document.fm.link_link_wikiname.onfocus = this.onFocusAutoCompleterField.bindAsEventListener(this);
    document.fm.link_link_wikiname_proj.onchange = this.onWikiNameProjectChange.bindAsEventListener(this);
  },
  initDND: function() {
    tid_setup_drop(document.fm.link_link_tractionid);
  },
  initProjectSharedFileFinder: function() {
    if (document.fm.link_link_share_url_textfield != null) {
      var FINDER_OPTIONS = {
	formField: document.fm.link_link_share_url_textfield,
	rootDirectory: this.PROJECT_SHARED_FOLDER_ROOT,
	finderID: "link_link_share_url_finder_table",
	listIDRoot: "link_link_share_url_filelist",
	throbberImage: document.getElementById("link_link_share_url_throbber"),
	onChooseFile: this.onChooseSharedFile.bind(this)
      };
      this.sharedFileFinder = new Util.Finder(FINDER_OPTIONS);
    }
  },
  onOKClick: function() {
    if (!this.validateOpener()) {
      return;
    }
    document.fm.cancel.disabled = true;
    document.fm.ok.disabled = true;
    document.fm.submit();
  },
  validateOpener: function() {
    if (!window.opener) {
      alert(i18n("tinymce_insertlink_no_opening_window",
		 "Nowhere to insert this link."));
      return false;
    }
    return true;
  },
  
  insertAndClose: function() {
    this.insertOrUpdateLink(this.getLinkAttributes(), this.getLinkText());
    setTimeout(function() {
		 window.close();
	       }, 250);
  },
  
  getHRef: function() {
    return document.fm.final_href.value;
  },
  
  getRS: function() {
    return document.fm.final_token.value;
  },
  
  getTargetFrameName: function() {
    var newWinRadio = document.getElementById("target_new");
    return (newWinRadio && newWinRadio.checked) ? "_blank" : "_self";
  },
  
  getTokenType: function() {
    return ((document.fm.link_link_type.value == "wikilink") ? "wikilink" : "link")
  },
  
  getLinkText: function() {
    return escape_forattribute(document.fm.linktext.value);
  },
  
  getLinkAttributes: function() {
    var linkAttributes = {
      "href": this.getHRef(),
      "traction_tokentype": this.getTokenType(),
      "traction_rs": this.getRS(),
      "target": this.getTargetFrameName()
    };
    if (document.fm.link_link_type.value == Traction.Edit.TinyMce.Dialogs.Link.LINK_TYPE_TRACTIONID) {
      var showTitle = (document.fm.link_link_tractionid_showtitle.value == "true");
      var showID    = (document.fm.link_link_tractionid_showid.value == "true");
      linkAttributes[Traction.Edit.TinyMce.LINK_TOKEN_REFERENCE_SHOW_TITLE_ATTRIBUTE_NAME] = showTitle ? "true" : "false";
      linkAttributes[Traction.Edit.TinyMce.LINK_TOKEN_REFERENCE_SHOW_ID_ATTRIBUTE_NAME] = showID ? "true" : "false";
    }
    else if (document.fm.link_link_type.value == Traction.Edit.TinyMce.Dialogs.Link.LINK_TYPE_WIKINAME) {
      linkAttributes[Traction.Edit.TinyMce.LINK_TOKEN_NAME_REFERENCE_SHOW_NAME_ATTRIBUTE_NAME] = document.fm.link_link_wikiname_showname.value;
    }
    return linkAttributes;
  },
  
  insertOrUpdateLink: function(linkAttributes, linkText) {
    var ed = tinyMCEPopup.editor;
    var elementArray, i;
    tinyMCEPopup.execCommand("mceBeginUndoLevel");
    if (this.linkElement == null) {
      if (ed.selection.isCollapsed()) {
	ed.execCommand('mceInsertContent', false, '<a id="__mce_tmp">'+linkText+'</a>', {skip_undo : 1});
	ed.dom.setAttribs('__mce_tmp', linkAttributes);
	ed.dom.setAttrib('__mce_tmp', 'id', '');
      } else {
	tinyMCEPopup.execCommand("CreateLink", false, "#mce_temp_url#", {skip_undo : 1});
	elementArray = tinymce.grep(ed.dom.select("a"), function(n) {return ed.dom.getAttrib(n, "href") == "#mce_temp_url#";});
	for (i = 0; i < elementArray.length; i ++) {
	  ed.dom.setAttribs(elementArray[i], linkAttributes);
	  elementArray[i].innerHTML = linkText;
	}
      }
    }
    else {
      ed.dom.setAttribs(this.linkElement, linkAttributes);
      this.linkElement.innerHTML = linkText;
    }
    tinyMCEPopup.execCommand("mceEndUndoLevel");
  },
  onChooseSharedFile: function(value) {
    document.fm.link_link_share_url_textfield.value = value.substring(this.PROJECT_SHARED_FOLDER_ROOT.length);
    document.fm.link_link_share_url.value           = value;
  },
  linktype2tabindex: function(linkType) {
    if (linkType.indexOf("def-val-") == 0) {
      linkType = linkType.substring(8);
    }
    var tabIndex = document.fm["linktype_" + linkType + "_tabindex"];
    if (tabIndex) {
      Debug.edit.println("Tab index for link type '", linkType, "' is ", tabIndex.value, ".");
      return parseInt(tabIndex.value, 10);
    }
    return -1;
  },
  tab2linktype: function(ricoAccordionTab) {
    if (ricoAccordionTab) {
      var titleBar = ricoAccordionTab.titleBar;
      if (titleBar) {
	return titleBar.parentNode.id.substring((new String("linktype_")).length);
      }
    }
    return document.fm.link_link_type.value;
  },
  cancelAction: function() {
    tinyMCEPopup.close();
  },
  onSelectLinkType: function(ricoAccordionTab) {
    var newVal = this.tab2linktype(ricoAccordionTab);
    document.fm.link_link_type.value = newVal;
    Debug.edit.println("Switched to '", newVal, "' link type.");
    document.getElementById(newVal + "_bullet").innerHTML = "&rarr;";
    if (document.fm.link_link_type.value == Traction.Edit.TinyMce.Dialogs.Link.LINK_TYPE_RS) {
      this.onChangeRSProject();
    }
    if (document.fm.link_link_type.value == Traction.Edit.TinyMce.Dialogs.Link.LINK_TYPE_TRACTIONID) {
      this.linkByTractionIDPanelVisited = true;
      this.synchronizeLinkTextForReference();
      if (document.fm.link_link_tractionid.value == "") {
	document.fm.link_link_tractionid.focus();
	var handler = this.acfieldname2achandler["link_link_tractionid"];
	handler.setup.bind(handler)();
	Debug.edit.println("Set auto-complete view type to '",
			   EAC_TYPE_COMPLETER,
			   "' because user selected Traction ID type link and we're auto-firing the atomcompleter search...");
	if (handler.onFocusField) {
	  handler.onFocusField.bind(handler)();
	}
      }
      virtuallyDisable((document.fm.link_link_tractionid_showid.value == "true" ||
			document.fm.link_link_tractionid_showtitle.value == "true"),
		       document.fm.linktext);
    }
    else if (document.fm.link_link_type.value == Traction.Edit.TinyMce.Dialogs.Link.LINK_TYPE_WIKINAME) {
      this.linkByNamePanelVisited = true;
      this.getTractionAtomPreview();
      if (document.fm.link_link_wikiname.value == "") {
	document.fm.link_link_wikiname.focus();
	if (document.fm.link_link_wikiname_showname.value == "true") {
	  document.fm.linktext.value = document.fm.link_link_wikiname.value;
	}
	var handler = this.acfieldname2achandler["link_link_wikiname"];
	if (handler.onFocusField) {
	  handler.onFocusField.bind(handler)();
	}
      }
      virtuallyDisable((document.fm.link_link_wikiname_showname.value == "true"),
		       document.fm.linktext);
    }
    else {
      virtuallyDisable(false, document.fm.linktext);
    }
    this.validateInputForExecution();
  },
  onDeselectLinkType: function(ricoAccordionTab) {
    var oldVal = this.tab2linktype(ricoAccordionTab);
    document.getElementById(oldVal + "_bullet").innerHTML = "&bull;";
  },
  onLinkTextChange: function() {
    if (!this.linkByTractionIDPanelVisited) {
      document.fm.link_link_tractionid_showtitle.value = document.fm.link_link_tractionid_showid.value = (trim(document.fm.linktext.value) == "" ? "true" : "false");
      document.fm.link_link_tractionid_showtitle_chk.checked = document.fm.link_link_tractionid_showid_chk.checked = (trim(document.fm.linktext.value) == "" ? true : false);
    }
    if (!this.linkByNamePanelVisited) {
      document.fm.link_link_wikiname_showname.value = (trim(document.fm.linktext.value) == "" ? "true" : "false");
      document.fm.link_link_wikiname_showname_chk.checked = (trim(document.fm.linktext.value) == "" ? true : false);
    }
    this.warnOnAutoLinkText = true;
    this.onPropertyChange();
  },
  onPropertyChange: function() {
    this.validateInputForExecution();
  },
  validateInputForExecution: function() {
    var type = document.fm.link_link_type.value;
    Debug.edit.println("Link type is ", document.fm.link_link_type.value);
    if (type == this.LINK_TYPE_TRACTIONID) {
      if (document.fm.link_link_tractionid_showtitle.value != "true" &&
	  document.fm.link_link_tractionid_showid.value != "true" &&
	  trim(document.fm.linktext.value) == "") {
	this.toggleExecutionButtonDisabledState(true);
	return;
      }
    }
    else if (trim(document.fm.linktext.value) == "") {
      this.toggleExecutionButtonDisabledState(true);
      return;
    }
    if (type == this.LINK_TYPE_EXTERNAL) {
      if (trim(document.fm.link_link_external_url.value) == "") {
	this.toggleExecutionButtonDisabledState(true);
	return;
      }
    } else if (type == this.LINK_TYPE_RS) {
      if (trim(document.fm.link_link_rs_expression.value) == "") {
	this.toggleExecutionButtonDisabledState(true);
	return;
      }
    } else if (type == this.LINK_TYPE_TRACTIONID) {
      if (trim(document.fm.link_link_tractionid.value) == "") {
	this.toggleExecutionButtonDisabledState(true);
	return;
      }
    } else if (type == this.LINK_TYPE_SHARE) {
      if (trim(document.fm.link_link_share_url.value) == "") {
	this.toggleExecutionButtonDisabledState(true);
	return;
      }
    }
    this.toggleExecutionButtonDisabledState(false);
  },
  toggleExecutionButtonDisabledState: function(disabled) {
    document.fm.ok.disabled = disabled;
  },
  
  onFocusAutoCompleterField: function(event) {
    Events.kill(null, "eacbeforerequest");
    var field = (typeof(event.target) == "undefined") ? event.srcElement : event.target;
    Debug.edit.println("onFocusAutoCompleterField for field name '", field.name, "'");
    var handler = this.acfieldname2achandler[field.name];
    if (handler == null) {
      Debug.edit.println("Skipping tihs auto-completer field because there's no handler!");
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    handler.setup.bind(handler)();
    Debug.edit.println("Set auto-complete view type to '", EAC_TYPE_COMPLETER, "'");
    if (handler.onFocusField) {
      handler.onFocusField.bind(handler)();
    }
  },
  fetchAutoCompletionMatches: function(field, sendEmpty, extras) {
    var q = field.value;
    if (q != "" || sendEmpty) {
      Debug.edit.println("Firing ", EAC_TYPE_COMPLETER, " search.");
      var url = FORM_ACTION_READ_ONLY + "?type=" + EAC_TYPE_COMPLETER + "&q=" + encode_url_parameter(q);
      if (extras != null && extras != "") {
	url += extras;
      }
      eac_Element = field;
      var throbber = eac_Element.nextSibling;
      eac_xmlget = xmlget_async(url, throbber, (throbber == null), eac_callback, [ eac_Element, null ], -1);
    }
  },
  
  acceptAutoCompletionMatch: function(index) {
    if (eac_DIV != null && eac_DIV.style.display != "none" && eac_Element != null) {
      var handler = this.acfieldname2achandler[eac_Element.name];
      if (handler) {
	Debug.edit.println("Using '", handler.name, "' to accept an auto-completion match.");
	handler.acceptMatch.bind(handler)(index);
      }
    }
  },
  onChangeSharedFilePath: function() {
    if (trim(document.fm.link_link_share_url_textfield.value) == "") {
      document.fm.link_link_share_url.value = "";
    } else {
      document.fm.link_link_share_url.value = Traction.Edit.TinyMce.Dialogs.Link.PROJECT_SHARED_FOLDER_ROOT + trim(document.fm.link_link_share_url_textfield.value);
      Debug.edit.println("Set link_link_share_url to '", document.fm.link_link_share_url.value, "'");
    }
    this.onPropertyChange();
  },
  onChangeRSProject: function() {
    var poststring = "";
    poststring = fm_append(poststring, "type=rsviewspec_");
    poststring = fm_append(poststring, "version=2"); // points to an SDL function from insertlink2.sdl.
    poststring = fm_append(poststring, "proj=" + encode_url_parameter(document.fm.link_link_rs_proj.value));
    poststring = fm_append(poststring, "name=link_link_rs_viewspec");
    poststring = fm_append(poststring, "viewspec=" + encode_url_parameter(document.fm.link_link_rs_viewspec.value));
    xmlpost_async(FORM_ACTION_READ_WRITE, poststring, null, true, this.onChangeRSProject_wakeup.bind(this));
  },
  onChangeRSProject_wakeup: function(responseText) {
    document.getElementById("link_link_rs_viewspec_container").innerHTML = responseText;
  },
  onRSExpressionChange: function(field) {
    try {
      if (document.fm.link_link_rs_viewspec.value != "") {
	Debug.edit.println(field.name, " was changed.  Changing effective RS expression.");
	document.fm.link_link_rs_expression.value = "link " +
        document.fm.link_link_rs_viewspec.value + " " +
        ((document.fm.link_link_rs_timeslice.value == "") ? "all" : document.fm.link_link_rs_timeslice.value) +
        ((document.fm.link_link_rs_volume.value != "") ? (" " + document.fm.link_link_rs_volume.value) : "");
      }
      document.getElementById("link_link_rs_expression_preview").disabled = (document.fm.link_link_rs_expression.value == "");
      document.getElementById("link_link_rs_mode_simple").checked = true;
      document.getElementById("link_link_rs_mode_advanced").checked = false;
      Traction.Edit.TinyMce.Dialogs.Link.onChangeRSMode("simple");
    } catch (x) { }
    this.validateInputForExecution();
  },
  onRSExpressionManualChange: function() {
    Debug.edit.println("The RS expression was manually changed.");
    document.getElementById("link_link_rs_expression_preview").disabled = (document.fm.link_link_rs_expression.value == "");
    document.getElementById("link_link_rs_mode_simple").checked = false;
    document.getElementById("link_link_rs_mode_advanced").checked = true;
    Traction.Edit.TinyMce.Dialogs.Link.onChangeRSMode("advanced");
  },
  onChangeRSMode: function(newMode) {
    Debug.edit.println("Changing rs editing mode to ", newMode);
    document.fm.link_link_rs_mode.value = newMode;
    var advanced = (newMode == "advanced");
    if (advanced) {
      document.fm.link_link_rs_expression.focus();
    }
    this.validateInputForExecution();
  },
  getTractionAtomPreview: function() {
    var url = FORM_ACTION_READ_ONLY + "?type=atomsnippet_";
    if (document.fm.link_link_type.value == Traction.Edit.TinyMce.Dialogs.Link.LINK_TYPE_WIKINAME) {
      Debug.edit.println("Retrieving atomsnippet_ for named page ", document.fm.link_link_wikiname.value, " in scope ",
		    document.fm.link_link_wikiname_proj.options[document.fm.link_link_wikiname_proj.selectedIndex].text,
		    "...");
      url += "&name=" + encode_url_parameter(document.fm.link_link_wikiname.value) +
	"&proj=" +  encode_url_parameter(document.fm.link_link_wikiname_proj.value.substring(2));
    } else {
      Debug.edit.println("Retrieving atomsnippet_ for ", document.fm.link_link_tractionid.value, "...");
      url += "&tractionid=" + encode_url_parameter(document.fm.link_link_tractionid.value);
    }
    if (document.fm.filecollection_files != null) {
      url += "&attachments=" + encode_url_parameter(document.fm.filecollection_files.value);
    }
    if (document.fm.sections != null) {
      url += "&sections=" + encode_url_parameter(document.fm.sections.value);
    }
    xmlget_async(url, null, true, this.getTractionAtomPreview_wakeup.bind(this));
  },
  getTractionAtomPreview_wakeup: function(responseText) {
    if (document.fm.link_link_type.value == Traction.Edit.TinyMce.Dialogs.Link.LINK_TYPE_WIKINAME) {
      document.getElementById("link_link_wikiname_preview").innerHTML = responseText;
    } else {
      document.getElementById("link_link_tractionid_preview").innerHTML = responseText;
    }
  },
  openRSLinkPreview: function() {
    do_window_open(FORM_ACTION_READ_ONLY + "?rs=" + encode_url_parameter(document.fm.link_link_rs_expression.value), "link_preview", "scrollbars\=yes,titlebar\=yes,resizable\=yes,alwaysRaised\=no,menubar\=yes,location\=yes,status\=yes,toolbar\=yes,directories\=yes");
  },
  onShowTitleIDChange: function(checkbox, field) {
    if (this.warnOnAutoLinkText) {
      if (confirm(i18n("tinymce_insert_link_override_link_text_confirmation_message",
		       "If you use automatically generated link text, the existing link text will be replaced.  Are you sure you want to do this?\n\nClick OK to replace your existing link text with the automatically generated link text.\n\nClick Cancel to keep your existing link text."))) {
	this.warnOnAutoLinkText = false; // no need to confirm again
	field.value = checkbox.checked ? "true" : "false";
	this.synchronizeLinkTextForReference();
	virtuallyDisable((document.fm.link_link_tractionid_showid.value == "true" ||
			  document.fm.link_link_tractionid_showtitle.value == "true"),
			 document.fm.linktext);
      } else {
	checkbox.checked = false;
      }
    } else {
      field.value = checkbox.checked ? "true" : "false";
      this.synchronizeLinkTextForReference();
      virtuallyDisable((document.fm.link_link_tractionid_showid.value == "true" ||
			document.fm.link_link_tractionid_showtitle.value == "true"),
		       document.fm.linktext);
    }
  },
  synchronizeLinkTextForReference: function() {
    Debug.edit.println("****** synchronizeLinkTextForReference for " + document.fm.link_link_tractionid.value + " ******");
    if ((document.fm.link_link_tractionid_showtitle.value == "true") ||
	(document.fm.link_link_tractionid_showid.value == "true")) {
      toggleReadOnly(true, document.fm.linktext);
    } else {
      toggleReadOnly(false, document.fm.linktext);
    }
    var requestedID = Traction.ID.parse(document.fm.link_link_tractionid.value);
    if (requestedID == null) {
      Debug.edit.println("Couldn't parse Traction ID");
      this.onPropertyChange();
      return;
    }
    var knownTitle;
    if (requestedID.separator == "@" ||
	requestedID.separator == "$") {
      knownTitle = this.id2title[requestedID.toString()];
    } else {
      knownTitle = this.id2title[requestedID.removeItem().toString()];
    }
    if (knownTitle) {
      Debug.edit.println("Title already known for ", requestedID.toString());
      this.fillInReferenceLinkText(requestedID, knownTitle);
    } else {
      Debug.edit.println("Retrieving title for ", document.fm.link_link_tractionid.value, "...");
      var url = FORM_ACTION_READ_ONLY + "?type=title&tractionid=" + encode_url_parameter(document.fm.link_link_tractionid.value);
      xmlget_async(url, null, true, this.synchronizeLinkTextForReference_wakeup.bind(this), requestedID);
    }
  },
  synchronizeLinkTextForReference_wakeup: function(responseText, requestedID) {
    if ( responseText == null ) {
      Debug.edit.println(i18n("xmlrequest_unauthorized_alert_message", "You are not authorized to perform the requested action with your current credentials.  Please open another browser window, sign in, and then try the action again."));
      this.onPropertyChange();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    if ( responseText == "" ) {
      Debug.edit.println(i18n("xmlrequest_failed_alert_message", "The request failed because the server could not be reached.  Please ensure that your network connection has not been interrupted and that the server is still online."));
      this.onPropertyChange();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    if (responseText == ("XXX CANNOT FIND ENTRY " + requestedID.removeItem().toString() + " XXX")) {
      Debug.edit.println("This was not a valid ID.")
      this.onPropertyChange();
      return;
    }
    if (responseText == "XXX TITLE NOT RESOLVED XXX") {
      responseText = "";
    }
    if (requestedID.separator == "@" ||
	requestedID.separator == "$") {
      this.id2title[requestedID.toString()] = responseText;
    } else {
      this.id2title[requestedID.removeItem().toString()] = responseText;
    }
    this.fillInReferenceLinkText(requestedID, responseText);
    this.onPropertyChange();
  },
  fillInReferenceLinkText: function(id, title) {
    if (document.fm.link_link_tractionid_showtitle.value == "true") {
      if (document.fm.link_link_tractionid_showid.value == "true") {
	document.fm.linktext.value = id.toString();
	if (title) {
	  document.fm.linktext.value += ": " + title;
	}
	id.debug();
      } else {
	document.fm.linktext.value = title;
      }
    } else {
      if (document.fm.link_link_tractionid_showid.value == "true") {
	document.fm.linktext.value = id.toString();
      }
    }
  },
  onTractionIDChange: function() {
    this.getTractionAtomPreview();
    this.onPropertyChange();
  },
  onWikiNameChange: function() {
    this.wikiNameChangeCount ++;
    var myCount = this.wikiNameChangeCount;
    var nameChangeWin = this;
    setTimeout(function() {
		 nameChangeWin.onWikiNameChange_wakeup.bind(nameChangeWin)(myCount);
	       }, 250);
  },
  onWikiNameChange_wakeup: function(callCount) {
    if (callCount != this.wikiNameChangeCount) {
      return;
    }
    this.getTractionAtomPreview();
    if (document.fm.link_link_wikiname_showname.value == "true") {
      document.fm.linktext.value = document.fm.link_link_wikiname.value;
    }
    this.onPropertyChange();
  },
  onWikiNameProjectChange: function() {
    if (trim(document.fm.link_link_wikiname.value) != "") {
      this.fetchAutoCompletionMatches(document.fm.link_link_wikiname, false, "&proj=" + encode_url_parameter(document.fm.link_link_wikiname_proj.value));
    }
    this.getTractionAtomPreview();
    this.onPropertyChange();
  },
  onUseWikiNameAsLinkTextChange: function(checkbox) {
    if (this.warnOnAutoLinkText) {
      if (confirm(i18n("tinymce_insert_link_override_link_text_confirmation_message",
		       "If you use automatically generated link text, the existing link text will be replaced.  Are you sure you want to do this?\n\nClick OK to replace your existing link text with the automatically generated link text.\n\nClick Cancel to keep your existing link text."))) {
	this.warnOnAutoLinkText = false; // no need to confirm again
	document.fm.link_link_wikiname_showname.value = checkbox.checked ? "true" : "false";
	virtuallyDisable((document.fm.link_link_wikiname_showname.value == "true"),
			 document.fm.linktext);
	document.fm.linktext.value = document.fm.link_link_wikiname.value;
      } else {
	checkbox.checked = false;
      }
    } else {
      document.fm.link_link_wikiname_showname.value = checkbox.checked ? "true" : "false";
      if (document.fm.link_link_wikiname_showname.value == "true") {
	document.fm.linktext.value = document.fm.link_link_wikiname.value;
      }
      this.synchronizeLinkTextForReference();
      virtuallyDisable((document.fm.link_link_wikiname_showname.value == "true"),
		       document.fm.linktext);
    }
  }
};

Traction.Edit.TinyMce.Dialogs.Link.TopLevelTractionIDCompleter = {
  name: "TopLevelTractionIDCompleter",
  TITLE_COMPLETER_VIEW_TYPE: "titlecompleter",
  CONTENT_COMPLETER_VIEW_TYPE: "contentcompleter",
  ATTACHMENTNAME_COMPLETER_VIEW_TYPE: "filenamecompleter",
  LOOKUP_TYPE_FIELD_IDS: [ "lookup_title", "lookup_content", "lookup_attachmentname" ],
  setup: function() {
    var newType = "title";
    for (var i = 0; i < this.LOOKUP_TYPE_FIELD_IDS.length; i ++) {
      var field = document.getElementById(this.LOOKUP_TYPE_FIELD_IDS[i]);
      if (field && field.checked) {
	newType = field.value;
	break;
      }
    }
    this.onChangeLookupType(newType);
    EAC_SEND_EMPTY = false;
    eac_Extras = "";
  },
  onFocusField: function() {
    eac_HighlightMatches = true;
  },
  onChangeLookupType: function(newType) {
    EAC_TYPE_COMPLETER = this.lookuptype2viewtype[newType];
  },
  acceptMatch: function(index) {
    if (eac_DIV != null && eac_DIV.style.display != "none") {
      document.fm.link_link_tractionid_lookup_text.value = eac_hits[index][0];
      var tractionID = eac_hits[index][1];
      if (tractionID == null) {
	document.fm.link_link_external_url.value = eac_hits[index][4];
	eac_hide();
	Traction.Edit.TinyMce.Dialogs.Link.accordionLayout.showTabByIndex(Traction.Edit.TinyMce.Dialogs.Link.linktype2tabindex(Traction.Edit.TinyMce.Dialogs.Link.LINK_TYPE_EXTERNAL, true));
	return;
      }
      document.fm.link_link_tractionid.value = tractionID;
      Debug.edit.println("Accepted '", eac_hits[index][1], "' as the match for a ", EAC_TYPE_COMPLETER, " search.");
      if (EAC_TYPE_COMPLETER != this.lookuptype2viewtype["attachmentname"]) {
	setTimeout(function() {
		     document.fm.link_link_tractionid.focus();
		   },
		   10);
      } else {
	Traction.Edit.TinyMce.Dialogs.Link.getTractionAtomPreview.bind(Traction.Edit.TinyMce.Dialogs.Link)();
      }
      eac_hide();
    }
    Traction.Edit.TinyMce.Dialogs.Link.onPropertyChange();
    Traction.Edit.TinyMce.Dialogs.Link.synchronizeLinkTextForReference();
  }
};

Traction.Edit.TinyMce.Dialogs.Link.TopLevelTractionIDCompleter.lookuptype2viewtype = {
  title: Traction.Edit.TinyMce.Dialogs.Link.TopLevelTractionIDCompleter.TITLE_COMPLETER_VIEW_TYPE,
  content: Traction.Edit.TinyMce.Dialogs.Link.TopLevelTractionIDCompleter.CONTENT_COMPLETER_VIEW_TYPE,
  attachmentname: (Traction.Edit.TinyMce.Dialogs.Link.TopLevelTractionIDCompleter.ATTACHMENTNAME_COMPLETER_VIEW_TYPE + "&attachmentsonly=true")
};

Traction.Edit.TinyMce.Dialogs.Link.EntryScopedTractionIDCompleter = {
  name: "EntryScopedTractionIDCompleter",
  setup: function() {
    EAC_TYPE_COMPLETER = "atomcompleter";
    EAC_SEND_EMPTY = true;
    eac_Extras = "";
    if (document.fm.filecollection_files != null) {
      eac_Extras += "&attachments=" + encode_url_parameter(document.fm.filecollection_files.value);
    }
    if (document.fm.sections != null) {
      eac_Extras += "&sections=" + encode_url_parameter(document.fm.sections.value);
    }
    if (document.fm.curentry != null) {
      eac_Extras += "&curentry=" + encode_url_parameter(document.fm.curentry.value);
    }
  },
  acceptMatch: function(index) {
    if (eac_DIV != null && eac_DIV.style.display != "none") {
      if (eac_hits[index][1] == "") {
	return;
      }
      document.fm.link_link_tractionid.value = eac_hits[index][1];
      Debug.edit.println("Accepted '", eac_hits[index][1], "' as the match for a ", EAC_TYPE_COMPLETER, " search.");
      eac_hide();
      Traction.Edit.TinyMce.Dialogs.Link.getTractionAtomPreview.bind(Traction.Edit.TinyMce.Dialogs.Link)();
    }
    Traction.Edit.TinyMce.Dialogs.Link.synchronizeLinkTextForReference();
    Traction.Edit.TinyMce.Dialogs.Link.onPropertyChange();
  },
  onFocusField: function() {
    eac_HighlightMatches = false;
    var extras = "";
    if (document.fm.filecollection_files != null) {
      extras += "&attachments=" + encode_url_parameter(document.fm.filecollection_files.value);
    }
    if (document.fm.sections != null) {
      extras += "&sections=" + encode_url_parameter(document.fm.sections.value);
    }
    if (document.fm.curentry != null) {
      extras += "&curentry=" + encode_url_parameter(document.fm.curentry.value);
    }
    Traction.Edit.TinyMce.Dialogs.Link.fetchAutoCompletionMatches.bind(Traction.Edit.TinyMce.Dialogs.Link)(document.fm.link_link_tractionid,
								       true,
								       extras);
  }
};

Traction.Edit.TinyMce.Dialogs.Link.SharedFilePathCompleter = {
  name: "SharedFilePathCompleter",
  setup: function() {
    EAC_TYPE_COMPLETER = "filepathcompleter&root=" + Traction.Edit.TinyMce.Dialogs.Link.PROJECT_SHARED_FOLDER_ROOT;
    EAC_SEND_EMPTY = false;
    eac_Extras = "";
  },
  onFocusField: function() {
    eac_HighlightMatches = false;
  },
  acceptMatch: function(index) {
    if (eac_DIV != null && eac_DIV.style.display != "none") {
      if (eac_hits[index][0] == "") {
	return;
      }
      var fullPath = Traction.Edit.TinyMce.Dialogs.Link.PROJECT_SHARED_FOLDER_ROOT + eac_hits[index][1];
      document.fm.link_link_share_url_textfield.value = eac_hits[index][1];
      document.fm.link_link_share_url.value           = fullPath;
      Debug.edit.println("Accepted '", eac_hits[index][1], "' as the match for a ", EAC_TYPE_COMPLETER, " search.");
      if (eac_hits[index][3]) {
	Traction.Edit.TinyMce.Dialogs.Link.fetchAutoCompletionMatches.bind(Traction.Edit.TinyMce.Dialogs.Link)(document.fm.link_link_share_url_textfield, false);
      }
      eac_hide();
    }
    Traction.Edit.TinyMce.Dialogs.Link.onPropertyChange();
  }
};

Traction.Edit.TinyMce.Dialogs.Link.SharedFileNameCompleter = {
  name: "SharedFileNameCompleter",
  setup: function() {
    EAC_TYPE_COMPLETER = "filenamecompleter&sharedonly=true";
    EAC_SEND_EMPTY = false;
    eac_Extras = "";
  },
  onFocusField: function() {
    eac_HighlightMatches = true;
  },
  acceptMatch: function(index) {
    if (eac_DIV != null && eac_DIV.style.display != "none") {
      if (eac_hits[index][0] == "") {
	return;
      }
      document.fm.link_link_share_url.value = document.fm.link_link_share_url_textfield.value = eac_hits[index][0].substring(Traction.Edit.TinyMce.Dialogs.Link.PROJECT_SHARED_FOLDER_ROOT.length);
      document.fm.link_link_share_url_lookup_sharefilename.value = eac_hits[index][2];
      Debug.edit.println("Accepted '", eac_hits[index][0], "' as the match for a ", EAC_TYPE_COMPLETER, " search.");
      if (eac_hits[index][3]) {
	Traction.Edit.TinyMce.Dialogs.Link.fetchAutoCompletionMatches.bind(Traction.Edit.TinyMce.Dialogs.Link)(document.fm.link_link_share_url_textfield, false);
      }
      eac_hide();
    }
    Traction.Edit.TinyMce.Dialogs.Link.onPropertyChange();
  }
};

Traction.Edit.TinyMce.Dialogs.Link.WikiNameCompleter = {
  name: "WikiNameCompleter",
  NAME_COMPLETER_VIEW_TYPE: "pagenamecompleter",
  LOOKUP_TYPE_FIELD_IDS: [ "namelookup_name" ],
  setup: function() {
    var newType = "name";
    for (var i = 0; i < this.LOOKUP_TYPE_FIELD_IDS.length; i ++) {
      var field = document.getElementById(this.LOOKUP_TYPE_FIELD_IDS[i]);
      if (field && field.checked) {
	newType = field.value;
	break;
      }
    }
    this.onChangeLookupType(newType);
    EAC_SEND_EMPTY = false;
    Events.attachCustom("eacbeforerequest", this, this.beforeEACRequest);
  },
  beforeEACRequest: function() {
    Debug.edit.println("WikiNameCompleter.beforeEACRequest");
    eac_Extras = "&proj=" + encode_url_parameter(document.fm.link_link_wikiname_proj.value.substring(2));
  },
  onFocusField: function() {
    eac_HighlightMatches = true;
  },
  onChangeLookupType: function(newType) {
    EAC_TYPE_COMPLETER = this.lookuptype2viewtype[newType];
  },
  acceptMatch: function(index) {
    if (eac_DIV != null && eac_DIV.style.display != "none") {
      document.fm.link_link_wikiname.value = eac_hits[index][0];
      Debug.edit.println("Accepted '", eac_hits[index][2], "' as the match for a ", EAC_TYPE_COMPLETER, " search.");
      Traction.Edit.TinyMce.Dialogs.Link.getTractionAtomPreview.bind(Traction.Edit.TinyMce.Dialogs.Link)();
      if (Traction.Edit.TinyMce.Dialogs.Link.useNameForLinkText) {
	document.fm.linktext.value = document.fm.link_link_wikiname.value;
      }
      eac_hide();
    }
    Traction.Edit.TinyMce.Dialogs.Link.onPropertyChange();
  }
};

Traction.Edit.TinyMce.Dialogs.Link.WikiNameCompleter.lookuptype2viewtype = {
  name: Traction.Edit.TinyMce.Dialogs.Link.WikiNameCompleter.NAME_COMPLETER_VIEW_TYPE
};

Traction.Edit.TinyMce.Dialogs.Link.acfieldname2achandler = {
  link_link_tractionid_lookup_text: Traction.Edit.TinyMce.Dialogs.Link.TopLevelTractionIDCompleter,
  link_link_tractionid: Traction.Edit.TinyMce.Dialogs.Link.EntryScopedTractionIDCompleter,
  link_link_share_url_textfield: Traction.Edit.TinyMce.Dialogs.Link.SharedFilePathCompleter,
  link_link_share_url_lookup_sharefilename: Traction.Edit.TinyMce.Dialogs.Link.SharedFileNameCompleter,
  link_link_wikiname: Traction.Edit.TinyMce.Dialogs.Link.WikiNameCompleter
};

//------------------------------------------------------------------------
// traction.edit.tinymce.dialogs.Media



Traction.Edit.TinyMce.Dialogs.Media = {
  sourcetype: null,
  previewSourcetype: null,
  currentAttachments: null,
  otherAttachments: null,
  currentPreviewAttachments: null,
  otherPreviewAttachments: null,
  widgetHTML: null,
  hasPosterMovie: false,
  nextAttachmentNumber: 0,
  
  onLoad: function() {
    Traction.Edit.Dialogs.Attachments.initAttachmentData();
    this.nextAttachmentNumber = Traction.Edit.Dialogs.Attachments.getNextAttachmentNumber();
    setTimeout(function() {
		 Traction.Edit.TinyMce.Dialogs.Media.initSelectors();
	       }, 10);
  },
  initSelectors: function() {
    Debug.richtext.println("Initializing selectors.");
    Traction.Edit.TinyMce.Dialogs.Media.sourcetype = new Traction.Select("sourcetype",
						       { submitMode: Traction.Select.MODE_NORMAL });
    Traction.Edit.TinyMce.Dialogs.Media.currentAttachments = new Traction.Select("current_attachmentnumber",
							       { submitMode: Traction.Select.MODE_NORMAL });
    Traction.Edit.TinyMce.Dialogs.Media.otherAttachments = new Traction.Select("other_attachmentnumber",
							     { submitMode: Traction.Select.MODE_NORMAL });
    Traction.Edit.TinyMce.Dialogs.Media.hasPosterMovie = (document.fm.widget_quicktime_postermovie != null);
    if (Traction.Edit.TinyMce.Dialogs.Media.hasPosterMovie) {
      Traction.Edit.TinyMce.Dialogs.Media.previewSourcetype = new Traction.Select("postermovie_sourcetype",
								{ submitMode: Traction.Select.MODE_NORMAL });
      Traction.Edit.TinyMce.Dialogs.Media.currentPreviewAttachments = new Traction.Select("postermovie_current_attachmentnumber",
									{ submitMode: Traction.Select.MODE_NORMAL });
      Traction.Edit.TinyMce.Dialogs.Media.otherPreviewAttachments = new Traction.Select("postermovie_other_attachmentnumber",
								      { submitMode: Traction.Select.MODE_NORMAL });
    }
  },
  onInsertClick: function() {
  },
  onOKClick: function() {
    if (!this.validateOpener()) {
      return;
    }
    if (!this.validateDimensions()) {
      return;
    }
    if (document.fm.width != null && document.fm.height != null) {
      var w = document.fm.width.value;
      var h = document.fm.height.value;
      if (trim(w) == "" && trim(h) == "") {
	document.fm.widget_media_dimensions.value = "";
      } else {
	document.fm.widget_media_dimensions.value = w + "x" + h;
      }
    }
    if (!this.setResourceAddress(this.sourcetype.currentValue(),
				 document.fm.file0,
				 document.getElementById("entrysource_current").checked,
				 document.fm.entryid,
				 document.fm.url,
				 this.currentAttachments,
				 this.otherAttachments,
				 document.fm.widget_media_resource)) {
      return;
    }
    if (this.hasPosterMovie) {
      if (document.fm.postermovie_enabled.checked) {
	if (!this.setResourceAddress(this.previewSourcetype.currentValue(),
				     document.fm.file1,
				     document.getElementById("postermovie_entrysource_current").checked,
				     document.fm.postermovie_entryid,
				     document.fm.postermovie_url,
				     this.currentPreviewAttachments,
				     this.otherPreviewAttachments,
				     document.fm.widget_quicktime_postermovie)) {
	  return;
	}
      } else {
	document.fm.widget_quicktime_postermovie.value = "";
      }
    }
    document.fm.cancel.disabled = true;
    document.fm.ok.disabled = true;
    document.fm.tokenedit_action.value = "render";
    document.fm.submit();
  },
  setResourceAddress: function(sourcetype, file, currententry, entryid, url, currentAttachments, otherAttachments, resourceField) {
    if (sourcetype == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_UPLOAD) {
      if (trim(file.value) == "") {
	alert(i18n("tinymce_insertimage_file_required_message",
		   "Please choose a file to upload."));
	setTimeout(function() {
		     file.focus();
		   }, 10);
	return false;
      }
      resourceField.value = "@" + this.nextAttachmentNumber;
      this.nextAttachmentNumber ++;
      document.fm.uploaded.value = "true";
    }
    else if (sourcetype == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_TRACTIONID) {
      if (currententry) {
	if (currentAttachments.currentValue() == "") {
	  alert(i18n("tinymce_insertwidget_attachment_required_message",
		     "Please choose an attachment."));
	  setTimeout(function() {
		       currentAttachments.selector.focus();
		     }, 10);
	  return false;
	}
	resourceField.value = "@" + currentAttachments.currentValue();
      } else {
	var error = false;
	if (trim(entryid.value) == "") {
	  alert(i18n("tinymce_insertwidget_no_entryid",
		     "Please type the Traction ID of an entry and click the 'Update' button to retrieve a listing of its attachments."));
	  setTimeout(function() {
		       entryid.focus();
		     }, 10);
	  error = true;
	}
	if (!error && otherAttachments.currentValue() == "") {
	  alert(i18n("tinymce_insertwidget_attachment_required_message",
		     "Please choose an attachment."));
	  setTimeout(function() {
		       currentAttachments.selector.focus();
		     }, 10);
	  error = true;
	}
	if (error) {
	  return false;
	}
	resourceField.value = entryid.value + "@" + otherAttachments.currentValue();
      }
    }
    else { // assume sourcetype == "url"
      if (trim(url.value) == "") {
	alert(i18n("tinymce_insertwidget_url_required_message",
		   "Please enter a URL."));
	setTimeout(function() {
		     url.focus();
		   }, 10);
	return false;
      }
      resourceField.value = url.value;
    }
    return true;
  },
  validateOpener: function() {
    if (window.opener == null) {
      alert(i18n("tinymce_insertwidget_no_opening_window",
		 "Nowhere to insert this widget."));
      return false;
    }
    return true;
  },
  validateDimensions: function() {
    if (document.fm.width == null ||
	document.fm.height == null) {
      return true;
    }
    var ret = true;
    var w = document.fm.width.value;
    var h = document.fm.height.value;
    if (trim(w) != "" || trim(h) != "") {
      try {
	parseInt(w, 10);
	parseInt(h, 10);
      } catch (xcp) {
	ret = false;
      }
    }
    if (!ret) {
      alert(i18n("tinymce_insertwidget_bad_dimensions_message",
		 "There's a problem with the dimensions you've specified."));
    }
    return ret;
  },
  onSwitchMediaSourceType: function() {
    var type = this.sourcetype.currentValue();
    var urlblock = document.getElementById("resource_by_url");
    var tidblock = document.getElementById("resource_by_tractionid");
    var uplblock = document.getElementById("resource_by_upload");
    if (urlblock != null) {
      show_TABLE(type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_URL, urlblock);
      if (type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_URL) {
      }
    }
    if (tidblock != null) {
      show_TABLE(type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_TRACTIONID, tidblock);
      if (type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_TRACTIONID) {
      }
    }
    if (uplblock != null) {
      show_TABLE(type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_UPLOAD, uplblock);
      if (type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_UPLOAD) {
      }
    }
  },
  onPropertyChange: function() {
    Traction.Edit.TinyMce.Dialogs.Media.validateInputForExecution();
  },
  validateInputForExecution: function() {
    var validateproperties;
    var type = Traction.Edit.TinyMce.Dialogs.Media.sourcetype.currentValue();
    if (type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_URL) {
      if (trim(document.fm.url.value) == "") {
	this.toggleExecutionButtonDisabledState(true);
	return;
      }
      validateproperties = (document.fm.addeduploads.value == "true" ? "update" : tinyMCEPopup.getWindowArg('action')) == "update";
    } else if (type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_TRACTIONID) {
      var attSel;
      if (document.getElementById("entrysource_current").checked) {
	attSel = this.currentAttachments;
      } else {
	attSel = this.otherAttachments;
      }
      if (attSel.currentValue() == "") {
	this.toggleExecutionButtonDisabledState(true);
	return;
      }
      validateproperties = (document.fm.addeduploads.value == "true" ? "update" : tinyMCEPopup.getWindowArg('action')) == "update";
    } else if (type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_UPLOAD) {
      if (document.fm.file0.value == "") {
	this.toggleExecutionButtonDisabledState(true);
	return;
      }
      validateproperties = false;
    }
    if (validateproperties) {
    }
    this.toggleExecutionButtonDisabledState(false);
  },
  toggleExecutionButtonDisabledState: function(disabled) {
    document.fm.ok.disabled = disabled;
  },
  onDimensionChange: function(e, field) {
  },
  
  onCancelClick: function() {
    window.close();
  },
  onSwitchEntrySource: function() {
    if (document.getElementById("entrysource_current").checked) {
      document.fm.entryid.disabled = true;
      this.otherAttachments.selector.style.display = "none";
      this.currentAttachments.selector.style.display = "";
      document.getElementById("currentatt_label").style.display = "";
      document.getElementById("otheratt_label").style.display = "none";
      document.fm.updateattachments.disabled = true;
    } else {
      document.fm.entryid.disabled = false;
      this.otherAttachments.selector.style.display = "";
      this.currentAttachments.selector.style.display = "none";
      document.getElementById("currentatt_label").style.display = "none";
      document.getElementById("otheratt_label").style.display = "";
      document.fm.updateattachments.disabled = false;
      if (document.fm.entryid.value == "") {
	document.fm.entryid.focus();
      }
    }
  },
  onSwitchEntryID: function() {
  },
  onUpdateAttachments: function() {
    document.fm.updateattachments.disabled = true;
    this.updateAttachments(document.fm.entryid.value,
			   this.otherAttachments,
			   document.fm.updateattachments);
  },
  updateAttachments: function(entryid, attSelector, updateButton) {
    var poststring = "";
    poststring = fm_append(poststring, "type=ajaxrpc");
    poststring = fm_append(poststring, "method=getAttachmentData");
    poststring = fm_append(poststring, "p0=" + encode_url_parameter(entryid));
    xmlpost_async(FORM_ACTION_READ_WRITE, poststring, null, true, this.updateAttachments_wakeup.bind(this), [attSelector, updateButton]);
  },
  updateAttachments_wakeup: function(content, args) {
    var attSelector = args[0];
    var enableButton = args[1];
    if ( content == null ) {
      alert(i18n("xmlrequest_unauthorized_alert_message", "You are not authorized to perform the requested action with your current credentials.  Please open another browser window, sign in, and then try the action again."));
      enableButton.disabled = false;
      return false; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    if ( content == "" ) {
      alert(i18n("xmlrequest_failed_alert_message", "The request failed because the server could not be reached.  Please ensure that your network connection has not been interrupted and that the server is still online."));
      enableButton.disabled = false;
      return false; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    var error = findErrorAndFeedback(content)[0];
    if (error != null && error != "" && error != "undefined") {
      alert(error);
      enableButton.disabled = false;
      return false; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    var attachmentInfo = eval("(" + content + ")");
    var newAttachments = attachmentInfo["attachmentdata"];
    var fillerText = attSelector.selector.options[0].text;
    attSelector.removeAll();
    attSelector.add(fillerText, "", true, false);
    for (var i = 0; i < newAttachments.length; i ++) {
      var num = newAttachments[i]["number"];
      attSelector.add(new String(num) + ". " + newAttachments[i]["fname"],
		      new String(num),
		      false,
		      false);
    }
    enableButton.disabled = false;
  },
  onSwitchPreviewEntrySource: function() {
    if (document.getElementById("postermovie_entrysource_current").checked) {
      document.fm.postermovie_entryid.disabled = true;
      this.otherPreviewAttachments.selector.style.display = "none";
      this.currentPreviewAttachments.selector.style.display = "";
      document.getElementById("postermovie_currentatt_label").style.display = "";
      document.getElementById("postermovie_otheratt_label").style.display = "none";
      document.fm.postermovie_updateattachments.disabled = true;
    } else {
      document.fm.postermovie_entryid.disabled = false;
      this.otherPreviewAttachments.selector.style.display = "";
      this.currentPreviewAttachments.selector.style.display = "none";
      document.getElementById("currentatt_label").style.display = "none";
      document.getElementById("otheratt_label").style.display = "";
      if (document.fm.postermovie_entryid.value == "") {
	document.fm.postermovie_entryid.focus();
      }
      document.fm.postermovie_updateattachments.disabled = false;
    }
  },
  onUpdatePreviewAttachments: function() {
    document.fm.postermovie_updateattachments.disabled = true;
    this.updateAttachments(document.fm.postermovie_entryid.value,
			   this.otherPreviewAttachments,
			   document.fm.postermovie_updateattachments);
  },
  onSwitchPreviewMediaSourceType: function() {
    var type = this.previewSourcetype.currentValue();
    var urlblock = document.getElementById("postermovie_resource_by_url");
    var tidblock = document.getElementById("postermovie_resource_by_tractionid");
    var uplblock = document.getElementById("postermovie_resource_by_upload");
    if (urlblock != null) {
      show_TABLE(type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_URL, urlblock);
      if (type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_URL) {
      }
    }
    if (tidblock != null) {
      show_TABLE(type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_TRACTIONID, tidblock);
      if (type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_TRACTIONID) {
      }
    }
    if (uplblock != null) {
      show_TABLE(type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_UPLOAD, uplblock);
      if (type == Traction.Edit.TinyMce.Dialogs.Image.IMAGE_SOURCE_TYPE_UPLOAD) {
      }
    }
  },
  onChangePreviewMediaStatus: function(on) {
    this.previewSourcetype.selector.disabled = !on;
    document.getElementById("postermovie_current_attachmentnumber").disabled = !on;
    this.otherPreviewAttachments.selector.disabled = !on;
    document.fm.postermovie_updateattachments.disabled = !on;
    document.fm.postermovie_url.disabled = !on;
    document.fm.file1.disabled = !on;
    document.getElementById("postermovie_entrysource_current").disabled = !on;
    document.getElementById("postermovie_entrysource_other").disabled = !on;
    if (on) {
      this.onSwitchPreviewEntrySource();
    }
  }
};

//------------------------------------------------------------------------
// traction.edit.tinymce.dialogs.Widget



Traction.Edit.TinyMce.Dialogs.Widget = {
  chooseWidget: function(name) {
    document.fm.tokentype.value = name;
    this.onOKClick();
  },
  init: function() {
    if (document.fm.tokentype) {
      if (document.fm.tokentype.selectedIndex != -1) {
	makeDirty();
      }
    }
  },
  
  onCancelClick: function() {
    window.close();
  },
  onOKClick: function() {
    if (!this.validateOpener()) {
      return;
    }
    document.fm.submit();
  },
  validateOpener: function() {
    if (window.opener == null) {
      alert(i18n("tinymce_insertwidget_no_opening_window",
		 "Nowhere to insert this widget."));
      return false;
    }
    return true;
  },
  insertAndClose: function() {
    Traction.Edit.Dialogs.Attachments.initAttachmentData();
    this.updateOpener();
    window.close();
  },
  updateOpener: function() {
    this.insertWidget();
    if (document.fm.filecollection_files &&
	document.fm.filecollection_count) {
      Traction.Edit.TinyMce.Dialogs.updateAttachmentData("insertwidget2", document.fm.filecollection_count);
    }
  },
  insertWidget: function() {
    tinyMCEPopup.execCommand("mceBeginUndoLevel");
    tinyMCEPopup.execCommand("mceInsertContent", false, this.widgetHTML);
    tinyMCEPopup.execCommand("mceEndUndoLevel");
  },
  makeDirty: function() {
    document.fm.cancel.disabled = false;
    document.fm.ok.disabled = false;
  }
};

//------------------------------------------------------------------------
// traction.edit.tinymce.DragAndDrop

Traction.Edit.TinyMce.DragAndDrop = {
  leaveAlone: false,
  setup: function(ed) {
    try {
      Traction.Edit.TinyMce.DragAndDrop.bindHandler(ed.getBody(), ed.getDoc(), ed.getWin(), ed.id);
    } catch (e) {
      alert(e);
    }
  },
  bindHandler: function(body, doc, win, editorId) {
    var handler = function() {
	var event = win.event;
	event.target = event.srcElement;
	event.target.body = body;
	return Traction.Edit.TinyMce.DragAndDrop.dragdrop(event);
      return true;
    }
    doc.body.ondragend = handler;
    doc.body.ondragover = handler;
    doc.body.ondragenter = handler;
    doc.body.ondrop = handler;
  },
  dragdrop: function(event) {
    var ret = Traction.Edit.TinyMce.DragAndDrop.dragdrop_(event);
    Debug.println(event.type, " - ", ret);
    return ret;
  },
  dragdrop_: function(event) {
    switch (event.type) {
      case "dragstart":
      Traction.Edit.TinyMce.DragAndDrop.leaveAlone = true;
      event.dataTransfer.effectAllowed = 'move';
      return true;
      case "dragover":
      return Traction.Edit.TinyMce.DragAndDrop.leaveAlone;
      case "dragend":
      Traction.Edit.TinyMce.DragAndDrop.leaveAlone = false;
      return true;
      case "drop":
      if (Traction.Edit.TinyMce.DragAndDrop.leaveAlone) return true;
      var dropped = event.dataTransfer.getData('text');
      var insert = tid_parseurl(dropped);
      if (!insert) return true;
      if (insert.match(/\.(gif|jpg|png|tiff)/)) return true;
      var range = event.target.body.createTextRange();
      range.moveToPoint(event.x, event.y);
      tid_fillrange(range, insert);
      Traction.Edit.TinyMce.DragAndDrop.leaveAlone = false;
      return false;
    }
  }
};

//------------------------------------------------------------------------
// traction.edit.tinymce.plugins.Plugins

Traction.Edit.TinyMce.Plugins = { };

//------------------------------------------------------------------------
// traction.edit.tinymce.plugins.AlignmentSelect


Traction.Edit.TinyMce.Plugins.AlignmentSelect = {
  PLUGIN_NAMESPACE: "AlignmentSelectPlugin",
  PLUGIN_NAME: "alignmentselect",
  createAndAddPlugin: function() {
    Debug.richtext.println("[AlignmentSelect] Creating and adding the Alignment Controls TinyMCE plug-in.");
    tinymce.create("tinymce.plugins." + Traction.Edit.TinyMce.Plugins.AlignmentSelect.PLUGIN_NAMESPACE,
		   Traction.Edit.TinyMce.Plugins.AlignmentSelect.pluginObj);
    tinymce.PluginManager.add(Traction.Edit.TinyMce.Plugins.AlignmentSelect.PLUGIN_NAME,
			      tinymce.plugins.AlignmentSelectPlugin);
  }
};

Traction.Edit.TinyMce.Plugins.AlignmentSelect.pluginObj = {
  CONTROL_NAME: "alignmentselect",
  ALIGNMENT_OPTIONS: [ "JustifyLeft", "JustifyCenter", "JustifyRight", "JustifyFull" ],
  TEXT_ALIGN_PROPERTY_TO_OPTION_VALUE: {
    "left": "JustifyLeft",
    "center": "JustifyCenter",
    "right": "JustifyRight",
    "justify": "JustifyFull"
  },
  init: function(ed, url) {
    this.url = url;
    Debug.richtext.println("[AlignmentSelect] initializing plug-in instance at ", url, " for ", ed.editorId);
    ed.onNodeChange.add(this.handleNodeChange, this);
  },
  menuItems: { },
  _getAlignment: function(ed, selElement) {
    var p = ed.dom.getParent(selElement, ed.dom.isBlock);
    if (p) {
      var selectedAlignment = p.style.textAlign ?
	Traction.Edit.TinyMce.Plugins.AlignmentSelect.pluginObj.TEXT_ALIGN_PROPERTY_TO_OPTION_VALUE[p.style.textAlign] :
	"JustifyLeft";
      if (!selectedAlignment) {
	selectedAlignment = "JustifyLeft";
      }
      return selectedAlignment;
    }
    return "JustifyLeft";
  },
  _changeIcon: function(editorId, v) {
    var icon = document.getElementById(editorId + "_" + Traction.Edit.TinyMce.Plugins.AlignmentSelect.pluginObj.CONTROL_NAME + "_action");
    if (icon) {
      icon.firstChild.className = icon.firstChild.className.substring(0, icon.firstChild.className.indexOf("mce_")) + "mce_" + v.toLowerCase();
    }
  },
  handleNodeChange: function(ed, controlManager, selElement, collapsed) {
    var align = controlManager.get(Traction.Edit.TinyMce.Plugins.AlignmentSelect.pluginObj.CONTROL_NAME);
    if (align) {
      var selectedAlignment = this._getAlignment(ed, selElement);
      tinymce.each(this.menuItems, function(v, k) { v.setSelected(selectedAlignment == k); });
      this._changeIcon(ed.editorId, selectedAlignment);
    }
  },
  createControl: function(n, cm) {
    var c, ed = cm.editor;
    if (n == Traction.Edit.TinyMce.Plugins.AlignmentSelect.pluginObj.CONTROL_NAME) {
      var splitButtonOptions = {
	title : i18n('tinymce_alignment_heading', 'Alignment'),
	"class": "mce_justifyleft",
	onclick : function() {
	}
      };
      c = cm.createSplitButton(Traction.Edit.TinyMce.Plugins.AlignmentSelect.pluginObj.CONTROL_NAME, splitButtonOptions);
      var t = this;
      var onRenderSplitButtonMenu = function(c, menu) {
	var titleItemOpts = {
	  title: i18n('tinymce_alignment_heading', 'Alignment'),
	  'class' : 'mceMenuItemTitle'
	};
	var headButton = menu.add(titleItemOpts).setDisabled(1);
	t.menuItems = { };
	tinymce.each(Traction.Edit.TinyMce.Plugins.AlignmentSelect.pluginObj.ALIGNMENT_OPTIONS,
	  function(v, k) {
	    var itemOpts = {
	      title: i18n("tinymce_alignment_option_" + v, v)
	    };
	    var menuItem;
	    itemOpts.onclick = function() {
	      Debug.richtext.println("[AlignmentSelect] chose alignment ", v);
	      var lastSel = "JustifyLeft";
	      tinymce.each(t.menuItems, function(v, k) { if (v.isSelected()) lastSel = k; v.setSelected(0); });
	      menuItem.setSelected(1);
	      ed.execCommand(v, false);
	      t._changeIcon(ed.editorId, v);
	    };
	    menuItem = menu.add(itemOpts);
	    t.menuItems[v] = menuItem;
	    menuItem.setSelected(v == t._getAlignment(ed, ed.selection.getNode()));
	  });
      };
      c.onRenderMenu.add(onRenderSplitButtonMenu, this);
      return c;
    }
    return null;
  },
  getInfo: function() {
    return {
      longname : 'Alignment Controls',
      author : 'Traction Software, Inc.',
      authorurl : 'http://traction.tractionsoftware.com/',
      infourl : 'https://teampage.tractionsoftware.com/traction/page/server/Alignment Controls Plugin for TinyMCE 3',
      version : "2.0"
    };
  }
};

//------------------------------------------------------------------------
// traction.edit.tinymce.plugins.CompactPasteControls


Traction.Edit.TinyMce.Plugins.CompactPasteControls = {
  PLUGIN_NAMESPACE: "CompactPasteControlsPlugin",
  PLUGIN_NAME: "compactpastecontrols",
  createAndAddPlugin: function() {
    Debug.richtext.println("[CompactPasteControls] Creating and adding the Compact Paste Controls TinyMCE plug-in.");
    tinymce.create("tinymce.plugins." + Traction.Edit.TinyMce.Plugins.CompactPasteControls.PLUGIN_NAMESPACE,
		   Traction.Edit.TinyMce.Plugins.CompactPasteControls.pluginObj);
    tinymce.PluginManager.add(Traction.Edit.TinyMce.Plugins.CompactPasteControls.PLUGIN_NAME,
			      tinymce.plugins.CompactPasteControlsPlugin);
  }
};

Traction.Edit.TinyMce.Plugins.CompactPasteControls.pluginObj = {
  editor: null,
  CONTROL_NAME: "compactpastecontrols",
  init: function(ed, url) {
    this.url = url;
    Debug.richtext.println("[CompactPasteControls] initializing plug-in instance at ", url, " for ", ed.editorId);
  },
  createControl: function(n, cm) {
    var c, ed = cm.editor;
    var t = this;
    if (n == Traction.Edit.TinyMce.Plugins.CompactPasteControls.pluginObj.CONTROL_NAME) {
      var onRenderSplitButtonMenu = function(splitButton, m) {
	Debug.richtext.println("[CompactPasteControls] Rendering paste menu");
	m.add({title: "advanced.paste_desc", cmd: "Paste", icon: "paste", ui: false});
	m.add({title: "paste.paste_text_desc", cmd: "mcePasteText", icon: "pastetext", ui: true});
	m.add({title: "paste.paste_word_desc", cmd: "mcePasteWord", icon: "pasteword", ui: true});
      };
      var splitButtonOptions = {
	title : "advanced.paste_desc",
	"class": "mce_paste",
	onclick : function() {
	  ed.execCommand("Paste", false);
	}
      };
      c = cm.createSplitButton(Traction.Edit.TinyMce.Plugins.CompactPasteControls.pluginObj.CONTROL_NAME, splitButtonOptions);
      c.onRenderMenu.add(onRenderSplitButtonMenu.bind(this));
      return c;
    }
    return null;
  },
  getInfo: function() {
    return {
      longname : 'Compact Paste Controls',
      author : 'Traction Software, Inc.',
      authorurl : 'http://traction.tractionsoftware.com/',
      infourl : 'https://teampage.tractionsoftware.com/traction/page/server/Compact Paste Controls Plugin for TinyMCE 3',
      version : "2.0"
    };
  }
};

//------------------------------------------------------------------------
// traction.edit.tinymce.plugins.CompactTableControls


Traction.Edit.TinyMce.Plugins.CompactTableControls = {
  PLUGIN_NAMESPACE: "CompactTableControlsPlugin",
  PLUGIN_NAME: "compacttablecontrols",
  createAndAddPlugin: function() {
    Debug.richtext.println("[CompactTableControls] Creating and adding the Compact Table Controls TinyMCE plug-in.");
    tinymce.create("tinymce.plugins." + Traction.Edit.TinyMce.Plugins.CompactTableControls.PLUGIN_NAMESPACE,
		   Traction.Edit.TinyMce.Plugins.CompactTableControls.pluginObj);
    tinymce.PluginManager.add(Traction.Edit.TinyMce.Plugins.CompactTableControls.PLUGIN_NAME,
			      tinymce.plugins.CompactTableControlsPlugin);
  }
};

Traction.Edit.TinyMce.Plugins.CompactTableControls.pluginObj = {
  editor: null,
  CONTROL_NAME: "compacttablecontrols",
  init: function(ed, url) {
    this.url = url;
    Debug.richtext.println("[CompactTableControls] initializing plug-in instance at ", url, " for ", ed.editorId);
    ed.onNodeChange.add(this.handleNodeChange, this);
  },
  menuItems: { },
  item2appliesToExistingTable: {
    table_insert: true,
    table_props: true,
    table_del: true,
    table_sep: true,
    table_cells: true,
    table_rows: true,
    table_cols: true,
    table_insert2: false
  },
  _existingTableSelected: function(ed) {
    var pTd = ed.dom.getParent(ed.selection.getNode(), 'td');
    var pTh = ed.dom.getParent(ed.selection.getNode(), 'th');
    return ((pTd != null) || (pTh != null));
  },
  handleNodeChange: function(ed, controlManager, selElement, collapsed) {
    var tableControls = controlManager.get(Traction.Edit.TinyMce.Plugins.CompactTableControls.pluginObj.CONTROL_NAME);
    if (tableControls) {
      this._toggleTableControlDisplay(ed, tableControls);
    }
  },
  _toggleTableControlDisplay: function(ed, tableControls) {
    Debug.richtext.println("[CompactTableControls] toggleTableControlDisplay for ", tableControls);
    var existingTable = this._existingTableSelected(ed);
    for (var n in this.menuItems) {
      if (n == "extend") continue;
      var item = this.menuItems[n];
      var domId = item.id;
      var elm = document.getElementById(domId);
      if (!elm) {
	Debug.richtext.println("[CompactTableControls] ", n, " -&gt; ", domId, " table control menu item not rendered yet.");
	return;
      }
      var shouldDisplay = (existingTable == Traction.Edit.TinyMce.Plugins.CompactTableControls.pluginObj.item2appliesToExistingTable[n]);
      show_TR(shouldDisplay, elm);
      Debug.richtext.println("[CompactTableControls] ", n, " -&gt; ", elm, " : ", (shouldDisplay ? "show" : "hide"));
    }
  },
  createControl: function(n, cm) {
    var c, ed = cm.editor;
    var t = this;
    if (n == Traction.Edit.TinyMce.Plugins.CompactTableControls.pluginObj.CONTROL_NAME) {
      var onRenderSplitButtonMenu = function(splitButton, m) {
	var sm; // a submenu
	Debug.richtext.println("************* Rendering menu items!");
	this.menuItems["table_insert"] = m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', ui : true, value : {action : 'insert'}});
	this.menuItems["table_props"] = m.add({title : 'table.props_desc', icon : 'table_props', cmd : 'mceInsertTable', ui : true});
	this.menuItems["table_del"] = m.add({title : 'table.del', icon : 'delete_table', cmd : 'mceTableDelete', ui : true});
	this.menuItems["table_sep"] = m.addSeparator();
	this.menuItems["table_cells"] = sm = m.addMenu({title : 'table.cell'});
	sm.add({title : 'table.cell_desc', icon : 'cell_props', cmd : 'mceTableCellProps', ui : true});
	sm.add({title : 'table.split_cells_desc', icon : 'split_cells', cmd : 'mceTableSplitCells', ui : true});
	sm.add({title : 'table.merge_cells_desc', icon : 'merge_cells', cmd : 'mceTableMergeCells', ui : true});
	this.menuItems["table_rows"] = sm = m.addMenu({title : 'table.row'});
	sm.add({title : 'table.row_desc', icon : 'row_props', cmd : 'mceTableRowProps', ui : true});
	sm.add({title : 'table.row_before_desc', icon : 'row_before', cmd : 'mceTableInsertRowBefore'});
	sm.add({title : 'table.row_after_desc', icon : 'row_after', cmd : 'mceTableInsertRowAfter'});
	sm.add({title : 'table.delete_row_desc', icon : 'delete_row', cmd : 'mceTableDeleteRow'});
	sm.addSeparator();
	sm.add({title : 'table.cut_row_desc', icon : 'cut', cmd : 'mceTableCutRow'});
	sm.add({title : 'table.copy_row_desc', icon : 'copy', cmd : 'mceTableCopyRow'});
	sm.add({title : 'table.paste_row_before_desc', icon : 'paste', cmd : 'mceTablePasteRowBefore'});
	sm.add({title : 'table.paste_row_after_desc', icon : 'paste', cmd : 'mceTablePasteRowAfter'});
	this.menuItems["table_cols"] = sm = m.addMenu({title : 'table.col'});
	sm.add({title : 'table.col_before_desc', icon : 'col_before', cmd : 'mceTableInsertColBefore'});
	sm.add({title : 'table.col_after_desc', icon : 'col_after', cmd : 'mceTableInsertColAfter'});
	sm.add({title : 'table.delete_col_desc', icon : 'delete_col', cmd : 'mceTableDeleteCol'});
	this.menuItems["table_insert2"] = m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', ui : true});
	m.onShowMenu.add(function() { this._toggleTableControlDisplay(ed, splitButton); }, this);
      };
      var splitButtonOptions = {
	title : i18n("tinymce_table_controls_title_tip", "Table Controls"),
	"class": "mce_table",
	onclick : function() {
	  ed.execCommand("mceInsertTable", true);
	}
      };
      c = cm.createSplitButton(Traction.Edit.TinyMce.Plugins.CompactTableControls.pluginObj.CONTROL_NAME, splitButtonOptions);
      c.onRenderMenu.add(onRenderSplitButtonMenu.bind(this));
      return c;
    }
    return null;
  },
  getInfo: function() {
    return {
      longname : 'Compact Table Controls',
      author : 'Traction Software, Inc.',
      authorurl : 'http://traction.tractionsoftware.com/',
      infourl : 'https://teampage.tractionsoftware.com/traction/page/server/Compact Table Controls Plugin for TinyMCE 3',
      version : "2.0"
    };
  }
};

//------------------------------------------------------------------------
// traction.edit.tinymce.plugins.ContextMenuExtras


Traction.Edit.TinyMce.Plugins.ContextMenuExtras = {
  PLUGIN_NAMESPACE: "ContextMenuExtrasPlugin",
  PLUGIN_NAME: "contextmenuextras",
  createAndAddPlugin: function() {
    Debug.richtext.println("[ContextMenuExtras] Creating and adding the Context Menu Extras TinyMCE plug-in.");
    tinymce.create("tinymce.plugins." + Traction.Edit.TinyMce.Plugins.ContextMenuExtras.PLUGIN_NAMESPACE,
		   Traction.Edit.TinyMce.Plugins.ContextMenuExtras.pluginObj);
    tinymce.PluginManager.add(Traction.Edit.TinyMce.Plugins.ContextMenuExtras.PLUGIN_NAME,
			      tinymce.plugins.ContextMenuExtrasPlugin);
  }
};

Traction.Edit.TinyMce.Plugins.ContextMenuExtras.pluginObj = {
  editor: null,
  CONTROL_NAME: "contextmenuextras",
  init: function(ed, url) {
    this.url = url;
    Debug.richtext.println("[ContextMenuExtras] initializing plug-in instance at ", url, " for ", ed.editorId);
    ed.onInit.add(this.setupContextMenuOnRender, this);
  },
  onContextMenu: function(th, m, e) {
    Debug.richtext.println("[ContextMenuExtras] adding extra context menu items");
    var ed = tinyMCE.activeEditor;
    m.addSeparator();
    m.add({title : 'advanced.bold_desc', icon : 'bold', cmd : 'Bold'});
    m.add({title : 'advanced.italic_desc', icon : 'italic', cmd : 'Italic'});
    m.add({title : 'advanced.underline_desc', icon : 'underline', cmd : 'Underline'});
    m.add({title : 'advanced.striketrough_desc', icon : 'strikethrough', cmd : 'Strikethrough'});
    m.add({title : 'advanced.sub_desc', icon : 'sub', cmd : 'Subscript'});
    m.add({title : 'advanced.sup_desc', icon : 'sup', cmd : 'Superscript'});
    if (!ed.selection.isCollapsed()) {
      m.add({title : i18n("tinymce_inline_format_Code", "Code"), cmd: "mceCtxMenuExtras", ui: false, value: "code" });
      m.add({title : i18n("tinymce_inline_format_Deletion", "Deletion"), cmd: "mceCtxMenuExtras", ui: false, value: "del" });
      m.add({title : i18n("tinymce_inline_format_Insertion", "Insertion"), cmd: "mceCtxMenuExtras", ui: false, value: "ins" });
    }
  },
  execCommand: function(cmd, ui, val) {
    var ed = tinyMCE.activeEditor;
    switch (cmd) {
    case "mceCtxMenuExtras":
      var bm = ed.selection.getBookmark()
      var startTag = "<" + val + ">";
      var endTag = "</" + val + ">";
      var selNode = ed.selection.getNode();
      Debug.richtext.println("[ContextMenuExtras] selected node: ", selNode);
      Debug.richtext.println("[ContextMenuExtras] content: ", escape_forattribute(ed.selection.getContent()));
      ed.execCommand('mceBeginUndoLevel');
      var selectContent = ed.selection.getContent();
      var n;
      var r = new RegExp("(.*)" + startTag + "(.*)" + endTag + "(.*)");
      var matches = r.exec(selectContent);
      if (matches) {
	ed.selection.setContent(matches[1] + matches[2] + matches[3]);
      } else {
	ed.selection.setContent(startTag + ed.selection.getContent() + endTag);
      }
      ed.execCommand('mceEndUndoLevel');
      ed.selection.moveToBookmark(bm);
      return true;
    }
    return false;
  },
  setupContextMenuOnRender: function(ed) {
    if (ed && ed.plugins.contextmenu) {
      Debug.richtext.println("[ContextMenuExtras] setting up onContextMenu add...");
      ed.plugins.contextmenu.onContextMenu.add(this.onContextMenu, this);
    }
  },
  getInfo: function() {
    return {
      longname : 'Context Menu Extras',
      author : 'Traction Software, Inc.',
      authorurl : 'http://traction.tractionsoftware.com/',
      infourl : 'https://teampage.tractionsoftware.com/traction/page/server/Context Menu Extras Plugin for TinyMCE 3',
      version : "2.0"
    };
  }
};

//------------------------------------------------------------------------
// traction.edit.tinymce.plugins.ControlSetModes


Traction.Edit.TinyMce.Plugins.ControlSetModes = {
  PLUGIN_NAMESPACE: "ControlSetModesPlugin",
  PLUGIN_NAME: "controlsetmodes",
  JUMPER_SETS: [
    [ "tractionlink", "unlink", "tractionimage", "tractionwidget", "iespell" ]
  ],
  DEFAULT_SETS: [
   {
     name: "simple",
     title: i18n("tinymce_controlsetmodes_simple", "Use Simple Editor"),
     toggleTargets: {
       controlIds : [ "styledformatselect", "fontselect", "fontsizeselect",  "modesimple" ],
       domIds: [ "%_toolbar2", "%_path_row" ]
     },
     "jumperLocations": [ { before: null, after: "alignmentselect", refSeparator: true } ]
   },
   {
     name: "advanced",
     title: i18n("tinymce_controlsetmodes_advanced", "Use Advanced Editor"),
     toggleTargets: {
       controlIds: [  "modeadvanced" ],
       domIds: [ ]
     },
     "jumperLocations": [ { before: "hr", after: null, refSeparator: true } ]
   }
  ],
  createAndAddPlugin: function() {
    Debug.richtext.println("[ControlSetModes] Creating and adding the Control Set Modes TinyMCE plug-in.");
    tinymce.create("tinymce.plugins." + Traction.Edit.TinyMce.Plugins.ControlSetModes.PLUGIN_NAMESPACE,
		   Traction.Edit.TinyMce.Plugins.ControlSetModes.pluginObj);
    tinymce.PluginManager.add(Traction.Edit.TinyMce.Plugins.ControlSetModes.PLUGIN_NAME,
			      tinymce.plugins.ControlSetModesPlugin);
  }
};

Traction.Edit.TinyMce.Plugins.ControlSetModes.pluginObj = {
  editor: null,
  registeredSets: [ ],
  init: function(ed, url) {
    this.url = url;
    Debug.richtext.println("[ControlSetModes] initializing plug-in instance at ", url, " for ", ed.editorId);
    this.registeredSets = Traction.Edit.TinyMce.Plugins.ControlSetModes.DEFAULT_SETS;
    this.initialSetName = Util.CookiePrefs.get(Util.CookiePrefs.TINYMCE_CONTROL_SET_MODE, "simple");
    ed.onInit.add(this._initializeControlSet, this);
  },
  _initializeControlSet: function(ed) {
    this._selectControlSet(ed, this.initialSetName, false);
  },
  _insertControlBefore: function(jumpCtrl, refCtrl, refSeparator) {
    var moveCell = jumpCtrl.parentNode;
    var refCell  = refCtrl.parentNode;
    if (refSeparator && refCell.previousSibling && this._isSeparator(refCell.previousSibling)) {
      refCell = refCell.previousSibling;
    }
    insertElementBefore(refCell.parentNode, moveCell, refCell);
  },
  _insertControlAfter: function(jumpCtrl, refCtrl, refSeparator) {
    var moveCell = jumpCtrl.parentNode;
    var refCell  = refCtrl.parentNode;
    if (refSeparator && refCell.nextSibling && this._isSeparator(refCell.nextSibling)) {
      refCell = refCell.nextSibling;
    }
    insertElementAfter(refCell.parentNode, moveCell, refCell);
  },
  _moveJumpers: function(ed, ctrlSet) {
    var jumperLocations = ctrlSet["jumperLocations"];
    for (var j = 0; j < Traction.Edit.TinyMce.Plugins.ControlSetModes.JUMPER_SETS.length; j ++) {
      var jSet = Traction.Edit.TinyMce.Plugins.ControlSetModes.JUMPER_SETS[j];
      var refCtrlId = Traction.Edit.TinyMce.getEditorNamespacedId(ed, jumperLocations[j]["before"] ? jumperLocations[j]["before"] : jumperLocations[j]["after"]);
      var refCtrl   = document.getElementById(refCtrlId);
      if (refCtrl) {
	var k = 0;
	var jumpCtrlId = Traction.Edit.TinyMce.getEditorNamespacedId(ed, jSet[k]);
	var jumpCtrl = document.getElementById(jumpCtrlId);
	if (jumpCtrl) {
	  if (jumperLocations[j]["before"] != null) {
	    this._insertControlBefore(jumpCtrl, refCtrl, jumperLocations[j]["refSeparator"]);
	  } else {
	    this._insertControlAfter(jumpCtrl, refCtrl, jumperLocations[j]["refSeparator"]);
	  }
	  refCtrl = jumpCtrl;
	  for (k = 1; k < jSet.length; k ++) {
	    jumpCtrlId = Traction.Edit.TinyMce.getEditorNamespacedId(ed, jSet[k]);
	    jumpCtrl = document.getElementById(jumpCtrlId);
	    if (jumpCtrl) {
	      this._insertControlAfter(jumpCtrl, refCtrl, false);
	      refCtrl = jumpCtrl;
	    }
	  }
	} else {
	  Debug.richtext.println("[ControlSetModes] Could not find jumpCtrl " + jumpCtrlId);
	}
      } else {
	Debug.richtext.println("[ControlSetModes] Could not find refCtrl " + refCtrlId);
      }
    }
  },
  _toggleControlSet: function(ed, ctrlSet, toggleOn) {
    Debug.richtext.println("[ControlSetModes] ", (toggleOn ? "showing" : "hiding"), " controls for set name '", ctrlSet["name"], "'...");
    if (!toggleOn) {
      this._moveJumpers(ed, ctrlSet);
    }
    var toggleTargets = ctrlSet["toggleTargets"];
    var toggleByDomId = toggleTargets["domIds"];
    var toggleByCtrlId = toggleTargets["controlIds"];
    for (var j = 0; j < toggleByDomId.length; j ++) {
      var id = toggleByDomId[j].replace(/%/gi, ed.editorId);
      var elm = document.getElementById(id);
      Debug.richtext.println("[ControlSetModes] ", (toggleOn ? "showing" : "hiding"), " DOM element with ID '", id, "' &gt; ", elm);
      if (elm) {
	show_any(toggleOn, elm);
      }
    }
    for (var j = 0; j < toggleByCtrlId.length; j ++) {
      var id = Traction.Edit.TinyMce.getEditorNamespacedId(ed, toggleByCtrlId[j]);
      var elm = document.getElementById(id);
      Debug.richtext.println("[ControlSetModes] ", (toggleOn ? "showing" : "hiding"), " control container for '", id, "' &gt; ", elm);
      if (elm) {
	elm = ed.dom.getParent(elm, "TD");
	if (elm) {
	  Debug.richtext.println("[ControlSetModes] (control container ", elm, ")");
	  show_any(toggleOn, elm);
	}
      }
    }
  },
  _isSeparator: function(controlCell) {
    var c = controlCell.firstChild;
    if (c) {
      while (c) {
	if (c.nodeName == "SPAN" && c.className.toLowerCase().indexOf("separator")) {
	  return true;
	}
	if (c.nextChild) {
	  c = c.nextChild;
	} else {
	  c = null;
	}
      }
    }
    return false;
  },
  _isHiddenControl: function(controlContainer) {
    return controlContainer.style.display == "none";
  },
  _toggleSeparators: function(toolbarRow) {
    Debug.richtext.println("[ControlSetModes] toggling separators for toolbar row ", toolbarRow.id, " (first child ", toolbarRow.firstChild, ")");
    var hasNonHidden = false;
    var separator = null;
    var nextControlContainer = toolbarRow.firstChild;
    if (nextControlContainer.nodeName != "TD") {
      nextControlContainer = getFirstSiblingByName(nextControlContainer, "TD");
    }
    while (nextControlContainer && !hasNonHidden) {
      if (this._isSeparator(nextControlContainer)) {
	show_TD(false, nextControlContainer);
      }
      else if (!this._isHiddenControl(nextControlContainer)) {
	hasNonHidden = true;
      }
      nextControlContainer = getFirstSiblingByName(nextControlContainer, "TD");
    }
    while (nextControlContainer) {
      if (this._isSeparator(nextControlContainer)) {
	if (separator) {
	  show_TD(false, separator);
	}
	separator = nextControlContainer;
      }
      else {
	if (!this._isHiddenControl(nextControlContainer)) {
	  if (separator) {
	    show_TD(true, separator);
	    separator = null;
	  }
	}
      }
      nextControlContainer = getFirstSiblingByName(nextControlContainer, "TD");
    }
  },
  _selectControlSet: function(ed, setName, setCookie) {
    var selectedCtrlSet = null;
    Debug.richtext.println("[ControlSetModes] Looking for '", setName, "'...");
    for (var i = 0; i < this.registeredSets.length; i ++) {
      var ctrlSet = this.registeredSets[i];
      if (ctrlSet["name"] == setName) {
	Debug.richtext.println("[ControlSetModes] matched set name '", ctrlSet["name"], "'...");
	selectedCtrlSet = ctrlSet;
      } else {
	this._toggleControlSet(ed, ctrlSet, true);
      }
    }
    if (!selectedCtrlSet) {
      Debug.richtext.println("[ControlSetModes] execCommand could not find registered set configuration for '", setName, "'");
      return;
    }
    this._toggleControlSet(ed, selectedCtrlSet, false);
    var rowN = 1;
    var toolbarRowTbl = document.getElementById(Traction.Edit.TinyMce.getEditorNamespacedId(ed, "toolbar" + rowN));
    while (toolbarRowTbl) {
      this._toggleSeparators(getFirstChildByName(getFirstChildByName(toolbarRowTbl, "TBODY"), "TR"));
      rowN ++;
      toolbarRowTbl = document.getElementById(Traction.Edit.TinyMce.getEditorNamespacedId(ed, "toolbar" + rowN));
    }
    if (setCookie) {
      Util.CookiePrefs.set(Util.CookiePrefs.TINYMCE_CONTROL_SET_MODE, setName);
    }
  },
  execCommand: function(cmd, ui, val) {
    var ed = tinyMCE.activeEditor;
    Debug.richtext.println("[ControlSetModes] handle '", cmd, "'?");
    if (cmd == "mceControlSetModes") {
      this._selectControlSet(ed, val, true);
      return true;
    }
    return false;
  },
  createControl: function(n, cm) {
    if (n.indexOf("mode") == 0) {
      n = n.substring(4);
      for (var i = 0; i < this.registeredSets.length; i ++) {
	var s = this.registeredSets[i];
	if (n == (s["name"])) {
	  var ctrlName = "mode" + n;
	  Debug.richtext.println("[ControlSetModes] creating control for mode", s["name"]);
	  return cm.createButton(ctrlName, { title: s["title"], "class": "mceNoIcon", label: s["title"], cmd: "mceControlSetModes", value: s["name"] });
	}
      }
    }
    return null;
  },
  getInfo: function() {
    return {
      longname : 'Layout Select',
      author : 'Traction Software, Inc.',
      authorurl : 'http://traction.tractionsoftware.com/',
      infourl : 'https://teampage.tractionsoftware.com/traction/page/server/Layout Select Plugin for TinyMCE 3',
      version : "2.0"
    };
  }
};

//------------------------------------------------------------------------
// traction.edit.tinymce.plugins.StyledFormatSelect


Traction.Edit.TinyMce.Plugins.StyledFormatSelect = {
  PLUGIN_NAMESPACE: "StyledFormatSelectPlugin",
  PLUGIN_NAME: "styledformatselect",
  createAndAddPlugin: function() {
    Debug.richtext.println("[StyledFormatSelect] Creating and adding the Styled Format Select TinyMCE plug-in.");
    tinymce.create("tinymce.plugins." + Traction.Edit.TinyMce.Plugins.StyledFormatSelect.PLUGIN_NAMESPACE,
		   Traction.Edit.TinyMce.Plugins.StyledFormatSelect.pluginObj);
    tinymce.PluginManager.add(Traction.Edit.TinyMce.Plugins.StyledFormatSelect.PLUGIN_NAME,
			      tinymce.plugins.StyledFormatSelectPlugin);
  }
};

Traction.Edit.TinyMce.Plugins.StyledFormatSelect.pluginObj = {
  editor: null,
  CONTROL_NAME: "styledformatselect",
  COMMAND_NAME: "FormatStyledBlock",
  init: function(ed, url) {
    this.editor = ed;
    Debug.richtext.println("[StyledFormatSelect] initializing plug-in instance at ", url, " for ", ed.editorId);
    ed.onNodeChange.add(this.handleNodeChange, this);
  },
  _getSelectedStyle: function(ed, selElement, styledFormatSelector) {
    Debug.richtext.println("[StyledFormatSelect] Looking for a format match for node ", selElement);
    if (selElement.nodeName == "BODY") {
      return "p";
    }
    var ret = "";
    var node = this._getTopLevelBlockContainer(selElement);
    for (var i = 0; i < styledFormatSelector.items.length; i ++) {
      var formatName = styledFormatSelector.items[i]["value"];
      if (ed.formatter.matchNode(node, formatName)) {
	Debug.richtext.println("[StyledFormatSelect] Matched style name ", formatName, " from node selection ", node);
	ret = formatName;
      }
    }
    return ret;
  },
  _getTopLevelBlockContainer: function(node) {
    while (node.parentNode != null && node.parentNode.nodeName != "BODY") {
      node = node.parentNode;
    }
    return node;
  },
  handleNodeChange: function(ed, controlManager, selElement, collapsed) {
    var styledFormatSelector = controlManager.get(Traction.Edit.TinyMce.Plugins.StyledFormatSelect.pluginObj.CONTROL_NAME);
    if (styledFormatSelector) {
      var selectedStyle = this._getSelectedStyle(ed, selElement, styledFormatSelector);
      Debug.richtext.println("[StyledFormatSelect] handling node change will select ", selectedStyle);
      styledFormatSelector.select(selectedStyle);
    }
  },
  createControl: function(n, cm) {
    var c, ed = cm.editor, t = this;
    if (n == Traction.Edit.TinyMce.Plugins.StyledFormatSelect.pluginObj.CONTROL_NAME) {
      var onSelectStyleOption = function(formatName) {
	var bm = ed.selection.getBookmark()
	var node = t._getTopLevelBlockContainer(ed.selection.getNode());
	Debug.richtext.println("[StyledFormatSelect] ***** selected ", formatName, "; trying to apply to ", node);
	ed.execCommand("mceBeginUndoLevel");
	ed.formatter.apply(formatName, null, node);
	ed.selection.moveToBookmark(bm);
	ed.execCommand("mceEndUndoLevel");
      };
      var listBoxOptions = {
	title: i18n("tinymce_style_heading", "Styles"),
	onselect: onSelectStyleOption
      };
      c = cm.createListBox(Traction.Edit.TinyMce.Plugins.StyledFormatSelect.pluginObj.CONTROL_NAME, listBoxOptions);
      if (c) {
	tinymce.each(ed.getParam("styledformatselect_options", "", "hash"),
		     function(rawValue, key) {
		       var optionVal = rawValue;
		       if (!optionVal) {
			 optionVal = key;
		       }
		       if (key.indexOf("_") == key.length - 1) {
			 key = key.substring(0, key.length - 1);
		       }
		       var split = rawValue.indexOf("|");
		       var displayCls;
		       if (split == -1) {
			 displayCls = "mce_formatPreview mce_" + key;
		       } else {
			 displayCls = "mce_formatPreview mce_" + rawValue.substring(0, split) + " " + rawValue.substring(split + 1);
			 optionVal = rawValue.substring(0, split);
		       }
		       c.add(i18n("tinymce_style_option_" + key, key), optionVal, { "class": displayCls });
		       Debug.richtext.println("Added styled format select option ", i18n("tinymce_style_option_" + key, key), " -&gt; ", optionVal);
		     });
      }
      return c;
    }
  },
  getInfo: function() {
    return {
      longname : "Styled Format Select",
      author : 'Traction Software, Inc.',
      authorurl : 'http://traction.tractionsoftware.com/',
      infourl : 'https://teampage.tractionsoftware.com/traction/page/server/Alignment Controls Plugin for TinyMCE 3',
      version : "2.1"
    };
  }
};

//------------------------------------------------------------------------
// traction.edit.tinymce.plugins.TractionImage


Traction.Edit.TinyMce.Plugins.TractionImage = {
  PLUGIN_NAMESPACE: "TractionImagePlugin",
  PLUGIN_NAME: "tractionimage",
  createAndAddPlugin: function() {
    Debug.richtext.println("[TractionImage] Creating and adding the Traction Image TinyMCE plug-in.");
    tinymce.create("tinymce.plugins." + Traction.Edit.TinyMce.Plugins.TractionImage.PLUGIN_NAMESPACE,
		   Traction.Edit.TinyMce.Plugins.TractionImage.pluginObj);
    tinymce.PluginManager.add(Traction.Edit.TinyMce.Plugins.TractionImage.PLUGIN_NAME,
			      tinymce.plugins.TractionImagePlugin);
  }
};

Traction.Edit.TinyMce.Plugins.TractionImage.pluginObj = {
  COMMAND: "tractionImage",
  BUTTON_NAME: "tractionimage",
  IMAGE_PATH: "/img/image.png",
  _getImageDialogUrl: function(ed, imgElement) {
    var disableAutoScale = ed.dom.getAttrib(imgElement,
					    Traction.Edit.TinyMce.IMAGE_TOKEN_DISABLE_AUTOSCALE_ATTRIBUTE_NAME,
					    "false");
    var display          = ed.dom.getAttrib(imgElement,
					    Traction.Edit.TinyMce.IMAGE_TOKEN_DISPLAY_ATTRIBUTE_NAME,
					    "auto");
    var autoCapture      = ed.dom.getAttrib(imgElement,
					    Traction.Edit.TinyMce.IMAGE_TOKEN_AUTOCAPTURE_ATTRIBUTE_NAME,
					    "true");
    return FORM_ACTION_READ_ONLY + "?" +
      "type=" + Traction.Edit.TinyMce.INSERT_IMAGE_VIEW_TYPE +
      "&uploaded=false" +
      Traction.Edit.Util.getAttachmentsUrlParameters(ed.getElement().form) +
      "&disableautoscale=" + disableAutoScale + "&display=" + display + "&autocapture=" + autoCapture;
  },
  init: function(ed, url) {
    Debug.richtext.println("[StyledFormatSelect] initializing plug-in instance at ", url, " for ", ed.editorId);
    var addCommand = function() {
      var imgElement = ed.selection.getNode();
      if (!ed.selection.isCollapsed() && !Traction.Edit.TinyMce.isRealImage(ed, imgElement)) {
	return;
      }
      var openOptions = {
	file: this._getImageDialogUrl(ed, imgElement),
	width: 375,
	popup_css: false,
	height: parseInt(ua("imageedit_window_height", "475"), 10),
	inline: 0
      };
      ed.windowManager.open(openOptions,
			    { plugin_url: url });
    };
    ed.addCommand(Traction.Edit.TinyMce.Plugins.TractionImage.pluginObj.COMMAND, addCommand, this);
    var regOptions = {
      title : i18n("tinymce_tooltip_image", "Insert/Edit Image"),
      image: url + "/img/image.png",
      cmd : Traction.Edit.TinyMce.Plugins.TractionImage.pluginObj.COMMAND
    };
    ed.addButton(Traction.Edit.TinyMce.Plugins.TractionImage.pluginObj.BUTTON_NAME, regOptions);
    var onContextMenuForImage = function(th, m, e) {
      var sm, se = ed.selection, el = se.getNode() || ed.getBody();
      if (el.nodeName == "IMG" && Traction.Edit.TinyMce.isRealImage(ed, el)) {
	m.removeAll();
	Traction.Edit.TinyMce.addImageContextMenuItem(m, url);
      }
    };
    var addTractionImageContextMenuItem = function() {
      if (ed && ed.plugins.contextmenu) {
	ed.plugins.contextmenu.onContextMenu.add(onContextMenuForImage);
      }
    };
    ed.onInit.add(addTractionImageContextMenuItem);
    ed.onNodeChange.add(this.handleNodeChange, this);
  },
  handleNodeChange: function(ed, controlManager, selElement, collapsed) {
    controlManager.setDisabled(Traction.Edit.TinyMce.Plugins.TractionImage.pluginObj.BUTTON_NAME,
			       !Traction.Edit.TinyMce.canInsertOrEditImage(ed, selElement, collapsed));
    controlManager.setActive(Traction.Edit.TinyMce.Plugins.TractionImage.pluginObj.BUTTON_NAME,
			     Traction.Edit.TinyMce.selectionIsImage(ed, selElement, collapsed));
  },
  getInfo: function() {
    return {
      longname : 'Traction Image Insert/Edit',
      author : 'Traction Software, Inc.',
      authorurl : 'http://traction.tractionsoftware.com/',
      infourl : 'https://teampage.tractionsoftware.com/traction/page/server/Traction Image Plugin for TinyMCE 3',
      version : "2.0"
    };
  }
};

//------------------------------------------------------------------------
// traction.edit.tinymce.plugins.TractionLink


Traction.Edit.TinyMce.Plugins.TractionLink = {
  PLUGIN_NAMESPACE: "TractionLinkPlugin",
  PLUGIN_NAME: "tractionlink",
  createAndAddPlugin: function() {
    Debug.richtext.println("[TractionLink] Creating and adding the Traction Link TinyMCE plug-in.");
    tinymce.create("tinymce.plugins." + Traction.Edit.TinyMce.Plugins.TractionLink.PLUGIN_NAMESPACE,
		   Traction.Edit.TinyMce.Plugins.TractionLink.pluginObj);
    tinymce.PluginManager.add(Traction.Edit.TinyMce.Plugins.TractionLink.PLUGIN_NAME,
			      tinymce.plugins.TractionLinkPlugin);
  }
};

Traction.Edit.TinyMce.Plugins.TractionLink.pluginObj = {
  COMMAND: "tractionLink",
  BUTTON_NAME: "tractionlink",
  IMAGE_PATH: "/img/link.gif",
  _getLinkText: function(ed, linkElement) {
    var selectedText = ed.selection.getContent({format : 'text'});
    if (selectedText == "" && linkElement) {
      selectedText = linkElement.innerHTML;
    }
    return selectedText;
  },
  _getLinkDialogUrl: function(ed, linkElement) {
    var selectedText = this._getLinkText(ed, linkElement);
    var rsToken;
    var href;
    if (linkElement) {
      href = ed.dom.getAttrib(linkElement, "href", "");
      href = ed.convertURL(href, linkElement, true);
      action = "edit";
      if (href.indexOf("../") == 0) {
	href = href.substring(2);
      }
      rsToken = ed.dom.getAttrib(linkElement, Traction.Edit.TinyMce.RS_EXPRESSION_ATTRIBUTE_NAME, "");
      if (rsToken == "") {
	rsToken = "[[ /link " + href + " ]]";
	Debug.richtext.println("[TractionLink] Writing RS Token '", rsToken, "' for link element that doesn't have a ", Traction.Edit.TinyMce.RS_EXPRESSION_ATTRIBUTE_NAME, " attribute.");
      }
    } else {
      href = "";
      rsToken = "";
      action = "new";
    }
    var formElm = ed.getElement().form;
    Debug.richtext.println("[TractionLink] Using rs token: ", rsToken);
    var urlString =
      FORM_ACTION_READ_ONLY + "?" +
      "type=" + Traction.Edit.TinyMce.INSERT_LINK_VIEW_TYPE +
      "&tokenedit_action=" + action +
      (rsToken ? ("&rstoken=" + encode_url_parameter(rsToken)) : "") +
      Traction.Edit.Util.getAttachmentsUrlParameters(formElm) +
      Traction.Edit.Util.getProjectURLParameter(formElm) +
      Traction.Edit.Util.getEditTargetURLParameter(formElm) +
      Traction.Edit.Util.getSectionsURLParameters(formElm) +
      ("&updatelink=" + ((linkElement != null) ? "true" : "false"));
    if (selectedText != null && selectedText != "") {
      urlString += "&linktext=" + encode_url_parameter(selectedText);
    }
    var showTitle = ed.dom.getAttrib(linkElement, Traction.Edit.TinyMce.LINK_TOKEN_REFERENCE_SHOW_TITLE_ATTRIBUTE_NAME, null);
    var showID    = ed.dom.getAttrib(linkElement, Traction.Edit.TinyMce.LINK_TOKEN_REFERENCE_SHOW_ID_ATTRIBUTE_NAME, null);
    if (showTitle) {
      urlString += "&showtitle=" + showTitle;
    }
    if (showID) {
      urlString += "&showid=" + showID;
    }
    Debug.richtext.println("[TractionLink] URL for insertlink view: '", urlString, "'");
    return urlString;
  },
  init: function(ed, url) {
    Debug.richtext.println("[TractionLink] initializing plug-in instance at ", url, " for ", ed.editorId);
    var addCommand = function() {
      var selElement = ed.selection.getNode();
      var parentLinkElement = ed.dom.getParent(selElement, "A");
      Debug.richtext.println("[TractionLink] Selected element: ", selElement);
      Debug.richtext.println("[TractionLink] Parent link element: ", parentLinkElement);
      if (selElement.nodeName == "IMG") {
	return;
      }
      var linkElement = (selElement.nodeName == "A") ? selElement : parentLinkElement;
      Debug.richtext.println("[TractionLink] Link element to edit: ", (linkElement ? linkElement.href : "(none)"));
      var openOptions = {
	file: this._getLinkDialogUrl(ed, linkElement),
	"width" : 565,
	"height" : 550,
	popup_css: false,
	inline: 0 // this cannot be an inline popup
      };
      ed.windowManager.open(openOptions,
			    { plugin_url: url });
    };
    ed.addCommand(Traction.Edit.TinyMce.Plugins.TractionLink.pluginObj.COMMAND, addCommand, this);
    var regOptions = {
      title : i18n("tinymce_tooltip_link", "Insert/Edit Link"),
      image: url + "/img/link.gif",
      cmd : Traction.Edit.TinyMce.Plugins.TractionLink.pluginObj.COMMAND
    };
    ed.addButton(Traction.Edit.TinyMce.Plugins.TractionLink.pluginObj.BUTTON_NAME, regOptions);
    ed.onNodeChange.add(this.handleNodeChange, this);
  },
  handleNodeChange: function(ed, controlManager, selElement, collapsed) {
    controlManager.setDisabled(Traction.Edit.TinyMce.Plugins.TractionLink.pluginObj.BUTTON_NAME,
			       !Traction.Edit.TinyMce.canInsertOrEditLink(ed, selElement, collapsed));
    controlManager.setActive(Traction.Edit.TinyMce.Plugins.TractionLink.pluginObj.BUTTON_NAME,
			     Traction.Edit.TinyMce.selectionIsLink(ed, selElement, collapsed));
  },
  getInfo: function() {
    return {
      longname : 'Traction Link Insert/Edit',
      author : 'Traction Software, Inc.',
      authorurl : 'http://traction.tractionsoftware.com/',
      infourl : 'https://teampage.tractionsoftware.com/traction/page/server/Traction Link Plugin for TinyMCE 3',
      version : "2.0"
    };
  }
};

//------------------------------------------------------------------------
// traction.edit.tinymce.plugins.TractionWidget


Traction.Edit.TinyMce.Plugins.TractionWidget = {
  PLUGIN_NAMESPACE: "TractionWidgetPlugin",
  PLUGIN_NAME: "tractionwidget",
  createAndAddPlugin: function() {
    Debug.richtext.println("Creating and adding the Traction Widget TinyMCE plug-in.");
    tinymce.create("tinymce.plugins." + Traction.Edit.TinyMce.Plugins.TractionWidget.PLUGIN_NAMESPACE,
		   Traction.Edit.TinyMce.Plugins.TractionWidget.pluginObj);
    tinymce.PluginManager.add(Traction.Edit.TinyMce.Plugins.TractionWidget.PLUGIN_NAME,
			      tinymce.plugins.TractionWidgetPlugin);
  }
};

Traction.Edit.TinyMce.Plugins.TractionWidget.pluginObj = {
  COMMAND: "tractionWidget",
  BUTTON_NAME: "tractionwidget",
  IMAGE_PATH: "/img/widget.png",
  _getWidgetDialogUrl: function(ed, widgetPlaceHolder) {
    var extras;
    var action;
    if (widgetPlaceHolder) {
      var tokenType = ed.dom.getAttrib(widgetPlaceHolder,
				       Traction.Edit.TinyMce.TOKEN_TYPE_ATTRIBUTE_NAME,
				       "");
      var rsToken   = ed.dom.getAttrib(widgetPlaceHolder,
				       Traction.Edit.TinyMce.RS_EXPRESSION_ATTRIBUTE_NAME,
				       "");
      action = "edit";
      extras += "&tokentype=" + tokenType + "&rstoken=" + encode_url_parameter(rsToken);
    } else {
      action = "init";
      extras = "";
    }
    var formElm = ed.getElement().form;
    return FORM_ACTION_READ_ONLY + "?" +
      "type=" + Traction.Edit.TinyMce.INSERT_WIDGET_VIEW_TYPE +
      "&tokenedit_action=" + action +
      "&uploaded=false" +
      extras +
      Traction.Edit.Util.getAttachmentsUrlParameters(formElm) +
      Traction.Edit.Util.getSectionsURLParameters(formElm);
  },
  init: function(ed, url) {
    var addCommand = function() {
      var widgetPlaceHolder = ed.selection.getNode();
      Debug.richtext.println("[TractionWidget] widgetPlaceHolder: ", widgetPlaceHolder);
      if (widgetPlaceHolder.nodeName == "IMG" && Traction.Edit.TinyMce.isRealImage(ed, widgetPlaceHolder)) {
	return;
      }
      if (widgetPlaceHolder.nodeName != "IMG") {
	widgetPlaceHolder = null;
      }
      var openOptions = {
	file: this._getWidgetDialogUrl(ed, widgetPlaceHolder),
	"width" : 410,
	"height" : parseInt(ua("widgetedit_window_height", "400"), 10),
	"scrollbars" : true,
	popup_css: false,
	inline: 0
      };
      ed.windowManager.open(openOptions,
			    { plugin_url: url });
    };
    ed.addCommand(Traction.Edit.TinyMce.Plugins.TractionWidget.pluginObj.COMMAND, addCommand, this);
    var regOptions = {
      title : i18n("tinymce_tooltip_widget", "Insert/Edit Widget"),
      image: url + Traction.Edit.TinyMce.Plugins.TractionWidget.pluginObj.IMAGE_PATH,
      cmd : Traction.Edit.TinyMce.Plugins.TractionWidget.pluginObj.COMMAND
    };
    ed.addButton(Traction.Edit.TinyMce.Plugins.TractionWidget.pluginObj.BUTTON_NAME, regOptions);
    var onContextMenuForWidget = function(th, m, e) {
      var sm, se = ed.selection, el = se.getNode() || ed.getBody();
      if (el.nodeName == "IMG" && !Traction.Edit.TinyMce.isRealImage(ed, el)) {
	m.removeAll();
	Traction.Edit.TinyMce.addWidgetContextMenuItem(m, url);
      }
    };
    var addTractionWidgetContextMenuItem = function() {
      Debug.richtext.println("[TractionWidget] addTractionWidgetContextMenuItem...");
      if (ed && ed.plugins.contextmenu) {
	ed.plugins.contextmenu.onContextMenu.add(onContextMenuForWidget);
      }
    };
    ed.onInit.add(addTractionWidgetContextMenuItem);
    ed.onNodeChange.add(this.handleNodeChange, this);
  },
  handleNodeChange: function(ed, controlManager, selElement, collapsed) {
    controlManager.setDisabled(Traction.Edit.TinyMce.Plugins.TractionWidget.pluginObj.BUTTON_NAME,
			       !Traction.Edit.TinyMce.canInsertOrEditWidget(ed, selElement, collapsed));
    controlManager.setActive(Traction.Edit.TinyMce.Plugins.TractionWidget.pluginObj.BUTTON_NAME,
			     Traction.Edit.TinyMce.selectionIsWidget(ed, selElement, collapsed));
  },
  getInfo: function() {
    return {
      longname : 'Traction Widget Insert/Edit',
      author : 'Traction Software, Inc.',
      authorurl : 'http://traction.tractionsoftware.com/',
      infourl : 'https://teampage.tractionsoftware.com/traction/page/server/Traction Widget Plugin for TinyMCE 3',
      version : "2.0"
    };
  }
};

//------------------------------------------------------------------------
// traction.fast.Fast

var Fast = {};
Fast.findSimilar = function(href, id, engine) {
  if (!engine) engine = "fast";
  href.innerHTML = "Searching...";
  var url = FORM_ACTION_READ_ONLY + "?type="+engine+"findsimilar_&tractionid=" + id;
  xmlget_async(url, null, true, Fast.findSimilarWakeup, [ href.parentNode ]);
};
Fast.findSimilarWakeup = function(content, args) {
  var node = args[0];
  node.innerHTML = content;
};
Fast.toggleAdvSearch = function(show) {
  if (cm) cm.hide();
  var advsearch_visible = document.getElementById("fastadvsearch_visible");
  var advsearch_hidden = document.getElementById("fastadvsearch_hidden");
  if (advsearch_visible && advsearch_visible != null &&
      advsearch_hidden && advsearch_hidden != null) {
    advsearch_visible.style.display = show ? "" : "none";
    advsearch_hidden.style.display = show ? "none" : "";
  }
  document.fastsearch.adv.value = show ? "t" : "f";
};
Fast.changedatetype = function(select) {
  if (select && select != null) {
    var datetype = currentSelValue(select);
    var ld = document.getElementById("daterange_ld");
    var sd = document.getElementById("daterange_sd");
    var ed = document.getElementById("daterange_ed");
    switch (datetype) {
    case "anytime":
      show_inline(false, ld);
      show_inline(false, sd);
      show_inline(false, ed);
      break;
    case "last":
      show_inline(true, ld);
      show_inline(false, sd);
      show_inline(false, ed);
      break;
    case "after":
      show_inline(false, ld);
      show_inline(true, sd);
      show_inline(false, ed);
      break;
    case "before":
      show_inline(false, ld);
      show_inline(false, sd);
      show_inline(true, ed);
      break;
    case "between":
      show_inline(false, ld);
      show_inline(true, sd);
      show_inline(true, ed);
      break;
    }
  }
};
Fast.choosedate = function(title, textbox) {
  openCalendarDialog(title,textbox.value);
  closeCalendarDialog = function(dialogname, datestr) {
    textbox.value = datestr;
  }
};
Fast.syncAdvSearch = function() {
  Fast.changedatetype(document.fastsearch.datetype);
};

//------------------------------------------------------------------------
// traction.fast.FastAdmin

Fast.Admin = {
  timer: null,
  autoRefresh: false,
  REFRESH_SECONDS: 5000,
  MAX_COUNT_BOUND: 1000,
  autoRefreshCountdown: 0,
  initCount: function() {
    this.autoRefreshCountdown = this.MAX_COUNT_BOUND;
    this.updateAutoRefreshCountdownDisplay();
  },
  toggleAutoRefresh: function(on) {
    this.autoRefresh = on;
    if (on) {
      this.initCount();
      this.refresh();
    } else {
      if (this.timer && this.timer != null) {
	clearTimeout(this.timer);
      }
      this.updateAutoRefreshCountdownDisplay();
    }
  },
  queueRefresh: function() {
    this.timer = setTimeout(this.refresh.bind(this), this.REFRESH_SECONDS);
  },
  refresh: function() {
    var url = FORM_ACTION_READ_ONLY + "?type="+document.fm.type.value+"&ajax=true&errors=false";
    xmlget_async(url, "ajaxprogress", false, this.refresh_wakeup.bind(this), []);
  },
  refresh_wakeup: function(content, args) {
    if (wasUnauthorized(content)) {
      failureUnauthorized();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    if (wasUnreachable(content)) {
      failureServerUnavailable();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    var error = findErrorAndFeedback(content);
    if (error[0] != null && error[0] != "" && error[0] != "undefined") {
      alert(error[0]);
      return;
    }
    this.autoRefreshCountdown --;
    var div = document.getElementById("replace_on_update");
    div.innerHTML = new String(content);
    if (this.autoRefreshCountdown > 0) {
      this.updateAutoRefreshCountdownDisplay();
      this.queueRefresh();
    } else {
      this.toggleAutoRefresh(false);
      this.updateAutoRefreshCountdownDisplay();
    }
  },
  updateAutoRefreshCountdownDisplay: function() {
    if (this.autoRefresh) {
      document.getElementById("fast-autorefresh-countdown").innerHTML =
      "(" + (new MessageFormat(i18n("n_more", "{0} more"))).format(this.autoRefreshCountdown) + ")";
    } else {
      document.getElementById("fast-autorefresh-countdown").innerHTML = "";
      document.getElementById("auto_refresh").checked = false;
    }
  },
  reindex: function(button) {
    if ((needsIndex == 0 && pendingIndex == 0 && complete == 0) ||
	confirm(i18n("fastadmin_start_feeding_button_confirmation_message", "Once the initial feed of Traction content is complete, the index will be updated automatically with new and updated content.\n\nAre you sure you want to reset the document status and feed all of the content again?"))) {
      document.fm.fb_submit.value = "index";
      document.fm.submit();
    }
    return false;
  },
  retrytype: function(type) {
    document.fm.retrytype.value = type;
    document.fm.fb_submit.value = "retrytype";
    document.fm.submit();
    return false;
  },
  verify: function() {
    if (confirm(i18n("fastadmin_start_verifying_button_confirmation_message", "Are you sure you want to verify the document status of all of the content?"))) {
      document.fm.fb_submit.value = "verify";    
      document.fm.submit();
    }
    return false;
  },
  deleteindex: function() {
    if (confirm("Are you sure you want to delete the entire index?")) {
      document.fm.fb_submit.value = "delete";    
      document.fm.submit();
    }
    return false;    
  },
  stopProcess: function(process) {
    document.fm.process.value = process;    
    document.fm.fb_submit.value = "stop";    
    document.fm.submit();    
  },
  startProcess: function(process) {
    document.fm.process.value = process;    
    document.fm.fb_submit.value = "start";    
    document.fm.submit();        
  },
  restartProcess: function(process) {
    document.fm.process.value = process;    
    document.fm.fb_submit.value = "restart";    
    document.fm.submit();        
  },
  resetIndex: function() {
    document.fm.fb_submit.value = "resetindex";    
    document.fm.submit();            
  },
  resumeIndexing: function() {
    document.fm.fb_submit.value = "resumeindexing";    
    document.fm.submit();            
  },
  suspendIndexing: function() {
    document.fm.fb_submit.value = "suspendindexing";    
    document.fm.submit();            
  }
};

//------------------------------------------------------------------------
// traction.fast.FastFilters


Fast.Filters = {
  init: function(filterArray, termsArray) {
    Fast.Filters.filters = filterArray;
    Fast.Filters.terms = termsArray;
  },
  remove: function(tag, filterIndex) {
    var filter = "";
    var filters = Fast.Filters.filters;
    for (var i=0; i<filters.length; i++) {
      if (i == filterIndex-1) { // 1 based
      } else {
	if (filter != "") {
	  filter += " ";
	}
	filter += filters[i];
      }
    }
    document.fastsearch.filter.value = filter;
    document.fastsearch.submit();
  },
  hoverFilter: function(tag, filterIndex) {
    Fast.Filters.showRemove(filterIndex, true);
    tag.style.textDecoration = "line-through";
    tag.style.cursor = ua("css_cursor_property_pointer_value", "pointer");    
  },
  unhoverFilter: function(tag, filterIndex) {
    Fast.Filters.showRemove(filterIndex, false);
    tag.style.textDecoration = "none";
  },
  showRemove: function(filterIndex, show) {
    var remove = document.getElementById("fast_removefilter_"+filterIndex);
    if (remove != null) {
      remove.style.display = show ? "inline" : "none";
    }
  }
};

//------------------------------------------------------------------------
// traction.fast.FastMod

Fast.Mod = Class.create();
Fast.Mod.compareByName = function(a,b) {
  var an = a.displayname.toLowerCase();
  var bn = b.displayname.toLowerCase();
  if (an < bn) {
    return -1;
  } else if (an == bn) {
    return 0;
  } else {
    return 1;
  }
};
Fast.Mod.compareByLabelName = function(a,b) {
  var an = a.displayname.toLowerCase();
  var bn = b.displayname.toLowerCase();
  if (an < bn) {
    return -1;
  } else if (an == bn) {
    return 0;
  } else {
    return 1;
  }
};
Fast.Mod.compareByCount = function(a,b) {
  if (a.count > b.count) {
    return -1;
  } else if (a.count == b.count) {
    return Fast.Mod.compareByName(a,b);
  } else {
    return 1;
  }
};
Fast.Mod.prototype = {
  
  fieldname: "",
  name: "",
  displayname: "",
  count: 0,
  filter: "",
  
  queuelink: null,
  initialize: function(filter, name, displayname, count, fieldname) {
    this.filter = filter;
    this.name = name;
    this.displayname = displayname;
    this.count = count;
    this.fieldname = fieldname;
  },
  navigate: function(e) {
    var newfilter = unescape_fromattribute(document.fastsearch.filter.value);
    if (newfilter.length > 0) {
      newfilter += " ";
    }
    newfilter += this.filter;
    document.fastsearch.filter.value = newfilter;
    document.fastsearch.submit();
  },
  add: function(e) {
    Fast.FastNavQueue.get().add(this, Util.getMousePosition(e));
  },
  remove: function(e) {
    Fast.FastNavQueue.get().remove(this);
  },
  setQueueLink: function(tag) {
    if (this.queuelink != null) {
    }
    this.queuelink = tag;
    tag.onclick = this.click.bindAsEventListener(this);
    tag.onmouseover = this.hover.bindAsEventListener(this);
    tag.onmouseout = this.unhover.bindAsEventListener(this);
    tag.style.cursor = ua("css_cursor_property_pointer_value", "pointer");
  },
  click: function(e) {
    this.remove(e);
  },
  hover: function(e) {
    this.queuelink.style.textDecoration = "line-through";
  },
  unhover: function(e) {
    this.queuelink.style.textDecoration = "none";
  },
  equals: function(other) {
    try {
      return (this.filter == other.filter &&
	      this.name == other.name &&
	      this.fieldname == other.fieldname);
    } catch (e) {
      return false;
    }
  }
};

//------------------------------------------------------------------------
// traction.fast.FastNavFrame


Fast.NavFrame = Class.create();
Fast.NavFrame.instance_ = null;
Fast.NavFrame.create = function(id) {
  Fast.NavFrame.instance_ = new Fast.NavFrame(id);
};
Fast.NavFrame.show = function(nav) {
  Fast.NavFrame.instance_.show(nav);
};
Fast.NavFrame.prototype = {
  
  id: "",
  
  nav: null,
  
  sortby: 0,
  
  scaleFont: false,
  initialize: function(id) {
    this.id = id;
    var framediv = document.getElementById(this.id);
    framediv.style.visibility = "hidden";
    framediv.style.display = "block";
    framediv.style.position = "absolute";
    this.bindclick(this.id+"_close", this, this.close);
    this.bindclick(this.id+"_sortbyname", this, this.sortByName);
    this.bindclick(this.id+"_sortbycount", this, this.sortByCount);
    this.bindclick(this.id+"_sortbycloud", this, this.sortByCloud);
    this.bindclick(this.id+"_groupby_chk", this, this.groupByClicked);
    this.bindclick(this.id+"_scalefont_chk", this, this.scaleFontClicked);
    this.scaleFont = (Util.CookiePrefs.get(Util.CookiePrefs.NAVFRAME_SCALE_FONT, "f") == "t");
    this.sortby = Util.CookiePrefs.get(Util.CookiePrefs.NAVFRAME_SORT_BY, 0);
  },
  bindclick: function(elmname, scope, handler) {
    var elm = document.getElementById(elmname);
    if (elm != null) {
      elm.onclick = handler.bindAsEventListener(scope);
    }    
  },
  
  setTitle: function(title) {
    var titlediv = document.getElementById(this.id+"_title");
    if (titlediv != null) {
      titlediv.innerHTML = title;
    }
  },
  
  showGroupBy: function(show) {
  },
   
  getTermDiv: function() {
    return document.getElementById(this.id+"_terms");
  },
  showProjectSelector: function() {
    if (document.fastsearch.proj_selector) {
      document.fastsearch.proj_selector.style.display = "";
    }
  },
  hideProjectSelector: function() {
    if (document.fastsearch.proj_selector) {
      document.fastsearch.proj_selector.style.display = "none";
    }
  },
  
  close: function() {
    this.zoom.hide();
    this.nav.setSelected(false);
    this.showProjectSelector();
    return false;
  },
  
  hide: function() {
    this.zoom.zoomOut({x:0,y:0});
    this.nav.setSelected(false);
    this.showProjectSelector();
  },
  sortByName: function() {
    this.sortBy(0);
    return false;
  },
  sortByCount: function() {
    this.sortBy(1);
    return false;
  },
  sortByCloud: function() {
    this.sortBy(2);
    return false;
  },
  getSortByDiv: function(sortby) {
    var id;
    switch (sortby) {
    case 0: id = this.id+"_sortbyname"; break;
    case 1: id = this.id+"_sortbycount"; break;
    case 2: id = this.id+"_sortbycloud"; break;
    }
    return document.getElementById(id);
  },
  sortBy: function(sortby) {
    this.sortby = sortby;
    this.show();    
    Util.CookiePrefs.set(Util.CookiePrefs.NAVFRAME_SORT_BY, sortby);
  },
  boldSortBy: function() {
    for (var i=0; i<3; i++) {
      var div = this.getSortByDiv(i);
      if (div != null) {
	if (i == this.sortby) {
	  div.style.fontWeight = "bold";
	} else {
	  div.style.fontWeight = "normal";	
	}
      }
    }
  },
  groupByClicked: function() {
    return true;
  },
  
  scaleFontClicked: function(show) {
    this.scaleFont = !this.scaleFont;
    this.show();
    Util.CookiePrefs.set(Util.CookiePrefs.NAVFRAME_SCALE_FONT, this.scaleFont ? "t" : "f");
    return true;
  },
  show: function(nav, event) {
    this.hideProjectSelector();
    if (this.zoom == null) {
      var framediv = document.getElementById(this.id);
      framediv.parentNode.removeChild(framediv);
      document.body.appendChild(framediv);
      this.zoom = new Traction.ZoomExpander(framediv, { className: "fastsearch-zoompanel" });
    }
    if (!nav) nav = this.nav;
    if (this.nav != null && this.nav.fieldname != nav.fieldname) {
      this.nav.setSelected(false);
    }
    this.nav = nav;
    this.setTitle(nav.title);
    this.boldSortBy();
    this.nav.setSelected(true);
    var chk = document.getElementById(this.id+"_scalefont_chk");
    chk.checked = this.scaleFont;
    var table = document.createElement("TABLE");
    table.cellSpacing = "0";
    table.cellPadding = "0";    
    var tbody = document.createElement("TBODY");
    table.appendChild(tbody);
    var tr = document.createElement("TR");
    tr.style.verticalAlign = "top";
    tbody.appendChild(tr);
    var td;
    var mods = nav.mods;
    var isLabel = (nav.fieldname == "label");
    this.sort(mods, isLabel);
    var cols = (mods.length > 40) ? 3 : (mods.length > 20) ? 2 : 1;
    var rowsize = mods.length / cols;
    var rows = 0;
    var lastproj = "";
    var min = 0;
    var max = 0;
    if (this.scaleFont) {
      for (var i=0; i<mods.length; i++) {
	var count = mods[i].count;
	if (min > count) min = count;
	if (max < count) max = count;
      }
    }
    var buckets = 5;
    var bucketrange = max - min;
    var bucketsize = (max - min) / buckets;
    for (var i=0; i<mods.length; i++) {
      var cur = mods[i];
      if (rows == 0 || rows >= rowsize) {
	td = document.createElement("TD");	
	tr.appendChild(td);
	rows = 0;
      }
      var div = document.createElement("DIV");
      var A = document.createElement("A");
      if (isLabel && this.sortby != 1) {
	var str = cur.displayname;
	var colon2 = str.indexOf(':',2);
	var proj = str.substring(1,colon2);
	var label = str.substring(colon2+1);
	if (proj != lastproj) {
	  var h5 = document.createElement("H5");
	  h5.innerHTML = proj;
	  h5.style.marginLeft = "12px"; // 9px img + 3px margin
	  td.appendChild(h5);
	  if (rows != 0) {
	    h5.style.marginTop = "6px";
	    rows++;
	  }
	  lastproj = proj;
	  rows++;
	}
	if (this.sortby != 2) {
	  A.innerHTML = label+" ("+cur.count+")";
	}
	else if (this.sortby == 2) {
	  A.innerHTML = label+" ("+cur.count+")";
	}
      }
      else {
	A.innerHTML = cur.displayname+" ("+cur.count+")";
      }
      div.style.whiteSpace = "nowrap";
      var navlink = new Fast.FastNavLink(div, A, cur);
      if (this.scaleFont == true) {
	var px;
	var mybucket = Math.floor((cur.count - min) / bucketsize);
	mybucket *= 2;
	switch (mybucket) {
	case 0: px = "10px"; break;
	case 1: px = "11px"; break;
	case 2: px = "12px"; break;
	case 3: px = "13px"; break;
	case 4: px = "14px"; break;
	case 5: px = "15px"; break;
	case 6: px = "16px"; break;
	case 7: px = "17px"; break;
	case 8: px = "18px"; break;
	case 9: px = "19px"; break;
	default: px = "20px"; break;
	} 
	A.style.fontSize = px;
      }
      div.appendChild(A);
      td.appendChild(div);
      rows++;
    }
    this.showGroupBy(nav.fieldname == "label");
    var termdiv = this.getTermDiv();
    termdiv.innerHTML = "";
    termdiv.style.width = "auto";
    termdiv.style.height = "auto";
    termdiv.appendChild(table);
    var framediv = document.getElementById(this.id);
    framediv.style.width = "auto";
    framediv.style.height = "auto";
    if (event && event != null) {
      framediv.style.visibility = "hidden";
      framediv.style.display = "block";
      var firsttable = getFirstChildByName(framediv, "TABLE");
      var bounds = Util.getElementBounds(firsttable);
      var titlebounds = Util.getElementBounds(this.nav.titlediv);
      var leftbounds = Util.getElementBounds(document.getElementById("left"));
      framediv.style.display = "none";
      bounds.y = titlebounds.y-2;
      bounds.x = leftbounds.x2()+1;
      var startpt = { x:bounds.x, y:bounds.y };
      var endpt = startpt;
      bounds = Util.keepInBounds(bounds, Util.getViewportBounds());
      this.zoom.zoomIn(startpt, endpt, bounds);
    } 
    else {
      this.zoom.show();
    }
    if (ua("selectors_always_on_top", "false") == "true") {
      Fast.toggleAdvSearch(0);
    }
  },
  sort: function(mods, isLabel) {
    mods.sort((this.sortby == 1) ? Fast.Mod.compareByCount : Fast.Mod.compareByName);
  }
};

//------------------------------------------------------------------------
// traction.fast.FastNav


Fast.Nav = Class.create();
Fast.Nav.navigators = new Array();
Fast.Nav.create = function(fieldname, displayname, modarray, isKeywords) {
  Fast.Nav.navigators[fieldname] = new Fast.Nav(fieldname, displayname, modarray, isKeywords);
};
Fast.Nav.show = function(fieldname) {
  var nav = Fast.Nav.navigators[fieldname];
  if (nav != null) {
    nav.show();
  }
};
Fast.Nav.prototype = {
  
  fieldname: "",
  title: "",
  mods: null,
  size: 0,
  arrow: null,
  titlediv: null,
  selected: false,
  hovered: false,
  
  initialize: function(fieldname, title, modarray, isKeywords) {
    this.fieldname = fieldname;
    this.title = title;
    this.mods = new Array();
    if (isKeywords) {
    } 
    else {
      for (var i=0; i<modarray.length; i++) {
	var arr = modarray[i];
	this.mods[this.mods.length] = new Fast.Mod(arr[0],arr[1],arr[2],arr[3],this.fieldname);
      }
    }
    var morelink = document.getElementById(this.fieldname+"_more");
    if (morelink != null) {
      morelink.onclick = this.moreClick.bindAsEventListener(this);
      morelink.onmouseover = this.moreHover.bindAsEventListener(this);
      morelink.onmouseout = this.moreUnhover.bindAsEventListener(this);
    }
    this.titlediv = document.getElementById(this.fieldname+"_title");
    if (this.titlediv != null) {
      this.titlediv.onclick = this.titleClick.bindAsEventListener(this);
      this.titlediv.onmouseover = this.titleHover.bindAsEventListener(this);
      this.titlediv.onmouseout = this.titleUnhover.bindAsEventListener(this);
      this.titlediv.style.cursor = ua("css_cursor_property_pointer_value", "pointer");
    }
    this.arrow = document.createElement("IMG");
    with (this.arrow) {
      src = "/images/submenu_grey.gif";
      border = "";
      style["float"] = "right";
      style.position = "absolute";
      style.right = "5px";
      style.padding = "2px 3px 0px 0px";
    }    
    this.titlediv.appendChild(this.arrow);
  },
  moreClick: function(event) {
    var navframe = Fast.NavFrame.instance_;
    navframe.show(this, event);
  },
  moreHover: function(event) {
  },
  moreUnhover: function(event) {
  },
  titleClick: function(event) {
    var navframe = Fast.NavFrame.instance_;
    if (this.selected) {
      navframe.hide();
      this.setTitleClass("fastsearch-navtitle-hover");
    } else {
      navframe.show(this, event);    
    }
  },
  titleHover: function(event) {
    this.hovered = true;
    this.setTitleClass("fastsearch-navtitle-hover");
  },
  titleUnhover: function(event) {
    this.hovered = false;
    this.setSelected(this.selected);
  },
  setTitleClass: function(cls) {
    if (this.selected) {
      this.arrow.src = "/images/submenu2.gif";
      this.arrow.style.display = "";
    }
    else if (this.hovered) {
      this.arrow.src = "/images/submenu.gif";      
      this.arrow.style.display = "";
    }
    else {
      this.arrow.src = "/images/submenu_grey.gif";      
    }
    var titlediv = document.getElementById(this.fieldname+"_title");
    titlediv.className = cls;
  },
  show: function() {
    var navframe = Fast.NavFrame.instance_;
    navframe.show(this);    
  },
  setSelected: function(selected) {
    this.selected = selected;
    if (this.selected) {
      this.setTitleClass("fastsearch-navtitle-selected");
    } else {
      this.setTitleClass("fastsearch-navtitle");
    }
  }
};

//------------------------------------------------------------------------
// traction.fast.FastNavLink

Fast.FastNavLink = Class.create();
Fast.FastNavLink.prototype = {
  
  mod: null,
   
  div: null,
  link: null,
  
  add: null,
  
  added: false,
  initialize: function(div, link, mod) {
    this.div = div;
    this.link = link;
    this.mod = mod;
    this.link.href = "javascript:void(0)";
    this.link.onclick = this.click.bindAsEventListener(this);
    this.div.onmouseover = this.hover.bindAsEventListener(this);
    this.div.onmouseout = this.unhover.bindAsEventListener(this);
    this.add = document.createElement("A");
    this.add.onclick = this.addclick.bindAsEventListener(this);
    this.img = document.createElement("IMG");
    this.add.appendChild(this.img);
    this.add.className = "fastsearch-addterm";
    this.add.title = "Add to multi-term filter";
    with (this.add) {
      style.display = "";
      style.visibility = "hidden";
      style.marginRight = "3px";
      style.cursor = ua("css_cursor_property_pointer_value", "pointer");      
    }
    this.added = Fast.FastNavQueue.get().contains(mod);
    this.refresh();
    this.div.insertBefore(this.add, this.div.firstChild);
  },
  click: function(e) {
    this.mod.navigate(e);
    return false;
  },
  hover: function(e) {
    this.add.style.visibility = "visible";
  },
  unhover: function(e) {
    if (!this.isAdded()) {
      this.add.style.visibility = "hidden";
    }
  },
  addclick: function(e) {
    if (this.added) {
      this.mod.remove();
    }
    else {
      this.added = true;
      this.mod.add(e);
    }
    this.refresh();
  },
  isAdded: function() {
    return this.added;
  },
  refresh: function() {
    if (this.isAdded()) {
      this.img.src = "/images/modern/check_on_green2.gif";
      this.add.style.visibility = "visible";
    } else {
      this.img.src = "/images/modern/icons/ic_plusA.gif";      
    }
  }
};

//------------------------------------------------------------------------
// traction.fast.FastNavQueue


Fast.FastNavQueue = Class.create();
Fast.FastNavQueue.apply = function() {
  Fast.FastNavQueue.get().apply();
}
Fast.FastNavQueue.get = function() {
  if (Fast.FastNavQueue.instance_ == null) {
    Fast.FastNavQueue.instance_ = new Fast.FastNavQueue("navqueue");
  }
  return Fast.FastNavQueue.instance_;
}
Fast.FastNavQueue.prototype = {
  
  div: null,
  
  filterdiv: null,
  
  mods: null,
  
  modheight: 0,
  lastmodtag: null,
  initialize: function(id) {
    this.div = document.getElementById(id);
    this.filterdiv = document.getElementById(id+"_filters");
    this.mods = new Array();
  },
  add: function(mod, srcpt) {
    if (!this.contains(mod)) {
      this.mods[this.mods.length] = mod;
      if (this.div.style.display == "none") {
	this.div.style.visibility = "hidden";
	this.div.style.display = "";
      }
      this.refresh(true);
      var rect;
      if (this.lastmodtag != null) {
	rect = Util.getElementBounds(this.lastmodtag);
      } else {
	rect = Util.getElementBounds(this.filterdiv);
      }
      rect.y += 2;
      var zoom = new Traction.ZoomExpander(null, { className: "navqueue-zoompanel", interval: 5, afterShow: this.onAnimationComplete.bind(this) });
      var panel = zoom.getpanel();
      panel.innerHTML = "<span style=\"white-space: nowrap\" href=\"#\">"+mod.displayname+"</span>";
      zoom.zoomIn(srcpt, null, rect);
    }
  },
  remove: function(mod) {
    var newmods = new Array();
    for (var i=0; i<this.mods.length; i++) {
      var cur = this.mods[i];
      if (cur.equals(mod)) {
      } else {
	newmods[newmods.length] = cur;
      }
    }
    this.mods = newmods;
    this.refresh(false);
    Fast.NavFrame.show();
  },
  onAnimationComplete: function() {
    this.refresh(false);
  },
  contains: function(mod) {
    for (var i=0; i<this.mods.length; i++) {
      var cur = this.mods[i];
      if (cur.equals(mod)) {
	return true;
      }
    }
    return false;
  },
  refresh: function(lastblank) {
    this.filterdiv.innerHTML = "";
    this.div.style.display = (this.mods.length > 0) ? "" : "none";
    this.div.style.visibility = "visible";
    for (var i=0; i<this.mods.length; i++) {
      var cur = this.mods[i];
      var tag = document.createElement("DIV");
      tag.className = "navqueue-filter";
      tag.setAttribute("title", "Remove Filter Term");
      if (lastblank && i==this.mods.length-1) {
	tag.innerHTML = "&nbsp;"
      } else {
	tag.innerHTML = cur.displayname;
      }
      cur.setQueueLink(tag);
      this.lastmodtag = tag;
      this.filterdiv.appendChild(tag);
    }
  },
  filter: function() {
    var ret = "";
    var none = document.getElementById("navqueue_none");
    if (none != null && none.checked) {
      for (var i=0; i<this.mods.length; i++) {
	var cur = this.mods[i];
	var curfilter = cur.filter;
	if (curfilter.startsWith("+")) {
	  curfilter = curfilter.substring(1);
	}
	if (i != 0) {
	  ret += " ";
	}
	ret += "-"+curfilter;
      }	
    } 
    else {
      var all = document.getElementById("navqueue_all");
      if (this.mods.length <= 1 || all == null || all.checked) {
	for (var i=0; i<this.mods.length; i++) {
	  var cur = this.mods[i];
	  var curfilter = cur.filter;
	  if (i != 0) {
	    ret += " ";
	  }
	  ret += curfilter;
	}
      }
      else {
	var any = document.getElementById("navqueue_any");
	if (any == null || any.checked) {
	  ret += "+(";
	  for (var i=0; i<this.mods.length; i++) {
	    var cur = this.mods[i];
	    var curfilter = cur.filter;
	    if (curfilter.startsWith("+")) {
	      curfilter = curfilter.substring(1);
	    }
	    if (i != 0) {
	      ret += " ";
	    }
	    ret += curfilter;
	  }
	  ret += ")";
	} 
      }
    }
    return ret;
  },
  apply: function() {
    var newfilter = document.fastsearch.filter.value;
    if (newfilter.length > 0) {
      newfilter += " ";
    }
    newfilter += this.filter();
    document.fastsearch.filter.value = newfilter;
    document.fastsearch.submit();
  }
};

//------------------------------------------------------------------------
// traction.fast.FastTerms


Fast.Terms = {
  init: function(termArray) {
    Fast.Terms.terms = termArray;
  },
  remove: function(tag, termIndex) {
    var query = "";
    var terms = Fast.Terms.terms;
    for (var i=0; i<terms.length; i++) {
      if (i == termIndex-1) { // 1 based
      } else {
	if (query != "") {
	  query += " ";
	}
	query += terms[i];
      }
    }
    document.fastsearch.query.value = query;
    document.fastsearch.submit();
  },
  hover: function(tag, termIndex) {
    tag.style.textDecoration = "line-through";
    tag.style.cursor = ua("css_cursor_property_pointer_value", "pointer");    
  },
  unhover: function(tag, termIndex) {
    tag.style.textDecoration = "none";
  }
};

//------------------------------------------------------------------------
// traction.ui.Button

Traction.Button = Class.create();
Traction.Button.prototype = {
  initialize: function(name, scope, handler, options) {
    this.setOptions(options);
    this.name = name;
    this.button = document.createElement("INPUT");
    this.button.type = "button";
    this.button.value = name;
    this.button.style.padding = this.options.padding;
    Events.attach(this.button, Events.Click, scope, handler);
  },
  setOptions: function(options) {
    this.options = Object.extend({
      padding: "0px 10px"
	  }, options || {});
  },  
  getButton: function() { 
    return this.button; 
  }
};

//------------------------------------------------------------------------
// traction.ui.TableSelectRow


Traction.TableSelectRow = Class.create();

Traction.TableSelectRow.fromTR = function(TR, dragDrop) {
  var value = TR.getAttribute("value");
  return new Traction.TableSelectRow(TR, value, dragDrop);
};

Traction.TableSelectRow.create = function(text, value, dragDrop) {
  if (typeof text == 'string') {
    text = [ text ];  // always an array of TD innerHTML
  }
  var TR = document.createElement("TR");
  for (var i=0; i<text.length; i++) {
    var TD = document.createElement("TD");
    TD.innerHTML = text[i];
    TR.appendChild(TD);
  }
  return new Traction.TableSelectRow(TR, value, dragDrop);
};
Traction.TableSelectRow.prototype = {
  
  TR: null,
  value: null,
  
  hovered: false,
  dragging: false,
  draggable: null,
  dragoffset: null,
  dragblank: null,
  
  selectionChanged: null,
  
  initialize: function(TR, value, dragDrop) {
    this.TR = TR;
    this.value = value;
    var TDs = TR.getElementsByTagName("TD");
    for (var i=0; i<TDs.length; i++) {
      var TD = TDs[i];
      TD.style.cursor = ua("css_cursor_property_pointer_value", "pointer");      
      TD.style.MozUserSelect = "none";
      TD.style.KhtmlUserSelect = "none";
      TD.style.userSelect = "none";
      Events.kill(TD, Events.SelectStart);
      Events.attach(TD, Events.MouseOver, this, this.mouseover);
      Events.attach(TD, Events.MouseOut, this, this.mouseout);
      Events.attach(TD, Events.Click, this, this.click);
      if (dragDrop) {
	Events.attach(TD, Events.MouseDown, this, this.mousedown);
	Events.attach(document.body, Events.MouseMove, this, this.mousemove);
	Events.attach(document.body, Events.MouseUp, this, this.mouseup);
      }
    }
  },
  click: function(event) {
    var wasSelected = this.isSelected();
    this.setSelected(true);
    if (this.selectionChanged != null) {
      this.selectionChanged(this, wasSelected, event.shiftKey);
    }
    return false;
  },
  mousedown: function(event) {
    Debug.println("mousedown");
    this.dragging = true;
    return false;
  },
  getNextBounds: function() {
    var next = this.TR.nextSibling;
    while (next != null && next.tagName != "TR") {
      next = next.nextSibling;
    }
    return (next != null) ? Util.getElementBounds(next) : null;
  },
  getPrevBounds: function() {
    var prev = this.TR.previousSibling;
    while (prev != null && prev.tagName != "TR") {
      prev = prev.previousSibling;
    }
    return (prev != null) ? Util.getElementBounds(prev) : null;
  },
  getTableId: function() {
    var table = getParentByName(this.TR, "TABLE");
    return table.id;
  },
  mousemove: function(event) {
    if (this.dragging) {
      var mouse = Util.getMousePosition(event);
      var bounds = Util.getElementBounds(this.TR);
      if (this.draggable == null) {
	this.draggable = document.createElement("DIV");
	this.draggable.style.position = "absolute";
	this.draggable.style.left = px(bounds.x+3);
	this.draggable.style.zIndex = 10000;
	this.draggable.style.backgroundColor = "transparent";
	this.draggable.style.color = "#000";
	this.draggable.style.margin = "0";
	this.draggable.style.padding = "0";
	Events.kill(this.draggable, Events.SelectStart);
	this.dragoffset = { 
	  x: bounds.x - mouse.x, 
	  y: bounds.y - mouse.y 
	};
	var origtable = getParentByName(this.TR, "TABLE");
	var table = document.createElement("TABLE");
	table.className = "dragging";
	table.cellPadding = origtable.cellPadding;
	table.cellSpacing = "0";
	var trcopy = this.TR.cloneNode(true); // deep copy
	trcopy.className = "dragging";
	var tbody = document.createElement("TBODY");
	tbody.appendChild(trcopy);
	table.appendChild(tbody);
	var origTDs = this.TR.getElementsByTagName("TD");
	var copyTDs = trcopy.getElementsByTagName("TD");
	for (var i=0; i<origTDs.length; i++) {
	  var size = Util.getElementBounds(origTDs[i]);
	  copyTDs[i].style.width = px(size.w-6);
	}
	this.draggable.appendChild(table);
	document.body.appendChild(this.draggable);
      }
      this.blank();
      var tbodybounds = Util.getElementBounds(getParentByName(this.TR, "TBODY"));
      var newy = mouse.y + this.dragoffset.y;
      if (newy < tbodybounds.y) newy = tbodybounds.y;
      if (newy > tbodybounds.y+tbodybounds.h-bounds.h) newy = tbodybounds.y+tbodybounds.h-bounds.h;
      var oldy = nopx(this.draggable.style.top);
      this.draggable.style.top = px(newy);
      var swapped = null;
      if (newy < oldy) {
	var pbounds = this.getPrevBounds();
	if (pbounds != null && newy < pbounds.y + pbounds.h - 3) {
	  swapped = Traction.TableSelect.swap(this.getTableId(), this, -1);
	}
      } else if (newy > oldy) {
	var nbounds = this.getNextBounds();
	if (nbounds != null && newy + bounds.h > nbounds.y + 3) {
	  swapped = Traction.TableSelect.swap(this.getTableId(), this, 1);
	}
      }
      if (swapped != null) {
	Debug.println("clear hover on "+swapped.value);
	swapped.setHovered(false);
      }
      if (userAgentName == "safari") {
	if (this.TR.nextSibling != null) {
	  var nextTR = this.TR.nextSibling;
	  if (nextTR != null) {
	    var TDs = nextTR.getElementsByTagName("TD");
	    for (var i=0; i<TDs.length; i++) {
	      TDs[i].innerHTML += "";
	    }
	  }
	}
      }
    }
    return false;
  },
  blank: function() {
    var TDs = this.TR.getElementsByTagName("TD");
    for (var i=0; i<TDs.length; i++) {
      TDs[i].style.visibility = "hidden";
    }
  },
  unblank: function() {
    var TDs = this.TR.getElementsByTagName("TD");
    for (var i=0; i<TDs.length; i++) {
      TDs[i].style.visibility = "visible";
    }    
  },
  mouseup: function(event) {
    this.dragging = false;
    if (this.draggable != null) {
      document.body.removeChild(this.draggable);
      this.draggable = null;
      this.unblank();
    }
    return false;
  },
  mouseover: function(event) {
    this.setHovered(true);
    return false;
  },
  mouseout: function(event) {
    this.setHovered(false);
    return false;
  },
  isSelected: function() {
    return this.TR.className.indexOf("selected") != -1;
  },
  setSelected: function(selected) {
    this.updateClass(selected);
  },
  setHovered: function(hovered) {
    this.hovered = hovered;
    this.updateClass(this.isSelected());
  },
  updateClass: function(selected) {
    var cls;
    if (selected) {
      if (this.hovered) {
	cls = "hoverselected";
      }
      else {
	cls = "selected";
      }
    }
    else if (this.hovered) {
      cls = "hover";
    }
    else {
      cls = "";
    }
    this.TR.className = cls;
  }
};

//------------------------------------------------------------------------
// traction.ui.TableSelect


Traction.TableSelect = Class.create();
Traction.TableSelect.registry = new Array();
Traction.TableSelect.MODE_SINGLE = 1;
Traction.TableSelect.MODE_MULTIPLE_TOGGLE = 2;
Traction.TableSelect.MODE_MULTIPLE_SHIFT = 3;
Traction.TableSelect.SUBMIT_NONE = 0;
Traction.TableSelect.SUBMIT_SELECTED = 1;
Traction.TableSelect.SUBMIT_ALL = 2;
Traction.TableSelect.SUBMIT_BOTH = 3;
Traction.TableSelect.add = function(id, text, value) { Traction.TableSelect.registry[id].add(text, value); };
Traction.TableSelect.swap = function(id, row, dir) { return Traction.TableSelect.registry[id].swap(row, dir); };
Traction.TableSelect.insert = function(id, index, text, value) { Traction.TableSelect.registry[id].insert(index, text, value); };
Traction.TableSelect.moveup = function(id) { Traction.TableSelect.registry[id].moveup(); };
Traction.TableSelect.movedown = function(id) { Traction.TableSelect.registry[id].movedown(); };
Traction.TableSelect.removeSelected = function(id, confirm) { Traction.TableSelect.registry[id].removeSelected(confirm); };
Traction.TableSelect.prototype = {
  element: null,
  TRparent: null,
  rows: null,
  onchange: null,
  
  hidden_all: null,
  hidden_selected: null,
  options: null,
  initialize: function(id, options) {
    this.setOptions(options);
    this.element = document.getElementById(id);
    this.rows = new Array();
    this.TRparent = this.element;
    var TBODY = this.element.getElementsByTagName("TBODY");
    if (TBODY != null && TBODY.length != 0) {
      this.TRparent = TBODY[0];
    }
    var TRs = this.TRparent.getElementsByTagName("TR");
    for (var i=0; i<TRs.length; i++) {
      this.rows[i] = Traction.TableSelectRow.fromTR(TRs[i], this.options.dragDropMove);
      this.rows[i].selectionChanged = this.selectionChanged.bind(this);
    }
    var parent = this.element.parentNode;
    if (this.options.submitMode == Traction.TableSelect.SUBMIT_ALL ||
	this.options.submitMode == Traction.TableSelect.SUBMIT_BOTH) {
      this.hidden_all = document.createElement("INPUT");
      this.hidden_all.setAttribute("type", "hidden");
      this.hidden_all.setAttribute("name", (this.options.submitMode == Traction.TableSelect.SUBMIT_ALL) ? id : id+"_all");
      parent.appendChild(this.hidden_all);
    }
    if (this.options.submitMode == Traction.TableSelect.SUBMIT_SELECTED ||
	this.options.submitMode == Traction.TableSelect.SUBMIT_BOTH) {
      this.hidden_selected = document.createElement("INPUT");
      this.hidden_selected.setAttribute("type", "hidden");
      this.hidden_selected.setAttribute("name", (this.options.submitMode == Traction.TableSelect.SUBMIT_SELECTED) ? id : id+"_selected");
      parent.appendChild(this.hidden_selected);
    }
    Traction.TableSelect.registry[id] = this;
    this.updateHidden();
  },
  setOptions: function(options) {
    this.options = Object.extend({
      selectMode: Traction.TableSelect.MODE_SINGLE,
      submitMode: Traction.TableSelect.SUBMIT_BOTH,
      dragDropMove: false
	  }, options || {});
  },
  add: function(text, value) {
    this.insert(-1, text, value);
  },
  
  insert: function(index, text, value) {
    Debug.println(index+","+value);
    if (index < 0 || index > this.rows.length) {
      index = this.rows.length;
    }
    Debug.println(index+","+value);
    if (this.options.selectMode == Traction.TableSelect.MODE_SINGLE) {
      this.clearSelection();
    }
    var row = Traction.TableSelectRow.create(text, value, this.options.dragDropMove);
    row.setSelected(true);
    if (index == this.rows.length) {
      this.TRparent.appendChild(row.TR);
    } else {
      this.TRparent.insertBefore(row.TR, this.rows[index].TR);
    }
    for (var i=this.rows.length; i > index; i--) {
      this.rows[i] = this.rows[i-1];
    }
    this.rows[index] = row;
    this.updateHidden();
  },
  
  remove: function(index) {
    if (index >= 0 && index < this.rows.length) {
      var removed = this.rows[index];
      for (var i=index; i<this.rows.length-1; i++) {
	this.rows[i] = this.row[i+1];
      }
      this.rows.length = this.rows.length-1;
      TRparent.removeNode(removed.TR);
    }
    this.updateHidden();
  },
  get: function(index) {
    if (index >= 0 && index < this.rows.length) {
      return this.rows[index];
    } else {
      return null;
    }
  },
   
  moveup: function() {
    var ret = false;
    var tmp;
    for (var i=1; i < this.rows.length; i++) {
      if (this.rows[i].isSelected() && !this.rows[i-1].isSelected()) {
	this.TRparent.removeChild(this.rows[i].TR);
	this.TRparent.insertBefore(this.rows[i].TR, this.rows[i-1].TR);
	tmp = this.rows[i];
	this.rows[i] = this.rows[i-1]
	this.rows[i-1] = tmp;
	ret = true;
      }
    }
    this.updateHidden();
    return ret;
  },
   
  movedown: function() {
    var ret = false;
    var tmp;
    for (var i=this.rows.length-2; i >= 0; i--) {
      if (this.rows[i].isSelected() && !this.rows[i+1].isSelected()) {
	this.TRparent.removeChild(this.rows[i+1].TR);
	this.TRparent.insertBefore(this.rows[i+1].TR, this.rows[i].TR);
	tmp = this.rows[i];
	this.rows[i] = this.rows[i+1];
	this.rows[i+1] = tmp;
	ret = true;
      }
    }
    this.updateHidden();
    return ret;
  },
  removeSelected: function(confirm) {
    var newrows = new Array();
    for (var i=0; i<this.rows.length; i++) {
      var row = this.rows[i];
      if (row.isSelected()) {
	this.TRparent.removeChild(row.TR);
      } else {
	newrows[newrows.length] = row;
      }
    }
    this.rows = newrows;
    this.updateHidden();
  },
  swap: function(row, dir) {
    var ret = null;
    var swapping = -1;
    for (var i=0; i<this.rows.length; i++) {
      if (row == this.rows[i]) {
	swapping = i;
	break;
      }
    }
    if (swapping != -1) {
      var swapwith = swapping + dir;
      Debug.println("Swap: "+this.rows[swapping].value+" with "+this.rows[swapwith].value);
      if (swapwith >= 0 && swapwith < this.rows.length) {
	var tmp;
	if (dir > 0) {
	  this.TRparent.removeChild(this.rows[swapwith].TR);
	  this.TRparent.insertBefore(this.rows[swapwith].TR, this.rows[swapping].TR);
	} else {
	  this.TRparent.removeChild(this.rows[swapping].TR);
	  this.TRparent.insertBefore(this.rows[swapping].TR, this.rows[swapwith].TR);	  
	}
	ret = this.rows[swapwith];
	tmp = this.rows[swapping];
	this.rows[swapping] = this.rows[swapwith];
	this.rows[swapwith] = tmp;
	this.updateHidden();
      }
    }
    return ret;
  },
  
  getSelected: function() {
    var ret = new Array();
    for (var i=0; i<this.rows.length; i++) {
      if (this.rows[i].isSelected()) {
	ret[ret.length] = this.rows[i];
      }
    }
    return ret;
  },
  
  selectionChanged: function(row, wasSelected, wasShiftKey) {
    Debug.println("row="+row.value+" wasSelected="+wasSelected+" wasShiftKey="+wasShiftKey);
    if (this.options.selectMode == Traction.TableSelect.MODE_SINGLE ||
	(this.options.selectMode == Traction.TableSelect.MODE_MULTIPLE_SHIFT && !wasShiftKey)) {
      this.clearSelection(row);
    }
    else if (this.options.selectMode == Traction.TableSelect.MODE_MULTIPLE_TOGGLE ||
	     (this.options.selectMode == Traction.TableSelect.MODE_MULTIPLE_SHIFT && wasShiftKey)) {
      row.setSelected( !wasSelected );
    }
    this.updateHidden();
  },
  toggleAll: function(checkbox) {
    if (checkbox.checked) {
      this.selectAll();
    } else {
      this.clearSelection();
    }
    this.updateHidden();
  },
  toggleRow: function(checkbox) {
    alert("not implemented");
  },
  selectAll: function(except) {
    for (var i=0; i<this.rows.length; i++) {
      if (this.rows[i] != except) {
	if (!this.rows[i].isSelected()) {
	  this.rows[i].setSelected(true);
	}
      }
    }
  },
  clearSelection: function(except) {
    for (var i=0; i<this.rows.length; i++) {
      if (this.rows[i] != except) {
	if (this.rows[i].isSelected()) {
	  this.rows[i].setSelected(false);
	}
      }
    }
  },
  
  updateHidden: function() {
    var value_selected = "";
    var value_all = "";
    for (var i=0; i<this.rows.length; i++) {
      if (this.rows[i].isSelected()) {
	if (value_selected != "") {
	  value_selected += ",";
	}
	value_selected += this.rows[i].value;
      }
      if (value_all != "") {
	value_all += ",";
      }
      value_all += this.rows[i].value;
    }
    if (this.hidden_all != null) {
      this.hidden_all.setAttribute("value", value_all);
    }
    if (this.hidden_selected != null) {
      this.hidden_selected.setAttribute("value", value_selected);
    }
    this.notify_onchange();
  },
  notify_onchange: function() {
    if (this.onchange != null) {
      this.onchange();
    }
  }
};

//------------------------------------------------------------------------
// traction.ui.Window


Traction.Window = Class.create();
Traction.Window.maxZorder = 1; // need this for bringToFront
Traction.Window.prototype = {
  initialize: function(options) {
    this.setOptions(options);
    this.build();
  },
  setOptions: function(options) {
    this.options = Object.extend({
      title: "untitled",
      showTitle: true,
      showClose: true,
      showResize: true,
      minWidth: 100,
      minHeight: 100,
      width: 0,
      height: 0,
      bodyPadding: "10px",
      buttons: [ new Traction.Button("OK", this, this.close) ]
	  }, options || {});
  },
  setButtons: function(buttons) {
    this.options.buttons = buttons;
    if (this.options.buttons.length == 0) {
	this.buttons.style.display = "none";
    } else {
	this.buttons.style.display = "";
	for (var i=0; i<this.options.buttons.length; i++) {
	  this.buttondiv.appendChild(this.options.buttons[i].getButton());
	}
    }
  },
  setTitle: function(title) {
    this.options.title = title;
  },
  
  showCentered: function() {
  },
  build: function() {
    if (this.div == null) {
      this.div = document.createElement("DIV");
      with (this.div) {
	if (this.options.width != 0) {
	  style.width = px(this.options.width);
	}
	if (this.options.height != 0) {
	  style.height = px(this.options.height);
	}
	style.position = "absolute";
	style.display = "none";
	className = "window";
      }
      this.table = document.createElement("TABLE");
      with (this.table) {
	cellSpacing = "0";
	cellPadding = "0";
	style.height = "100%";
	border = "0";
      }
      this.div.appendChild(this.table);
      this.title = document.createElement("TD");
      this.body = document.createElement("TD");
      this.buttons = document.createElement("TD");
      this.tbody = document.createElement("TBODY");
      this.table.appendChild(this.tbody);
      var titletr = document.createElement("TR");
      titletr.className = "title";
      titletr.style.verticalAlign = "top";
      titletr.appendChild(this.title);
      this.tbody.appendChild(titletr);
      this.title.style.cursor = "default";
      Events.attach(this.div, Events.MouseDown, this, this.bringToFront);
      Events.attach(this.title, Events.MouseDown, this, this.title_mousedown);
      Events.attach(document.body, Events.MouseMove, this, this.body_mousemove);
      Events.attach(document.body, Events.MouseUp,   this, this.body_mouseup);
      this.titlespan = document.createElement("SPAN");
      this.titlespan.style.cursor = "default";
      this.titlespan.innerHTML = this.options.title;
      this.title.appendChild(this.titlespan);
      Events.kill(this.titlespan, Events.MouseDown);
      Events.kill(this.titlespan, Events.SelectStart);
      if (this.options.showClose) {
	var img = document.createElement("IMG");
	with (img) {
	  src = "/images/modern/icons/close.gif";
	  border = "";
	  style["float"] = "right";
	  style.verticalAlign = "middle";
	  style.position = "absolute";
	  style.right = "1px";
	  style.padding = "1px 3px 0px 0px";
	  style.cursor = ua("css_cursor_property_pointer_value", "pointer");
	}    
	Events.attach(img, Events.Click, this, this.close);
	this.title.appendChild(img);
      }
      var bodytr = document.createElement("TR");
      bodytr.className = "body";
      bodytr.style.verticalAlign = "top";
      bodytr.appendChild(this.body);
      this.tbody.appendChild(bodytr);
      this.body.style.padding = this.options.bodyPadding;
      var buttonstr = document.createElement("TR");
      buttonstr.className = "buttons";
      buttonstr.style.verticalAlign = "top";
      buttonstr.appendChild(this.buttons);
      this.buttondiv = document.createElement("DIV");
      this.buttondiv.style.margin = "auto";
      this.buttons.appendChild(this.buttondiv);
      this.tbody.appendChild(buttonstr);
      if (this.options.buttons.length == 0) {
	this.buttons.style.display = "none";
      } else {
	for (var i=0; i<this.options.buttons.length; i++) {
	  this.buttondiv.appendChild(this.options.buttons[i].getButton());
	}
      }
      if (this.options.showResize) {
	this.resize = document.createElement("IMG");
	with (this.resize) {
	  src = "/images/resize.gif";
	  style.width = "16px";
	  style.height = "16px";
	  style.position = "absolute";
	  style.cursor = "se-resize";
	}
	Events.kill(this.resize, Events.DragStart); // for IE
	Events.kill(this.resize, Events.MouseDown); // for FF 1.5
	Events.attach(this.resize, Events.MouseDown, this, this.resize_mousedown);
	this.div.appendChild(this.resize);
      }
      document.body.appendChild(this.div);
    }
  },
  setBody: function(body) {
    this.body.innerHTML = body;
  },
  
  show: function(x, y) {
    this.setPosition({x:x,y:y});
    this.div.style.display = "";
    this.setBounds(this.getBounds());
    this.bringToFront();
  },
  bringToFront: function() {
    this.div.style.zIndex = ++Traction.Window.maxZorder;
    Debug.println("bringToFront = "+Traction.Window.maxZorder);
  },
  getBounds: function() {
    var tbody = Util.getElementBounds(this.tbody);
    var title = Util.getElementBounds(this.title);
    var rect = new Rect(tbody.x, tbody.y, title.w, tbody.h);
    return rect;
  },
  setBounds: function(rect) {
    this.setPosition({ x: rect.x, y: rect.y });
    this.setSize({ x: rect.w, y: rect.h });
  },
  getPosition: function() {
    return { x: nopx(this.div.style.left), y: nopx(this.div.style.top) };
  },
  setPosition: function(pt) {
    if (pt.y < 0) pt.y = 0;
    this.div.style.left = px(pt.x);
    this.div.style.top = px(pt.y);
  },
  getSize: function() {
    var rect = Util.getElementBounds(this.div);
    return { x: rect.w, y: rect.h };
  },
  setSize: function(size) {
    if (size.x < this.options.minWidth) size.x = this.options.minWidth;
    if (size.y < this.options.minHeight) size.y = this.options.minHeight;
    this.table.style.height = "100%";
    this.div.style.height = px(size.y);
    this.div.style.width = px(size.x);
    var rect = this.getBounds();
    if (rect.h > size.y) {
      var adjusted = size.y - (rect.h - size.y);
      if (adjusted <= 0) adjusted = 1;
      this.table.style.height = px(adjusted);
    }
    this.table.style.width = this.div.style.width;
    var cursize = this.getBounds();
    var divsize = Util.getElementBounds(this.div);
    if (divsize.w < cursize.w) {
      this.div.style.width = px(cursize.w);
      this.table.style.width = this.div.style.width;
    }
    if (divsize.h < cursize.h) {
      this.div.style.height = px(cursize.h);
    }
    var cursize = this.getBounds();
    if (this.resize != null) {
      this.resize.style.left = px(cursize.w - 16);
      this.resize.style.top = px(cursize.h - 16);
    }
  },
  hide: function() {
    this.div.style.display = "none";
  },
  close: function() {
    this.hide();
  },
  resize_mousedown: function(event) {
    Debug.println("resize_mousedown");
    this.isResize = true;
    this.startpt = Util.getMousePosition(event);
    this.start = this.getBounds();
    this.bringToFront();
    return false;
  },
  title_mousedown: function(event) {
    Debug.println("title_mousedown");
    this.isMove = true;
    this.startpt = Util.getMousePosition(event);
    this.start = this.getBounds();
    this.bringToFront();
    return false;
  },
  body_mousemove: function(event) {
    if (this.isMove) {
      var curpt = Util.getMousePosition(event);
      var movex = curpt.x - this.startpt.x;
      var movey = curpt.y - this.startpt.y;
      this.setPosition({ x: this.start.x + movex, y: this.start.y + movey });
    }
    else if (this.isResize) {
      var curpt = Util.getMousePosition(event);
      var movex = curpt.x - this.startpt.x;
      var movey = curpt.y - this.startpt.y;
      Debug.println("movex="+movex+", movey="+movey);
      this.setSize({ x: this.start.w + movex, y: this.start.h + movey });
    }
    return false;
  },
  body_mouseup: function(event) {
    Debug.println("body_mouseup");
    this.startpt = null;
    this.startposition = null;
    this.isResize = false;
    this.isMove = false;
    return false;
  }
};

//------------------------------------------------------------------------
// traction.ui.ZoomExpander

Traction.ZoomExpander = Class.create();
Traction.ZoomExpander.disabled = false;
Traction.ZoomExpander.prototype = {
  
  panel: null,
  
  pt: null,
  
  rect: null,
  initialize: function(element, options) {
    this.setOptions(options);
    this.element = element;
  },
  getpanel: function() {
    if (this.panel == null) {
      this.panel = document.createElement("DIV");
      if (this.options.className != null) {
	this.panel.className = this.options.className;
      }
      with (this.panel) {
	style.display = "none";
	style.position = "absolute";
	style.zIndex = "2000";
      }
      document.body.appendChild(this.panel);
    }
    return this.panel;
  },
  setOptions: function(options) {
    this.options = Object.extend({
      steps: 23,     // number of frames to show
      interval: 7, // time in ms
      border: null,
      panelcolor: "#666",  // color of the panel used to zoom.  it
      afterShow: null
	  }, options || {});
  },
  reset: function() {
    if (this.timer) clearTimeout(this.timer);
    this.step = 1;
  },
  show: function() {
    if (this.element != null) {
      this.element.style.display = "";
      this.element.style.visibility = "visible";
    }
    this.getpanel().style.display = "none";
    if (this.options.afterShow != null) {
      this.options.afterShow(this);
    }
  },
  hide: function() {
    this.getpanel().style.display = "none";
    if (this.element != null) {
      this.element.style.display = "none";
    }
  },
  zoomIn: function(pt1,pt2,rect) {
    if (this.element != null) {
      this.element.style.left = px(pt2.x);
      this.element.style.top = px(pt2.y);
    }
    this.pt = pt1;
    this.rect = rect;
    if (Traction.ZoomExpander.disabled) {
      this.show();
    }
    else {
      this.step = 1;
      this.timer = setTimeout(this.zoomingIn.bind(this), this.options.interval);
    }
  },
  zoomOut: function(pt) {
    if (Traction.ZoomExpander.disabled) {
      this.hide();
    }
    else if (this.pt == null || this.rect == null) {
      this.hide();
    }
    else {
      this.hide();
      this.step = this.options.steps;
      this.timer = setTimeout(this.zoomingOut.bind(this), this.options.interval);
    }
  },
  zoomingIn: function() {
    Debug.println("zoomingIn: "+this.step+"/"+this.options.steps);
    if (this.step >= this.options.steps) {
      clearTimeout(this.timer);
      this.show();
      return;
    }
    this.updatePanel();
    this.step++;
    this.timer = setTimeout(this.zoomingIn.bind(this), this.options.interval);
  },
  zoomingOut: function() {
    Debug.println("zoomingOut: "+this.step+"/"+this.options.steps);
    if (this.step <= 0) {
      clearTimeout(this.timer);
      this.hide();
      return;
    }
    this.updatePanel();
    this.step--;
    this.timer = setTimeout(this.zoomingOut.bind(this), this.options.interval);
  },
  updatePanel: function() {
    var newrect = new Rect(this.computeStep(this.pt.x, this.rect.x),
			   this.computeStep(this.pt.y, this.rect.y),
			   this.computeStep(0, this.rect.w),
			   this.computeStep(0, this.rect.h));
    this.movePanel(newrect);
  },
  movePanel: function(rect) {
    with (this.getpanel()) {
      style.left = px(rect.x);
      style.top = px(rect.y);
      style.width = px(rect.w);
      style.height = px(rect.h);
      style.display = "block";
      style.visibility = "visible";
    }
  },
  computeStep: function(a,b) {
    return Math.round(a + (((b-a) / this.options.steps) * this.step));
  }
};

//------------------------------------------------------------------------
// traction.view.commentvolume

var commentvolume_ = {
  formElm: null,
  cVol: null,
  contentRoot: null,
  contentRootID: null,
  lastVolume: null,
  init: function(formID, contentRootID, cVolSelectorID) {
    commentvolume_.formElm = document.getElementById(formID);
    commentvolume_.selector = new Traction.Select(cVolSelectorID,
						  { submitMode: Traction.Select.MODE_NORMAL });
    commentvolume_.contentRootID = contentRootID;
    Events.attach(window, Events.Load, commentvolume_, commentvolume_.saveContentElement);
  },
  changeCommentVolume: function() {
    if (commentvolume_.contentRoot != null) {
      var letter = null;
      switch(commentvolume_.selector.currentValue()) {
      case "expanded":
	letter = 'e';
	break;
      case "collapsed":
	letter = 'c';
	break;
      case "hidden":
	letter = 'h';
	break;
      default:
	alert("I don't know about comment volume '" + commentvolume_.selector.currentValue() + "'.");
      }
      commentvolume_.togglePageCommentState();
      if (letter != null) {
	commentvolume_.setCookie(letter, VOLUME_INDEX);
      }
    } else {
      commentvolume_.formElm.submit();
    }
  },
  DEFAULT_VALUE: "cecceccc",
  COOKIE_BLOCK_SEPARATOR: ".",
  setCookie: function(cVolLetter, volumeIndex) {
    var c = Util.Cookie.getCookie("TractionView", true);
    if (typeof(c.value) == "undefined" || c.value == null) {
      c.value = commentvolume_.COOKIE_BLOCK_SEPARATOR + commentvolume_.DEFAULT_VALUE;
    }
    if (c.value.indexOf("{64}") == 0) {
      if (commentvolume_.formElm) {
	commentvolume_.formElm.submit();
      }
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    var parts = c.value.split(commentvolume_.COOKIE_BLOCK_SEPARATOR);
    if (parts.length < 2) {
      c.value = c.value + commentvolume_.COOKIE_BLOCK_SEPARATOR + commentvolume_.DEFAULT_VALUE;
    } else {
      var newCommentVol = parts[1].substring(0, volumeIndex) + cVolLetter;
      if (volumeIndex + 1 != parts[1].length) {
	newCommentVol += parts[1].substring(volumeIndex + 1);
      }
      parts[1] = newCommentVol;
      c.value = parts[0];
      for (var i = 1; i < parts.length; i ++) {
	c.value += commentvolume_.COOKIE_BLOCK_SEPARATOR + parts[i];
      }
    }
    c.savePermanent();
  },
  saveContentElement: function() {
    if (commentvolume_.contentRootID != null) {
      commentvolume_.contentRoot = document.getElementById(commentvolume_.contentRootID);
      commentvolume_.lastVolume = commentvolume_.selector.currentValue();
    }
  },
  togglePageCommentState: function() {
    var newVolume = commentvolume_.selector.currentValue();
    var expand = (newVolume == "expanded");
    var hide   = (newVolume == "hidden");
    if (hide) {
      commentvolume_.toggleVisibility(false);
    }
    else {
      var isHidden   = (commentvolume_.lastVolume == "hidden");
      if (isHidden) {
	commentvolume_.toggleVisibility(true);
      }
      var isExpanded = (commentvolume_.lastVolume == "expanded" ||
			(newVolume == "collapsed" && isHidden));
      commentvolume_._toggleFromNode(isExpanded, commentvolume_.contentRoot);
    }
    commentvolume_.lastVolume = newVolume;
  },
  toggleVisibility: function(show) {
    var containers = getElementsByClassFromRootNode("commentcontainer", commentvolume_.contentRoot, "DIV");
    for (var i = 0; i < containers.length; i ++) {
      show_inline(show, containers[i]);
    }
  },
  toggleSubtree: function(toggleImg) {
    var container = toggleImg.parentNode.nextSibling;
    var isExpanded = (toggleImg.src.indexOf("ic_minus.gif") != -1);
    commentvolume_._toggleFromNode(isExpanded, container);
    toggleImg.src = "/images/modern/icons/ic_" + (isExpanded ? "plus" : "minus") + ".gif";
    toggleImg.title = (isExpanded ?
		       i18n("commentvolume_expand_comment_subtree_toggle_title",   "Expand Comments") :
		       i18n("commentvolume_collapse_comment_subtree_toggle_title", "Collapse Comments"));
  },
  toggleComment: function(toggleElm) {
    var toggleImg;
    var savedLink = null;
    if (toggleElm.nodeName.toLowerCase() == "img") {
      toggleImg = toggleElm;
    } else if (toggleElm.nodeName.toLowerCase() == "a") {
      savedLink = toggleElm.href;
      toggleElm.removeAttribute("href");
      toggleImg = toggleElm.parentNode;
      while (toggleImg.nodeName.toLowerCase() != "td") {
	toggleImg = toggleImg.parentNode;
      }
      toggleImg = toggleImg.firstChild;
    } else {
      alert("This comment toggle element is not set up correctly.");
      return;
    }
    var isExpanded = (toggleImg.src.indexOf("hide.gif") != -1);
    var expandedComment = toggleImg.nextSibling;
    show_block(!isExpanded, expandedComment);
    var collapsedComment = expandedComment.nextSibling;
    show_block(isExpanded, collapsedComment);
    var commentInner = expandedComment.parentNode;
    commentvolume_._toggleFromNode(isExpanded, commentInner);
    toggleImg.src = "/images/modern/icons/" + (isExpanded ? "show" : "hide") + ".gif";
    toggleImg.title = (isExpanded ?
		       i18n("commentvolume_expand_comment_toggle_title",   "Expand") :
		       i18n("commentvolume_collapse_comment_toggle_title", "Collapse"));
    var rowContainer = commentInner.parentNode.parentNode;
    var subtreeToggleImg = rowContainer.parentNode.parentNode.previousSibling.firstChild;
    if (subtreeToggleImg != null && subtreeToggleImg.nodeName.toLowerCase() == "img" &&
	commentvolume_.allCommentsInSameState(rowContainer)) {
      subtreeToggleImg.src = "/images/modern/icons/ic_" + (isExpanded ? "plus" : "minus") + ".gif";
      subtreeToggleImg.title = (isExpanded ?
				i18n("commentvolume_expand_comment_subtree_toggle_title",   "Expand Comments") :
				i18n("commentvolume_collapse_comment_subtree_toggle_title", "Collapse Comments"));
    }
    if (savedLink != null) {
      setTimeout(function() {
		   toggleElm.href = savedLink;
		 }, 10);
    }
  },
  allCommentsInSameState: function(rowContainer) {
    var commentRow = rowContainer.firstChild;
    var commentToggle = commentRow.firstChild.firstChild;
    var isExpanded = (commentToggle.src.indexOf("hide.gif") != -1);
    var sameState = true;
    commentRow = commentRow.nextSibling;
    while (sameState && commentRow != null) {
      commentToggle = commentRow.firstChild.firstChild;
      var nextIsExpanded = (commentToggle.src.indexOf("hide.gif") != -1);
      sameState = (nextIsExpanded == isExpanded);
      isExpanded = nextIsExpanded;
      commentRow = commentRow.nextSibling;
    }
    return sameState;
  },
  _toggleFromNode: function(isExpanded, fromNode) {
    var expandedComments  = getElementsByClassFromRootNode("commentexpanded",  fromNode, "DIV");
    var collapsedComments = getElementsByClassFromRootNode("commentcollapsed", fromNode, "DIV");
    var toggleSubtreeImages = getElementsByClassFromRootNode("subtreetoggle", fromNode, "IMG");
    var toggleCommentImages = getElementsByClassFromRootNode("commenttoggle", fromNode, "IMG");
    var show = (isExpanded ? collapsedComments : expandedComments);
    var hide = (isExpanded ? expandedComments : collapsedComments);
    for (var i = 0; i < hide.length; i ++) {
      show_block(false, hide[i]);
    }
    for (var i = 0; i < show.length; i ++) {
      show_block(true, show[i]);
    }
    var commentToggleImgSrc = "/images/modern/icons/" + (isExpanded ? "show" : "hide") + ".gif";
    var subtreeToggleImgSrc = "/images/modern/icons/ic_" + (isExpanded ? "plus" : "minus") + ".gif";
    var commentToggleImgTitle = (isExpanded ?
				 i18n("commentvolume_expand_comment_toggle_title",   "Expand") :
				 i18n("commentvolume_collapse_comment_toggle_title", "Collapse"));
    var subtreeToggleImgTitle = (isExpanded ?
				 i18n("commentvolume_expand_comment_subtree_toggle_title",   "Expand Comments") :
				 i18n("commentvolume_collapse_comment_subtree_toggle_title", "Collapse Comments"));
    for (var i = 0; i < toggleSubtreeImages.length; i ++) {
      toggleSubtreeImages[i].src   = subtreeToggleImgSrc;
      toggleSubtreeImages[i].title = subtreeToggleImgTitle;
    }
    for (var i = 0; i < toggleCommentImages.length; i ++) {
      toggleCommentImages[i].src   = commentToggleImgSrc;
      toggleCommentImages[i].title = commentToggleImgTitle;
    }
  }
};

//------------------------------------------------------------------------
// traction.view.history

var history_ = {};
history_.originalClassName = new Array();
history_.radio = function(elm) {
  history_.update();
};
history_.setup = function(A, B) {
  if (document.diff.A == null || document.diff.B == null) return;
  var foundA = false;
  var foundB = false;
  if (A && B) {
    for (var i=0; i<document.diff.A.length; i++) {
      if (document.diff.A[i].value == A) {
	document.diff.A[i].checked = true;
	foundA = true;
	break;
      }
    }
    for (var i=0; i<document.diff.B.length; i++) {
      if (document.diff.B[i].value == B) {
	document.diff.B[i].checked = true;
	foundB = true;
	break;
      }
    }
  }
  if (!foundA || !foundB) {
    document.diff.A[1].checked = true;
    document.diff.B[0].checked = true;
  }
  history_.update();
};
history_.update = function() {
  var safe = false;
  for (var i=0; i<document.diff.A.length; i++) {
    document.diff.A[i].style.visibility = safe ? "visible" : "hidden";
    if (!document.diff.A[i].checked) history_.setTR(i, "");
    if (document.diff.B[i].checked) {
      safe = true;
      history_.setTR(i, "history-B");
    }
  }
  safe = false;
  for (var i=document.diff.B.length-1; i>=0; i--) {
    document.diff.B[i].style.visibility = safe ? "visible" : "hidden";
    if (!document.diff.B[i].checked) history_.setTR(i, "");
    if (document.diff.A[i].checked) {
      safe = true;
      history_.setTR(i, "history-A");
    } 
  }
};
history_.setTR = function(i, className) {
  var TR = getParentByName(document.diff.B[i], "TR");
  if (TR) {
    if (!this.originalClassName[i]) {
      this.originalClassName[i] = TR.className;
    }
    className = (this.originalClassName[i] + " " + className).trim();
    TR.className = className;
  }
};

//------------------------------------------------------------------------
// traction.view.LogoManager

Traction.LogoManagerWindow = {
  logoImageDirectory: null,
  
  defaultLogoImage: null,
  
  hardCodedDefaultLogoImage: null,
  
  fieldNamespace: null,
  logos: new Array(),
  nextRowToExpand: 1,
  expandedUploadBlocks: 1,
  currentDefaultIndex: -2,
  onLoad: function() {
    if (this.fieldNamespace) {
      for (var i = -1; i < this.logos.length; i ++) {
	if (document.getElementById(this.fieldNamespace + "_default" + i).checked) {
	  this.currentDefaultIndex = i;
	  break;
	}
      }
    }
    this.updateOpener();
  },
  updateOpener: function() {
    Debug.println("Updating logo image selectors from parent window.");
    if (window.opener != null && !window.opener.closed && window.opener.Settings) {
      var updateThese = window.opener.Settings.PROJECT_LOGO_IMAGE_SELECTORS;
      for (var i = 0; i < updateThese.length; i ++) {
	Debug.println("Updating logo image selector: [ ", this.logoImageDirectory, " ] [ ", this.defaultLogoImage, " ] [ ", this.logos, " ]");
	updateThese[i].updateLogoListing(this.logoImageDirectory, this.defaultLogoImage, this.logos);
      }
    } else {
      Debug.println("Can't seem to find the parent window...");
    }
  },
  showMoreUploadRows: function() {
    if (!document.getElementById("uploadrow" + this.nextRowToExpand)) {
      return;
    }
    var i;
    for (i = this.nextRowToExpand; i < this.nextRowToExpand + this.expandedUploadBlocks + 1; i ++) {
      show_TR(true, document.getElementById("uploadrow" + i));
    }
    this.expandedUploadBlocks ++;
    this.nextRowToExpand = i; // i was incremented 1 past the last one that was expanded, so i is now the next to-be-expanded
    if (!document.getElementById("uploadrow" + (this.nextRowToExpand))) {
      show_TR(false, document.getElementById("showmorelink"));
    }
  },
  onChangeDefault: function(index) {
    if ((this.currentDefaultIndex >= 0) &&
	(document.getElementById(this.fieldNamespace + "_default" + this.currentDefaultIndex).value != this.hardCodedDefaultLogoImage)) {
      document.fm["remove" + this.currentDefaultIndex].disabled = false;
    }
    var removeCheckBox = document.fm["remove" + index];
    if (removeCheckBox) {
      removeCheckBox.disabled = true;
    }
    this.currentDefaultIndex = index;
    this.makeDirty();
  },
  onToggleRemoveStatus: function(checkBox, index) {
    if (this.fieldNamespace) {
      document.getElementById(this.fieldNamespace + "_default" + index).disabled = checkBox.checked;
    }
    this.makeDirty();
  },
  makeDirty: function() {
    if (typeof(makeDirty) == "function") {
      makeDirty();
    }
  },
  applyChanges: function() {
    dialog_apply();
  }
};

//------------------------------------------------------------------------
// traction.view.ProfilePictureManager

Traction.ProfilePictureManagerWindow = {
  
  fieldNamespace: null,
  nextRowToExpand: 1,
  expandedUploadBlocks: 1,
  userId: -1,
  
  currentProfilePicture: null,
  
  defaultProfilePicture: null,
  onLoad: function() {
    this.updateOpener();
  },
  updateOpener: function() {
    if (!isDirty) {
      Debug.println("Updating profile picture preview in parent window.");
      var updateWithData = Traction.ProfilePictureManagerWindow.currentProfilePicture["url"] ?
        Traction.ProfilePictureManagerWindow.currentProfilePicture : Traction.ProfilePictureManagerWindow.defaultProfilePicture;
      if (window.opener != null && !window.opener.closed) {
	window.opener.updateFromProfilePictureManager(updateWithData, this.userId);
      } else {
	Debug.println("Can't seem to find the parent window...");
      }
    }
  },
  showMoreUploadRows: function() {
    if (!document.getElementById("uploadrow" + this.nextRowToExpand)) {
      return;
    }
    var i;
    for (i = this.nextRowToExpand; i < this.nextRowToExpand + this.expandedUploadBlocks + 1; i ++) {
      show_TR(true, document.getElementById("uploadrow" + i));
    }
    this.expandedUploadBlocks ++;
    this.nextRowToExpand = i; // i was incremented 1 past the last one that was expanded, so i is now the next to-be-expanded
    if (!document.getElementById("uploadrow" + (this.nextRowToExpand))) {
      show_TR(false, document.getElementById("showmorelink"));
    }
  },
  onDeleteClick: function(pictureFilename, pictureKey) {
    if (this.warnDelete()) {
      var settingInput = document.getElementById("setting-value");
      if (settingInput.value == pictureKey) {
	document.getElementById("setting-value").value = "def-val-" + this.defaultProfilePicture["key"];
      }
      document.fm.fb_submit.value = "Apply";
      document.fm.remove0.value = pictureFilename;
      triggerCustomEvent("apply");
      document.fm.submit();
    }
  },
  warnDelete: function() {
    return (confirm(i18n("profilepictures_delete_confirmation_message", "Are you sure you want to delete this picture?")));
  },
  onPictureChooseClick: function(pictureKey) {
    document.fm.fb_submit.value = "Apply";
    document.getElementById("setting-value").value = pictureKey;
    this.makeDirty();
    triggerCustomEvent("apply");
    document.fm.submit();
  },
  makeDirty: function() {
    if (typeof(makeDirty) == "function") {
      makeDirty();
    }
  },
  makeDirtyForUpload: function() {
    document.fm.upload.disabled = false;
  },
  onUploadClick: function() {
    triggerCustomEvent("apply");
    document.fm.fb_submit.value="Upload";
    submitForm();
  }
};

