From 720080bce5651f6a76399ae0ee5ee30dbde44733 Mon Sep 17 00:00:00 2001 From: jonobr1 Date: Fri, 28 Jan 2011 17:04:12 -0700 Subject: [PATCH 01/23] updated README to reflect our demo and reference the documentation --- README | 59 ++++++++++++++++++++++++++++++++---------------------- index.html | 20 +++++++++--------- 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/README b/README index 426e5b2..1f8431a 100644 --- a/README +++ b/README @@ -1,31 +1,42 @@ - var controllableObject = - { - numberProperty: 20, - anotherNumberProperty: 0, - textProperty: "a string", - booleanProperty: false, - functionProperty: function() { - alert("I am a function!"); - } - }; +# gui-dat +**gui-dat** is a lightweight controller library for JavaScript. It allows you to easily manipulate variables and fire functions on the fly. +## Basic Usage + + ++ ui-dat will infer the type of the property you're trying to add (based on its initial value) and create the corresponding control. ++ The properties must be public, i.e. defined by `**this**.prop = value`. +## Monitor variable changes outside of the GUI +Let's say you have a variable that changes by itself from time to time. If you'd like the GUI to reflect those changes, use the listen() method + GUI.add(obj, "propName").listen(); +## Fire a function when someone uses a control + GUI.add(obj, "propName").onChange(function(n) { + alert("You changed me to " + n); + }); +Initiated by [George Michael Brower]:http://georgemichaelbrower.com/ and [Jono Brandel]:http://jonobr1.com/ of the Data Arts Team, Google Creative Lab. diff --git a/index.html b/index.html index dffb602..0c4a9cf 100644 --- a/index.html +++ b/index.html @@ -97,17 +97,17 @@ window.onload = function() {
  • gui-dat will infer the type of the property you're trying to add
    (based on its initial value) and create the corresponding control.
  • The properties must be public, i.e. defined by this.prop = value.
  • - -

    Fire a function when someone uses a control

    -
    GUI.add(obj, "propName").onChange(function(n) {
    -	alert("You changed me to " + n);
    -});
    ---> From 760f5101e42a242566d7fd2f19fd2fdb38ae4dc7 Mon Sep 17 00:00:00 2001 From: jonobr1 Date: Fri, 28 Jan 2011 17:15:50 -0700 Subject: [PATCH 02/23] added suffix to README --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..2952c1a --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# gui-dat +**gui-dat** is a lightweight controller library for JavaScript. It allows you to easily manipulate variables and fire functions on the fly. +## Basic Usage + + ++ ui-dat will infer the type of the property you're trying to add (based on its initial value) and create the corresponding control. ++ The properties must be public, i.e. defined by `**this**.prop = value`. +## Monitor variable changes outside of the GUI +Let's say you have a variable that changes by itself from time to time. If you'd like the GUI to reflect those changes, use the listen() method + GUI.add(obj, "propName").listen(); +## Fire a function when someone uses a control + GUI.add(obj, "propName").onChange(function(n) { + alert("You changed me to " + n); + }); +Initiated by [George Michael Brower]:http://georgemichaelbrower.com/ and [Jono Brandel]:http://jonobr1.com/ of the Data Arts Team, Google Creative Lab. From d0ae26b62fe50486442988cefed92b564c1ad10f Mon Sep 17 00:00:00 2001 From: jonobr1 Date: Fri, 28 Jan 2011 17:18:34 -0700 Subject: [PATCH 03/23] changing README styles --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2952c1a..1ce5d31 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ + ui-dat will infer the type of the property you're trying to add (based on its initial value) and create the corresponding control. + The properties must be public, i.e. defined by `**this**.prop = value`. + ## Monitor variable changes outside of the GUI Let's say you have a variable that changes by itself from time to time. If you'd like the GUI to reflect those changes, use the listen() method GUI.add(obj, "propName").listen(); From 8d3f8c0dd90bed6c51b73b1ffc1c7c8e554161bc Mon Sep 17 00:00:00 2001 From: jonobr1 Date: Fri, 28 Jan 2011 17:20:51 -0700 Subject: [PATCH 04/23] changing README styles --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1ce5d31..f2e812d 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,9 @@ + ui-dat will infer the type of the property you're trying to add (based on its initial value) and create the corresponding control. -+ The properties must be public, i.e. defined by `**this**.prop = value`. ++ The properties must be public, i.e. defined by `this.prop = value`. -## Monitor variable changes outside of the GUI +\#\# Monitor variable changes outside of the GUI Let's say you have a variable that changes by itself from time to time. If you'd like the GUI to reflect those changes, use the listen() method GUI.add(obj, "propName").listen(); ## Fire a function when someone uses a control From 5e066f4477abca8b90f7ad36f5c11409f277323f Mon Sep 17 00:00:00 2001 From: jonobr1 Date: Fri, 28 Jan 2011 17:21:55 -0700 Subject: [PATCH 05/23] changing README styles --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f2e812d..1ae0348 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,8 @@ + ui-dat will infer the type of the property you're trying to add (based on its initial value) and create the corresponding control. + The properties must be public, i.e. defined by `this.prop = value`. -\#\# Monitor variable changes outside of the GUI +Monitor variable changes outside of the GUI +-------------------------------------------------- Let's say you have a variable that changes by itself from time to time. If you'd like the GUI to reflect those changes, use the listen() method GUI.add(obj, "propName").listen(); ## Fire a function when someone uses a control From 3f9103b432ae5ac0e7ff58c2c5a1fbd42530386c Mon Sep 17 00:00:00 2001 From: jonobr1 Date: Fri, 28 Jan 2011 17:22:27 -0700 Subject: [PATCH 06/23] changing README styles --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 1ae0348..912f4fd 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,6 @@ + ui-dat will infer the type of the property you're trying to add (based on its initial value) and create the corresponding control. + The properties must be public, i.e. defined by `this.prop = value`. - Monitor variable changes outside of the GUI -------------------------------------------------- Let's say you have a variable that changes by itself from time to time. If you'd like the GUI to reflect those changes, use the listen() method From d078d4acea3ccea710d48c3d225adabef395a71a Mon Sep 17 00:00:00 2001 From: jonobr1 Date: Fri, 28 Jan 2011 17:23:05 -0700 Subject: [PATCH 07/23] changing README styles --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 912f4fd..1ae0348 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ + ui-dat will infer the type of the property you're trying to add (based on its initial value) and create the corresponding control. + The properties must be public, i.e. defined by `this.prop = value`. + Monitor variable changes outside of the GUI -------------------------------------------------- Let's say you have a variable that changes by itself from time to time. If you'd like the GUI to reflect those changes, use the listen() method From 3785bcd7c64bc988cbc0a5f7e0b0650ef102502d Mon Sep 17 00:00:00 2001 From: jonobr1 Date: Fri, 28 Jan 2011 17:23:59 -0700 Subject: [PATCH 08/23] changing README styles --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1ae0348..415cd25 100644 --- a/README.md +++ b/README.md @@ -41,4 +41,4 @@ Let's say you have a variable that changes by itself from time to time. If you'd GUI.add(obj, "propName").onChange(function(n) { alert("You changed me to " + n); }); -Initiated by [George Michael Brower]:http://georgemichaelbrower.com/ and [Jono Brandel]:http://jonobr1.com/ of the Data Arts Team, Google Creative Lab. +Initiated by [George Michael Brower](http://georgemichaelbrower.com/) and [Jono Brandel](http://jonobr1.com/) of the Data Arts Team, Google Creative Lab. From 3b4f6e3944c382a80c1b13723c1e5e7d08ad3ecc Mon Sep 17 00:00:00 2001 From: "Mr.doob" Date: Sat, 29 Jan 2011 00:38:58 +0000 Subject: [PATCH 09/23] Changed GUI to be instanceable (rather than just one GUI per project). GUI.start() is now var gui = new GUI(); Included prettify.js in the repo so people checking the demo localy doesn't need the internets. Changed way of hidding the panel (controllerContainer.style.height rather than this.domElement.stylemarginTop). --- controllers/controller.js | 76 +- controllers/controller.number.js | 4 +- demo/prettify.js | 1538 ++++++++++++++++++++++++++++++ gui.js | 197 ++-- index.html | 105 +- 5 files changed, 1716 insertions(+), 204 deletions(-) create mode 100644 demo/prettify.js diff --git a/controllers/controller.js b/controllers/controller.js index 467ea51..ad90e73 100644 --- a/controllers/controller.js +++ b/controllers/controller.js @@ -2,53 +2,55 @@ var Controller = function() { var onChange = null; - this.setName = function(n) { - this.propertyNameElement.innerHTML = n; - return this; - } - - this.setValue = function(n) { - this.object[this.propertyName] = n; - if (onChange != null) { - onChange.call(this, n); - } - return this; - } - - this.getValue = function() { - return this.object[this.propertyName]; - } - - this.onChange = function(fnc) { - onChange = fnc; - return this; - } - + this.parent = null; + + this.setName = function(n) { + this.propertyNameElement.innerHTML = n; + return this; + } + + this.setValue = function(n) { + this.object[this.propertyName] = n; + if (onChange != null) { + onChange.call(this, n); + } + return this; + } + + this.getValue = function() { + return this.object[this.propertyName]; + } + + this.onChange = function(fnc) { + onChange = fnc; + return this; + } + this.makeUnselectable = function(elem) { elem.onselectstart = function() { return false; }; elem.style.MozUserSelect = "none"; elem.style.KhtmlUserSelect = "none"; elem.unselectable = "on"; } - + this.makeSelectable = function(elem) { elem.onselectstart = function() { }; elem.style.MozUserSelect = "auto"; elem.style.KhtmlUserSelect = "auto"; elem.unselectable = "off"; } - - this.domElement = document.createElement('div'); - this.domElement.setAttribute('class', 'guidat-controller ' + this.type); - this.object = arguments[0]; - this.propertyName = arguments[1]; - - this.propertyNameElement = document.createElement('span'); - this.propertyNameElement.setAttribute('class', 'guidat-propertyname'); - this.setName(this.propertyName); - this.domElement.appendChild(this.propertyNameElement); - - this.makeUnselectable(this.domElement); - -}; \ No newline at end of file + this.domElement = document.createElement('div'); + this.domElement.setAttribute('class', 'guidat-controller ' + this.type); + + this.object = arguments[0]; + this.propertyName = arguments[1]; + + this.propertyNameElement = document.createElement('span'); + this.propertyNameElement.setAttribute('class', 'guidat-propertyname'); + this.setName(this.propertyName); + this.domElement.appendChild(this.propertyNameElement); + + this.makeUnselectable(this.domElement); + +}; diff --git a/controllers/controller.number.js b/controllers/controller.number.js index b6cef94..80d299b 100644 --- a/controllers/controller.number.js +++ b/controllers/controller.number.js @@ -70,7 +70,7 @@ var NumberController = function() { document.addEventListener('mouseup', function(e) { document.removeEventListener('mousemove', dragNumberField, false); - _this.makeSelectable(GUI.domElement); + _this.makeSelectable(_this.parent.domElement); _this.makeSelectable(numberField); if (clickedNumberField && !draggedNumberField) { numberField.focus(); @@ -129,4 +129,4 @@ var NumberController = function() { }; NumberController.prototype = new Controller(); -NumberController.prototype.constructor = NumberController; \ No newline at end of file +NumberController.prototype.constructor = NumberController; diff --git a/demo/prettify.js b/demo/prettify.js new file mode 100644 index 0000000..bb76f07 --- /dev/null +++ b/demo/prettify.js @@ -0,0 +1,1538 @@ +// Copyright (C) 2006 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/** + * @fileoverview + * some functions for browser-side pretty printing of code contained in html. + *

    + * + * For a fairly comprehensive set of languages see the + * README + * file that came with this source. At a minimum, the lexer should work on a + * number of languages including C and friends, Java, Python, Bash, SQL, HTML, + * XML, CSS, Javascript, and Makefiles. It works passably on Ruby, PHP and Awk + * and a subset of Perl, but, because of commenting conventions, doesn't work on + * Smalltalk, Lisp-like, or CAML-like languages without an explicit lang class. + *

    + * Usage:

      + *
    1. include this source file in an html page via + * {@code } + *
    2. define style rules. See the example page for examples. + *
    3. mark the {@code
      } and {@code } tags in your source with
      + *    {@code class=prettyprint.}
      + *    You can also use the (html deprecated) {@code } tag, but the pretty
      + *    printer needs to do more substantial DOM manipulations to support that, so
      + *    some css styles may not be preserved.
      + * </ol>
      + * That's it.  I wanted to keep the API as simple as possible, so there's no
      + * need to specify which language the code is in, but if you wish, you can add
      + * another class to the {@code <pre>} or {@code <code>} element to specify the
      + * language, as in {@code <pre class="prettyprint lang-java">}.  Any class that
      + * starts with "lang-" followed by a file extension, specifies the file type.
      + * See the "lang-*.js" files in this directory for code that implements
      + * per-language file handlers.
      + * <p>
      + * Change log:<br>
      + * cbeust, 2006/08/22
      + * <blockquote>
      + *   Java annotations (start with "@") are now captured as literals ("lit")
      + * </blockquote>
      + * @requires console
      + */
      +
      +// JSLint declarations
      +/*global console, document, navigator, setTimeout, window */
      +
      +/**
      + * Split {@code prettyPrint} into multiple timeouts so as not to interfere with
      + * UI events.
      + * If set to {@code false}, {@code prettyPrint()} is synchronous.
      + */
      +window['PR_SHOULD_USE_CONTINUATION'] = true;
      +
      +/** the number of characters between tab columns */
      +window['PR_TAB_WIDTH'] = 8;
      +
      +/** Walks the DOM returning a properly escaped version of innerHTML.
      +  * @param {Node} node
      +  * @param {Array.<string>} out output buffer that receives chunks of HTML.
      +  */
      +window['PR_normalizedHtml']
      +
      +/** Contains functions for creating and registering new language handlers.
      +  * @type {Object}
      +  */
      +  = window['PR']
      +
      +/** Pretty print a chunk of code.
      +  *
      +  * @param {string} sourceCodeHtml code as html
      +  * @return {string} code as html, but prettier
      +  */
      +  = window['prettyPrintOne']
      +/** Find all the {@code <pre>} and {@code <code>} tags in the DOM with
      +  * {@code class=prettyprint} and prettify them.
      +  * @param {Function?} opt_whenDone if specified, called when the last entry
      +  *     has been finished.
      +  */
      +  = window['prettyPrint'] = void 0;
      +
      +/** browser detection. @extern @returns false if not IE, otherwise the major version. */
      +window['_pr_isIE6'] = function () {
      +  var ieVersion = navigator && navigator.userAgent &&
      +      navigator.userAgent.match(/\bMSIE ([678])\./);
      +  ieVersion = ieVersion ? +ieVersion[1] : false;
      +  window['_pr_isIE6'] = function () { return ieVersion; };
      +  return ieVersion;
      +};
      +
      +
      +(function () {
      +  // Keyword lists for various languages.
      +  var FLOW_CONTROL_KEYWORDS =
      +      "break continue do else for if return while ";
      +  var C_KEYWORDS = FLOW_CONTROL_KEYWORDS + "auto case char const default " +
      +      "double enum extern float goto int long register short signed sizeof " +
      +      "static struct switch typedef union unsigned void volatile ";
      +  var COMMON_KEYWORDS = C_KEYWORDS + "catch class delete false import " +
      +      "new operator private protected public this throw true try typeof ";
      +  var CPP_KEYWORDS = COMMON_KEYWORDS + "alignof align_union asm axiom bool " +
      +      "concept concept_map const_cast constexpr decltype " +
      +      "dynamic_cast explicit export friend inline late_check " +
      +      "mutable namespace nullptr reinterpret_cast static_assert static_cast " +
      +      "template typeid typename using virtual wchar_t where ";
      +  var JAVA_KEYWORDS = COMMON_KEYWORDS +
      +      "abstract boolean byte extends final finally implements import " +
      +      "instanceof null native package strictfp super synchronized throws " +
      +      "transient ";
      +  var CSHARP_KEYWORDS = JAVA_KEYWORDS +
      +      "as base by checked decimal delegate descending dynamic event " +
      +      "fixed foreach from group implicit in interface internal into is lock " +
      +      "object out override orderby params partial readonly ref sbyte sealed " +
      +      "stackalloc string select uint ulong unchecked unsafe ushort var ";
      +  var COFFEE_KEYWORDS = "all and by catch class else extends false finally " +
      +      "for if in is isnt loop new no not null of off on or return super then " +
      +      "true try unless until when while yes ";
      +  var JSCRIPT_KEYWORDS = COMMON_KEYWORDS +
      +      "debugger eval export function get null set undefined var with " +
      +      "Infinity NaN ";
      +  var PERL_KEYWORDS = "caller delete die do dump elsif eval exit foreach for " +
      +      "goto if import last local my next no our print package redo require " +
      +      "sub undef unless until use wantarray while BEGIN END ";
      +  var PYTHON_KEYWORDS = FLOW_CONTROL_KEYWORDS + "and as assert class def del " +
      +      "elif except exec finally from global import in is lambda " +
      +      "nonlocal not or pass print raise try with yield " +
      +      "False True None ";
      +  var RUBY_KEYWORDS = FLOW_CONTROL_KEYWORDS + "alias and begin case class def" +
      +      " defined elsif end ensure false in module next nil not or redo rescue " +
      +      "retry self super then true undef unless until when yield BEGIN END ";
      +  var SH_KEYWORDS = FLOW_CONTROL_KEYWORDS + "case done elif esac eval fi " +
      +      "function in local set then until ";
      +  var ALL_KEYWORDS = (
      +      CPP_KEYWORDS + CSHARP_KEYWORDS + JSCRIPT_KEYWORDS + PERL_KEYWORDS +
      +      PYTHON_KEYWORDS + RUBY_KEYWORDS + SH_KEYWORDS);
      +
      +  // token style names.  correspond to css classes
      +  /** token style for a string literal */
      +  var PR_STRING = 'str';
      +  /** token style for a keyword */
      +  var PR_KEYWORD = 'kwd';
      +  /** token style for a comment */
      +  var PR_COMMENT = 'com';
      +  /** token style for a type */
      +  var PR_TYPE = 'typ';
      +  /** token style for a literal value.  e.g. 1, null, true. */
      +  var PR_LITERAL = 'lit';
      +  /** token style for a punctuation string. */
      +  var PR_PUNCTUATION = 'pun';
      +  /** token style for a punctuation string. */
      +  var PR_PLAIN = 'pln';
      +
      +  /** token style for an sgml tag. */
      +  var PR_TAG = 'tag';
      +  /** token style for a markup declaration such as a DOCTYPE. */
      +  var PR_DECLARATION = 'dec';
      +  /** token style for embedded source. */
      +  var PR_SOURCE = 'src';
      +  /** token style for an sgml attribute name. */
      +  var PR_ATTRIB_NAME = 'atn';
      +  /** token style for an sgml attribute value. */
      +  var PR_ATTRIB_VALUE = 'atv';
      +
      +  /**
      +   * A class that indicates a section of markup that is not code, e.g. to allow
      +   * embedding of line numbers within code listings.
      +   */
      +  var PR_NOCODE = 'nocode';
      +
      +  /** A set of tokens that can precede a regular expression literal in
      +    * javascript.
      +    * http://www.mozilla.org/js/language/js20/rationale/syntax.html has the full
      +    * list, but I've removed ones that might be problematic when seen in
      +    * languages that don't support regular expression literals.
      +    *
      +    * <p>Specifically, I've removed any keywords that can't precede a regexp
      +    * literal in a syntactically legal javascript program, and I've removed the
      +    * "in" keyword since it's not a keyword in many languages, and might be used
      +    * as a count of inches.
      +    *
      +    * <p>The link a above does not accurately describe EcmaScript rules since
      +    * it fails to distinguish between (a=++/b/i) and (a++/b/i) but it works
      +    * very well in practice.
      +    *
      +    * @private
      +    */
      +  var REGEXP_PRECEDER_PATTERN = function () {
      +      var preceders = [
      +          "!", "!=", "!==", "#", "%", "%=", "&", "&&", "&&=",
      +          "&=", "(", "*", "*=", /* "+", */ "+=", ",", /* "-", */ "-=",
      +          "->", /*".", "..", "...", handled below */ "/", "/=", ":", "::", ";",
      +          "<", "<<", "<<=", "<=", "=", "==", "===", ">",
      +          ">=", ">>", ">>=", ">>>", ">>>=", "?", "@", "[",
      +          "^", "^=", "^^", "^^=", "{", "|", "|=", "||",
      +          "||=", "~" /* handles =~ and !~ */,
      +          "break", "case", "continue", "delete",
      +          "do", "else", "finally", "instanceof",
      +          "return", "throw", "try", "typeof"
      +          ];
      +      var pattern = '(?:^^|[+-]';
      +      for (var i = 0; i < preceders.length; ++i) {
      +        pattern += '|' + preceders[i].replace(/([^=<>:&a-z])/g, '\\$1');
      +      }
      +      pattern += ')\\s*';  // matches at end, and matches empty string
      +      return pattern;
      +      // CAVEAT: this does not properly handle the case where a regular
      +      // expression immediately follows another since a regular expression may
      +      // have flags for case-sensitivity and the like.  Having regexp tokens
      +      // adjacent is not valid in any language I'm aware of, so I'm punting.
      +      // TODO: maybe style special characters inside a regexp as punctuation.
      +    }();
      +
      +  // Define regexps here so that the interpreter doesn't have to create an
      +  // object each time the function containing them is called.
      +  // The language spec requires a new object created even if you don't access
      +  // the $1 members.
      +  var pr_amp = /&/g;
      +  var pr_lt = /</g;
      +  var pr_gt = />/g;
      +  var pr_quot = /\"/g;
      +  /** like textToHtml but escapes double quotes to be attribute safe. */
      +  function attribToHtml(str) {
      +    return str.replace(pr_amp, '&amp;')
      +        .replace(pr_lt, '&lt;')
      +        .replace(pr_gt, '&gt;')
      +        .replace(pr_quot, '&quot;');
      +  }
      +
      +  /** escapest html special characters to html. */
      +  function textToHtml(str) {
      +    return str.replace(pr_amp, '&amp;')
      +        .replace(pr_lt, '&lt;')
      +        .replace(pr_gt, '&gt;');
      +  }
      +
      +
      +  var pr_ltEnt = /&lt;/g;
      +  var pr_gtEnt = /&gt;/g;
      +  var pr_aposEnt = /&apos;/g;
      +  var pr_quotEnt = /&quot;/g;
      +  var pr_ampEnt = /&amp;/g;
      +  var pr_nbspEnt = /&nbsp;/g;
      +  /** unescapes html to plain text. */
      +  function htmlToText(html) {
      +    var pos = html.indexOf('&');
      +    if (pos < 0) { return html; }
      +    // Handle numeric entities specially.  We can't use functional substitution
      +    // since that doesn't work in older versions of Safari.
      +    // These should be rare since most browsers convert them to normal chars.
      +    for (--pos; (pos = html.indexOf('&#', pos + 1)) >= 0;) {
      +      var end = html.indexOf(';', pos);
      +      if (end >= 0) {
      +        var num = html.substring(pos + 3, end);
      +        var radix = 10;
      +        if (num && num.charAt(0) === 'x') {
      +          num = num.substring(1);
      +          radix = 16;
      +        }
      +        var codePoint = parseInt(num, radix);
      +        if (!isNaN(codePoint)) {
      +          html = (html.substring(0, pos) + String.fromCharCode(codePoint) +
      +                  html.substring(end + 1));
      +        }
      +      }
      +    }
      +
      +    return html.replace(pr_ltEnt, '<')
      +        .replace(pr_gtEnt, '>')
      +        .replace(pr_aposEnt, "'")
      +        .replace(pr_quotEnt, '"')
      +        .replace(pr_nbspEnt, ' ')
      +        .replace(pr_ampEnt, '&');
      +  }
      +
      +  /** is the given node's innerHTML normally unescaped? */
      +  function isRawContent(node) {
      +    return 'XMP' === node.tagName;
      +  }
      +
      +  var newlineRe = /[\r\n]/g;
      +  /**
      +   * Are newlines and adjacent spaces significant in the given node's innerHTML?
      +   */
      +  function isPreformatted(node, content) {
      +    // PRE means preformatted, and is a very common case, so don't create
      +    // unnecessary computed style objects.
      +    if ('PRE' === node.tagName) { return true; }
      +    if (!newlineRe.test(content)) { return true; }  // Don't care
      +    var whitespace = '';
      +    // For disconnected nodes, IE has no currentStyle.
      +    if (node.currentStyle) {
      +      whitespace = node.currentStyle.whiteSpace;
      +    } else if (window.getComputedStyle) {
      +      // Firefox makes a best guess if node is disconnected whereas Safari
      +      // returns the empty string.
      +      whitespace = window.getComputedStyle(node, null).whiteSpace;
      +    }
      +    return !whitespace || whitespace === 'pre';
      +  }
      +
      +  function normalizedHtml(node, out, opt_sortAttrs) {
      +    switch (node.nodeType) {
      +      case 1:  // an element
      +        var name = node.tagName.toLowerCase();
      +
      +        out.push('<', name);
      +        var attrs = node.attributes;
      +        var n = attrs.length;
      +        if (n) {
      +          if (opt_sortAttrs) {
      +            var sortedAttrs = [];
      +            for (var i = n; --i >= 0;) { sortedAttrs[i] = attrs[i]; }
      +            sortedAttrs.sort(function (a, b) {
      +                return (a.name < b.name) ? -1 : a.name === b.name ? 0 : 1;
      +              });
      +            attrs = sortedAttrs;
      +          }
      +          for (var i = 0; i < n; ++i) {
      +            var attr = attrs[i];
      +            if (!attr.specified) { continue; }
      +            out.push(' ', attr.name.toLowerCase(),
      +                     '="', attribToHtml(attr.value), '"');
      +          }
      +        }
      +        out.push('>');
      +        for (var child = node.firstChild; child; child = child.nextSibling) {
      +          normalizedHtml(child, out, opt_sortAttrs);
      +        }
      +        if (node.firstChild || !/^(?:br|link|img)$/.test(name)) {
      +          out.push('<\/', name, '>');
      +        }
      +        break;
      +      case 3: case 4: // text
      +        out.push(textToHtml(node.nodeValue));
      +        break;
      +    }
      +  }
      +
      +  /**
      +   * Given a group of {@link RegExp}s, returns a {@code RegExp} that globally
      +   * matches the union o the sets o strings matched d by the input RegExp.
      +   * Since it matches globally, if the input strings have a start-of-input
      +   * anchor (/^.../), it is ignored for the purposes of unioning.
      +   * @param {Array.<RegExp>} regexs non multiline, non-global regexs.
      +   * @return {RegExp} a global regex.
      +   */
      +  function combinePrefixPatterns(regexs) {
      +    var capturedGroupIndex = 0;
      +
      +    var needToFoldCase = false;
      +    var ignoreCase = false;
      +    for (var i = 0, n = regexs.length; i < n; ++i) {
      +      var regex = regexs[i];
      +      if (regex.ignoreCase) {
      +        ignoreCase = true;
      +      } else if (/[a-z]/i.test(regex.source.replace(
      +                     /\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi, ''))) {
      +        needToFoldCase = true;
      +        ignoreCase = false;
      +        break;
      +      }
      +    }
      +
      +    function decodeEscape(charsetPart) {
      +      if (charsetPart.charAt(0) !== '\\') { return charsetPart.charCodeAt(0); }
      +      switch (charsetPart.charAt(1)) {
      +        case 'b': return 8;
      +        case 't': return 9;
      +        case 'n': return 0xa;
      +        case 'v': return 0xb;
      +        case 'f': return 0xc;
      +        case 'r': return 0xd;
      +        case 'u': case 'x':
      +          return parseInt(charsetPart.substring(2), 16)
      +              || charsetPart.charCodeAt(1);
      +        case '0': case '1': case '2': case '3': case '4':
      +        case '5': case '6': case '7':
      +          return parseInt(charsetPart.substring(1), 8);
      +        default: return charsetPart.charCodeAt(1);
      +      }
      +    }
      +
      +    function encodeEscape(charCode) {
      +      if (charCode < 0x20) {
      +        return (charCode < 0x10 ? '\\x0' : '\\x') + charCode.toString(16);
      +      }
      +      var ch = String.fromCharCode(charCode);
      +      if (ch === '\\' || ch === '-' || ch === '[' || ch === ']') {
      +        ch = '\\' + ch;
      +      }
      +      return ch;
      +    }
      +
      +    function caseFoldCharset(charSet) {
      +      var charsetParts = charSet.substring(1, charSet.length - 1).match(
      +          new RegExp(
      +              '\\\\u[0-9A-Fa-f]{4}'
      +              + '|\\\\x[0-9A-Fa-f]{2}'
      +              + '|\\\\[0-3][0-7]{0,2}'
      +              + '|\\\\[0-7]{1,2}'
      +              + '|\\\\[\\s\\S]'
      +              + '|-'
      +              + '|[^-\\\\]',
      +              'g'));
      +      var groups = [];
      +      var ranges = [];
      +      var inverse = charsetParts[0] === '^';
      +      for (var i = inverse ? 1 : 0, n = charsetParts.length; i < n; ++i) {
      +        var p = charsetParts[i];
      +        switch (p) {
      +          case '\\B': case '\\b':
      +          case '\\D': case '\\d':
      +          case '\\S': case '\\s':
      +          case '\\W': case '\\w':
      +            groups.push(p);
      +            continue;
      +        }
      +        var start = decodeEscape(p);
      +        var end;
      +        if (i + 2 < n && '-' === charsetParts[i + 1]) {
      +          end = decodeEscape(charsetParts[i + 2]);
      +          i += 2;
      +        } else {
      +          end = start;
      +        }
      +        ranges.push([start, end]);
      +        // If the range might intersect letters, then expand it.
      +        if (!(end < 65 || start > 122)) {
      +          if (!(end < 65 || start > 90)) {
      +            ranges.push([Math.max(65, start) | 32, Math.min(end, 90) | 32]);
      +          }
      +          if (!(end < 97 || start > 122)) {
      +            ranges.push([Math.max(97, start) & ~32, Math.min(end, 122) & ~32]);
      +          }
      +        }
      +      }
      +
      +      // [[1, 10], [3, 4], [8, 12], [14, 14], [16, 16], [17, 17]]
      +      // -> [[1, 12], [14, 14], [16, 17]]
      +      ranges.sort(function (a, b) { return (a[0] - b[0]) || (b[1]  - a[1]); });
      +      var consolidatedRanges = [];
      +      var lastRange = [NaN, NaN];
      +      for (var i = 0; i < ranges.length; ++i) {
      +        var range = ranges[i];
      +        if (range[0] <= lastRange[1] + 1) {
      +          lastRange[1] = Math.max(lastRange[1], range[1]);
      +        } else {
      +          consolidatedRanges.push(lastRange = range);
      +        }
      +      }
      +
      +      var out = ['['];
      +      if (inverse) { out.push('^'); }
      +      out.push.apply(out, groups);
      +      for (var i = 0; i < consolidatedRanges.length; ++i) {
      +        var range = consolidatedRanges[i];
      +        out.push(encodeEscape(range[0]));
      +        if (range[1] > range[0]) {
      +          if (range[1] + 1 > range[0]) { out.push('-'); }
      +          out.push(encodeEscape(range[1]));
      +        }
      +      }
      +      out.push(']');
      +      return out.join('');
      +    }
      +
      +    function allowAnywhereFoldCaseAndRenumberGroups(regex) {
      +      // Split into character sets, escape sequences, punctuation strings
      +      // like ('(', '(?:', ')', '^'), and runs of characters that do not
      +      // include any of the above.
      +      var parts = regex.source.match(
      +          new RegExp(
      +              '(?:'
      +              + '\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]'  // a character set
      +              + '|\\\\u[A-Fa-f0-9]{4}'  // a unicode escape
      +              + '|\\\\x[A-Fa-f0-9]{2}'  // a hex escape
      +              + '|\\\\[0-9]+'  // a back-reference or octal escape
      +              + '|\\\\[^ux0-9]'  // other escape sequence
      +              + '|\\(\\?[:!=]'  // start of a non-capturing group
      +              + '|[\\(\\)\\^]'  // start/emd of a group, or line start
      +              + '|[^\\x5B\\x5C\\(\\)\\^]+'  // run of other characters
      +              + ')',
      +              'g'));
      +      var n = parts.length;
      +
      +      // Maps captured group numbers to the number they will occupy in
      +      // the output or to -1 if that has not been determined, or to
      +      // undefined if they need not be capturing in the output.
      +      var capturedGroups = [];
      +
      +      // Walk over and identify back references to build the capturedGroups
      +      // mapping.
      +      for (var i = 0, groupIndex = 0; i < n; ++i) {
      +        var p = parts[i];
      +        if (p === '(') {
      +          // groups are 1-indexed, so max group index is count of '('
      +          ++groupIndex;
      +        } else if ('\\' === p.charAt(0)) {
      +          var decimalValue = +p.substring(1);
      +          if (decimalValue && decimalValue <= groupIndex) {
      +            capturedGroups[decimalValue] = -1;
      +          }
      +        }
      +      }
      +
      +      // Renumber groups and reduce capturing groups to non-capturing groups
      +      // where possible.
      +      for (var i = 1; i < capturedGroups.length; ++i) {
      +        if (-1 === capturedGroups[i]) {
      +          capturedGroups[i] = ++capturedGroupIndex;
      +        }
      +      }
      +      for (var i = 0, groupIndex = 0; i < n; ++i) {
      +        var p = parts[i];
      +        if (p === '(') {
      +          ++groupIndex;
      +          if (capturedGroups[groupIndex] === undefined) {
      +            parts[i] = '(?:';
      +          }
      +        } else if ('\\' === p.charAt(0)) {
      +          var decimalValue = +p.substring(1);
      +          if (decimalValue && decimalValue <= groupIndex) {
      +            parts[i] = '\\' + capturedGroups[groupIndex];
      +          }
      +        }
      +      }
      +
      +      // Remove any prefix anchors so that the output will match anywhere.
      +      // ^^ really does mean an anchored match though.
      +      for (var i = 0, groupIndex = 0; i < n; ++i) {
      +        if ('^' === parts[i] && '^' !== parts[i + 1]) { parts[i] = ''; }
      +      }
      +
      +      // Expand letters to groupts to handle mixing of case-sensitive and
      +      // case-insensitive patterns if necessary.
      +      if (regex.ignoreCase && needToFoldCase) {
      +        for (var i = 0; i < n; ++i) {
      +          var p = parts[i];
      +          var ch0 = p.charAt(0);
      +          if (p.length >= 2 && ch0 === '[') {
      +            parts[i] = caseFoldCharset(p);
      +          } else if (ch0 !== '\\') {
      +            // TODO: handle letters in numeric escapes.
      +            parts[i] = p.replace(
      +                /[a-zA-Z]/g,
      +                function (ch) {
      +                  var cc = ch.charCodeAt(0);
      +                  return '[' + String.fromCharCode(cc & ~32, cc | 32) + ']';
      +                });
      +          }
      +        }
      +      }
      +
      +      return parts.join('');
      +    }
      +
      +    var rewritten = [];
      +    for (var i = 0, n = regexs.length; i < n; ++i) {
      +      var regex = regexs[i];
      +      if (regex.global || regex.multiline) { throw new Error('' + regex); }
      +      rewritten.push(
      +          '(?:' + allowAnywhereFoldCaseAndRenumberGroups(regex) + ')');
      +    }
      +
      +    return new RegExp(rewritten.join('|'), ignoreCase ? 'gi' : 'g');
      +  }
      +
      +  var PR_innerHtmlWorks = null;
      +  function getInnerHtml(node) {
      +    // inner html is hopelessly broken in Safari 2.0.4 when the content is
      +    // an html description of well formed XML and the containing tag is a PRE
      +    // tag, so we detect that case and emulate innerHTML.
      +    if (null === PR_innerHtmlWorks) {
      +      var testNode = document.createElement('PRE');
      +      testNode.appendChild(
      +          document.createTextNode('<!DOCTYPE foo PUBLIC "foo bar">\n<foo />'));
      +      PR_innerHtmlWorks = !/</.test(testNode.innerHTML);
      +    }
      +
      +    if (PR_innerHtmlWorks) {
      +      var content = node.innerHTML;
      +      // XMP tags contain unescaped entities so require special handling.
      +      if (isRawContent(node)) {
      +        content = textToHtml(content);
      +      } else if (!isPreformatted(node, content)) {
      +        content = content.replace(/(<br\s*\/?>)[\r\n]+/g, '$1')
      +            .replace(/(?:[\r\n]+[ \t]*)+/g, ' ');
      +      }
      +      return content;
      +    }
      +
      +    var out = [];
      +    for (var child = node.firstChild; child; child = child.nextSibling) {
      +      normalizedHtml(child, out);
      +    }
      +    return out.join('');
      +  }
      +
      +  /** returns a function that expand tabs to spaces.  This function can be fed
      +    * successive chunks of text, and will maintain its own internal state to
      +    * keep track of how tabs are expanded.
      +    * @return {function (string) : string} a function that takes
      +    *   plain text and return the text with tabs expanded.
      +    * @private
      +    */
      +  function makeTabExpander(tabWidth) {
      +    var SPACES = '                ';
      +    var charInLine = 0;
      +
      +    return function (plainText) {
      +      // walk over each character looking for tabs and newlines.
      +      // On tabs, expand them.  On newlines, reset charInLine.
      +      // Otherwise increment charInLine
      +      var out = null;
      +      var pos = 0;
      +      for (var i = 0, n = plainText.length; i < n; ++i) {
      +        var ch = plainText.charAt(i);
      +
      +        switch (ch) {
      +          case '\t':
      +            if (!out) { out = []; }
      +            out.push(plainText.substring(pos, i));
      +            // calculate how much space we need in front of this part
      +            // nSpaces is the amount of padding -- the number of spaces needed
      +            // to move us to the next column, where columns occur at factors of
      +            // tabWidth.
      +            var nSpaces = tabWidth - (charInLine % tabWidth);
      +            charInLine += nSpaces;
      +            for (; nSpaces >= 0; nSpaces -= SPACES.length) {
      +              out.push(SPACES.substring(0, nSpaces));
      +            }
      +            pos = i + 1;
      +            break;
      +          case '\n':
      +            charInLine = 0;
      +            break;
      +          default:
      +            ++charInLine;
      +        }
      +      }
      +      if (!out) { return plainText; }
      +      out.push(plainText.substring(pos));
      +      return out.join('');
      +    };
      +  }
      +
      +  var pr_chunkPattern = new RegExp(
      +      '[^<]+'  // A run of characters other than '<'
      +      + '|<\!--[\\s\\S]*?--\>'  // an HTML comment
      +      + '|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>'  // a CDATA section
      +      // a probable tag that should not be highlighted
      +      + '|<\/?[a-zA-Z](?:[^>\"\']|\'[^\']*\'|\"[^\"]*\")*>'
      +      + '|<',  // A '<' that does not begin a larger chunk
      +      'g');
      +  var pr_commentPrefix = /^<\!--/;
      +  var pr_cdataPrefix = /^<!\[CDATA\[/;
      +  var pr_brPrefix = /^<br\b/i;
      +  var pr_tagNameRe = /^<(\/?)([a-zA-Z][a-zA-Z0-9]*)/;
      +
      +  /** split markup into chunks of html tags (style null) and
      +    * plain text (style {@link #PR_PLAIN}), converting tags which are
      +    * significant for tokenization (<br>) into their textual equivalent.
      +    *
      +    * @param {string} s html where whitespace is considered significant.
      +    * @return {Object} source code and extracted tags.
      +    * @private
      +    */
      +  function extractTags(s) {
      +    // since the pattern has the 'g' modifier and defines no capturing groups,
      +    // this will return a list of all chunks which we then classify and wrap as
      +    // PR_Tokens
      +    var matches = s.match(pr_chunkPattern);
      +    var sourceBuf = [];
      +    var sourceBufLen = 0;
      +    var extractedTags = [];
      +    if (matches) {
      +      for (var i = 0, n = matches.length; i < n; ++i) {
      +        var match = matches[i];
      +        if (match.length > 1 && match.charAt(0) === '<') {
      +          if (pr_commentPrefix.test(match)) { continue; }
      +          if (pr_cdataPrefix.test(match)) {
      +            // strip CDATA prefix and suffix.  Don't unescape since it's CDATA
      +            sourceBuf.push(match.substring(9, match.length - 3));
      +            sourceBufLen += match.length - 12;
      +          } else if (pr_brPrefix.test(match)) {
      +            // <br> tags are lexically significant so convert them to text.
      +            // This is undone later.
      +            sourceBuf.push('\n');
      +            ++sourceBufLen;
      +          } else {
      +            if (match.indexOf(PR_NOCODE) >= 0 && isNoCodeTag(match)) {
      +              // A <span class="nocode"> will start a section that should be
      +              // ignored.  Continue walking the list until we see a matching end
      +              // tag.
      +              var name = match.match(pr_tagNameRe)[2];
      +              var depth = 1;
      +              var j;
      +              end_tag_loop:
      +              for (j = i + 1; j < n; ++j) {
      +                var name2 = matches[j].match(pr_tagNameRe);
      +                if (name2 && name2[2] === name) {
      +                  if (name2[1] === '/') {
      +                    if (--depth === 0) { break end_tag_loop; }
      +                  } else {
      +                    ++depth;
      +                  }
      +                }
      +              }
      +              if (j < n) {
      +                extractedTags.push(
      +                    sourceBufLen, matches.slice(i, j + 1).join(''));
      +                i = j;
      +              } else {  // Ignore unclosed sections.
      +                extractedTags.push(sourceBufLen, match);
      +              }
      +            } else {
      +              extractedTags.push(sourceBufLen, match);
      +            }
      +          }
      +        } else {
      +          var literalText = htmlToText(match);
      +          sourceBuf.push(literalText);
      +          sourceBufLen += literalText.length;
      +        }
      +      }
      +    }
      +    return { source: sourceBuf.join(''), tags: extractedTags };
      +  }
      +
      +  /** True if the given tag contains a class attribute with the nocode class. */
      +  function isNoCodeTag(tag) {
      +    return !!tag
      +        // First canonicalize the representation of attributes
      +        .replace(/\s(\w+)\s*=\s*(?:\"([^\"]*)\"|'([^\']*)'|(\S+))/g,
      +                 ' $1="$2$3$4"')
      +        // Then look for the attribute we want.
      +        .match(/[cC][lL][aA][sS][sS]=\"[^\"]*\bnocode\b/);
      +  }
      +
      +  /**
      +   * Apply the given language handler to sourceCode and add the resulting
      +   * decorations to out.
      +   * @param {number} basePos the index of sourceCode within the chunk of source
      +   *    whose decorations are already present on out.
      +   */
      +  function appendDecorations(basePos, sourceCode, langHandler, out) {
      +    if (!sourceCode) { return; }
      +    var job = {
      +      source: sourceCode,
      +      basePos: basePos
      +    };
      +    langHandler(job);
      +    out.push.apply(out, job.decorations);
      +  }
      +
      +  /** Given triples of [style, pattern, context] returns a lexing function,
      +    * The lexing function interprets the patterns to find token boundaries and
      +    * returns a decoration list of the form
      +    * [index_0, style_0, index_1, style_1, ..., index_n, style_n]
      +    * where index_n is an index into the sourceCode, and style_n is a style
      +    * constant like PR_PLAIN.  index_n-1 <= index_n, and style_n-1 applies to
      +    * all characters in sourceCode[index_n-1:index_n].
      +    *
      +    * The stylePatterns is a list whose elements have the form
      +    * [style : string, pattern : RegExp, DEPRECATED, shortcut : string].
      +    *
      +    * Style is a style constant like PR_PLAIN, or can be a string of the
      +    * form 'lang-FOO', where FOO is a language extension describing the
      +    * language of the portion of the token in $1 after pattern executes.
      +    * E.g., if style is 'lang-lisp', and group 1 contains the text
      +    * '(hello (world))', then that portion of the token will be passed to the
      +    * registered lisp handler for formatting.
      +    * The text before and after group 1 will be restyled using this decorator
      +    * so decorators should take care that this doesn't result in infinite
      +    * recursion.  For example, the HTML lexer rule for SCRIPT elements looks
      +    * something like ['lang-js', /<[s]cript>(.+?)<\/script>/].  This may match
      +    * '<script>foo()<\/script>', which would cause the current decorator to
      +    * be called with '<script>' which would not match the same rule since
      +    * group 1 must not be empty, so it would be instead styled as PR_TAG by
      +    * the generic tag rule.  The handler registered for the 'js' extension would
      +    * then be called with 'foo()', and finally, the current decorator would
      +    * be called with '<\/script>' which would not match the original rule and
      +    * so the generic tag rule would identify it as a tag.
      +    *
      +    * Pattern must only match prefixes, and if it matches a prefix, then that
      +    * match is considered a token with the same style.
      +    *
      +    * Context is applied to the last non-whitespace, non-comment token
      +    * recognized.
      +    *
      +    * Shortcut is an optional string of characters, any of which, if the first
      +    * character, gurantee that this pattern and only this pattern matches.
      +    *
      +    * @param {Array} shortcutStylePatterns patterns that always start with
      +    *   a known character.  Must have a shortcut string.
      +    * @param {Array} fallthroughStylePatterns patterns that will be tried in
      +    *   order if the shortcut ones fail.  May have shortcuts.
      +    *
      +    * @return {function (Object)} a
      +    *   function that takes source code and returns a list of decorations.
      +    */
      +  function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) {
      +    var shortcuts = {};
      +    var tokenizer;
      +    (function () {
      +      var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns);
      +      var allRegexs = [];
      +      var regexKeys = {};
      +      for (var i = 0, n = allPatterns.length; i < n; ++i) {
      +        var patternParts = allPatterns[i];
      +        var shortcutChars = patternParts[3];
      +        if (shortcutChars) {
      +          for (var c = shortcutChars.length; --c >= 0;) {
      +            shortcuts[shortcutChars.charAt(c)] = patternParts;
      +          }
      +        }
      +        var regex = patternParts[1];
      +        var k = '' + regex;
      +        if (!regexKeys.hasOwnProperty(k)) {
      +          allRegexs.push(regex);
      +          regexKeys[k] = null;
      +        }
      +      }
      +      allRegexs.push(/[\0-\uffff]/);
      +      tokenizer = combinePrefixPatterns(allRegexs);
      +    })();
      +
      +    var nPatterns = fallthroughStylePatterns.length;
      +    var notWs = /\S/;
      +
      +    /**
      +     * Lexes job.source and produces an output array job.decorations of style
      +     * classes preceded by the position at which they start in job.source in
      +     * order.
      +     *
      +     * @param {Object} job an object like {@code
      +     *    source: {string} sourceText plain text,
      +     *    basePos: {int} position of job.source in the larger chunk of
      +     *        sourceCode.
      +     * }
      +     */
      +    var decorate = function (job) {
      +      var sourceCode = job.source, basePos = job.basePos;
      +      /** Even entries are positions in source in ascending order.  Odd enties
      +        * are style markers (e.g., PR_COMMENT) that run from that position until
      +        * the end.
      +        * @type {Array.<number|string>}
      +        */
      +      var decorations = [basePos, PR_PLAIN];
      +      var pos = 0;  // index into sourceCode
      +      var tokens = sourceCode.match(tokenizer) || [];
      +      var styleCache = {};
      +
      +      for (var ti = 0, nTokens = tokens.length; ti < nTokens; ++ti) {
      +        var token = tokens[ti];
      +        var style = styleCache[token];
      +        var match = void 0;
      +
      +        var isEmbedded;
      +        if (typeof style === 'string') {
      +          isEmbedded = false;
      +        } else {
      +          var patternParts = shortcuts[token.charAt(0)];
      +          if (patternParts) {
      +            match = token.match(patternParts[1]);
      +            style = patternParts[0];
      +          } else {
      +            for (var i = 0; i < nPatterns; ++i) {
      +              patternParts = fallthroughStylePatterns[i];
      +              match = token.match(patternParts[1]);
      +              if (match) {
      +                style = patternParts[0];
      +                break;
      +              }
      +            }
      +
      +            if (!match) {  // make sure that we make progress
      +              style = PR_PLAIN;
      +            }
      +          }
      +
      +          isEmbedded = style.length >= 5 && 'lang-' === style.substring(0, 5);
      +          if (isEmbedded && !(match && typeof match[1] === 'string')) {
      +            isEmbedded = false;
      +            style = PR_SOURCE;
      +          }
      +
      +          if (!isEmbedded) { styleCache[token] = style; }
      +        }
      +
      +        var tokenStart = pos;
      +        pos += token.length;
      +
      +        if (!isEmbedded) {
      +          decorations.push(basePos + tokenStart, style);
      +        } else {  // Treat group 1 as an embedded block of source code.
      +          var embeddedSource = match[1];
      +          var embeddedSourceStart = token.indexOf(embeddedSource);
      +          var embeddedSourceEnd = embeddedSourceStart + embeddedSource.length;
      +          if (match[2]) {
      +            // If embeddedSource can be blank, then it would match at the
      +            // beginning which would cause us to infinitely recurse on the
      +            // entire token, so we catch the right context in match[2].
      +            embeddedSourceEnd = token.length - match[2].length;
      +            embeddedSourceStart = embeddedSourceEnd - embeddedSource.length;
      +          }
      +          var lang = style.substring(5);
      +          // Decorate the left of the embedded source
      +          appendDecorations(
      +              basePos + tokenStart,
      +              token.substring(0, embeddedSourceStart),
      +              decorate, decorations);
      +          // Decorate the embedded source
      +          appendDecorations(
      +              basePos + tokenStart + embeddedSourceStart,
      +              embeddedSource,
      +              langHandlerForExtension(lang, embeddedSource),
      +              decorations);
      +          // Decorate the right of the embedded section
      +          appendDecorations(
      +              basePos + tokenStart + embeddedSourceEnd,
      +              token.substring(embeddedSourceEnd),
      +              decorate, decorations);
      +        }
      +      }
      +      job.decorations = decorations;
      +    };
      +    return decorate;
      +  }
      +
      +  /** returns a function that produces a list of decorations from source text.
      +    *
      +    * This code treats ", ', and ` as string delimiters, and \ as a string
      +    * escape.  It does not recognize perl's qq() style strings.
      +    * It has no special handling for double delimiter escapes as in basic, or
      +    * the tripled delimiters used in python, but should work on those regardless
      +    * although in those cases a single string literal may be broken up into
      +    * multiple adjacent string literals.
      +    *
      +    * It recognizes C, C++, and shell style comments.
      +    *
      +    * @param {Object} options a set of optional parameters.
      +    * @return {function (Object)} a function that examines the source code
      +    *     in the input job and builds the decoration list.
      +    */
      +  function sourceDecorator(options) {
      +    var shortcutStylePatterns = [], fallthroughStylePatterns = [];
      +    if (options['tripleQuotedStrings']) {
      +      // '''multi-line-string''', 'single-line-string', and double-quoted
      +      shortcutStylePatterns.push(
      +          [PR_STRING,  /^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,
      +           null, '\'"']);
      +    } else if (options['multiLineStrings']) {
      +      // 'multi-line-string', "multi-line-string"
      +      shortcutStylePatterns.push(
      +          [PR_STRING,  /^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,
      +           null, '\'"`']);
      +    } else {
      +      // 'single-line-string', "single-line-string"
      +      shortcutStylePatterns.push(
      +          [PR_STRING,
      +           /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,
      +           null, '"\'']);
      +    }
      +    if (options['verbatimStrings']) {
      +      // verbatim-string-literal production from the C# grammar.  See issue 93.
      +      fallthroughStylePatterns.push(
      +          [PR_STRING, /^@\"(?:[^\"]|\"\")*(?:\"|$)/, null]);
      +    }
      +    var hc = options['hashComments'];
      +    if (hc) {
      +      if (options['cStyleComments']) {
      +        if (hc > 1) {  // multiline hash comments
      +          shortcutStylePatterns.push(
      +              [PR_COMMENT, /^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/, null, '#']);
      +        } else {
      +          // Stop C preprocessor declarations at an unclosed open comment
      +          shortcutStylePatterns.push(
      +              [PR_COMMENT, /^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,
      +               null, '#']);
      +        }
      +        fallthroughStylePatterns.push(
      +            [PR_STRING,
      +             /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,
      +             null]);
      +      } else {
      +        shortcutStylePatterns.push([PR_COMMENT, /^#[^\r\n]*/, null, '#']);
      +      }
      +    }
      +    if (options['cStyleComments']) {
      +      fallthroughStylePatterns.push([PR_COMMENT, /^\/\/[^\r\n]*/, null]);
      +      fallthroughStylePatterns.push(
      +          [PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]);
      +    }
      +    if (options['regexLiterals']) {
      +      var REGEX_LITERAL = (
      +          // A regular expression literal starts with a slash that is
      +          // not followed by * or / so that it is not confused with
      +          // comments.
      +          '/(?=[^/*])'
      +          // and then contains any number of raw characters,
      +          + '(?:[^/\\x5B\\x5C]'
      +          // escape sequences (\x5C),
      +          +    '|\\x5C[\\s\\S]'
      +          // or non-nesting character sets (\x5B\x5D);
      +          +    '|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+'
      +          // finally closed by a /.
      +          + '/');
      +      fallthroughStylePatterns.push(
      +          ['lang-regex',
      +           new RegExp('^' + REGEXP_PRECEDER_PATTERN + '(' + REGEX_LITERAL + ')')
      +           ]);
      +    }
      +
      +    var keywords = options['keywords'].replace(/^\s+|\s+$/g, '');
      +    if (keywords.length) {
      +      fallthroughStylePatterns.push(
      +          [PR_KEYWORD,
      +           new RegExp('^(?:' + keywords.replace(/\s+/g, '|') + ')\\b'), null]);
      +    }
      +
      +    shortcutStylePatterns.push([PR_PLAIN,       /^\s+/, null, ' \r\n\t\xA0']);
      +    fallthroughStylePatterns.push(
      +        // TODO(mikesamuel): recognize non-latin letters and numerals in idents
      +        [PR_LITERAL,     /^@[a-z_$][a-z_$@0-9]*/i, null],
      +        [PR_TYPE,        /^@?[A-Z]+[a-z][A-Za-z_$@0-9]*/, null],
      +        [PR_PLAIN,       /^[a-z_$][a-z_$@0-9]*/i, null],
      +        [PR_LITERAL,
      +         new RegExp(
      +             '^(?:'
      +             // A hex number
      +             + '0x[a-f0-9]+'
      +             // or an octal or decimal number,
      +             + '|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)'
      +             // possibly in scientific notation
      +             + '(?:e[+\\-]?\\d+)?'
      +             + ')'
      +             // with an optional modifier like UL for unsigned long
      +             + '[a-z]*', 'i'),
      +         null, '0123456789'],
      +        [PR_PUNCTUATION, /^.[^\s\w\.$@\'\"\`\/\#]*/, null]);
      +
      +    return createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns);
      +  }
      +
      +  var decorateSource = sourceDecorator({
      +        'keywords': ALL_KEYWORDS,
      +        'hashComments': true,
      +        'cStyleComments': true,
      +        'multiLineStrings': true,
      +        'regexLiterals': true
      +      });
      +
      +  /** Breaks {@code job.source} around style boundaries in
      +    * {@code job.decorations} while re-interleaving {@code job.extractedTags},
      +    * and leaves the result in {@code job.prettyPrintedHtml}.
      +    * @param {Object} job like {
      +    *    source: {string} source as plain text,
      +    *    extractedTags: {Array.<number|string>} extractedTags chunks of raw
      +    *                   html preceded by their position in {@code job.source}
      +    *                   in order
      +    *    decorations: {Array.<number|string} an array of style classes preceded
      +    *                 by the position at which they start in job.source in order
      +    * }
      +    * @private
      +    */
      +  function recombineTagsAndDecorations(job) {
      +    var sourceText = job.source;
      +    var extractedTags = job.extractedTags;
      +    var decorations = job.decorations;
      +    var numberLines = job.numberLines;
      +    var sourceNode = job.sourceNode;
      +
      +    var html = [];
      +    // index past the last char in sourceText written to html
      +    var outputIdx = 0;
      +
      +    var openDecoration = null;
      +    var currentDecoration = null;
      +    var tagPos = 0;  // index into extractedTags
      +    var decPos = 0;  // index into decorations
      +    var tabExpander = makeTabExpander(window['PR_TAB_WIDTH']);
      +
      +    var adjacentSpaceRe = /([\r\n ]) /g;
      +    var startOrSpaceRe = /(^| ) /gm;
      +    var newlineRe = /\r\n?|\n/g;
      +    var trailingSpaceRe = /[ \r\n]$/;
      +    var lastWasSpace = true;  // the last text chunk emitted ended with a space.
      +
      +    // See bug 71 and http://stackoverflow.com/questions/136443/why-doesnt-ie7-
      +    var isIE678 = window['_pr_isIE6']();
      +    var lineBreakHtml = (
      +        isIE678
      +        ? (sourceNode && sourceNode.tagName === 'PRE'
      +           // Use line feeds instead of <br>s so that copying and pasting works
      +           // on IE.
      +           // See Issue 104 for the derivation of this mess.
      +           ? (isIE678 === 6 ? '&#160;\r\n' :
      +              isIE678 === 7 ? '&#160;<br />\r' :
      +              isIE678 === 8 ? '&#160;<br />' : '&#160;\r')
      +           // IE collapses multiple adjacent <br>s into 1 line break.
      +           // Prefix every newline with '&#160;' to prevent such behavior.
      +           // &nbsp; is the same as &#160; but works in XML as well as HTML.
      +           : '&#160;<br />')
      +        : '<br />');
      +
      +    var lineBreaker;
      +    if (numberLines) {
      +      var lineBreaks = [];
      +      for (var i = 0; i < 10; ++i) {
      +        lineBreaks[i] = lineBreakHtml + '</li><li class="L' + i + '">';
      +      }
      +      var lineNum = typeof numberLines === 'number'
      +          ? numberLines - 1 /* number lines are 1 indexed */ : 0;
      +      html.push('<ol class="linenums"><li class="L', (lineNum) % 10, '"');
      +      if (lineNum) {
      +        html.push(' value="', lineNum + 1, '"');
      +      }
      +      html.push('>');
      +      lineBreaker = function () {
      +        var lb = lineBreaks[++lineNum % 10];
      +        // If a decoration is open, we need to close it before closing a list-item
      +        // and reopen it on the other side of the list item.
      +        return openDecoration
      +            ? ('</span>' + lb + '<span class="' + openDecoration + '">') : lb;
      +      };
      +    } else {
      +      lineBreaker = lineBreakHtml;
      +    }
      +
      +    // A helper function that is responsible for opening sections of decoration
      +    // and outputing properly escaped chunks of source
      +    function emitTextUpTo(sourceIdx) {
      +      if (sourceIdx > outputIdx) {
      +        if (openDecoration && openDecoration !== currentDecoration) {
      +          // Close the current decoration
      +          html.push('</span>');
      +          openDecoration = null;
      +        }
      +        if (!openDecoration && currentDecoration) {
      +          openDecoration = currentDecoration;
      +          html.push('<span class="', openDecoration, '">');
      +        }
      +        // This interacts badly with some wikis which introduces paragraph tags
      +        // into pre blocks for some strange reason.
      +        // It's necessary for IE though which seems to lose the preformattedness
      +        // of <pre> tags when their innerHTML is assigned.
      +        // http://stud3.tuwien.ac.at/~e0226430/innerHtmlQuirk.html
      +        // and it serves to undo the conversion of <br>s to newlines done in
      +        // chunkify.
      +        var htmlChunk = textToHtml(
      +            tabExpander(sourceText.substring(outputIdx, sourceIdx)))
      +            .replace(lastWasSpace
      +                     ? startOrSpaceRe
      +                     : adjacentSpaceRe, '$1&#160;');
      +        // Keep track of whether we need to escape space at the beginning of the
      +        // next chunk.
      +        lastWasSpace = trailingSpaceRe.test(htmlChunk);
      +        html.push(htmlChunk.replace(newlineRe, lineBreaker));
      +        outputIdx = sourceIdx;
      +      }
      +    }
      +
      +    while (true) {
      +      // Determine if we're going to consume a tag this time around.  Otherwise
      +      // we consume a decoration or exit.
      +      var outputTag;
      +      if (tagPos < extractedTags.length) {
      +        if (decPos < decorations.length) {
      +          // Pick one giving preference to extractedTags since we shouldn't open
      +          // a new style that we're going to have to immediately close in order
      +          // to output a tag.
      +          outputTag = extractedTags[tagPos] <= decorations[decPos];
      +        } else {
      +          outputTag = true;
      +        }
      +      } else {
      +        outputTag = false;
      +      }
      +      // Consume either a decoration or a tag or exit.
      +      if (outputTag) {
      +        emitTextUpTo(extractedTags[tagPos]);
      +        if (openDecoration) {
      +          // Close the current decoration
      +          html.push('</span>');
      +          openDecoration = null;
      +        }
      +        html.push(extractedTags[tagPos + 1]);
      +        tagPos += 2;
      +      } else if (decPos < decorations.length) {
      +        emitTextUpTo(decorations[decPos]);
      +        currentDecoration = decorations[decPos + 1];
      +        decPos += 2;
      +      } else {
      +        break;
      +      }
      +    }
      +    emitTextUpTo(sourceText.length);
      +    if (openDecoration) {
      +      html.push('</span>');
      +    }
      +    if (numberLines) { html.push('</li></ol>'); }
      +    job.prettyPrintedHtml = html.join('');
      +  }
      +
      +  /** Maps language-specific file extensions to handlers. */
      +  var langHandlerRegistry = {};
      +  /** Register a language handler for the given file extensions.
      +    * @param {function (Object)} handler a function from source code to a list
      +    *      of decorations.  Takes a single argument job which describes the
      +    *      state of the computation.   The single parameter has the form
      +    *      {@code {
      +    *        source: {string} as plain text.
      +    *        decorations: {Array.<number|string>} an array of style classes
      +    *                     preceded by the position at which they start in
      +    *                     job.source in order.
      +    *                     The language handler should assigned this field.
      +    *        basePos: {int} the position of source in the larger source chunk.
      +    *                 All positions in the output decorations array are relative
      +    *                 to the larger source chunk.
      +    *      } }
      +    * @param {Array.<string>} fileExtensions
      +    */
      +  function registerLangHandler(handler, fileExtensions) {
      +    for (var i = fileExtensions.length; --i >= 0;) {
      +      var ext = fileExtensions[i];
      +      if (!langHandlerRegistry.hasOwnProperty(ext)) {
      +        langHandlerRegistry[ext] = handler;
      +      } else if ('console' in window) {
      +        console['warn']('cannot override language handler %s', ext);
      +      }
      +    }
      +  }
      +  function langHandlerForExtension(extension, source) {
      +    if (!(extension && langHandlerRegistry.hasOwnProperty(extension))) {
      +      // Treat it as markup if the first non whitespace character is a < and
      +      // the last non-whitespace character is a >.
      +      extension = /^\s*</.test(source)
      +          ? 'default-markup'
      +          : 'default-code';
      +    }
      +    return langHandlerRegistry[extension];
      +  }
      +  registerLangHandler(decorateSource, ['default-code']);
      +  registerLangHandler(
      +      createSimpleLexer(
      +          [],
      +          [
      +           [PR_PLAIN,       /^[^<?]+/],
      +           [PR_DECLARATION, /^<!\w[^>]*(?:>|$)/],
      +           [PR_COMMENT,     /^<\!--[\s\S]*?(?:-\->|$)/],
      +           // Unescaped content in an unknown language
      +           ['lang-',        /^<\?([\s\S]+?)(?:\?>|$)/],
      +           ['lang-',        /^<%([\s\S]+?)(?:%>|$)/],
      +           [PR_PUNCTUATION, /^(?:<[%?]|[%?]>)/],
      +           ['lang-',        /^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],
      +           // Unescaped content in javascript.  (Or possibly vbscript).
      +           ['lang-js',      /^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],
      +           // Contains unescaped stylesheet content
      +           ['lang-css',     /^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],
      +           ['lang-in.tag',  /^(<\/?[a-z][^<>]*>)/i]
      +          ]),
      +      ['default-markup', 'htm', 'html', 'mxml', 'xhtml', 'xml', 'xsl']);
      +  registerLangHandler(
      +      createSimpleLexer(
      +          [
      +           [PR_PLAIN,        /^[\s]+/, null, ' \t\r\n'],
      +           [PR_ATTRIB_VALUE, /^(?:\"[^\"]*\"?|\'[^\']*\'?)/, null, '\"\'']
      +           ],
      +          [
      +           [PR_TAG,          /^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],
      +           [PR_ATTRIB_NAME,  /^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],
      +           ['lang-uq.val',   /^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],
      +           [PR_PUNCTUATION,  /^[=<>\/]+/],
      +           ['lang-js',       /^on\w+\s*=\s*\"([^\"]+)\"/i],
      +           ['lang-js',       /^on\w+\s*=\s*\'([^\']+)\'/i],
      +           ['lang-js',       /^on\w+\s*=\s*([^\"\'>\s]+)/i],
      +           ['lang-css',      /^style\s*=\s*\"([^\"]+)\"/i],
      +           ['lang-css',      /^style\s*=\s*\'([^\']+)\'/i],
      +           ['lang-css',      /^style\s*=\s*([^\"\'>\s]+)/i]
      +           ]),
      +      ['in.tag']);
      +  registerLangHandler(
      +      createSimpleLexer([], [[PR_ATTRIB_VALUE, /^[\s\S]+/]]), ['uq.val']);
      +  registerLangHandler(sourceDecorator({
      +          'keywords': CPP_KEYWORDS,
      +          'hashComments': true,
      +          'cStyleComments': true
      +        }), ['c', 'cc', 'cpp', 'cxx', 'cyc', 'm']);
      +  registerLangHandler(sourceDecorator({
      +          'keywords': 'null true false'
      +        }), ['json']);
      +  registerLangHandler(sourceDecorator({
      +          'keywords': CSHARP_KEYWORDS,
      +          'hashComments': true,
      +          'cStyleComments': true,
      +          'verbatimStrings': true
      +        }), ['cs']);
      +  registerLangHandler(sourceDecorator({
      +          'keywords': JAVA_KEYWORDS,
      +          'cStyleComments': true
      +        }), ['java']);
      +  registerLangHandler(sourceDecorator({
      +          'keywords': SH_KEYWORDS,
      +          'hashComments': true,
      +          'multiLineStrings': true
      +        }), ['bsh', 'csh', 'sh']);
      +  registerLangHandler(sourceDecorator({
      +          'keywords': PYTHON_KEYWORDS,
      +          'hashComments': true,
      +          'multiLineStrings': true,
      +          'tripleQuotedStrings': true
      +        }), ['cv', 'py']);
      +  registerLangHandler(sourceDecorator({
      +          'keywords': PERL_KEYWORDS,
      +          'hashComments': true,
      +          'multiLineStrings': true,
      +          'regexLiterals': true
      +        }), ['perl', 'pl', 'pm']);
      +  registerLangHandler(sourceDecorator({
      +          'keywords': RUBY_KEYWORDS,
      +          'hashComments': true,
      +          'multiLineStrings': true,
      +          'regexLiterals': true
      +        }), ['rb']);
      +  registerLangHandler(sourceDecorator({
      +          'keywords': JSCRIPT_KEYWORDS,
      +          'cStyleComments': true,
      +          'regexLiterals': true
      +        }), ['js']);
      +  registerLangHandler(sourceDecorator({
      +          'keywords': COFFEE_KEYWORDS,
      +          'hashComments': 3,  // ### style block comments
      +          'cStyleComments': true,
      +          'multilineStrings': true,
      +          'tripleQuotedStrings': true,
      +          'regexLiterals': true
      +        }), ['coffee']);
      +  registerLangHandler(createSimpleLexer([], [[PR_STRING, /^[\s\S]+/]]), ['regex']);
      +
      +  function applyDecorator(job) {
      +    var sourceCodeHtml = job.sourceCodeHtml;
      +    var opt_langExtension = job.langExtension;
      +
      +    // Prepopulate output in case processing fails with an exception.
      +    job.prettyPrintedHtml = sourceCodeHtml;
      +
      +    try {
      +      // Extract tags, and convert the source code to plain text.
      +      var sourceAndExtractedTags = extractTags(sourceCodeHtml);
      +      /** Plain text. @type {string} */
      +      var source = sourceAndExtractedTags.source;
      +      job.source = source;
      +      job.basePos = 0;
      +
      +      /** Even entries are positions in source in ascending order.  Odd entries
      +        * are tags that were extracted at that position.
      +        * @type {Array.<number|string>}
      +        */
      +      job.extractedTags = sourceAndExtractedTags.tags;
      +
      +      // Apply the appropriate language handler
      +      langHandlerForExtension(opt_langExtension, source)(job);
      +      // Integrate the decorations and tags back into the source code to produce
      +      // a decorated html string which is left in job.prettyPrintedHtml.
      +      recombineTagsAndDecorations(job);
      +    } catch (e) {
      +      if ('console' in window) {
      +        console['log'](e && e['stack'] ? e['stack'] : e);
      +      }
      +    }
      +  }
      +
      +  /**
      +   * @param sourceCodeHtml {string} The HTML to pretty print.
      +   * @param opt_langExtension {string} The language name to use.
      +   *     Typically, a filename extension like 'cpp' or 'java'.
      +   * @param opt_numberLines {number|boolean} True to number lines,
      +   *     or the 1-indexed number of the first line in sourceCodeHtml.
      +   */
      +  function prettyPrintOne(sourceCodeHtml, opt_langExtension, opt_numberLines) {
      +    var job = {
      +      sourceCodeHtml: sourceCodeHtml,
      +      langExtension: opt_langExtension,
      +      numberLines: opt_numberLines
      +    };
      +    applyDecorator(job);
      +    return job.prettyPrintedHtml;
      +  }
      +
      +  function prettyPrint(opt_whenDone) {
      +    function byTagName(tn) { return document.getElementsByTagName(tn); }
      +    // fetch a list of nodes to rewrite
      +    var codeSegments = [byTagName('pre'), byTagName('code'), byTagName('xmp')];
      +    var elements = [];
      +    for (var i = 0; i < codeSegments.length; ++i) {
      +      for (var j = 0, n = codeSegments[i].length; j < n; ++j) {
      +        elements.push(codeSegments[i][j]);
      +      }
      +    }
      +    codeSegments = null;
      +
      +    var clock = Date;
      +    if (!clock['now']) {
      +      clock = { 'now': function () { return (new Date).getTime(); } };
      +    }
      +
      +    // The loop is broken into a series of continuations to make sure that we
      +    // don't make the browser unresponsive when rewriting a large page.
      +    var k = 0;
      +    var prettyPrintingJob;
      +
      +    function doWork() {
      +      var endTime = (window['PR_SHOULD_USE_CONTINUATION'] ?
      +                     clock.now() + 250 /* ms */ :
      +                     Infinity);
      +      for (; k < elements.length && clock.now() < endTime; k++) {
      +        var cs = elements[k];
      +        if (cs.className && cs.className.indexOf('prettyprint') >= 0) {
      +          // If the classes includes a language extensions, use it.
      +          // Language extensions can be specified like
      +          //     <pre class="prettyprint lang-cpp">
      +          // the language extension "cpp" is used to find a language handler as
      +          // passed to PR.registerLangHandler.
      +          var langExtension = cs.className.match(/\blang-(\w+)\b/);
      +          if (langExtension) { langExtension = langExtension[1]; }
      +
      +          // make sure this is not nested in an already prettified element
      +          var nested = false;
      +          for (var p = cs.parentNode; p; p = p.parentNode) {
      +            if ((p.tagName === 'pre' || p.tagName === 'code' ||
      +                 p.tagName === 'xmp') &&
      +                p.className && p.className.indexOf('prettyprint') >= 0) {
      +              nested = true;
      +              break;
      +            }
      +          }
      +          if (!nested) {
      +            // fetch the content as a snippet of properly escaped HTML.
      +            // Firefox adds newlines at the end.
      +            var content = getInnerHtml(cs);
      +            content = content.replace(/(?:\r\n?|\n)$/, '');
      +
      +            // Look for a class like linenums or linenums:<n> where <n> is the
      +            // 1-indexed number of the first line.
      +            var numberLines = cs.className.match(/\blinenums\b(?::(\d+))?/);
      +
      +            // do the pretty printing
      +            prettyPrintingJob = {
      +              sourceCodeHtml: content,
      +              langExtension: langExtension,
      +              sourceNode: cs,
      +              numberLines: numberLines
      +                  ? numberLines[1] && numberLines[1].length ? +numberLines[1] : true
      +                  : false
      +            };
      +            applyDecorator(prettyPrintingJob);
      +            replaceWithPrettyPrintedHtml();
      +          }
      +        }
      +      }
      +      if (k < elements.length) {
      +        // finish up in a continuation
      +        setTimeout(doWork, 250);
      +      } else if (opt_whenDone) {
      +        opt_whenDone();
      +      }
      +    }
      +
      +    function replaceWithPrettyPrintedHtml() {
      +      var newContent = prettyPrintingJob.prettyPrintedHtml;
      +      if (!newContent) { return; }
      +      var cs = prettyPrintingJob.sourceNode;
      +
      +      // push the prettified html back into the tag.
      +      if (!isRawContent(cs)) {
      +        // just replace the old html with the new
      +        cs.innerHTML = newContent;
      +      } else {
      +        // we need to change the tag to a <pre> since <xmp>s do not allow
      +        // embedded tags such as the span tags used to attach styles to
      +        // sections of source code.
      +        var pre = document.createElement('PRE');
      +        for (var i = 0; i < cs.attributes.length; ++i) {
      +          var a = cs.attributes[i];
      +          if (a.specified) {
      +            var aname = a.name.toLowerCase();
      +            if (aname === 'class') {
      +              pre.className = a.value;  // For IE 6
      +            } else {
      +              pre.setAttribute(a.name, a.value);
      +            }
      +          }
      +        }
      +        pre.innerHTML = newContent;
      +
      +        // remove the old
      +        cs.parentNode.replaceChild(pre, cs);
      +        cs = pre;
      +      }
      +    }
      +
      +    doWork();
      +  }
      +
      +  window['PR_normalizedHtml'] = normalizedHtml;
      +  window['prettyPrintOne'] = prettyPrintOne;
      +  window['prettyPrint'] = prettyPrint;
      +  window['PR'] = {
      +        'combinePrefixPatterns': combinePrefixPatterns,
      +        'createSimpleLexer': createSimpleLexer,
      +        'registerLangHandler': registerLangHandler,
      +        'sourceDecorator': sourceDecorator,
      +        'PR_ATTRIB_NAME': PR_ATTRIB_NAME,
      +        'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE,
      +        'PR_COMMENT': PR_COMMENT,
      +        'PR_DECLARATION': PR_DECLARATION,
      +        'PR_KEYWORD': PR_KEYWORD,
      +        'PR_LITERAL': PR_LITERAL,
      +        'PR_NOCODE': PR_NOCODE,
      +        'PR_PLAIN': PR_PLAIN,
      +        'PR_PUNCTUATION': PR_PUNCTUATION,
      +        'PR_SOURCE': PR_SOURCE,
      +        'PR_STRING': PR_STRING,
      +        'PR_TAG': PR_TAG,
      +        'PR_TYPE': PR_TYPE
      +      };
      +})();
      diff --git a/gui.js b/gui.js
      index 03d3c8c..96baa02 100644
      --- a/gui.js
      +++ b/gui.js
      @@ -1,66 +1,34 @@
      -var GUI = new function() {
      +var GUI = function () {
       
      -	var _this = this;
      -	
      -	var controllers = [];
      +	var _this = this, open = false,
      +	controllers = [], controllersWatched = [];
       
      -	this.add = function() {
      -	
      -		// We need to call GUI.start() before .add()
      -		if (!started) {
      -			error("Make sure to call GUI.start() in the window.onload function");
      -			return;
      -		}
      -	
      -		var object = arguments[0];
      -		var propertyName = arguments[1];
      -	
      -		// Have we already added this?
      -		if (alreadyControlled(object, propertyName)) {
      -			error("Controller for \"" + propertyName+"\" already added.");
      -			return;
      -		}
      -	
      -		var value = object[propertyName];
      -		
      -		// Does this value exist? Is it accessible?
      -		if (value == undefined) {
      -			error(object + " either has no property \""+propertyName+"\", or the property is inaccessible.");
      -			return;
      -		}
      -		
      -		var type = typeof value;
      -		var handler = addHandlers[type];
      -		
      -		// Do we know how to deal with this data type?
      -		if (handler == undefined) {
      -			error("Cannot create controller for data type \""+type+"\"");
      -			return;
      -		}
      -	
      -		var controllerObject = construct(handler, arguments);
      -		
      -		// Were we able to make the controller?
      -		if (!controllerObject) {		
      -			error("Error creating controller for \""+propertyName+"\".");	
      -			return;
      -		}
      -		
      -		// Success.
      -		controllerContainer.appendChild(controllerObject.domElement);
      -		controllers.push(controllerObject);
      -		
      -		return controllerObject;
      -		
      -	}
      -	
      -	var addHandlers = {
      +	this.domElement = document.createElement('div');
      +	this.domElement.setAttribute('id', 'guidat');
      +
      +	controllerContainer = document.createElement('div');
      +	controllerContainer.setAttribute('id', 'guidat-controllers');
      +	controllerContainer.style.height = '0px';
      +
      +	toggleButton = document.createElement('a');
      +	toggleButton.setAttribute('id', 'guidat-toggle');
      +	toggleButton.setAttribute('href', '#');
      +	toggleButton.innerHTML = "Show Controls";
      +	toggleButton.addEventListener('click', function(e) {
      +		_this.toggle();
      +		e.preventDefault();
      +	}, false);
      +
      +	this.domElement.appendChild(controllerContainer);
      +	this.domElement.appendChild(toggleButton);
      +
      +	var handlerTypes = {
       		"number": NumberController,
       		"string": StringController,
       		"boolean": BooleanController,
       		"function": FunctionController
       	};
      -	
      +
       	var alreadyControlled = function(object, propertyName) {
       		for (var i in controllers) {
       			if (controllers[i].object == object &&
      @@ -70,81 +38,86 @@ var GUI = new function() {
       		}
       		return false;
       	};
      -	
      +
       	var error = function(str) {
       		if (typeof console.log == 'function') {
       			console.error("[GUI ERROR] " + str);
       		}
       	};
      -	
      +
       	var construct = function(constructor, args) {
      -        function F() {
      -            return constructor.apply(this, args);
      -        }
      -        F.prototype = constructor.prototype;
      -        return new F();
      -    };
      -
      -
      -
      -	// GUI ... GUI
      -	
      -	this.domElement = null;
      -	var controllerContainer;
      -	var started = false;
      -	var open = false;
      -	
      -	// TODO: obtain this dynamically?
      -	var domElementMarginTop = 300;
      -	
      -	this.start = function() {
      -		
      -		this.domElement = document.createElement('div');
      -		this.domElement.setAttribute('id', 'guidat');
      -		
      -		controllerContainer = document.createElement('div');
      -		controllerContainer.setAttribute('id', 'guidat-controllers');
      -		
      -		toggleButton = document.createElement('a');
      -		toggleButton.setAttribute('id', 'guidat-toggle');
      -		toggleButton.setAttribute('href', '#');
      -		toggleButton.innerHTML = "Show Controls";
      -		toggleButton.addEventListener('click', function(e) {
      -			_this.toggle();
      -			e.preventDefault();
      -		}, false);
      -		
      -		this.domElement.appendChild(controllerContainer);
      -		this.domElement.appendChild(toggleButton);
      -		
      -		this.domElement.style.marginTop = -domElementMarginTop+"px";
      -		
      -		document.body.appendChild(this.domElement);
      -		
      -		started = true;
      -		
      +		function F() {
      +		    return constructor.apply(this, args);
      +		}
      +		F.prototype = constructor.prototype;
      +		return new F();
       	};
      -	
      +
      +	this.add = function() {
      +
      +		var object = arguments[0];
      +		var propertyName = arguments[1];
      +
      +		// Have we already added this?
      +		if (alreadyControlled(object, propertyName)) {
      +			error("Controller for \"" + propertyName+"\" already added.");
      +			return;
      +		}
      +
      +		var value = object[propertyName];
      +
      +		// Does this value exist? Is it accessible?
      +		if (value == undefined) {
      +			error(object + " either has no property \""+propertyName+"\", or the property is inaccessible.");
      +			return;
      +		}
      +
      +		var type = typeof value;
      +		var handler = handlerTypes[type];
      +
      +		// Do we know how to deal with this data type?
      +		if (handler == undefined) {
      +			error("Cannot create controller for data type \""+type+"\"");
      +			return;
      +		}
      +
      +		var controllerObject = construct( handler, arguments);
      +		controllerObject.parent = _this;
      +
      +		// Were we able to make the controller?
      +		if (!controllerObject) {
      +			error("Error creating controller for \""+propertyName+"\".");
      +			return;
      +		}
      +
      +		// Success.
      +		controllerContainer.appendChild(controllerObject.domElement);
      +		controllers.push(controllerObject);
      +
      +		return controllerObject;
      +
      +	}
      +
       	this.toggle = function() {
      -		
      +
       		if (open) {
       			this.hide();
      -		} else { 
      +		} else {
       			this.show();
       		}
      -		
      +
       	};
      -	
      +
       	this.show = function() {
      -		this.domElement.style.marginTop = 0+"px";
      +		controllerContainer.style.height = '300px';
       		toggleButton.innerHTML = "Hide Controls";
       		open = true;
       	}
      -	
      +
       	this.hide = function() {
      -		this.domElement.style.marginTop = -domElementMarginTop+"px";
      +		controllerContainer.style.height = '0px';
       		toggleButton.innerHTML = "Show Controls";
       		open = false;
       	}
      -	
      +
       };
      diff --git a/index.html b/index.html
      index dffb602..e08f68e 100644
      --- a/index.html
      +++ b/index.html
      @@ -1,46 +1,58 @@
       <!doctype html>
           <head>
               <title>gui-dat</title>
      -        
      +
               <link rel="icon" type="image/png" href="demo/assets/favicon.png" />
               <link href="demo/demo.css" media="screen" rel="stylesheet" type="text/css" />
      -        <script type="text/javascript" src="http://google-code-prettify.googlecode.com/svn/trunk/src/prettify.js"></script>
      -        <script type="text/javascript" src="gui.min.js"></script>
      +
      +	<!-- <script type="text/javascript" src="gui.min.js"></script> -->
      +
      +	<link href="gui.css" media="screen" rel="stylesheet" type="text/css" />
      +
      +	<script type="text/javascript" src="controllers/slider.js"></script>
      +	<script type="text/javascript" src="controllers/controller.js"></script>
      +	<script type="text/javascript" src="controllers/controller.boolean.js"></script>
      +	<script type="text/javascript" src="controllers/controller.function.js"></script>
      +	<script type="text/javascript" src="controllers/controller.number.js"></script>
      +	<script type="text/javascript" src="controllers/controller.string.js"></script>
      +	<script type="text/javascript" src="gui.js"></script>
      +
               <script type="text/javascript" src="demo/improvedNoise.js"></script>
      +        <script type="text/javascript" src="demo/prettify.js"></script>
               <script type="text/javascript" src="demo/demo.js"></script>
               <script type="text/javascript">
                   window.onload = function() {
       
      -			   prettyPrint();
      +		prettyPrint();
       
      -               var fizzyText = new FizzyText("gui-dat");
      +		var fizzyText = new FizzyText("gui-dat");
       
      -               GUI.start();
      -               
      -               // Text field
      -               GUI.add(fizzyText, "message");
      +		var gui = new GUI();
      +		document.body.appendChild( gui.domElement );
      +
      +		// Text field
      +		gui.add(fizzyText, "message");
      +
      +		// Sliders with min and max
      +		gui.add(fizzyText, "maxSize", 0.5, 7);
      +		gui.add(fizzyText, "growthSpeed", 0.01, 1);
      +		gui.add(fizzyText, "speed", 0.1, 2);
      +
      +		// Sliders with min, max and increment.
      +		gui.add(fizzyText, "noiseStrength", 10, 100, 5);
      +
      +		// Boolean checkbox
      +		gui.add(fizzyText, "displayOutline");
      +
      +		// Fires a function called "explode"
      +		gui.add(fizzyText, "explode").setName('Explode!');    // Specify a custom name.
       
      -               // Sliders with min and max
      -               GUI.add(fizzyText, "maxSize", 0.5, 7);
      -               GUI.add(fizzyText, "growthSpeed", 0.01, 1);
      -               GUI.add(fizzyText, "speed", 0.1, 2);
      -               
      -               // Sliders with min, max and increment.
      -               GUI.add(fizzyText, "noiseStrength", 10, 100, 5);
      -               
      -               // Boolean checkbox
      -               GUI.add(fizzyText, "displayOutline");
      -	
      -			   // Fires a function called "explode"
      -               GUI.add(fizzyText, "explode")
      -                    .setName('Explode!');    // Specify a custom name.
      -               
                   };
      -            
      +
                   var _gaq = _gaq || [];
                   _gaq.push(['_setAccount', 'UA-20996084-1']);
                   _gaq.push(['_trackPageview']);
      -            
      +
                   (function() {
                       var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
                       ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
      @@ -49,21 +61,20 @@
               </script>
           </head>
           <body>
      -    	<div id="container">
       	    <div id = "helvetica-demo"></div>
       	    <div id = "notifier"></div>
               <h1><a href = "http://twitter.com/guidat"><img src = "demo/assets/profile.png" border = "0" alt = "GUI-DAT flag" /></a></h1>
               <p>
                   <strong>gui-dat</strong> is a lightweight controller library for JavaScript. It allows you to easily manipulate variables and fire functions on the fly.
               </p>
      -    
      +
       		<ul>
       			<li><a href="https://github.com/jonobr1/GUI-DAT/raw/master/gui.min.js"><strong>Download the minified source</strong></a> <small>[9.8kb]</small></li>
               	<li><a href="http://github.com/jonobr1/GUI-DAT">Contribute on GitHub!</a></li>
               </ul>
      -    
      +
               <h2>Basic Usage</h2>
      -        <pre class="prettyprint">&lt;script type=&quot;text/javascript&quot; src=&quot;demo/demo.js&quot;&gt;&lt;/script&gt;
      +        <pre id="demo-pre" class="prettyprint">&lt;script type=&quot;text/javascript&quot; src=&quot;demo/demo.js&quot;&gt;&lt;/script&gt;
       &lt;script type=&quot;text/javascript&quot;&gt;
       
       window.onload = function() {
      @@ -71,7 +82,7 @@ window.onload = function() {
          var fizzyText = new <a href="demo/demo.js">FizzyText</a>(&quot;gui-dat&quot;);
       
          GUI.start();
      -   
      +
          // Text field
          GUI.add(fizzyText, &quot;message&quot;);
       
      @@ -79,38 +90,26 @@ window.onload = function() {
          GUI.add(fizzyText, &quot;maxSize&quot;, 0.5, 7);
          GUI.add(fizzyText, &quot;growthSpeed&quot;, 0.01, 1);
          GUI.add(fizzyText, &quot;speed&quot;, 0.1, 2);
      -   
      -   // Sliders with min, max and increment
      +
      +   // Sliders with min, max and increment.
          GUI.add(fizzyText, &quot;noiseStrength&quot;, 10, 100, 5);
      -   
      +
          // Boolean checkbox
          GUI.add(fizzyText, &quot;displayOutline&quot;);
       
          // Fires a function called &quot;explode&quot;
      -   GUI.add(fizzyText, &quot;explode&quot;).setName(&quot;Explode!&quot;); // Specify a custom name.
      -   
      +   GUI.add(fizzyText, &quot;explode&quot;).setName(&#039;Explode!&#039;); // Specify a custom name.
      +
       };
       
       &lt;/script&gt;</pre>
      -        
      +
           	<ul id="desc">
           		<li><strong>gui-dat</strong> will infer the type of the property you're trying to add<br/>(based on its initial value) and create the corresponding control.</li>
           		<li>The properties must be public, i.e. defined by <code><strong>this</strong>.prop = value</code>.</li>
           	</ul>
      -    	<!--
      -		<hr/>
      -    	<h2>Monitor variable changes <em>outside</em> of the GUI</h2>
      -    	<p>Let's say you have a variable that changes by itself from time to time. If you'd like the GUI to reflect those changes, use the <code>listen()</code> method.</p>
      -    	<pre class="prettyprint">GUI.add(obj, "propName").listen();</pre>
      -
      -    	<h2>Fire a function when someone uses a control</h2>
      -    	<pre class="prettyprint">GUI.add(obj, "propName").onChange(function(n) {
      -	alert("You changed me to " + n);
      -});</pre>
      --->
      -		<footer> 
      -            Initiated by <a href="http://georgemichaelbrower.com/">George Michael Brower</a> and <a href="http://jonobr1.com/">Jono Brandel</a> of the Data Arts Team, Google Creative Lab.
      -        </footer> 
      -		</div>
      +		<footer>
      +            By <a href="http://georgemichaelbrower.com/">George Michael Brower</a>, <a href="http://jonobr1.com/">Jono Brandel</a>, and <a href="http://github.com/jonobr1/GUI-DAT">you</a>.
      +        </footer>
           </body>
      -</html>
      \ No newline at end of file
      +</html>
      
      From 402093179a87f524ce4f4eaec7db2094f6a8f155 Mon Sep 17 00:00:00 2001
      From: "Mr.doob" <info@mrdoob.com>
      Date: Sat, 29 Jan 2011 00:44:54 +0000
      Subject: [PATCH 10/23] Removed google analytics (to avoid lag when reloading
       :S) Updated "pretified" code.
      
      ---
       index.html | 27 +++++++++------------------
       1 file changed, 9 insertions(+), 18 deletions(-)
      
      diff --git a/index.html b/index.html
      index e08f68e..d5bb603 100644
      --- a/index.html
      +++ b/index.html
      @@ -48,16 +48,6 @@
       		gui.add(fizzyText, "explode").setName('Explode!');    // Specify a custom name.
       
                   };
      -
      -            var _gaq = _gaq || [];
      -            _gaq.push(['_setAccount', 'UA-20996084-1']);
      -            _gaq.push(['_trackPageview']);
      -
      -            (function() {
      -                var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
      -                ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
      -                var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
      -            })();
               </script>
           </head>
           <body>
      @@ -81,24 +71,25 @@ window.onload = function() {
       
          var fizzyText = new <a href="demo/demo.js">FizzyText</a>(&quot;gui-dat&quot;);
       
      -   GUI.start();
      +   var gui = new GUI();
      +   document.body.appendChild( gui.domElement );
       
          // Text field
      -   GUI.add(fizzyText, &quot;message&quot;);
      +   gui.add(fizzyText, &quot;message&quot;);
       
          // Sliders with min and max
      -   GUI.add(fizzyText, &quot;maxSize&quot;, 0.5, 7);
      -   GUI.add(fizzyText, &quot;growthSpeed&quot;, 0.01, 1);
      -   GUI.add(fizzyText, &quot;speed&quot;, 0.1, 2);
      +   gui.add(fizzyText, &quot;maxSize&quot;, 0.5, 7);
      +   gui.add(fizzyText, &quot;growthSpeed&quot;, 0.01, 1);
      +   gui.add(fizzyText, &quot;speed&quot;, 0.1, 2);
       
          // Sliders with min, max and increment.
      -   GUI.add(fizzyText, &quot;noiseStrength&quot;, 10, 100, 5);
      +   gui.add(fizzyText, &quot;noiseStrength&quot;, 10, 100, 5);
       
          // Boolean checkbox
      -   GUI.add(fizzyText, &quot;displayOutline&quot;);
      +   gui.add(fizzyText, &quot;displayOutline&quot;);
       
          // Fires a function called &quot;explode&quot;
      -   GUI.add(fizzyText, &quot;explode&quot;).setName(&#039;Explode!&#039;); // Specify a custom name.
      +   gui.add(fizzyText, &quot;explode&quot;).setName(&#039;Explode!&#039;); // Specify a custom name.
       
       };
       
      
      From d770dbb0c20e41a3ab38605d46ceff5e69696461 Mon Sep 17 00:00:00 2001
      From: "Mr.doob" <info@mrdoob.com>
      Date: Sat, 29 Jan 2011 01:23:20 +0000
      Subject: [PATCH 11/23] Hacked in a Controllers Watcher. It would be nice if
       the controllers had the last value cached so setValue will execute only if
       _value != value. Added a setWatched example in the demo.
      
      ---
       controllers/controller.js        | 13 ++++++--
       controllers/controller.number.js | 10 ++----
       demo/demo.js                     | 11 +++++--
       gui.js                           | 21 ++++++++----
       index.html                       | 55 +++++++++++++++++---------------
       5 files changed, 65 insertions(+), 45 deletions(-)
      
      diff --git a/controllers/controller.js b/controllers/controller.js
      index ad90e73..ab9ae44 100644
      --- a/controllers/controller.js
      +++ b/controllers/controller.js
      @@ -9,6 +9,15 @@ var Controller = function() {
       	    return this;
       	}
       
      +	this.setWatched = function() {
      +		this.parent.watchController(this);
      +		return this;
      +	}
      +
      +	this.getValue = function() {
      +		return this.object[this.propertyName];
      +	}
      +
       	this.setValue = function(n) {
       		this.object[this.propertyName] = n;
       		if (onChange != null) {
      @@ -17,8 +26,8 @@ var Controller = function() {
       		return this;
       	}
       
      -	this.getValue = function() {
      -		return this.object[this.propertyName];
      +	this.watchValue = function() {
      +		this.updateValue(this.object[this.propertyName]);
       	}
       
       	this.onChange = function(fnc) {
      diff --git a/controllers/controller.number.js b/controllers/controller.number.js
      index 80d299b..0907c1c 100644
      --- a/controllers/controller.number.js
      +++ b/controllers/controller.number.js
      @@ -20,15 +20,9 @@ var NumberController = function() {
           var step = arguments[4];
           
           if (!step) {
      -    	if (min != undefined && max != undefined) {
      -    		step = (max-min)*0.01;
      -    	} else {
      -    		step = 1;
      -    	}	
      +	step = min != undefined && max != undefined ? (max-min)*0.01: 1;
           }
           
      -    console.log("step " + step);
      -    
           var numberField = document.createElement('input');
           numberField.setAttribute('id', this.propertyName);
           
      @@ -94,7 +88,7 @@ var NumberController = function() {
       		// We don't want to be highlighting this field as we scroll.
       		// Or any other fields in this gui for that matter ... 
       		// TODO: Make makeUselectable go through each element and child element.
      -		_this.makeUnselectable(GUI.domElement);
      +		_this.makeUnselectable(_this.parent.domElement);
       		_this.makeUnselectable(numberField);
       		
       		py = y;
      diff --git a/demo/demo.js b/demo/demo.js
      index 9ac306c..0a4298a 100644
      --- a/demo/demo.js
      +++ b/demo/demo.js
      @@ -1,15 +1,18 @@
       function FizzyText(message) {
       
      +	var that = this;
      +
       	// These are the variables that we manipulate with gui-dat.
       	// Notice they're all defined with "this". That makes them public.
       	// Otherwise, gui-dat can't see them.
      -	
      +
           this.growthSpeed = 0.5;		  // how fast do particles change size?
           this.maxSize = 3.2;			  // how big can they get?
           this.noiseStrength = 10;	  // how turbulent is the flow?
           this.speed = 0.4;			  // how fast do particles move?
           this.displayOutline = false;  // should we draw the message as a stroke?
      -    
      +    this.framesRendered = 0;
      +
           // __defineGetter__ and __defineSetter__ makes JavaScript believe that
           // we've defined a variable 'this.message'. This way, whenever we 
           // change the message variable, we can call some more functions.
      @@ -98,6 +101,8 @@ function FizzyText(message) {
       	// Called once per frame, updates the animation.
           var render = function () {
       
      +	that.framesRendered ++;
      +
               g.clearRect(0, 0, width, height);
       
               if (_this.displayOutline) {
      @@ -216,4 +221,4 @@ function FizzyText(message) {
               return v;
           }
       
      -}
      \ No newline at end of file
      +}
      diff --git a/gui.js b/gui.js
      index 96baa02..c9f1e8c 100644
      --- a/gui.js
      +++ b/gui.js
      @@ -22,6 +22,13 @@ var GUI = function () {
       	this.domElement.appendChild(controllerContainer);
       	this.domElement.appendChild(toggleButton);
       
      +	// Controllers Watcher
      +	setInterval( function() {
      +		for (var c in controllersWatched) {
      +			controllersWatched[c].watchValue();
      +		}
      +	}, 1000 / 60 );
      +
       	var handlerTypes = {
       		"number": NumberController,
       		"string": StringController,
      @@ -96,16 +103,16 @@ var GUI = function () {
       
       		return controllerObject;
       
      +	};
      +
      +	this.watchController = function(c) {
      +
      +		controllersWatched.push(c);
      +
       	}
       
       	this.toggle = function() {
      -
      -		if (open) {
      -			this.hide();
      -		} else {
      -			this.show();
      -		}
      -
      +		open ? this.hide() : this.show();
       	};
       
       	this.show = function() {
      diff --git a/index.html b/index.html
      index d5bb603..ff4dbc8 100644
      --- a/index.html
      +++ b/index.html
      @@ -2,8 +2,8 @@
           <head>
               <title>gui-dat</title>
       
      -        <link rel="icon" type="image/png" href="demo/assets/favicon.png" />
      -        <link href="demo/demo.css" media="screen" rel="stylesheet" type="text/css" />
      +	<link rel="icon" type="image/png" href="demo/assets/favicon.png" />
      +	<link href="demo/demo.css" media="screen" rel="stylesheet" type="text/css" />
       
       	<!-- <script type="text/javascript" src="gui.min.js"></script> -->
       
      @@ -17,37 +17,42 @@
       	<script type="text/javascript" src="controllers/controller.string.js"></script>
       	<script type="text/javascript" src="gui.js"></script>
       
      -        <script type="text/javascript" src="demo/improvedNoise.js"></script>
      -        <script type="text/javascript" src="demo/prettify.js"></script>
      -        <script type="text/javascript" src="demo/demo.js"></script>
      -        <script type="text/javascript">
      -            window.onload = function() {
      +	<script type="text/javascript" src="demo/improvedNoise.js"></script>
      +	<script type="text/javascript" src="demo/prettify.js"></script>
      +	<script type="text/javascript" src="demo/demo.js"></script>
      +	<script type="text/javascript">
       
      -		prettyPrint();
      +		window.onload = function() {
       
      -		var fizzyText = new FizzyText("gui-dat");
      +			prettyPrint();
       
      -		var gui = new GUI();
      -		document.body.appendChild( gui.domElement );
      +			var fizzyText = new FizzyText("gui-dat");
       
      -		// Text field
      -		gui.add(fizzyText, "message");
      +			var gui = new GUI();
      +			document.body.appendChild( gui.domElement );
       
      -		// Sliders with min and max
      -		gui.add(fizzyText, "maxSize", 0.5, 7);
      -		gui.add(fizzyText, "growthSpeed", 0.01, 1);
      -		gui.add(fizzyText, "speed", 0.1, 2);
      +			// Text field
      +			gui.add(fizzyText, "message");
       
      -		// Sliders with min, max and increment.
      -		gui.add(fizzyText, "noiseStrength", 10, 100, 5);
      +			// Sliders with min and max
      +			gui.add(fizzyText, "maxSize", 0.5, 7);
      +			gui.add(fizzyText, "growthSpeed", 0.01, 1);
      +			gui.add(fizzyText, "speed", 0.1, 2);
       
      -		// Boolean checkbox
      -		gui.add(fizzyText, "displayOutline");
      +			// Sliders with min, max and increment.
      +			gui.add(fizzyText, "noiseStrength", 10, 100, 5);
       
      -		// Fires a function called "explode"
      -		gui.add(fizzyText, "explode").setName('Explode!');    // Specify a custom name.
      +			// Boolean checkbox
      +			gui.add(fizzyText, "displayOutline");
      +
      +			// Watches a property
      +			gui.add(fizzyText, "framesRendered").setWatched();
      +
      +			// Fires a function called "explode"
      +			gui.add(fizzyText, "explode").setName("Explode!");    // Specify a custom name.
      +
      +		};
       
      -            };
               </script>
           </head>
           <body>
      @@ -89,7 +94,7 @@ window.onload = function() {
          gui.add(fizzyText, &quot;displayOutline&quot;);
       
          // Fires a function called &quot;explode&quot;
      -   gui.add(fizzyText, &quot;explode&quot;).setName(&#039;Explode!&#039;); // Specify a custom name.
      +   gui.add(fizzyText, &quot;explode&quot;).setName(&quot;Explode!&quot;); // Specify a custom name.
       
       };
       
      
      From a3b3e38d1dfd96461ac41d4cb4ee764e6bcb7942 Mon Sep 17 00:00:00 2001
      From: "Mr.doob" <info@mrdoob.com>
      Date: Sat, 29 Jan 2011 01:34:57 +0000
      Subject: [PATCH 12/23] Merged with jonobr1 branch.
      
      ---
       README     | 59 ++++++++++++++++++++++++++++++++----------------------
       README.md  | 44 ++++++++++++++++++++++++++++++++++++++++
       index.html | 18 ++++++++++++++---
       3 files changed, 94 insertions(+), 27 deletions(-)
       create mode 100644 README.md
      
      diff --git a/README b/README
      index 426e5b2..1f8431a 100644
      --- a/README
      +++ b/README
      @@ -1,31 +1,42 @@
      -    var controllableObject = 
      -    	{	
      -    		numberProperty: 20,
      -    		anotherNumberProperty: 0,
      -    		textProperty: "a string",
      -    		booleanProperty: false,
      -    		functionProperty: function() {
      -    			alert("I am a function!");
      -    		}
      -    	};
      +# gui-dat
      +**gui-dat** is a lightweight controller library for JavaScript. It allows you to easily manipulate variables and fire functions on the fly.
      +## Basic Usage
      +    <script type="text/javascript" src="demo/demo.js"></script>
      +    <script type="text/javascript">
       
           window.onload = function() {
       
      -    	GUI.start();
      +       var fizzyText = new FizzyText("gui-dat");
       
      -    	// Creates a number box
      -    	GUI.add(controllableObject, "numberProperty");
      +       GUI.start();
      +   
      +       // Text field
      +       GUI.add(fizzyText, "message");
       
      -    	// Creates a slider (min, max)
      -    	GUI.add(controllableObject, "anotherNumberProperty", -100, 100);
      +       // Sliders with min and max
      +       GUI.add(fizzyText, "maxSize", 0.5, 7);
      +       GUI.add(fizzyText, "growthSpeed", 0.01, 1);
      +       GUI.add(fizzyText, "speed", 0.1, 2);
      +   
      +       // Sliders with min, max and increment
      +       GUI.add(fizzyText, "noiseStrength", 10, 100, 5);
      +   
      +       // Boolean checkbox
      +       GUI.add(fizzyText, "displayOutline");
       
      -    	// Creates a text field
      -    	GUI.add(controllableObject, "textProperty");
      +       // Fires a function called "explode"
      +       GUI.add(fizzyText, "explode").setName("Explode!"); // Specify a custom name.
      +   
      +    };
       
      -    	// Creates a checkbox
      -    	GUI.add(controllableObject, "booleanProperty");
      -
      -    	// Creates a button
      -    	GUI.add(controllableObject, "functionProperty");
      -
      -    }
      \ No newline at end of file
      +    </script>
      ++   ui-dat will infer the type of the property you're trying to add (based on its initial value) and create the corresponding control.
      ++   The properties must be public, i.e. defined by `**this**.prop = value`.
      +## Monitor variable changes <i>outside</i> of the GUI
      +Let's say you have a variable that changes by itself from time to time. If you'd like the GUI to reflect those changes, use the listen() method
      +    GUI.add(obj, "propName").listen();
      +## Fire a function when someone uses a control
      +    GUI.add(obj, "propName").onChange(function(n) {
      +        alert("You changed me to " + n);
      +    });
      +Initiated by [George Michael Brower]:http://georgemichaelbrower.com/ and [Jono Brandel]:http://jonobr1.com/ of the Data Arts Team, Google Creative Lab.
      diff --git a/README.md b/README.md
      new file mode 100644
      index 0000000..415cd25
      --- /dev/null
      +++ b/README.md
      @@ -0,0 +1,44 @@
      +# gui-dat
      +**gui-dat** is a lightweight controller library for JavaScript. It allows you to easily manipulate variables and fire functions on the fly.
      +## Basic Usage
      +    <script type="text/javascript" src="demo/demo.js"></script>
      +    <script type="text/javascript">
      +
      +    window.onload = function() {
      +
      +       var fizzyText = new FizzyText("gui-dat");
      +
      +       GUI.start();
      +   
      +       // Text field
      +       GUI.add(fizzyText, "message");
      +
      +       // Sliders with min and max
      +       GUI.add(fizzyText, "maxSize", 0.5, 7);
      +       GUI.add(fizzyText, "growthSpeed", 0.01, 1);
      +       GUI.add(fizzyText, "speed", 0.1, 2);
      +   
      +       // Sliders with min, max and increment
      +       GUI.add(fizzyText, "noiseStrength", 10, 100, 5);
      +   
      +       // Boolean checkbox
      +       GUI.add(fizzyText, "displayOutline");
      +
      +       // Fires a function called "explode"
      +       GUI.add(fizzyText, "explode").name("Explode!"); // Specify a custom name.
      +   
      +    };
      +
      +    </script>
      ++   ui-dat will infer the type of the property you're trying to add (based on its initial value) and create the corresponding control.
      ++   The properties must be public, i.e. defined by `this.prop = value`.
      +
      +Monitor variable changes <i>outside</i> of the GUI
      +--------------------------------------------------
      +Let's say you have a variable that changes by itself from time to time. If you'd like the GUI to reflect those changes, use the listen() method
      +    GUI.add(obj, "propName").listen();
      +## Fire a function when someone uses a control
      +    GUI.add(obj, "propName").onChange(function(n) {
      +        alert("You changed me to " + n);
      +    });
      +Initiated by [George Michael Brower](http://georgemichaelbrower.com/) and [Jono Brandel](http://jonobr1.com/) of the Data Arts Team, Google Creative Lab.
      diff --git a/index.html b/index.html
      index ff4dbc8..259cc21 100644
      --- a/index.html
      +++ b/index.html
      @@ -104,8 +104,20 @@ window.onload = function() {
           		<li><strong>gui-dat</strong> will infer the type of the property you're trying to add<br/>(based on its initial value) and create the corresponding control.</li>
           		<li>The properties must be public, i.e. defined by <code><strong>this</strong>.prop = value</code>.</li>
           	</ul>
      -		<footer>
      -            By <a href="http://georgemichaelbrower.com/">George Michael Brower</a>, <a href="http://jonobr1.com/">Jono Brandel</a>, and <a href="http://github.com/jonobr1/GUI-DAT">you</a>.
      -        </footer>
      +        
      +        <!-- <hr/>
      +        <h2>Monitor variable changes <em>outside</em> of the GUI</h2>
      +        <p>Let's say you have a variable that changes by itself from time to time. If you'd like the GUI to reflect those changes, use the <code>listen()</code> method.</p>
      +        <pre class="prettyprint">GUI.add(obj, "propName").listen();</pre>
      +
      +        <h2>Fire a function when someone uses a control</h2>
      +        <pre class="prettyprint">GUI.add(obj, "propName").onChange(function(n) {
      +    alert("You changed me to " + n);
      +});</pre> -->
      +
      +		<footer> 
      +            Initiated by <a href="http://georgemichaelbrower.com/">George Michael Brower</a> and <a href="http://jonobr1.com/">Jono Brandel</a> of the Data Arts Team, Google Creative Lab.
      +        </footer> 
      +		</div>
           </body>
       </html>
      
      From 4774ed04a721877ad0f221a22302836b54d9f5b6 Mon Sep 17 00:00:00 2001
      From: jonobr1 <jonobrandel@gmail.com>
      Date: Fri, 28 Jan 2011 19:19:30 -0700
      Subject: [PATCH 13/23] removed README
      
      ---
       README | 42 ------------------------------------------
       1 file changed, 42 deletions(-)
       delete mode 100644 README
      
      diff --git a/README b/README
      deleted file mode 100644
      index 1f8431a..0000000
      --- a/README
      +++ /dev/null
      @@ -1,42 +0,0 @@
      -# gui-dat
      -**gui-dat** is a lightweight controller library for JavaScript. It allows you to easily manipulate variables and fire functions on the fly.
      -## Basic Usage
      -    <script type="text/javascript" src="demo/demo.js"></script>
      -    <script type="text/javascript">
      -
      -    window.onload = function() {
      -
      -       var fizzyText = new FizzyText("gui-dat");
      -
      -       GUI.start();
      -   
      -       // Text field
      -       GUI.add(fizzyText, "message");
      -
      -       // Sliders with min and max
      -       GUI.add(fizzyText, "maxSize", 0.5, 7);
      -       GUI.add(fizzyText, "growthSpeed", 0.01, 1);
      -       GUI.add(fizzyText, "speed", 0.1, 2);
      -   
      -       // Sliders with min, max and increment
      -       GUI.add(fizzyText, "noiseStrength", 10, 100, 5);
      -   
      -       // Boolean checkbox
      -       GUI.add(fizzyText, "displayOutline");
      -
      -       // Fires a function called "explode"
      -       GUI.add(fizzyText, "explode").setName("Explode!"); // Specify a custom name.
      -   
      -    };
      -
      -    </script>
      -+   ui-dat will infer the type of the property you're trying to add (based on its initial value) and create the corresponding control.
      -+   The properties must be public, i.e. defined by `**this**.prop = value`.
      -## Monitor variable changes <i>outside</i> of the GUI
      -Let's say you have a variable that changes by itself from time to time. If you'd like the GUI to reflect those changes, use the listen() method
      -    GUI.add(obj, "propName").listen();
      -## Fire a function when someone uses a control
      -    GUI.add(obj, "propName").onChange(function(n) {
      -        alert("You changed me to " + n);
      -    });
      -Initiated by [George Michael Brower]:http://georgemichaelbrower.com/ and [Jono Brandel]:http://jonobr1.com/ of the Data Arts Team, Google Creative Lab.
      
      From fe172c75fb4beb4bc7def2805d18175d7c6a335b Mon Sep 17 00:00:00 2001
      From: "Mr.doob" <info@mrdoob.com>
      Date: Sat, 29 Jan 2011 02:25:48 +0000
      Subject: [PATCH 14/23] Added updateValue method to StringController (so
       watched string controllers update). Moved easing from margin-top to height.
       It's now showing an ugly scrollbar jump. Hmm...
      
      ---
       controllers/controller.string.js | 50 ++++++++++++++++++--------------
       gui.css                          |  8 ++---
       2 files changed, 32 insertions(+), 26 deletions(-)
      
      diff --git a/controllers/controller.string.js b/controllers/controller.string.js
      index aa955ea..835ba39 100644
      --- a/controllers/controller.string.js
      +++ b/controllers/controller.string.js
      @@ -1,27 +1,33 @@
       var StringController = function() {
      -	
      +
       	this.type = "string";
      -	
      +
       	var _this = this;
      -	
      -    Controller.apply(this, arguments);
      -    
      -    var input = document.createElement('input');
      -    
      -    var initialValue = this.getValue();
      -    
      -    input.setAttribute('value', initialValue);
      -    input.setAttribute('spellcheck', 'false');
      -    this.domElement.addEventListener('mouseup', function() {
      -    	input.focus();
      -    	input.select();
      -    }, false);
      -    
      -    input.addEventListener('keyup', function() {
      -        _this.setValue(input.value);
      -    }, false);
      -    
      -    this.domElement.appendChild(input);
      +
      +	Controller.apply(this, arguments);
      +
      +	var input = document.createElement('input');
      +
      +	var initialValue = this.getValue();
      +
      +	input.setAttribute('value', initialValue);
      +	input.setAttribute('spellcheck', 'false');
      +
      +	this.domElement.addEventListener('mouseup', function() {
      +		input.focus();
      +		input.select();
      +	}, false);
      +
      +	input.addEventListener('keyup', function() {
      +		_this.setValue(input.value);
      +	}, false);
      +
      +	this.domElement.appendChild(input);
      +
      +	this.updateValue = function(val) {
      +		input.setAttribute('value', val);
      +	}
      +
       };
       StringController.prototype = new Controller();
      -StringController.prototype.constructor = StringController;
      \ No newline at end of file
      +StringController.prototype.constructor = StringController;
      diff --git a/gui.css b/gui.css
      index 98a57a8..d035320 100644
      --- a/gui.css
      +++ b/gui.css
      @@ -8,9 +8,6 @@
       	left: 100%;
       	margin-left: -300px;
       	background-color: #fff;
      -	-moz-transition: margin-top .2s ease-out;
      -	-webkit-transition: margin-top .2s ease-out;
      -	transition: margin-top .2s ease-out;
       	-webkit-box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
       	-moz-box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
       	box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
      @@ -26,6 +23,9 @@
       	overflow-y: auto;
       	overflow-x: hidden;
       	background-color: rgba(0,0,0,0.1);
      +	-moz-transition: height .2s ease-out;
      +	-webkit-transition: height .2s ease-out;
      +	transition: height .2s ease-out;
       }
       
       #guidat-toggle { 
      @@ -155,4 +155,4 @@ width: 148px;
       .guidat-slider-fg { 
       	background-color: #00aeff;
       	height: 20px;
      -}
      \ No newline at end of file
      +}
      
      From f736507fd460001e7b160324f9bc33b00515d2b2 Mon Sep 17 00:00:00 2001
      From: jonobr1 <jonobrandel@gmail.com>
      Date: Fri, 28 Jan 2011 19:55:43 -0700
      Subject: [PATCH 15/23] Merged in mr doobs changes for watchers and changed
       setName to name
      
      ---
       controllers/controller.js | 4 ++--
       1 file changed, 2 insertions(+), 2 deletions(-)
      
      diff --git a/controllers/controller.js b/controllers/controller.js
      index ab9ae44..92c770f 100644
      --- a/controllers/controller.js
      +++ b/controllers/controller.js
      @@ -4,7 +4,7 @@ var Controller = function() {
       
       	this.parent = null;
       
      -	this.setName = function(n) {
      +	this.name = function(n) {
       	    this.propertyNameElement.innerHTML = n;
       	    return this;
       	}
      @@ -57,7 +57,7 @@ var Controller = function() {
       
       	this.propertyNameElement = document.createElement('span');
       	this.propertyNameElement.setAttribute('class', 'guidat-propertyname');
      -	this.setName(this.propertyName);
      +	this.name(this.propertyName);
       	this.domElement.appendChild(this.propertyNameElement);
       
       	this.makeUnselectable(this.domElement);
      
      From a4d600f3b063d9b58054733c4ec1c0fe90001d80 Mon Sep 17 00:00:00 2001
      From: jonobr1 <jonobrandel@gmail.com>
      Date: Fri, 28 Jan 2011 20:03:56 -0700
      Subject: [PATCH 16/23] why does README keep popping back up?
      
      ---
       README | 42 ------------------------------------------
       1 file changed, 42 deletions(-)
       delete mode 100644 README
      
      diff --git a/README b/README
      deleted file mode 100644
      index 1f8431a..0000000
      --- a/README
      +++ /dev/null
      @@ -1,42 +0,0 @@
      -# gui-dat
      -**gui-dat** is a lightweight controller library for JavaScript. It allows you to easily manipulate variables and fire functions on the fly.
      -## Basic Usage
      -    <script type="text/javascript" src="demo/demo.js"></script>
      -    <script type="text/javascript">
      -
      -    window.onload = function() {
      -
      -       var fizzyText = new FizzyText("gui-dat");
      -
      -       GUI.start();
      -   
      -       // Text field
      -       GUI.add(fizzyText, "message");
      -
      -       // Sliders with min and max
      -       GUI.add(fizzyText, "maxSize", 0.5, 7);
      -       GUI.add(fizzyText, "growthSpeed", 0.01, 1);
      -       GUI.add(fizzyText, "speed", 0.1, 2);
      -   
      -       // Sliders with min, max and increment
      -       GUI.add(fizzyText, "noiseStrength", 10, 100, 5);
      -   
      -       // Boolean checkbox
      -       GUI.add(fizzyText, "displayOutline");
      -
      -       // Fires a function called "explode"
      -       GUI.add(fizzyText, "explode").setName("Explode!"); // Specify a custom name.
      -   
      -    };
      -
      -    </script>
      -+   ui-dat will infer the type of the property you're trying to add (based on its initial value) and create the corresponding control.
      -+   The properties must be public, i.e. defined by `**this**.prop = value`.
      -## Monitor variable changes <i>outside</i> of the GUI
      -Let's say you have a variable that changes by itself from time to time. If you'd like the GUI to reflect those changes, use the listen() method
      -    GUI.add(obj, "propName").listen();
      -## Fire a function when someone uses a control
      -    GUI.add(obj, "propName").onChange(function(n) {
      -        alert("You changed me to " + n);
      -    });
      -Initiated by [George Michael Brower]:http://georgemichaelbrower.com/ and [Jono Brandel]:http://jonobr1.com/ of the Data Arts Team, Google Creative Lab.
      
      From 092419bdad4e49333b8f8194a27035572d34225a Mon Sep 17 00:00:00 2001
      From: jonobr1 <jonobrandel@gmail.com>
      Date: Fri, 28 Jan 2011 20:17:57 -0700
      Subject: [PATCH 17/23] updated index.html doc to reflect setWatched()
      
      ---
       index.html | 3 +++
       1 file changed, 3 insertions(+)
      
      diff --git a/index.html b/index.html
      index 259cc21..245dd3d 100644
      --- a/index.html
      +++ b/index.html
      @@ -93,6 +93,9 @@ window.onload = function() {
          // Boolean checkbox
          gui.add(fizzyText, &quot;displayOutline&quot;);
       
      +	// Watches a property
      +	gui.add(fizzyText, "framesRendered").setWatched();
      +
          // Fires a function called &quot;explode&quot;
          gui.add(fizzyText, &quot;explode&quot;).setName(&quot;Explode!&quot;); // Specify a custom name.
       
      
      From 2dc271fa44ac2aec43ffedd197fbe56bde299464 Mon Sep 17 00:00:00 2001
      From: jonobr1 <jonobrandel@gmail.com>
      Date: Fri, 28 Jan 2011 20:22:05 -0700
      Subject: [PATCH 18/23] updated index to reflect the new listen() and
       onChange() functionality
      
      ---
       controllers/controller.js | 2 +-
       index.html                | 8 ++++----
       2 files changed, 5 insertions(+), 5 deletions(-)
      
      diff --git a/controllers/controller.js b/controllers/controller.js
      index 92c770f..0fa37ff 100644
      --- a/controllers/controller.js
      +++ b/controllers/controller.js
      @@ -9,7 +9,7 @@ var Controller = function() {
       	    return this;
       	}
       
      -	this.setWatched = function() {
      +	this.listen = function() {
       		this.parent.watchController(this);
       		return this;
       	}
      diff --git a/index.html b/index.html
      index 245dd3d..416e06a 100644
      --- a/index.html
      +++ b/index.html
      @@ -46,7 +46,7 @@
       			gui.add(fizzyText, "displayOutline");
       
       			// Watches a property
      -			gui.add(fizzyText, "framesRendered").setWatched();
      +			gui.add(fizzyText, "framesRendered").listen();
       
       			// Fires a function called "explode"
       			gui.add(fizzyText, "explode").setName("Explode!");    // Specify a custom name.
      @@ -94,7 +94,7 @@ window.onload = function() {
          gui.add(fizzyText, &quot;displayOutline&quot;);
       
       	// Watches a property
      -	gui.add(fizzyText, "framesRendered").setWatched();
      +	gui.add(fizzyText, "framesRendered").listen();
       
          // Fires a function called &quot;explode&quot;
          gui.add(fizzyText, &quot;explode&quot;).setName(&quot;Explode!&quot;); // Specify a custom name.
      @@ -108,7 +108,7 @@ window.onload = function() {
           		<li>The properties must be public, i.e. defined by <code><strong>this</strong>.prop = value</code>.</li>
           	</ul>
               
      -        <!-- <hr/>
      +        <hr/>
               <h2>Monitor variable changes <em>outside</em> of the GUI</h2>
               <p>Let's say you have a variable that changes by itself from time to time. If you'd like the GUI to reflect those changes, use the <code>listen()</code> method.</p>
               <pre class="prettyprint">GUI.add(obj, "propName").listen();</pre>
      @@ -116,7 +116,7 @@ window.onload = function() {
               <h2>Fire a function when someone uses a control</h2>
               <pre class="prettyprint">GUI.add(obj, "propName").onChange(function(n) {
           alert("You changed me to " + n);
      -});</pre> -->
      +});</pre>
       
       		<footer> 
                   Initiated by <a href="http://georgemichaelbrower.com/">George Michael Brower</a> and <a href="http://jonobr1.com/">Jono Brandel</a> of the Data Arts Team, Google Creative Lab.
      
      From 54d198cac50a1adb08bb619bec6e0edc78b94f4f Mon Sep 17 00:00:00 2001
      From: jonobr1 <jonobrandel@gmail.com>
      Date: Fri, 28 Jan 2011 20:27:17 -0700
      Subject: [PATCH 19/23] minor tweaks
      
      ---
       demo/demo.js | 2 +-
       1 file changed, 1 insertion(+), 1 deletion(-)
      
      diff --git a/demo/demo.js b/demo/demo.js
      index 0a4298a..5bea7e1 100644
      --- a/demo/demo.js
      +++ b/demo/demo.js
      @@ -101,7 +101,7 @@ function FizzyText(message) {
       	// Called once per frame, updates the animation.
           var render = function () {
       
      -	that.framesRendered ++;
      +	    that.framesRendered ++;
       
               g.clearRect(0, 0, width, height);
       
      
      From d8dc82515e79a00de4595e8160b308e484832083 Mon Sep 17 00:00:00 2001
      From: jonobr1 <jonobrandel@gmail.com>
      Date: Fri, 28 Jan 2011 20:38:05 -0700
      Subject: [PATCH 20/23] had to update setName() to name() again
      
      ---
       index.html | 4 ++--
       1 file changed, 2 insertions(+), 2 deletions(-)
      
      diff --git a/index.html b/index.html
      index 416e06a..d020927 100644
      --- a/index.html
      +++ b/index.html
      @@ -49,7 +49,7 @@
       			gui.add(fizzyText, "framesRendered").listen();
       
       			// Fires a function called "explode"
      -			gui.add(fizzyText, "explode").setName("Explode!");    // Specify a custom name.
      +			gui.add(fizzyText, "explode").name("Explode!");    // Specify a custom name.
       
       		};
       
      @@ -97,7 +97,7 @@ window.onload = function() {
       	gui.add(fizzyText, "framesRendered").listen();
       
          // Fires a function called &quot;explode&quot;
      -   gui.add(fizzyText, &quot;explode&quot;).setName(&quot;Explode!&quot;); // Specify a custom name.
      +   gui.add(fizzyText, &quot;explode&quot;).name(&quot;Explode!&quot;); // Specify a custom name.
       
       };
       
      
      From d6edaf6da5f2e035704fb80c20b9496cef47e9ab Mon Sep 17 00:00:00 2001
      From: jonobr1 <jonobrandel@gmail.com>
      Date: Fri, 28 Jan 2011 20:51:44 -0700
      Subject: [PATCH 21/23] minor tweaks
      
      ---
       index.html | 4 ++--
       1 file changed, 2 insertions(+), 2 deletions(-)
      
      diff --git a/index.html b/index.html
      index d020927..3d751d9 100644
      --- a/index.html
      +++ b/index.html
      @@ -93,8 +93,8 @@ window.onload = function() {
          // Boolean checkbox
          gui.add(fizzyText, &quot;displayOutline&quot;);
       
      -	// Watches a property
      -	gui.add(fizzyText, "framesRendered").listen();
      +   // Watches a property
      +   gui.add(fizzyText, "framesRendered").listen();
       
          // Fires a function called &quot;explode&quot;
          gui.add(fizzyText, &quot;explode&quot;).name(&quot;Explode!&quot;); // Specify a custom name.
      
      From 7ba44aace68a0f04ac1c84da315122fff2dff92a Mon Sep 17 00:00:00 2001
      From: jonobr1 <jonobrandel@gmail.com>
      Date: Fri, 28 Jan 2011 20:54:26 -0700
      Subject: [PATCH 22/23] minor tweaks
      
      ---
       index.html | 3 ---
       1 file changed, 3 deletions(-)
      
      diff --git a/index.html b/index.html
      index 3d751d9..e4d62c6 100644
      --- a/index.html
      +++ b/index.html
      @@ -93,9 +93,6 @@ window.onload = function() {
          // Boolean checkbox
          gui.add(fizzyText, &quot;displayOutline&quot;);
       
      -   // Watches a property
      -   gui.add(fizzyText, "framesRendered").listen();
      -
          // Fires a function called &quot;explode&quot;
          gui.add(fizzyText, &quot;explode&quot;).name(&quot;Explode!&quot;); // Specify a custom name.
       
      
      From a378d33baeb2e78e77ed03e40c165d9fe29d2368 Mon Sep 17 00:00:00 2001
      From: "Mr.doob" <info@mrdoob.com>
      Date: Sat, 29 Jan 2011 03:56:32 +0000
      Subject: [PATCH 23/23] Fixed loop issue. Now watched controllers don't set the
       value after updating.
      
      ---
       controllers/controller.boolean.js  |  39 ++---
       controllers/controller.function.js |  16 +-
       controllers/controller.js          |  23 +--
       controllers/controller.number.js   | 226 +++++++++++++++--------------
       controllers/controller.string.js   |  15 +-
       controllers/slider.js              |   3 +-
       gui.js                             |   2 +-
       7 files changed, 167 insertions(+), 157 deletions(-)
      
      diff --git a/controllers/controller.boolean.js b/controllers/controller.boolean.js
      index 3acc78f..2287f19 100644
      --- a/controllers/controller.boolean.js
      +++ b/controllers/controller.boolean.js
      @@ -1,25 +1,26 @@
       var BooleanController = function() {
       	this.type = "boolean";
      -    Controller.apply(this, arguments);
      +	Controller.apply(this, arguments);
       
      -	var _this = this;
      -    var input = document.createElement('input');
      -    input.setAttribute('type', 'checkbox');
      -    
      -    this.domElement.addEventListener('click', function(e) {
      -    	input.checked = !input.checked;
      -    	e.preventDefault();
      -    	_this.setValue(input.checked);
      -    }, false);
      -    
      -    input.addEventListener('mouseup', function(e) {
      -    	input.checked = !input.checked; // counteracts default.
      -    }, false);
      -    
      -    this.domElement.style.cursor = "pointer";
      -    this.propertyNameElement.style.cursor = "pointer";
      -    this.domElement.appendChild(input);
      +	var that = this;
      +	var input = document.createElement('input');
      +	input.setAttribute('type', 'checkbox');
      +
      +	this.domElement.addEventListener('click', function(e) {
      +		e.preventDefault();
      +		input.checked = !input.checked;
      +		that.value = input.checked;
      +		that.setTargetValue(that.value);
      +	}, false);
      +
      +	input.addEventListener('mouseup', function(e) {
      +		input.checked = !input.checked; // counteracts default.
      +	}, false);
      +
      +	this.domElement.style.cursor = "pointer";
      +	this.propertyNameElement.style.cursor = "pointer";
      +	this.domElement.appendChild(input);
       
       };
       BooleanController.prototype = new Controller();
      -BooleanController.prototype.constructor = BooleanController;
      \ No newline at end of file
      +BooleanController.prototype.constructor = BooleanController;
      diff --git a/controllers/controller.function.js b/controllers/controller.function.js
      index d523562..c95c5ec 100644
      --- a/controllers/controller.function.js
      +++ b/controllers/controller.function.js
      @@ -1,12 +1,12 @@
       var FunctionController = function() {
       	this.type = "function";
      -	var _this = this;
      -    Controller.apply(this, arguments);
      -    this.domElement.addEventListener('click', function() {
      -    	_this.object[_this.propertyName].call(_this.object);
      -    }, false);
      -    this.domElement.style.cursor = "pointer";
      -    this.propertyNameElement.style.cursor = "pointer";
      +	var that = this;
      +	Controller.apply(this, arguments);
      +	this.domElement.addEventListener('click', function() {
      +		that.object[that.propertyName].call(that.object);
      +	}, false);
      +	this.domElement.style.cursor = "pointer";
      +	this.propertyNameElement.style.cursor = "pointer";
       };
       FunctionController.prototype = new Controller();
      -FunctionController.prototype.constructor = FunctionController;
      \ No newline at end of file
      +FunctionController.prototype.constructor = FunctionController;
      diff --git a/controllers/controller.js b/controllers/controller.js
      index ab9ae44..80253dc 100644
      --- a/controllers/controller.js
      +++ b/controllers/controller.js
      @@ -3,51 +3,52 @@ var Controller = function() {
       	var onChange = null;
       
       	this.parent = null;
      +	this.value = null;
       
       	this.setName = function(n) {
       	    this.propertyNameElement.innerHTML = n;
       	    return this;
      -	}
      +	};
       
       	this.setWatched = function() {
       		this.parent.watchController(this);
       		return this;
      -	}
      +	};
       
      -	this.getValue = function() {
      +	this.getTargetValue = function() {
       		return this.object[this.propertyName];
      -	}
      +	};
       
      -	this.setValue = function(n) {
      +	this.setTargetValue = function(n) {
       		this.object[this.propertyName] = n;
       		if (onChange != null) {
       			onChange.call(this, n);
       		}
       		return this;
      -	}
      +	};
       
      -	this.watchValue = function() {
      +	this.watchTargetValue = function() {
       		this.updateValue(this.object[this.propertyName]);
      -	}
      +	};
       
       	this.onChange = function(fnc) {
       		onChange = fnc;
       		return this;
      -	}
      +	};
       
       	this.makeUnselectable = function(elem) {
       		elem.onselectstart = function() { return false; };
       		elem.style.MozUserSelect = "none";
       		elem.style.KhtmlUserSelect = "none";
       		elem.unselectable = "on";
      -	}
      +	};
       
       	this.makeSelectable = function(elem) {
       		elem.onselectstart = function() { };
       		elem.style.MozUserSelect = "auto";
       		elem.style.KhtmlUserSelect = "auto";
       		elem.unselectable = "off";
      -	}
      +	};
       
       	this.domElement = document.createElement('div');
       	this.domElement.setAttribute('class', 'guidat-controller ' + this.type);
      diff --git a/controllers/controller.number.js b/controllers/controller.number.js
      index 0907c1c..30f2856 100644
      --- a/controllers/controller.number.js
      +++ b/controllers/controller.number.js
      @@ -2,124 +2,130 @@
       var NumberController = function() {
       
       	this.type = "number";
      -    
      -    Controller.apply(this, arguments);
      -    
      -    var _this = this;
      -    
      -    // If we simply click and release a number field, we want to highlight it.
      -    // This variable keeps track of whether or not we've dragged
      -    var draggedNumberField = false;
      -    
      -    var clickedNumberField = false;
      -    
      -    var y = py = 0;
      -    
      -    var min = arguments[2];
      -    var max = arguments[3];
      -    var step = arguments[4];
      -    
      -    if (!step) {
      -	step = min != undefined && max != undefined ? (max-min)*0.01: 1;
      -    }
      -    
      -    var numberField = document.createElement('input');
      -    numberField.setAttribute('id', this.propertyName);
      -    
      -    // Little up and down arrows are pissing me off.
      -    numberField.setAttribute('type', 'text');
      -    numberField.setAttribute('value', this.getValue());
      -    
      -    if (step) numberField.setAttribute('step', step);
      -    
      -    this.domElement.appendChild(numberField);
      -    
      -    var slider;
      -    
      -    if (min != undefined && max != undefined) {
      -    	slider = new Slider(this, min, max, step, this.getValue());
      -    	this.domElement.appendChild(slider.domElement);
      -    }
      -    
      -    numberField.addEventListener('blur', function(e) {
      -        var val = parseFloat(this.value);
      -        if (!isNaN(val)) {
      -	        _this.updateValue(val);
      -        } else { 
      -        	this.value = _this.getValue();
      -        }
      -    }, false);
      -    
      -    numberField.addEventListener('mousewheel', function(e) {
      -    	e.preventDefault();
      -    	this.updateValue(_this.getValue() + Math.abs(e.wheelDeltaY)/e.wheelDeltaY*step);
      -    	return false;
      -    }, false);
      -    
      -    numberField.addEventListener('mousedown', function(e) {
      -        py = y = e.pageY;
      -		clickedNumberField = true;
      -        document.addEventListener('mousemove', dragNumberField, false);
      -    }, false);
      -    
      -    document.addEventListener('mouseup', function(e) {
      -        document.removeEventListener('mousemove', dragNumberField, false);
      -        _this.makeSelectable(_this.parent.domElement); 
      -        _this.makeSelectable(numberField);
      -        if (clickedNumberField && !draggedNumberField) { 
      -	        numberField.focus();
      -	        numberField.select();
      -        }
      -        draggedNumberField = false;
      -        clickedNumberField = false;
      -    }, false);
      -    
      -    // Kinda nast
      -    if (navigator.appVersion.indexOf('chrome') != -1) {
      -        document.addEventListener('mouseout', function(e) {
      -            document.removeEventListener('mousemove', dragNumberField, false);
      -        }, false);
      -    }
      -    
      -    var dragNumberField = function(e) {
      -    	draggedNumberField = true;
      +
      +	Controller.apply(this, arguments);
      +
      +	var that = this;
      +
      +	// If we simply click and release a number field, we want to highlight it.
      +	// This variable keeps track of whether or not we've dragged
      +	var draggedNumberField = false, clickedNumberField = false,
      +	y = 0, py = 0;
      +
      +	var min = arguments[2], max = arguments[3], step = arguments[4];
      +
      +	if (!step) {
      +		step = min != undefined && max != undefined ? (max-min)*0.01: 1;
      +	}
      +
      +	var numberField = document.createElement('input');
      +	numberField.setAttribute('id', this.propertyName);
      +
      +	this.value = this.getTargetValue();
      +
      +	// Little up and down arrows are pissing me off.
      +	numberField.setAttribute('type', 'text');
      +	numberField.setAttribute('value', this.value);
      +
      +	if (step) numberField.setAttribute('step', step);
      +
      +	this.domElement.appendChild(numberField);
      +
      +	var slider;
      +
      +	if (min != undefined && max != undefined) {
      +		slider = new Slider(this, min, max, step, this.value);
      +		this.domElement.appendChild(slider.domElement);
      +	}
      +
      +	numberField.addEventListener('blur', function(e) {
      +		var val = parseFloat(this.value);
      +		if (!isNaN(val)) {
      +			that.updateValue(val);
      +			that.setTargetValue(that.value);
      +		} else {
      +			this.value = that.value;
      +		}
      +	}, false);
      +
      +	numberField.addEventListener('mousewheel', function(e) {
       		e.preventDefault();
      -		
      +		that.updateValue(that.value + Math.abs(e.wheelDeltaY)/e.wheelDeltaY*step);
      +		that.setTargetValue(that.value);
      +		return false;
      +	}, false);
      +
      +	numberField.addEventListener('mousedown', function(e) {
      +		py = y = e.pageY;
      +		clickedNumberField = true;
      +		document.addEventListener('mousemove', dragNumberField, false);
      +	}, false);
      +
      +	document.addEventListener('mouseup', function(e) {
      +		document.removeEventListener('mousemove', dragNumberField, false);
      +		that.makeSelectable(that.parent.domElement);
      +		that.makeSelectable(numberField);
      +
      +		if (clickedNumberField && !draggedNumberField) {
      +			numberField.focus();
      +			numberField.select();
      +		}
      +
      +		draggedNumberField = false;
      +		clickedNumberField = false;
      +	}, false);
      +
      +	// Kinda nast
      +	if (navigator.appVersion.indexOf('chrome') != -1) {
      +		document.addEventListener('mouseout', function(e) {
      +		    document.removeEventListener('mousemove', dragNumberField, false);
      +		}, false);
      +	}
      +
      +	var dragNumberField = function(e) {
      +		draggedNumberField = true;
      +		e.preventDefault();
      +
       		// We don't want to be highlighting this field as we scroll.
      -		// Or any other fields in this gui for that matter ... 
      +		// Or any other fields in this gui for that matter ...
       		// TODO: Make makeUselectable go through each element and child element.
      -		_this.makeUnselectable(_this.parent.domElement);
      -		_this.makeUnselectable(numberField);
      -		
      +		that.makeUnselectable(that.parent.domElement);
      +		that.makeUnselectable(numberField);
      +
       		py = y;
       		y = e.pageY;
       		var dy = py - y;
      -		var newVal = _this.getValue() + dy*step;	
      -		_this.updateValue(newVal);
      +		that.updateValue(that.value + dy*step);
      +		that.setTargetValue(that.value);
       		return false;
      -    }
      -    
      -    this.updateValue = function(val) {
      +	}
      +
      +	this.updateValue = function(val) {
      +
      +		if (that.value != val) {
      +
      +			val = parseFloat(val);
      +
      +			if (min != undefined && val <= min) {
      +				val = min;
      +			} else if (max != undefined && val >= max) {
      +				val = max;
      +			}
      +
      +			that.value = val;
      +
      +			numberField.value = roundToDecimal(val, 4);
      +
      +			if (slider) slider.value = val;
      +
      +		}
      +	}
      +
      +	var roundToDecimal = function(n, decimals) {
      +		var t = Math.pow(10, decimals);
      +		return Math.round(n*t)/t;
      +	}
       
      -		val = parseFloat(val);
      -		
      -    	if (min != undefined && val <= min) {
      -    		val = min;
      -    	} else if (max != undefined && val >= max) { 
      -    		val = max;
      -    	}
      -    	
      -        _this.setValue(val);
      -        
      -        numberField.value = roundToDecimal(_this.getValue(), 4);
      -        if (slider) slider.value = _this.getValue();
      -    }
      -    
      -    var roundToDecimal = function(n, decimals) {
      -	    var t = Math.pow(10, decimals);
      -    	return Math.round(n*t)/t;
      -    }
      -    
       };
       
       NumberController.prototype = new Controller();
      diff --git a/controllers/controller.string.js b/controllers/controller.string.js
      index 835ba39..789d074 100644
      --- a/controllers/controller.string.js
      +++ b/controllers/controller.string.js
      @@ -2,15 +2,14 @@ var StringController = function() {
       
       	this.type = "string";
       
      -	var _this = this;
      +	var that = this;
       
       	Controller.apply(this, arguments);
       
      +	this.value = this.getTargetValue();
      +
       	var input = document.createElement('input');
      -
      -	var initialValue = this.getValue();
      -
      -	input.setAttribute('value', initialValue);
      +	input.setAttribute('value', this.value);
       	input.setAttribute('spellcheck', 'false');
       
       	this.domElement.addEventListener('mouseup', function() {
      @@ -19,13 +18,15 @@ var StringController = function() {
       	}, false);
       
       	input.addEventListener('keyup', function() {
      -		_this.setValue(input.value);
      +		that.updateValue(input.value);
      +		that.setTargetValue(that.value);
       	}, false);
       
       	this.domElement.appendChild(input);
       
       	this.updateValue = function(val) {
      -		input.setAttribute('value', val);
      +		that.value = val;
      +		input.setAttribute('value', that.value);
       	}
       
       };
      diff --git a/controllers/slider.js b/controllers/slider.js
      index a45ea1a..55db279 100644
      --- a/controllers/slider.js
      +++ b/controllers/slider.js
      @@ -49,6 +49,7 @@ var Slider = function(numberController, min, max, step, initValue) {
       		var val = map(e.pageX, pos[0], pos[0] + _this.domElement.offsetWidth, min, max);
       		val = Math.round(val/step)*step;
       		numberController.updateValue(val);
      +		numberController.setTargetValue(numberController.value);
       	}
       	
       	this.domElement.addEventListener('mousedown', function(e) {
      @@ -70,4 +71,4 @@ var Slider = function(numberController, min, max, step, initValue) {
       	
       	this.value = initValue;	
       		
      -}
      \ No newline at end of file
      +}
      diff --git a/gui.js b/gui.js
      index c9f1e8c..0bfc4a9 100644
      --- a/gui.js
      +++ b/gui.js
      @@ -25,7 +25,7 @@ var GUI = function () {
       	// Controllers Watcher
       	setInterval( function() {
       		for (var c in controllersWatched) {
      -			controllersWatched[c].watchValue();
      +			controllersWatched[c].watchTargetValue();
       		}
       	}, 1000 / 60 );