]> jfr.im git - irc/quakenet/qwebirc.git/commitdiff
normalise line endings
authorChris Porter <redacted>
Thu, 13 Jul 2017 23:29:29 +0000 (00:29 +0100)
committerChris Porter <redacted>
Fri, 14 Jul 2017 00:09:23 +0000 (01:09 +0100)
14 files changed:
.gitattributes [new file with mode: 0644]
bin/dependencies.py
bin/dependencies_b.py
css/mochaui/content.css
css/mochaui/ui.css
dummyauthgate/__init__.py
js/irc/ignorecontroller.js
js/irc/nicknamevalidator.js
js/md5.js
js/qhash.js
static/js/mochaui/excanvas-compressed.js
static/js/mochaui/mocha.js
static/js/soundmanager2-nodebug-jsmin.js
static/js/soundmanager2.js

diff --git a/.gitattributes b/.gitattributes
new file mode 100644 (file)
index 0000000..176a458
--- /dev/null
@@ -0,0 +1 @@
+* text=auto
index b04811d96aff47c80286666b6fc8bf4fbd605cf6..bee1ffa731450204b5142f7e50e9bf8d9ac63423 100644 (file)
@@ -1,45 +1,45 @@
-import sys\r
-\r
-def check_dependencies():\r
-  def fail(message):\r
-    sys.stderr.write(message + "\n")\r
-    sys.stderr.flush()\r
-    sys.exit(1)\r
-    \r
-  major, minor = sys.version_info[:2]\r
-  if major >= 3:\r
-    fail("qwebirc cannot run on python >=3 yet, install python 2.6.X:\nhttp://www.python.org/download/")\r
-    \r
-  if major < 2 or minor < 5:\r
-    fail("qwebirc requires python 2.5, you have: %s, install python 2.6.X:\nhttp://www.python.org/download/" % ".".join(map(str, sys.version_info[:3])))\r
-       \r
-  # this is done so we can use Python 2.5 syntax...\r
-  import dependencies_b\r
-  dependencies_b.check_dependencies()\r
-\r
-def has_checked():\r
-  try:\r
-    f = open(".checked", "r")\r
-    f.close()\r
-    return True\r
-  except:\r
-    pass\r
-    \r
-  try:\r
-    f = open(os.path.join("bin", ".checked"), "r")\r
-    f.close()\r
-    return True\r
-  except:\r
-    pass\r
-  \r
-  return False\r
-  \r
-def vcheck():\r
-  if not has_checked():\r
-    sys.stderr.write("first run, checking dependencies...\n")\r
-    sys.stderr.flush()\r
-    check_dependencies()\r
-\r
-if __name__ == "__main__":\r
-  check_dependencies()\r
+import sys
+
+def check_dependencies():
+  def fail(message):
+    sys.stderr.write(message + "\n")
+    sys.stderr.flush()
+    sys.exit(1)
+    
+  major, minor = sys.version_info[:2]
+  if major >= 3:
+    fail("qwebirc cannot run on python >=3 yet, install python 2.6.X:\nhttp://www.python.org/download/")
+    
+  if major < 2 or minor < 5:
+    fail("qwebirc requires python 2.5, you have: %s, install python 2.6.X:\nhttp://www.python.org/download/" % ".".join(map(str, sys.version_info[:3])))
+       
+  # this is done so we can use Python 2.5 syntax...
+  import dependencies_b
+  dependencies_b.check_dependencies()
+
+def has_checked():
+  try:
+    f = open(".checked", "r")
+    f.close()
+    return True
+  except:
+    pass
+    
+  try:
+    f = open(os.path.join("bin", ".checked"), "r")
+    f.close()
+    return True
+  except:
+    pass
+  
+  return False
+  
+def vcheck():
+  if not has_checked():
+    sys.stderr.write("first run, checking dependencies...\n")
+    sys.stderr.flush()
+    check_dependencies()
+
+if __name__ == "__main__":
+  check_dependencies()
   
\ No newline at end of file
index bada44999fb0cc5e550d5eac3082e0740bed6460..92086e0f58826c7e19851639a598672354a8d8b5 100644 (file)
-# this is separate to allow us to use python 2.5 syntax without\r
-# the dependency checker breaking on earlier versions.\r
-\r
-import sys\r
-import subprocess\r
-import os\r
-\r
-def fail(*message):\r
-  print >>sys.stderr, "\n".join(message)\r
-  sys.exit(1)\r
-  \r
-def warn(*message):\r
-  print >>sys.stderr, "warning:", "\nwarning: ".join(message), "\n"\r
-\r
-def check_dependencies():\r
-  i = 0\r
-\r
-  check_zope()\r
-  check_twisted()\r
-  check_win32()\r
-  i+=check_json()\r
-  i+=check_java()\r
-  i+=check_hg()\r
-  \r
-  print "0 errors, %d warnings." % i\r
-  \r
-  if i == 0:\r
-    print "looks like you've got everything you need to run qwebirc!"\r
-  else:\r
-    print "you can run qwebirc despite these."\r
-\r
-  f = open(".checked", "w")\r
-  f.close()\r
-  \r
-def check_win32():\r
-  if not sys.platform.startswith("win"):\r
-    return\r
-  \r
-  try:\r
-    import win32con\r
-  except ImportError:\r
-    fail("qwebirc requires pywin32, see:", "http://sourceforge.net/project/showfiles.php?group_id=78018")\r
-  \r
-def check_java():\r
-  def java_warn(specific):\r
-    warn(specific, "java is not required, but allows qwebirc to compress output,", "making it faster to download.", "you can get java at http://www.java.com/")\r
-    \r
-  try:\r
-    p = subprocess.Popen(["java", "-version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=os.name == "nt")\r
-    p.communicate()\r
-    if p.wait() != 0:\r
-      java_warn("something went wrong looking for java.")\r
-      return 1\r
-  except: # ugh\r
-    java_warn("couldn't find java.")\r
-    return 1\r
-    \r
-  return 0\r
-  \r
-def check_hg():\r
-  def hg_warn(specific):\r
-    warn(specific, "mercurial (hg) is not required, but allows qwebirc to save bandwidth by versioning.", "you can get hg at http://www.selenic.com/mercurial/")\r
-    \r
-  try:\r
-    p = subprocess.Popen(["hg", "id"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=os.name == "nt")\r
-    p.communicate()\r
-    if p.wait() != 0:\r
-      hg_warn("something went wrong looking for mercurial.")\r
-      return 1\r
-  except: # ugh\r
-    hg_warn("couldn't find mercurial.")\r
-    return 1\r
-    \r
-  return 0\r
-  \r
-def check_zope():\r
-  try:\r
-    from zope.interface import Interface\r
-  except ImportError:\r
-    if sys.platform.startswith("win"):\r
-      fail("qwebirc requires zope interface",\r
-           "see pypi: http://pypi.python.org/pypi/zope.interface")\r
-    else:\r
-      fail("qwebirc requires zope interface.",\r
-           "this should normally come with twisted, but can be downloaded",\r
-           "from pypi: http://pypi.python.org/pypi/zope.interface")\r
-           \r
-def check_twisted():\r
-  try:\r
-    import twisted\r
-  except ImportError:\r
-    fail("qwebirc requires twisted (at least 8.2.0), see http://twistedmatrix.com/")\r
-\r
-  def twisted_fail(x, y=None):\r
-    fail("you don't seem to have twisted's %s module." % x,\r
-         "your distro is most likely modular, look for a twisted %s package%s." % (x, " %s" % y if y else "",))\r
-\r
-  try:\r
-    import twisted.names\r
-  except ImportError:\r
-    twisted_fail("names")\r
-\r
-  try:\r
-    import twisted.mail\r
-  except ImportError:\r
-    twisted_fail("mail")\r
-\r
-  try:\r
-    import twisted.web\r
-  except ImportError:\r
-    twisted_fail("web", "(not web2)")\r
-\r
-  try:\r
-    import twisted.words\r
-  except ImportError:\r
-    twisted_fail("words")\r
-    \r
-def check_json():\r
-  import qwebirc.util.qjson\r
-  if qwebirc.util.qjson.slow:\r
-    warn("simplejson module with C speedups not installed.",\r
-         "using embedded module (slower); consider installing simplejson from:",\r
-         "http://pypi.python.org/pypi/simplejson/")\r
-    return 1\r
-  return 0\r
-  \r
-if __name__ == "__main__":\r
-  import dependencies\r
-  dependencies.check_dependencies()\r
+# this is separate to allow us to use python 2.5 syntax without
+# the dependency checker breaking on earlier versions.
+
+import sys
+import subprocess
+import os
+
+def fail(*message):
+  print >>sys.stderr, "\n".join(message)
+  sys.exit(1)
+  
+def warn(*message):
+  print >>sys.stderr, "warning:", "\nwarning: ".join(message), "\n"
+
+def check_dependencies():
+  i = 0
+
+  check_zope()
+  check_twisted()
+  check_win32()
+  i+=check_json()
+  i+=check_java()
+  i+=check_hg()
+  
+  print "0 errors, %d warnings." % i
+  
+  if i == 0:
+    print "looks like you've got everything you need to run qwebirc!"
+  else:
+    print "you can run qwebirc despite these."
+
+  f = open(".checked", "w")
+  f.close()
+  
+def check_win32():
+  if not sys.platform.startswith("win"):
+    return
+  
+  try:
+    import win32con
+  except ImportError:
+    fail("qwebirc requires pywin32, see:", "http://sourceforge.net/project/showfiles.php?group_id=78018")
+  
+def check_java():
+  def java_warn(specific):
+    warn(specific, "java is not required, but allows qwebirc to compress output,", "making it faster to download.", "you can get java at http://www.java.com/")
+    
+  try:
+    p = subprocess.Popen(["java", "-version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=os.name == "nt")
+    p.communicate()
+    if p.wait() != 0:
+      java_warn("something went wrong looking for java.")
+      return 1
+  except: # ugh
+    java_warn("couldn't find java.")
+    return 1
+    
+  return 0
+  
+def check_hg():
+  def hg_warn(specific):
+    warn(specific, "mercurial (hg) is not required, but allows qwebirc to save bandwidth by versioning.", "you can get hg at http://www.selenic.com/mercurial/")
+    
+  try:
+    p = subprocess.Popen(["hg", "id"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=os.name == "nt")
+    p.communicate()
+    if p.wait() != 0:
+      hg_warn("something went wrong looking for mercurial.")
+      return 1
+  except: # ugh
+    hg_warn("couldn't find mercurial.")
+    return 1
+    
+  return 0
+  
+def check_zope():
+  try:
+    from zope.interface import Interface
+  except ImportError:
+    if sys.platform.startswith("win"):
+      fail("qwebirc requires zope interface",
+           "see pypi: http://pypi.python.org/pypi/zope.interface")
+    else:
+      fail("qwebirc requires zope interface.",
+           "this should normally come with twisted, but can be downloaded",
+           "from pypi: http://pypi.python.org/pypi/zope.interface")
+           
+def check_twisted():
+  try:
+    import twisted
+  except ImportError:
+    fail("qwebirc requires twisted (at least 8.2.0), see http://twistedmatrix.com/")
+
+  def twisted_fail(x, y=None):
+    fail("you don't seem to have twisted's %s module." % x,
+         "your distro is most likely modular, look for a twisted %s package%s." % (x, " %s" % y if y else "",))
+
+  try:
+    import twisted.names
+  except ImportError:
+    twisted_fail("names")
+
+  try:
+    import twisted.mail
+  except ImportError:
+    twisted_fail("mail")
+
+  try:
+    import twisted.web
+  except ImportError:
+    twisted_fail("web", "(not web2)")
+
+  try:
+    import twisted.words
+  except ImportError:
+    twisted_fail("words")
+    
+def check_json():
+  import qwebirc.util.qjson
+  if qwebirc.util.qjson.slow:
+    warn("simplejson module with C speedups not installed.",
+         "using embedded module (slower); consider installing simplejson from:",
+         "http://pypi.python.org/pypi/simplejson/")
+    return 1
+  return 0
+  
+if __name__ == "__main__":
+  import dependencies
+  dependencies.check_dependencies()
index 2a6dc882030ff4dd785ff7352423d7f301761bf0..536255cdb8c53b450487f896b59d60081119132f 100644 (file)
-/*\r
-\r
-CSS for Mocha UI\r
-\r
-Copyright:\r
-       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.\r
-\r
-License:\r
-       MIT-style license.\r
-\r
-Note:  \r
-       If you are adding MochaUI to an existing site, this CSS file may not be necessary.\r
-\r
-*/\r
-\r
-\r
-/* Reset */\r
-\r
-ul,ol,dl,li,dt,dd,h1,h2,h3,h4,h5,h6,pre,form,body,html,p,blockquote,fieldset,input,object,iframe { margin: 0; padding: 0; }\r
-a img,:link img,:visited img { border: none; }\r
-table { border-collapse: collapse; border-spacing: 0; }\r
-:focus { outline: none; }\r
-\r
-/* Structure */\r
-\r
-body {\r
-       text-align: left;\r
-       font-family: Arial, Helvetica, sans-serif;\r
-       font-size: 12px;\r
-       line-height: 18px;\r
-       color: #3f3f3f; \r
-}\r
-\r
-/* Typography */\r
-       \r
-h2, h3, h4 {\r
-       margin: 0;\r
-       padding: 0 0 5px 0;\r
-       font-size: 12px;\r
-       font-weight: bold;\r
-}\r
-       \r
-h2 {\r
-       font-size: 14px;\r
-       font-weight: bold;\r
-}\r
-       \r
-#mochaPage h3 {\r
-       display: block;\r
-       font-size: 12px;\r
-       padding: 6px 0 6px 0;\r
-       margin: 0 0 8px 0;\r
-       border-bottom: 1px solid #bbb;\r
-}\r
-\r
-h4 {\r
-       font-size: 11px;\r
-}\r
-\r
-a {\r
-       color: #098ED1;\r
-       text-decoration: none;\r
-       cursor: pointer;\r
-}\r
-\r
-a:hover {\r
-       text-decoration: none;\r
-}\r
-\r
-p {\r
-       margin: 0;\r
-       padding: 0 0 9px 0;\r
-}\r
-\r
-.lite {\r
-       color: #999;\r
-}\r
-\r
-/* List Elements */\r
-\r
-ul {\r
-       list-style: outside;\r
-       margin: 0 0 9px 16px;\r
-       list-style-type: disc;\r
-}\r
-\r
-dt {\r
-       font-weight: bold;\r
-}\r
-\r
-dd {\r
-       padding: 0 0 9px 0;\r
-}\r
-\r
-/* Forms */\r
-\r
-textarea, input {\r
-       font-family: Arial, Helvetica, sans-serif;\r
-       font-size: 12px;                \r
-}      \r
-\r
-/* Code */\r
-\r
-pre {\r
-       background-color: #f6f6f6;\r
-       color: #060;\r
-       display: block;\r
-       font-family: 'Courier New', Courier, monospace;\r
-       font-size: 11px;\r
-       max-height: 250px;\r
-       overflow: auto;\r
-       margin: 0 0 10px 0;\r
-       padding: 0 10px 10px 10px;\r
-       border: 1px solid #d1d7dc;      \r
-}\r
-\r
-/* Dividers */\r
-\r
-hr {\r
-       background-color: #ddd;\r
-       color: #ccc;\r
-       height: 1px;\r
-       border: 0px;\r
+/*
+
+CSS for Mocha UI
+
+Copyright:
+       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.
+
+License:
+       MIT-style license.
+
+Note:  
+       If you are adding MochaUI to an existing site, this CSS file may not be necessary.
+
+*/
+
+
+/* Reset */
+
+ul,ol,dl,li,dt,dd,h1,h2,h3,h4,h5,h6,pre,form,body,html,p,blockquote,fieldset,input,object,iframe { margin: 0; padding: 0; }
+a img,:link img,:visited img { border: none; }
+table { border-collapse: collapse; border-spacing: 0; }
+:focus { outline: none; }
+
+/* Structure */
+
+body {
+       text-align: left;
+       font-family: Arial, Helvetica, sans-serif;
+       font-size: 12px;
+       line-height: 18px;
+       color: #3f3f3f; 
+}
+
+/* Typography */
+       
+h2, h3, h4 {
+       margin: 0;
+       padding: 0 0 5px 0;
+       font-size: 12px;
+       font-weight: bold;
+}
+       
+h2 {
+       font-size: 14px;
+       font-weight: bold;
+}
+       
+#mochaPage h3 {
+       display: block;
+       font-size: 12px;
+       padding: 6px 0 6px 0;
+       margin: 0 0 8px 0;
+       border-bottom: 1px solid #bbb;
+}
+
+h4 {
+       font-size: 11px;
+}
+
+a {
+       color: #098ED1;
+       text-decoration: none;
+       cursor: pointer;
+}
+
+a:hover {
+       text-decoration: none;
+}
+
+p {
+       margin: 0;
+       padding: 0 0 9px 0;
+}
+
+.lite {
+       color: #999;
+}
+
+/* List Elements */
+
+ul {
+       list-style: outside;
+       margin: 0 0 9px 16px;
+       list-style-type: disc;
+}
+
+dt {
+       font-weight: bold;
+}
+
+dd {
+       padding: 0 0 9px 0;
+}
+
+/* Forms */
+
+textarea, input {
+       font-family: Arial, Helvetica, sans-serif;
+       font-size: 12px;                
+}      
+
+/* Code */
+
+pre {
+       background-color: #f6f6f6;
+       color: #060;
+       display: block;
+       font-family: 'Courier New', Courier, monospace;
+       font-size: 11px;
+       max-height: 250px;
+       overflow: auto;
+       margin: 0 0 10px 0;
+       padding: 0 10px 10px 10px;
+       border: 1px solid #d1d7dc;      
+}
+
+/* Dividers */
+
+hr {
+       background-color: #ddd;
+       color: #ccc;
+       height: 1px;
+       border: 0px;
 }
\ No newline at end of file
index b37f8a105a82ac33f90ab1ee55a59f7a697e1ccc..96a7a9f7807275544218cd109508984911f51e66 100644 (file)
-/*\r
-\r
-CSS for Mocha UI\r
-\r
-Copyright:\r
-       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.\r
-       \r
-License:\r
-       MIT-style license.\r
-\r
-*/\r
-\r
-/* Layout\r
----------------------------------------------------------------- */\r
-\r
-html, body {\r
-       overflow: hidden;\r
-}\r
-\r
-body {\r
-       margin: 0; /* Required */\r
-       background: #fff; /* #6989b3 url(../../images/mochaui/splash.gif) center center no-repeat; */\r
-}\r
-\r
-#desktop {\r
-       visibility: hidden; \r
-       position: relative;\r
-       min-width: 750px; /* Helps keep header content from wrapping */\r
-       height: 100%;\r
-       overflow: hidden;\r
-       cursor: default; /* Fix for issue in IE7. IE7 wants to use the I-bar text cursor */\r
-}\r
-\r
-#desktopHeader {\r
-       background: #f1f1f1;\r
-}\r
-\r
-#desktopTitlebarWrapper {\r
-       position: relative;     \r
-       height: 45px;\r
-       overflow: hidden;\r
-       background: #333 url(../../images/mochaui/bg-header.gif) repeat-x;\r
-}\r
-\r
-#desktopTitlebar {\r
-       padding: 7px 8px 6px 8px;\r
-       height: 32px;\r
-       background: url(../../images/mochaui/logo.gif) no-repeat;\r
-       background-position: left 0;\r
-}\r
-\r
-#desktopTitlebar h1.applicationTitle {\r
-       display: none;\r
-       margin: 0;\r
-       padding: 0 5px 0 0;\r
-       font-size: 20px;\r
-       line-height: 25px;\r
-       font-weight: bold;\r
-       color: #fff;\r
-}\r
-\r
-#desktopTitlebar h2.tagline {\r
-       font-size: 12px;\r
-       color: #b2b2b2;\r
-       font-weight: bold;\r
-       padding: 5px 0 0 0;\r
-       text-align: center;\r
-}\r
-\r
-#desktopTitlebar h2.tagline .taglineEm {\r
-       color: #fff;\r
-       font-weight: bold;\r
-}\r
-\r
-#topNav {\r
-       font-family: Verdana, Arial, Helvetica, sans-serif;\r
-       font-size: 10px;\r
-       position: absolute;\r
-       right: 0;\r
-       top: 0;\r
-       color: #b2b2b2;\r
-       text-align: right;\r
-       padding: 13px 10px 0 0;\r
-}\r
-\r
-#topNav a {\r
-       color: #7DD0FA;\r
-       font-weight: normal;\r
-}\r
-\r
-#topNav a:hover {\r
-       text-decoration: none;\r
-}\r
-\r
-/* Toolboxes */\r
-\r
-.toolbox {\r
-       float: right;\r
-       padding: 6px 3px 0 5px;\r
-       height: 23px;\r
-       overflow: hidden;\r
-}\r
-\r
-div.toolbox.divider { /* Have to specify div here for IE6's sake */\r
-       background: url(../../images/mochaui/toolbox-divider.gif) left center no-repeat;\r
-       padding: 6px 3px 0 12px;\r
-}\r
-\r
-div.toolbox.divider2 { /* Have to specify div here for IE6's sake */\r
-       background: url(../../images/mochaui/toolbox-divider2.gif) left center no-repeat;\r
-       padding: 6px 4px 0 12px;\r
-}\r
-\r
-.toolbox img {\r
-       cursor: pointer;\r
-       margin-right: 6px;\r
-       padding: 0;\r
-       float: left;\r
-}\r
-\r
-.toolbox img.disabled {\r
-       cursor: default;\r
-}\r
-\r
-#spinnerWrapper {\r
-       width: 16px;\r
-       height: 16px;\r
-       background: url(../../images/mochaui/spinner-placeholder.gif) no-repeat;\r
-       margin-right: 5px;\r
-}\r
-\r
-#spinner {\r
-       visibility: hidden;\r
-       background: url(../../images/mochaui/spinner.gif) no-repeat;\r
-       width: 16px;\r
-       height: 16px;\r
-}\r
-\r
-/* Navbar */\r
-\r
-#desktopNavbar {\r
-       background: #4C4C4C;\r
-       height: 30px;\r
-       margin: 0 0px;  \r
-}\r
-\r
-#desktopNavbar ul {    \r
-       padding: 0;\r
-       margin: 0;\r
-       list-style: none;\r
-       font-size: 12px;\r
-}\r
-\r
-#desktopNavbar li {\r
-       float: left;\r
-}\r
-\r
-#desktopNavbar a {\r
-       display: block;\r
-}      \r
-       \r
-#desktopNavbar ul li a {\r
-       padding: 6px 10px 6px 10px;     \r
-       color: #b2b2b2;\r
-       font-weight: normal;\r
-}\r
-\r
-#desktopNavbar ul li a:hover {\r
-       color: #fff;\r
-}\r
-\r
-#desktopNavbar ul li a.arrow-right, #desktopNavbar ul li a:hover.arrow-right {\r
-       background-image: url(../../images/mochaui/arrow-right.gif);\r
-       background-repeat: no-repeat;\r
-       background-position: right 7px; \r
-} \r
-\r
-#desktopNavbar li ul {\r
-       padding: 2px;\r
-       border: 1px solid #3f3f3f;\r
-       background: #fff url(../../images/mochaui/bg-dropdown.gif) repeat-y;\r
-       position: absolute;\r
-       width: 164px;\r
-       left: -999em;\r
-       z-index: 8000;\r
-}\r
-\r
-#desktopNavbar li:hover ul ul,\r
-#desktopNavbar li.ieHover ul ul,\r
-#desktopNavbar li:hover ul ul ul,\r
-#desktopNavbar li.ieHover ul ul ul {\r
-       left: -999em;\r
-}\r
-\r
-#desktopNavbar li ul ul { /* third-and-above-level lists */\r
-       margin: -22px 0 0 164px;\r
-}\r
-\r
-#desktopNavbar li ul li .check {\r
-       position: absolute;\r
-       top: 8px;\r
-       left: 6px;\r
-       width: 5px;\r
-       height: 5px;\r
-       background: #555;\r
-       overflow: hidden;\r
-       line-height: 1px;\r
-       font-size: 1px;\r
-}\r
-\r
-#desktopNavbar li ul li a {\r
-       position: relative;\r
-       padding: 1px 9px 1px 25px;\r
-       width: 130px;\r
-       color: #3f3f3f;\r
-       font-weight: normal;\r
-}\r
-\r
-#desktopNavbar li ul li a:hover {\r
-       background: #6C98D9;\r
-       color: #fff;\r
-}\r
-\r
-#desktopNavbar li ul li a:hover .check {\r
-       background: #fff;\r
-}\r
-\r
-#desktopNavbar li:hover ul,\r
-#desktopNavbar li.ieHover ul,\r
-#desktopNavbar li li.ieHover ul,\r
-#desktopNavbar li li li.ieHover ul,\r
-#desktopNavbar li li:hover ul,\r
-#desktopNavbar li li li:hover ul { /* lists nested under hovered list items */\r
-       left: auto;\r
-}\r
-\r
-#desktopNavbar li:hover { /* For IE7 */\r
-    position: static;\r
-}\r
-\r
-li.divider {\r
-       margin-top: 2px;\r
-       padding-top: 3px;       \r
-       border-top: 1px solid #ebebeb;\r
-}      \r
-\r
-#pageWrapper {\r
-       position: relative;\r
-       overflow: hidden; /* This can be set to hidden or auto */\r
-       border-top: 1px solid #222;\r
-}\r
-\r
-/* Footer */\r
-#desktopFooterWrapper {\r
-       position: absolute;\r
-       left: 0;\r
-       bottom: 0;\r
-       width: 100%;\r
-       height: 30px;\r
-       overflow: hidden;\r
-       border-top: 1px solid #222;\r
-}\r
-\r
-#desktopFooter {\r
-       font-family: Verdana, Arial, Helvetica, sans-serif;\r
-       font-size: 10px;\r
-       height: 24px;\r
-       padding: 6px 8px 0 8px;\r
-       background: #333;\r
-       color: #b2b2b2;\r
-}\r
-\r
-#desktopFooter a {\r
-       color: #7DD0FA;\r
-       font-weight: normal;\r
-}\r
-\r
-#desktopFooter a:hover {\r
-       text-decoration: none;\r
-}\r
-\r
-/* Dock/Taskbar */\r
-\r
-#dockWrapper {\r
-       display: none;\r
-       width: 100%;\r
-       border-top: 1px solid #222;\r
-}\r
-\r
-#dockWrapper.top {\r
-       border: 0;\r
-}\r
-\r
-#dock {\r
-       position: relative;\r
-       padding: 3px 16px 0 6px;\r
-       bottom: 0;\r
-       left: 0;\r
-       background: #4c4c4c;\r
-       min-height: 27px;\r
-       height: auto;\r
-}\r
-\r
-*html #dock {\r
-       height: 30px; /* Used for IE 6.0 since it does not support min-height */\r
-}\r
-\r
-.dockTab {\r
-       float: left;\r
-       position: relative;     \r
-       font-size: 11px;\r
-       width: 150px;\r
-       height: 24px;\r
-       margin: 0 3px 2px 0;\r
-       overflow: hidden;       \r
-       cursor: pointer;\r
-       background: url(../../images/mochaui/dock-tabs.gif) left top no-repeat;\r
-}\r
-\r
-.dockTab.activeDockTab {       \r
-       background-position: left -24px;\r
-}\r
-\r
-.dockText {\r
-       position: absolute;\r
-       top: 0;\r
-       left: 0;\r
-       display: block;\r
-       font-weight: normal;\r
-       color: #bbb;    \r
-       text-align: left;\r
-       padding: 4px 10px 2px 10px;\r
-       width: 130px;\r
-       height: 19px;\r
-       overflow: hidden;\r
-}\r
-\r
-.dockText:hover {\r
-       color: #fff;\r
-}\r
-\r
-.dockTab.activeDockTab .dockText {\r
-       color: #fff;\r
-}\r
-\r
-#dockCanvas {\r
-       position: absolute;\r
-       top: 5px;\r
-       right: 3px;\r
-       z-index: 2;\r
-}\r
-\r
-#dockPlacement {\r
-       position: absolute;\r
-       top: 4px;\r
-       right: 8px;\r
-       width: 10px;\r
-       height: 9px;\r
-       opacity: 0;\r
-       filter: alpha(opacity=0);\r
-       -moz-opacity: 0;\r
-       background: #f00; /* for troubleshooting */\r
-       cursor: pointer;\r
-       z-index: 3; /* for IE */\r
-       text-align: right;\r
-}\r
-\r
-#dockAutoHide {\r
-       position: absolute;\r
-       top: 14px;\r
-       right: 8px;\r
-       width: 10px;\r
-       height: 9px;\r
-       opacity: 0;\r
-       filter: alpha(opacity=0);\r
-       -moz-opacity: 0;\r
-       background: #f00; /* for troubleshooting */\r
-       cursor: pointer;\r
-       z-index: 3; /* for IE */\r
-}\r
-\r
-/* Panel Layout\r
----------------------------------------------------------------- */\r
-\r
-/* Columns */\r
-\r
-.column {\r
-       position: relative;     \r
-       float: left;\r
-       overflow: hidden;\r
-       background: #f1f1f1;\r
-}\r
-\r
-/* Panels */\r
-\r
-.panel {\r
-       position: relative;     \r
-       overflow: auto;\r
-       border-bottom: 1px solid #b9b9b9;\r
-       border-top: 0;\r
-}\r
-\r
-.pad {\r
-       position: absolute;\r
-       top: 0;\r
-       left: 0;\r
-       padding: 8px;\r
-       overflow: hidden;\r
-}\r
-\r
-#mainPanel {\r
-       background: #fff;       \r
-}      \r
-\r
-.panel-header {\r
-       position: relative;     \r
-       background: #f1f1f1 url(../../images/mochaui/bg-panel-header.gif) repeat-x;\r
-       height: 30px;\r
-       overflow: hidden;\r
-       border-bottom: 1px solid #d3d3d3;               \r
-}\r
-\r
-.panel-headerContent {\r
-       padding-top: 2px;\r
-}      \r
-\r
-.panel-headerContent.tabs {\r
-       background: url(../../images/mochaui/tabs.gif) repeat-x;\r
-       background-position: left -68px;        \r
-}\r
-\r
-.panel-header h2 {\r
-       display: inline-block;\r
-       font-size: 12px;\r
-       margin: 0;\r
-       padding: 3px 8px 0 8px;\r
-       height: 22px;\r
-       overflow: hidden;\r
-       color: #3f3f3f;\r
-}\r
-\r
-.panel-header-toolbox {\r
-       float: right;\r
-       height: 26px;\r
-       margin: 2px 5px 5px 0;\r
-       text-align: right;\r
-}\r
-\r
-.panel-collapse {\r
-       background: url(../../images/mochaui/collapse-expand.gif) left top no-repeat;\r
-}\r
-\r
-.panel-expand {\r
-       background: url(../../images/mochaui/collapse-expand.gif) left -16px no-repeat;\r
-}\r
-\r
-.icon16 {\r
-       margin: 5px 0 0 2px;\r
-       cursor: pointer;\r
-}\r
-\r
-.panel-footerWrapper {\r
-       position: absolute;\r
-       left: 0;\r
-       bottom: 0;\r
-       width: 100%;\r
-       background: #f9f9f9;\r
-       height: 30px;\r
-       overflow: hidden;\r
-       border-top: 1px solid #b9b9b9;\r
-}\r
-\r
-.panel-footer {\r
-       padding: 1px 0 0 8px;   \r
-}\r
-\r
-.panel-footerContent {\r
-       margin-top: 5px;\r
-}\r
-\r
-/* Handles */  \r
-\r
-.horizontalHandle {\r
-       height: 4px;\r
-       line-height: 1px;\r
-       font-size: 1px;\r
-       overflow: hidden;\r
-       background: #d1d1d1 url(../../images/mochaui/bg-handle-horizontal.gif) repeat-x;\r
-}\r
-\r
-.horizontalHandle.detached .handleIcon {\r
-       background: transparent;        \r
-}\r
-\r
-.horizontalHandle .handleIcon {        \r
-       margin: 0 auto;\r
-       height: 4px;\r
-       line-height: 1px;\r
-       font-size: 1px;\r
-       overflow: hidden;\r
-       background: url(../../images/mochaui/handle-icon-horizontal.gif) center center no-repeat;\r
-}\r
-\r
-.columnHandle {\r
-       min-height: 10px;       \r
-       float: left;\r
-       width: 4px;\r
-       overflow: hidden;\r
-       background: #bbb url(../../images/mochaui/handle-icon.gif) center center no-repeat;\r
-       border: 1px solid #9a9a9a;\r
-       border-top: 0;\r
-}\r
-\r
-/* Viewport overlays\r
----------------------------------------------------------------- */\r
-\r
-#modalOverlay {\r
-       display: none;\r
-       position: fixed;\r
-       top: 0;\r
-       left: 0;\r
-       width: 100%;\r
-       background: #000;\r
-       opacity: 0;\r
-       filter: alpha(opacity=0);\r
-       -moz-opacity: 0;\r
-       z-index: 10000;\r
-}\r
-\r
-* html         #modalOverlay {\r
-       position: absolute;\r
-}\r
-\r
-/* Fix for IE6 select z-index issue */\r
-#modalFix {\r
-       display: none;\r
-       position: absolute;\r
-       top: 0;\r
-       left: 0;\r
-       width: 100%;\r
-       opacity: 0;\r
-       filter: alpha(opacity=0);\r
-       -moz-opacity: 0;\r
-       z-index: 9999;\r
-}\r
-\r
-/* Underlay */\r
-\r
-#windowUnderlay { \r
-       position: fixed;\r
-       top: 0;\r
-       left: 0;\r
-       width: 100%;\r
-       background: #fff;       \r
-}\r
-\r
-* html #windowUnderlay { \r
-       position: absolute;\r
-}\r
-\r
-/* Windows\r
----------------------------------------------------------------- */\r
-\r
-.mocha {\r
-       position: absolute;\r
-       top: 0;\r
-       left: 0;\r
-       display: none;\r
-       overflow: hidden;       \r
-}\r
-\r
-.mocha.isFocused {     \r
-}      \r
-\r
-.mochaOverlay {\r
-       position: absolute;\r
-       top: 0;\r
-       left: 0;        \r
-}\r
-       \r
-.mochaTitlebar {\r
-       width: 100%;\r
-       overflow: hidden;       \r
-}\r
-\r
-.mochaTitlebar h3 {\r
-       font-size: 12px;\r
-       line-height: 15px;      \r
-       font-weight: bold;\r
-       margin: 5px 10px 4px 12px;\r
-       padding: 0;\r
-       color: #888;\r
-}\r
-\r
-.mocha.isFocused .mochaTitlebar h3 {\r
-       color: #141414;\r
-}\r
-\r
-.mochaToolbarWrapper {\r
-       width: 100%; /* For IE */\r
-       position: relative;\r
-       height: 29px;\r
-       background: #f1f1f1;    \r
-       overflow: hidden;\r
-       border-top: 1px solid #d9d9d9;\r
-}\r
-\r
-div.mochaToolbarWrapper.bottom {\r
-       border: 0;\r
-       border-bottom: 1px solid #d9d9d9;\r
-}\r
-\r
-.mochaToolbar {\r
-       width: 100%; /* For IE */\r
-       border-top: 1px solid #fff;\r
-}\r
-\r
-.mochaContentBorder {\r
-       border-top: 1px solid #dadada;\r
-       border-bottom: 1px solid #dadada;\r
-}\r
-\r
-.mochaContentWrapper { /* Has a fixed height and scrollbars if required. */\r
-       font-size: 12px;\r
-       overflow: auto;\r
-}\r
-       \r
-.mochaContent {\r
-       padding: 10px 12px;\r
-}\r
-\r
-.mocha .handle {\r
-       position: absolute;\r
-       background: #0f0;\r
-       width: 3px;\r
-       height: 3px;\r
-       z-index: 2;\r
-       opacity: .0;\r
-       filter: alpha(opacity=0);\r
-       -moz-opacity: .0;\r
-       overflow: hidden;\r
-       font-size: 1px; /* For IE6 */\r
-}\r
-\r
-.mocha .corner { /* Corner resize handles */\r
-       background: #f00;\r
-       width: 10px;\r
-       height: 10px;\r
-}\r
-\r
-.mocha .cornerSE { /* Bottom right resize handle */\r
-       background: #f00;\r
-       width: 20px;\r
-       height: 20px;\r
-}\r
-\r
-.mochaCanvasHeader {\r
-       position: absolute;\r
-       top: 0;\r
-       left: 0;\r
-       background: transparent;\r
-       z-index: -1;\r
-       display: none;\r
-       overflow: hidden;\r
-}\r
-\r
-.mochaControls {\r
-       position: absolute;\r
-       width: 52px;\r
-       top: 8px;\r
-       right: 8px;\r
-       height: 14px;\r
-       z-index: 4;\r
-       background: transparent;\r
-}\r
-\r
-.mochaCanvasControls {\r
-       position: absolute;     \r
-       top: 8px;\r
-       right: 8px;     \r
-       z-index: 3;\r
-       background: transparent;\r
-}\r
-\r
-/*\r
-       To use images for these buttons:\r
-       1. Set the useCanvasControls window option to false.\r
-       2. If you use a different button size you may need to reposition the controls.\r
-          Modify the controlsOffset window option.             \r
-       2. Add background images to each button.\r
-\r
-*/\r
-.mochaMinimizeButton, .mochaMaximizeButton, .mochaCloseButton {\r
-       float: right;\r
-       width: 14px;\r
-       height: 14px;\r
-       font-size: 1px; \r
-       cursor: pointer;\r
-       z-index: 4;\r
-       background: #f00;\r
-       margin-left: 5px;\r
-}\r
-\r
-.mochaMinimizeButton {\r
-       margin-left: 0;\r
-}\r
-\r
-.mochaMaximizeButton {\r
-}\r
-\r
-.mochaCloseButton {\r
-}\r
-\r
-.mochaSpinner{\r
-       visibility: hidden;     \r
-       position: absolute;\r
-       bottom: 7px;\r
-       left: 6px;\r
-       width: 16px;\r
-       height: 16px;\r
-       background: url(../../images/mochaui/spinner.gif) no-repeat;\r
-}\r
-\r
-.mochaIframe {\r
-       width: 100%;\r
-}  \r
-               \r
-/* Fix for IE6 select z-index issue */\r
-.zIndexFix {\r
-       display: block;\r
-       position: absolute;\r
-       top: 0;\r
-       left: 0;\r
-       z-index: -1;\r
-       filter: mask();\r
-       width: 100px;\r
-       height: 100px;\r
-       border: 1px solid transparent;\r
-}\r
-\r
-/* Modals */\r
-\r
-.modal2 {\r
-       border: 8px solid #fff;         \r
-}\r
-\r
-.modal2 .mochaContentBorder {\r
-       border-width: 0px;\r
-}\r
-       \r
-/* Window Themes */\r
-\r
-.mocha.no-canvas {\r
-       background: #f1f1f1;\r
-       border: 2px solid #555; \r
-}\r
-\r
-.mocha.no-canvas .mochaTitlebar {\r
-       background: #f1f1f1;    \r
-}\r
-\r
-.mocha.transparent .mochaTitlebar h3 {\r
-       color: #fff;\r
-       display: none;\r
-}\r
-\r
-.mocha.notification .mochaTitlebar {\r
-       opacity: .0;\r
-       filter: alpha(opacity=0);\r
-       -moz-opacity: 0;\r
-}\r
-\r
-.mocha.notification .mochaContentBorder {\r
-       border-width: 0px;\r
-}\r
-\r
-.mocha.notification .mochaContentWrapper {\r
-       text-align: center;\r
-       font-size: 12px;\r
-       font-weight: bold;\r
-}\r
-\r
-/* Compontents\r
----------------------------------------------------------------- */\r
-\r
-/* Toolbar Tabs */\r
-\r
-.toolbarTabs { \r
-       padding: 0 5px 2px 2px;\r
-       background: url(../../images/mochaui/tabs.gif) repeat-x;\r
-       background-position: left -70px;\r
-       overflow: visible;\r
-}\r
-\r
-.tab-menu {    \r
-       padding-top: 1px;\r
-       list-style:     none;\r
-       margin: 0;\r
-       padding: 0;\r
-       line-height: 16px;\r
-       font-size: 11px;\r
-}\r
-\r
-.tab-menu li {\r
-       display: block;\r
-       float: left;\r
-       margin: 0 0 5px 0;\r
-       cursor: pointer;        \r
-       background: url(../../images/mochaui/tabs.gif) repeat-x;\r
-       background-position: left -35px;\r
-}\r
-\r
-.tab-menu li.selected {\r
-       background: url(../../images/mochaui/tabs.gif) repeat-x;\r
-       background-position: left 0;\r
-}\r
-\r
-.tab-menu li a {\r
-       display: block;\r
-       margin-left: 8px;\r
-       padding: 6px 16px 5px 10px;\r
-       text-align: center;\r
-       font-weight: normal;\r
-       color: #141414;\r
-       background: url(../../images/mochaui/tabs.gif) repeat-x;\r
-       background-position: right -35px;       \r
-}\r
-\r
-.tab-menu li.selected a {\r
-       color: #141414;\r
-       font-weight: bold;      \r
-       background: url(../../images/mochaui/tabs.gif) repeat-x;\r
-       background-position: right 0;\r
-}\r
-\r
-/* Accordian */\r
-\r
-.accordianWrapper {\r
-       padding: 0;\r
-       background: #fff;\r
-}\r
-\r
-.accordianToggler {\r
-       margin: 0;\r
-       padding: 6px 10px;\r
-       background: #f1f1f1 url(../../images/mochaui/bg-panel-header.gif) repeat-x;\r
-       font-size: 12px;\r
-       cursor: pointer;\r
-       border-top: 1px solid #e3e3e3;  \r
-}\r
-\r
-.topToggler {\r
-       border-top: none;\r
-}\r
-\r
-.accordianToggler.open {\r
-       background: #fff url(../../images/mochaui/bg-panel-header.gif) repeat-x;        \r
-}\r
-\r
-.accordianContent {\r
-       padding: 10px 10px 5px 10px;\r
-}\r
-\r
-/* Sliders */\r
-\r
-.slider {\r
-       clear: both;\r
-       position: relative;\r
-       font-size: 12px;\r
-       font-weight: bold;\r
-       width: 200px;\r
-       margin-bottom: 15px;    \r
-}\r
-\r
-.sliderWrapper {\r
-       position: relative;\r
-       font-size: 1px;\r
-       line-height: 1px;\r
-       height: 9px;\r
-       width: 222px;\r
-}\r
-\r
-.sliderarea {\r
-       position: absolute;\r
-       top: 0;\r
-       left: 0;\r
-       height: 7px;\r
-       width: 220px;\r
-       font-size: 1px;\r
-       line-height: 1px;\r
-       background: url(../../images/mochaui/slider-area.gif) repeat-x;\r
-       border: 1px solid #a3a3a3;\r
-       border-bottom: 1px solid #ccc;\r
-       border-left: 1px solid #ccc;\r
-       margin: 0;\r
-       padding: 0;\r
-       overflow: hidden;\r
-}\r
\r
-.sliderknob {\r
-       position: absolute;\r
-       top: 0;\r
-       left: 0;\r
-       height: 9px;\r
-       width: 19px;\r
-       font-size: 1px;\r
-       line-height: 1px;       \r
-       background: url(../../images/mochaui/knob.gif) no-repeat;\r
-       cursor: pointer;\r
-       overflow: hidden;\r
-       z-index: 2;\r
-}\r
-       \r
-.update {\r
-       padding-bottom: 5px;    \r
-}\r
-\r
-/* Folder Tree */\r
-\r
-.tree {\r
-       font-size: 11px;\r
-       line-height: 15px;\r
-       margin: 0;\r
-}\r
-\r
-.tree ul {\r
-       margin: 0;\r
-}\r
-\r
-.tree li {\r
-       list-style-type: none;\r
-       white-space: nowrap;\r
-}\r
-\r
-.tree li a {\r
-       color: #3f3f3f; \r
-}      \r
-\r
-.tree li img {\r
-       vertical-align: middle;\r
-       width: 18px;\r
-       height: 18px;\r
-       overflow: hidden;\r
-}\r
-\r
-.tree li span {\r
-       padding-left: 2px;\r
-}\r
-\r
-/* View Toggle */\r
-\r
-.viewToggle {\r
-       position: absolute;\r
-       top: 4px;\r
-       right: 5px;\r
-       width: 60px;\r
-       text-align: right;\r
-}\r
-\r
-.viewToggle img.viewToggleList, .viewToggle img.viewToggleGrid {\r
-       width: 28px;\r
-       height: 22px;\r
-}      \r
-\r
-.viewToggle img.viewToggleList {\r
-       background: url(../../images/mochaui/view-toggle.gif) no-repeat;\r
-       background-position: 0 -66px;\r
-}\r
-\r
-.viewToggle img.viewToggleGrid {\r
-       background: url(../../images/mochaui/view-toggle.gif) no-repeat;\r
-       background-position: 0 0;\r
-}\r
-\r
-/* Miscellaneous\r
----------------------------------------------------------------- */\r
-\r
-/* Window Builder Form Elements */\r
-       \r
-#desktop form {\r
-       margin: 0 0 0 0;\r
-       padding: 5px 0 0 0;\r
-}\r
-\r
-#newWindowForm {\r
-       width: 320px;\r
-}\r
-\r
-#desktop .input {\r
-       width: 225px;\r
-       padding: 1px 0 1px 3px;\r
-       border: 1px solid #bbb; \r
-}\r
-\r
-#desktop textarea {\r
-       width: 225px;\r
-       height: 100px;\r
-       padding: 1px 0 1px 3px;\r
-       border: 1px solid #bbb;\r
-}\r
-\r
-#desktop .formLabel {\r
-       float: left;    \r
-       text-align: right;\r
-       width: 80px;\r
-       margin: 0 0 5px 0;\r
-}\r
-\r
-#desktop .formField {\r
-       float: right;\r
-       margin: 0 0 5px 0;\r
-       padding: 0 0 0 0;\r
-       width: 230px;\r
-}\r
-\r
-#desktop form .number {\r
-       width: 40px;\r
-}\r
-\r
-/* Menus */\r
-\r
-.menu-right li {\r
-       list-style-type: none;\r
-       display: inline;        \r
-       margin: 0 0 0 15px;\r
-}\r
-\r
-/* Notifications */\r
-\r
-/* Success, error & notice boxes for messages and errors. */\r
-.error,\r
-.notice, \r
-.success    { padding: 8px; margin-bottom: 10px; border: 2px solid #ddd; }\r
-.error      { background: #FBE3E4; color: #D12F19; border-color: #FBC2C4; }\r
-.notice     { background: #FFF6BF; color: #817134; border-color: #FFD324; }\r
-.success    { background: #E6EFC2; color: #529214; border-color: #C6D880; }\r
-.error a    { color: #D12F19; }\r
-.notice a   { color: #817134; }\r
-.success a  { color: #529214; }\r
-\r
-\r
-/* Clears */\r
-\r
-.clear {\r
-       clear: both;\r
-       height: 0;\r
-}\r
-\r
-*html .clear {\r
-       height: 1%;\r
-       font-size: 1px;\r
-       line-height: 1px;\r
-       overflow: hidden;\r
-       visibility: hidden;\r
+/*
+
+CSS for Mocha UI
+
+Copyright:
+       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.
+       
+License:
+       MIT-style license.
+
+*/
+
+/* Layout
+---------------------------------------------------------------- */
+
+html, body {
+       overflow: hidden;
+}
+
+body {
+       margin: 0; /* Required */
+       background: #fff; /* #6989b3 url(../../images/mochaui/splash.gif) center center no-repeat; */
+}
+
+#desktop {
+       visibility: hidden; 
+       position: relative;
+       min-width: 750px; /* Helps keep header content from wrapping */
+       height: 100%;
+       overflow: hidden;
+       cursor: default; /* Fix for issue in IE7. IE7 wants to use the I-bar text cursor */
+}
+
+#desktopHeader {
+       background: #f1f1f1;
+}
+
+#desktopTitlebarWrapper {
+       position: relative;     
+       height: 45px;
+       overflow: hidden;
+       background: #333 url(../../images/mochaui/bg-header.gif) repeat-x;
+}
+
+#desktopTitlebar {
+       padding: 7px 8px 6px 8px;
+       height: 32px;
+       background: url(../../images/mochaui/logo.gif) no-repeat;
+       background-position: left 0;
+}
+
+#desktopTitlebar h1.applicationTitle {
+       display: none;
+       margin: 0;
+       padding: 0 5px 0 0;
+       font-size: 20px;
+       line-height: 25px;
+       font-weight: bold;
+       color: #fff;
+}
+
+#desktopTitlebar h2.tagline {
+       font-size: 12px;
+       color: #b2b2b2;
+       font-weight: bold;
+       padding: 5px 0 0 0;
+       text-align: center;
+}
+
+#desktopTitlebar h2.tagline .taglineEm {
+       color: #fff;
+       font-weight: bold;
+}
+
+#topNav {
+       font-family: Verdana, Arial, Helvetica, sans-serif;
+       font-size: 10px;
+       position: absolute;
+       right: 0;
+       top: 0;
+       color: #b2b2b2;
+       text-align: right;
+       padding: 13px 10px 0 0;
+}
+
+#topNav a {
+       color: #7DD0FA;
+       font-weight: normal;
+}
+
+#topNav a:hover {
+       text-decoration: none;
+}
+
+/* Toolboxes */
+
+.toolbox {
+       float: right;
+       padding: 6px 3px 0 5px;
+       height: 23px;
+       overflow: hidden;
+}
+
+div.toolbox.divider { /* Have to specify div here for IE6's sake */
+       background: url(../../images/mochaui/toolbox-divider.gif) left center no-repeat;
+       padding: 6px 3px 0 12px;
+}
+
+div.toolbox.divider2 { /* Have to specify div here for IE6's sake */
+       background: url(../../images/mochaui/toolbox-divider2.gif) left center no-repeat;
+       padding: 6px 4px 0 12px;
+}
+
+.toolbox img {
+       cursor: pointer;
+       margin-right: 6px;
+       padding: 0;
+       float: left;
+}
+
+.toolbox img.disabled {
+       cursor: default;
+}
+
+#spinnerWrapper {
+       width: 16px;
+       height: 16px;
+       background: url(../../images/mochaui/spinner-placeholder.gif) no-repeat;
+       margin-right: 5px;
+}
+
+#spinner {
+       visibility: hidden;
+       background: url(../../images/mochaui/spinner.gif) no-repeat;
+       width: 16px;
+       height: 16px;
+}
+
+/* Navbar */
+
+#desktopNavbar {
+       background: #4C4C4C;
+       height: 30px;
+       margin: 0 0px;  
+}
+
+#desktopNavbar ul {    
+       padding: 0;
+       margin: 0;
+       list-style: none;
+       font-size: 12px;
+}
+
+#desktopNavbar li {
+       float: left;
+}
+
+#desktopNavbar a {
+       display: block;
+}      
+       
+#desktopNavbar ul li a {
+       padding: 6px 10px 6px 10px;     
+       color: #b2b2b2;
+       font-weight: normal;
+}
+
+#desktopNavbar ul li a:hover {
+       color: #fff;
+}
+
+#desktopNavbar ul li a.arrow-right, #desktopNavbar ul li a:hover.arrow-right {
+       background-image: url(../../images/mochaui/arrow-right.gif);
+       background-repeat: no-repeat;
+       background-position: right 7px; 
+} 
+
+#desktopNavbar li ul {
+       padding: 2px;
+       border: 1px solid #3f3f3f;
+       background: #fff url(../../images/mochaui/bg-dropdown.gif) repeat-y;
+       position: absolute;
+       width: 164px;
+       left: -999em;
+       z-index: 8000;
+}
+
+#desktopNavbar li:hover ul ul,
+#desktopNavbar li.ieHover ul ul,
+#desktopNavbar li:hover ul ul ul,
+#desktopNavbar li.ieHover ul ul ul {
+       left: -999em;
+}
+
+#desktopNavbar li ul ul { /* third-and-above-level lists */
+       margin: -22px 0 0 164px;
+}
+
+#desktopNavbar li ul li .check {
+       position: absolute;
+       top: 8px;
+       left: 6px;
+       width: 5px;
+       height: 5px;
+       background: #555;
+       overflow: hidden;
+       line-height: 1px;
+       font-size: 1px;
+}
+
+#desktopNavbar li ul li a {
+       position: relative;
+       padding: 1px 9px 1px 25px;
+       width: 130px;
+       color: #3f3f3f;
+       font-weight: normal;
+}
+
+#desktopNavbar li ul li a:hover {
+       background: #6C98D9;
+       color: #fff;
+}
+
+#desktopNavbar li ul li a:hover .check {
+       background: #fff;
+}
+
+#desktopNavbar li:hover ul,
+#desktopNavbar li.ieHover ul,
+#desktopNavbar li li.ieHover ul,
+#desktopNavbar li li li.ieHover ul,
+#desktopNavbar li li:hover ul,
+#desktopNavbar li li li:hover ul { /* lists nested under hovered list items */
+       left: auto;
+}
+
+#desktopNavbar li:hover { /* For IE7 */
+    position: static;
+}
+
+li.divider {
+       margin-top: 2px;
+       padding-top: 3px;       
+       border-top: 1px solid #ebebeb;
+}      
+
+#pageWrapper {
+       position: relative;
+       overflow: hidden; /* This can be set to hidden or auto */
+       border-top: 1px solid #222;
+}
+
+/* Footer */
+#desktopFooterWrapper {
+       position: absolute;
+       left: 0;
+       bottom: 0;
+       width: 100%;
+       height: 30px;
+       overflow: hidden;
+       border-top: 1px solid #222;
+}
+
+#desktopFooter {
+       font-family: Verdana, Arial, Helvetica, sans-serif;
+       font-size: 10px;
+       height: 24px;
+       padding: 6px 8px 0 8px;
+       background: #333;
+       color: #b2b2b2;
+}
+
+#desktopFooter a {
+       color: #7DD0FA;
+       font-weight: normal;
+}
+
+#desktopFooter a:hover {
+       text-decoration: none;
+}
+
+/* Dock/Taskbar */
+
+#dockWrapper {
+       display: none;
+       width: 100%;
+       border-top: 1px solid #222;
+}
+
+#dockWrapper.top {
+       border: 0;
+}
+
+#dock {
+       position: relative;
+       padding: 3px 16px 0 6px;
+       bottom: 0;
+       left: 0;
+       background: #4c4c4c;
+       min-height: 27px;
+       height: auto;
+}
+
+*html #dock {
+       height: 30px; /* Used for IE 6.0 since it does not support min-height */
+}
+
+.dockTab {
+       float: left;
+       position: relative;     
+       font-size: 11px;
+       width: 150px;
+       height: 24px;
+       margin: 0 3px 2px 0;
+       overflow: hidden;       
+       cursor: pointer;
+       background: url(../../images/mochaui/dock-tabs.gif) left top no-repeat;
+}
+
+.dockTab.activeDockTab {       
+       background-position: left -24px;
+}
+
+.dockText {
+       position: absolute;
+       top: 0;
+       left: 0;
+       display: block;
+       font-weight: normal;
+       color: #bbb;    
+       text-align: left;
+       padding: 4px 10px 2px 10px;
+       width: 130px;
+       height: 19px;
+       overflow: hidden;
+}
+
+.dockText:hover {
+       color: #fff;
+}
+
+.dockTab.activeDockTab .dockText {
+       color: #fff;
+}
+
+#dockCanvas {
+       position: absolute;
+       top: 5px;
+       right: 3px;
+       z-index: 2;
+}
+
+#dockPlacement {
+       position: absolute;
+       top: 4px;
+       right: 8px;
+       width: 10px;
+       height: 9px;
+       opacity: 0;
+       filter: alpha(opacity=0);
+       -moz-opacity: 0;
+       background: #f00; /* for troubleshooting */
+       cursor: pointer;
+       z-index: 3; /* for IE */
+       text-align: right;
+}
+
+#dockAutoHide {
+       position: absolute;
+       top: 14px;
+       right: 8px;
+       width: 10px;
+       height: 9px;
+       opacity: 0;
+       filter: alpha(opacity=0);
+       -moz-opacity: 0;
+       background: #f00; /* for troubleshooting */
+       cursor: pointer;
+       z-index: 3; /* for IE */
+}
+
+/* Panel Layout
+---------------------------------------------------------------- */
+
+/* Columns */
+
+.column {
+       position: relative;     
+       float: left;
+       overflow: hidden;
+       background: #f1f1f1;
+}
+
+/* Panels */
+
+.panel {
+       position: relative;     
+       overflow: auto;
+       border-bottom: 1px solid #b9b9b9;
+       border-top: 0;
+}
+
+.pad {
+       position: absolute;
+       top: 0;
+       left: 0;
+       padding: 8px;
+       overflow: hidden;
+}
+
+#mainPanel {
+       background: #fff;       
+}      
+
+.panel-header {
+       position: relative;     
+       background: #f1f1f1 url(../../images/mochaui/bg-panel-header.gif) repeat-x;
+       height: 30px;
+       overflow: hidden;
+       border-bottom: 1px solid #d3d3d3;               
+}
+
+.panel-headerContent {
+       padding-top: 2px;
+}      
+
+.panel-headerContent.tabs {
+       background: url(../../images/mochaui/tabs.gif) repeat-x;
+       background-position: left -68px;        
+}
+
+.panel-header h2 {
+       display: inline-block;
+       font-size: 12px;
+       margin: 0;
+       padding: 3px 8px 0 8px;
+       height: 22px;
+       overflow: hidden;
+       color: #3f3f3f;
+}
+
+.panel-header-toolbox {
+       float: right;
+       height: 26px;
+       margin: 2px 5px 5px 0;
+       text-align: right;
+}
+
+.panel-collapse {
+       background: url(../../images/mochaui/collapse-expand.gif) left top no-repeat;
+}
+
+.panel-expand {
+       background: url(../../images/mochaui/collapse-expand.gif) left -16px no-repeat;
+}
+
+.icon16 {
+       margin: 5px 0 0 2px;
+       cursor: pointer;
+}
+
+.panel-footerWrapper {
+       position: absolute;
+       left: 0;
+       bottom: 0;
+       width: 100%;
+       background: #f9f9f9;
+       height: 30px;
+       overflow: hidden;
+       border-top: 1px solid #b9b9b9;
+}
+
+.panel-footer {
+       padding: 1px 0 0 8px;   
+}
+
+.panel-footerContent {
+       margin-top: 5px;
+}
+
+/* Handles */  
+
+.horizontalHandle {
+       height: 4px;
+       line-height: 1px;
+       font-size: 1px;
+       overflow: hidden;
+       background: #d1d1d1 url(../../images/mochaui/bg-handle-horizontal.gif) repeat-x;
+}
+
+.horizontalHandle.detached .handleIcon {
+       background: transparent;        
+}
+
+.horizontalHandle .handleIcon {        
+       margin: 0 auto;
+       height: 4px;
+       line-height: 1px;
+       font-size: 1px;
+       overflow: hidden;
+       background: url(../../images/mochaui/handle-icon-horizontal.gif) center center no-repeat;
+}
+
+.columnHandle {
+       min-height: 10px;       
+       float: left;
+       width: 4px;
+       overflow: hidden;
+       background: #bbb url(../../images/mochaui/handle-icon.gif) center center no-repeat;
+       border: 1px solid #9a9a9a;
+       border-top: 0;
+}
+
+/* Viewport overlays
+---------------------------------------------------------------- */
+
+#modalOverlay {
+       display: none;
+       position: fixed;
+       top: 0;
+       left: 0;
+       width: 100%;
+       background: #000;
+       opacity: 0;
+       filter: alpha(opacity=0);
+       -moz-opacity: 0;
+       z-index: 10000;
+}
+
+* html         #modalOverlay {
+       position: absolute;
+}
+
+/* Fix for IE6 select z-index issue */
+#modalFix {
+       display: none;
+       position: absolute;
+       top: 0;
+       left: 0;
+       width: 100%;
+       opacity: 0;
+       filter: alpha(opacity=0);
+       -moz-opacity: 0;
+       z-index: 9999;
+}
+
+/* Underlay */
+
+#windowUnderlay { 
+       position: fixed;
+       top: 0;
+       left: 0;
+       width: 100%;
+       background: #fff;       
+}
+
+* html #windowUnderlay { 
+       position: absolute;
+}
+
+/* Windows
+---------------------------------------------------------------- */
+
+.mocha {
+       position: absolute;
+       top: 0;
+       left: 0;
+       display: none;
+       overflow: hidden;       
+}
+
+.mocha.isFocused {     
+}      
+
+.mochaOverlay {
+       position: absolute;
+       top: 0;
+       left: 0;        
+}
+       
+.mochaTitlebar {
+       width: 100%;
+       overflow: hidden;       
+}
+
+.mochaTitlebar h3 {
+       font-size: 12px;
+       line-height: 15px;      
+       font-weight: bold;
+       margin: 5px 10px 4px 12px;
+       padding: 0;
+       color: #888;
+}
+
+.mocha.isFocused .mochaTitlebar h3 {
+       color: #141414;
+}
+
+.mochaToolbarWrapper {
+       width: 100%; /* For IE */
+       position: relative;
+       height: 29px;
+       background: #f1f1f1;    
+       overflow: hidden;
+       border-top: 1px solid #d9d9d9;
+}
+
+div.mochaToolbarWrapper.bottom {
+       border: 0;
+       border-bottom: 1px solid #d9d9d9;
+}
+
+.mochaToolbar {
+       width: 100%; /* For IE */
+       border-top: 1px solid #fff;
+}
+
+.mochaContentBorder {
+       border-top: 1px solid #dadada;
+       border-bottom: 1px solid #dadada;
+}
+
+.mochaContentWrapper { /* Has a fixed height and scrollbars if required. */
+       font-size: 12px;
+       overflow: auto;
+}
+       
+.mochaContent {
+       padding: 10px 12px;
+}
+
+.mocha .handle {
+       position: absolute;
+       background: #0f0;
+       width: 3px;
+       height: 3px;
+       z-index: 2;
+       opacity: .0;
+       filter: alpha(opacity=0);
+       -moz-opacity: .0;
+       overflow: hidden;
+       font-size: 1px; /* For IE6 */
+}
+
+.mocha .corner { /* Corner resize handles */
+       background: #f00;
+       width: 10px;
+       height: 10px;
+}
+
+.mocha .cornerSE { /* Bottom right resize handle */
+       background: #f00;
+       width: 20px;
+       height: 20px;
+}
+
+.mochaCanvasHeader {
+       position: absolute;
+       top: 0;
+       left: 0;
+       background: transparent;
+       z-index: -1;
+       display: none;
+       overflow: hidden;
+}
+
+.mochaControls {
+       position: absolute;
+       width: 52px;
+       top: 8px;
+       right: 8px;
+       height: 14px;
+       z-index: 4;
+       background: transparent;
+}
+
+.mochaCanvasControls {
+       position: absolute;     
+       top: 8px;
+       right: 8px;     
+       z-index: 3;
+       background: transparent;
+}
+
+/*
+       To use images for these buttons:
+       1. Set the useCanvasControls window option to false.
+       2. If you use a different button size you may need to reposition the controls.
+          Modify the controlsOffset window option.             
+       2. Add background images to each button.
+
+*/
+.mochaMinimizeButton, .mochaMaximizeButton, .mochaCloseButton {
+       float: right;
+       width: 14px;
+       height: 14px;
+       font-size: 1px; 
+       cursor: pointer;
+       z-index: 4;
+       background: #f00;
+       margin-left: 5px;
+}
+
+.mochaMinimizeButton {
+       margin-left: 0;
+}
+
+.mochaMaximizeButton {
+}
+
+.mochaCloseButton {
+}
+
+.mochaSpinner{
+       visibility: hidden;     
+       position: absolute;
+       bottom: 7px;
+       left: 6px;
+       width: 16px;
+       height: 16px;
+       background: url(../../images/mochaui/spinner.gif) no-repeat;
+}
+
+.mochaIframe {
+       width: 100%;
+}  
+               
+/* Fix for IE6 select z-index issue */
+.zIndexFix {
+       display: block;
+       position: absolute;
+       top: 0;
+       left: 0;
+       z-index: -1;
+       filter: mask();
+       width: 100px;
+       height: 100px;
+       border: 1px solid transparent;
+}
+
+/* Modals */
+
+.modal2 {
+       border: 8px solid #fff;         
+}
+
+.modal2 .mochaContentBorder {
+       border-width: 0px;
+}
+       
+/* Window Themes */
+
+.mocha.no-canvas {
+       background: #f1f1f1;
+       border: 2px solid #555; 
+}
+
+.mocha.no-canvas .mochaTitlebar {
+       background: #f1f1f1;    
+}
+
+.mocha.transparent .mochaTitlebar h3 {
+       color: #fff;
+       display: none;
+}
+
+.mocha.notification .mochaTitlebar {
+       opacity: .0;
+       filter: alpha(opacity=0);
+       -moz-opacity: 0;
+}
+
+.mocha.notification .mochaContentBorder {
+       border-width: 0px;
+}
+
+.mocha.notification .mochaContentWrapper {
+       text-align: center;
+       font-size: 12px;
+       font-weight: bold;
+}
+
+/* Compontents
+---------------------------------------------------------------- */
+
+/* Toolbar Tabs */
+
+.toolbarTabs { 
+       padding: 0 5px 2px 2px;
+       background: url(../../images/mochaui/tabs.gif) repeat-x;
+       background-position: left -70px;
+       overflow: visible;
+}
+
+.tab-menu {    
+       padding-top: 1px;
+       list-style:     none;
+       margin: 0;
+       padding: 0;
+       line-height: 16px;
+       font-size: 11px;
+}
+
+.tab-menu li {
+       display: block;
+       float: left;
+       margin: 0 0 5px 0;
+       cursor: pointer;        
+       background: url(../../images/mochaui/tabs.gif) repeat-x;
+       background-position: left -35px;
+}
+
+.tab-menu li.selected {
+       background: url(../../images/mochaui/tabs.gif) repeat-x;
+       background-position: left 0;
+}
+
+.tab-menu li a {
+       display: block;
+       margin-left: 8px;
+       padding: 6px 16px 5px 10px;
+       text-align: center;
+       font-weight: normal;
+       color: #141414;
+       background: url(../../images/mochaui/tabs.gif) repeat-x;
+       background-position: right -35px;       
+}
+
+.tab-menu li.selected a {
+       color: #141414;
+       font-weight: bold;      
+       background: url(../../images/mochaui/tabs.gif) repeat-x;
+       background-position: right 0;
+}
+
+/* Accordian */
+
+.accordianWrapper {
+       padding: 0;
+       background: #fff;
+}
+
+.accordianToggler {
+       margin: 0;
+       padding: 6px 10px;
+       background: #f1f1f1 url(../../images/mochaui/bg-panel-header.gif) repeat-x;
+       font-size: 12px;
+       cursor: pointer;
+       border-top: 1px solid #e3e3e3;  
+}
+
+.topToggler {
+       border-top: none;
+}
+
+.accordianToggler.open {
+       background: #fff url(../../images/mochaui/bg-panel-header.gif) repeat-x;        
+}
+
+.accordianContent {
+       padding: 10px 10px 5px 10px;
+}
+
+/* Sliders */
+
+.slider {
+       clear: both;
+       position: relative;
+       font-size: 12px;
+       font-weight: bold;
+       width: 200px;
+       margin-bottom: 15px;    
+}
+
+.sliderWrapper {
+       position: relative;
+       font-size: 1px;
+       line-height: 1px;
+       height: 9px;
+       width: 222px;
+}
+
+.sliderarea {
+       position: absolute;
+       top: 0;
+       left: 0;
+       height: 7px;
+       width: 220px;
+       font-size: 1px;
+       line-height: 1px;
+       background: url(../../images/mochaui/slider-area.gif) repeat-x;
+       border: 1px solid #a3a3a3;
+       border-bottom: 1px solid #ccc;
+       border-left: 1px solid #ccc;
+       margin: 0;
+       padding: 0;
+       overflow: hidden;
+}
+.sliderknob {
+       position: absolute;
+       top: 0;
+       left: 0;
+       height: 9px;
+       width: 19px;
+       font-size: 1px;
+       line-height: 1px;       
+       background: url(../../images/mochaui/knob.gif) no-repeat;
+       cursor: pointer;
+       overflow: hidden;
+       z-index: 2;
+}
+       
+.update {
+       padding-bottom: 5px;    
+}
+
+/* Folder Tree */
+
+.tree {
+       font-size: 11px;
+       line-height: 15px;
+       margin: 0;
+}
+
+.tree ul {
+       margin: 0;
+}
+
+.tree li {
+       list-style-type: none;
+       white-space: nowrap;
+}
+
+.tree li a {
+       color: #3f3f3f; 
+}      
+
+.tree li img {
+       vertical-align: middle;
+       width: 18px;
+       height: 18px;
+       overflow: hidden;
+}
+
+.tree li span {
+       padding-left: 2px;
+}
+
+/* View Toggle */
+
+.viewToggle {
+       position: absolute;
+       top: 4px;
+       right: 5px;
+       width: 60px;
+       text-align: right;
+}
+
+.viewToggle img.viewToggleList, .viewToggle img.viewToggleGrid {
+       width: 28px;
+       height: 22px;
+}      
+
+.viewToggle img.viewToggleList {
+       background: url(../../images/mochaui/view-toggle.gif) no-repeat;
+       background-position: 0 -66px;
+}
+
+.viewToggle img.viewToggleGrid {
+       background: url(../../images/mochaui/view-toggle.gif) no-repeat;
+       background-position: 0 0;
+}
+
+/* Miscellaneous
+---------------------------------------------------------------- */
+
+/* Window Builder Form Elements */
+       
+#desktop form {
+       margin: 0 0 0 0;
+       padding: 5px 0 0 0;
+}
+
+#newWindowForm {
+       width: 320px;
+}
+
+#desktop .input {
+       width: 225px;
+       padding: 1px 0 1px 3px;
+       border: 1px solid #bbb; 
+}
+
+#desktop textarea {
+       width: 225px;
+       height: 100px;
+       padding: 1px 0 1px 3px;
+       border: 1px solid #bbb;
+}
+
+#desktop .formLabel {
+       float: left;    
+       text-align: right;
+       width: 80px;
+       margin: 0 0 5px 0;
+}
+
+#desktop .formField {
+       float: right;
+       margin: 0 0 5px 0;
+       padding: 0 0 0 0;
+       width: 230px;
+}
+
+#desktop form .number {
+       width: 40px;
+}
+
+/* Menus */
+
+.menu-right li {
+       list-style-type: none;
+       display: inline;        
+       margin: 0 0 0 15px;
+}
+
+/* Notifications */
+
+/* Success, error & notice boxes for messages and errors. */
+.error,
+.notice, 
+.success    { padding: 8px; margin-bottom: 10px; border: 2px solid #ddd; }
+.error      { background: #FBE3E4; color: #D12F19; border-color: #FBC2C4; }
+.notice     { background: #FFF6BF; color: #817134; border-color: #FFD324; }
+.success    { background: #E6EFC2; color: #529214; border-color: #C6D880; }
+.error a    { color: #D12F19; }
+.notice a   { color: #817134; }
+.success a  { color: #529214; }
+
+
+/* Clears */
+
+.clear {
+       clear: both;
+       height: 0;
+}
+
+*html .clear {
+       height: 1%;
+       font-size: 1px;
+       line-height: 1px;
+       overflow: hidden;
+       visibility: hidden;
 }
\ No newline at end of file
index 77e96b5809bd5465b3da8dafa5c9d2e9fe40dcad..541103c0a9ddb9ca7637aba4cf32369896ab7e8f 100644 (file)
@@ -1,15 +1,15 @@
-class DummyImplementation(object):\r
-  def __init__(self, *args, **kwargs):\r
-    pass\r
-    \r
-  def __getattr__(self, *args, **kwargs):\r
-    raise Exception, "Not implemented."\r
-    \r
-  def login_optional(self, *args, **kwargs):\r
-    return None\r
-\r
-  @classmethod\r
-  def get_session_data(self, *args, **kwargs):\r
-    return {}\r
-    \r
+class DummyImplementation(object):
+  def __init__(self, *args, **kwargs):
+    pass
+    
+  def __getattr__(self, *args, **kwargs):
+    raise Exception, "Not implemented."
+    
+  def login_optional(self, *args, **kwargs):
+    return None
+
+  @classmethod
+  def get_session_data(self, *args, **kwargs):
+    return {}
+    
 twisted = DummyImplementation
\ No newline at end of file
index 4f2554548609c40e0ef4fda9c8acd92a1433f8ee..0480eb1659999211a726e6c50910cc56732b99eb 100644 (file)
@@ -1,47 +1,47 @@
-qwebirc.irc.IgnoreController = new Class({\r
-  initialize: function(toIRCLower) {\r
-    this.toIRCLower = toIRCLower;\r
-    this.ignored = new QHash();\r
-  },\r
-  __toHostKey: function(host) {\r
-    if(host.indexOf("!") == -1 && host.indexOf("@") == -1)\r
-      host = host + "!*@*";\r
-\r
-    return this.toIRCLower(host);\r
-  },\r
-  ignore: function(host) {\r
-    if(this.isIgnored(host))\r
-      return false;\r
-\r
-    var hostKey = this.__toHostKey(host);\r
-    this.ignored.put(hostKey, [host, new RegExp("^" + RegExp.fromIRCPattern(hostKey) + "$")]);\r
-\r
-    return hostKey;\r
-  },\r
-  unignore: function(host) {\r
-    if(!this.isIgnored(host))\r
-      return null;\r
-\r
-    var hostKey = this.__toHostKey(host);\r
-    this.ignored.remove(hostKey);\r
-\r
-    return hostKey;\r
-  },\r
-  get: function() {\r
-    return this.ignored.map(function(k, v) {\r
-      return v[0];\r
-    });\r
-  },\r
-  isIgnored: function(nick, host) {\r
-    if(host === undefined)\r
-      return this.ignored.contains(this.__toHostKey(nick));\r
-\r
-    var mask = this.toIRCLower(nick + "!" + host);\r
-\r
-    return this.ignored.each(function(k, v) {\r
-//      console.log(k + " (" + v[0] + ")" + " matched against " + mask);\r
-      if(mask.match(v[1]))\r
-        return true;\r
-    }) === true;\r
-  }\r
+qwebirc.irc.IgnoreController = new Class({
+  initialize: function(toIRCLower) {
+    this.toIRCLower = toIRCLower;
+    this.ignored = new QHash();
+  },
+  __toHostKey: function(host) {
+    if(host.indexOf("!") == -1 && host.indexOf("@") == -1)
+      host = host + "!*@*";
+
+    return this.toIRCLower(host);
+  },
+  ignore: function(host) {
+    if(this.isIgnored(host))
+      return false;
+
+    var hostKey = this.__toHostKey(host);
+    this.ignored.put(hostKey, [host, new RegExp("^" + RegExp.fromIRCPattern(hostKey) + "$")]);
+
+    return hostKey;
+  },
+  unignore: function(host) {
+    if(!this.isIgnored(host))
+      return null;
+
+    var hostKey = this.__toHostKey(host);
+    this.ignored.remove(hostKey);
+
+    return hostKey;
+  },
+  get: function() {
+    return this.ignored.map(function(k, v) {
+      return v[0];
+    });
+  },
+  isIgnored: function(nick, host) {
+    if(host === undefined)
+      return this.ignored.contains(this.__toHostKey(nick));
+
+    var mask = this.toIRCLower(nick + "!" + host);
+
+    return this.ignored.each(function(k, v) {
+//      console.log(k + " (" + v[0] + ")" + " matched against " + mask);
+      if(mask.match(v[1]))
+        return true;
+    }) === true;
+  }
 });
\ No newline at end of file
index b751b90e6cdc9052f57b95de61b91e71306ae467..67a77696b99c7238837b9116651f3e7a54dea2f2 100644 (file)
@@ -1,31 +1,31 @@
-qwebirc.irc.DummyNicknameValidator = new Class({\r
-  validate: function(x) {\r
-    return x;\r
-  }\r
-});\r
-\r
-qwebirc.irc.NicknameValidator = new Class({\r
-  initialize: function(options) {\r
-    this.options = options;\r
-  },\r
-  validate: function(nick, permitDot) {\r
-    var r = [];\r
-    \r
-    var max = Math.min(this.options.maxLen, nick.length);\r
-    var exploded = nick.split("");\r
-    for(var i=0;i<max;i++) {\r
-      var c = exploded[i];\r
-      \r
-      var valid = i == 0 ? this.options.validFirstChar : this.options.validSubChars;\r
-      if(valid.indexOf(c) != -1 || permitDot && c == ".") {\r
-        r.push(c);\r
-      } else {\r
-        r.push("_"); /* yeah we assume this is valid... */\r
-      }\r
-    }\r
-\r
-    while(r.length < this.options.minLen)\r
-      r.push("_");  /* yeah we assume this is valid... */\r
-    return r.join("");\r
-  }\r
-});\r
+qwebirc.irc.DummyNicknameValidator = new Class({
+  validate: function(x) {
+    return x;
+  }
+});
+
+qwebirc.irc.NicknameValidator = new Class({
+  initialize: function(options) {
+    this.options = options;
+  },
+  validate: function(nick, permitDot) {
+    var r = [];
+    
+    var max = Math.min(this.options.maxLen, nick.length);
+    var exploded = nick.split("");
+    for(var i=0;i<max;i++) {
+      var c = exploded[i];
+      
+      var valid = i == 0 ? this.options.validFirstChar : this.options.validSubChars;
+      if(valid.indexOf(c) != -1 || permitDot && c == ".") {
+        r.push(c);
+      } else {
+        r.push("_"); /* yeah we assume this is valid... */
+      }
+    }
+
+    while(r.length < this.options.minLen)
+      r.push("_");  /* yeah we assume this is valid... */
+    return r.join("");
+  }
+});
index c5f3508caa8ab7db8e0a85d0c7559e11b6929016..93fde5bdd93403633baa496684b4e883bf0ed6d7 100644 (file)
--- a/js/md5.js
+++ b/js/md5.js
-/*\r
- * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message\r
- * Digest Algorithm, as defined in RFC 1321.\r
- * Copyright (C) Paul Johnston 1999 - 2000.\r
- * See http://pajhome.org.uk/site/legal.html for details.\r
- */\r
-\r
-/*\r
- * Converted freestanding JavaScript code to fully encapsulated object.\r
- * Andrew Collins, andrewrcollins@yahoo.com, 2000-11-28\r
- */\r
-\r
-/*\r
- * MD5\r
- *\r
- * Usage:\r
- *\r
- *   var object = new MD5()\r
- *\r
- *     Returns a MD5 object.\r
- *\r
- *   object.digest(input)\r
- *\r
- *     Returns MD5 message digest of input.\r
- *\r
- * Example:\r
- *\r
- *   var object = new MD5();\r
- *\r
- *   // Examples drawn from RFC1321 test suite\r
- *   object.digest("");\r
- *   // d41d8cd98f00b204e9800998ecf8427e\r
- *\r
- *   object.digest("a");\r
- *   // 0cc175b9c0f1b6a831c399e269772661\r
- *\r
- *   object.digest("abc");\r
- *   // 900150983cd24fb0d6963f7d28e17f72\r
- *\r
- *   object.digest("message digest");\r
- *   // f96b697d7cb7938d525a2f31aaf161d0\r
- *\r
- *   object.digest("abcdefghijklmnopqrstuvwxyz");\r
- *   // c3fcd3d76192e4007dfb496cca67e13b\r
- *\r
- *   object.digest("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");\r
- *   // d174ab98d277d9f5a5611c2c9f419d9f\r
- *\r
- *   object.digest("12345678901234567890123456789012345678901234567890123456789012345678901234567890");\r
- *   // 57edf4a22be3c955ac49da2e2107b67a\r
- */\r
-\r
-qwebirc.util.crypto.MD5 = function() {\r
-  this.digest = calcMD5;\r
-\r
-/*\r
- * Convert a 32-bit number to a hex string with ls-byte first\r
- */\r
-  var hex_chr = "0123456789abcdef";\r
-  function rhex(num)\r
-  {\r
-    var str = "";\r
-    for(var j = 0; j <= 3; j++)\r
-      str += hex_chr.charAt((num >> (j * 8 + 4)) & 0x0F) + hex_chr.charAt((num >> (j * 8)) & 0x0F);\r
-    return str;\r
-  }\r
-\r
-/*\r
- * Convert a string to a sequence of 16-word blocks, stored as an array.\r
- * Append padding bits and the length, as described in the MD5 standard.\r
- */\r
-  function str2blks_MD5(str)\r
-  {\r
-    var nblk = ((str.length + 8) >> 6) + 1;\r
-    var blks = new Array(nblk * 16);\r
-    for(var i = 0; i < nblk * 16; i++) blks[i] = 0;\r
-    for(var i = 0; i < str.length; i++)\r
-      blks[i >> 2] |= str.charCodeAt(i) << ((i % 4) * 8);\r
-    blks[i >> 2] |= 0x80 << ((i % 4) * 8);\r
-    blks[nblk * 16 - 2] = str.length * 8;\r
-    return blks;\r
-  }\r
-\r
-/*\r
- * Add integers, wrapping at 2^32\r
- */\r
-  function add(x, y)\r
-  {\r
-    return ((x&0x7FFFFFFF) + (y&0x7FFFFFFF)) ^ (x&0x80000000) ^ (y&0x80000000);\r
-  }\r
-\r
-/*\r
- * Bitwise rotate a 32-bit number to the left\r
- */\r
-function rol(num, cnt)\r
-  {\r
-    return (num << cnt) | (num >>> (32 - cnt));\r
-  }\r
-\r
-/*\r
- * These functions implement the basic operation for each round of the\r
- * algorithm.\r
- */\r
-  function cmn(q, a, b, x, s, t)\r
-  {\r
-    return add(rol(add(add(a, q), add(x, t)), s), b);\r
-  }\r
-  function ff(a, b, c, d, x, s, t)\r
-  {\r
-    return cmn((b & c) | ((~b) & d), a, b, x, s, t);\r
-  }\r
-  function gg(a, b, c, d, x, s, t)\r
-  {\r
-    return cmn((b & d) | (c & (~d)), a, b, x, s, t);\r
-  }\r
-  function hh(a, b, c, d, x, s, t)\r
-  {\r
-    return cmn(b ^ c ^ d, a, b, x, s, t);\r
-  }\r
-  function ii(a, b, c, d, x, s, t)\r
-  {\r
-    return cmn(c ^ (b | (~d)), a, b, x, s, t);\r
-  }\r
-\r
-/*\r
- * Take a string and return the hex representation of its MD5.\r
- */\r
-  function calcMD5(str)\r
-  {\r
-    var x = str2blks_MD5(str);\r
-    var a = 0x67452301;\r
-    var b = 0xEFCDAB89;\r
-    var c = 0x98BADCFE;\r
-    var d = 0x10325476;\r
-\r
-    for(var i = 0; i < x.length; i += 16)\r
-    {\r
-      var olda = a;\r
-      var oldb = b;\r
-      var oldc = c;\r
-      var oldd = d;\r
-\r
-      a = ff(a, b, c, d, x[i+ 0], 7 , 0xD76AA478);\r
-      d = ff(d, a, b, c, x[i+ 1], 12, 0xE8C7B756);\r
-      c = ff(c, d, a, b, x[i+ 2], 17, 0x242070DB);\r
-      b = ff(b, c, d, a, x[i+ 3], 22, 0xC1BDCEEE);\r
-      a = ff(a, b, c, d, x[i+ 4], 7 , 0xF57C0FAF);\r
-      d = ff(d, a, b, c, x[i+ 5], 12, 0x4787C62A);\r
-      c = ff(c, d, a, b, x[i+ 6], 17, 0xA8304613);\r
-      b = ff(b, c, d, a, x[i+ 7], 22, 0xFD469501);\r
-      a = ff(a, b, c, d, x[i+ 8], 7 , 0x698098D8);\r
-      d = ff(d, a, b, c, x[i+ 9], 12, 0x8B44F7AF);\r
-      c = ff(c, d, a, b, x[i+10], 17, 0xFFFF5BB1);\r
-      b = ff(b, c, d, a, x[i+11], 22, 0x895CD7BE);\r
-      a = ff(a, b, c, d, x[i+12], 7 , 0x6B901122);\r
-      d = ff(d, a, b, c, x[i+13], 12, 0xFD987193);\r
-      c = ff(c, d, a, b, x[i+14], 17, 0xA679438E);\r
-      b = ff(b, c, d, a, x[i+15], 22, 0x49B40821);\r
-\r
-      a = gg(a, b, c, d, x[i+ 1], 5 , 0xF61E2562);\r
-      d = gg(d, a, b, c, x[i+ 6], 9 , 0xC040B340);\r
-      c = gg(c, d, a, b, x[i+11], 14, 0x265E5A51);\r
-      b = gg(b, c, d, a, x[i+ 0], 20, 0xE9B6C7AA);\r
-      a = gg(a, b, c, d, x[i+ 5], 5 , 0xD62F105D);\r
-      d = gg(d, a, b, c, x[i+10], 9 , 0x02441453);\r
-      c = gg(c, d, a, b, x[i+15], 14, 0xD8A1E681);\r
-      b = gg(b, c, d, a, x[i+ 4], 20, 0xE7D3FBC8);\r
-      a = gg(a, b, c, d, x[i+ 9], 5 , 0x21E1CDE6);\r
-      d = gg(d, a, b, c, x[i+14], 9 , 0xC33707D6);\r
-      c = gg(c, d, a, b, x[i+ 3], 14, 0xF4D50D87);\r
-      b = gg(b, c, d, a, x[i+ 8], 20, 0x455A14ED);\r
-      a = gg(a, b, c, d, x[i+13], 5 , 0xA9E3E905);\r
-      d = gg(d, a, b, c, x[i+ 2], 9 , 0xFCEFA3F8);\r
-      c = gg(c, d, a, b, x[i+ 7], 14, 0x676F02D9);\r
-      b = gg(b, c, d, a, x[i+12], 20, 0x8D2A4C8A);\r
-\r
-      a = hh(a, b, c, d, x[i+ 5], 4 , 0xFFFA3942);\r
-      d = hh(d, a, b, c, x[i+ 8], 11, 0x8771F681);\r
-      c = hh(c, d, a, b, x[i+11], 16, 0x6D9D6122);\r
-      b = hh(b, c, d, a, x[i+14], 23, 0xFDE5380C);\r
-      a = hh(a, b, c, d, x[i+ 1], 4 , 0xA4BEEA44);\r
-      d = hh(d, a, b, c, x[i+ 4], 11, 0x4BDECFA9);\r
-      c = hh(c, d, a, b, x[i+ 7], 16, 0xF6BB4B60);\r
-      b = hh(b, c, d, a, x[i+10], 23, 0xBEBFBC70);\r
-      a = hh(a, b, c, d, x[i+13], 4 , 0x289B7EC6);\r
-      d = hh(d, a, b, c, x[i+ 0], 11, 0xEAA127FA);\r
-      c = hh(c, d, a, b, x[i+ 3], 16, 0xD4EF3085);\r
-      b = hh(b, c, d, a, x[i+ 6], 23, 0x04881D05);\r
-      a = hh(a, b, c, d, x[i+ 9], 4 , 0xD9D4D039);\r
-      d = hh(d, a, b, c, x[i+12], 11, 0xE6DB99E5);\r
-      c = hh(c, d, a, b, x[i+15], 16, 0x1FA27CF8);\r
-      b = hh(b, c, d, a, x[i+ 2], 23, 0xC4AC5665);\r
-\r
-      a = ii(a, b, c, d, x[i+ 0], 6 , 0xF4292244);\r
-      d = ii(d, a, b, c, x[i+ 7], 10, 0x432AFF97);\r
-      c = ii(c, d, a, b, x[i+14], 15, 0xAB9423A7);\r
-      b = ii(b, c, d, a, x[i+ 5], 21, 0xFC93A039);\r
-      a = ii(a, b, c, d, x[i+12], 6 , 0x655B59C3);\r
-      d = ii(d, a, b, c, x[i+ 3], 10, 0x8F0CCC92);\r
-      c = ii(c, d, a, b, x[i+10], 15, 0xFFEFF47D);\r
-      b = ii(b, c, d, a, x[i+ 1], 21, 0x85845DD1);\r
-      a = ii(a, b, c, d, x[i+ 8], 6 , 0x6FA87E4F);\r
-      d = ii(d, a, b, c, x[i+15], 10, 0xFE2CE6E0);\r
-      c = ii(c, d, a, b, x[i+ 6], 15, 0xA3014314);\r
-      b = ii(b, c, d, a, x[i+13], 21, 0x4E0811A1);\r
-      a = ii(a, b, c, d, x[i+ 4], 6 , 0xF7537E82);\r
-      d = ii(d, a, b, c, x[i+11], 10, 0xBD3AF235);\r
-      c = ii(c, d, a, b, x[i+ 2], 15, 0x2AD7D2BB);\r
-      b = ii(b, c, d, a, x[i+ 9], 21, 0xEB86D391);\r
-\r
-      a = add(a, olda);\r
-      b = add(b, oldb);\r
-      c = add(c, oldc);\r
-      d = add(d, oldd);\r
-    }\r
-    return rhex(a) + rhex(b) + rhex(c) + rhex(d);\r
-  }\r
-}\r
+/*
+ * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
+ * Digest Algorithm, as defined in RFC 1321.
+ * Copyright (C) Paul Johnston 1999 - 2000.
+ * See http://pajhome.org.uk/site/legal.html for details.
+ */
+
+/*
+ * Converted freestanding JavaScript code to fully encapsulated object.
+ * Andrew Collins, andrewrcollins@yahoo.com, 2000-11-28
+ */
+
+/*
+ * MD5
+ *
+ * Usage:
+ *
+ *   var object = new MD5()
+ *
+ *     Returns a MD5 object.
+ *
+ *   object.digest(input)
+ *
+ *     Returns MD5 message digest of input.
+ *
+ * Example:
+ *
+ *   var object = new MD5();
+ *
+ *   // Examples drawn from RFC1321 test suite
+ *   object.digest("");
+ *   // d41d8cd98f00b204e9800998ecf8427e
+ *
+ *   object.digest("a");
+ *   // 0cc175b9c0f1b6a831c399e269772661
+ *
+ *   object.digest("abc");
+ *   // 900150983cd24fb0d6963f7d28e17f72
+ *
+ *   object.digest("message digest");
+ *   // f96b697d7cb7938d525a2f31aaf161d0
+ *
+ *   object.digest("abcdefghijklmnopqrstuvwxyz");
+ *   // c3fcd3d76192e4007dfb496cca67e13b
+ *
+ *   object.digest("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
+ *   // d174ab98d277d9f5a5611c2c9f419d9f
+ *
+ *   object.digest("12345678901234567890123456789012345678901234567890123456789012345678901234567890");
+ *   // 57edf4a22be3c955ac49da2e2107b67a
+ */
+
+qwebirc.util.crypto.MD5 = function() {
+  this.digest = calcMD5;
+
+/*
+ * Convert a 32-bit number to a hex string with ls-byte first
+ */
+  var hex_chr = "0123456789abcdef";
+  function rhex(num)
+  {
+    var str = "";
+    for(var j = 0; j <= 3; j++)
+      str += hex_chr.charAt((num >> (j * 8 + 4)) & 0x0F) + hex_chr.charAt((num >> (j * 8)) & 0x0F);
+    return str;
+  }
+
+/*
+ * Convert a string to a sequence of 16-word blocks, stored as an array.
+ * Append padding bits and the length, as described in the MD5 standard.
+ */
+  function str2blks_MD5(str)
+  {
+    var nblk = ((str.length + 8) >> 6) + 1;
+    var blks = new Array(nblk * 16);
+    for(var i = 0; i < nblk * 16; i++) blks[i] = 0;
+    for(var i = 0; i < str.length; i++)
+      blks[i >> 2] |= str.charCodeAt(i) << ((i % 4) * 8);
+    blks[i >> 2] |= 0x80 << ((i % 4) * 8);
+    blks[nblk * 16 - 2] = str.length * 8;
+    return blks;
+  }
+
+/*
+ * Add integers, wrapping at 2^32
+ */
+  function add(x, y)
+  {
+    return ((x&0x7FFFFFFF) + (y&0x7FFFFFFF)) ^ (x&0x80000000) ^ (y&0x80000000);
+  }
+
+/*
+ * Bitwise rotate a 32-bit number to the left
+ */
+function rol(num, cnt)
+  {
+    return (num << cnt) | (num >>> (32 - cnt));
+  }
+
+/*
+ * These functions implement the basic operation for each round of the
+ * algorithm.
+ */
+  function cmn(q, a, b, x, s, t)
+  {
+    return add(rol(add(add(a, q), add(x, t)), s), b);
+  }
+  function ff(a, b, c, d, x, s, t)
+  {
+    return cmn((b & c) | ((~b) & d), a, b, x, s, t);
+  }
+  function gg(a, b, c, d, x, s, t)
+  {
+    return cmn((b & d) | (c & (~d)), a, b, x, s, t);
+  }
+  function hh(a, b, c, d, x, s, t)
+  {
+    return cmn(b ^ c ^ d, a, b, x, s, t);
+  }
+  function ii(a, b, c, d, x, s, t)
+  {
+    return cmn(c ^ (b | (~d)), a, b, x, s, t);
+  }
+
+/*
+ * Take a string and return the hex representation of its MD5.
+ */
+  function calcMD5(str)
+  {
+    var x = str2blks_MD5(str);
+    var a = 0x67452301;
+    var b = 0xEFCDAB89;
+    var c = 0x98BADCFE;
+    var d = 0x10325476;
+
+    for(var i = 0; i < x.length; i += 16)
+    {
+      var olda = a;
+      var oldb = b;
+      var oldc = c;
+      var oldd = d;
+
+      a = ff(a, b, c, d, x[i+ 0], 7 , 0xD76AA478);
+      d = ff(d, a, b, c, x[i+ 1], 12, 0xE8C7B756);
+      c = ff(c, d, a, b, x[i+ 2], 17, 0x242070DB);
+      b = ff(b, c, d, a, x[i+ 3], 22, 0xC1BDCEEE);
+      a = ff(a, b, c, d, x[i+ 4], 7 , 0xF57C0FAF);
+      d = ff(d, a, b, c, x[i+ 5], 12, 0x4787C62A);
+      c = ff(c, d, a, b, x[i+ 6], 17, 0xA8304613);
+      b = ff(b, c, d, a, x[i+ 7], 22, 0xFD469501);
+      a = ff(a, b, c, d, x[i+ 8], 7 , 0x698098D8);
+      d = ff(d, a, b, c, x[i+ 9], 12, 0x8B44F7AF);
+      c = ff(c, d, a, b, x[i+10], 17, 0xFFFF5BB1);
+      b = ff(b, c, d, a, x[i+11], 22, 0x895CD7BE);
+      a = ff(a, b, c, d, x[i+12], 7 , 0x6B901122);
+      d = ff(d, a, b, c, x[i+13], 12, 0xFD987193);
+      c = ff(c, d, a, b, x[i+14], 17, 0xA679438E);
+      b = ff(b, c, d, a, x[i+15], 22, 0x49B40821);
+
+      a = gg(a, b, c, d, x[i+ 1], 5 , 0xF61E2562);
+      d = gg(d, a, b, c, x[i+ 6], 9 , 0xC040B340);
+      c = gg(c, d, a, b, x[i+11], 14, 0x265E5A51);
+      b = gg(b, c, d, a, x[i+ 0], 20, 0xE9B6C7AA);
+      a = gg(a, b, c, d, x[i+ 5], 5 , 0xD62F105D);
+      d = gg(d, a, b, c, x[i+10], 9 , 0x02441453);
+      c = gg(c, d, a, b, x[i+15], 14, 0xD8A1E681);
+      b = gg(b, c, d, a, x[i+ 4], 20, 0xE7D3FBC8);
+      a = gg(a, b, c, d, x[i+ 9], 5 , 0x21E1CDE6);
+      d = gg(d, a, b, c, x[i+14], 9 , 0xC33707D6);
+      c = gg(c, d, a, b, x[i+ 3], 14, 0xF4D50D87);
+      b = gg(b, c, d, a, x[i+ 8], 20, 0x455A14ED);
+      a = gg(a, b, c, d, x[i+13], 5 , 0xA9E3E905);
+      d = gg(d, a, b, c, x[i+ 2], 9 , 0xFCEFA3F8);
+      c = gg(c, d, a, b, x[i+ 7], 14, 0x676F02D9);
+      b = gg(b, c, d, a, x[i+12], 20, 0x8D2A4C8A);
+
+      a = hh(a, b, c, d, x[i+ 5], 4 , 0xFFFA3942);
+      d = hh(d, a, b, c, x[i+ 8], 11, 0x8771F681);
+      c = hh(c, d, a, b, x[i+11], 16, 0x6D9D6122);
+      b = hh(b, c, d, a, x[i+14], 23, 0xFDE5380C);
+      a = hh(a, b, c, d, x[i+ 1], 4 , 0xA4BEEA44);
+      d = hh(d, a, b, c, x[i+ 4], 11, 0x4BDECFA9);
+      c = hh(c, d, a, b, x[i+ 7], 16, 0xF6BB4B60);
+      b = hh(b, c, d, a, x[i+10], 23, 0xBEBFBC70);
+      a = hh(a, b, c, d, x[i+13], 4 , 0x289B7EC6);
+      d = hh(d, a, b, c, x[i+ 0], 11, 0xEAA127FA);
+      c = hh(c, d, a, b, x[i+ 3], 16, 0xD4EF3085);
+      b = hh(b, c, d, a, x[i+ 6], 23, 0x04881D05);
+      a = hh(a, b, c, d, x[i+ 9], 4 , 0xD9D4D039);
+      d = hh(d, a, b, c, x[i+12], 11, 0xE6DB99E5);
+      c = hh(c, d, a, b, x[i+15], 16, 0x1FA27CF8);
+      b = hh(b, c, d, a, x[i+ 2], 23, 0xC4AC5665);
+
+      a = ii(a, b, c, d, x[i+ 0], 6 , 0xF4292244);
+      d = ii(d, a, b, c, x[i+ 7], 10, 0x432AFF97);
+      c = ii(c, d, a, b, x[i+14], 15, 0xAB9423A7);
+      b = ii(b, c, d, a, x[i+ 5], 21, 0xFC93A039);
+      a = ii(a, b, c, d, x[i+12], 6 , 0x655B59C3);
+      d = ii(d, a, b, c, x[i+ 3], 10, 0x8F0CCC92);
+      c = ii(c, d, a, b, x[i+10], 15, 0xFFEFF47D);
+      b = ii(b, c, d, a, x[i+ 1], 21, 0x85845DD1);
+      a = ii(a, b, c, d, x[i+ 8], 6 , 0x6FA87E4F);
+      d = ii(d, a, b, c, x[i+15], 10, 0xFE2CE6E0);
+      c = ii(c, d, a, b, x[i+ 6], 15, 0xA3014314);
+      b = ii(b, c, d, a, x[i+13], 21, 0x4E0811A1);
+      a = ii(a, b, c, d, x[i+ 4], 6 , 0xF7537E82);
+      d = ii(d, a, b, c, x[i+11], 10, 0xBD3AF235);
+      c = ii(c, d, a, b, x[i+ 2], 15, 0x2AD7D2BB);
+      b = ii(b, c, d, a, x[i+ 9], 21, 0xEB86D391);
+
+      a = add(a, olda);
+      b = add(b, oldb);
+      c = add(c, oldc);
+      d = add(d, oldd);
+    }
+    return rhex(a) + rhex(b) + rhex(c) + rhex(d);
+  }
+}
index f76199b95fc90aca004d9972eed83fe3fb480fc8..f18e0fca21722df07751aacb4141414750f342fb 100644 (file)
-QHash = function(def) {\r
-  this.__map = {};\r
-  this.length = 0;\r
-\r
-  if(def !== undefined) {\r
-    var h = Object.prototype.hasOwnProperty;\r
-    for (var k in def) {\r
-      if(!h.call(def, k))\r
-        continue;\r
-      this.put(k, def[k]);\r
-    }\r
-  }\r
-};\r
-\r
-QHash.prototype.__toKey = function(key) {\r
-  if(typeof(key) !== "string")\r
-    throw new TypeError("Not a string: " + key);\r
-  return key + "$";\r
-};\r
-\r
-QHash.prototype.get = function(key) {\r
-  return this.__map[this.__toKey(key)];\r
-};\r
-\r
-QHash.prototype.put = function(key, value) {\r
-  key = this.__toKey(key);\r
-\r
-  var exists = key in this.__map;\r
-  var oldValue = this.__map[key];\r
-\r
-  this.__map[key] = value;\r
-\r
-  if(!exists) {\r
-    this.length++;\r
-    return null;\r
-  }\r
-\r
-  return oldValue;\r
-};\r
-\r
-QHash.prototype.contains = function(key) {\r
-  return this.__toKey(key) in this.__map;\r
-};\r
-\r
-QHash.prototype.remove = function(key) {\r
-  key = this.__toKey(key);\r
-\r
-  var exists = key in this.__map;\r
-  if(!exists)\r
-    return null;\r
-\r
-  var oldValue = this.__map[key];\r
-  delete this.__map[key];\r
-  this.length--;\r
-\r
-  return oldValue;\r
-};\r
-\r
-QHash.prototype.each = function(fn, def) {\r
-  var h = Object.prototype.hasOwnProperty;\r
-  var m = this.__map;\r
-  for(var k in m) {\r
-    if(!h.call(m, k))\r
-      continue;\r
-\r
-    var break_ = fn.call(def, k.substr(0, k.length - 1), m[k]) !== undefined;\r
-    if(break_)\r
-      return break_;\r
-  }\r
-};\r
-\r
-QHash.prototype.isEmpty = function() {\r
-  return this.length == 0;\r
-};\r
-\r
-QHash.prototype.map = function(fn, def) {\r
-  var l = [];\r
-  this.each(function(k, v) {\r
-    l.push(fn.call(def, k, v));\r
-  });\r
-  return l;\r
-};\r
-\r
-QHash.prototype.keys = function() {\r
-  return this.map(function(k) { return k; });\r
-};\r
-\r
-QHash.prototype.values = function() {\r
-  return this.map(function(k, v) { return v; });\r
-};\r
-\r
-QHash.prototype.items = function() {\r
-  return this.map(function(k, v) { return [k, v]; });\r
-};\r
-\r
-QHash.prototype.toString = function() {\r
-  var m = this.map(function(k, v) {\r
-    return k + "=" + v;\r
-  }, this);\r
-\r
-  return "{QHash length=" + this.length + " map={" + m + "}}";\r
-};\r
-\r
-QSet = function() {\r
-  this.__map = new QHash();\r
-\r
-  this.length = 0;\r
-  for(var i=0;i<arguments.length;i++)\r
-    this.add(arguments[i]);\r
-};\r
-\r
-QSet.prototype.add = function(value) {\r
-  var v = this.__map.put(value, true);\r
-  if(v !== true)\r
-    this.length = this.__map.length;\r
-\r
-  return v;\r
-};\r
-\r
-QSet.prototype.contains = function(value) {\r
-  return this.__map.contains(value);\r
-};\r
-\r
-QSet.prototype.remove = function(value) {\r
-  var v = this.__map.remove(value, true);\r
-  if(v)\r
-    this.length = this.__map.length;\r
-\r
-  return v;\r
-};\r
-\r
-QSet.prototype.each = function(fn, def) {\r
-  return this.__map.each(fn, def);\r
-};\r
-\r
-QSet.prototype.values = function() {\r
-  return this.__map.keys();\r
-}\r
-\r
-QSet.prototype.isEmpty = function() {\r
-  return this.length == 0;\r
-};\r
-\r
-QSet.prototype.map = function(fn, def) {\r
-  return this.__map.map(fn, def);\r
-};\r
-\r
-QSet.prototype.toString = function(value) {\r
-  return "{QSet length=" + this.length + " set={" + this.values() + "}}";\r
-};\r
+QHash = function(def) {
+  this.__map = {};
+  this.length = 0;
+
+  if(def !== undefined) {
+    var h = Object.prototype.hasOwnProperty;
+    for (var k in def) {
+      if(!h.call(def, k))
+        continue;
+      this.put(k, def[k]);
+    }
+  }
+};
+
+QHash.prototype.__toKey = function(key) {
+  if(typeof(key) !== "string")
+    throw new TypeError("Not a string: " + key);
+  return key + "$";
+};
+
+QHash.prototype.get = function(key) {
+  return this.__map[this.__toKey(key)];
+};
+
+QHash.prototype.put = function(key, value) {
+  key = this.__toKey(key);
+
+  var exists = key in this.__map;
+  var oldValue = this.__map[key];
+
+  this.__map[key] = value;
+
+  if(!exists) {
+    this.length++;
+    return null;
+  }
+
+  return oldValue;
+};
+
+QHash.prototype.contains = function(key) {
+  return this.__toKey(key) in this.__map;
+};
+
+QHash.prototype.remove = function(key) {
+  key = this.__toKey(key);
+
+  var exists = key in this.__map;
+  if(!exists)
+    return null;
+
+  var oldValue = this.__map[key];
+  delete this.__map[key];
+  this.length--;
+
+  return oldValue;
+};
+
+QHash.prototype.each = function(fn, def) {
+  var h = Object.prototype.hasOwnProperty;
+  var m = this.__map;
+  for(var k in m) {
+    if(!h.call(m, k))
+      continue;
+
+    var break_ = fn.call(def, k.substr(0, k.length - 1), m[k]) !== undefined;
+    if(break_)
+      return break_;
+  }
+};
+
+QHash.prototype.isEmpty = function() {
+  return this.length == 0;
+};
+
+QHash.prototype.map = function(fn, def) {
+  var l = [];
+  this.each(function(k, v) {
+    l.push(fn.call(def, k, v));
+  });
+  return l;
+};
+
+QHash.prototype.keys = function() {
+  return this.map(function(k) { return k; });
+};
+
+QHash.prototype.values = function() {
+  return this.map(function(k, v) { return v; });
+};
+
+QHash.prototype.items = function() {
+  return this.map(function(k, v) { return [k, v]; });
+};
+
+QHash.prototype.toString = function() {
+  var m = this.map(function(k, v) {
+    return k + "=" + v;
+  }, this);
+
+  return "{QHash length=" + this.length + " map={" + m + "}}";
+};
+
+QSet = function() {
+  this.__map = new QHash();
+
+  this.length = 0;
+  for(var i=0;i<arguments.length;i++)
+    this.add(arguments[i]);
+};
+
+QSet.prototype.add = function(value) {
+  var v = this.__map.put(value, true);
+  if(v !== true)
+    this.length = this.__map.length;
+
+  return v;
+};
+
+QSet.prototype.contains = function(value) {
+  return this.__map.contains(value);
+};
+
+QSet.prototype.remove = function(value) {
+  var v = this.__map.remove(value, true);
+  if(v)
+    this.length = this.__map.length;
+
+  return v;
+};
+
+QSet.prototype.each = function(fn, def) {
+  return this.__map.each(fn, def);
+};
+
+QSet.prototype.values = function() {
+  return this.__map.keys();
+}
+
+QSet.prototype.isEmpty = function() {
+  return this.length == 0;
+};
+
+QSet.prototype.map = function(fn, def) {
+  return this.__map.map(fn, def);
+};
+
+QSet.prototype.toString = function(value) {
+  return "{QSet length=" + this.length + " set={" + this.values() + "}}";
+};
index 6f50b92a4dc67d5ba15b8416c48f97b38e5fb87e..9d71658adda2a6c71e419544eff32680043d6aac 100644 (file)
@@ -1,19 +1,19 @@
-if(!window.CanvasRenderingContext2D){(function(){var I=Math,i=I.round,L=I.sin,M=I.cos,m=10,A=m/2,Q={init:function(a){var b=a||document;if(/MSIE/.test(navigator.userAgent)&&!window.opera){var c=this;b.attachEvent("onreadystatechange",function(){c.r(b)})}},r:function(a){if(a.readyState=="complete"){if(!a.namespaces["s"]){a.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml")}var b=a.createStyleSheet();b.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}g_vml_\\:*{behavior:url(#default#VML)}";\r
-var c=a.getElementsByTagName("canvas");for(var d=0;d<c.length;d++){if(!c[d].getContext){this.initElement(c[d])}}}},q:function(a){var b=a.outerHTML,c=a.ownerDocument.createElement(b);if(b.slice(-2)!="/>"){var d="/"+a.tagName,e;while((e=a.nextSibling)&&e.tagName!=d){e.removeNode()}if(e){e.removeNode()}}a.parentNode.replaceChild(c,a);return c},initElement:function(a){a=this.q(a);a.getContext=function(){if(this.l){return this.l}return this.l=new K(this)};a.attachEvent("onpropertychange",V);a.attachEvent("onresize",\r
-W);var b=a.attributes;if(b.width&&b.width.specified){a.style.width=b.width.nodeValue+"px"}else{a.width=a.clientWidth}if(b.height&&b.height.specified){a.style.height=b.height.nodeValue+"px"}else{a.height=a.clientHeight}return a}};function V(a){var b=a.srcElement;switch(a.propertyName){case "width":b.style.width=b.attributes.width.nodeValue+"px";b.getContext().clearRect();break;case "height":b.style.height=b.attributes.height.nodeValue+"px";b.getContext().clearRect();break}}function W(a){var b=a.srcElement;\r
-if(b.firstChild){b.firstChild.style.width=b.clientWidth+"px";b.firstChild.style.height=b.clientHeight+"px"}}Q.init();var R=[];for(var E=0;E<16;E++){for(var F=0;F<16;F++){R[E*16+F]=E.toString(16)+F.toString(16)}}function J(){return[[1,0,0],[0,1,0],[0,0,1]]}function G(a,b){var c=J();for(var d=0;d<3;d++){for(var e=0;e<3;e++){var g=0;for(var h=0;h<3;h++){g+=a[d][h]*b[h][e]}c[d][e]=g}}return c}function N(a,b){b.fillStyle=a.fillStyle;b.lineCap=a.lineCap;b.lineJoin=a.lineJoin;b.lineWidth=a.lineWidth;b.miterLimit=\r
-a.miterLimit;b.shadowBlur=a.shadowBlur;b.shadowColor=a.shadowColor;b.shadowOffsetX=a.shadowOffsetX;b.shadowOffsetY=a.shadowOffsetY;b.strokeStyle=a.strokeStyle;b.d=a.d;b.e=a.e}function O(a){var b,c=1;a=String(a);if(a.substring(0,3)=="rgb"){var d=a.indexOf("(",3),e=a.indexOf(")",d+1),g=a.substring(d+1,e).split(",");b="#";for(var h=0;h<3;h++){b+=R[Number(g[h])]}if(g.length==4&&a.substr(3,1)=="a"){c=g[3]}}else{b=a}return[b,c]}function S(a){switch(a){case "butt":return"flat";case "round":return"round";\r
-case "square":default:return"square"}}function K(a){this.a=J();this.m=[];this.k=[];this.c=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=m*1;this.globalAlpha=1;this.canvas=a;var b=a.ownerDocument.createElement("div");b.style.width=a.clientWidth+"px";b.style.height=a.clientHeight+"px";b.style.overflow="hidden";b.style.position="absolute";a.appendChild(b);this.j=b;this.d=1;this.e=1}var j=K.prototype;j.clearRect=function(){this.j.innerHTML=\r
-"";this.c=[]};j.beginPath=function(){this.c=[]};j.moveTo=function(a,b){this.c.push({type:"moveTo",x:a,y:b});this.f=a;this.g=b};j.lineTo=function(a,b){this.c.push({type:"lineTo",x:a,y:b});this.f=a;this.g=b};j.bezierCurveTo=function(a,b,c,d,e,g){this.c.push({type:"bezierCurveTo",cp1x:a,cp1y:b,cp2x:c,cp2y:d,x:e,y:g});this.f=e;this.g=g};j.quadraticCurveTo=function(a,b,c,d){var e=this.f+0.6666666666666666*(a-this.f),g=this.g+0.6666666666666666*(b-this.g),h=e+(c-this.f)/3,l=g+(d-this.g)/3;this.bezierCurveTo(e,\r
-g,h,l,c,d)};j.arc=function(a,b,c,d,e,g){c*=m;var h=g?"at":"wa",l=a+M(d)*c-A,n=b+L(d)*c-A,o=a+M(e)*c-A,f=b+L(e)*c-A;if(l==o&&!g){l+=0.125}this.c.push({type:h,x:a,y:b,radius:c,xStart:l,yStart:n,xEnd:o,yEnd:f})};j.rect=function(a,b,c,d){this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath()};j.strokeRect=function(a,b,c,d){this.beginPath();this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath();this.stroke()};j.fillRect=function(a,\r
-b,c,d){this.beginPath();this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath();this.fill()};j.createLinearGradient=function(a,b,c,d){var e=new H("gradient");return e};j.createRadialGradient=function(a,b,c,d,e,g){var h=new H("gradientradial");h.n=c;h.o=g;h.i.x=a;h.i.y=b;return h};j.drawImage=function(a,b){var c,d,e,g,h,l,n,o,f=a.runtimeStyle.width,k=a.runtimeStyle.height;a.runtimeStyle.width="auto";a.runtimeStyle.height="auto";var q=a.width,r=a.height;a.runtimeStyle.width=\r
-f;a.runtimeStyle.height=k;if(arguments.length==3){c=arguments[1];d=arguments[2];h=(l=0);n=(e=q);o=(g=r)}else if(arguments.length==5){c=arguments[1];d=arguments[2];e=arguments[3];g=arguments[4];h=(l=0);n=q;o=r}else if(arguments.length==9){h=arguments[1];l=arguments[2];n=arguments[3];o=arguments[4];c=arguments[5];d=arguments[6];e=arguments[7];g=arguments[8]}else{throw"Invalid number of arguments";}var s=this.b(c,d),t=[],v=10,w=10;t.push(" <g_vml_:group",' coordsize="',m*v,",",m*w,'"',' coordorigin="0,0"',\r
-' style="width:',v,";height:",w,";position:absolute;");if(this.a[0][0]!=1||this.a[0][1]){var x=[];x.push("M11='",this.a[0][0],"',","M12='",this.a[1][0],"',","M21='",this.a[0][1],"',","M22='",this.a[1][1],"',","Dx='",i(s.x/m),"',","Dy='",i(s.y/m),"'");var p=s,y=this.b(c+e,d),z=this.b(c,d+g),B=this.b(c+e,d+g);p.x=Math.max(p.x,y.x,z.x,B.x);p.y=Math.max(p.y,y.y,z.y,B.y);t.push("padding:0 ",i(p.x/m),"px ",i(p.y/m),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",x.join(""),", sizingmethod='clip');")}else{t.push("top:",\r
-i(s.y/m),"px;left:",i(s.x/m),"px;")}t.push(' ">','<g_vml_:image src="',a.src,'"',' style="width:',m*e,";"," height:",m*g,';"',' cropleft="',h/q,'"',' croptop="',l/r,'"',' cropright="',(q-h-n)/q,'"',' cropbottom="',(r-l-o)/r,'"'," />","</g_vml_:group>");this.j.insertAdjacentHTML("BeforeEnd",t.join(""))};j.stroke=function(a){var b=[],c=O(a?this.fillStyle:this.strokeStyle),d=c[0],e=c[1]*this.globalAlpha,g=10,h=10;b.push("<g_vml_:shape",' fillcolor="',d,'"',' filled="',Boolean(a),'"',' style="position:absolute;width:',\r
-g,";height:",h,';"',' coordorigin="0 0" coordsize="',m*g," ",m*h,'"',' stroked="',!a,'"',' strokeweight="',this.lineWidth,'"',' strokecolor="',d,'"',' path="');var l={x:null,y:null},n={x:null,y:null};for(var o=0;o<this.c.length;o++){var f=this.c[o];if(f.type=="moveTo"){b.push(" m ");var k=this.b(f.x,f.y);b.push(i(k.x),",",i(k.y))}else if(f.type=="lineTo"){b.push(" l ");var k=this.b(f.x,f.y);b.push(i(k.x),",",i(k.y))}else if(f.type=="close"){b.push(" x ")}else if(f.type=="bezierCurveTo"){b.push(" c ");\r
-var k=this.b(f.x,f.y),q=this.b(f.cp1x,f.cp1y),r=this.b(f.cp2x,f.cp2y);b.push(i(q.x),",",i(q.y),",",i(r.x),",",i(r.y),",",i(k.x),",",i(k.y))}else if(f.type=="at"||f.type=="wa"){b.push(" ",f.type," ");var k=this.b(f.x,f.y),s=this.b(f.xStart,f.yStart),t=this.b(f.xEnd,f.yEnd);b.push(i(k.x-this.d*f.radius),",",i(k.y-this.e*f.radius)," ",i(k.x+this.d*f.radius),",",i(k.y+this.e*f.radius)," ",i(s.x),",",i(s.y)," ",i(t.x),",",i(t.y))}if(k){if(l.x==null||k.x<l.x){l.x=k.x}if(n.x==null||k.x>n.x){n.x=k.x}if(l.y==\r
-null||k.y<l.y){l.y=k.y}if(n.y==null||k.y>n.y){n.y=k.y}}}b.push(' ">');if(typeof this.fillStyle=="object"){var v={x:"50%",y:"50%"},w=n.x-l.x,x=n.y-l.y,p=w>x?w:x;v.x=i(this.fillStyle.i.x/w*100+50)+"%";v.y=i(this.fillStyle.i.y/x*100+50)+"%";var y=[];if(this.fillStyle.p=="gradientradial"){var z=this.fillStyle.n/p*100,B=this.fillStyle.o/p*100-z}else{var z=0,B=100}var C={offset:null,color:null},D={offset:null,color:null};this.fillStyle.h.sort(function(T,U){return T.offset-U.offset});for(var o=0;o<this.fillStyle.h.length;o++){var u=\r
-this.fillStyle.h[o];y.push(u.offset*B+z,"% ",u.color,",");if(u.offset>C.offset||C.offset==null){C.offset=u.offset;C.color=u.color}if(u.offset<D.offset||D.offset==null){D.offset=u.offset;D.color=u.color}}y.pop();b.push("<g_vml_:fill",' color="',D.color,'"',' color2="',C.color,'"',' type="',this.fillStyle.p,'"',' focusposition="',v.x,", ",v.y,'"',' colors="',y.join(""),'"',' opacity="',e,'" />')}else if(a){b.push('<g_vml_:fill color="',d,'" opacity="',e,'" />')}else{b.push("<g_vml_:stroke",' opacity="',\r
-e,'"',' joinstyle="',this.lineJoin,'"',' miterlimit="',this.miterLimit,'"',' endcap="',S(this.lineCap),'"',' weight="',this.lineWidth,'px"',' color="',d,'" />')}b.push("</g_vml_:shape>");this.j.insertAdjacentHTML("beforeEnd",b.join(""));this.c=[]};j.fill=function(){this.stroke(true)};j.closePath=function(){this.c.push({type:"close"})};j.b=function(a,b){return{x:m*(a*this.a[0][0]+b*this.a[1][0]+this.a[2][0])-A,y:m*(a*this.a[0][1]+b*this.a[1][1]+this.a[2][1])-A}};j.save=function(){var a={};N(this,a);\r
-this.k.push(a);this.m.push(this.a);this.a=G(J(),this.a)};j.restore=function(){N(this.k.pop(),this);this.a=this.m.pop()};j.translate=function(a,b){var c=[[1,0,0],[0,1,0],[a,b,1]];this.a=G(c,this.a)};j.rotate=function(a){var b=M(a),c=L(a),d=[[b,c,0],[-c,b,0],[0,0,1]];this.a=G(d,this.a)};j.scale=function(a,b){this.d*=a;this.e*=b;var c=[[a,0,0],[0,b,0],[0,0,1]];this.a=G(c,this.a)};j.clip=function(){};j.arcTo=function(){};j.createPattern=function(){return new P};function H(a){this.p=a;this.n=0;this.o=\r
-0;this.h=[];this.i={x:0,y:0}}H.prototype.addColorStop=function(a,b){b=O(b);this.h.push({offset:1-a,color:b})};function P(){}G_vmlCanvasManager=Q;CanvasRenderingContext2D=K;CanvasGradient=H;CanvasPattern=P})()};\r
+if(!window.CanvasRenderingContext2D){(function(){var I=Math,i=I.round,L=I.sin,M=I.cos,m=10,A=m/2,Q={init:function(a){var b=a||document;if(/MSIE/.test(navigator.userAgent)&&!window.opera){var c=this;b.attachEvent("onreadystatechange",function(){c.r(b)})}},r:function(a){if(a.readyState=="complete"){if(!a.namespaces["s"]){a.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml")}var b=a.createStyleSheet();b.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}g_vml_\\:*{behavior:url(#default#VML)}";
+var c=a.getElementsByTagName("canvas");for(var d=0;d<c.length;d++){if(!c[d].getContext){this.initElement(c[d])}}}},q:function(a){var b=a.outerHTML,c=a.ownerDocument.createElement(b);if(b.slice(-2)!="/>"){var d="/"+a.tagName,e;while((e=a.nextSibling)&&e.tagName!=d){e.removeNode()}if(e){e.removeNode()}}a.parentNode.replaceChild(c,a);return c},initElement:function(a){a=this.q(a);a.getContext=function(){if(this.l){return this.l}return this.l=new K(this)};a.attachEvent("onpropertychange",V);a.attachEvent("onresize",
+W);var b=a.attributes;if(b.width&&b.width.specified){a.style.width=b.width.nodeValue+"px"}else{a.width=a.clientWidth}if(b.height&&b.height.specified){a.style.height=b.height.nodeValue+"px"}else{a.height=a.clientHeight}return a}};function V(a){var b=a.srcElement;switch(a.propertyName){case "width":b.style.width=b.attributes.width.nodeValue+"px";b.getContext().clearRect();break;case "height":b.style.height=b.attributes.height.nodeValue+"px";b.getContext().clearRect();break}}function W(a){var b=a.srcElement;
+if(b.firstChild){b.firstChild.style.width=b.clientWidth+"px";b.firstChild.style.height=b.clientHeight+"px"}}Q.init();var R=[];for(var E=0;E<16;E++){for(var F=0;F<16;F++){R[E*16+F]=E.toString(16)+F.toString(16)}}function J(){return[[1,0,0],[0,1,0],[0,0,1]]}function G(a,b){var c=J();for(var d=0;d<3;d++){for(var e=0;e<3;e++){var g=0;for(var h=0;h<3;h++){g+=a[d][h]*b[h][e]}c[d][e]=g}}return c}function N(a,b){b.fillStyle=a.fillStyle;b.lineCap=a.lineCap;b.lineJoin=a.lineJoin;b.lineWidth=a.lineWidth;b.miterLimit=
+a.miterLimit;b.shadowBlur=a.shadowBlur;b.shadowColor=a.shadowColor;b.shadowOffsetX=a.shadowOffsetX;b.shadowOffsetY=a.shadowOffsetY;b.strokeStyle=a.strokeStyle;b.d=a.d;b.e=a.e}function O(a){var b,c=1;a=String(a);if(a.substring(0,3)=="rgb"){var d=a.indexOf("(",3),e=a.indexOf(")",d+1),g=a.substring(d+1,e).split(",");b="#";for(var h=0;h<3;h++){b+=R[Number(g[h])]}if(g.length==4&&a.substr(3,1)=="a"){c=g[3]}}else{b=a}return[b,c]}function S(a){switch(a){case "butt":return"flat";case "round":return"round";
+case "square":default:return"square"}}function K(a){this.a=J();this.m=[];this.k=[];this.c=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=m*1;this.globalAlpha=1;this.canvas=a;var b=a.ownerDocument.createElement("div");b.style.width=a.clientWidth+"px";b.style.height=a.clientHeight+"px";b.style.overflow="hidden";b.style.position="absolute";a.appendChild(b);this.j=b;this.d=1;this.e=1}var j=K.prototype;j.clearRect=function(){this.j.innerHTML=
+"";this.c=[]};j.beginPath=function(){this.c=[]};j.moveTo=function(a,b){this.c.push({type:"moveTo",x:a,y:b});this.f=a;this.g=b};j.lineTo=function(a,b){this.c.push({type:"lineTo",x:a,y:b});this.f=a;this.g=b};j.bezierCurveTo=function(a,b,c,d,e,g){this.c.push({type:"bezierCurveTo",cp1x:a,cp1y:b,cp2x:c,cp2y:d,x:e,y:g});this.f=e;this.g=g};j.quadraticCurveTo=function(a,b,c,d){var e=this.f+0.6666666666666666*(a-this.f),g=this.g+0.6666666666666666*(b-this.g),h=e+(c-this.f)/3,l=g+(d-this.g)/3;this.bezierCurveTo(e,
+g,h,l,c,d)};j.arc=function(a,b,c,d,e,g){c*=m;var h=g?"at":"wa",l=a+M(d)*c-A,n=b+L(d)*c-A,o=a+M(e)*c-A,f=b+L(e)*c-A;if(l==o&&!g){l+=0.125}this.c.push({type:h,x:a,y:b,radius:c,xStart:l,yStart:n,xEnd:o,yEnd:f})};j.rect=function(a,b,c,d){this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath()};j.strokeRect=function(a,b,c,d){this.beginPath();this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath();this.stroke()};j.fillRect=function(a,
+b,c,d){this.beginPath();this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath();this.fill()};j.createLinearGradient=function(a,b,c,d){var e=new H("gradient");return e};j.createRadialGradient=function(a,b,c,d,e,g){var h=new H("gradientradial");h.n=c;h.o=g;h.i.x=a;h.i.y=b;return h};j.drawImage=function(a,b){var c,d,e,g,h,l,n,o,f=a.runtimeStyle.width,k=a.runtimeStyle.height;a.runtimeStyle.width="auto";a.runtimeStyle.height="auto";var q=a.width,r=a.height;a.runtimeStyle.width=
+f;a.runtimeStyle.height=k;if(arguments.length==3){c=arguments[1];d=arguments[2];h=(l=0);n=(e=q);o=(g=r)}else if(arguments.length==5){c=arguments[1];d=arguments[2];e=arguments[3];g=arguments[4];h=(l=0);n=q;o=r}else if(arguments.length==9){h=arguments[1];l=arguments[2];n=arguments[3];o=arguments[4];c=arguments[5];d=arguments[6];e=arguments[7];g=arguments[8]}else{throw"Invalid number of arguments";}var s=this.b(c,d),t=[],v=10,w=10;t.push(" <g_vml_:group",' coordsize="',m*v,",",m*w,'"',' coordorigin="0,0"',
+' style="width:',v,";height:",w,";position:absolute;");if(this.a[0][0]!=1||this.a[0][1]){var x=[];x.push("M11='",this.a[0][0],"',","M12='",this.a[1][0],"',","M21='",this.a[0][1],"',","M22='",this.a[1][1],"',","Dx='",i(s.x/m),"',","Dy='",i(s.y/m),"'");var p=s,y=this.b(c+e,d),z=this.b(c,d+g),B=this.b(c+e,d+g);p.x=Math.max(p.x,y.x,z.x,B.x);p.y=Math.max(p.y,y.y,z.y,B.y);t.push("padding:0 ",i(p.x/m),"px ",i(p.y/m),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",x.join(""),", sizingmethod='clip');")}else{t.push("top:",
+i(s.y/m),"px;left:",i(s.x/m),"px;")}t.push(' ">','<g_vml_:image src="',a.src,'"',' style="width:',m*e,";"," height:",m*g,';"',' cropleft="',h/q,'"',' croptop="',l/r,'"',' cropright="',(q-h-n)/q,'"',' cropbottom="',(r-l-o)/r,'"'," />","</g_vml_:group>");this.j.insertAdjacentHTML("BeforeEnd",t.join(""))};j.stroke=function(a){var b=[],c=O(a?this.fillStyle:this.strokeStyle),d=c[0],e=c[1]*this.globalAlpha,g=10,h=10;b.push("<g_vml_:shape",' fillcolor="',d,'"',' filled="',Boolean(a),'"',' style="position:absolute;width:',
+g,";height:",h,';"',' coordorigin="0 0" coordsize="',m*g," ",m*h,'"',' stroked="',!a,'"',' strokeweight="',this.lineWidth,'"',' strokecolor="',d,'"',' path="');var l={x:null,y:null},n={x:null,y:null};for(var o=0;o<this.c.length;o++){var f=this.c[o];if(f.type=="moveTo"){b.push(" m ");var k=this.b(f.x,f.y);b.push(i(k.x),",",i(k.y))}else if(f.type=="lineTo"){b.push(" l ");var k=this.b(f.x,f.y);b.push(i(k.x),",",i(k.y))}else if(f.type=="close"){b.push(" x ")}else if(f.type=="bezierCurveTo"){b.push(" c ");
+var k=this.b(f.x,f.y),q=this.b(f.cp1x,f.cp1y),r=this.b(f.cp2x,f.cp2y);b.push(i(q.x),",",i(q.y),",",i(r.x),",",i(r.y),",",i(k.x),",",i(k.y))}else if(f.type=="at"||f.type=="wa"){b.push(" ",f.type," ");var k=this.b(f.x,f.y),s=this.b(f.xStart,f.yStart),t=this.b(f.xEnd,f.yEnd);b.push(i(k.x-this.d*f.radius),",",i(k.y-this.e*f.radius)," ",i(k.x+this.d*f.radius),",",i(k.y+this.e*f.radius)," ",i(s.x),",",i(s.y)," ",i(t.x),",",i(t.y))}if(k){if(l.x==null||k.x<l.x){l.x=k.x}if(n.x==null||k.x>n.x){n.x=k.x}if(l.y==
+null||k.y<l.y){l.y=k.y}if(n.y==null||k.y>n.y){n.y=k.y}}}b.push(' ">');if(typeof this.fillStyle=="object"){var v={x:"50%",y:"50%"},w=n.x-l.x,x=n.y-l.y,p=w>x?w:x;v.x=i(this.fillStyle.i.x/w*100+50)+"%";v.y=i(this.fillStyle.i.y/x*100+50)+"%";var y=[];if(this.fillStyle.p=="gradientradial"){var z=this.fillStyle.n/p*100,B=this.fillStyle.o/p*100-z}else{var z=0,B=100}var C={offset:null,color:null},D={offset:null,color:null};this.fillStyle.h.sort(function(T,U){return T.offset-U.offset});for(var o=0;o<this.fillStyle.h.length;o++){var u=
+this.fillStyle.h[o];y.push(u.offset*B+z,"% ",u.color,",");if(u.offset>C.offset||C.offset==null){C.offset=u.offset;C.color=u.color}if(u.offset<D.offset||D.offset==null){D.offset=u.offset;D.color=u.color}}y.pop();b.push("<g_vml_:fill",' color="',D.color,'"',' color2="',C.color,'"',' type="',this.fillStyle.p,'"',' focusposition="',v.x,", ",v.y,'"',' colors="',y.join(""),'"',' opacity="',e,'" />')}else if(a){b.push('<g_vml_:fill color="',d,'" opacity="',e,'" />')}else{b.push("<g_vml_:stroke",' opacity="',
+e,'"',' joinstyle="',this.lineJoin,'"',' miterlimit="',this.miterLimit,'"',' endcap="',S(this.lineCap),'"',' weight="',this.lineWidth,'px"',' color="',d,'" />')}b.push("</g_vml_:shape>");this.j.insertAdjacentHTML("beforeEnd",b.join(""));this.c=[]};j.fill=function(){this.stroke(true)};j.closePath=function(){this.c.push({type:"close"})};j.b=function(a,b){return{x:m*(a*this.a[0][0]+b*this.a[1][0]+this.a[2][0])-A,y:m*(a*this.a[0][1]+b*this.a[1][1]+this.a[2][1])-A}};j.save=function(){var a={};N(this,a);
+this.k.push(a);this.m.push(this.a);this.a=G(J(),this.a)};j.restore=function(){N(this.k.pop(),this);this.a=this.m.pop()};j.translate=function(a,b){var c=[[1,0,0],[0,1,0],[a,b,1]];this.a=G(c,this.a)};j.rotate=function(a){var b=M(a),c=L(a),d=[[b,c,0],[-c,b,0],[0,0,1]];this.a=G(d,this.a)};j.scale=function(a,b){this.d*=a;this.e*=b;var c=[[a,0,0],[0,b,0],[0,0,1]];this.a=G(c,this.a)};j.clip=function(){};j.arcTo=function(){};j.createPattern=function(){return new P};function H(a){this.p=a;this.n=0;this.o=
+0;this.h=[];this.i={x:0,y:0}}H.prototype.addColorStop=function(a,b){b=O(b);this.h.push({offset:1-a,color:b})};function P(){}G_vmlCanvasManager=Q;CanvasRenderingContext2D=K;CanvasGradient=H;CanvasPattern=P})()};
index 6963c32fbd77b8e39f6b740b92dd33b5e50bd2ea..658d2819dcfebfdb778983ef130ce1d7c9e24d45 100644 (file)
-/* \r
-\r
-Script: Core.js\r
-       MochaUI - A Web Applications User Interface Framework.\r
-\r
-Copyright:\r
-       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.\r
-\r
-License:\r
-       MIT-style license.\r
-\r
-Contributors:\r
-       - Scott F. Frederick\r
-       - Joel Lindau\r
-\r
-Note:\r
-       This documentation is taken directly from the javascript source files. It is built using Natural Docs.\r
-\r
-Todo:\r
-       Consider making title tooltips optional and using them more often.\r
-\r
-*/\r
-\r
-var MochaUI = new Hash({\r
-       options: new Hash({\r
-               useEffects: true  // Toggles the majority of window fade and move effects.\r
-       }),\r
-       Columns: {\r
-               instances:      new Hash()\r
-       },\r
-       Panels: {\r
-               instances:      new Hash()\r
-       },              \r
-       Windows: {        \r
-               instances:      new Hash(),\r
-               indexLevel:     100,          // Used for z-Index\r
-               windowIDCount:  0,                // Used for windows without an ID defined by the user\r
-               windowsVisible: true          // Ctrl-Alt-Q to toggle window visibility\r
-       },      \r
-       ieSupport:  'excanvas',   // Makes it easier to switch between Excanvas and Moocanvas for testing\r
-       focusingWindow: 'false',\r
-       /*\r
-       \r
-       Function: updateContent\r
-               Replace the content of a window or panel.\r
-               \r
-       Arguments:\r
-               element - The parent window or panel.\r
-               childElement - The child element of the window or panel recieving the content.\r
-               title - (string) Change this if you want to change the title of the window or panel.\r
-               content - (string or element) An html loadMethod option.\r
-               loadMethod - ('html', 'xhr', or 'iframe') Defaults to 'html'.\r
-               url - Used if loadMethod is set to 'xhr' or 'iframe'.\r
-               padding - (object)\r
-\r
-       */      \r
-       updateContent: function(updateOptions){\r
-\r
-               var options = {\r
-                       'element':      null,\r
-                       'childElement': null,\r
-                       'title':        null,\r
-                       'content':      null,\r
-                       'loadMethod':   null,\r
-                       'url':          null,\r
-                       'padding':      null\r
-               };\r
-               $extend(options, updateOptions);\r
-\r
-               if (!options.element) return;\r
-               var element = options.element;\r
-\r
-               if (MochaUI.Windows.instances.get(element.id)) {\r
-                       var recipient = 'window';\r
-                       var currentInstance = MochaUI.Windows.instances.get(element.id);\r
-                       var spinnerEl = currentInstance.spinnerEl;\r
-                       if (options.title) {\r
-                               currentInstance.titleEl.set('html', options.title);\r
-                       }\r
-               }\r
-               else {\r
-                       var recipient = 'panel';\r
-                       var currentInstance = MochaUI.Panels.instances.get(element.id);\r
-                       if (options.title) {\r
-                               currentInstance.titleEl.set('html', options.title);\r
-                       }\r
-               }\r
-\r
-               var contentEl = currentInstance.contentEl;\r
-               if (options.childElement != null) {\r
-                       var contentContainer = options.childElement;\r
-               }\r
-               else {\r
-                       var contentContainer = currentInstance.contentEl;\r
-               }\r
-               \r
-               var loadMethod = options.loadMethod != null ? options.loadMethod : currentInstance.options.loadMethod;\r
-               \r
-               // Set scrollbars if loading content in main content container.\r
-               // Always use 'hidden' for iframe windows\r
-               if (contentContainer == currentInstance.contentEl) {\r
-                       currentInstance.contentWrapperEl.setStyles({\r
-                               'overflow': currentInstance.options.scrollbars == true && loadMethod != 'iframe' ? 'auto' : 'hidden'\r
-                       });\r
-               }\r
-\r
-               var contentWrapperEl = currentInstance.contentWrapperEl;\r
-               \r
-               if (options.padding != null) {\r
-                       contentEl.setStyles({\r
-                               'padding-top': options.padding.top,\r
-                               'padding-bottom': options.padding.bottom,\r
-                               'padding-left': options.padding.left,\r
-                               'padding-right': options.padding.right\r
-                       });\r
-               }\r
-\r
-               // Remove old content.\r
-               if (contentContainer == contentEl){\r
-                       contentEl.empty();\r
-               }\r
-\r
-               // Load new content.\r
-               switch(loadMethod){\r
-                       case 'xhr':\r
-                               new Request.HTML({\r
-                                       url: options.url,\r
-                                       update: contentContainer,\r
-                                       evalScripts: currentInstance.options.evalScripts,\r
-                                       evalResponse: currentInstance.options.evalResponse,\r
-                                       onRequest: function(){\r
-                                               if (recipient == 'window' && contentContainer == contentEl){\r
-                                                       currentInstance.showSpinner(spinnerEl);\r
-                                               }\r
-                                               else if (recipient == 'panel' && contentContainer == contentEl && $('spinner')){\r
-                                                       $('spinner').setStyle('visibility','visible');  \r
-                                               }\r
-                                       }.bind(this),\r
-                                       onFailure: function(){\r
-                                               if (contentContainer == contentEl){\r
-                                                       contentContainer.set('html','<p><strong>Error Loading XMLHttpRequest</strong></p>');\r
-                                                       if (recipient == 'window') {\r
-                                                               currentInstance.hideSpinner(spinnerEl);\r
-                                                       }\r
-                                                       else if (recipient == 'panel' && $('spinner')) {\r
-                                                               $('spinner').setStyle('visibility', 'hidden');\r
-                                                       }\r
-                                               }\r
-                                       }.bind(this),\r
-                                       onException: function(){}.bind(this),\r
-                                       onSuccess: function(){\r
-                                               if (contentContainer == contentEl){\r
-                                                       if (recipient == 'window'){\r
-                                                               currentInstance.hideSpinner(spinnerEl);\r
-                                                       }\r
-                                                       else if (recipient == 'panel' && $('spinner')){\r
-                                                               $('spinner').setStyle('visibility', 'hidden');\r
-                                                       }\r
-                                                       currentInstance.fireEvent('onContentLoaded', element);\r
-                                               }\r
-                                       }.bind(this),\r
-                                       onComplete: function(){}.bind(this)\r
-                               }).get();\r
-                               break;\r
-                       case 'iframe': // May be able to streamline this if the iframe already exists.\r
-                               if ( currentInstance.options.contentURL == '' || contentContainer != contentEl) {\r
-                                       break;\r
-                               }\r
-                               currentInstance.iframeEl = new Element('iframe', {\r
-                                       'id': currentInstance.options.id + '_iframe',\r
-                                       'name':  currentInstance.options.id + '_iframe',\r
-                                       'class': 'mochaIframe',\r
-                                       'src': options.url,\r
-                                       'marginwidth':  0,\r
-                                       'marginheight': 0,\r
-                                       'frameBorder':  0,\r
-                                       'scrolling':    'auto',\r
-                                       'styles': {\r
-                                               'height': contentWrapperEl.offsetHeight - contentWrapperEl.getStyle('border-top').toInt() - contentWrapperEl.getStyle('border-bottom').toInt(),\r
-                                               'width': currentInstance.panelEl ? contentWrapperEl.offsetWidth - contentWrapperEl.getStyle('border-left').toInt() - contentWrapperEl.getStyle('border-right').toInt() : '100%' \r
-                                       }\r
-                               }).injectInside(contentEl);\r
-\r
-                               // Add onload event to iframe so we can hide the spinner and run onContentLoaded()\r
-                               currentInstance.iframeEl.addEvent('load', function(e) {\r
-                                       if (recipient == 'window') {\r
-                                               currentInstance.hideSpinner(spinnerEl);\r
-                                       }\r
-                                       else if (recipient == 'panel' && contentContainer == contentEl && $('spinner')) {\r
-                                               $('spinner').setStyle('visibility', 'hidden');\r
-                                       }\r
-                                       currentInstance.fireEvent('onContentLoaded', element);\r
-                               }.bind(this));\r
-                               if (recipient == 'window') {\r
-                                       currentInstance.showSpinner(spinnerEl);\r
-                               }\r
-                               else if (recipient == 'panel' && contentContainer == contentEl && $('spinner')){\r
-                                       $('spinner').setStyle('visibility', 'visible'); \r
-                               }\r
-                               break;\r
-                       case 'html':\r
-                       default:\r
-                               // Need to test injecting elements as content.\r
-                               var elementTypes = new Array('element', 'textnode', 'whitespace', 'collection');\r
-                               if (elementTypes.contains($type(options.content))){\r
-                                       options.content.inject(contentContainer);\r
-                               } else {\r
-                                       contentContainer.set('html', options.content);\r
-                               }\r
-                               currentInstance.fireEvent('onContentLoaded', element);\r
-                               break;\r
-               }\r
-\r
-       },\r
-       /*\r
-       \r
-       Function: reloadIframe\r
-               Reload an iframe. Fixes an issue in Firefox when trying to use location.reload on an iframe that has been destroyed and recreated.\r
-\r
-       Arguments:\r
-               iframe - This should be both the name and the id of the iframe.\r
-\r
-       Syntax:\r
-               (start code)\r
-               MochaUI.reloadIframe(element);\r
-               (end)\r
-\r
-       Example:\r
-               To reload an iframe from within another iframe:\r
-               (start code)\r
-               parent.MochaUI.reloadIframe('myIframeName');\r
-               (end)\r
-\r
-       */\r
-       reloadIframe: function(iframe){\r
-               if (Browser.Engine.gecko) {\r
-                       $(iframe).src = $(iframe).src;\r
-               }\r
-               else {\r
-                       top.frames[iframe].location.reload(true);\r
-               }\r
-       },\r
-       collapseToggle: function(windowEl){\r
-               var instances = MochaUI.Windows.instances;\r
-               var currentInstance = instances.get(windowEl.id);\r
-               var handles = currentInstance.windowEl.getElements('.handle');\r
-               if (currentInstance.isMaximized == true) return;                \r
-               if (currentInstance.isCollapsed == false) {\r
-                       currentInstance.isCollapsed = true;\r
-                       handles.setStyle('display', 'none');\r
-                       if ( currentInstance.iframeEl ) {\r
-                               currentInstance.iframeEl.setStyle('visibility', 'hidden');\r
-                       }\r
-                       currentInstance.contentBorderEl.setStyles({\r
-                               visibility: 'hidden',\r
-                               position: 'absolute',\r
-                               top: -10000,\r
-                               left: -10000\r
-                       });\r
-                       if(currentInstance.toolbarWrapperEl){\r
-                               currentInstance.toolbarWrapperEl.setStyles({\r
-                                       visibility: 'hidden',\r
-                                       position: 'absolute',\r
-                                       top: -10000,\r
-                                       left: -10000\r
-                               });\r
-                       }\r
-                       currentInstance.drawWindowCollapsed(windowEl);\r
-               }\r
-               else {\r
-                       currentInstance.isCollapsed = false;\r
-                       currentInstance.drawWindow(windowEl);\r
-                       currentInstance.contentBorderEl.setStyles({\r
-                               visibility: 'visible',\r
-                               position: null,\r
-                               top: null,\r
-                               left: null\r
-                       });\r
-                       if(currentInstance.toolbarWrapperEl){\r
-                               currentInstance.toolbarWrapperEl.setStyles({\r
-                                       visibility: 'visible',\r
-                                       position: null,\r
-                                       top: null,\r
-                                       left: null\r
-                               });\r
-                       }\r
-                       if ( currentInstance.iframeEl ) {\r
-                               currentInstance.iframeEl.setStyle('visibility', 'visible');\r
-                       }\r
-                       handles.setStyle('display', 'block');\r
-               }\r
-       },\r
-       /*\r
-\r
-       Function: closeWindow\r
-               Closes a window.\r
-\r
-       Syntax:\r
-       (start code)\r
-               MochaUI.closeWindow();\r
-       (end)\r
-\r
-       Arguments: \r
-               windowEl - the ID of the window to be closed\r
-\r
-       Returns:\r
-               true - the window was closed\r
-               false - the window was not closed\r
-\r
-       */\r
-       closeWindow: function(windowEl){\r
-               // Does window exist and is not already in process of closing ?\r
-\r
-               var instances = MochaUI.Windows.instances;\r
-               var currentInstance = instances.get(windowEl.id);\r
-               if (windowEl != $(windowEl) || currentInstance.isClosing) return;\r
-                       \r
-               currentInstance.isClosing = true;\r
-               currentInstance.fireEvent('onClose', windowEl);\r
-               if (currentInstance.check) currentInstance.check.destroy();\r
-\r
-               if ((currentInstance.options.type == 'modal' || currentInstance.options.type == 'modal2') && Browser.Engine.trident4){\r
-                               $('modalFix').setStyle('display', 'none');\r
-               }\r
-\r
-               if (MochaUI.options.useEffects == false){\r
-                       if (currentInstance.options.type == 'modal' || currentInstance.options.type == 'modal2'){\r
-                               $('modalOverlay').setStyle('opacity', 0);\r
-                       }\r
-                       MochaUI.closingJobs(windowEl);\r
-                       return true;    \r
-               }\r
-               else {\r
-                       // Redraws IE windows without shadows since IE messes up canvas alpha when you change element opacity\r
-                       if (Browser.Engine.trident) currentInstance.drawWindow(windowEl, false);\r
-                       if (currentInstance.options.type == 'modal' || currentInstance.options.type == 'modal2'){\r
-                               MochaUI.Modal.modalOverlayCloseMorph.start({\r
-                                       'opacity': 0\r
-                               });\r
-                       }\r
-                       var closeMorph = new Fx.Morph(windowEl, {\r
-                               duration: 120,\r
-                               onComplete: function(){\r
-                                       MochaUI.closingJobs(windowEl);\r
-                                       return true;\r
-                               }.bind(this)\r
-                       });\r
-                       closeMorph.start({\r
-                               'opacity': .4\r
-                       });\r
-               }\r
-\r
-       },\r
-       closingJobs: function(windowEl){\r
-\r
-               var instances = MochaUI.Windows.instances;\r
-               var currentInstance = instances.get(windowEl.id);\r
-               windowEl.setStyle('visibility', 'hidden');\r
-               windowEl.destroy();\r
-               currentInstance.fireEvent('onCloseComplete');\r
-               \r
-               if (currentInstance.options.type != 'notification'){\r
-                       var newFocus = this.getWindowWithHighestZindex();\r
-                       this.focusWindow(newFocus);\r
-               }\r
-\r
-               instances.erase(currentInstance.options.id);\r
-               if (this.loadingWorkspace == true) {\r
-                       this.windowUnload();\r
-               }\r
-\r
-               if (MochaUI.Dock && $(MochaUI.options.dock) && currentInstance.options.type == 'window') {\r
-                       var currentButton = $(currentInstance.options.id + '_dockTab');\r
-                       if (currentButton != null) {\r
-                               MochaUI.Dock.dockSortables.removeItems(currentButton).destroy();\r
-                       }\r
-                       // Need to resize everything in case the dock becomes smaller when a tab is removed\r
-                       MochaUI.Desktop.setDesktopSize();\r
-               }\r
-       },\r
-       /*\r
-       \r
-       Function: closeAll      \r
-               Close all open windows.\r
-\r
-       */\r
-       closeAll: function() {          \r
-               $$('div.mocha').each(function(windowEl){\r
-                       this.closeWindow(windowEl);\r
-               }.bind(this));\r
-       },\r
-       /*\r
-\r
-       Function: toggleWindowVisibility\r
-               Toggle window visibility with Ctrl-Alt-Q.\r
-\r
-       */      \r
-       toggleWindowVisibility: function(){\r
-               MochaUI.Windows.instances.each(function(instance){\r
-                       if (instance.options.type == 'modal' || instance.options.type == 'modal2' || instance.isMinimized == true) return;                                                                      \r
-                       var id = $(instance.options.id);\r
-                       if (id.getStyle('visibility') == 'visible'){\r
-                               if (instance.iframe){\r
-                                       instance.iframeEl.setStyle('visibility', 'hidden');\r
-                               }\r
-                               if (instance.toolbarEl){\r
-                                       instance.toolbarWrapperEl.setStyle('visibility', 'hidden');\r
-                               }\r
-                               instance.contentBorderEl.setStyle('visibility', 'hidden');\r
-\r
-                               id.setStyle('visibility', 'hidden');\r
-                               MochaUI.Windows.windowsVisible = false;\r
-                       }\r
-                       else {\r
-                               id.setStyle('visibility', 'visible');\r
-                               instance.contentBorderEl.setStyle('visibility', 'visible');\r
-                               if (instance.iframe){\r
-                                       instance.iframeEl.setStyle('visibility', 'visible');\r
-                               }\r
-                               if (instance.toolbarEl){\r
-                                       instance.toolbarWrapperEl.setStyle('visibility', 'visible');\r
-                               }\r
-                               MochaUI.Windows.windowsVisible = true;\r
-                       }\r
-               }.bind(this));\r
-\r
-       },\r
-       focusWindow: function(windowEl, fireEvent){\r
-\r
-               // This is used with blurAll\r
-               MochaUI.focusingWindow = 'true';\r
-               var windowClicked = function(){\r
-                       MochaUI.focusingWindow = 'false';\r
-               };              \r
-               windowClicked.delay(170, this);\r
-\r
-               // Only focus when needed\r
-               if ($$('.mocha').length == 0) return;\r
-               if (windowEl != $(windowEl) || windowEl.hasClass('isFocused')) return;\r
-\r
-               var instances =  MochaUI.Windows.instances;\r
-               var currentInstance = instances.get(windowEl.id);\r
-       \r
-               if (currentInstance.options.type == 'notification') return;\r
-\r
-               MochaUI.Windows.indexLevel += 2;\r
-               windowEl.setStyle('zIndex', MochaUI.Windows.indexLevel);\r
-\r
-               // Used when dragging and resizing windows\r
-               $('windowUnderlay').setStyle('zIndex', MochaUI.Windows.indexLevel - 1).inject($(windowEl),'after');\r
-\r
-               // Fire onBlur for the window that lost focus.\r
-               instances.each(function(instance){\r
-                       if (instance.windowEl.hasClass('isFocused')){\r
-                               instance.fireEvent('onBlur', instance.windowEl);\r
-                       }\r
-                       instance.windowEl.removeClass('isFocused');\r
-               });\r
-\r
-               if (MochaUI.Dock && $(MochaUI.options.dock) && currentInstance.options.type == 'window') {\r
-                       MochaUI.Dock.makeActiveTab();\r
-               }\r
-               currentInstance.windowEl.addClass('isFocused');\r
-\r
-               if (fireEvent != false){\r
-                       currentInstance.fireEvent('onFocus', windowEl);\r
-               }\r
-\r
-       },\r
-       getWindowWithHighestZindex: function(){\r
-               this.highestZindex = 0;\r
-               $$('div.mocha').each(function(element){\r
-                       this.zIndex = element.getStyle('zIndex');\r
-                       if (this.zIndex >= this.highestZindex) {\r
-                               this.highestZindex = this.zIndex;\r
-                       }       \r
-               }.bind(this));\r
-               $$('div.mocha').each(function(element){\r
-                       if (element.getStyle('zIndex') == this.highestZindex) {\r
-                               this.windowWithHighestZindex = element;\r
-                       }\r
-               }.bind(this));\r
-               return this.windowWithHighestZindex;\r
-       },\r
-       blurAll: function(){\r
-               if (MochaUI.focusingWindow == 'false') {\r
-                       $$('.mocha').each(function(windowEl){\r
-                               var instances =  MochaUI.Windows.instances;\r
-                               var currentInstance = instances.get(windowEl.id);\r
-                               if (currentInstance.options.type != 'modal' && currentInstance.options.type != 'modal2'){\r
-                                       windowEl.removeClass('isFocused');\r
-                               }\r
-                       });\r
-                       $$('div.dockTab').removeClass('activeDockTab');\r
-               }\r
-       },\r
-       roundedRect: function(ctx, x, y, width, height, radius, rgb, a){\r
-               ctx.fillStyle = 'rgba(' + rgb.join(',') + ',' + a + ')';\r
-               ctx.beginPath();\r
-               ctx.moveTo(x, y + radius);\r
-               ctx.lineTo(x, y + height - radius);\r
-               ctx.quadraticCurveTo(x, y + height, x + radius, y + height);\r
-               ctx.lineTo(x + width - radius, y + height);\r
-               ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);\r
-               ctx.lineTo(x + width, y + radius);\r
-               ctx.quadraticCurveTo(x + width, y, x + width - radius, y);\r
-               ctx.lineTo(x + radius, y);\r
-               ctx.quadraticCurveTo(x, y, x, y + radius);\r
-               ctx.fill(); \r
-       },\r
-       triangle: function(ctx, x, y, width, height, rgb, a){\r
-               ctx.beginPath();\r
-               ctx.moveTo(x + width, y);\r
-               ctx.lineTo(x, y + height);\r
-               ctx.lineTo(x + width, y + height);\r
-               ctx.closePath();\r
-               ctx.fillStyle = 'rgba(' + rgb.join(',') + ',' + a + ')';\r
-               ctx.fill();\r
-       },\r
-       circle: function(ctx, x, y, diameter, rgb, a){\r
-               ctx.beginPath();\r
-               ctx.moveTo(x, y);\r
-               ctx.arc(x, y, diameter, 0, Math.PI*2, true);\r
-               ctx.fillStyle = 'rgba(' + rgb.join(',') + ',' + a + ')';\r
-               ctx.fill();\r
-       },\r
-       /*\r
-\r
-       Function: centerWindow\r
-               Center a window in it's container. If windowEl is undefined it will center the window that has focus.\r
-\r
-       */\r
-       centerWindow: function(windowEl){\r
-               \r
-               if(!windowEl){\r
-                       MochaUI.Windows.instances.each(function(instance){\r
-                               if (instance.windowEl.hasClass('isFocused')){\r
-                                       windowEl = instance.windowEl;\r
-                               }\r
-                       });\r
-               }\r
-\r
-               var currentInstance = MochaUI.Windows.instances.get(windowEl.id);\r
-               var options = currentInstance.options;\r
-               var dimensions = options.container.getCoordinates();\r
-               var windowPosTop = (dimensions.height * .5) - ((options.height + currentInstance.headerFooterShadow) * .5);\r
-               if (windowPosTop < 0) {\r
-                       windowPosTop = 0;\r
-               }\r
-               var windowPosLeft =     (dimensions.width * .5) - (options.width * .5);\r
-               if (windowPosLeft < 0) {\r
-                       windowPosLeft = 0;\r
-               }\r
-               if (MochaUI.options.useEffects == true){\r
-                       currentInstance.morph.start({\r
-                               'top': windowPosTop,\r
-                               'left': windowPosLeft\r
-                       });\r
-               }\r
-               else {\r
-                       windowEl.setStyles({\r
-                               'top': windowPosTop,\r
-                               'left': windowPosLeft\r
-                       });\r
-               }\r
-       },\r
-       notification: function(message){\r
-                       new MochaUI.Window({\r
-                               loadMethod: 'html',\r
-                               closeAfter: 1500,\r
-                               type: 'notification',\r
-                               addClass: 'notification',\r
-                               content: message,\r
-                               width: 220,\r
-                               height: 40,\r
-                               y: 53,\r
-                               padding:  { top: 10, right: 12, bottom: 10, left: 12 },\r
-                               shadowBlur: 5,\r
-                               bodyBgColor: [255, 255, 255]    \r
-                       });\r
-       },\r
-       /*\r
-\r
-       Function: dynamicResize\r
-               Use with a timer to resize a window as the window's content size changes, such as with an accordian.\r
-\r
-       */\r
-       dynamicResize: function(windowEl){\r
-               var currentInstance = MochaUI.Windows.instances.get(windowEl.id);\r
-               var contentWrapperEl = currentInstance.contentWrapperEl;\r
-               var contentEl = currentInstance.contentEl;\r
-               \r
-               contentWrapperEl.setStyle('height', contentEl.offsetHeight);\r
-               contentWrapperEl.setStyle('width', contentEl.offsetWidth);                      \r
-               currentInstance.drawWindow(windowEl);\r
-       },      \r
-       /*\r
-\r
-       Function: garbageCleanUp\r
-               Empties all windows of their children, and removes and garbages the windows. It is does not trigger onClose() or onCloseComplete(). This is useful to clear memory before the pageUnload.\r
-\r
-       Syntax:\r
-       (start code)\r
-               MochaUI.garbageCleanUp();\r
-       (end)\r
-       \r
-       */\r
-       garbageCleanUp: function(){\r
-               $$('div.mocha').each(function(el){\r
-                       el.destroy();\r
-               }.bind(this));\r
-       },\r
-       /*\r
-       \r
-       The underlay is inserted directly under windows when they are being dragged or resized\r
-       so that the cursor is not captured by iframes or other plugins (such as Flash)\r
-       underneath the window.\r
-       \r
-       */\r
-       underlayInitialize: function(){\r
-               var windowUnderlay = new Element('div', {\r
-                       'id': 'windowUnderlay',\r
-                       'styles': {\r
-                               'height': parent.getCoordinates().height,\r
-                               'opacity': .01,\r
-                               'display': 'none'\r
-                       }\r
-               }).inject(document.body);\r
-       },\r
-       setUnderlaySize: function(){\r
-               $('windowUnderlay').setStyle('height', parent.getCoordinates().height);\r
-       }\r
-});\r
-\r
-/* \r
-\r
-function: fixPNG\r
-       Bob Osola's PngFix for IE6.\r
-\r
-example:\r
-       (begin code)\r
-       <img src="xyz.png" alt="foo" width="10" height="20" onload="fixPNG(this)">\r
-       (end)\r
-\r
-note:\r
-       You must have the image height and width attributes specified in the markup.\r
-\r
-*/\r
-\r
-function fixPNG(myImage){\r
-       if (Browser.Engine.trident4 && document.body.filters){\r
-               var imgID = (myImage.id) ? "id='" + myImage.id + "' " : "";\r
-               var imgClass = (myImage.className) ? "class='" + myImage.className + "' " : "";\r
-               var imgTitle = (myImage.title) ? "title='" + myImage.title  + "' " : "title='" + myImage.alt + "' ";\r
-               var imgStyle = "display:inline-block;" + myImage.style.cssText;\r
-               var strNewHTML = "<span " + imgID + imgClass + imgTitle\r
-                       + " style=\"" + "width:" + myImage.width\r
-                       + "px; height:" + myImage.height\r
-                       + "px;" + imgStyle + ";"\r
-                       + "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader"\r
-                       + "(src=\'" + myImage.src + "\', sizingMethod='scale');\"></span>";\r
-               myImage.outerHTML = strNewHTML;         \r
-       }\r
-}\r
-\r
-// Toggle window visibility with Ctrl-Alt-Q\r
-document.addEvent('keydown', function(event){\r
-       if (event.key == 'q' && event.control && event.alt) {\r
-               MochaUI.toggleWindowVisibility();\r
-       }\r
-});\r
-\r
-// Blur all windows if user clicks anywhere else on the page\r
-document.addEvent('mousedown', function(event){\r
-       MochaUI.blurAll.delay(50);\r
-});\r
-\r
-document.addEvent('domready', function(){\r
-       MochaUI.underlayInitialize();\r
-});\r
-\r
-window.addEvent('resize', function(){\r
-               MochaUI.setUnderlaySize();\r
-});\r
-/*\r
-\r
-Script: Window.js\r
-       Build windows.\r
-\r
-Copyright:\r
-       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.\r
-\r
-License:\r
-       MIT-style license.      \r
-\r
-Requires:\r
-       Core.js\r
-\r
-*/\r
-\r
-/*\r
-Class: Window\r
-       Creates a single MochaUI window.\r
-       \r
-Syntax:\r
-       (start code)\r
-       new MochaUI.Window(options);\r
-       (end)   \r
-\r
-Arguments:\r
-       options\r
-\r
-Options:\r
-       id - The ID of the window. If not defined, it will be set to 'win' + windowIDCount.\r
-       title - The title of the window.\r
-       icon - Place an icon in the window's titlebar. This is either set to false or to the url of the icon. It is set up for icons that are 16 x 16px.\r
-       type - ('window', 'modal', 'modal2', or 'notification') Defaults to 'window'.\r
-       loadMethod - ('html', 'xhr', or 'iframe') Defaults to 'html'.\r
-       contentURL - Used if loadMethod is set to 'xhr' or 'iframe'.\r
-       closeAfter - Either false or time in milliseconds. Closes the window after a certain period of time in milliseconds. This is particularly useful for notifications.\r
-       evalScripts - (boolean) An xhr loadMethod option. Defaults to true.\r
-       evalResponse - (boolean) An xhr loadMethod option. Defaults to false.\r
-       content - (string or element) An html loadMethod option.\r
-       toolbar - (boolean) Create window toolbar. Defaults to false. This can be used for tabs, media controls, and so forth.\r
-       toolbarPosition - ('top' or 'bottom') Defaults to top.\r
-       toolbarHeight - (number)\r
-       toolbarURL - (url) Defaults to 'pages/lipsum.html'.\r
-       toolbarContent - (string)\r
-       container - (element ID) Element the window is injected in. The container defaults to 'desktop'. If no desktop then to document.body. Use 'pageWrapper' if you don't want the windows to overlap the toolbars.\r
-       restrict - (boolean) Restrict window to container when dragging.\r
-       shape - ('box' or 'gauge') Shape of window. Defaults to 'box'.\r
-       collapsible - (boolean) Defaults to true.\r
-       minimizable - (boolean) Requires MochaUI.Desktop and MochaUI.Dock. Defaults to true if dependenices are met. \r
-       maximizable - (boolean) Requires MochaUI.Desktop. Defaults to true if dependenices are met.\r
-       closable - (boolean) Defaults to true. \r
-       draggable - (boolean) Defaults to false for modals; otherwise true.\r
-       draggableGrid - (false or number) Distance in pixels for snap-to-grid dragging. Defaults to false. \r
-       draggableLimit - (false or number) An object with x and y properties used to limit the movement of the Window. Defaults to false.\r
-       draggableSnap - (boolean) The distance to drag before the Window starts to respond to the drag. Defaults to false.\r
-       resizable - (boolean) Defaults to false for modals, notifications and gauges; otherwise true.\r
-       resizeLimit - (object) Minimum and maximum width and height of window when resized.\r
-       addClass - (string) Add a class to the window for more control over styling.    \r
-       width - (number) Width of content area. \r
-       height - (number) Height of content area.\r
-       x - (number) If x and y are left undefined the window is centered on the page.\r
-       y - (number)\r
-       scrollbars - (boolean)\r
-       padding - (object)\r
-       shadowBlur - (number) Width of shadows.\r
-       shadowOffset - Should be positive and not be greater than the ShadowBlur.\r
-       controlsOffset - Change this if you want to reposition the window controls.\r
-       useCanvas - (boolean) Set this to false if you don't want a canvas body.\r
-       useCanvasControls - (boolean) Set this to false if you wish to use images for the buttons.\r
-       headerHeight - (number) Height of window titlebar.\r
-       footerHeight - (number) Height of window footer.\r
-       cornerRadius - (number)\r
-       contentBgColor - (hex) Body background color\r
-       headerStartColor - ([r,g,b,]) Titlebar gradient's top color\r
-       headerStopColor - ([r,g,b,]) Titlebar gradient's bottom color\r
-       bodyBgColor - ([r,g,b,]) Background color of the main canvas shape\r
-       minimizeBgColor - ([r,g,b,]) Minimize button background color\r
-       minimizeColor - ([r,g,b,]) Minimize button color\r
-       maximizeBgColor - ([r,g,b,]) Maximize button background color\r
-       maximizeColor - ([r,g,b,]) Maximize button color\r
-       closeBgColor - ([r,g,b,]) Close button background color\r
-       closeColor - ([r,g,b,]) Close button color\r
-       resizableColor - ([r,g,b,]) Resizable icon color\r
-       onBeforeBuild - (function) Fired just before the window is built.\r
-       onContentLoaded - (function) Fired when content is successfully loaded via XHR or Iframe.\r
-       onFocus - (function)  Fired when the window is focused.\r
-       onBlur - (function) Fired when window loses focus.\r
-       onResize - (function) Fired when the window is resized.\r
-       onMinimize - (function) Fired when the window is minimized.\r
-       onMaximize - (function) Fired when the window is maximized.\r
-       onRestore - (function) Fired when a window is restored from minimized or maximized.\r
-       onClose - (function) Fired just before the window is closed.\r
-       onCloseComplete - (function) Fired after the window is closed.\r
-\r
-Returns:\r
-       Window object.\r
-\r
-Example:\r
-       Define a window. It is suggested you name the function the same as your window ID + "Window".\r
-       (start code)\r
-       var mywindowWindow = function(){\r
-               new MochaUI.Window({\r
-                       id: 'mywindow',\r
-                       title: 'My Window',\r
-                       loadMethod: 'xhr',\r
-                       contentURL: 'pages/lipsum.html',\r
-                       width: 340,\r
-                       height: 150\r
-               });\r
-       }\r
-       (end)\r
-\r
-Example:\r
-       Create window onDomReady.\r
-       (start code)    \r
-       window.addEvent('domready', function(){\r
-               mywindow();\r
-       });\r
-       (end)\r
-\r
-Example:\r
-       Add link events to build future windows. It is suggested you give your anchor the same ID as your window + "WindowLink" or + "WindowLinkCheck". Use the latter if it is a link in the menu toolbar.\r
-\r
-       If you wish to add links in windows that open other windows remember to add events to those links when the windows are created.\r
-\r
-       (start code)\r
-       // Javascript:\r
-       if ($('mywindowLink')){\r
-               $('mywindowLink').addEvent('click', function(e) {\r
-                       new Event(e).stop();\r
-                       mywindow();\r
-               });\r
-       }\r
-\r
-       // HTML:\r
-       <a id="mywindowLink" href="pages/lipsum.html">My Window</a>     \r
-       (end)\r
-\r
-\r
-       Loading Content with an XMLHttpRequest(xhr):\r
-               For content to load via xhr all the files must be online and in the same domain. If you need to load content from another domain or wish to have it work offline, load the content in an iframe instead of using the xhr option.\r
-       \r
-       Iframes:\r
-               If you use the iframe loadMethod your iframe will automatically be resized when the window it is in is resized. If you want this same functionality when using one of the other load options simply add class="mochaIframe" to those iframes and they will be resized for you as well.\r
-\r
-*/\r
-\r
-// Having these options outside of the Class allows us to add, change, and remove\r
-// individual options without rewriting all of them.\r
-\r
-MochaUI.Windows.windowOptions = {\r
-       id:                null,\r
-       title:             'New Window',\r
-       icon:              false,\r
-       type:              'window',\r
-\r
-       loadMethod:        'html',\r
-       contentURL:        'pages/lipsum.html',\r
-\r
-       closeAfter:        false,\r
-\r
-       // xhr options\r
-       evalScripts:       true,\r
-       evalResponse:      false,\r
-\r
-       // html options\r
-       content:           'Window content',\r
-\r
-       // Toolbar\r
-       toolbar:           false,\r
-       toolbarPosition:   'top',\r
-       toolbarHeight:     29,\r
-       toolbarURL:        'pages/lipsum.html',\r
-       toolbarContent:    '',\r
-  toolbarLoadMethod: 'xhr',\r
-  \r
-       // Toolbar\r
-       toolbar2:           false,\r
-       toolbar2Position:   'bottom',\r
-       toolbar2Height:     29,\r
-       toolbar2URL:        'pages/lipsum.html',\r
-       toolbar2Content:    '', \r
-  toolbar2LoadMethod: 'xhr',\r
-  \r
-       // Container options\r
-       container:         null,\r
-       restrict:          true,\r
-       shape:             'box',\r
-\r
-       // Window Controls\r
-       collapsible:       true,\r
-       minimizable:       true,\r
-       maximizable:       true,\r
-       closable:          true,\r
-\r
-       // Draggable\r
-       draggable:         null,\r
-       draggableGrid:     false,\r
-       draggableLimit:    false,\r
-       draggableSnap:     false,\r
-\r
-       // Resizable\r
-       resizable:         null,\r
-       resizeLimit:       {'x': [250, 2500], 'y': [125, 2000]},\r
-       \r
-       // Style options:\r
-       addClass:          '',\r
-       width:             300,\r
-       height:            125,\r
-       x:                 null,\r
-       y:                 null,\r
-       scrollbars:        true,\r
-       padding:                   { top: 10, right: 12, bottom: 10, left: 12 },\r
-       shadowBlur:        5,\r
-       shadowOffset:      {'x': 0, 'y': 1},\r
-       controlsOffset:    {'right': 6, 'top': 6},\r
-       useCanvas:         true,\r
-       useCanvasControls: true,\r
-       useSpinner:        true,    // Toggles whether or not the ajax spinners are displayed in window footers.\r
-\r
-       // Color options:               \r
-       headerHeight:      25,\r
-       footerHeight:      25,\r
-       cornerRadius:      8,\r
-       contentBgColor:    '#fff',\r
-       headerStartColor:  [250, 250, 250],\r
-       headerStopColor:   [229, 229, 229],\r
-       bodyBgColor:       [229, 229, 229],\r
-       minimizeBgColor:   [255, 255, 255],\r
-       minimizeColor:     [0, 0, 0],\r
-       maximizeBgColor:   [255, 255, 255],\r
-       maximizeColor:     [0, 0, 0],\r
-       closeBgColor:      [255, 255, 255],\r
-       closeColor:        [0, 0, 0],\r
-       resizableColor:    [254, 254, 254],\r
-\r
-       // Events\r
-       onBeforeBuild:     $empty,\r
-       onContentLoaded:   $empty,\r
-       onFocus:           $empty,\r
-       onBlur:            $empty,\r
-       onResize:          $empty,\r
-       onMinimize:        $empty,\r
-       onMaximize:        $empty,\r
-       onRestore:         $empty,\r
-       onClose:           $empty,\r
-       onCloseComplete:   $empty\r
-};\r
-\r
-MochaUI.Window = new Class({\r
-       options: MochaUI.Windows.windowOptions,\r
-       initialize: function(options){\r
-               this.setOptions(options);\r
-\r
-               // Shorten object chain\r
-               var options = this.options;\r
-\r
-               $extend(this, {\r
-                       mochaControlsWidth: 0,\r
-                       minimizebuttonX:  0,  // Minimize button horizontal position\r
-                       maximizebuttonX: 0,  // Maximize button horizontal position\r
-                       closebuttonX: 0,  // Close button horizontal position\r
-                       headerFooterShadow: options.headerHeight + options.footerHeight + (options.shadowBlur * 2),\r
-                       oldTop: 0,\r
-                       oldLeft: 0,\r
-                       isMaximized: false,\r
-                       isMinimized: false,\r
-                       isCollapsed: false,\r
-                       timestamp: $time()\r
-               });\r
-               \r
-               // May be better to use if type != window\r
-               if (options.type != 'window'){\r
-                       options.container = document.body;\r
-                       options.minimizable = false;\r
-               }\r
-               if (!options.container){\r
-                       options.container = MochaUI.Desktop.desktop ? MochaUI.Desktop.desktop : document.body;\r
-               }\r
-\r
-               // Set this.options.resizable to default if it was not defined\r
-               if (options.resizable == null){\r
-                       if (options.type != 'window' || options.shape == 'gauge'){\r
-                               options.resizable = false;\r
-                       }\r
-                       else {\r
-                               options.resizable = true;       \r
-                       }\r
-               }\r
-\r
-               // Set this.options.draggable if it was not defined\r
-               if (options.draggable == null){\r
-                       if (options.type != 'window'){\r
-                               options.draggable = false;\r
-                       }\r
-                       else {\r
-                               options.draggable = true;\r
-                       }\r
-               }\r
-\r
-               // Gauges are not maximizable or resizable\r
-               if (options.shape == 'gauge' || options.type == 'notification'){\r
-                       options.collapsible = false;\r
-                       options.maximizable = false;\r
-                       options.contentBgColor = 'transparent';\r
-                       options.scrollbars = false;\r
-                       options.footerHeight = 0;\r
-               }\r
-               if (options.type == 'notification'){\r
-                       options.closable = false;\r
-                       options.headerHeight = 0;\r
-               }\r
-               \r
-               // Minimizable, dock is required and window cannot be modal\r
-               if (MochaUI.Dock && $(MochaUI.options.dock)){\r
-                       if (MochaUI.Dock.dock && options.type != 'modal' && options.type != 'modal2'){\r
-                               options.minimizable = options.minimizable;\r
-                       }\r
-               }\r
-               else {\r
-                       options.minimizable = false;\r
-               }\r
-\r
-               // Maximizable, desktop is required\r
-               options.maximizable = MochaUI.Desktop.desktop && options.maximizable && options.type != 'modal' && options.type != 'modal2';\r
-\r
-               if (this.options.type == 'modal2') {\r
-                       this.options.shadowBlur = 0;\r
-                       this.options.shadowOffset = {'x': 0, 'y': 0};\r
-                       this.options.useSpinner = false;\r
-                       this.options.useCanvas = false;\r
-                       this.options.footerHeight = 0;\r
-                       this.options.headerHeight = 0;\r
-               }\r
-\r
-               // If window has no ID, give it one.\r
-               if (options.id == null){\r
-                       options.id = 'win' + (++MochaUI.Windows.windowIDCount);\r
-               }\r
-               this.windowEl = $(options.id);\r
-               \r
-               this.newWindow();\r
-               \r
-               // Return window object\r
-               return this;\r
-       },\r
-       saveValues: function(){ \r
-               var coordinates = this.windowEl.getCoordinates();\r
-               this.options.x = coordinates.left.toInt();\r
-               this.options.y = coordinates.top.toInt();\r
-       },\r
-       /*\r
-\r
-       Internal Function: newWindow\r
-       \r
-       Arguments: \r
-               properties\r
-\r
-       */\r
-       newWindow: function(properties){ // options is not doing anything\r
-\r
-               // Shorten object chain\r
-               var instances = MochaUI.Windows.instances;\r
-               var instanceID = instances.get(this.options.id);\r
-       \r
-               // Here we check to see if there is already a class instance for this window\r
-               if (instanceID){\r
-                       var currentInstance = instanceID;\r
-               }\r
-\r
-               // Check if window already exists and is not in progress of closing\r
-               if ( this.windowEl && !this.isClosing ){\r
-                        // Restore if minimized\r
-                       if (currentInstance.isMinimized){\r
-                               MochaUI.Dock.restoreMinimized(this.windowEl);\r
-                       }\r
-                       // Expand and focus if collapsed\r
-                       if (currentInstance.isCollapsed){\r
-                               MochaUI.collapseToggle(this.windowEl);\r
-                               setTimeout(MochaUI.focusWindow.pass(this.windowEl, this),10);\r
-                       }\r
-                       // Else focus\r
-                       else {\r
-                               var coordinates = document.getCoordinates();\r
-                               if (this.windowEl.getStyle('left').toInt() > coordinates.width || this.windowEl.getStyle('top').toInt() > coordinates.height){\r
-                                       MochaUI.centerWindow(this.windowEl);    \r
-                               }\r
-                               setTimeout(MochaUI.focusWindow.pass(this.windowEl, this),10);\r
-                       }\r
-                       return;\r
-               }\r
-               else {\r
-                       instances.set(this.options.id, this);\r
-               }\r
-\r
-               this.isClosing = false;\r
-               this.fireEvent('onBeforeBuild');\r
-\r
-               // Create window div\r
-               MochaUI.Windows.indexLevel++;\r
-               this.windowEl = new Element('div', {\r
-                       'class': 'mocha',\r
-                       'id':    this.options.id,\r
-                       'styles': {\r
-                               'width':   this.options.width,\r
-                               'height':  this.options.height,\r
-                               'display': 'block',\r
-                               'opacity': 0,\r
-                               'zIndex': MochaUI.Windows.indexLevel += 2\r
-                       }\r
-               });\r
-\r
-               this.windowEl.addClass(this.options.addClass);\r
-               \r
-               if (this.options.type == 'modal2') {\r
-                       this.windowEl.addClass('modal2');\r
-               }\r
-\r
-               // Fix a mouseover issue with gauges in IE7\r
-               if ( Browser.Engine.trident && this.options.shape == 'gauge') {\r
-                       this.windowEl.setStyle('background', 'url(../images/spacer.gif)');\r
-               }\r
-\r
-               if ((this.options.type == 'modal' || this.options.type == 'modal2' ) && Browser.Platform.mac && Browser.Engine.gecko){\r
-                       if (/Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent)) {\r
-                               var ffversion = new Number(RegExp.$1);\r
-                               if (ffversion < 3) {\r
-                                       this.windowEl.setStyle('position', 'fixed');\r
-                               }\r
-                       }\r
-               }\r
-\r
-               if (this.options.loadMethod == 'iframe') {\r
-                       this.options.padding = { top: 0, right: 0, bottom: 0, left: 0 };\r
-               }\r
-\r
-               // Insert sub elements inside windowEl\r
-               this.insertWindowElements();\r
-\r
-               // Set title\r
-               this.titleEl.set('html',this.options.title);\r
-\r
-               // Set scrollbars, always use 'hidden' for iframe windows\r
-               this.contentWrapperEl.setStyles({\r
-                       'overflow': 'hidden',\r
-                       'background': this.options.contentBgColor\r
-               });\r
-\r
-               this.contentEl.setStyles({\r
-                       'padding-top': this.options.padding.top,\r
-                       'padding-bottom': this.options.padding.bottom,\r
-                       'padding-left': this.options.padding.left,\r
-                       'padding-right': this.options.padding.right\r
-               });\r
-\r
-\r
-               if (this.options.shape == 'gauge'){\r
-                       if (this.options.useCanvasControls){\r
-                               this.canvasControlsEl.setStyle('display', 'none');\r
-                       }\r
-                       else {\r
-                               this.controlsEl.setStyle('display', 'none');\r
-                       }\r
-                       this.windowEl.addEvent('mouseover', function(){\r
-                               this.mouseover = true;\r
-                               var showControls = function(){\r
-                                       if (this.mouseover != false){\r
-                                               if (this.options.useCanvasControls){\r
-                                                       this.canvasControlsEl.setStyle('display', 'block');\r
-                                               }\r
-                                               else {\r
-                                                       this.controlsEl.setStyle('display', 'block');\r
-                                               }\r
-                                               this.canvasHeaderEl.setStyle('display', 'block');\r
-                                               this.titleEl.setStyle('display', 'block');\r
-                                       }\r
-                               };\r
-                               showControls.delay(150, this);\r
-\r
-                       }.bind(this));\r
-                       this.windowEl.addEvent('mouseleave', function(){\r
-                               this.mouseover = false;\r
-                               if (this.options.useCanvasControls){\r
-                                       this.canvasControlsEl.setStyle('display', 'none');\r
-                               }\r
-                               else {\r
-                                       this.controlsEl.setStyle('display', 'none');\r
-                               }\r
-                               this.canvasHeaderEl.setStyle('display', 'none');\r
-                               this.titleEl.setStyle('display', 'none');\r
-                       }.bind(this));\r
-               }\r
-\r
-               // Inject window into DOM\r
-               this.windowEl.injectInside(this.options.container);\r
-\r
-               if (this.options.type != 'notification'){\r
-                       this.setMochaControlsWidth();\r
-               }               \r
-\r
-               // Add content to window.\r
-               MochaUI.updateContent({\r
-                       'element': this.windowEl,\r
-                       'content':  this.options.content,\r
-                       'url':      this.options.contentURL\r
-               });     \r
-               \r
-               // Add content to window toolbar.\r
-               if (this.options.toolbar == true){\r
-                       MochaUI.updateContent({\r
-                               'element':       this.windowEl,\r
-                               'childElement':  this.toolbarEl,\r
-                               'content':       this.options.toolbarContent,\r
-                               'loadMethod':    this.options.toolbarLoadMethod,\r
-                               'url':           this.options.toolbarURL\r
-                       });\r
-               }\r
-\r
-               // Add content to window toolbar.\r
-               if (this.options.toolbar2 == true){\r
-                       MochaUI.updateContent({\r
-                               'element':       this.windowEl,\r
-                               'childElement':  this.toolbar2El,\r
-                               'content':       this.options.toolbar2Content,\r
-                               'loadMethod':    this.options.toolbar2LoadMethod,\r
-                               'url':           this.options.toolbar2URL\r
-                       });\r
-               }\r
-               \r
-               this.drawWindow(this.windowEl);\r
-               \r
-               // Attach events to the window\r
-               this.attachDraggable(this.windowEl);\r
-               this.attachResizable(this.windowEl);\r
-               this.setupEvents(this.windowEl);\r
-               \r
-               if (this.options.resizable){\r
-                       this.adjustHandles();\r
-               }\r
-\r
-               // Move window into position. If position not specified by user then center the window on the page.\r
-               if (this.options.container == document.body || this.options.container == MochaUI.Desktop.desktop){\r
-                       var dimensions = window.getSize();\r
-               }\r
-               else {\r
-                       var dimensions = $(this.options.container).getSize();\r
-               }\r
-\r
-               if (!this.options.y) {\r
-                       var y = (dimensions.y * .5) - ((this.options.height + this.headerFooterShadow + this.windowEl.getStyle('border-top').toInt() + this.windowEl.getStyle('border-bottom').toInt()) * .5);\r
-               }\r
-               else {\r
-                       var y = this.options.y - this.options.shadowBlur;\r
-               }\r
-\r
-               if (!this.options.x) {\r
-                       var x = (dimensions.x * .5) - (this.options.width * .5);\r
-               }\r
-               else {\r
-                       var x = this.options.x - this.options.shadowBlur;\r
-               }\r
-\r
-               this.windowEl.setStyles({\r
-                       'top': y,\r
-                       'left': x\r
-               });\r
-\r
-               // Create opacityMorph\r
-               if (MochaUI.options.useEffects == true){\r
-                       // IE cannot handle both element opacity and VML alpha at the same time.\r
-                       if (Browser.Engine.trident){\r
-                               this.drawWindow(this.windowEl, false);\r
-                       }\r
-                       this.opacityMorph = new Fx.Morph(this.windowEl, {\r
-                               'duration': 350,\r
-                               onComplete: function(){\r
-                                       if (Browser.Engine.trident){\r
-                                               this.drawWindow(this.windowEl);\r
-                                       }\r
-                               }.bind(this)\r
-                       });\r
-               }\r
-\r
-               if (this.options.type == 'modal' || this.options.type == 'modal2') {\r
-                       MochaUI.currentModal = this.windowEl;\r
-                       if (Browser.Engine.trident4){\r
-                               $('modalFix').setStyle('display', 'block');\r
-                       }\r
-                       $('modalOverlay').setStyle('display', 'block');\r
-                       if (MochaUI.options.useEffects == false){\r
-                               $('modalOverlay').setStyle('opacity', .6);\r
-                               this.windowEl.setStyles({\r
-                                       'zIndex': 11000,\r
-                                       'opacity': 1\r
-                               });\r
-                       }\r
-                       else {\r
-                               MochaUI.Modal.modalOverlayCloseMorph.cancel();\r
-                               MochaUI.Modal.modalOverlayOpenMorph.start({\r
-                                       'opacity': .6\r
-                               });\r
-                               this.windowEl.setStyles({\r
-                                       'zIndex': 11000\r
-                               });\r
-                               this.opacityMorph.start({\r
-                                       'opacity': 1\r
-                               });\r
-                       }\r
-\r
-                       $$('.dockTab').removeClass('activeDockTab');\r
-                       $$('.mocha').removeClass('isFocused');\r
-                       this.windowEl.addClass('isFocused');\r
-                       \r
-               }\r
-               else if (MochaUI.options.useEffects == false){\r
-                       this.windowEl.setStyle('opacity', 1);\r
-                       setTimeout(MochaUI.focusWindow.pass(this.windowEl, this), 10);\r
-               }\r
-               else {\r
-                       this.opacityMorph.start({\r
-                               'opacity': 1\r
-                       });\r
-                       setTimeout(MochaUI.focusWindow.pass(this.windowEl, this), 10);\r
-               }\r
-\r
-               // This is a generic morph that can be reused later by functions like centerWindow()\r
-               this.morph = new Fx.Morph(this.windowEl, {\r
-                       'duration': 200\r
-               });\r
-\r
-               // Add check mark to menu if link exists in menu\r
-               // Need to make sure the check mark is not added to links not in menu\r
-       \r
-               if ($(this.windowEl.id + 'LinkCheck')){\r
-                       this.check = new Element('div', {\r
-                               'class': 'check',\r
-                               'id': this.options.id + '_check'\r
-                       }).inject(this.windowEl.id + 'LinkCheck');\r
-               }\r
-               \r
-               if (this.options.closeAfter != false){\r
-                       MochaUI.closeWindow.delay(this.options.closeAfter, this, this.windowEl);\r
-               }\r
-\r
-               if (MochaUI.Dock && $(MochaUI.options.dock) && this.options.type == 'window' ){\r
-                       MochaUI.Dock.createDockTab(this.windowEl);\r
-               }\r
-\r
-       },\r
-       setupEvents: function(windowEl) {\r
-\r
-               // Set events\r
-               // Note: if a button does not exist, its due to properties passed to newWindow() stating otherwice\r
-               if (this.closeButtonEl){\r
-                       this.closeButtonEl.addEvent('click', function(e) {\r
-                               new Event(e).stop();\r
-                               MochaUI.closeWindow(windowEl);\r
-                       }.bind(this));\r
-               }\r
-\r
-               if (this.options.type == 'window'){\r
-                       windowEl.addEvent('mousedown', function() {\r
-                               MochaUI.focusWindow(windowEl);\r
-                       }.bind(this));\r
-               }\r
-\r
-               if (this.minimizeButtonEl) {\r
-                       this.minimizeButtonEl.addEvent('click', function(e) {\r
-                               new Event(e).stop();\r
-                               MochaUI.Dock.minimizeWindow(windowEl);\r
-               }.bind(this));\r
-               }\r
-\r
-               if (this.maximizeButtonEl) {\r
-                       this.maximizeButtonEl.addEvent('click', function(e) {\r
-                               new Event(e).stop(); \r
-                               if (this.isMaximized) {\r
-                                       MochaUI.Desktop.restoreWindow(windowEl);\r
-                               } else {\r
-                                       MochaUI.Desktop.maximizeWindow(windowEl);\r
-                               }\r
-                       }.bind(this));\r
-               }\r
-\r
-               if (this.options.collapsible == true){\r
-                       // Keep titlebar text from being selected on double click in Safari.\r
-                       this.titleEl.addEvent('selectstart', function(e) {\r
-                               e = new Event(e).stop();\r
-                       }.bind(this));\r
-                       // Keep titlebar text from being selected on double click in Opera.\r
-                       this.titleBarEl.addEvent('mousedown', function(e) {\r
-                               if (Browser.Engine.trident) {\r
-                                       this.titleEl.setCapture();\r
-                               }\r
-                       }.bind(this));\r
-                       this.titleBarEl.addEvent('mouseup', function(e) {\r
-                               if (Browser.Engine.trident) {\r
-                                       this.titleEl.releaseCapture();\r
-                               }\r
-                       }.bind(this));\r
-                       this.titleBarEl.addEvent('dblclick', function(e) {\r
-                               e = new Event(e).stop();\r
-                               MochaUI.collapseToggle(this.windowEl);\r
-                       }.bind(this));\r
-               }\r
-\r
-       },\r
-       /*\r
-\r
-       Internal Function: attachDraggable()\r
-               Make window draggable.\r
-\r
-       Arguments:\r
-               windowEl\r
-               \r
-       */\r
-       attachDraggable: function(windowEl){\r
-               if (!this.options.draggable) return;\r
-               this.windowDrag = new Drag.Move(windowEl, {\r
-                       handle: this.titleBarEl,\r
-                       container: this.options.restrict == true ? $(this.options.container) : false,\r
-                       grid: this.options.draggableGrid,\r
-                       limit: this.options.draggableLimit,\r
-                       snap: this.options.draggableSnap,\r
-                       onStart: function() {\r
-                               if (this.options.type != 'modal' && this.options.type != 'modal2'){ \r
-                                       MochaUI.focusWindow(windowEl);\r
-                                       $('windowUnderlay').setStyle('display','block');\r
-                               }\r
-                               if ( this.iframeEl )\r
-                                       this.iframeEl.setStyle('visibility', 'hidden');\r
-                       }.bind(this),\r
-                       onComplete: function() {\r
-                               if (this.options.type != 'modal' && this.options.type != 'modal2') {\r
-                                       $('windowUnderlay').setStyle('display', 'none');\r
-                               }\r
-                               if ( this.iframeEl ){\r
-                                       this.iframeEl.setStyle('visibility', 'visible');\r
-                               }\r
-                               // Store new position in options.\r
-                               this.saveValues();\r
-                       }.bind(this)\r
-               });\r
-       },\r
-       /*\r
-\r
-       Internal Function: attachResizable\r
-               Make window resizable.\r
-\r
-       Arguments:\r
-               windowEl\r
-\r
-       */\r
-       attachResizable: function(windowEl){\r
-               if (!this.options.resizable) return;\r
-               this.resizable1 = this.windowEl.makeResizable({\r
-                       handle: [this.n, this.ne, this.nw],\r
-                       limit: {\r
-                               y: [\r
-                                       function(){\r
-                                               return this.windowEl.getStyle('top').toInt() + this.windowEl.getStyle('height').toInt() - this.options.resizeLimit.y[1];\r
-                                       }.bind(this),\r
-                                       function(){\r
-                                               return this.windowEl.getStyle('top').toInt() + this.windowEl.getStyle('height').toInt() - this.options.resizeLimit.y[0];\r
-                                       }.bind(this)\r
-                               ]\r
-                       },\r
-                       modifiers: {x: false, y: 'top'},\r
-                       onStart: function(){\r
-                               this.resizeOnStart();\r
-                               this.coords = this.contentWrapperEl.getCoordinates();\r
-                               this.y2 = this.coords.top.toInt() + this.contentWrapperEl.offsetHeight;\r
-                       }.bind(this),\r
-                       onDrag: function(){\r
-                               this.coords = this.contentWrapperEl.getCoordinates();\r
-                               this.contentWrapperEl.setStyle('height', this.y2 - this.coords.top.toInt());\r
-                               this.drawWindow(windowEl);\r
-                               this.adjustHandles();\r
-                       }.bind(this),\r
-                       onComplete: function(){\r
-                               this.resizeOnComplete();\r
-                       }.bind(this)\r
-               });\r
-\r
-               this.resizable2 = this.contentWrapperEl.makeResizable({\r
-                       handle: [this.e, this.ne],\r
-                       limit: {\r
-                               x: [this.options.resizeLimit.x[0] - (this.options.shadowBlur * 2), this.options.resizeLimit.x[1] - (this.options.shadowBlur * 2) ]\r
-                       },      \r
-                       modifiers: {x: 'width', y: false},\r
-                       onStart: function(){\r
-                               this.resizeOnStart();\r
-                       }.bind(this),\r
-                       onDrag: function(){\r
-                               this.drawWindow(windowEl);\r
-                               this.adjustHandles();\r
-                       }.bind(this),\r
-                       onComplete: function(){\r
-                               this.resizeOnComplete();\r
-                       }.bind(this)\r
-               });\r
-\r
-               this.resizable3 = this.contentWrapperEl.makeResizable({\r
-                       container: this.options.restrict == true ? $(this.options.container) : false,\r
-                       handle: this.se,\r
-                       limit: {\r
-                               x: [this.options.resizeLimit.x[0] - (this.options.shadowBlur * 2), this.options.resizeLimit.x[1] - (this.options.shadowBlur * 2) ],\r
-                               y: [this.options.resizeLimit.y[0] - this.headerFooterShadow, this.options.resizeLimit.y[1] - this.headerFooterShadow]\r
-                       },\r
-                       modifiers: {x: 'width', y: 'height'},\r
-                       onStart: function(){\r
-                               this.resizeOnStart();\r
-                       }.bind(this),\r
-                       onDrag: function(){\r
-                               this.drawWindow(windowEl);      \r
-                               this.adjustHandles();\r
-                       }.bind(this),\r
-                       onComplete: function(){\r
-                               this.resizeOnComplete();\r
-                       }.bind(this)    \r
-               });\r
-\r
-               this.resizable4 = this.contentWrapperEl.makeResizable({\r
-                       handle: [this.s, this.sw],\r
-                       limit: {\r
-                               y: [this.options.resizeLimit.y[0] - this.headerFooterShadow, this.options.resizeLimit.y[1] - this.headerFooterShadow]\r
-                       },\r
-                       modifiers: {x: false, y: 'height'},\r
-                       onStart: function(){\r
-                               this.resizeOnStart();\r
-                       }.bind(this),\r
-                       onDrag: function(){\r
-                               this.drawWindow(windowEl);\r
-                               this.adjustHandles();\r
-                       }.bind(this),\r
-                       onComplete: function(){\r
-                               this.resizeOnComplete();\r
-                       }.bind(this)\r
-               });\r
-\r
-               this.resizable5 = this.windowEl.makeResizable({\r
-                       handle: [this.w, this.sw, this.nw],\r
-                       limit: {\r
-                               x: [\r
-                                       function(){\r
-                                               return this.windowEl.getStyle('left').toInt() + this.windowEl.getStyle('width').toInt() - this.options.resizeLimit.x[1];\r
-                                       }.bind(this),\r
-                                  function(){\r
-                                          return this.windowEl.getStyle('left').toInt() + this.windowEl.getStyle('width').toInt() - this.options.resizeLimit.x[0];\r
-                                       }.bind(this)\r
-                               ]\r
-                       },\r
-                       modifiers: {x: 'left', y: false},\r
-                       onStart: function(){\r
-                               this.resizeOnStart();\r
-                               this.coords = this.contentWrapperEl.getCoordinates();\r
-                               this.x2 = this.coords.left.toInt() + this.contentWrapperEl.offsetWidth;\r
-                       }.bind(this),\r
-                       onDrag: function(){\r
-                               this.coords = this.contentWrapperEl.getCoordinates();\r
-                               this.contentWrapperEl.setStyle('width', this.x2 - this.coords.left.toInt());\r
-                               this.drawWindow(windowEl);\r
-                               this.adjustHandles();\r
-                       }.bind(this),\r
-                       onComplete: function(){\r
-                               this.resizeOnComplete();\r
-                       }.bind(this)\r
-               });\r
-\r
-       },\r
-       resizeOnStart: function(){\r
-               $('windowUnderlay').setStyle('display','block');\r
-               if (this.iframeEl){\r
-                       this.iframeEl.setStyle('visibility', 'hidden');\r
-               }       \r
-       },      \r
-       resizeOnComplete: function(){\r
-               $('windowUnderlay').setStyle('display','none');\r
-               if (this.iframeEl){\r
-                       this.iframeEl.setStyle('visibility', 'visible');\r
-               }\r
-               this.fireEvent('onResize', this.windowEl);\r
-       },\r
-       adjustHandles: function(){\r
-\r
-               var shadowBlur = this.options.shadowBlur;\r
-               var shadowBlur2x = shadowBlur * 2;\r
-               var shadowOffset = this.options.shadowOffset;\r
-               var top = shadowBlur - shadowOffset.y - 1;\r
-               var right = shadowBlur + shadowOffset.x - 1;\r
-               var bottom = shadowBlur + shadowOffset.y - 1;\r
-               var left = shadowBlur - shadowOffset.x - 1;\r
-               \r
-               var coordinates = this.windowEl.getCoordinates();\r
-               var width = coordinates.width - shadowBlur2x + 2;\r
-               var height = coordinates.height - shadowBlur2x + 2;\r
-\r
-               this.n.setStyles({\r
-                       'top': top,\r
-                       'left': left + 10,\r
-                       'width': width - 20\r
-               });\r
-               this.e.setStyles({\r
-                       'top': top + 10,\r
-                       'right': right,\r
-                       'height': height - 30\r
-               });\r
-               this.s.setStyles({\r
-                       'bottom': bottom,\r
-                       'left': left + 10,\r
-                       'width': width - 30\r
-               });\r
-               this.w.setStyles({\r
-                       'top': top + 10,\r
-                       'left': left,\r
-                       'height': height - 20\r
-               });\r
-               this.ne.setStyles({\r
-                       'top': top,\r
-                       'right': right  \r
-               });\r
-               this.se.setStyles({\r
-                       'bottom': bottom,\r
-                       'right': right\r
-               });\r
-               this.sw.setStyles({\r
-                       'bottom': bottom,\r
-                       'left': left\r
-               });\r
-               this.nw.setStyles({\r
-                       'top': top,\r
-                       'left': left\r
-               });\r
-       },\r
-       detachResizable: function(){\r
-                       this.resizable1.detach();\r
-                       this.resizable2.detach();\r
-                       this.resizable3.detach();\r
-                       this.resizable4.detach();\r
-                       this.resizable5.detach();\r
-                       this.windowEl.getElements('.handle').setStyle('display', 'none');\r
-       },\r
-       reattachResizable: function(){\r
-                       this.resizable1.attach();\r
-                       this.resizable2.attach();\r
-                       this.resizable3.attach();\r
-                       this.resizable4.attach();\r
-                       this.resizable5.attach();\r
-                       this.windowEl.getElements('.handle').setStyle('display', 'block');\r
-       },\r
-       /*\r
-\r
-       Internal Function: insertWindowElements\r
-\r
-       Arguments:\r
-               windowEl\r
-\r
-       */\r
-       insertWindowElements: function(){\r
-               \r
-               var options = this.options;\r
-               var height = options.height;\r
-               var width = options.width;\r
-               var id = options.id;\r
-\r
-               var cache = {};\r
-\r
-               if (Browser.Engine.trident4){\r
-                       cache.zIndexFixEl = new Element('iframe', {\r
-                               'id': id + '_zIndexFix',\r
-                               'class': 'zIndexFix',\r
-                               'scrolling': 'no',\r
-                               'marginWidth': 0,\r
-                               'marginHeight': 0,\r
-                               'src': ''\r
-                       }).inject(this.windowEl);\r
-               }\r
-\r
-               cache.overlayEl = new Element('div', {\r
-                       'id': id + '_overlay',\r
-                       'class': 'mochaOverlay'\r
-               }).inject(this.windowEl);\r
-\r
-               cache.titleBarEl = new Element('div', {\r
-                       'id': id + '_titleBar',\r
-                       'class': 'mochaTitlebar',\r
-                       'styles': {\r
-                               'cursor': options.draggable ? 'move' : 'default'\r
-                       }\r
-               }).inject(cache.overlayEl, 'top');\r
-\r
-               cache.titleEl = new Element('h3', {\r
-                       'id': id + '_title',\r
-                       'class': 'mochaTitle'\r
-               }).inject(cache.titleBarEl);\r
-\r
-               if (options.icon != false){\r
-                       cache.titleBarEl.setStyles({\r
-                               'padding-left': 15,\r
-                               'background': 'url(' + options.icon + ') 5px 5px no-repeat'\r
-                       });\r
-               }\r
-               \r
-               cache.contentBorderEl = new Element('div', {\r
-                       'id': id + '_contentBorder',\r
-                       'class': 'mochaContentBorder'\r
-               }).inject(cache.overlayEl);\r
-\r
-               if (options.toolbar){\r
-                       cache.toolbarWrapperEl = new Element('div', {\r
-                               'id': id + '_toolbarWrapper',\r
-                               'class': 'mochaToolbarWrapper'\r
-                       }).inject(cache.contentBorderEl, options.toolbarPosition == 'bottom' ? 'after' : 'before');\r
-\r
-                       if (options.toolbarPosition == 'bottom') {\r
-                               cache.toolbarWrapperEl.addClass('bottom');\r
-                       }\r
-                       cache.toolbarEl = new Element('div', {\r
-                               'id': id + '_toolbar',\r
-                               'class': 'mochaToolbar'\r
-                       }).inject(cache.toolbarWrapperEl);\r
-               }\r
-\r
-               if (options.toolbar2){\r
-                       cache.toolbar2WrapperEl = new Element('div', {\r
-                               'id': id + '_toolbar2Wrapper',\r
-                               'class': 'mochaToolbarWrapper'\r
-                       }).inject(cache.contentBorderEl, options.toolbar2Position == 'bottom' ? 'after' : 'before');\r
-\r
-                       if (options.toolbar2Position == 'bottom') {\r
-                               cache.toolbar2WrapperEl.addClass('bottom');\r
-                       }\r
-                       cache.toolbar2El = new Element('div', {\r
-                               'id': id + '_toolbar2',\r
-                               'class': 'mochaToolbar'\r
-                       }).inject(cache.toolbar2WrapperEl);\r
-               }\r
-\r
-               cache.contentWrapperEl = new Element('div', {\r
-                       'id': id + '_contentWrapper',\r
-                       'class': 'mochaContentWrapper',\r
-                       'styles': {\r
-                               'width': width + 'px',\r
-                               'height': height + 'px'\r
-                       }\r
-               }).inject(cache.contentBorderEl);\r
-               \r
-               if (this.options.shape == 'gauge'){\r
-                       cache.contentBorderEl.setStyle('borderWidth', 0);\r
-               }\r
-\r
-               cache.contentEl = new Element('div', {\r
-                       'id': id + '_content',\r
-                       'class': 'mochaContent'\r
-               }).inject(cache.contentWrapperEl);\r
-\r
-               if (this.options.useCanvas == true) {\r
-                       cache.canvasEl = new Element('canvas', {\r
-                               'id': id + '_canvas',\r
-                               'class': 'mochaCanvas',\r
-                               'width': 1,\r
-                               'height': 1\r
-                       }).inject(this.windowEl);\r
-\r
-                       if (Browser.Engine.trident && MochaUI.ieSupport == 'excanvas'){\r
-                               G_vmlCanvasManager.initElement(cache.canvasEl);\r
-                               cache.canvasEl = this.windowEl.getElement('.mochaCanvas');\r
-                       }\r
-               }\r
-\r
-               cache.controlsEl = new Element('div', {\r
-                       'id': id + '_controls',\r
-                       'class': 'mochaControls'\r
-               }).inject(cache.overlayEl, 'after');\r
-\r
-               if (options.useCanvasControls == true){\r
-                       cache.canvasControlsEl = new Element('canvas', {\r
-                               'id': id + '_canvasControls',\r
-                               'class': 'mochaCanvasControls',\r
-                               'width': 14,\r
-                               'height': 14\r
-                       }).inject(this.windowEl);\r
-\r
-                       if (Browser.Engine.trident && MochaUI.ieSupport == 'excanvas'){\r
-                               G_vmlCanvasManager.initElement(cache.canvasControlsEl);\r
-                               cache.canvasControlsEl = this.windowEl.getElement('.mochaCanvasControls');\r
-                       }\r
-               }\r
-\r
-               if (options.closable){\r
-                       cache.closeButtonEl = new Element('div', {\r
-                               'id': id + '_closeButton',\r
-                               'class': 'mochaCloseButton',\r
-                               'title': 'Close'\r
-                       }).inject(cache.controlsEl);\r
-                       if (options.useCanvasControls == true){\r
-                               cache.closeButtonEl.setStyle('background', 'none');\r
-                       }\r
-               }\r
-\r
-               if (options.maximizable){\r
-                       cache.maximizeButtonEl = new Element('div', {\r
-                               'id': id + '_maximizeButton',\r
-                               'class': 'mochaMaximizeButton',\r
-                               'title': 'Maximize'\r
-                       }).inject(cache.controlsEl);\r
-                       if (options.useCanvasControls == true){\r
-                               cache.maximizeButtonEl.setStyle('background', 'none');\r
-                       }\r
-               }\r
-\r
-               if (options.minimizable){\r
-                       cache.minimizeButtonEl = new Element('div', {\r
-                               'id': id + '_minimizeButton',\r
-                               'class': 'mochaMinimizeButton',\r
-                               'title': 'Minimize'\r
-                       }).inject(cache.controlsEl);\r
-                       if (options.useCanvasControls == true){\r
-                               cache.minimizeButtonEl.setStyle('background', 'none');\r
-                       }\r
-               }\r
-\r
-               if (options.useSpinner == true && options.shape != 'gauge' && options.type != 'notification'){\r
-                       cache.spinnerEl = new Element('div', {\r
-                               'id': id + '_spinner',\r
-                               'class': 'mochaSpinner',\r
-                               'width': 16,\r
-                               'height': 16\r
-                       }).inject(this.windowEl, 'bottom');\r
-               }\r
-\r
-               if (this.options.shape == 'gauge'){\r
-                       cache.canvasHeaderEl = new Element('canvas', {\r
-                               'id': id + '_canvasHeader',\r
-                               'class': 'mochaCanvasHeader',\r
-                               'width': this.options.width,\r
-                               'height': 26\r
-                       }).inject(this.windowEl, 'bottom');\r
-               \r
-                       if (Browser.Engine.trident && MochaUI.ieSupport == 'excanvas'){\r
-                               G_vmlCanvasManager.initElement(cache.canvasHeaderEl);\r
-                               cache.canvasHeaderEl = this.windowEl.getElement('.mochaCanvasHeader');\r
-                       }\r
-               }\r
-\r
-               if ( Browser.Engine.trident ){\r
-                       cache.overlayEl.setStyle('zIndex', 2);\r
-               }\r
-\r
-               // For Mac Firefox 2 to help reduce scrollbar bugs in that browser\r
-               if (Browser.Platform.mac && Browser.Engine.gecko){\r
-                       if (/Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent)){\r
-                               var ffversion = new Number(RegExp.$1);\r
-                               if (ffversion < 3){\r
-                                       cache.overlayEl.setStyle('overflow', 'auto');\r
-                               }\r
-                       }\r
-               }\r
-\r
-               if (options.resizable){\r
-                       cache.n = new Element('div', {\r
-                               'id': id + '_resizeHandle_n',\r
-                               'class': 'handle',\r
-                               'styles': {\r
-                                       'top': 0,\r
-                                       'left': 10,\r
-                                       'cursor': 'n-resize'\r
-                               }\r
-                       }).inject(cache.overlayEl, 'after');\r
-\r
-                       cache.ne = new Element('div', {\r
-                               'id': id + '_resizeHandle_ne',\r
-                               'class': 'handle corner',\r
-                               'styles': {\r
-                                       'top': 0,\r
-                                       'right': 0,\r
-                                       'cursor': 'ne-resize'\r
-                               }\r
-                       }).inject(cache.overlayEl, 'after');\r
-                       \r
-                       cache.e = new Element('div', {\r
-                               'id': id + '_resizeHandle_e',\r
-                               'class': 'handle',              \r
-                               'styles': {\r
-                                       'top': 10,\r
-                                       'right': 0,\r
-                                       'cursor': 'e-resize'\r
-                               }\r
-                       }).inject(cache.overlayEl, 'after');\r
-                       \r
-                       cache.se = new Element('div', {\r
-                               'id': id + '_resizeHandle_se',\r
-                               'class': 'handle cornerSE',\r
-                               'styles': {\r
-                                       'bottom': 0,\r
-                                       'right': 0,\r
-                                       'cursor': 'se-resize'\r
-                               }\r
-                       }).inject(cache.overlayEl, 'after');\r
-\r
-                       cache.s = new Element('div', {\r
-                               'id': id + '_resizeHandle_s',\r
-                               'class': 'handle',\r
-                               'styles': {\r
-                                       'bottom': 0,\r
-                                       'left': 10,\r
-                                       'cursor': 's-resize'\r
-                               }\r
-                       }).inject(cache.overlayEl, 'after');\r
-                       \r
-                       cache.sw = new Element('div', {\r
-                               'id': id + '_resizeHandle_sw',\r
-                               'class': 'handle corner',\r
-                               'styles': {\r
-                                       'bottom': 0,\r
-                                       'left': 0,\r
-                                       'cursor': 'sw-resize'\r
-                               }\r
-                       }).inject(cache.overlayEl, 'after');\r
-                       \r
-                       cache.w = new Element('div', {\r
-                               'id': id + '_resizeHandle_w',\r
-                               'class': 'handle',              \r
-                               'styles': {\r
-                                       'top': 10,\r
-                                       'left': 0,\r
-                                       'cursor': 'w-resize'\r
-                               }\r
-                       }).inject(cache.overlayEl, 'after');\r
-                       \r
-                       cache.nw = new Element('div', {\r
-                               'id': id + '_resizeHandle_nw',\r
-                               'class': 'handle corner',               \r
-                               'styles': {\r
-                                       'top': 0,\r
-                                       'left': 0,\r
-                                       'cursor': 'nw-resize'\r
-                               }\r
-                       }).inject(cache.overlayEl, 'after');\r
-               }\r
-               $extend(this, cache);\r
-               \r
-       },\r
-       /*\r
-\r
-       Internal function: drawWindow\r
-               This is where we create the canvas GUI  \r
-\r
-       Arguments: \r
-               windowEl: the $(window)\r
-               shadows: (boolean) false will draw a window without shadows\r
-\r
-       */      \r
-       drawWindow: function(windowEl, shadows) {\r
-                               \r
-               if (this.isCollapsed){\r
-                       this.drawWindowCollapsed(windowEl, shadows);\r
-                       return;\r
-               }\r
-\r
-               var options = this.options;\r
-               var shadowBlur = options.shadowBlur;\r
-               var shadowBlur2x = shadowBlur * 2;\r
-               var shadowOffset = this.options.shadowOffset;\r
-\r
-               this.overlayEl.setStyles({\r
-                       'width': this.contentWrapperEl.offsetWidth\r
-               });\r
-\r
-               // Resize iframe when window is resized\r
-               if (this.iframeEl) {\r
-                       this.iframeEl.setStyles({\r
-                               'height': this.contentWrapperEl.offsetHeight\r
-                       });\r
-               }\r
-\r
-               var borderHeight = this.contentBorderEl.getStyle('border-top').toInt() + this.contentBorderEl.getStyle('border-bottom').toInt();\r
-               var toolbarHeight = this.toolbarWrapperEl ? this.toolbarWrapperEl.getStyle('height').toInt() + this.toolbarWrapperEl.getStyle('border-top').toInt() : 0;\r
-               var toolbar2Height = this.toolbar2WrapperEl ? this.toolbar2WrapperEl.getStyle('height').toInt() + this.toolbar2WrapperEl.getStyle('border-top').toInt() : 0;\r
-\r
-               this.headerFooterShadow = options.headerHeight + options.footerHeight + shadowBlur2x;\r
-               var height = this.contentWrapperEl.getStyle('height').toInt() + this.headerFooterShadow + toolbarHeight + toolbar2Height + borderHeight;\r
-               var width = this.contentWrapperEl.getStyle('width').toInt() + shadowBlur2x;\r
-               this.windowEl.setStyles({\r
-                       'height': height,\r
-                       'width': width\r
-               });\r
-\r
-               this.overlayEl.setStyles({\r
-                       'height': height,\r
-                       'top': shadowBlur - shadowOffset.y,\r
-                       'left': shadowBlur - shadowOffset.x\r
-               });             \r
-\r
-               // Opera requires the canvas height and width be set this way when resizing:\r
-               if (this.options.useCanvas == true) {\r
-                       this.canvasEl.height = height;\r
-                       this.canvasEl.width = width;\r
-               }\r
-\r
-               // Part of the fix for IE6 select z-index bug\r
-               if (Browser.Engine.trident4){\r
-                       this.zIndexFixEl.setStyles({\r
-                               'width': width,\r
-                               'height': height\r
-                       })\r
-               }\r
-\r
-               this.titleBarEl.setStyles({\r
-                       'width': width - shadowBlur2x,\r
-                       'height': options.headerHeight\r
-               });\r
-\r
-               // Make sure loading icon is placed correctly.\r
-               if (options.useSpinner == true && options.shape != 'gauge' && options.type != 'notification'){\r
-                       this.spinnerEl.setStyles({\r
-                               'left': shadowBlur - shadowOffset.x + 3,\r
-                               'bottom': shadowBlur + shadowOffset.y +  4\r
-                       });\r
-               }\r
-               \r
-               if (this.options.useCanvas != false) {\r
-               \r
-                       // Draw Window\r
-                       var ctx = this.canvasEl.getContext('2d');\r
-                       ctx.clearRect(0, 0, width, height);\r
-                       \r
-                       switch (options.shape) {\r
-                               case 'box':\r
-                                       this.drawBox(ctx, width, height, shadowBlur, shadowOffset, shadows);\r
-                                       break;\r
-                               case 'gauge':\r
-                                       this.drawGauge(ctx, width, height, shadowBlur, shadowOffset, shadows);\r
-                                       break;\r
-                       }\r
-\r
-\r
-                       if (options.resizable){ \r
-                               MochaUI.triangle(\r
-                                       ctx,\r
-                                       width - (shadowBlur + shadowOffset.x + 17),\r
-                                       height - (shadowBlur + shadowOffset.y + 18),\r
-                                       11,\r
-                                       11,\r
-                                       options.resizableColor,\r
-                                       1.0\r
-                               );\r
-                       }\r
-\r
-                       // Invisible dummy object. The last element drawn is not rendered consistently while resizing in IE6 and IE7\r
-                       if (Browser.Engine.trident){\r
-                               MochaUI.triangle(ctx, 0, 0, 10, 10, options.resizableColor, 0);\r
-                       }\r
-               }\r
-               \r
-               if (options.type != 'notification' && options.useCanvasControls == true){\r
-                       this.drawControls(width, height, shadows);\r
-               }\r
-\r
-       },\r
-       drawWindowCollapsed: function(windowEl, shadows) {\r
-               \r
-               var options = this.options;\r
-               var shadowBlur = options.shadowBlur;\r
-               var shadowBlur2x = shadowBlur * 2;\r
-               var shadowOffset = options.shadowOffset;\r
-               \r
-               var headerShadow = options.headerHeight + shadowBlur2x + 2;\r
-               var height = headerShadow;\r
-               var width = this.contentWrapperEl.getStyle('width').toInt() + shadowBlur2x;\r
-               this.windowEl.setStyle('height', height);\r
-               \r
-               this.overlayEl.setStyles({\r
-                       'height': height,\r
-                       'top': shadowBlur - shadowOffset.y,\r
-                       'left': shadowBlur - shadowOffset.x\r
-               });             \r
-\r
-               // Opera height and width must be set like this, when resizing:\r
-               this.canvasEl.height = height;\r
-               this.canvasEl.width = width;\r
-\r
-               // Part of the fix for IE6 select z-index bug\r
-               if (Browser.Engine.trident4){\r
-                       this.zIndexFixEl.setStyles({\r
-                               'width': width,\r
-                               'height': height\r
-                       });\r
-               }\r
-\r
-               // Set width\r
-               this.windowEl.setStyle('width', width);\r
-               this.overlayEl.setStyle('width', width);\r
-               this.titleBarEl.setStyles({\r
-                       'width': width - shadowBlur2x,\r
-                       'height': options.headerHeight\r
-               });\r
-       \r
-               // Draw Window\r
-               if (this.options.useCanvas != false) {\r
-                       var ctx = this.canvasEl.getContext('2d');\r
-                       ctx.clearRect(0, 0, width, height);\r
-                       \r
-                       this.drawBoxCollapsed(ctx, width, height, shadowBlur, shadowOffset, shadows);\r
-                       if (options.useCanvasControls == true) {\r
-                               this.drawControls(width, height, shadows);\r
-                       }\r
-                       \r
-                       // Invisible dummy object. The last element drawn is not rendered consistently while resizing in IE6 and IE7\r
-                       if (Browser.Engine.trident){\r
-                               MochaUI.triangle(ctx, 0, 0, 10, 10, options.resizableColor, 0);\r
-                       }\r
-               }\r
-\r
-       },      \r
-       drawControls : function(width, height, shadows){\r
-               var options = this.options;\r
-               var shadowBlur = options.shadowBlur;\r
-               var shadowOffset = options.shadowOffset;\r
-               var controlsOffset = options.controlsOffset;\r
-               \r
-               // Make sure controls are placed correctly.\r
-               this.controlsEl.setStyles({\r
-                       'right': shadowBlur + shadowOffset.x + controlsOffset.right,\r
-                       'top': shadowBlur - shadowOffset.y + controlsOffset.top\r
-               });\r
-\r
-               this.canvasControlsEl.setStyles({\r
-                       'right': shadowBlur + shadowOffset.x + controlsOffset.right,\r
-                       'top': shadowBlur - shadowOffset.y + controlsOffset.top\r
-               });\r
-\r
-               // Calculate X position for controlbuttons\r
-               //var mochaControlsWidth = 52;\r
-               this.closebuttonX = options.closable ? this.mochaControlsWidth - 7 : this.mochaControlsWidth + 12;\r
-               this.maximizebuttonX = this.closebuttonX - (options.maximizable ? 19 : 0);\r
-               this.minimizebuttonX = this.maximizebuttonX - (options.minimizable ? 19 : 0);\r
-               \r
-               var ctx2 = this.canvasControlsEl.getContext('2d');\r
-               ctx2.clearRect(0, 0, 100, 100);\r
-\r
-               if (this.options.closable){\r
-                       this.closebutton(\r
-                               ctx2,\r
-                               this.closebuttonX,\r
-                               7,\r
-                               options.closeBgColor,\r
-                               1.0,\r
-                               options.closeColor,\r
-                               1.0\r
-                       );\r
-               }\r
-               if (this.options.maximizable){\r
-                       this.maximizebutton(\r
-                               ctx2,\r
-                               this.maximizebuttonX,\r
-                               7,\r
-                               options.maximizeBgColor,\r
-                               1.0,\r
-                               options.maximizeColor,\r
-                               1.0\r
-                       );\r
-               }\r
-               if (this.options.minimizable){\r
-                       this.minimizebutton(\r
-                               ctx2,\r
-                               this.minimizebuttonX,\r
-                               7,\r
-                               options.minimizeBgColor,\r
-                               1.0,\r
-                               options.minimizeColor,\r
-                               1.0\r
-                       );\r
-               }\r
-               \r
-       },\r
-       drawBox: function(ctx, width, height, shadowBlur, shadowOffset, shadows){\r
-\r
-               var shadowBlur2x = shadowBlur * 2;\r
-               var cornerRadius = this.options.cornerRadius;\r
-\r
-               // This is the drop shadow. It is created onion style.\r
-               if ( shadows != false ) {       \r
-                       for (var x = 0; x <= shadowBlur; x++){\r
-                               MochaUI.roundedRect(\r
-                                       ctx,\r
-                                       shadowOffset.x + x,\r
-                                       shadowOffset.y + x,\r
-                                       width - (x * 2) - shadowOffset.x,\r
-                                       height - (x * 2) - shadowOffset.y,\r
-                                       cornerRadius + (shadowBlur - x),\r
-                                       [0, 0, 0],\r
-                                       x == shadowBlur ? .29 : .065 + (x * .01)\r
-                               );\r
-                       }\r
-               }\r
-               // Window body.\r
-               this.bodyRoundedRect(\r
-                       ctx,                          // context\r
-                       shadowBlur - shadowOffset.x,  // x\r
-                       shadowBlur - shadowOffset.y,  // y\r
-                       width - shadowBlur2x,         // width\r
-                       height - shadowBlur2x,        // height\r
-                       cornerRadius,                 // corner radius\r
-                       this.options.bodyBgColor      // Footer color\r
-               );\r
-\r
-               if (this.options.type != 'notification'){\r
-               // Window header.\r
-                       this.topRoundedRect(\r
-                               ctx,                            // context\r
-                               shadowBlur - shadowOffset.x,    // x\r
-                               shadowBlur - shadowOffset.y,    // y\r
-                               width - shadowBlur2x,           // width\r
-                               this.options.headerHeight,      // height\r
-                               cornerRadius,                   // corner radius\r
-                               this.options.headerStartColor,  // Header gradient's top color\r
-                               this.options.headerStopColor    // Header gradient's bottom color\r
-                       );\r
-               }       \r
-       },\r
-       drawBoxCollapsed: function(ctx, width, height, shadowBlur, shadowOffset, shadows){\r
-\r
-               var options = this.options;\r
-               var shadowBlur2x = shadowBlur * 2;\r
-               var cornerRadius = options.cornerRadius;\r
-       \r
-               // This is the drop shadow. It is created onion style.\r
-               if ( shadows != false ){\r
-                       for (var x = 0; x <= shadowBlur; x++){\r
-                               MochaUI.roundedRect(\r
-                                       ctx,\r
-                                       shadowOffset.x + x,\r
-                                       shadowOffset.y + x,\r
-                                       width - (x * 2) - shadowOffset.x,\r
-                                       height - (x * 2) - shadowOffset.y,\r
-                                       cornerRadius + (shadowBlur - x),\r
-                                       [0, 0, 0],\r
-                                       x == shadowBlur ? .3 : .06 + (x * .01)\r
-                               );\r
-                       }\r
-               }\r
-\r
-               // Window header\r
-               this.topRoundedRect2(\r
-                       ctx,                          // context\r
-                       shadowBlur - shadowOffset.x,  // x\r
-                       shadowBlur - shadowOffset.y,  // y\r
-                       width - shadowBlur2x,         // width\r
-                       options.headerHeight + 2,     // height\r
-                       cornerRadius,                 // corner radius\r
-                       options.headerStartColor,     // Header gradient's top color\r
-                       options.headerStopColor       // Header gradient's bottom color\r
-               );\r
-\r
-       },      \r
-       drawGauge: function(ctx, width, height, shadowBlur, shadowOffset, shadows){\r
-               var options = this.options;\r
-               var radius = (width * .5) - (shadowBlur) + 16;\r
-               if (shadows != false) { \r
-                       for (var x = 0; x <= shadowBlur; x++){\r
-                               MochaUI.circle(\r
-                                       ctx,\r
-                                       width * .5 + shadowOffset.x,\r
-                                       (height  + options.headerHeight) * .5 + shadowOffset.x,\r
-                                       (width *.5) - (x * 2) - shadowOffset.x,\r
-                                       [0, 0, 0],\r
-                                       x == shadowBlur ? .75 : .075 + (x * .04)\r
-                               );\r
-                       }\r
-               }\r
-               MochaUI.circle(\r
-                       ctx,\r
-                       width * .5  - shadowOffset.x,\r
-                       (height + options.headerHeight) * .5  - shadowOffset.y,\r
-                       (width *.5) - shadowBlur,\r
-                       options.bodyBgColor,\r
-                       1\r
-               );\r
-\r
-               // Draw gauge header\r
-               this.canvasHeaderEl.setStyles({\r
-                       'top': shadowBlur - shadowOffset.y,\r
-                       'left': shadowBlur - shadowOffset.x\r
-               });             \r
-               var ctx = this.canvasHeaderEl.getContext('2d');\r
-               ctx.clearRect(0, 0, width, 100);\r
-               ctx.beginPath();\r
-               ctx.lineWidth = 24;\r
-               ctx.lineCap = 'round';\r
-               ctx.moveTo(13, 13);\r
-               ctx.lineTo(width - (shadowBlur*2) - 13, 13);\r
-               ctx.strokeStyle = 'rgba(0, 0, 0, .65)';\r
-               ctx.stroke();\r
-       },\r
-       bodyRoundedRect: function(ctx, x, y, width, height, radius, rgb){\r
-               ctx.fillStyle = 'rgba(' + rgb.join(',') + ', 100)';\r
-               ctx.beginPath();\r
-               ctx.moveTo(x, y + radius);\r
-               ctx.lineTo(x, y + height - radius);\r
-               ctx.quadraticCurveTo(x, y + height, x + radius, y + height);\r
-               ctx.lineTo(x + width - radius, y + height);\r
-               ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);\r
-               ctx.lineTo(x + width, y + radius);\r
-               ctx.quadraticCurveTo(x + width, y, x + width - radius, y);\r
-               ctx.lineTo(x + radius, y);\r
-               ctx.quadraticCurveTo(x, y, x, y + radius);\r
-               ctx.fill();\r
-\r
-       },\r
-       topRoundedRect: function(ctx, x, y, width, height, radius, headerStartColor, headerStopColor){\r
-               var lingrad = ctx.createLinearGradient(0, 0, 0, height);\r
-               lingrad.addColorStop(0, 'rgba(' + headerStartColor.join(',') + ', 1)');\r
-               lingrad.addColorStop(1, 'rgba(' + headerStopColor.join(',') + ', 1)');          \r
-               ctx.fillStyle = lingrad;\r
-               ctx.beginPath();\r
-               ctx.moveTo(x, y);\r
-               ctx.lineTo(x, y + height);\r
-               ctx.lineTo(x + width, y + height);\r
-               ctx.lineTo(x + width, y + radius);\r
-               ctx.quadraticCurveTo(x + width, y, x + width - radius, y);\r
-               ctx.lineTo(x + radius, y);\r
-               ctx.quadraticCurveTo(x, y, x, y + radius);\r
-               ctx.fill();\r
-               /*\r
-               ctx.beginPath();\r
-               ctx.strokeStyle = '#000';\r
-               ctx.lineWidth = 1;\r
-               ctx.moveTo(x, y + height + .5);\r
-               ctx.lineTo(x + width, y + height + .5);\r
-               ctx.stroke();\r
-               */\r
-\r
-       },\r
-       topRoundedRect2: function(ctx, x, y, width, height, radius, headerStartColor, headerStopColor){\r
-               var lingrad = ctx.createLinearGradient(0, this.options.shadowBlur - 1, 0, height + this.options.shadowBlur + 3);\r
-               lingrad.addColorStop(0, 'rgba(' + headerStartColor.join(',') + ', 1)');\r
-               lingrad.addColorStop(1, 'rgba(' + headerStopColor.join(',') + ', 1)');\r
-               ctx.fillStyle = lingrad;\r
-               ctx.beginPath();\r
-               ctx.moveTo(x, y + radius);\r
-               ctx.lineTo(x, y + height - radius);\r
-               ctx.quadraticCurveTo(x, y + height, x + radius, y + height);\r
-               ctx.lineTo(x + width - radius, y + height);\r
-               ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);\r
-               ctx.lineTo(x + width, y + radius);\r
-               ctx.quadraticCurveTo(x + width, y, x + width - radius, y);\r
-               ctx.lineTo(x + radius, y);\r
-               ctx.quadraticCurveTo(x, y, x, y + radius);\r
-               ctx.fill();     \r
-       },\r
-       maximizebutton: function(ctx, x, y, rgbBg, aBg, rgb, a){\r
-               // Circle\r
-               ctx.beginPath();\r
-               ctx.moveTo(x, y);\r
-               ctx.arc(x, y, 7, 0, Math.PI*2, true);\r
-               ctx.fillStyle = 'rgba(' + rgbBg.join(',') + ',' + aBg + ')';\r
-               ctx.fill();\r
-               // X sign\r
-               ctx.strokeStyle = 'rgba(' + rgb.join(',') + ',' + a + ')';\r
-               ctx.beginPath();\r
-               ctx.moveTo(x, y - 4);\r
-               ctx.lineTo(x, y + 4);\r
-               ctx.stroke();\r
-               ctx.beginPath();\r
-               ctx.moveTo(x - 4, y);\r
-               ctx.lineTo(x + 4, y);\r
-               ctx.stroke();\r
-       },\r
-       closebutton: function(ctx, x, y, rgbBg, aBg, rgb, a){\r
-               // Circle\r
-               ctx.beginPath();\r
-               ctx.moveTo(x, y);\r
-               ctx.arc(x, y, 7, 0, Math.PI*2, true);\r
-               ctx.fillStyle = 'rgba(' + rgbBg.join(',') + ',' + aBg + ')';\r
-               ctx.fill();\r
-               // Plus sign\r
-               ctx.strokeStyle = 'rgba(' + rgb.join(',') + ',' + a + ')';\r
-               ctx.beginPath();\r
-               ctx.moveTo(x - 3, y - 3);\r
-               ctx.lineTo(x + 3, y + 3);\r
-               ctx.stroke();\r
-               ctx.beginPath();\r
-               ctx.moveTo(x + 3, y - 3);\r
-               ctx.lineTo(x - 3, y + 3);\r
-               ctx.stroke();\r
-       },\r
-       minimizebutton: function(ctx, x, y, rgbBg, aBg, rgb, a){\r
-               // Circle\r
-               ctx.beginPath();\r
-               ctx.moveTo(x,y);\r
-               ctx.arc(x,y,7,0,Math.PI*2,true);\r
-               ctx.fillStyle = 'rgba(' + rgbBg.join(',') + ',' + aBg + ')';\r
-               ctx.fill();\r
-               // Minus sign\r
-               ctx.strokeStyle = 'rgba(' + rgb.join(',') + ',' + a + ')';\r
-               ctx.beginPath();\r
-               ctx.moveTo(x - 4, y);\r
-               ctx.lineTo(x + 4, y);\r
-               ctx.stroke();\r
-       },\r
-       /*\r
-\r
-       Function: hideSpinner\r
-               Hides the spinner.\r
-               \r
-       */      \r
-       hideSpinner: function(spinner) {\r
-               if ($(spinner)) $(spinner).setStyle('visibility', 'hidden');\r
-       },\r
-       /*\r
-\r
-       Function: showSpinner\r
-               Shows the spinner.\r
-       \r
-       */      \r
-       showSpinner: function(spinner){\r
-               if (!this.options.useSpinner || this.options.shape == 'gauge' || this.options.type == 'notification') return;\r
-               $(spinner).setStyles({\r
-                       'visibility': 'visible'\r
-               });\r
-       },\r
-       setMochaControlsWidth: function(){\r
-               this.mochaControlsWidth = 0;\r
-               var options = this.options;\r
-               if (options.minimizable){\r
-                       this.mochaControlsWidth += (this.minimizeButtonEl.getStyle('margin-left').toInt() + this.minimizeButtonEl.getStyle('width').toInt());\r
-               }\r
-               if (options.maximizable){\r
-                       this.mochaControlsWidth += (this.maximizeButtonEl.getStyle('margin-left').toInt() + this.maximizeButtonEl.getStyle('width').toInt());\r
-               }\r
-               if (options.closable){\r
-                       this.mochaControlsWidth += (this.closeButtonEl.getStyle('margin-left').toInt() + this.closeButtonEl.getStyle('width').toInt());\r
-               }\r
-               this.controlsEl.setStyle('width', this.mochaControlsWidth);\r
-               if (options.useCanvasControls == true){\r
-                       this.canvasControlsEl.setProperty('width', this.mochaControlsWidth);\r
-               }\r
-       }\r
-});\r
-MochaUI.Window.implement(new Options, new Events);\r
-/*\r
-\r
-Script: Modal.js\r
-       Create modal dialog windows.\r
-\r
-Copyright:\r
-       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.  \r
-\r
-License:\r
-       MIT-style license.      \r
-\r
-Requires:\r
-       Core.js, Window.js\r
-\r
-See Also:\r
-       <Window>        \r
-       \r
-*/\r
-\r
-MochaUI.Modal = new Class({\r
-\r
-       Extends: MochaUI.Window,\r
-\r
-       Implements: [Events, Options],\r
-\r
-       initialize: function(options){\r
-\r
-               this.modalInitialize();\r
-               \r
-               window.addEvent('resize', function(){\r
-                       this.setModalSize();\r
-               }.bind(this));\r
-\r
-       },\r
-       modalInitialize: function(){\r
-               var modalOverlay = new Element('div', {\r
-                       'id': 'modalOverlay',\r
-                       'styles': {\r
-                               'height': document.getCoordinates().height,\r
-                               'opacity': .6\r
-                       }\r
-               }).inject(document.body);\r
-               \r
-               modalOverlay.addEvent('click', function(e){\r
-                       MochaUI.closeWindow(MochaUI.currentModal);\r
-               });\r
-               \r
-               if (Browser.Engine.trident4){\r
-                       var modalFix = new Element('iframe', {\r
-                               'id': 'modalFix',\r
-                               'scrolling': 'no',\r
-                               'marginWidth': 0,\r
-                               'marginHeight': 0,\r
-                               'src': '',\r
-                               'styles': {\r
-                                       'height': document.getCoordinates().height\r
-                               }\r
-                       }).inject(document.body);\r
-               }\r
-\r
-               this.modalOverlayOpenMorph = new Fx.Morph($('modalOverlay'), {\r
-                               'duration': 150\r
-                               });\r
-               this.modalOverlayCloseMorph = new Fx.Morph($('modalOverlay'), {\r
-                       'duration': 150,\r
-                       onComplete: function(){\r
-                               $('modalOverlay').setStyle('display', 'none');\r
-                               if (Browser.Engine.trident4){\r
-                                       $('modalFix').setStyle('display', 'none');\r
-                               }\r
-                       }.bind(this)\r
-               });\r
-       },\r
-       setModalSize: function(){\r
-               $('modalOverlay').setStyle('height', document.getCoordinates().height);\r
-               if (Browser.Engine.trident4){\r
-                       $('modalFix').setStyle('height', document.getCoordinates().height);\r
-               }\r
-       }\r
-});\r
-MochaUI.Modal.implement(new Options, new Events);\r
-/*\r
-\r
-Script: Windows-from-html.js\r
-       Create windows from html markup in page.\r
-\r
-Copyright:\r
-       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.  \r
-\r
-License:\r
-       MIT-style license.      \r
-\r
-Requires:\r
-       Core.js, Window.js\r
-\r
-Example:\r
-       HTML markup.\r
-       (start code)\r
-<div class="mocha" id="mywindow" style="width:300px;height:255px;top:50px;left:350px">\r
-       <h3 class="mochaTitle">My Window</h3>\r
-       <p>My Window Content</p>\r
-</div> \r
-       (end)\r
-\r
-See Also:\r
-       <Window>\r
-\r
-*/\r
-\r
-MochaUI.extend({\r
-       NewWindowsFromHTML: function(){\r
-               $$('div.mocha').each(function(el) {\r
-                       // Get the window title and destroy that element, so it does not end up in window content\r
-                       if ( Browser.Engine.presto || Browser.Engine.trident5 ){\r
-                               el.setStyle('display','block'); // Required by Opera, and probably IE7\r
-                       }\r
-                       var title = el.getElement('h3.mochaTitle');\r
-                       var elDimensions = el.getStyles('height', 'width');\r
-                       var properties = {\r
-                               id: el.getProperty('id'),\r
-                               height: elDimensions.height.toInt(),\r
-                               width: elDimensions.width.toInt(),\r
-                               x: el.getStyle('left').toInt(),\r
-                               y: el.getStyle('top').toInt()\r
-                       };\r
-                       // If there is a title element, set title and destroy the element so it does not end up in window content\r
-                       if ( title ) {\r
-                               properties.title = title.innerHTML;\r
-                               title.destroy();\r
-                       }\r
-               \r
-                       // Get content and destroy the element\r
-                       properties.content = el.innerHTML;\r
-                       el.destroy();\r
-                       \r
-                       // Create window\r
-                       new MochaUI.Window(properties, true);\r
-               }.bind(this));\r
-       }\r
-});\r
-/*\r
-\r
-Script: Windows-from-json.js\r
-       Create one or more windows from JSON data. You can define all the same properties as you can for new MochaUI.Window(). Undefined properties are set to their defaults.\r
-\r
-Copyright:\r
-       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.  \r
-\r
-License:\r
-       MIT-style license.      \r
-\r
-Syntax:\r
-       (start code)\r
-       MochaUI.newWindowsFromJSON(properties);\r
-       (end)\r
-\r
-Example:\r
-       (start code)\r
-       MochaUI.jsonWindows = function(){\r
-               var url = 'data/json-windows-data.js';\r
-               var request = new Request.JSON({\r
-                       url: url,\r
-                       method: 'get',\r
-                       onComplete: function(properties) {\r
-                               MochaUI.newWindowsFromJSON(properties.windows);\r
-                       }\r
-               }).send();\r
-       }\r
-       (end)\r
-\r
-Note: \r
-       Windows created from JSON are not compatible with the current cookie based version\r
-       of Save and Load Workspace.     \r
-\r
-See Also:\r
-       <Window>\r
-\r
-*/\r
-\r
-MochaUI.extend({       \r
-       newWindowsFromJSON: function(properties){\r
-               properties.each(function(properties) {\r
-                               new MochaUI.Window(properties);\r
-               }.bind(this));\r
-       }\r
-});\r
-/*\r
-\r
-Script: Arrange-cascade.js\r
-       Cascade windows.\r
-\r
-Copyright:\r
-       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.  \r
-\r
-License:\r
-       MIT-style license.      \r
-\r
-Requires:\r
-       Core.js, Window.js\r
-\r
-Syntax:\r
-       (start code)\r
-       MochaUI.arrangeCascade();\r
-       (end)\r
-\r
-*/\r
-\r
-MochaUI.options.extend({\r
-       viewportTopOffset:  30,    // Use a negative number if neccessary to place first window where you want it\r
-       viewportLeftOffset: 20,\r
-       windowTopOffset:    50,    // Initial vertical spacing of each window\r
-       windowLeftOffset:   40     // Initial horizontal spacing of each window \r
-});\r
-\r
-MochaUI.extend({   \r
-       arrangeCascade: function(){\r
-               // See how much space we have to work with\r
-               var coordinates = document.getCoordinates();\r
-               \r
-               var openWindows = 0;\r
-               MochaUI.Windows.instances.each(function(instance){\r
-                       if (!instance.isMinimized) openWindows ++; \r
-               });\r
-               \r
-               if ((this.options.windowTopOffset * (openWindows + 1)) >= (coordinates.height - this.options.viewportTopOffset)) {\r
-                       var topOffset = (coordinates.height - this.options.viewportTopOffset) / (openWindows + 1);\r
-               }\r
-               else {\r
-                       var topOffset = this.options.windowTopOffset;\r
-               }\r
-               \r
-               if ((this.options.windowLeftOffset * (openWindows + 1)) >= (coordinates.width - this.options.viewportLeftOffset - 20)) {\r
-                       var leftOffset = (coordinates.width - this.options.viewportLeftOffset - 20) / (openWindows + 1);\r
-               }\r
-               else {\r
-                       var leftOffset = this.options.windowLeftOffset;\r
-               }\r
-\r
-               var x = this.options.viewportLeftOffset;\r
-               var y = this.options.viewportTopOffset;\r
-               $$('div.mocha').each(function(windowEl){\r
-                       var currentWindowClass = MochaUI.Windows.instances.get(windowEl.id);\r
-                       if (!currentWindowClass.isMinimized && !currentWindowClass.isMaximized){\r
-                               id = windowEl.id;\r
-                               MochaUI.focusWindow(windowEl);\r
-                               x += leftOffset;\r
-                               y += topOffset;\r
-\r
-                               if (MochaUI.options.useEffects == false){\r
-                                       windowEl.setStyles({\r
-                                               'top': y,\r
-                                               'left': x\r
-                                       });\r
-                               }\r
-                               else {\r
-                                       var cascadeMorph = new Fx.Morph(windowEl, {\r
-                                               'duration': 550\r
-                                       });\r
-                                       cascadeMorph.start({\r
-                                               'top': y,\r
-                                               'left': x\r
-                                       });\r
-                               }\r
-                       }\r
-               }.bind(this));\r
-       }\r
-});\r
-/*\r
-\r
-Script: Arrange-tile.js\r
-       Cascade windows.\r
-\r
-Authors:\r
-       Harry Roberts and Greg Houston\r
-\r
-License:\r
-       MIT-style license.      \r
-\r
-Requires:\r
-       Core.js, Window.js\r
-\r
-Syntax:\r
-       (start code)\r
-       MochaUI.arrangeTile();\r
-       (end)\r
-\r
-*/\r
\r
-MochaUI.extend({\r
-       arrangeTile: function(){\r
-               var x = 10;\r
-               var y = 10;\r
-       \r
-               var instances =  MochaUI.Windows.instances;\r
-\r
-               var windowsNum = 0;\r
-\r
-               instances.each(function(instance){\r
-                       if (!instance.isMinimized && !instance.isMaximized){\r
-                               windowsNum++;\r
-                       }\r
-               });\r
-\r
-               var cols = 3;\r
-               var rows = Math.ceil(windowsNum / cols);\r
-               \r
-               var coordinates = document.getCoordinates();\r
-       \r
-               var col_width = ((coordinates.width - this.options.viewportLeftOffset) / cols);\r
-               var col_height = ((coordinates.height - this.options.viewportTopOffset) / rows);\r
-               \r
-               var row = 0;\r
-               var col = 0;\r
-               \r
-               instances.each(function(instance){\r
-                       if (!instance.isMinimized && !instance.isMaximized){\r
-                               \r
-                               var content = instance.contentWrapperEl;\r
-                               var content_coords = content.getCoordinates();\r
-                               var window_coords = instance.windowEl.getCoordinates();\r
-                               \r
-                               // Calculate the amount of padding around the content window\r
-                               var padding_top = content_coords.top - window_coords.top;\r
-                               var padding_bottom = window_coords.height - content_coords.height - padding_top;\r
-                               var padding_left = content_coords.left - window_coords.left;\r
-                               var padding_right = window_coords.width - content_coords.width - padding_left;\r
-\r
-                               /*\r
-\r
-                               // This resizes the windows\r
-                               if (instance.options.shape != 'gauge' && instance.options.resizable == true){\r
-                                       var width = (col_width - 3 - padding_left - padding_right);\r
-                                       var height = (col_height - 3 - padding_top - padding_bottom);\r
-\r
-                                       if (width > instance.options.resizeLimit.x[0] && width < instance.options.resizeLimit.x[1]){\r
-                                               content.setStyle('width', width);\r
-                                       }\r
-                                       if (height > instance.options.resizeLimit.y[0] && height < instance.options.resizeLimit.y[1]){\r
-                                               content.setStyle('height', height);\r
-                                       }\r
-\r
-                               }*/\r
-\r
-                               var left = (x + (col * col_width));\r
-                               var top = (y + (row * col_height));\r
-\r
-                               instance.windowEl.setStyles({\r
-                                       'left': left,\r
-                                       'top': top\r
-                               });\r
-\r
-                               instance.drawWindow(instance.windowEl);\r
-\r
-                               MochaUI.focusWindow(instance.windowEl);\r
-\r
-                               if (++col === cols) {\r
-                                       row++;\r
-                                       col = 0;\r
-                               }\r
-                       }\r
-               }.bind(this));\r
-       }\r
-});/*\r
-\r
-Script: Tabs.js\r
-       Functionality for window tabs.\r
-\r
-Copyright:\r
-       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.  \r
-\r
-License:\r
-       MIT-style license.\r
-\r
-Requires:\r
-       Core.js, Window.js (for tabbed windows) or Layout.js (for tabbed panels)\r
-\r
-*/\r
-\r
-MochaUI.extend({\r
-       /*\r
-\r
-       Function: initializeTabs\r
-               Add click event to each list item that fires the selected function.\r
-\r
-       */\r
-       initializeTabs: function(el){\r
-               $(el).getElements('li').each(function(listitem){\r
-                       listitem.addEvent('click', function(e){\r
-                               MochaUI.selected(this, el);\r
-                       });\r
-               });\r
-       },\r
-       /*\r
-\r
-       Function: selected\r
-               Add "selected" class to current list item and remove it from sibling list items.\r
-\r
-       Syntax:\r
-               (start code)\r
-                       selected(el, parent);\r
-               (end)\r
-\r
-Arguments:\r
-       el - the list item\r
-       parent - the ul\r
-\r
-       */\r
-       selected: function(el, parent){\r
-               $(parent).getChildren().each(function(listitem){\r
-                       listitem.removeClass('selected');\r
-               });\r
-               el.addClass('selected');\r
-       }\r
-});\r
-\r
-/*\r
-\r
-Script: Layout.js\r
-       Create web application layouts. Enables window maximize.\r
-       \r
-Copyright:\r
-       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.\r
-\r
-License:\r
-       MIT-style license.      \r
-\r
-Requires:\r
-       Core.js, Window.js\r
-       \r
-*/\r
-\r
-MochaUI.Desktop = new Class({\r
-\r
-       Extends: MochaUI.Window,\r
-\r
-       Implements: [Events, Options],\r
-       \r
-       options: {\r
-               // Naming options:\r
-               // If you change the IDs of the Mocha Desktop containers in your HTML, you need to change them here as well.\r
-               desktop:             'desktop',\r
-               desktopHeader:       'desktopHeader',\r
-               desktopFooter:       'desktopFooter',\r
-               desktopNavBar:       'desktopNavbar',\r
-               pageWrapper:         'pageWrapper',\r
-               page:                'page',\r
-               desktopFooter:       'desktopFooterWrapper'\r
-       },      \r
-       initialize: function(options){\r
-               this.setOptions(options);\r
-               this.desktop         = $(this.options.desktop);\r
-               this.desktopHeader   = $(this.options.desktopHeader);\r
-               this.desktopNavBar   = $(this.options.desktopNavBar);\r
-               this.pageWrapper     = $(this.options.pageWrapper);\r
-               this.page            = $(this.options.page);\r
-               this.desktopFooter   = $(this.options.desktopFooter);           \r
-       \r
-               // This is run on dock initialize so no need to do it twice.\r
-               if (!MochaUI.Dock.dockWrapper){\r
-                       this.setDesktopSize();\r
-               }\r
-               this.menuInitialize();          \r
-\r
-               // Resize desktop, page wrapper, modal overlay, and maximized windows when browser window is resized\r
-               window.addEvent('resize', function(e){\r
-                       this.onBrowserResize();\r
-               }.bind(this));\r
-       },\r
-       menuInitialize: function(){\r
-               // Fix for dropdown menus in IE6\r
-               if (Browser.Engine.trident4 && this.desktopNavBar){\r
-                       this.desktopNavBar.getElements('li').each(function(element) {\r
-                               element.addEvent('mouseenter', function(){\r
-                                       this.addClass('ieHover');\r
-                               });\r
-                               element.addEvent('mouseleave', function(){\r
-                                       this.removeClass('ieHover');\r
-                               });\r
-                       });\r
-               };\r
-       },\r
-       onBrowserResize: function(){\r
-               this.setDesktopSize();\r
-               // Resize maximized windows to fit new browser window size\r
-               setTimeout( function(){\r
-                       MochaUI.Windows.instances.each(function(instance){\r
-                               if (instance.isMaximized){\r
-\r
-                                       // Hide iframe while resize for better performance\r
-                                       if ( instance.iframeEl ){\r
-                                               instance.iframeEl.setStyle('visibility', 'hidden');\r
-                                       }\r
-\r
-                                       var coordinates = document.getCoordinates();\r
-                                       var borderHeight = instance.contentBorderEl.getStyle('border-top').toInt() + instance.contentBorderEl.getStyle('border-bottom').toInt();\r
-                                       var toolbarHeight = instance.toolbarWrapperEl ? instance.toolbarWrapperEl.getStyle('height').toInt() + instance.toolbarWrapperEl.getStyle('border-top').toInt() : 0;\r
-                                       instance.contentWrapperEl.setStyles({\r
-                                               'height': coordinates.height - instance.options.headerHeight - instance.options.footerHeight - borderHeight - toolbarHeight,\r
-                                               'width': coordinates.width\r
-                                       });\r
-\r
-                                       instance.drawWindow($(instance.options.id));\r
-                                       if ( instance.iframeEl ){\r
-                                               instance.iframeEl.setStyles({\r
-                                                       'height': instance.contentWrapperEl.getStyle('height')\r
-                                               });\r
-                                               instance.iframeEl.setStyle('visibility', 'visible');\r
-                                       }\r
-\r
-                               }\r
-                       }.bind(this));\r
-               }.bind(this), 100);\r
-       },\r
-       setDesktopSize: function(){\r
-               var windowDimensions = window.getCoordinates();\r
-\r
-               // var dock = $(MochaUI.options.dock);\r
-               var dockWrapper = $(MochaUI.options.dockWrapper);\r
-               \r
-               // Setting the desktop height may only be needed by IE7\r
-               if (this.desktop){\r
-                       this.desktop.setStyle('height', windowDimensions.height);\r
-               }\r
-\r
-               // Set pageWrapper height so the dock doesn't cover the pageWrapper scrollbars.\r
-               if (this.pageWrapper) {\r
-\r
-                       var dockOffset = MochaUI.dockVisible ? dockWrapper.offsetHeight : 0;\r
-                       var pageWrapperHeight = windowDimensions.height;\r
-                       pageWrapperHeight -= this.pageWrapper.getStyle('border-top').toInt();\r
-                       pageWrapperHeight -= this.pageWrapper.getStyle('border-bottom').toInt();\r
-                       if (this.desktopHeader){ pageWrapperHeight -= this.desktopHeader.offsetHeight; }\r
-                       if (this.desktopFooter){ pageWrapperHeight -= this.desktopFooter.offsetHeight; }\r
-                       pageWrapperHeight -= dockOffset;\r
-\r
-                       if (pageWrapperHeight < 0){\r
-                               pageWrapperHeight = 0;\r
-                       }\r
-                       this.pageWrapper.setStyle('height', pageWrapperHeight);\r
-               }\r
-\r
-               if (MochaUI.Columns.instances.getKeys().length > 0){ // Conditional is a fix for a bug in IE6 in the no toolbars demo.\r
-                       MochaUI.Desktop.resizePanels();\r
-               }               \r
-       },\r
-       resizePanels: function(){\r
-               if (Browser.Engine.trident4){\r
-                       $$('.pad').setStyle('display', 'none');\r
-                       $$('.rHeight').setStyle('height', 1);\r
-               }\r
-               MochaUI.panelHeight();\r
-               MochaUI.rWidth();\r
-               if (Browser.Engine.trident4) $$('.pad').setStyle('display', 'block');\r
-       },\r
-       /*\r
-       \r
-       Function: maximizeWindow\r
-               Maximize a window.\r
-       \r
-       Syntax:\r
-               (start code)\r
-               MochaUI.Desktop.maximizeWindow(windowEl);\r
-               (end)   \r
-\r
-       */      \r
-       maximizeWindow: function(windowEl){\r
-\r
-               var currentInstance = MochaUI.Windows.instances.get(windowEl.id);\r
-               var options = currentInstance.options;\r
-               var windowDrag = currentInstance.windowDrag;\r
-\r
-               // If window no longer exists or is maximized, stop\r
-               if (windowEl != $(windowEl) || currentInstance.isMaximized ) return;\r
-               \r
-               if (currentInstance.isCollapsed){\r
-                       MochaUI.collapseToggle(windowEl);       \r
-               }\r
-\r
-               currentInstance.isMaximized = true;\r
-               \r
-               // If window is restricted to a container, it should not be draggable when maximized.\r
-               if (currentInstance.options.restrict){\r
-                       windowDrag.detach();\r
-                       if (options.resizable) {\r
-                               currentInstance.detachResizable();\r
-                       }\r
-                       currentInstance.titleBarEl.setStyle('cursor', 'default');\r
-               }       \r
-\r
-               // If the window has a container that is not the desktop\r
-               // temporarily move the window to the desktop while it is minimized.\r
-               if (options.container != this.desktop){\r
-                       this.desktop.grab(windowEl);\r
-                       if (this.options.restrict){\r
-                       windowDrag.container = this.desktop;\r
-                       }\r
-               }               \r
-\r
-               // Save original position\r
-               currentInstance.oldTop = windowEl.getStyle('top');\r
-               currentInstance.oldLeft = windowEl.getStyle('left');\r
-\r
-               var contentWrapperEl = currentInstance.contentWrapperEl;\r
-\r
-               // Save original dimensions\r
-               contentWrapperEl.oldWidth = contentWrapperEl.getStyle('width');\r
-               contentWrapperEl.oldHeight = contentWrapperEl.getStyle('height');\r
-\r
-               // Hide iframe\r
-               // Iframe should be hidden when minimizing, maximizing, and moving for performance and Flash issues\r
-               if ( currentInstance.iframeEl ) {\r
-                       currentInstance.iframeEl.setStyle('visibility', 'hidden');\r
-               }\r
-\r
-               var windowDimensions = document.getCoordinates();\r
-               var options = currentInstance.options;\r
-               var shadowBlur = options.shadowBlur;\r
-               var shadowOffset = options.shadowOffset;\r
-               var newHeight = windowDimensions.height - options.headerHeight - options.footerHeight;\r
-               newHeight -= currentInstance.contentBorderEl.getStyle('border-top').toInt();\r
-               newHeight -= currentInstance.contentBorderEl.getStyle('border-bottom').toInt();\r
-               newHeight -= (  currentInstance.toolbarWrapperEl ? currentInstance.toolbarWrapperEl.getStyle('height').toInt() + currentInstance.toolbarWrapperEl.getStyle('border-top').toInt() : 0);\r
-\r
-               if (MochaUI.options.useEffects == false){\r
-                       windowEl.setStyles({\r
-                               'top': shadowOffset.y - shadowBlur,\r
-                               'left': shadowOffset.x - shadowBlur\r
-                       });\r
-                       currentInstance.contentWrapperEl.setStyles({\r
-                               'height': newHeight,\r
-                               'width':  windowDimensions.width\r
-                       });\r
-                       currentInstance.drawWindow(windowEl);\r
-                       // Show iframe\r
-                       if ( currentInstance.iframeEl ) {\r
-                               currentInstance.iframeEl.setStyle('visibility', 'visible');\r
-                       }\r
-                       currentInstance.fireEvent('onMaximize', windowEl);\r
-               }\r
-               else {\r
-\r
-                       // Todo: Initialize the variables for these morphs once in an initialize function and reuse them\r
-\r
-                       var maximizeMorph = new Fx.Elements([contentWrapperEl, windowEl], { \r
-                               duration: 70,\r
-                               onStart: function(windowEl){\r
-                                       currentInstance.maximizeAnimation = currentInstance.drawWindow.periodical(20, currentInstance, windowEl);\r
-                               }.bind(this),\r
-                               onComplete: function(windowEl){\r
-                                       $clear(currentInstance.maximizeAnimation);\r
-                                       currentInstance.drawWindow(windowEl);\r
-                                       // Show iframe\r
-                                       if ( currentInstance.iframeEl ) {\r
-                                               currentInstance.iframeEl.setStyle('visibility', 'visible');\r
-                                       }\r
-                                       currentInstance.fireEvent('onMaximize', windowEl);      \r
-                               }.bind(this)\r
-                       });\r
-                       maximizeMorph.start({\r
-                               '0': {  'height': newHeight,\r
-                                               'width':  windowDimensions.width\r
-                               },\r
-                               '1': {  'top': shadowOffset.y - shadowBlur,\r
-                                               'left': shadowOffset.x - shadowBlur \r
-                               }\r
-                       });             \r
-               }\r
-               currentInstance.maximizeButtonEl.setProperty('title', 'Restore');\r
-               MochaUI.focusWindow(windowEl);\r
-\r
-       },\r
-       /*\r
-\r
-       Function: restoreWindow\r
-               Restore a maximized window.\r
-\r
-       Syntax:\r
-               (start code)\r
-               MochaUI.Desktop.restoreWindow(windowEl);\r
-               (end)   \r
-\r
-       */      \r
-       restoreWindow: function(windowEl){      \r
-       \r
-               var currentInstance = MochaUI.Windows.instances.get(windowEl.id);\r
-               \r
-               // Window exists and is maximized ?\r
-               if (windowEl != $(windowEl) || !currentInstance.isMaximized) return;\r
-                       \r
-               var options = currentInstance.options;\r
-               currentInstance.isMaximized = false;\r
-               \r
-               if (options.restrict){\r
-                       currentInstance.windowDrag.attach();\r
-                       if (options.resizable){\r
-                               currentInstance.reattachResizable();\r
-                       }                       \r
-                       currentInstance.titleBarEl.setStyle('cursor', 'move');\r
-               }               \r
-               \r
-               // Hide iframe\r
-               // Iframe should be hidden when minimizing, maximizing, and moving for performance and Flash issues\r
-               if ( currentInstance.iframeEl ) {\r
-                       currentInstance.iframeEl.setStyle('visibility', 'hidden');\r
-               }\r
-               \r
-               var contentWrapperEl = currentInstance.contentWrapperEl;\r
-               \r
-               if (MochaUI.options.useEffects == false){\r
-                       contentWrapperEl.setStyles({\r
-                               'width':  contentWrapperEl.oldWidth,\r
-                               'height': contentWrapperEl.oldHeight\r
-                       });\r
-                       currentInstance.drawWindow(windowEl);\r
-                       windowEl.setStyles({\r
-                               'top': currentInstance.oldTop,\r
-                               'left': currentInstance.oldLeft\r
-                       });\r
-                       if ( currentInstance.iframeEl ) {\r
-                               currentInstance.iframeEl.setStyle('visibility', 'visible');\r
-                       }                       \r
-                       if (options.container != this.desktop){\r
-                               $(options.container).grab(windowEl);\r
-                               if (options.restrict){\r
-                                       currentInstance.windowDrag.container = $(options.container);\r
-                               }\r
-                       }\r
-                       currentInstance.fireEvent('onRestore', windowEl);\r
-               }\r
-               else {\r
-                       var restoreMorph = new Fx.Elements([contentWrapperEl, windowEl], { \r
-                               'duration':   150,\r
-                               'onStart': function(windowEl){\r
-                                       currentInstance.maximizeAnimation = currentInstance.drawWindow.periodical(20, currentInstance, windowEl);\r
-                               }.bind(this),\r
-                               'onComplete': function(el){\r
-                                       $clear(currentInstance.maximizeAnimation);\r
-                                       currentInstance.drawWindow(windowEl);\r
-                                       if (currentInstance.iframeEl){\r
-                                               currentInstance.iframeEl.setStyle('visibility', 'visible');\r
-                                       }\r
-                                       if (options.container != this.desktop){\r
-                                               $(options.container).grab(windowEl);\r
-                                               if (options.restrict){  \r
-                                                       currentInstance.windowDrag.container = $(options.container);\r
-                                               }\r
-                                       }\r
-                                       currentInstance.fireEvent('onRestore', windowEl);\r
-                               }.bind(this)\r
-                       });\r
-                       restoreMorph.start({ \r
-                               '0': {  'height': contentWrapperEl.oldHeight,\r
-                                               'width':  contentWrapperEl.oldWidth\r
-                               },\r
-                               '1': {  'top':  currentInstance.oldTop,\r
-                                               'left': currentInstance.oldLeft\r
-                               }\r
-                       });\r
-               }\r
-               currentInstance.maximizeButtonEl.setProperty('title', 'Maximize');\r
-       }\r
-});\r
-MochaUI.Desktop.implement(new Options, new Events);\r
-\r
-/*\r
-\r
-Class: Column\r
-       Create a column. Columns should be created from left to right.\r
-\r
-Syntax:\r
-(start code)\r
-       MochaUI.Panel();\r
-(end)\r
-\r
-Arguments:\r
-       options\r
-\r
-Options:\r
-       id - The ID of the column. This must be set when creating the column.   \r
-       placement - Can be 'right', 'main', or 'left'. There must be at least one column with the 'main' option.\r
-       width - 'main' column is fluid and should not be given a width.\r
-       resizeLimit - resizelimit of a 'right' or 'left' column.\r
-       onResize - (function) Fired when the column is resized.\r
-       onCollapse - (function) Fired when the column is collapsed.\r
-       onExpand - (function) Fired when the column is expanded.\r
-               \r
-*/\r
-MochaUI.Column = new Class({\r
-\r
-       Extends: MochaUI.Desktop,\r
-\r
-       Implements: [Events, Options],\r
-\r
-       options: {\r
-               id:            null, \r
-               placement:     null, \r
-               width:         null,\r
-               resizeLimit:   [],\r
-\r
-               // Events\r
-               onResize:     $empty, \r
-               onCollapse:   $empty,\r
-               onExpand:     $empty\r
-\r
-       },\r
-       initialize: function(options){\r
-               this.setOptions(options);\r
-               \r
-               $extend(this, {\r
-                       timestamp: $time(),\r
-                       isCollapsed: false,\r
-                       oldWidth: 0\r
-               });\r
-\r
-               // Shorten object chain\r
-               var options = this.options;\r
-               var instances = MochaUI.Columns.instances;\r
-               var instanceID = instances.get(options.id);\r
-\r
-               // Check to see if there is already a class instance for this Column\r
-               if (instanceID){\r
-                       var currentInstance = instanceID;\r
-               }\r
-\r
-               // Check if column already exists\r
-               if ( this.columnEl ){\r
-                       return;\r
-               }\r
-               else {                  \r
-                       instances.set(options.id, this);\r
-               }               \r
-                               \r
-               this.columnEl = new Element('div', {\r
-                       'id': this.options.id,\r
-                       'class': 'column expanded',\r
-                       'styles': {\r
-                               'width': options.placement == 'main' ? null : options.width\r
-                       }\r
-               }).inject($(MochaUI.Desktop.pageWrapper));\r
-\r
-               var parent = this.columnEl.getParent();\r
-               var columnHeight = parent.getStyle('height').toInt();\r
-               this.columnEl.setStyle('height', columnHeight);\r
-\r
-               if (options.placement == 'main'){\r
-                       this.columnEl.addClass('rWidth');\r
-               }\r
-\r
-               this.spacerEl = new Element('div', {\r
-                       'id': this.options.id + '_spacer',\r
-                       'class': 'horizontalHandle'\r
-               }).inject(this.columnEl);\r
-\r
-               switch (this.options.placement) {\r
-                       case 'left':\r
-                               this.handleEl = new Element('div', {\r
-                                       'id': this.options.id + '_handle',\r
-                                       'class': 'columnHandle'\r
-                               }).inject(this.columnEl, 'after');\r
-\r
-                               this.handleIconEl = new Element('div', {\r
-                                       'id': options.id + '_handle_icon',\r
-                                       'class': 'handleIcon'\r
-                               }).inject(this.handleEl);\r
-\r
-                               addResizeRight(this.columnEl, options.resizeLimit[0], options.resizeLimit[1]);\r
-                               break;\r
-                       case 'right':\r
-                               this.handleEl = new Element('div', {\r
-                                       'id': this.options.id + '_handle',\r
-                                       'class': 'columnHandle'\r
-                               }).inject(this.columnEl, 'before');\r
-\r
-                               this.handleIconEl = new Element('div', {\r
-                                       'id': options.id + '_handle_icon',\r
-                                       'class': 'handleIcon'\r
-                               }).inject(this.handleEl);\r
-                               addResizeLeft(this.columnEl, options.resizeLimit[0], options.resizeLimit[1]);\r
-                               break;\r
-               }\r
-\r
-               if (this.handleEl != null){\r
-                       this.handleEl.addEvent('dblclick', function(){\r
-                               this.columnToggle();\r
-                       }.bind(this));\r
-               }\r
-\r
-               MochaUI.rWidth();\r
-\r
-       },\r
-       columnToggle: function(){\r
-               var column= this.columnEl;\r
-               \r
-               // Collapse\r
-               if (this.isCollapsed == false){\r
-                       this.oldWidth = column.getStyle('width').toInt();\r
-\r
-                       this.resize.detach();\r
-                       this.handleEl.removeEvents('dblclick');\r
-                       this.handleEl.addEvent('click', function(){\r
-                               this.columnToggle();\r
-                       }.bind(this));\r
-                       this.handleEl.setStyle('cursor', 'pointer').addClass('detached');\r
-                       \r
-                       column.setStyle('width', 0);\r
-                       this.isCollapsed = true;\r
-                       column.addClass('collapsed');\r
-                       column.removeClass('expanded');\r
-\r
-                       MochaUI.rWidth();\r
-                       this.fireEvent('onCollapse');\r
-               }\r
-               // Expand\r
-               else {\r
-                       column.setStyle('width', this.oldWidth);\r
-                       this.isCollapsed = false;\r
-                       column.addClass('expanded');\r
-                       column.removeClass('collapsed');\r
-\r
-                       this.handleEl.removeEvents('click');\r
-                       this.handleEl.addEvent('dblclick', function(){\r
-                               this.columnToggle();\r
-                       }.bind(this));\r
-                       this.resize.attach();\r
-                       this.handleEl.setStyle('cursor', 'e-resize').addClass('attached');\r
-\r
-                       MochaUI.rWidth();\r
-                       this.fireEvent('onExpand');\r
-               }\r
-       }\r
-});\r
-MochaUI.Column.implement(new Options, new Events);\r
-\r
-/*\r
-\r
-Class: Panel\r
-       Create a panel. Panels go one on top of another in columns. Create your columns first and then add your panels. Panels should be created from top to bottom, left to right.\r
-\r
-Syntax:\r
-(start code)\r
-       MochaUI.Panel();\r
-(end)\r
-\r
-Arguments:\r
-       options\r
-\r
-Options:\r
-       id - The ID of the panel. This must be set when creating the panel.\r
-       column - Where to inject the panel. This must be set when creating the panel.\r
-       loadMethod - ('html', 'xhr', or 'iframe')\r
-       contentURL - Used if loadMethod is set to 'xhr' or 'iframe'.\r
-       evalScripts - (boolean) An xhr loadMethod option. Defaults to true.\r
-       evalResponse - (boolean) An xhr loadMethod option. Defaults to false.\r
-       content - (string or element) An html loadMethod option.\r
-       tabsURL - (url) \r
-       footer - (boolean)\r
-       footerURL - (url)\r
-       height - (number) Height of content area.\r
-       addClass - (string) Add a class to the panel.\r
-       scrollbars - (boolean)\r
-       padding - (object)\r
-       panelBackground - CSS background property for the panel.\r
-       onBeforeBuild - (function) Fired before the panel is created.\r
-       onContentLoaded - (function) Fired after the panel's conten is loaded.\r
-       onResize - (function) Fired when the panel is resized.\r
-       onCollapse - (function) Fired when the panel is collapsed.\r
-       onExpand - (function) Fired when the panel is expanded.\r
-               \r
-*/\r
-MochaUI.Panel = new Class({\r
-                                                       \r
-       Extends: MochaUI.Desktop,\r
-       \r
-       Implements: [Events, Options],\r
-       \r
-       options: {\r
-               id:               null,\r
-               title:            'New Panel',\r
-               column:           null,\r
-               loadMethod:       'html',\r
-               contentURL:       'pages/lipsum.html',\r
-       \r
-               // xhr options\r
-               evalScripts:      true,\r
-               evalResponse:     false,\r
-       \r
-               // html options\r
-               content:          'Panel content',\r
-               \r
-               // Tabs\r
-               tabsURL:          null,\r
-\r
-               footer:           false,\r
-               footerURL:        'pages/lipsum.html',\r
-               \r
-               // Style options:\r
-               height:           125,\r
-               addClass:         '',\r
-               scrollbars:       true,\r
-               padding:                  { top: 8, right: 8, bottom: 8, left: 8 },\r
-\r
-               // Color options:               \r
-               panelBackground:   '#f8f8f8',\r
-\r
-               // Events\r
-               onBeforeBuild:     $empty,\r
-               onContentLoaded:   $empty,\r
-               onResize:          $empty,\r
-               onCollapse:        $empty,\r
-               onExpand:          $empty\r
-\r
-       },      \r
-       initialize: function(options){\r
-               this.setOptions(options);\r
-\r
-               $extend(this, {\r
-                       timestamp: $time(),\r
-                       isCollapsed: false,\r
-                       oldHeight: 0,\r
-                       partner: null\r
-               });\r
-\r
-               // Shorten object chain\r
-               var instances = MochaUI.Panels.instances;\r
-               var instanceID = instances.get(this.options.id);\r
-       \r
-               // Check to see if there is already a class instance for this panel\r
-               if (instanceID){\r
-                       var currentInstance = instanceID;\r
-               }\r
-\r
-               // Check if panel already exists\r
-               if ( this.panelEl ){\r
-                       return;\r
-               }\r
-               else {                  \r
-                       instances.set(this.options.id, this);\r
-               }\r
-\r
-               this.fireEvent('onBeforeBuild');\r
-               \r
-               if (this.options.loadMethod == 'iframe') {\r
-                       // Iframes have their own scrollbars and padding.\r
-                       this.options.scrollbars = false;\r
-                       this.options.padding = { top: 0, right: 0, bottom: 0, left: 0 };\r
-               }\r
-\r
-               this.showHandle = true;\r
-               if ($(this.options.column).getChildren().length == 0){\r
-                       this.showHandle = false;\r
-               }\r
-\r
-               this.panelEl = new Element('div', {\r
-                       'id': this.options.id,\r
-                       'class': 'panel expanded',\r
-                       'styles': {\r
-                               'height': this.options.height,\r
-                               'background': this.options.panelBackground\r
-                       }\r
-               }).inject($(this.options.column));\r
-\r
-               this.panelEl.addClass(this.options.addClass);\r
-\r
-               this.contentEl = new Element('div', {\r
-                       'id': this.options.id + '_pad',\r
-                       'class': 'pad'\r
-               }).inject(this.panelEl);\r
-\r
-               if (this.options.footer){\r
-                       this.footerWrapperEl = new Element('div', {\r
-                               'id': this.options.id + '_panelFooterWrapper',\r
-                               'class': 'panel-footerWrapper'\r
-                       }).inject(this.panelEl);\r
-                       \r
-                       this.footerEl = new Element('div', {\r
-                               'id': this.options.id + '_panelFooter',\r
-                               'class': 'panel-footer'\r
-                       }).inject(this.footerWrapperEl);\r
-\r
-\r
-                       MochaUI.updateContent({\r
-                               'element':       this.panelEl,\r
-                               'childElement':  this.footerEl,\r
-                               'loadMethod':    'xhr',\r
-                               'url':           this.options.footerURL\r
-                       });\r
-                       \r
-               }\r
-\r
-               // This is in order to use the same variable as the windows do in updateContent.\r
-               // May rethink this.\r
-               this.contentWrapperEl = this.panelEl;\r
-               \r
-               // Set scrollbars, always use 'hidden' for iframe windows\r
-               this.contentWrapperEl.setStyles({\r
-                       'overflow': this.options.scrollbars && !this.iframeEl ? 'auto' : 'hidden'\r
-               });\r
-\r
-               this.contentEl.setStyles({\r
-                       'padding-top': this.options.padding.top,\r
-                       'padding-bottom': this.options.padding.bottom,\r
-                       'padding-left': this.options.padding.left,\r
-                       'padding-right': this.options.padding.right\r
-               });                     \r
-               \r
-               this.panelHeaderEl = new Element('div', {\r
-                       'id': this.options.id + '_header',\r
-                       'class': 'panel-header'\r
-               }).inject(this.panelEl, 'before');\r
-               \r
-               this.panelHeaderToolboxEl = new Element('div', {\r
-                       'id': this.options.id + '_headerToolbox',\r
-                       'class': 'panel-header-toolbox'\r
-               }).inject(this.panelHeaderEl);\r
-\r
-               this.collapseToggleEl = new Element('div', {\r
-                       'id': this.options.id + '_minmize',\r
-                       'class': 'panel-collapse icon16',\r
-                       'styles': {\r
-                               'width': 16,\r
-                               'height': 16\r
-                       },\r
-                       'title': 'Collapse Panel'\r
-               }).inject(this.panelHeaderToolboxEl);\r
-\r
-               this.collapseToggleEl.addEvent('click', function(event){\r
-                       var panel = this.panelEl;\r
-                       \r
-                       // Get siblings and make sure they are not all collapsed.\r
-                       var instances = MochaUI.Panels.instances;\r
-                       var expandedSiblings = [];\r
-                       panel.getAllPrevious('.panel').each(function(sibling){\r
-                               var currentInstance = instances.get(sibling.id);\r
-                               if (currentInstance.isCollapsed == false){\r
-                                       expandedSiblings.push(sibling);\r
-                               }\r
-                       });\r
-                       panel.getAllNext('.panel').each(function(sibling){\r
-                               var currentInstance = instances.get(sibling.id);\r
-                               if (currentInstance.isCollapsed == false){\r
-                                       expandedSiblings.push(sibling);\r
-                               }\r
-                       });\r
-\r
-                       if (this.isCollapsed == false) {\r
-                               var currentColumn = MochaUI.Columns.instances.get($(this.options.column).id);\r
-\r
-                               if (expandedSiblings.length == 0 && currentColumn.options.placement != 'main'){\r
-                                       var currentColumn = MochaUI.Columns.instances.get($(this.options.column).id);\r
-                                       currentColumn.columnToggle();\r
-                                       return;\r
-                               }\r
-                               else if (expandedSiblings.length == 0 && currentColumn.options.placement == 'main'){\r
-                                       return;\r
-                               }\r
-                               this.oldHeight = panel.getStyle('height').toInt();\r
-                               if (this.oldHeight < 10) this.oldHeight = 20;\r
-                               panel.setStyle('height', 0);\r
-                               this.isCollapsed = true;\r
-                               panel.addClass('collapsed');\r
-                               panel.removeClass('expanded');\r
-                               MochaUI.panelHeight(this.options.column, panel, 'collapsing');\r
-                               this.collapseToggleEl.removeClass('panel-collapsed');\r
-                               this.collapseToggleEl.addClass('panel-expand');\r
-                               this.collapseToggleEl.setProperty('title','Expand Panel');\r
-                               this.fireEvent('onCollapse');\r
-                       }\r
-                       else {\r
-                               panel.setStyle('height', this.oldHeight);\r
-                               this.isCollapsed = false;\r
-                               panel.addClass('expanded');\r
-                               panel.removeClass('collapsed');\r
-                               MochaUI.panelHeight(this.options.column, panel, 'expanding');\r
-                               this.collapseToggleEl.removeClass('panel-expand');\r
-                               this.collapseToggleEl.addClass('panel-collapsed');\r
-                               this.collapseToggleEl.setProperty('title','Collapse Panel');\r
-                               this.fireEvent('onExpand');\r
-                       }\r
-               }\r
-               .bind(this));\r
-               \r
-               this.panelHeaderContentEl = new Element('div', {\r
-                       'id': this.options.id + '_headerContent',\r
-                       'class': 'panel-headerContent'\r
-               }).inject(this.panelHeaderEl);\r
-\r
-               this.titleEl = new Element('h2', {\r
-                       'id': this.options.id + '_title'\r
-               }).inject(this.panelHeaderContentEl);\r
-\r
-               if (this.options.tabsURL == null){\r
-                       this.titleEl.set('html', this.options.title);\r
-               }               \r
-               else {\r
-                       this.panelHeaderContentEl.addClass('tabs');\r
-                       MochaUI.updateContent({\r
-                               'element':      this.panelEl,\r
-                               'childElement': this.panelHeaderContentEl,\r
-                               'loadMethod':   'xhr',\r
-                               'url':          this.options.tabsURL\r
-                       });\r
-               }\r
-\r
-               this.handleEl = new Element('div', {\r
-                       'id': this.options.id + '_handle',\r
-                       'class': 'horizontalHandle',\r
-                       'styles': {\r
-                               'display': this.showHandle == true ? 'block' : 'none'\r
-                       }\r
-               }).inject(this.panelEl, 'after');\r
-               \r
-               this.handleIconEl = new Element('div', {\r
-                       'id': this.options.id + '_handle_icon',\r
-                       'class': 'handleIcon'\r
-               }).inject(this.handleEl);\r
-\r
-               addResizeBottom(this.options.id);\r
-\r
-               // Add content to panel.\r
-               MochaUI.updateContent({\r
-                       'element': this.panelEl,\r
-                       'content':  this.options.content,\r
-                       'url':      this.options.contentURL\r
-               });\r
-\r
-               MochaUI.panelHeight(this.options.column, this.panelEl, 'new');\r
-\r
-       }\r
-});\r
-MochaUI.Panel.implement(new Options, new Events);\r
-\r
-\r
-MochaUI.extend({\r
-       // Panel Height \r
-       panelHeight: function(column, changing, action){\r
-               if (column != null) {\r
-                       MochaUI.panelHeight2($(column), changing, action);\r
-               }\r
-               else {\r
-                       $$('.column').each(function(column){\r
-                               MochaUI.panelHeight2(column);\r
-                       }.bind(this));\r
-               }\r
-       },\r
-       /*\r
-\r
-       actions can be new, collapsing or expanding.\r
-\r
-       */\r
-       panelHeight2: function(column, changing, action){\r
-\r
-                       var instances = MochaUI.Panels.instances;\r
-                       \r
-                       var parent = column.getParent();\r
-                       var columnHeight = parent.getStyle('height').toInt();\r
-                       if (Browser.Engine.trident4){\r
-                               columnHeight -= 1;\r
-                       }\r
-                       column.setStyle('height', columnHeight);\r
-\r
-                       var panels = column.getChildren('.panel');            // All the panels in the column.\r
-                       var panelsExpanded = column.getChildren('.expanded'); // All the expanded panels in the column.\r
-                       var panelsToResize = [];    // All the panels in the column whose height will be effected.\r
-                       var tallestPanel;           // The panel with the greatest height\r
-                       var tallestPanelHeight = 0;\r
-\r
-                       this.panelsHeight = 0;          // Height of all the panels in the column       \r
-                       this.height = 0;            // Height of all the elements in the column \r
-\r
-                       // Set panel resize partners\r
-                       panels.each(function(panel){\r
-                               currentInstance = instances.get(panel.id);\r
-                               if (panel.hasClass('expanded') && panel.getNext('.expanded')){\r
-                                       currentInstance.partner = panel.getNext('.expanded');\r
-                                       currentInstance.resize.attach();\r
-                                       currentInstance.handleEl.setStyles({\r
-                                               'display': 'block',\r
-                                               'cursor': 'n-resize'\r
-                                       }).removeClass('detached');\r
-                               }\r
-                               else {\r
-                                       currentInstance.resize.detach();\r
-                                       currentInstance.handleEl.setStyle('cursor', null).addClass('detached');\r
-                               }\r
-                               if (panel.getNext('.panel') == null){\r
-                                       currentInstance.handleEl.setStyle('display', 'none');\r
-                               }\r
-                       }.bind(this));\r
-                       \r
-                       // Get the total height of all the column's children\r
-                       column.getChildren().each(function(el){\r
-\r
-                               if (el.hasClass('panel')){\r
-                                       var currentInstance = instances.get(el.id);\r
-\r
-                                       // Are any next siblings Expanded?\r
-                                       areAnyNextSiblingsExpanded = function(el){\r
-                                               var test;\r
-                                               el.getAllNext('.panel').each(function(sibling){\r
-                                                       var siblingInstance = instances.get(sibling.id);\r
-                                                       if (siblingInstance.isCollapsed == false){\r
-                                                               test = true;\r
-                                                       }\r
-                                               }.bind(this));\r
-                                               return test;\r
-                                       }.bind(this);\r
-\r
-                                       // If a next sibling is expanding, are any of the nexts siblings of the expanding sibling Expanded?\r
-                                       areAnyExpandingNextSiblingsExpanded = function(){\r
-                                               var test;\r
-                                               changing.getAllNext('.panel').each(function(sibling){\r
-                                                       var siblingInstance = instances.get(sibling.id);\r
-                                                       if (siblingInstance.isCollapsed == false){\r
-                                                               test = true;\r
-                                                       }\r
-                                               }.bind(this));\r
-                                               return test;\r
-                                       }.bind(this);\r
-                                       \r
-                                       // Resize panels that are not collapsed or "new"\r
-                                       if (action == 'new' ) {\r
-                                               if (currentInstance.isCollapsed != true && el != changing) {\r
-                                                       panelsToResize.push(el);\r
-                                               }\r
-                                               \r
-                                               // Height of panels that can be resized\r
-                                               if (currentInstance.isCollapsed != true && el != changing) {\r
-                                                       this.panelsHeight += el.offsetHeight.toInt();\r
-                                               }\r
-                                       }\r
-                                       // Resize panels that are not collapsed. If a panel is collapsing\r
-                                       // resize any expanded panels below. If there are no expanded panels\r
-                                       // below it, resize the expanded panels above it.\r
-                                       else if (action == null || action == 'collapsing' ){\r
-                                               if (currentInstance.isCollapsed != true && (el.getAllNext('.panel').contains(changing) != true || areAnyNextSiblingsExpanded(el) != true)){\r
-                                                       panelsToResize.push(el);\r
-                                               }\r
-                                               \r
-                                               // Height of panels that can be resized\r
-                                               if (currentInstance.isCollapsed != true && (el.getAllNext('.panel').contains(changing) != true || areAnyNextSiblingsExpanded(el) != true)){\r
-                                                       this.panelsHeight += el.offsetHeight.toInt();\r
-                                               }\r
-                                       }\r
-                                       // Resize panels that are not collapsed and are not expanding.\r
-                                       // Resize any expanded panels below the expanding panel. If there are no expanded panels\r
-                                       // below it, resize the first expanded panel above it.\r
-                                       else if (action == 'expanding'){\r
-                                                  \r
-                                               if (currentInstance.isCollapsed != true && (el.getAllNext('.panel').contains(changing) != true || (areAnyExpandingNextSiblingsExpanded() != true && el.getNext('.expanded') == changing)) && el != changing){\r
-                                                       panelsToResize.push(el);\r
-                                               }\r
-                                               // Height of panels that can be resized\r
-                                               if (currentInstance.isCollapsed != true && (el.getAllNext('.panel').contains(changing) != true || (areAnyExpandingNextSiblingsExpanded() != true && el.getNext('.expanded') == changing)) && el != changing){\r
-                                                       this.panelsHeight += el.offsetHeight.toInt();\r
-                                               }\r
-                                       }\r
-\r
-                                       if (el.style.height){\r
-                                               this.height += el.getStyle('height').toInt();\r
-                                       }\r
-                               }\r
-                               else {\r
-                                       this.height += el.offsetHeight.toInt();\r
-                               }\r
-                       }.bind(this));\r
-\r
-                       // Get the remaining height\r
-                       var remainingHeight = column.offsetHeight.toInt() - this.height;\r
-                       \r
-                       this.height = 0;\r
-\r
-                       // Get height of all the column's children\r
-                       column.getChildren().each(function(el){\r
-                               this.height += el.offsetHeight.toInt();\r
-                       }.bind(this));\r
-                               \r
-                       var remainingHeight = column.offsetHeight.toInt() - this.height;\r
-\r
-                       panelsToResize.each(function(panel){\r
-                               var ratio = this.panelsHeight / panel.offsetHeight.toInt();\r
-                               var newPanelHeight = panel.getStyle('height').toInt() + (remainingHeight / ratio);\r
-                               if (newPanelHeight < 1){\r
-                                       newPanelHeight = 0;\r
-                               }\r
-                               panel.setStyle('height', newPanelHeight);\r
-                       }.bind(this));  \r
-\r
-                       // Make sure the remaining height is 0. If not add/subtract the\r
-                       // remaining height to the tallest panel. This makes up for browser resizing,\r
-                       // off ratios, and users trying to give panels too much height.\r
-                       \r
-                       // Get height of all the column's children\r
-                       this.height = 0;\r
-                       column.getChildren().each(function(el){\r
-                               this.height += el.offsetHeight.toInt();\r
-                               if (el.hasClass('panel') && el.getStyle('height').toInt() > tallestPanelHeight){\r
-                                       tallestPanel = el;\r
-                                       tallestPanelHeight = el.getStyle('height').toInt();\r
-                               }\r
-                       }.bind(this));\r
-\r
-                       var remainingHeight = column.offsetHeight.toInt() - this.height;\r
-\r
-                       if ((remainingHeight > 0 || remainingHeight < 0) && tallestPanelHeight > 0){\r
-                               tallestPanel.setStyle('height', tallestPanel.getStyle('height').toInt() + remainingHeight );\r
-                               if (tallestPanel.getStyle('height') < 1){\r
-                                       tallestPanel.setStyle('height', 0 );\r
-                               }\r
-                       }\r
-\r
-                       $$('.columnHandle').each(function(handle){\r
-                               var handleHeight = parent.getStyle('height').toInt() - handle.getStyle('border-top').toInt() - handle.getStyle('border-bottom').toInt();\r
-                               if (Browser.Engine.trident4){\r
-                                       handleHeight -= 1;\r
-                               }\r
-                               handle.setStyle('height', handleHeight);\r
-                       });\r
-                       \r
-                       panelsExpanded.each(function(panel){\r
-                               MochaUI.resizeChildren(panel);\r
-                       }.bind(this));\r
-       },\r
-       // May rename this resizeIframeEl()\r
-       resizeChildren: function(panel){\r
-               var instances = MochaUI.Panels.instances;\r
-               var currentInstance = instances.get(panel.id);\r
-               var contentWrapperEl = currentInstance.contentWrapperEl;\r
-\r
-               if (currentInstance.iframeEl){\r
-                       currentInstance.iframeEl.setStyles({\r
-                               'height': contentWrapperEl.getStyle('height'),\r
-                               'width': contentWrapperEl.offsetWidth - contentWrapperEl.getStyle('border-left').toInt() - contentWrapperEl.getStyle('border-right').toInt()\r
-                       });\r
-               }\r
-       },\r
-       // Remaining Width\r
-       rWidth: function(){     \r
-               $$('.rWidth').each(function(column){\r
-                       var currentWidth = column.offsetWidth.toInt();\r
-                       currentWidth -= column.getStyle('border-left').toInt();\r
-                       currentWidth -= column.getStyle('border-right').toInt();\r
-\r
-                       var parent = column.getParent();\r
-                       this.width = 0;\r
-                       \r
-                       // Get the total width of all the parent element's children\r
-                       parent.getChildren().each(function(el){\r
-                               if (el.hasClass('mocha') != true){\r
-                                       this.width += el.offsetWidth.toInt();\r
-                               }\r
-                       }.bind(this));\r
-               \r
-                       // Add the remaining width to the current element\r
-                       var remainingWidth = parent.offsetWidth.toInt() - this.width;\r
-                       var newWidth =  currentWidth + remainingWidth;\r
-                       if (newWidth < 1) newWidth = 0;\r
-                       column.setStyle('width', newWidth);\r
-                       column.getChildren('.panel').each(function(panel){\r
-                               panel.setStyle('width', newWidth - panel.getStyle('border-left').toInt() - panel.getStyle('border-right').toInt());\r
-                               MochaUI.resizeChildren(panel);\r
-                       }.bind(this));\r
-               });\r
-       }\r
-\r
-});\r
-\r
-function addResizeRight(element, min, max){\r
-       if (!$(element)) return;\r
-       element = $(element);\r
-\r
-       var instances = MochaUI.Columns.instances;\r
-       var currentInstance = instances.get(element.id);\r
-\r
-       var handle = element.getNext('.columnHandle');\r
-       handle.setStyle('cursor', 'e-resize');  \r
-       if (!min) min = 50;\r
-       if (!max) max = 250;\r
-       if (Browser.Engine.trident){\r
-               handle.addEvents({\r
-                       'mousedown': function(){\r
-                               handle.setCapture();\r
-                       },\r
-                       'mouseup': function(){\r
-                               handle.releaseCapture();\r
-                       }\r
-               });\r
-       }\r
-       currentInstance.resize = element.makeResizable({\r
-               handle: handle,\r
-               modifiers: {x: 'width', y: false},\r
-               limit: { x: [min, max] },\r
-               onStart: function(){\r
-                       element.getElements('iframe').setStyle('visibility','hidden');\r
-                       element.getNext('.column').getElements('iframe').setStyle('visibility','hidden');\r
-               }.bind(this),\r
-               onDrag: function(){\r
-                       MochaUI.rWidth();\r
-                       if (Browser.Engine.trident4){\r
-                               element.getChildren().each(function(el){\r
-                                       var width = $(element).getStyle('width').toInt();\r
-                                       width -= el.getStyle('border-right').toInt();\r
-                                       width -= el.getStyle('border-left').toInt();\r
-                                       width -= el.getStyle('padding-right').toInt();\r
-                                       width -= el.getStyle('padding-left').toInt();\r
-                                       el.setStyle('width', width);\r
-                               }.bind(this));\r
-                       }                                               \r
-               }.bind(this),\r
-               onComplete: function(){\r
-                       MochaUI.rWidth();\r
-                       element.getElements('iframe').setStyle('visibility','visible');\r
-                       element.getNext('.column').getElements('iframe').setStyle('visibility','visible');\r
-                       currentInstance.fireEvent('onResize');\r
-               }.bind(this)\r
-       });     \r
-}\r
-\r
-function addResizeLeft(element, min, max){\r
-       if (!$(element)) return;\r
-       element = $(element);\r
-\r
-       var instances = MochaUI.Columns.instances;\r
-       var currentInstance = instances.get(element.id);\r
-\r
-       var handle = element.getPrevious('.columnHandle');\r
-       handle.setStyle('cursor', 'e-resize');\r
-       var partner = element.getPrevious('.column');\r
-       if (!min) min = 50;\r
-       if (!max) max = 250;\r
-       if (Browser.Engine.trident){    \r
-               handle.addEvents({\r
-                       'mousedown': function(){\r
-                               handle.setCapture();\r
-                       },      \r
-                       'mouseup': function(){\r
-                               handle.releaseCapture();\r
-                       }\r
-               });\r
-       }\r
-       currentInstance.resize = element.makeResizable({\r
-               handle: handle,\r
-               modifiers: {x: 'width' , y: false},\r
-               invert: true,\r
-               limit: { x: [min, max] },\r
-               onStart: function(){\r
-                       $(element).getElements('iframe').setStyle('visibility','hidden');\r
-                       partner.getElements('iframe').setStyle('visibility','hidden');\r
-               }.bind(this),\r
-               onDrag: function(){\r
-                       MochaUI.rWidth();\r
-               }.bind(this),\r
-               onComplete: function(){\r
-                       MochaUI.rWidth();\r
-                       $(element).getElements('iframe').setStyle('visibility','visible');\r
-                       partner.getElements('iframe').setStyle('visibility','visible');\r
-                       currentInstance.fireEvent('onResize');\r
-               }.bind(this)\r
-       });\r
-}\r
-\r
-function addResizeBottom(element){\r
-       if (!$(element)) return;\r
-       var element = $(element);\r
-       \r
-       var instances = MochaUI.Panels.instances;\r
-       var currentInstance = instances.get(element.id);\r
-       var handle = currentInstance.handleEl;\r
-       handle.setStyle('cursor', 'n-resize');\r
-       partner = currentInstance.partner;\r
-       min = 0;\r
-       max = function(){\r
-               return element.getStyle('height').toInt() + partner.getStyle('height').toInt();\r
-       }.bind(this);\r
-       \r
-       if (Browser.Engine.trident){    \r
-               handle.addEvents({\r
-                       'mousedown': function(){\r
-                               handle.setCapture();\r
-                       },      \r
-                       'mouseup': function(){\r
-                               handle.releaseCapture();\r
-                       }\r
-               });\r
-       }\r
-       currentInstance.resize = element.makeResizable({\r
-               handle: handle,\r
-               modifiers: {x: false, y: 'height'},\r
-               limit: { y: [min, max] },\r
-               invert: false,\r
-               onBeforeStart: function(){\r
-                       partner = currentInstance.partner;\r
-                       this.originalHeight = element.getStyle('height').toInt();\r
-                       this.partnerOriginalHeight = partner.getStyle('height').toInt();\r
-               }.bind(this),\r
-               onStart: function(){\r
-                       if (currentInstance.iframeEl) {\r
-                               currentInstance.iframeEl.setStyle('visibility', 'hidden');\r
-                       }\r
-                       partner.getElements('iframe').setStyle('visibility','hidden');\r
-               }.bind(this),\r
-               onDrag: function(){\r
-                       partnerHeight = partnerOriginalHeight + (this.originalHeight - element.getStyle('height').toInt());\r
-                       partner.setStyle('height', partnerHeight);\r
-                       MochaUI.resizeChildren(element, element.getStyle('height').toInt());\r
-                       MochaUI.resizeChildren(partner, partnerHeight);\r
-               }.bind(this),\r
-               onComplete: function(){\r
-                       partnerHeight = partnerOriginalHeight + (this.originalHeight - element.getStyle('height').toInt());\r
-                       partner.setStyle('height', partnerHeight);\r
-                       MochaUI.resizeChildren(element, element.getStyle('height').toInt());\r
-                       MochaUI.resizeChildren(partner, partnerHeight);\r
-                       if (currentInstance.iframeEl) {\r
-                               currentInstance.iframeEl.setStyle('visibility', 'visible');\r
-                       }\r
-                       partner.getElements('iframe').setStyle('visibility','visible');\r
-                       currentInstance.fireEvent('onResize');\r
-               }.bind(this)\r
-       });\r
-}\r
-/*\r
-\r
-Script: Dock.js\r
-       Implements the dock/taskbar. Enables window minimize.\r
-\r
-Copyright:\r
-       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.  \r
-\r
-License:\r
-       MIT-style license.\r
-\r
-Requires:\r
-       Core.js, Window.js, Layout.js   \r
-\r
-Todo:\r
-       - Make it so the dock requires no initial html markup.\r
-\r
-*/\r
-\r
-MochaUI.options.extend({\r
-               // Naming options:\r
-               // If you change the IDs of the Mocha Desktop containers in your HTML, you need to change them here as well.\r
-               dockWrapper: 'dockWrapper',\r
-               dock:        'dock'\r
-});\r
-\r
-// Used by Desktop.js before MochaUI.Dock is initialized.\r
-window.addEvent('domready', function(){        \r
-       if ($('dockWrapper')) {\r
-               MochaUI.dockVisible = true;\r
-       }\r
-});\r
-\r
-MochaUI.extend({\r
-       /*\r
-\r
-       Function: minimizeAll\r
-               Minimize all windows that are minimizable.\r
-\r
-       */      \r
-       minimizeAll: function() {\r
-               $$('div.mocha').each(function(windowEl){\r
-               var currentInstance = MochaUI.Windows.instances.get(windowEl.id);\r
-                       if (!currentInstance.isMinimized && currentInstance.options.minimizable == true){\r
-                               MochaUI.Dock.minimizeWindow(windowEl);\r
-                       }\r
-               }.bind(this));\r
-       }\r
-});\r
-\r
-MochaUI.Dock = new Class({\r
-       Extends: MochaUI.Window,\r
-\r
-       Implements: [Events, Options],\r
-\r
-       options: {\r
-               useControls:          true,      // Toggles autohide and dock placement controls.\r
-               dockPosition:         'top',     // Position the dock starts in, top or bottom.\r
-               // Style options\r
-               dockTabColor:         [255, 255, 255],\r
-               trueButtonColor:      [70, 245, 70],     // Color for autohide on\r
-               enabledButtonColor:   [125, 208, 250], \r
-               disabledButtonColor:  [170, 170, 170]\r
-       },\r
-       initialize: function(options){\r
-               // Stops if MochaUI.Desktop is not implemented\r
-               if (!MochaUI.Desktop) return;\r
-               this.setOptions(options);\r
-               \r
-               this.dockWrapper   = $(MochaUI.options.dockWrapper);\r
-               this.dock          = $(MochaUI.options.dock);\r
-               this.autoHideEvent = null;              \r
-               this.dockAutoHide  = false;  // True when dock autohide is set to on, false if set to off\r
-\r
-               if (!this.dockWrapper) return;\r
-\r
-               if (!this.options.useControls){\r
-                       if($('dockPlacement')){\r
-                               $('dockPlacement').setStyle('cursor', 'default');\r
-                       }\r
-                       if($('dockAutoHide')){\r
-                               $('dockAutoHide').setStyle('cursor', 'default');\r
-                       }\r
-               }\r
-\r
-               this.dockWrapper.setStyles({\r
-                       'display':  'block',\r
-                       'position': 'absolute',\r
-                       'top':      null,\r
-                       'bottom':   MochaUI.Desktop.desktopFooter ? MochaUI.Desktop.desktopFooter.offsetHeight : 0,\r
-                       'left':     0\r
-               });\r
-               \r
-               if (this.options.useControls){\r
-                       this.initializeDockControls();\r
-               }\r
-\r
-               // Add check mark to menu if link exists in menu\r
-               if ($('dockLinkCheck')){\r
-                       this.sidebarCheck = new Element('div', {\r
-                               'class': 'check',\r
-                               'id': 'dock_check'\r
-                       }).inject($('dockLinkCheck'));\r
-               }\r
-\r
-               this.dockSortables = new Sortables('#dockSort', {\r
-                       opacity: Browser.Engine.trident ? 1 : .5,\r
-                       constrain: true,\r
-                       clone: false,\r
-                       revert: false\r
-               });\r
-\r
-               MochaUI.Desktop.setDesktopSize();\r
-       },\r
-       initializeDockControls: function(){\r
-               \r
-               if (this.options.useControls){\r
-                       // Insert canvas\r
-                       var canvas = new Element('canvas', {\r
-                               'id':     'dockCanvas',\r
-                               'width':  '15',\r
-                               'height': '18'\r
-                       }).inject(this.dock);\r
-\r
-                       // Dynamically initialize canvas using excanvas. This is only required by IE\r
-                       if (Browser.Engine.trident && MochaUI.ieSupport == 'excanvas'){\r
-                               G_vmlCanvasManager.initElement(canvas);\r
-                       }\r
-               }\r
-               \r
-               var dockPlacement = $('dockPlacement');\r
-               var dockAutoHide = $('dockAutoHide');\r
-\r
-               // Position top or bottom selector\r
-               dockPlacement.setProperty('title','Position Dock Top');\r
-\r
-               // Attach event\r
-               dockPlacement.addEvent('click', function(){\r
-                       this.moveDock();\r
-               }.bind(this));\r
-\r
-               // Auto Hide toggle switch\r
-               dockAutoHide.setProperty('title','Turn Auto Hide On');\r
-               \r
-               // Attach event Auto Hide \r
-               dockAutoHide.addEvent('click', function(event){\r
-                       if ( this.dockWrapper.getProperty('dockPosition') == 'top' )\r
-                               return false;\r
-\r
-                       var ctx = $('dockCanvas').getContext('2d');\r
-                       this.dockAutoHide = !this.dockAutoHide; // Toggle\r
-                       if (this.dockAutoHide){\r
-                               $('dockAutoHide').setProperty('title', 'Turn Auto Hide Off');\r
-                               //ctx.clearRect(0, 11, 100, 100);\r
-                               MochaUI.circle(ctx, 5 , 14, 3, this.options.trueButtonColor, 1.0);\r
-\r
-                               // Define event\r
-                               this.autoHideEvent = function(event) {\r
-                                       if (!this.dockAutoHide)\r
-                                               return;\r
-                                       if (!MochaUI.Desktop.desktopFooter) {\r
-                                               var dockHotspotHeight = this.dockWrapper.offsetHeight;\r
-                                               if (dockHotspotHeight < 25) dockHotspotHeight = 25;\r
-                                       }\r
-                                       else if (MochaUI.Desktop.desktopFooter) {\r
-                                               var dockHotspotHeight = this.dockWrapper.offsetHeight + MochaUI.Desktop.desktopFooter.offsetHeight;\r
-                                               if (dockHotspotHeight < 25) dockHotspotHeight = 25;\r
-                                       }                                               \r
-                                       if (!MochaUI.Desktop.desktopFooter && event.client.y > (document.getCoordinates().height - dockHotspotHeight)){\r
-                                               if (!MochaUI.dockVisible){\r
-                                                       this.dockWrapper.setStyle('display', 'block');\r
-                                                       MochaUI.dockVisible = true;\r
-                                                       MochaUI.Desktop.setDesktopSize();\r
-                                               }\r
-                                       }\r
-                                       else if (MochaUI.Desktop.desktopFooter && event.client.y > (document.getCoordinates().height - dockHotspotHeight)){\r
-                                               if (!MochaUI.dockVisible){\r
-                                                       this.dockWrapper.setStyle('display', 'block');\r
-                                                       MochaUI.dockVisible = true;\r
-                                                       MochaUI.Desktop.setDesktopSize();\r
-                                               }\r
-                                       }\r
-                                       else if (MochaUI.dockVisible){\r
-                                               this.dockWrapper.setStyle('display', 'none');\r
-                                               MochaUI.dockVisible = false;\r
-                                               MochaUI.Desktop.setDesktopSize();\r
-                                               \r
-                                       }\r
-                               }.bind(this);\r
-\r
-                               // Add event\r
-                               document.addEvent('mousemove', this.autoHideEvent);\r
-\r
-                       } else {\r
-                               $('dockAutoHide').setProperty('title', 'Turn Auto Hide On');\r
-                               //ctx.clearRect(0, 11, 100, 100);\r
-                               MochaUI.circle(ctx, 5 , 14, 3, this.options.enabledButtonColor, 1.0);\r
-                               // Remove event\r
-                               document.removeEvent('mousemove', this.autoHideEvent);\r
-                       }\r
-\r
-               }.bind(this));\r
-\r
-               // Draw dock controls\r
-               var ctx = $('dockCanvas').getContext('2d');\r
-               ctx.clearRect(0, 0, 100, 100);\r
-               MochaUI.circle(ctx, 5 , 4, 3, this.options.enabledButtonColor, 1.0);\r
-               MochaUI.circle(ctx, 5 , 14, 3, this.options.enabledButtonColor, 1.0);\r
-               \r
-               if (this.options.dockPosition == 'top'){\r
-                       this.moveDock();\r
-               }\r
-\r
-       },\r
-       moveDock: function(){\r
-                       var ctx = $('dockCanvas').getContext('2d');\r
-                       // Move dock to top position\r
-                       if (this.dockWrapper.getStyle('position') != 'relative'){\r
-                               this.dockWrapper.setStyles({\r
-                                       'position': 'relative',\r
-                                       'bottom':   null\r
-                               });\r
-                               this.dockWrapper.addClass('top');\r
-                               MochaUI.Desktop.setDesktopSize();\r
-                               this.dockWrapper.setProperty('dockPosition','top');\r
-                               ctx.clearRect(0, 0, 100, 100);\r
-                               MochaUI.circle(ctx, 5, 4, 3, this.options.enabledButtonColor, 1.0);\r
-                               MochaUI.circle(ctx, 5, 14, 3, this.options.disabledButtonColor, 1.0);\r
-                               $('dockPlacement').setProperty('title', 'Position Dock Bottom');\r
-                               $('dockAutoHide').setProperty('title', 'Auto Hide Disabled in Top Dock Position');\r
-                               this.dockAutoHide = false;\r
-                       }\r
-                       // Move dock to bottom position\r
-                       else {\r
-                               this.dockWrapper.setStyles({\r
-                                       'position':      'absolute',\r
-                                       'bottom':        MochaUI.Desktop.desktopFooter ? MochaUI.Desktop.desktopFooter.offsetHeight : 0\r
-                               });\r
-                               this.dockWrapper.removeClass('top');\r
-                               MochaUI.Desktop.setDesktopSize();\r
-                               this.dockWrapper.setProperty('dockPosition', 'bottom');\r
-                               ctx.clearRect(0, 0, 100, 100);\r
-                               MochaUI.circle(ctx, 5, 4, 3, this.options.enabledButtonColor, 1.0);\r
-                               MochaUI.circle(ctx, 5 , 14, 3, this.options.enabledButtonColor, 1.0);\r
-                               $('dockPlacement').setProperty('title', 'Position Dock Top');\r
-                               $('dockAutoHide').setProperty('title', 'Turn Auto Hide On');\r
-                       }\r
-       },\r
-       createDockTab: function(windowEl){\r
-\r
-               var currentInstance = MochaUI.Windows.instances.get(windowEl.id);\r
-\r
-               var dockTab = new Element('div', {\r
-                       'id': currentInstance.options.id + '_dockTab',\r
-                       'class': 'dockTab',\r
-                       'title': titleText\r
-               }).inject($('dockClear'), 'before');\r
-               \r
-               dockTab.addEvent('mousedown', function(e){\r
-                       new Event(e).stop();\r
-                       this.timeDown = $time();\r
-               });\r
-               \r
-               dockTab.addEvent('mouseup', function(e){\r
-                       this.timeUp = $time();\r
-                       if ((this.timeUp - this.timeDown) < 275){\r
-                               // If the visibility of the windows on the page are toggled off, toggle visibility on.\r
-                               if (MochaUI.Windows.windowsVisible == false) {\r
-                                       MochaUI.toggleWindowVisibility();\r
-                                       if (currentInstance.isMinimized == true) {\r
-                                               MochaUI.Dock.restoreMinimized.delay(25, MochaUI.Dock, windowEl);\r
-                                       }\r
-                                       else {\r
-                                               MochaUI.focusWindow(windowEl);\r
-                                       }\r
-                                       return;\r
-                               }\r
-                               // If window is minimized, restore window.\r
-                               if (currentInstance.isMinimized == true) {\r
-                                       MochaUI.Dock.restoreMinimized.delay(25, MochaUI.Dock, windowEl);\r
-                               }\r
-                               else{\r
-                                       // If window is not minimized and is focused, minimize window.\r
-                                       if (currentInstance.windowEl.hasClass('isFocused') && currentInstance.options.minimizable == true){\r
-                                               MochaUI.Dock.minimizeWindow(windowEl)\r
-                                       }\r
-                                       // If window is not minimized and is not focused, focus window. \r
-                                       else{\r
-                                               MochaUI.focusWindow(windowEl);\r
-                                       }\r
-                                       // if the window is not minimized and is outside the viewport, center it in the viewport.\r
-                                       var coordinates = document.getCoordinates();\r
-                                       if (windowEl.getStyle('left').toInt() > coordinates.width || windowEl.getStyle('top').toInt() > coordinates.height){\r
-                                               MochaUI.centerWindow(windowEl); \r
-                                       }\r
-                               }\r
-                       }\r
-               });\r
-\r
-               this.dockSortables.addItems(dockTab);\r
-\r
-               var titleText = currentInstance.titleEl.innerHTML;\r
-\r
-               var dockTabText = new Element('div', {\r
-                       'id': currentInstance.options.id + '_dockTabText',\r
-                       'class': 'dockText'\r
-               }).set('html', titleText.substring(0,20) + (titleText.length > 20 ? '...' : '')).inject($(dockTab));\r
-\r
-               // If I implement this again, will need to also adjust the titleText truncate and the tab's\r
-               // left padding.\r
-               if (currentInstance.options.icon != false){\r
-                       // dockTabText.setStyle('background', 'url(' + currentInstance.options.icon + ') 4px 4px no-repeat');\r
-               }\r
-               \r
-               // Need to resize everything in case the dock wraps when a new tab is added\r
-               MochaUI.Desktop.setDesktopSize();\r
-\r
-       },\r
-       makeActiveTab: function(){\r
-\r
-               // getWindowWith HighestZindex is used in case the currently focused window\r
-               // is closed.           \r
-               var windowEl = MochaUI.getWindowWithHighestZindex();\r
-               var currentInstance = MochaUI.Windows.instances.get(windowEl.id);\r
-               \r
-               $$('div.dockTab').removeClass('activeDockTab');\r
-               if (currentInstance.isMinimized != true) {\r
-                       \r
-                       currentInstance.windowEl.addClass('isFocused');\r
-\r
-                       var currentButton = $(currentInstance.options.id + '_dockTab');\r
-                       if (currentButton != null) {\r
-                               currentButton.addClass('activeDockTab');\r
-                       }\r
-               }\r
-               else {\r
-                       currentInstance.windowEl.removeClass('isFocused');\r
-               }       \r
-       },      \r
-       minimizeWindow: function(windowEl){\r
-               if (windowEl != $(windowEl)) return;\r
-               \r
-               var currentInstance = MochaUI.Windows.instances.get(windowEl.id);\r
-               currentInstance.isMinimized = true;\r
-\r
-               // Hide iframe\r
-               // Iframe should be hidden when minimizing, maximizing, and moving for performance and Flash issues\r
-               if ( currentInstance.iframeEl ) {\r
-                       currentInstance.iframeEl.setStyle('visibility', 'hidden');\r
-               }\r
-\r
-               // Hide window and add to dock  \r
-               currentInstance.contentBorderEl.setStyle('visibility', 'hidden');\r
-               if(currentInstance.toolbarWrapperEl){           \r
-                       currentInstance.toolbarWrapperEl.setStyle('visibility', 'hidden');\r
-               }\r
-               windowEl.setStyle('visibility', 'hidden');\r
-\r
-                // Fixes a scrollbar issue in Mac FF2\r
-               if (Browser.Platform.mac && Browser.Engine.gecko){\r
-                       if (/Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent)) {\r
-                               var ffversion = new Number(RegExp.$1);\r
-                               if (ffversion < 3) {\r
-                                       currentInstance.contentWrapperEl.setStyle('overflow', 'hidden');\r
-                               }\r
-                       }\r
-               }\r
-       \r
-               MochaUI.Desktop.setDesktopSize();\r
-\r
-               // Have to use timeout because window gets focused when you click on the minimize button\r
-               setTimeout(function(){\r
-                       windowEl.setStyle('zIndex', 1);\r
-                       windowEl.removeClass('isFocused');\r
-                       this.makeActiveTab();   \r
-               }.bind(this),100);      \r
-\r
-               currentInstance.fireEvent('onMinimize', windowEl);\r
-       },\r
-       restoreMinimized: function(windowEl) {\r
-\r
-               var currentInstance = MochaUI.Windows.instances.get(windowEl.id);\r
-\r
-               if (currentInstance.isMinimized == false) return;\r
-\r
-               if (MochaUI.Windows.windowsVisible == false){\r
-                       MochaUI.toggleWindowVisibility();\r
-               }\r
-\r
-               MochaUI.Desktop.setDesktopSize();\r
-\r
-                // Part of Mac FF2 scrollbar fix\r
-               if (currentInstance.options.scrollbars == true && !currentInstance.iframeEl){ \r
-                       currentInstance.contentWrapperEl.setStyle('overflow', 'auto');\r
-               }\r
-\r
-               if (currentInstance.isCollapsed) {\r
-                       MochaUI.collapseToggle(windowEl);\r
-               }\r
-\r
-               windowEl.setStyle('visibility', 'visible');\r
-               currentInstance.contentBorderEl.setStyle('visibility', 'visible');\r
-               if(currentInstance.toolbarWrapperEl){\r
-                       currentInstance.toolbarWrapperEl.setStyle('visibility', 'visible');\r
-               }\r
-\r
-               // Show iframe\r
-               if ( currentInstance.iframeEl ) {\r
-                       currentInstance.iframeEl.setStyle('visibility', 'visible');\r
-               }\r
-\r
-               currentInstance.isMinimized = false;\r
-               MochaUI.focusWindow(windowEl);\r
-               currentInstance.fireEvent('onRestore', windowEl);\r
-\r
-       }\r
-});\r
-MochaUI.Dock.implement(new Options, new Events);\r
-/*\r
-\r
-Script: Workspaces.js\r
-       Save and load workspaces. The Workspaces emulate Adobe Illustrator functionality remembering what windows are open and where they are positioned. There will be two versions, a limited version that saves state to a cookie, and a fully functional version that saves state to a database.\r
-\r
-Copyright:\r
-       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.\r
-\r
-License:\r
-       MIT-style license.\r
-\r
-Requires:\r
-       Core.js, Window.js\r
-\r
-To do:\r
-       - Move to Window\r
-\r
-*/\r
-\r
-MochaUI.extend({                          \r
-       /*\r
-       \r
-       Function: saveWorkspace\r
-               Save the current workspace.\r
-       \r
-       Syntax:\r
-       (start code)\r
-               MochaUI.saveWorkspace();\r
-       (end)\r
-       \r
-       Notes:\r
-               This is experimental. This version saves the ID of each open window to a cookie, and reloads those windows using the functions in mocha-init.js. This requires that each window have a function in mocha-init.js used to open them. Functions must be named the windowID + "Window". So if your window is called mywindow, it needs a function called mywindowWindow in mocha-init.js.\r
-       \r
-       */\r
-       saveWorkspace: function(){\r
-               this.cookie = new Hash.Cookie('mochaUIworkspaceCookie', {duration: 3600});\r
-               this.cookie.empty();\r
-               MochaUI.Windows.instances.each(function(instance) {\r
-                       instance.saveValues();\r
-                       this.cookie.set(instance.options.id, {\r
-                               'id': instance.options.id,\r
-                               'top': instance.options.y,\r
-                               'left': instance.options.x\r
-                       });\r
-               }.bind(this));\r
-               this.cookie.save();\r
-\r
-               new MochaUI.Window({\r
-                       loadMethod: 'html',\r
-                       type: 'notification',\r
-                       addClass: 'notification',\r
-                       content: 'Workspace saved.',\r
-                       closeAfter: '1400',\r
-                       width: 200,\r
-                       height: 40,\r
-                       y: 53,\r
-                       padding:  { top: 10, right: 12, bottom: 10, left: 12 },\r
-                       shadowBlur: 5,\r
-                       bodyBgColor: [255, 255, 255]\r
-               });\r
-               \r
-       },\r
-       windowUnload: function(){\r
-               if ($$('div.mocha').length == 0 && this.myChain){\r
-                       this.myChain.callChain();\r
-               }               \r
-       },\r
-       loadWorkspace2: function(workspaceWindows){             \r
-               workspaceWindows.each(function(instance){\r
-                       windowFunction = eval('MochaUI.' + instance.id + 'Window');\r
-                       if (windowFunction){\r
-                               eval('MochaUI.' + instance.id + 'Window();');\r
-                               $(instance.id).setStyles({\r
-                                       top: instance.top,\r
-                                       left: instance.left\r
-                               });\r
-                       }\r
-               }.bind(this));\r
-               this.loadingWorkspace = false;\r
-       },\r
-       /*\r
-\r
-       Function: loadWorkspace\r
-               Load the saved workspace.\r
-\r
-       Syntax:\r
-       (start code)\r
-               MochaUI.loadWorkspace();\r
-       (end)\r
-\r
-       */\r
-       loadWorkspace: function(){\r
-               cookie = new Hash.Cookie('mochaUIworkspaceCookie', {duration: 3600});\r
-               workspaceWindows = cookie.load();\r
-\r
-               if(!cookie.getKeys().length){\r
-                       new MochaUI.Window({\r
-                               loadMethod: 'html',\r
-                               type: 'notification',\r
-                               addClass: 'notification',\r
-                               content: 'You have no saved workspace.',\r
-                               closeAfter: '1400',\r
-                               width: 220,\r
-                               height: 40,\r
-                               y: 25,\r
-                               padding:  { top: 10, right: 12, bottom: 10, left: 12 },\r
-                               shadowBlur: 5,\r
-                               bodyBgColor: [255, 255, 255]\r
-                       });\r
-                       return;\r
-               }\r
-\r
-               if ($$('div.mocha').length != 0){\r
-                       this.loadingWorkspace = true;\r
-                       this.myChain = new Chain();\r
-                       this.myChain.chain(\r
-                               function(){\r
-                                       $$('div.mocha').each(function(el) {\r
-                                               this.closeWindow(el);\r
-                                       }.bind(this));\r
-                               }.bind(this),\r
-                               function(){\r
-                                       this.loadWorkspace2(workspaceWindows);\r
-                               }.bind(this)\r
-                       );\r
-                       this.myChain.callChain();\r
-               }\r
-               else {\r
-                       this.loadWorkspace2(workspaceWindows);\r
-               }\r
-\r
-       }\r
-});\r
+/* 
+
+Script: Core.js
+       MochaUI - A Web Applications User Interface Framework.
+
+Copyright:
+       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.
+
+License:
+       MIT-style license.
+
+Contributors:
+       - Scott F. Frederick
+       - Joel Lindau
+
+Note:
+       This documentation is taken directly from the javascript source files. It is built using Natural Docs.
+
+Todo:
+       Consider making title tooltips optional and using them more often.
+
+*/
+
+var MochaUI = new Hash({
+       options: new Hash({
+               useEffects: true  // Toggles the majority of window fade and move effects.
+       }),
+       Columns: {
+               instances:      new Hash()
+       },
+       Panels: {
+               instances:      new Hash()
+       },              
+       Windows: {        
+               instances:      new Hash(),
+               indexLevel:     100,          // Used for z-Index
+               windowIDCount:  0,                // Used for windows without an ID defined by the user
+               windowsVisible: true          // Ctrl-Alt-Q to toggle window visibility
+       },      
+       ieSupport:  'excanvas',   // Makes it easier to switch between Excanvas and Moocanvas for testing
+       focusingWindow: 'false',
+       /*
+       
+       Function: updateContent
+               Replace the content of a window or panel.
+               
+       Arguments:
+               element - The parent window or panel.
+               childElement - The child element of the window or panel recieving the content.
+               title - (string) Change this if you want to change the title of the window or panel.
+               content - (string or element) An html loadMethod option.
+               loadMethod - ('html', 'xhr', or 'iframe') Defaults to 'html'.
+               url - Used if loadMethod is set to 'xhr' or 'iframe'.
+               padding - (object)
+
+       */      
+       updateContent: function(updateOptions){
+
+               var options = {
+                       'element':      null,
+                       'childElement': null,
+                       'title':        null,
+                       'content':      null,
+                       'loadMethod':   null,
+                       'url':          null,
+                       'padding':      null
+               };
+               $extend(options, updateOptions);
+
+               if (!options.element) return;
+               var element = options.element;
+
+               if (MochaUI.Windows.instances.get(element.id)) {
+                       var recipient = 'window';
+                       var currentInstance = MochaUI.Windows.instances.get(element.id);
+                       var spinnerEl = currentInstance.spinnerEl;
+                       if (options.title) {
+                               currentInstance.titleEl.set('html', options.title);
+                       }
+               }
+               else {
+                       var recipient = 'panel';
+                       var currentInstance = MochaUI.Panels.instances.get(element.id);
+                       if (options.title) {
+                               currentInstance.titleEl.set('html', options.title);
+                       }
+               }
+
+               var contentEl = currentInstance.contentEl;
+               if (options.childElement != null) {
+                       var contentContainer = options.childElement;
+               }
+               else {
+                       var contentContainer = currentInstance.contentEl;
+               }
+               
+               var loadMethod = options.loadMethod != null ? options.loadMethod : currentInstance.options.loadMethod;
+               
+               // Set scrollbars if loading content in main content container.
+               // Always use 'hidden' for iframe windows
+               if (contentContainer == currentInstance.contentEl) {
+                       currentInstance.contentWrapperEl.setStyles({
+                               'overflow': currentInstance.options.scrollbars == true && loadMethod != 'iframe' ? 'auto' : 'hidden'
+                       });
+               }
+
+               var contentWrapperEl = currentInstance.contentWrapperEl;
+               
+               if (options.padding != null) {
+                       contentEl.setStyles({
+                               'padding-top': options.padding.top,
+                               'padding-bottom': options.padding.bottom,
+                               'padding-left': options.padding.left,
+                               'padding-right': options.padding.right
+                       });
+               }
+
+               // Remove old content.
+               if (contentContainer == contentEl){
+                       contentEl.empty();
+               }
+
+               // Load new content.
+               switch(loadMethod){
+                       case 'xhr':
+                               new Request.HTML({
+                                       url: options.url,
+                                       update: contentContainer,
+                                       evalScripts: currentInstance.options.evalScripts,
+                                       evalResponse: currentInstance.options.evalResponse,
+                                       onRequest: function(){
+                                               if (recipient == 'window' && contentContainer == contentEl){
+                                                       currentInstance.showSpinner(spinnerEl);
+                                               }
+                                               else if (recipient == 'panel' && contentContainer == contentEl && $('spinner')){
+                                                       $('spinner').setStyle('visibility','visible');  
+                                               }
+                                       }.bind(this),
+                                       onFailure: function(){
+                                               if (contentContainer == contentEl){
+                                                       contentContainer.set('html','<p><strong>Error Loading XMLHttpRequest</strong></p>');
+                                                       if (recipient == 'window') {
+                                                               currentInstance.hideSpinner(spinnerEl);
+                                                       }
+                                                       else if (recipient == 'panel' && $('spinner')) {
+                                                               $('spinner').setStyle('visibility', 'hidden');
+                                                       }
+                                               }
+                                       }.bind(this),
+                                       onException: function(){}.bind(this),
+                                       onSuccess: function(){
+                                               if (contentContainer == contentEl){
+                                                       if (recipient == 'window'){
+                                                               currentInstance.hideSpinner(spinnerEl);
+                                                       }
+                                                       else if (recipient == 'panel' && $('spinner')){
+                                                               $('spinner').setStyle('visibility', 'hidden');
+                                                       }
+                                                       currentInstance.fireEvent('onContentLoaded', element);
+                                               }
+                                       }.bind(this),
+                                       onComplete: function(){}.bind(this)
+                               }).get();
+                               break;
+                       case 'iframe': // May be able to streamline this if the iframe already exists.
+                               if ( currentInstance.options.contentURL == '' || contentContainer != contentEl) {
+                                       break;
+                               }
+                               currentInstance.iframeEl = new Element('iframe', {
+                                       'id': currentInstance.options.id + '_iframe',
+                                       'name':  currentInstance.options.id + '_iframe',
+                                       'class': 'mochaIframe',
+                                       'src': options.url,
+                                       'marginwidth':  0,
+                                       'marginheight': 0,
+                                       'frameBorder':  0,
+                                       'scrolling':    'auto',
+                                       'styles': {
+                                               'height': contentWrapperEl.offsetHeight - contentWrapperEl.getStyle('border-top').toInt() - contentWrapperEl.getStyle('border-bottom').toInt(),
+                                               'width': currentInstance.panelEl ? contentWrapperEl.offsetWidth - contentWrapperEl.getStyle('border-left').toInt() - contentWrapperEl.getStyle('border-right').toInt() : '100%' 
+                                       }
+                               }).injectInside(contentEl);
+
+                               // Add onload event to iframe so we can hide the spinner and run onContentLoaded()
+                               currentInstance.iframeEl.addEvent('load', function(e) {
+                                       if (recipient == 'window') {
+                                               currentInstance.hideSpinner(spinnerEl);
+                                       }
+                                       else if (recipient == 'panel' && contentContainer == contentEl && $('spinner')) {
+                                               $('spinner').setStyle('visibility', 'hidden');
+                                       }
+                                       currentInstance.fireEvent('onContentLoaded', element);
+                               }.bind(this));
+                               if (recipient == 'window') {
+                                       currentInstance.showSpinner(spinnerEl);
+                               }
+                               else if (recipient == 'panel' && contentContainer == contentEl && $('spinner')){
+                                       $('spinner').setStyle('visibility', 'visible'); 
+                               }
+                               break;
+                       case 'html':
+                       default:
+                               // Need to test injecting elements as content.
+                               var elementTypes = new Array('element', 'textnode', 'whitespace', 'collection');
+                               if (elementTypes.contains($type(options.content))){
+                                       options.content.inject(contentContainer);
+                               } else {
+                                       contentContainer.set('html', options.content);
+                               }
+                               currentInstance.fireEvent('onContentLoaded', element);
+                               break;
+               }
+
+       },
+       /*
+       
+       Function: reloadIframe
+               Reload an iframe. Fixes an issue in Firefox when trying to use location.reload on an iframe that has been destroyed and recreated.
+
+       Arguments:
+               iframe - This should be both the name and the id of the iframe.
+
+       Syntax:
+               (start code)
+               MochaUI.reloadIframe(element);
+               (end)
+
+       Example:
+               To reload an iframe from within another iframe:
+               (start code)
+               parent.MochaUI.reloadIframe('myIframeName');
+               (end)
+
+       */
+       reloadIframe: function(iframe){
+               if (Browser.Engine.gecko) {
+                       $(iframe).src = $(iframe).src;
+               }
+               else {
+                       top.frames[iframe].location.reload(true);
+               }
+       },
+       collapseToggle: function(windowEl){
+               var instances = MochaUI.Windows.instances;
+               var currentInstance = instances.get(windowEl.id);
+               var handles = currentInstance.windowEl.getElements('.handle');
+               if (currentInstance.isMaximized == true) return;                
+               if (currentInstance.isCollapsed == false) {
+                       currentInstance.isCollapsed = true;
+                       handles.setStyle('display', 'none');
+                       if ( currentInstance.iframeEl ) {
+                               currentInstance.iframeEl.setStyle('visibility', 'hidden');
+                       }
+                       currentInstance.contentBorderEl.setStyles({
+                               visibility: 'hidden',
+                               position: 'absolute',
+                               top: -10000,
+                               left: -10000
+                       });
+                       if(currentInstance.toolbarWrapperEl){
+                               currentInstance.toolbarWrapperEl.setStyles({
+                                       visibility: 'hidden',
+                                       position: 'absolute',
+                                       top: -10000,
+                                       left: -10000
+                               });
+                       }
+                       currentInstance.drawWindowCollapsed(windowEl);
+               }
+               else {
+                       currentInstance.isCollapsed = false;
+                       currentInstance.drawWindow(windowEl);
+                       currentInstance.contentBorderEl.setStyles({
+                               visibility: 'visible',
+                               position: null,
+                               top: null,
+                               left: null
+                       });
+                       if(currentInstance.toolbarWrapperEl){
+                               currentInstance.toolbarWrapperEl.setStyles({
+                                       visibility: 'visible',
+                                       position: null,
+                                       top: null,
+                                       left: null
+                               });
+                       }
+                       if ( currentInstance.iframeEl ) {
+                               currentInstance.iframeEl.setStyle('visibility', 'visible');
+                       }
+                       handles.setStyle('display', 'block');
+               }
+       },
+       /*
+
+       Function: closeWindow
+               Closes a window.
+
+       Syntax:
+       (start code)
+               MochaUI.closeWindow();
+       (end)
+
+       Arguments: 
+               windowEl - the ID of the window to be closed
+
+       Returns:
+               true - the window was closed
+               false - the window was not closed
+
+       */
+       closeWindow: function(windowEl){
+               // Does window exist and is not already in process of closing ?
+
+               var instances = MochaUI.Windows.instances;
+               var currentInstance = instances.get(windowEl.id);
+               if (windowEl != $(windowEl) || currentInstance.isClosing) return;
+                       
+               currentInstance.isClosing = true;
+               currentInstance.fireEvent('onClose', windowEl);
+               if (currentInstance.check) currentInstance.check.destroy();
+
+               if ((currentInstance.options.type == 'modal' || currentInstance.options.type == 'modal2') && Browser.Engine.trident4){
+                               $('modalFix').setStyle('display', 'none');
+               }
+
+               if (MochaUI.options.useEffects == false){
+                       if (currentInstance.options.type == 'modal' || currentInstance.options.type == 'modal2'){
+                               $('modalOverlay').setStyle('opacity', 0);
+                       }
+                       MochaUI.closingJobs(windowEl);
+                       return true;    
+               }
+               else {
+                       // Redraws IE windows without shadows since IE messes up canvas alpha when you change element opacity
+                       if (Browser.Engine.trident) currentInstance.drawWindow(windowEl, false);
+                       if (currentInstance.options.type == 'modal' || currentInstance.options.type == 'modal2'){
+                               MochaUI.Modal.modalOverlayCloseMorph.start({
+                                       'opacity': 0
+                               });
+                       }
+                       var closeMorph = new Fx.Morph(windowEl, {
+                               duration: 120,
+                               onComplete: function(){
+                                       MochaUI.closingJobs(windowEl);
+                                       return true;
+                               }.bind(this)
+                       });
+                       closeMorph.start({
+                               'opacity': .4
+                       });
+               }
+
+       },
+       closingJobs: function(windowEl){
+
+               var instances = MochaUI.Windows.instances;
+               var currentInstance = instances.get(windowEl.id);
+               windowEl.setStyle('visibility', 'hidden');
+               windowEl.destroy();
+               currentInstance.fireEvent('onCloseComplete');
+               
+               if (currentInstance.options.type != 'notification'){
+                       var newFocus = this.getWindowWithHighestZindex();
+                       this.focusWindow(newFocus);
+               }
+
+               instances.erase(currentInstance.options.id);
+               if (this.loadingWorkspace == true) {
+                       this.windowUnload();
+               }
+
+               if (MochaUI.Dock && $(MochaUI.options.dock) && currentInstance.options.type == 'window') {
+                       var currentButton = $(currentInstance.options.id + '_dockTab');
+                       if (currentButton != null) {
+                               MochaUI.Dock.dockSortables.removeItems(currentButton).destroy();
+                       }
+                       // Need to resize everything in case the dock becomes smaller when a tab is removed
+                       MochaUI.Desktop.setDesktopSize();
+               }
+       },
+       /*
+       
+       Function: closeAll      
+               Close all open windows.
+
+       */
+       closeAll: function() {          
+               $$('div.mocha').each(function(windowEl){
+                       this.closeWindow(windowEl);
+               }.bind(this));
+       },
+       /*
+
+       Function: toggleWindowVisibility
+               Toggle window visibility with Ctrl-Alt-Q.
+
+       */      
+       toggleWindowVisibility: function(){
+               MochaUI.Windows.instances.each(function(instance){
+                       if (instance.options.type == 'modal' || instance.options.type == 'modal2' || instance.isMinimized == true) return;                                                                      
+                       var id = $(instance.options.id);
+                       if (id.getStyle('visibility') == 'visible'){
+                               if (instance.iframe){
+                                       instance.iframeEl.setStyle('visibility', 'hidden');
+                               }
+                               if (instance.toolbarEl){
+                                       instance.toolbarWrapperEl.setStyle('visibility', 'hidden');
+                               }
+                               instance.contentBorderEl.setStyle('visibility', 'hidden');
+
+                               id.setStyle('visibility', 'hidden');
+                               MochaUI.Windows.windowsVisible = false;
+                       }
+                       else {
+                               id.setStyle('visibility', 'visible');
+                               instance.contentBorderEl.setStyle('visibility', 'visible');
+                               if (instance.iframe){
+                                       instance.iframeEl.setStyle('visibility', 'visible');
+                               }
+                               if (instance.toolbarEl){
+                                       instance.toolbarWrapperEl.setStyle('visibility', 'visible');
+                               }
+                               MochaUI.Windows.windowsVisible = true;
+                       }
+               }.bind(this));
+
+       },
+       focusWindow: function(windowEl, fireEvent){
+
+               // This is used with blurAll
+               MochaUI.focusingWindow = 'true';
+               var windowClicked = function(){
+                       MochaUI.focusingWindow = 'false';
+               };              
+               windowClicked.delay(170, this);
+
+               // Only focus when needed
+               if ($$('.mocha').length == 0) return;
+               if (windowEl != $(windowEl) || windowEl.hasClass('isFocused')) return;
+
+               var instances =  MochaUI.Windows.instances;
+               var currentInstance = instances.get(windowEl.id);
+       
+               if (currentInstance.options.type == 'notification') return;
+
+               MochaUI.Windows.indexLevel += 2;
+               windowEl.setStyle('zIndex', MochaUI.Windows.indexLevel);
+
+               // Used when dragging and resizing windows
+               $('windowUnderlay').setStyle('zIndex', MochaUI.Windows.indexLevel - 1).inject($(windowEl),'after');
+
+               // Fire onBlur for the window that lost focus.
+               instances.each(function(instance){
+                       if (instance.windowEl.hasClass('isFocused')){
+                               instance.fireEvent('onBlur', instance.windowEl);
+                       }
+                       instance.windowEl.removeClass('isFocused');
+               });
+
+               if (MochaUI.Dock && $(MochaUI.options.dock) && currentInstance.options.type == 'window') {
+                       MochaUI.Dock.makeActiveTab();
+               }
+               currentInstance.windowEl.addClass('isFocused');
+
+               if (fireEvent != false){
+                       currentInstance.fireEvent('onFocus', windowEl);
+               }
+
+       },
+       getWindowWithHighestZindex: function(){
+               this.highestZindex = 0;
+               $$('div.mocha').each(function(element){
+                       this.zIndex = element.getStyle('zIndex');
+                       if (this.zIndex >= this.highestZindex) {
+                               this.highestZindex = this.zIndex;
+                       }       
+               }.bind(this));
+               $$('div.mocha').each(function(element){
+                       if (element.getStyle('zIndex') == this.highestZindex) {
+                               this.windowWithHighestZindex = element;
+                       }
+               }.bind(this));
+               return this.windowWithHighestZindex;
+       },
+       blurAll: function(){
+               if (MochaUI.focusingWindow == 'false') {
+                       $$('.mocha').each(function(windowEl){
+                               var instances =  MochaUI.Windows.instances;
+                               var currentInstance = instances.get(windowEl.id);
+                               if (currentInstance.options.type != 'modal' && currentInstance.options.type != 'modal2'){
+                                       windowEl.removeClass('isFocused');
+                               }
+                       });
+                       $$('div.dockTab').removeClass('activeDockTab');
+               }
+       },
+       roundedRect: function(ctx, x, y, width, height, radius, rgb, a){
+               ctx.fillStyle = 'rgba(' + rgb.join(',') + ',' + a + ')';
+               ctx.beginPath();
+               ctx.moveTo(x, y + radius);
+               ctx.lineTo(x, y + height - radius);
+               ctx.quadraticCurveTo(x, y + height, x + radius, y + height);
+               ctx.lineTo(x + width - radius, y + height);
+               ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
+               ctx.lineTo(x + width, y + radius);
+               ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
+               ctx.lineTo(x + radius, y);
+               ctx.quadraticCurveTo(x, y, x, y + radius);
+               ctx.fill(); 
+       },
+       triangle: function(ctx, x, y, width, height, rgb, a){
+               ctx.beginPath();
+               ctx.moveTo(x + width, y);
+               ctx.lineTo(x, y + height);
+               ctx.lineTo(x + width, y + height);
+               ctx.closePath();
+               ctx.fillStyle = 'rgba(' + rgb.join(',') + ',' + a + ')';
+               ctx.fill();
+       },
+       circle: function(ctx, x, y, diameter, rgb, a){
+               ctx.beginPath();
+               ctx.moveTo(x, y);
+               ctx.arc(x, y, diameter, 0, Math.PI*2, true);
+               ctx.fillStyle = 'rgba(' + rgb.join(',') + ',' + a + ')';
+               ctx.fill();
+       },
+       /*
+
+       Function: centerWindow
+               Center a window in it's container. If windowEl is undefined it will center the window that has focus.
+
+       */
+       centerWindow: function(windowEl){
+               
+               if(!windowEl){
+                       MochaUI.Windows.instances.each(function(instance){
+                               if (instance.windowEl.hasClass('isFocused')){
+                                       windowEl = instance.windowEl;
+                               }
+                       });
+               }
+
+               var currentInstance = MochaUI.Windows.instances.get(windowEl.id);
+               var options = currentInstance.options;
+               var dimensions = options.container.getCoordinates();
+               var windowPosTop = (dimensions.height * .5) - ((options.height + currentInstance.headerFooterShadow) * .5);
+               if (windowPosTop < 0) {
+                       windowPosTop = 0;
+               }
+               var windowPosLeft =     (dimensions.width * .5) - (options.width * .5);
+               if (windowPosLeft < 0) {
+                       windowPosLeft = 0;
+               }
+               if (MochaUI.options.useEffects == true){
+                       currentInstance.morph.start({
+                               'top': windowPosTop,
+                               'left': windowPosLeft
+                       });
+               }
+               else {
+                       windowEl.setStyles({
+                               'top': windowPosTop,
+                               'left': windowPosLeft
+                       });
+               }
+       },
+       notification: function(message){
+                       new MochaUI.Window({
+                               loadMethod: 'html',
+                               closeAfter: 1500,
+                               type: 'notification',
+                               addClass: 'notification',
+                               content: message,
+                               width: 220,
+                               height: 40,
+                               y: 53,
+                               padding:  { top: 10, right: 12, bottom: 10, left: 12 },
+                               shadowBlur: 5,
+                               bodyBgColor: [255, 255, 255]    
+                       });
+       },
+       /*
+
+       Function: dynamicResize
+               Use with a timer to resize a window as the window's content size changes, such as with an accordian.
+
+       */
+       dynamicResize: function(windowEl){
+               var currentInstance = MochaUI.Windows.instances.get(windowEl.id);
+               var contentWrapperEl = currentInstance.contentWrapperEl;
+               var contentEl = currentInstance.contentEl;
+               
+               contentWrapperEl.setStyle('height', contentEl.offsetHeight);
+               contentWrapperEl.setStyle('width', contentEl.offsetWidth);                      
+               currentInstance.drawWindow(windowEl);
+       },      
+       /*
+
+       Function: garbageCleanUp
+               Empties all windows of their children, and removes and garbages the windows. It is does not trigger onClose() or onCloseComplete(). This is useful to clear memory before the pageUnload.
+
+       Syntax:
+       (start code)
+               MochaUI.garbageCleanUp();
+       (end)
+       
+       */
+       garbageCleanUp: function(){
+               $$('div.mocha').each(function(el){
+                       el.destroy();
+               }.bind(this));
+       },
+       /*
+       
+       The underlay is inserted directly under windows when they are being dragged or resized
+       so that the cursor is not captured by iframes or other plugins (such as Flash)
+       underneath the window.
+       
+       */
+       underlayInitialize: function(){
+               var windowUnderlay = new Element('div', {
+                       'id': 'windowUnderlay',
+                       'styles': {
+                               'height': parent.getCoordinates().height,
+                               'opacity': .01,
+                               'display': 'none'
+                       }
+               }).inject(document.body);
+       },
+       setUnderlaySize: function(){
+               $('windowUnderlay').setStyle('height', parent.getCoordinates().height);
+       }
+});
+
+/* 
+
+function: fixPNG
+       Bob Osola's PngFix for IE6.
+
+example:
+       (begin code)
+       <img src="xyz.png" alt="foo" width="10" height="20" onload="fixPNG(this)">
+       (end)
+
+note:
+       You must have the image height and width attributes specified in the markup.
+
+*/
+
+function fixPNG(myImage){
+       if (Browser.Engine.trident4 && document.body.filters){
+               var imgID = (myImage.id) ? "id='" + myImage.id + "' " : "";
+               var imgClass = (myImage.className) ? "class='" + myImage.className + "' " : "";
+               var imgTitle = (myImage.title) ? "title='" + myImage.title  + "' " : "title='" + myImage.alt + "' ";
+               var imgStyle = "display:inline-block;" + myImage.style.cssText;
+               var strNewHTML = "<span " + imgID + imgClass + imgTitle
+                       + " style=\"" + "width:" + myImage.width
+                       + "px; height:" + myImage.height
+                       + "px;" + imgStyle + ";"
+                       + "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader"
+                       + "(src=\'" + myImage.src + "\', sizingMethod='scale');\"></span>";
+               myImage.outerHTML = strNewHTML;         
+       }
+}
+
+// Toggle window visibility with Ctrl-Alt-Q
+document.addEvent('keydown', function(event){
+       if (event.key == 'q' && event.control && event.alt) {
+               MochaUI.toggleWindowVisibility();
+       }
+});
+
+// Blur all windows if user clicks anywhere else on the page
+document.addEvent('mousedown', function(event){
+       MochaUI.blurAll.delay(50);
+});
+
+document.addEvent('domready', function(){
+       MochaUI.underlayInitialize();
+});
+
+window.addEvent('resize', function(){
+               MochaUI.setUnderlaySize();
+});
+/*
+
+Script: Window.js
+       Build windows.
+
+Copyright:
+       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.
+
+License:
+       MIT-style license.      
+
+Requires:
+       Core.js
+
+*/
+
+/*
+Class: Window
+       Creates a single MochaUI window.
+       
+Syntax:
+       (start code)
+       new MochaUI.Window(options);
+       (end)   
+
+Arguments:
+       options
+
+Options:
+       id - The ID of the window. If not defined, it will be set to 'win' + windowIDCount.
+       title - The title of the window.
+       icon - Place an icon in the window's titlebar. This is either set to false or to the url of the icon. It is set up for icons that are 16 x 16px.
+       type - ('window', 'modal', 'modal2', or 'notification') Defaults to 'window'.
+       loadMethod - ('html', 'xhr', or 'iframe') Defaults to 'html'.
+       contentURL - Used if loadMethod is set to 'xhr' or 'iframe'.
+       closeAfter - Either false or time in milliseconds. Closes the window after a certain period of time in milliseconds. This is particularly useful for notifications.
+       evalScripts - (boolean) An xhr loadMethod option. Defaults to true.
+       evalResponse - (boolean) An xhr loadMethod option. Defaults to false.
+       content - (string or element) An html loadMethod option.
+       toolbar - (boolean) Create window toolbar. Defaults to false. This can be used for tabs, media controls, and so forth.
+       toolbarPosition - ('top' or 'bottom') Defaults to top.
+       toolbarHeight - (number)
+       toolbarURL - (url) Defaults to 'pages/lipsum.html'.
+       toolbarContent - (string)
+       container - (element ID) Element the window is injected in. The container defaults to 'desktop'. If no desktop then to document.body. Use 'pageWrapper' if you don't want the windows to overlap the toolbars.
+       restrict - (boolean) Restrict window to container when dragging.
+       shape - ('box' or 'gauge') Shape of window. Defaults to 'box'.
+       collapsible - (boolean) Defaults to true.
+       minimizable - (boolean) Requires MochaUI.Desktop and MochaUI.Dock. Defaults to true if dependenices are met. 
+       maximizable - (boolean) Requires MochaUI.Desktop. Defaults to true if dependenices are met.
+       closable - (boolean) Defaults to true. 
+       draggable - (boolean) Defaults to false for modals; otherwise true.
+       draggableGrid - (false or number) Distance in pixels for snap-to-grid dragging. Defaults to false. 
+       draggableLimit - (false or number) An object with x and y properties used to limit the movement of the Window. Defaults to false.
+       draggableSnap - (boolean) The distance to drag before the Window starts to respond to the drag. Defaults to false.
+       resizable - (boolean) Defaults to false for modals, notifications and gauges; otherwise true.
+       resizeLimit - (object) Minimum and maximum width and height of window when resized.
+       addClass - (string) Add a class to the window for more control over styling.    
+       width - (number) Width of content area. 
+       height - (number) Height of content area.
+       x - (number) If x and y are left undefined the window is centered on the page.
+       y - (number)
+       scrollbars - (boolean)
+       padding - (object)
+       shadowBlur - (number) Width of shadows.
+       shadowOffset - Should be positive and not be greater than the ShadowBlur.
+       controlsOffset - Change this if you want to reposition the window controls.
+       useCanvas - (boolean) Set this to false if you don't want a canvas body.
+       useCanvasControls - (boolean) Set this to false if you wish to use images for the buttons.
+       headerHeight - (number) Height of window titlebar.
+       footerHeight - (number) Height of window footer.
+       cornerRadius - (number)
+       contentBgColor - (hex) Body background color
+       headerStartColor - ([r,g,b,]) Titlebar gradient's top color
+       headerStopColor - ([r,g,b,]) Titlebar gradient's bottom color
+       bodyBgColor - ([r,g,b,]) Background color of the main canvas shape
+       minimizeBgColor - ([r,g,b,]) Minimize button background color
+       minimizeColor - ([r,g,b,]) Minimize button color
+       maximizeBgColor - ([r,g,b,]) Maximize button background color
+       maximizeColor - ([r,g,b,]) Maximize button color
+       closeBgColor - ([r,g,b,]) Close button background color
+       closeColor - ([r,g,b,]) Close button color
+       resizableColor - ([r,g,b,]) Resizable icon color
+       onBeforeBuild - (function) Fired just before the window is built.
+       onContentLoaded - (function) Fired when content is successfully loaded via XHR or Iframe.
+       onFocus - (function)  Fired when the window is focused.
+       onBlur - (function) Fired when window loses focus.
+       onResize - (function) Fired when the window is resized.
+       onMinimize - (function) Fired when the window is minimized.
+       onMaximize - (function) Fired when the window is maximized.
+       onRestore - (function) Fired when a window is restored from minimized or maximized.
+       onClose - (function) Fired just before the window is closed.
+       onCloseComplete - (function) Fired after the window is closed.
+
+Returns:
+       Window object.
+
+Example:
+       Define a window. It is suggested you name the function the same as your window ID + "Window".
+       (start code)
+       var mywindowWindow = function(){
+               new MochaUI.Window({
+                       id: 'mywindow',
+                       title: 'My Window',
+                       loadMethod: 'xhr',
+                       contentURL: 'pages/lipsum.html',
+                       width: 340,
+                       height: 150
+               });
+       }
+       (end)
+
+Example:
+       Create window onDomReady.
+       (start code)    
+       window.addEvent('domready', function(){
+               mywindow();
+       });
+       (end)
+
+Example:
+       Add link events to build future windows. It is suggested you give your anchor the same ID as your window + "WindowLink" or + "WindowLinkCheck". Use the latter if it is a link in the menu toolbar.
+
+       If you wish to add links in windows that open other windows remember to add events to those links when the windows are created.
+
+       (start code)
+       // Javascript:
+       if ($('mywindowLink')){
+               $('mywindowLink').addEvent('click', function(e) {
+                       new Event(e).stop();
+                       mywindow();
+               });
+       }
+
+       // HTML:
+       <a id="mywindowLink" href="pages/lipsum.html">My Window</a>     
+       (end)
+
+
+       Loading Content with an XMLHttpRequest(xhr):
+               For content to load via xhr all the files must be online and in the same domain. If you need to load content from another domain or wish to have it work offline, load the content in an iframe instead of using the xhr option.
+       
+       Iframes:
+               If you use the iframe loadMethod your iframe will automatically be resized when the window it is in is resized. If you want this same functionality when using one of the other load options simply add class="mochaIframe" to those iframes and they will be resized for you as well.
+
+*/
+
+// Having these options outside of the Class allows us to add, change, and remove
+// individual options without rewriting all of them.
+
+MochaUI.Windows.windowOptions = {
+       id:                null,
+       title:             'New Window',
+       icon:              false,
+       type:              'window',
+
+       loadMethod:        'html',
+       contentURL:        'pages/lipsum.html',
+
+       closeAfter:        false,
+
+       // xhr options
+       evalScripts:       true,
+       evalResponse:      false,
+
+       // html options
+       content:           'Window content',
+
+       // Toolbar
+       toolbar:           false,
+       toolbarPosition:   'top',
+       toolbarHeight:     29,
+       toolbarURL:        'pages/lipsum.html',
+       toolbarContent:    '',
+  toolbarLoadMethod: 'xhr',
+  
+       // Toolbar
+       toolbar2:           false,
+       toolbar2Position:   'bottom',
+       toolbar2Height:     29,
+       toolbar2URL:        'pages/lipsum.html',
+       toolbar2Content:    '', 
+  toolbar2LoadMethod: 'xhr',
+  
+       // Container options
+       container:         null,
+       restrict:          true,
+       shape:             'box',
+
+       // Window Controls
+       collapsible:       true,
+       minimizable:       true,
+       maximizable:       true,
+       closable:          true,
+
+       // Draggable
+       draggable:         null,
+       draggableGrid:     false,
+       draggableLimit:    false,
+       draggableSnap:     false,
+
+       // Resizable
+       resizable:         null,
+       resizeLimit:       {'x': [250, 2500], 'y': [125, 2000]},
+       
+       // Style options:
+       addClass:          '',
+       width:             300,
+       height:            125,
+       x:                 null,
+       y:                 null,
+       scrollbars:        true,
+       padding:                   { top: 10, right: 12, bottom: 10, left: 12 },
+       shadowBlur:        5,
+       shadowOffset:      {'x': 0, 'y': 1},
+       controlsOffset:    {'right': 6, 'top': 6},
+       useCanvas:         true,
+       useCanvasControls: true,
+       useSpinner:        true,    // Toggles whether or not the ajax spinners are displayed in window footers.
+
+       // Color options:               
+       headerHeight:      25,
+       footerHeight:      25,
+       cornerRadius:      8,
+       contentBgColor:    '#fff',
+       headerStartColor:  [250, 250, 250],
+       headerStopColor:   [229, 229, 229],
+       bodyBgColor:       [229, 229, 229],
+       minimizeBgColor:   [255, 255, 255],
+       minimizeColor:     [0, 0, 0],
+       maximizeBgColor:   [255, 255, 255],
+       maximizeColor:     [0, 0, 0],
+       closeBgColor:      [255, 255, 255],
+       closeColor:        [0, 0, 0],
+       resizableColor:    [254, 254, 254],
+
+       // Events
+       onBeforeBuild:     $empty,
+       onContentLoaded:   $empty,
+       onFocus:           $empty,
+       onBlur:            $empty,
+       onResize:          $empty,
+       onMinimize:        $empty,
+       onMaximize:        $empty,
+       onRestore:         $empty,
+       onClose:           $empty,
+       onCloseComplete:   $empty
+};
+
+MochaUI.Window = new Class({
+       options: MochaUI.Windows.windowOptions,
+       initialize: function(options){
+               this.setOptions(options);
+
+               // Shorten object chain
+               var options = this.options;
+
+               $extend(this, {
+                       mochaControlsWidth: 0,
+                       minimizebuttonX:  0,  // Minimize button horizontal position
+                       maximizebuttonX: 0,  // Maximize button horizontal position
+                       closebuttonX: 0,  // Close button horizontal position
+                       headerFooterShadow: options.headerHeight + options.footerHeight + (options.shadowBlur * 2),
+                       oldTop: 0,
+                       oldLeft: 0,
+                       isMaximized: false,
+                       isMinimized: false,
+                       isCollapsed: false,
+                       timestamp: $time()
+               });
+               
+               // May be better to use if type != window
+               if (options.type != 'window'){
+                       options.container = document.body;
+                       options.minimizable = false;
+               }
+               if (!options.container){
+                       options.container = MochaUI.Desktop.desktop ? MochaUI.Desktop.desktop : document.body;
+               }
+
+               // Set this.options.resizable to default if it was not defined
+               if (options.resizable == null){
+                       if (options.type != 'window' || options.shape == 'gauge'){
+                               options.resizable = false;
+                       }
+                       else {
+                               options.resizable = true;       
+                       }
+               }
+
+               // Set this.options.draggable if it was not defined
+               if (options.draggable == null){
+                       if (options.type != 'window'){
+                               options.draggable = false;
+                       }
+                       else {
+                               options.draggable = true;
+                       }
+               }
+
+               // Gauges are not maximizable or resizable
+               if (options.shape == 'gauge' || options.type == 'notification'){
+                       options.collapsible = false;
+                       options.maximizable = false;
+                       options.contentBgColor = 'transparent';
+                       options.scrollbars = false;
+                       options.footerHeight = 0;
+               }
+               if (options.type == 'notification'){
+                       options.closable = false;
+                       options.headerHeight = 0;
+               }
+               
+               // Minimizable, dock is required and window cannot be modal
+               if (MochaUI.Dock && $(MochaUI.options.dock)){
+                       if (MochaUI.Dock.dock && options.type != 'modal' && options.type != 'modal2'){
+                               options.minimizable = options.minimizable;
+                       }
+               }
+               else {
+                       options.minimizable = false;
+               }
+
+               // Maximizable, desktop is required
+               options.maximizable = MochaUI.Desktop.desktop && options.maximizable && options.type != 'modal' && options.type != 'modal2';
+
+               if (this.options.type == 'modal2') {
+                       this.options.shadowBlur = 0;
+                       this.options.shadowOffset = {'x': 0, 'y': 0};
+                       this.options.useSpinner = false;
+                       this.options.useCanvas = false;
+                       this.options.footerHeight = 0;
+                       this.options.headerHeight = 0;
+               }
+
+               // If window has no ID, give it one.
+               if (options.id == null){
+                       options.id = 'win' + (++MochaUI.Windows.windowIDCount);
+               }
+               this.windowEl = $(options.id);
+               
+               this.newWindow();
+               
+               // Return window object
+               return this;
+       },
+       saveValues: function(){ 
+               var coordinates = this.windowEl.getCoordinates();
+               this.options.x = coordinates.left.toInt();
+               this.options.y = coordinates.top.toInt();
+       },
+       /*
+
+       Internal Function: newWindow
+       
+       Arguments: 
+               properties
+
+       */
+       newWindow: function(properties){ // options is not doing anything
+
+               // Shorten object chain
+               var instances = MochaUI.Windows.instances;
+               var instanceID = instances.get(this.options.id);
+       
+               // Here we check to see if there is already a class instance for this window
+               if (instanceID){
+                       var currentInstance = instanceID;
+               }
+
+               // Check if window already exists and is not in progress of closing
+               if ( this.windowEl && !this.isClosing ){
+                        // Restore if minimized
+                       if (currentInstance.isMinimized){
+                               MochaUI.Dock.restoreMinimized(this.windowEl);
+                       }
+                       // Expand and focus if collapsed
+                       if (currentInstance.isCollapsed){
+                               MochaUI.collapseToggle(this.windowEl);
+                               setTimeout(MochaUI.focusWindow.pass(this.windowEl, this),10);
+                       }
+                       // Else focus
+                       else {
+                               var coordinates = document.getCoordinates();
+                               if (this.windowEl.getStyle('left').toInt() > coordinates.width || this.windowEl.getStyle('top').toInt() > coordinates.height){
+                                       MochaUI.centerWindow(this.windowEl);    
+                               }
+                               setTimeout(MochaUI.focusWindow.pass(this.windowEl, this),10);
+                       }
+                       return;
+               }
+               else {
+                       instances.set(this.options.id, this);
+               }
+
+               this.isClosing = false;
+               this.fireEvent('onBeforeBuild');
+
+               // Create window div
+               MochaUI.Windows.indexLevel++;
+               this.windowEl = new Element('div', {
+                       'class': 'mocha',
+                       'id':    this.options.id,
+                       'styles': {
+                               'width':   this.options.width,
+                               'height':  this.options.height,
+                               'display': 'block',
+                               'opacity': 0,
+                               'zIndex': MochaUI.Windows.indexLevel += 2
+                       }
+               });
+
+               this.windowEl.addClass(this.options.addClass);
+               
+               if (this.options.type == 'modal2') {
+                       this.windowEl.addClass('modal2');
+               }
+
+               // Fix a mouseover issue with gauges in IE7
+               if ( Browser.Engine.trident && this.options.shape == 'gauge') {
+                       this.windowEl.setStyle('background', 'url(../images/spacer.gif)');
+               }
+
+               if ((this.options.type == 'modal' || this.options.type == 'modal2' ) && Browser.Platform.mac && Browser.Engine.gecko){
+                       if (/Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent)) {
+                               var ffversion = new Number(RegExp.$1);
+                               if (ffversion < 3) {
+                                       this.windowEl.setStyle('position', 'fixed');
+                               }
+                       }
+               }
+
+               if (this.options.loadMethod == 'iframe') {
+                       this.options.padding = { top: 0, right: 0, bottom: 0, left: 0 };
+               }
+
+               // Insert sub elements inside windowEl
+               this.insertWindowElements();
+
+               // Set title
+               this.titleEl.set('html',this.options.title);
+
+               // Set scrollbars, always use 'hidden' for iframe windows
+               this.contentWrapperEl.setStyles({
+                       'overflow': 'hidden',
+                       'background': this.options.contentBgColor
+               });
+
+               this.contentEl.setStyles({
+                       'padding-top': this.options.padding.top,
+                       'padding-bottom': this.options.padding.bottom,
+                       'padding-left': this.options.padding.left,
+                       'padding-right': this.options.padding.right
+               });
+
+
+               if (this.options.shape == 'gauge'){
+                       if (this.options.useCanvasControls){
+                               this.canvasControlsEl.setStyle('display', 'none');
+                       }
+                       else {
+                               this.controlsEl.setStyle('display', 'none');
+                       }
+                       this.windowEl.addEvent('mouseover', function(){
+                               this.mouseover = true;
+                               var showControls = function(){
+                                       if (this.mouseover != false){
+                                               if (this.options.useCanvasControls){
+                                                       this.canvasControlsEl.setStyle('display', 'block');
+                                               }
+                                               else {
+                                                       this.controlsEl.setStyle('display', 'block');
+                                               }
+                                               this.canvasHeaderEl.setStyle('display', 'block');
+                                               this.titleEl.setStyle('display', 'block');
+                                       }
+                               };
+                               showControls.delay(150, this);
+
+                       }.bind(this));
+                       this.windowEl.addEvent('mouseleave', function(){
+                               this.mouseover = false;
+                               if (this.options.useCanvasControls){
+                                       this.canvasControlsEl.setStyle('display', 'none');
+                               }
+                               else {
+                                       this.controlsEl.setStyle('display', 'none');
+                               }
+                               this.canvasHeaderEl.setStyle('display', 'none');
+                               this.titleEl.setStyle('display', 'none');
+                       }.bind(this));
+               }
+
+               // Inject window into DOM
+               this.windowEl.injectInside(this.options.container);
+
+               if (this.options.type != 'notification'){
+                       this.setMochaControlsWidth();
+               }               
+
+               // Add content to window.
+               MochaUI.updateContent({
+                       'element': this.windowEl,
+                       'content':  this.options.content,
+                       'url':      this.options.contentURL
+               });     
+               
+               // Add content to window toolbar.
+               if (this.options.toolbar == true){
+                       MochaUI.updateContent({
+                               'element':       this.windowEl,
+                               'childElement':  this.toolbarEl,
+                               'content':       this.options.toolbarContent,
+                               'loadMethod':    this.options.toolbarLoadMethod,
+                               'url':           this.options.toolbarURL
+                       });
+               }
+
+               // Add content to window toolbar.
+               if (this.options.toolbar2 == true){
+                       MochaUI.updateContent({
+                               'element':       this.windowEl,
+                               'childElement':  this.toolbar2El,
+                               'content':       this.options.toolbar2Content,
+                               'loadMethod':    this.options.toolbar2LoadMethod,
+                               'url':           this.options.toolbar2URL
+                       });
+               }
+               
+               this.drawWindow(this.windowEl);
+               
+               // Attach events to the window
+               this.attachDraggable(this.windowEl);
+               this.attachResizable(this.windowEl);
+               this.setupEvents(this.windowEl);
+               
+               if (this.options.resizable){
+                       this.adjustHandles();
+               }
+
+               // Move window into position. If position not specified by user then center the window on the page.
+               if (this.options.container == document.body || this.options.container == MochaUI.Desktop.desktop){
+                       var dimensions = window.getSize();
+               }
+               else {
+                       var dimensions = $(this.options.container).getSize();
+               }
+
+               if (!this.options.y) {
+                       var y = (dimensions.y * .5) - ((this.options.height + this.headerFooterShadow + this.windowEl.getStyle('border-top').toInt() + this.windowEl.getStyle('border-bottom').toInt()) * .5);
+               }
+               else {
+                       var y = this.options.y - this.options.shadowBlur;
+               }
+
+               if (!this.options.x) {
+                       var x = (dimensions.x * .5) - (this.options.width * .5);
+               }
+               else {
+                       var x = this.options.x - this.options.shadowBlur;
+               }
+
+               this.windowEl.setStyles({
+                       'top': y,
+                       'left': x
+               });
+
+               // Create opacityMorph
+               if (MochaUI.options.useEffects == true){
+                       // IE cannot handle both element opacity and VML alpha at the same time.
+                       if (Browser.Engine.trident){
+                               this.drawWindow(this.windowEl, false);
+                       }
+                       this.opacityMorph = new Fx.Morph(this.windowEl, {
+                               'duration': 350,
+                               onComplete: function(){
+                                       if (Browser.Engine.trident){
+                                               this.drawWindow(this.windowEl);
+                                       }
+                               }.bind(this)
+                       });
+               }
+
+               if (this.options.type == 'modal' || this.options.type == 'modal2') {
+                       MochaUI.currentModal = this.windowEl;
+                       if (Browser.Engine.trident4){
+                               $('modalFix').setStyle('display', 'block');
+                       }
+                       $('modalOverlay').setStyle('display', 'block');
+                       if (MochaUI.options.useEffects == false){
+                               $('modalOverlay').setStyle('opacity', .6);
+                               this.windowEl.setStyles({
+                                       'zIndex': 11000,
+                                       'opacity': 1
+                               });
+                       }
+                       else {
+                               MochaUI.Modal.modalOverlayCloseMorph.cancel();
+                               MochaUI.Modal.modalOverlayOpenMorph.start({
+                                       'opacity': .6
+                               });
+                               this.windowEl.setStyles({
+                                       'zIndex': 11000
+                               });
+                               this.opacityMorph.start({
+                                       'opacity': 1
+                               });
+                       }
+
+                       $$('.dockTab').removeClass('activeDockTab');
+                       $$('.mocha').removeClass('isFocused');
+                       this.windowEl.addClass('isFocused');
+                       
+               }
+               else if (MochaUI.options.useEffects == false){
+                       this.windowEl.setStyle('opacity', 1);
+                       setTimeout(MochaUI.focusWindow.pass(this.windowEl, this), 10);
+               }
+               else {
+                       this.opacityMorph.start({
+                               'opacity': 1
+                       });
+                       setTimeout(MochaUI.focusWindow.pass(this.windowEl, this), 10);
+               }
+
+               // This is a generic morph that can be reused later by functions like centerWindow()
+               this.morph = new Fx.Morph(this.windowEl, {
+                       'duration': 200
+               });
+
+               // Add check mark to menu if link exists in menu
+               // Need to make sure the check mark is not added to links not in menu
+       
+               if ($(this.windowEl.id + 'LinkCheck')){
+                       this.check = new Element('div', {
+                               'class': 'check',
+                               'id': this.options.id + '_check'
+                       }).inject(this.windowEl.id + 'LinkCheck');
+               }
+               
+               if (this.options.closeAfter != false){
+                       MochaUI.closeWindow.delay(this.options.closeAfter, this, this.windowEl);
+               }
+
+               if (MochaUI.Dock && $(MochaUI.options.dock) && this.options.type == 'window' ){
+                       MochaUI.Dock.createDockTab(this.windowEl);
+               }
+
+       },
+       setupEvents: function(windowEl) {
+
+               // Set events
+               // Note: if a button does not exist, its due to properties passed to newWindow() stating otherwice
+               if (this.closeButtonEl){
+                       this.closeButtonEl.addEvent('click', function(e) {
+                               new Event(e).stop();
+                               MochaUI.closeWindow(windowEl);
+                       }.bind(this));
+               }
+
+               if (this.options.type == 'window'){
+                       windowEl.addEvent('mousedown', function() {
+                               MochaUI.focusWindow(windowEl);
+                       }.bind(this));
+               }
+
+               if (this.minimizeButtonEl) {
+                       this.minimizeButtonEl.addEvent('click', function(e) {
+                               new Event(e).stop();
+                               MochaUI.Dock.minimizeWindow(windowEl);
+               }.bind(this));
+               }
+
+               if (this.maximizeButtonEl) {
+                       this.maximizeButtonEl.addEvent('click', function(e) {
+                               new Event(e).stop(); 
+                               if (this.isMaximized) {
+                                       MochaUI.Desktop.restoreWindow(windowEl);
+                               } else {
+                                       MochaUI.Desktop.maximizeWindow(windowEl);
+                               }
+                       }.bind(this));
+               }
+
+               if (this.options.collapsible == true){
+                       // Keep titlebar text from being selected on double click in Safari.
+                       this.titleEl.addEvent('selectstart', function(e) {
+                               e = new Event(e).stop();
+                       }.bind(this));
+                       // Keep titlebar text from being selected on double click in Opera.
+                       this.titleBarEl.addEvent('mousedown', function(e) {
+                               if (Browser.Engine.trident) {
+                                       this.titleEl.setCapture();
+                               }
+                       }.bind(this));
+                       this.titleBarEl.addEvent('mouseup', function(e) {
+                               if (Browser.Engine.trident) {
+                                       this.titleEl.releaseCapture();
+                               }
+                       }.bind(this));
+                       this.titleBarEl.addEvent('dblclick', function(e) {
+                               e = new Event(e).stop();
+                               MochaUI.collapseToggle(this.windowEl);
+                       }.bind(this));
+               }
+
+       },
+       /*
+
+       Internal Function: attachDraggable()
+               Make window draggable.
+
+       Arguments:
+               windowEl
+               
+       */
+       attachDraggable: function(windowEl){
+               if (!this.options.draggable) return;
+               this.windowDrag = new Drag.Move(windowEl, {
+                       handle: this.titleBarEl,
+                       container: this.options.restrict == true ? $(this.options.container) : false,
+                       grid: this.options.draggableGrid,
+                       limit: this.options.draggableLimit,
+                       snap: this.options.draggableSnap,
+                       onStart: function() {
+                               if (this.options.type != 'modal' && this.options.type != 'modal2'){ 
+                                       MochaUI.focusWindow(windowEl);
+                                       $('windowUnderlay').setStyle('display','block');
+                               }
+                               if ( this.iframeEl )
+                                       this.iframeEl.setStyle('visibility', 'hidden');
+                       }.bind(this),
+                       onComplete: function() {
+                               if (this.options.type != 'modal' && this.options.type != 'modal2') {
+                                       $('windowUnderlay').setStyle('display', 'none');
+                               }
+                               if ( this.iframeEl ){
+                                       this.iframeEl.setStyle('visibility', 'visible');
+                               }
+                               // Store new position in options.
+                               this.saveValues();
+                       }.bind(this)
+               });
+       },
+       /*
+
+       Internal Function: attachResizable
+               Make window resizable.
+
+       Arguments:
+               windowEl
+
+       */
+       attachResizable: function(windowEl){
+               if (!this.options.resizable) return;
+               this.resizable1 = this.windowEl.makeResizable({
+                       handle: [this.n, this.ne, this.nw],
+                       limit: {
+                               y: [
+                                       function(){
+                                               return this.windowEl.getStyle('top').toInt() + this.windowEl.getStyle('height').toInt() - this.options.resizeLimit.y[1];
+                                       }.bind(this),
+                                       function(){
+                                               return this.windowEl.getStyle('top').toInt() + this.windowEl.getStyle('height').toInt() - this.options.resizeLimit.y[0];
+                                       }.bind(this)
+                               ]
+                       },
+                       modifiers: {x: false, y: 'top'},
+                       onStart: function(){
+                               this.resizeOnStart();
+                               this.coords = this.contentWrapperEl.getCoordinates();
+                               this.y2 = this.coords.top.toInt() + this.contentWrapperEl.offsetHeight;
+                       }.bind(this),
+                       onDrag: function(){
+                               this.coords = this.contentWrapperEl.getCoordinates();
+                               this.contentWrapperEl.setStyle('height', this.y2 - this.coords.top.toInt());
+                               this.drawWindow(windowEl);
+                               this.adjustHandles();
+                       }.bind(this),
+                       onComplete: function(){
+                               this.resizeOnComplete();
+                       }.bind(this)
+               });
+
+               this.resizable2 = this.contentWrapperEl.makeResizable({
+                       handle: [this.e, this.ne],
+                       limit: {
+                               x: [this.options.resizeLimit.x[0] - (this.options.shadowBlur * 2), this.options.resizeLimit.x[1] - (this.options.shadowBlur * 2) ]
+                       },      
+                       modifiers: {x: 'width', y: false},
+                       onStart: function(){
+                               this.resizeOnStart();
+                       }.bind(this),
+                       onDrag: function(){
+                               this.drawWindow(windowEl);
+                               this.adjustHandles();
+                       }.bind(this),
+                       onComplete: function(){
+                               this.resizeOnComplete();
+                       }.bind(this)
+               });
+
+               this.resizable3 = this.contentWrapperEl.makeResizable({
+                       container: this.options.restrict == true ? $(this.options.container) : false,
+                       handle: this.se,
+                       limit: {
+                               x: [this.options.resizeLimit.x[0] - (this.options.shadowBlur * 2), this.options.resizeLimit.x[1] - (this.options.shadowBlur * 2) ],
+                               y: [this.options.resizeLimit.y[0] - this.headerFooterShadow, this.options.resizeLimit.y[1] - this.headerFooterShadow]
+                       },
+                       modifiers: {x: 'width', y: 'height'},
+                       onStart: function(){
+                               this.resizeOnStart();
+                       }.bind(this),
+                       onDrag: function(){
+                               this.drawWindow(windowEl);      
+                               this.adjustHandles();
+                       }.bind(this),
+                       onComplete: function(){
+                               this.resizeOnComplete();
+                       }.bind(this)    
+               });
+
+               this.resizable4 = this.contentWrapperEl.makeResizable({
+                       handle: [this.s, this.sw],
+                       limit: {
+                               y: [this.options.resizeLimit.y[0] - this.headerFooterShadow, this.options.resizeLimit.y[1] - this.headerFooterShadow]
+                       },
+                       modifiers: {x: false, y: 'height'},
+                       onStart: function(){
+                               this.resizeOnStart();
+                       }.bind(this),
+                       onDrag: function(){
+                               this.drawWindow(windowEl);
+                               this.adjustHandles();
+                       }.bind(this),
+                       onComplete: function(){
+                               this.resizeOnComplete();
+                       }.bind(this)
+               });
+
+               this.resizable5 = this.windowEl.makeResizable({
+                       handle: [this.w, this.sw, this.nw],
+                       limit: {
+                               x: [
+                                       function(){
+                                               return this.windowEl.getStyle('left').toInt() + this.windowEl.getStyle('width').toInt() - this.options.resizeLimit.x[1];
+                                       }.bind(this),
+                                  function(){
+                                          return this.windowEl.getStyle('left').toInt() + this.windowEl.getStyle('width').toInt() - this.options.resizeLimit.x[0];
+                                       }.bind(this)
+                               ]
+                       },
+                       modifiers: {x: 'left', y: false},
+                       onStart: function(){
+                               this.resizeOnStart();
+                               this.coords = this.contentWrapperEl.getCoordinates();
+                               this.x2 = this.coords.left.toInt() + this.contentWrapperEl.offsetWidth;
+                       }.bind(this),
+                       onDrag: function(){
+                               this.coords = this.contentWrapperEl.getCoordinates();
+                               this.contentWrapperEl.setStyle('width', this.x2 - this.coords.left.toInt());
+                               this.drawWindow(windowEl);
+                               this.adjustHandles();
+                       }.bind(this),
+                       onComplete: function(){
+                               this.resizeOnComplete();
+                       }.bind(this)
+               });
+
+       },
+       resizeOnStart: function(){
+               $('windowUnderlay').setStyle('display','block');
+               if (this.iframeEl){
+                       this.iframeEl.setStyle('visibility', 'hidden');
+               }       
+       },      
+       resizeOnComplete: function(){
+               $('windowUnderlay').setStyle('display','none');
+               if (this.iframeEl){
+                       this.iframeEl.setStyle('visibility', 'visible');
+               }
+               this.fireEvent('onResize', this.windowEl);
+       },
+       adjustHandles: function(){
+
+               var shadowBlur = this.options.shadowBlur;
+               var shadowBlur2x = shadowBlur * 2;
+               var shadowOffset = this.options.shadowOffset;
+               var top = shadowBlur - shadowOffset.y - 1;
+               var right = shadowBlur + shadowOffset.x - 1;
+               var bottom = shadowBlur + shadowOffset.y - 1;
+               var left = shadowBlur - shadowOffset.x - 1;
+               
+               var coordinates = this.windowEl.getCoordinates();
+               var width = coordinates.width - shadowBlur2x + 2;
+               var height = coordinates.height - shadowBlur2x + 2;
+
+               this.n.setStyles({
+                       'top': top,
+                       'left': left + 10,
+                       'width': width - 20
+               });
+               this.e.setStyles({
+                       'top': top + 10,
+                       'right': right,
+                       'height': height - 30
+               });
+               this.s.setStyles({
+                       'bottom': bottom,
+                       'left': left + 10,
+                       'width': width - 30
+               });
+               this.w.setStyles({
+                       'top': top + 10,
+                       'left': left,
+                       'height': height - 20
+               });
+               this.ne.setStyles({
+                       'top': top,
+                       'right': right  
+               });
+               this.se.setStyles({
+                       'bottom': bottom,
+                       'right': right
+               });
+               this.sw.setStyles({
+                       'bottom': bottom,
+                       'left': left
+               });
+               this.nw.setStyles({
+                       'top': top,
+                       'left': left
+               });
+       },
+       detachResizable: function(){
+                       this.resizable1.detach();
+                       this.resizable2.detach();
+                       this.resizable3.detach();
+                       this.resizable4.detach();
+                       this.resizable5.detach();
+                       this.windowEl.getElements('.handle').setStyle('display', 'none');
+       },
+       reattachResizable: function(){
+                       this.resizable1.attach();
+                       this.resizable2.attach();
+                       this.resizable3.attach();
+                       this.resizable4.attach();
+                       this.resizable5.attach();
+                       this.windowEl.getElements('.handle').setStyle('display', 'block');
+       },
+       /*
+
+       Internal Function: insertWindowElements
+
+       Arguments:
+               windowEl
+
+       */
+       insertWindowElements: function(){
+               
+               var options = this.options;
+               var height = options.height;
+               var width = options.width;
+               var id = options.id;
+
+               var cache = {};
+
+               if (Browser.Engine.trident4){
+                       cache.zIndexFixEl = new Element('iframe', {
+                               'id': id + '_zIndexFix',
+                               'class': 'zIndexFix',
+                               'scrolling': 'no',
+                               'marginWidth': 0,
+                               'marginHeight': 0,
+                               'src': ''
+                       }).inject(this.windowEl);
+               }
+
+               cache.overlayEl = new Element('div', {
+                       'id': id + '_overlay',
+                       'class': 'mochaOverlay'
+               }).inject(this.windowEl);
+
+               cache.titleBarEl = new Element('div', {
+                       'id': id + '_titleBar',
+                       'class': 'mochaTitlebar',
+                       'styles': {
+                               'cursor': options.draggable ? 'move' : 'default'
+                       }
+               }).inject(cache.overlayEl, 'top');
+
+               cache.titleEl = new Element('h3', {
+                       'id': id + '_title',
+                       'class': 'mochaTitle'
+               }).inject(cache.titleBarEl);
+
+               if (options.icon != false){
+                       cache.titleBarEl.setStyles({
+                               'padding-left': 15,
+                               'background': 'url(' + options.icon + ') 5px 5px no-repeat'
+                       });
+               }
+               
+               cache.contentBorderEl = new Element('div', {
+                       'id': id + '_contentBorder',
+                       'class': 'mochaContentBorder'
+               }).inject(cache.overlayEl);
+
+               if (options.toolbar){
+                       cache.toolbarWrapperEl = new Element('div', {
+                               'id': id + '_toolbarWrapper',
+                               'class': 'mochaToolbarWrapper'
+                       }).inject(cache.contentBorderEl, options.toolbarPosition == 'bottom' ? 'after' : 'before');
+
+                       if (options.toolbarPosition == 'bottom') {
+                               cache.toolbarWrapperEl.addClass('bottom');
+                       }
+                       cache.toolbarEl = new Element('div', {
+                               'id': id + '_toolbar',
+                               'class': 'mochaToolbar'
+                       }).inject(cache.toolbarWrapperEl);
+               }
+
+               if (options.toolbar2){
+                       cache.toolbar2WrapperEl = new Element('div', {
+                               'id': id + '_toolbar2Wrapper',
+                               'class': 'mochaToolbarWrapper'
+                       }).inject(cache.contentBorderEl, options.toolbar2Position == 'bottom' ? 'after' : 'before');
+
+                       if (options.toolbar2Position == 'bottom') {
+                               cache.toolbar2WrapperEl.addClass('bottom');
+                       }
+                       cache.toolbar2El = new Element('div', {
+                               'id': id + '_toolbar2',
+                               'class': 'mochaToolbar'
+                       }).inject(cache.toolbar2WrapperEl);
+               }
+
+               cache.contentWrapperEl = new Element('div', {
+                       'id': id + '_contentWrapper',
+                       'class': 'mochaContentWrapper',
+                       'styles': {
+                               'width': width + 'px',
+                               'height': height + 'px'
+                       }
+               }).inject(cache.contentBorderEl);
+               
+               if (this.options.shape == 'gauge'){
+                       cache.contentBorderEl.setStyle('borderWidth', 0);
+               }
+
+               cache.contentEl = new Element('div', {
+                       'id': id + '_content',
+                       'class': 'mochaContent'
+               }).inject(cache.contentWrapperEl);
+
+               if (this.options.useCanvas == true) {
+                       cache.canvasEl = new Element('canvas', {
+                               'id': id + '_canvas',
+                               'class': 'mochaCanvas',
+                               'width': 1,
+                               'height': 1
+                       }).inject(this.windowEl);
+
+                       if (Browser.Engine.trident && MochaUI.ieSupport == 'excanvas'){
+                               G_vmlCanvasManager.initElement(cache.canvasEl);
+                               cache.canvasEl = this.windowEl.getElement('.mochaCanvas');
+                       }
+               }
+
+               cache.controlsEl = new Element('div', {
+                       'id': id + '_controls',
+                       'class': 'mochaControls'
+               }).inject(cache.overlayEl, 'after');
+
+               if (options.useCanvasControls == true){
+                       cache.canvasControlsEl = new Element('canvas', {
+                               'id': id + '_canvasControls',
+                               'class': 'mochaCanvasControls',
+                               'width': 14,
+                               'height': 14
+                       }).inject(this.windowEl);
+
+                       if (Browser.Engine.trident && MochaUI.ieSupport == 'excanvas'){
+                               G_vmlCanvasManager.initElement(cache.canvasControlsEl);
+                               cache.canvasControlsEl = this.windowEl.getElement('.mochaCanvasControls');
+                       }
+               }
+
+               if (options.closable){
+                       cache.closeButtonEl = new Element('div', {
+                               'id': id + '_closeButton',
+                               'class': 'mochaCloseButton',
+                               'title': 'Close'
+                       }).inject(cache.controlsEl);
+                       if (options.useCanvasControls == true){
+                               cache.closeButtonEl.setStyle('background', 'none');
+                       }
+               }
+
+               if (options.maximizable){
+                       cache.maximizeButtonEl = new Element('div', {
+                               'id': id + '_maximizeButton',
+                               'class': 'mochaMaximizeButton',
+                               'title': 'Maximize'
+                       }).inject(cache.controlsEl);
+                       if (options.useCanvasControls == true){
+                               cache.maximizeButtonEl.setStyle('background', 'none');
+                       }
+               }
+
+               if (options.minimizable){
+                       cache.minimizeButtonEl = new Element('div', {
+                               'id': id + '_minimizeButton',
+                               'class': 'mochaMinimizeButton',
+                               'title': 'Minimize'
+                       }).inject(cache.controlsEl);
+                       if (options.useCanvasControls == true){
+                               cache.minimizeButtonEl.setStyle('background', 'none');
+                       }
+               }
+
+               if (options.useSpinner == true && options.shape != 'gauge' && options.type != 'notification'){
+                       cache.spinnerEl = new Element('div', {
+                               'id': id + '_spinner',
+                               'class': 'mochaSpinner',
+                               'width': 16,
+                               'height': 16
+                       }).inject(this.windowEl, 'bottom');
+               }
+
+               if (this.options.shape == 'gauge'){
+                       cache.canvasHeaderEl = new Element('canvas', {
+                               'id': id + '_canvasHeader',
+                               'class': 'mochaCanvasHeader',
+                               'width': this.options.width,
+                               'height': 26
+                       }).inject(this.windowEl, 'bottom');
+               
+                       if (Browser.Engine.trident && MochaUI.ieSupport == 'excanvas'){
+                               G_vmlCanvasManager.initElement(cache.canvasHeaderEl);
+                               cache.canvasHeaderEl = this.windowEl.getElement('.mochaCanvasHeader');
+                       }
+               }
+
+               if ( Browser.Engine.trident ){
+                       cache.overlayEl.setStyle('zIndex', 2);
+               }
+
+               // For Mac Firefox 2 to help reduce scrollbar bugs in that browser
+               if (Browser.Platform.mac && Browser.Engine.gecko){
+                       if (/Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent)){
+                               var ffversion = new Number(RegExp.$1);
+                               if (ffversion < 3){
+                                       cache.overlayEl.setStyle('overflow', 'auto');
+                               }
+                       }
+               }
+
+               if (options.resizable){
+                       cache.n = new Element('div', {
+                               'id': id + '_resizeHandle_n',
+                               'class': 'handle',
+                               'styles': {
+                                       'top': 0,
+                                       'left': 10,
+                                       'cursor': 'n-resize'
+                               }
+                       }).inject(cache.overlayEl, 'after');
+
+                       cache.ne = new Element('div', {
+                               'id': id + '_resizeHandle_ne',
+                               'class': 'handle corner',
+                               'styles': {
+                                       'top': 0,
+                                       'right': 0,
+                                       'cursor': 'ne-resize'
+                               }
+                       }).inject(cache.overlayEl, 'after');
+                       
+                       cache.e = new Element('div', {
+                               'id': id + '_resizeHandle_e',
+                               'class': 'handle',              
+                               'styles': {
+                                       'top': 10,
+                                       'right': 0,
+                                       'cursor': 'e-resize'
+                               }
+                       }).inject(cache.overlayEl, 'after');
+                       
+                       cache.se = new Element('div', {
+                               'id': id + '_resizeHandle_se',
+                               'class': 'handle cornerSE',
+                               'styles': {
+                                       'bottom': 0,
+                                       'right': 0,
+                                       'cursor': 'se-resize'
+                               }
+                       }).inject(cache.overlayEl, 'after');
+
+                       cache.s = new Element('div', {
+                               'id': id + '_resizeHandle_s',
+                               'class': 'handle',
+                               'styles': {
+                                       'bottom': 0,
+                                       'left': 10,
+                                       'cursor': 's-resize'
+                               }
+                       }).inject(cache.overlayEl, 'after');
+                       
+                       cache.sw = new Element('div', {
+                               'id': id + '_resizeHandle_sw',
+                               'class': 'handle corner',
+                               'styles': {
+                                       'bottom': 0,
+                                       'left': 0,
+                                       'cursor': 'sw-resize'
+                               }
+                       }).inject(cache.overlayEl, 'after');
+                       
+                       cache.w = new Element('div', {
+                               'id': id + '_resizeHandle_w',
+                               'class': 'handle',              
+                               'styles': {
+                                       'top': 10,
+                                       'left': 0,
+                                       'cursor': 'w-resize'
+                               }
+                       }).inject(cache.overlayEl, 'after');
+                       
+                       cache.nw = new Element('div', {
+                               'id': id + '_resizeHandle_nw',
+                               'class': 'handle corner',               
+                               'styles': {
+                                       'top': 0,
+                                       'left': 0,
+                                       'cursor': 'nw-resize'
+                               }
+                       }).inject(cache.overlayEl, 'after');
+               }
+               $extend(this, cache);
+               
+       },
+       /*
+
+       Internal function: drawWindow
+               This is where we create the canvas GUI  
+
+       Arguments: 
+               windowEl: the $(window)
+               shadows: (boolean) false will draw a window without shadows
+
+       */      
+       drawWindow: function(windowEl, shadows) {
+                               
+               if (this.isCollapsed){
+                       this.drawWindowCollapsed(windowEl, shadows);
+                       return;
+               }
+
+               var options = this.options;
+               var shadowBlur = options.shadowBlur;
+               var shadowBlur2x = shadowBlur * 2;
+               var shadowOffset = this.options.shadowOffset;
+
+               this.overlayEl.setStyles({
+                       'width': this.contentWrapperEl.offsetWidth
+               });
+
+               // Resize iframe when window is resized
+               if (this.iframeEl) {
+                       this.iframeEl.setStyles({
+                               'height': this.contentWrapperEl.offsetHeight
+                       });
+               }
+
+               var borderHeight = this.contentBorderEl.getStyle('border-top').toInt() + this.contentBorderEl.getStyle('border-bottom').toInt();
+               var toolbarHeight = this.toolbarWrapperEl ? this.toolbarWrapperEl.getStyle('height').toInt() + this.toolbarWrapperEl.getStyle('border-top').toInt() : 0;
+               var toolbar2Height = this.toolbar2WrapperEl ? this.toolbar2WrapperEl.getStyle('height').toInt() + this.toolbar2WrapperEl.getStyle('border-top').toInt() : 0;
+
+               this.headerFooterShadow = options.headerHeight + options.footerHeight + shadowBlur2x;
+               var height = this.contentWrapperEl.getStyle('height').toInt() + this.headerFooterShadow + toolbarHeight + toolbar2Height + borderHeight;
+               var width = this.contentWrapperEl.getStyle('width').toInt() + shadowBlur2x;
+               this.windowEl.setStyles({
+                       'height': height,
+                       'width': width
+               });
+
+               this.overlayEl.setStyles({
+                       'height': height,
+                       'top': shadowBlur - shadowOffset.y,
+                       'left': shadowBlur - shadowOffset.x
+               });             
+
+               // Opera requires the canvas height and width be set this way when resizing:
+               if (this.options.useCanvas == true) {
+                       this.canvasEl.height = height;
+                       this.canvasEl.width = width;
+               }
+
+               // Part of the fix for IE6 select z-index bug
+               if (Browser.Engine.trident4){
+                       this.zIndexFixEl.setStyles({
+                               'width': width,
+                               'height': height
+                       })
+               }
+
+               this.titleBarEl.setStyles({
+                       'width': width - shadowBlur2x,
+                       'height': options.headerHeight
+               });
+
+               // Make sure loading icon is placed correctly.
+               if (options.useSpinner == true && options.shape != 'gauge' && options.type != 'notification'){
+                       this.spinnerEl.setStyles({
+                               'left': shadowBlur - shadowOffset.x + 3,
+                               'bottom': shadowBlur + shadowOffset.y +  4
+                       });
+               }
+               
+               if (this.options.useCanvas != false) {
+               
+                       // Draw Window
+                       var ctx = this.canvasEl.getContext('2d');
+                       ctx.clearRect(0, 0, width, height);
+                       
+                       switch (options.shape) {
+                               case 'box':
+                                       this.drawBox(ctx, width, height, shadowBlur, shadowOffset, shadows);
+                                       break;
+                               case 'gauge':
+                                       this.drawGauge(ctx, width, height, shadowBlur, shadowOffset, shadows);
+                                       break;
+                       }
+
+
+                       if (options.resizable){ 
+                               MochaUI.triangle(
+                                       ctx,
+                                       width - (shadowBlur + shadowOffset.x + 17),
+                                       height - (shadowBlur + shadowOffset.y + 18),
+                                       11,
+                                       11,
+                                       options.resizableColor,
+                                       1.0
+                               );
+                       }
+
+                       // Invisible dummy object. The last element drawn is not rendered consistently while resizing in IE6 and IE7
+                       if (Browser.Engine.trident){
+                               MochaUI.triangle(ctx, 0, 0, 10, 10, options.resizableColor, 0);
+                       }
+               }
+               
+               if (options.type != 'notification' && options.useCanvasControls == true){
+                       this.drawControls(width, height, shadows);
+               }
+
+       },
+       drawWindowCollapsed: function(windowEl, shadows) {
+               
+               var options = this.options;
+               var shadowBlur = options.shadowBlur;
+               var shadowBlur2x = shadowBlur * 2;
+               var shadowOffset = options.shadowOffset;
+               
+               var headerShadow = options.headerHeight + shadowBlur2x + 2;
+               var height = headerShadow;
+               var width = this.contentWrapperEl.getStyle('width').toInt() + shadowBlur2x;
+               this.windowEl.setStyle('height', height);
+               
+               this.overlayEl.setStyles({
+                       'height': height,
+                       'top': shadowBlur - shadowOffset.y,
+                       'left': shadowBlur - shadowOffset.x
+               });             
+
+               // Opera height and width must be set like this, when resizing:
+               this.canvasEl.height = height;
+               this.canvasEl.width = width;
+
+               // Part of the fix for IE6 select z-index bug
+               if (Browser.Engine.trident4){
+                       this.zIndexFixEl.setStyles({
+                               'width': width,
+                               'height': height
+                       });
+               }
+
+               // Set width
+               this.windowEl.setStyle('width', width);
+               this.overlayEl.setStyle('width', width);
+               this.titleBarEl.setStyles({
+                       'width': width - shadowBlur2x,
+                       'height': options.headerHeight
+               });
+       
+               // Draw Window
+               if (this.options.useCanvas != false) {
+                       var ctx = this.canvasEl.getContext('2d');
+                       ctx.clearRect(0, 0, width, height);
+                       
+                       this.drawBoxCollapsed(ctx, width, height, shadowBlur, shadowOffset, shadows);
+                       if (options.useCanvasControls == true) {
+                               this.drawControls(width, height, shadows);
+                       }
+                       
+                       // Invisible dummy object. The last element drawn is not rendered consistently while resizing in IE6 and IE7
+                       if (Browser.Engine.trident){
+                               MochaUI.triangle(ctx, 0, 0, 10, 10, options.resizableColor, 0);
+                       }
+               }
+
+       },      
+       drawControls : function(width, height, shadows){
+               var options = this.options;
+               var shadowBlur = options.shadowBlur;
+               var shadowOffset = options.shadowOffset;
+               var controlsOffset = options.controlsOffset;
+               
+               // Make sure controls are placed correctly.
+               this.controlsEl.setStyles({
+                       'right': shadowBlur + shadowOffset.x + controlsOffset.right,
+                       'top': shadowBlur - shadowOffset.y + controlsOffset.top
+               });
+
+               this.canvasControlsEl.setStyles({
+                       'right': shadowBlur + shadowOffset.x + controlsOffset.right,
+                       'top': shadowBlur - shadowOffset.y + controlsOffset.top
+               });
+
+               // Calculate X position for controlbuttons
+               //var mochaControlsWidth = 52;
+               this.closebuttonX = options.closable ? this.mochaControlsWidth - 7 : this.mochaControlsWidth + 12;
+               this.maximizebuttonX = this.closebuttonX - (options.maximizable ? 19 : 0);
+               this.minimizebuttonX = this.maximizebuttonX - (options.minimizable ? 19 : 0);
+               
+               var ctx2 = this.canvasControlsEl.getContext('2d');
+               ctx2.clearRect(0, 0, 100, 100);
+
+               if (this.options.closable){
+                       this.closebutton(
+                               ctx2,
+                               this.closebuttonX,
+                               7,
+                               options.closeBgColor,
+                               1.0,
+                               options.closeColor,
+                               1.0
+                       );
+               }
+               if (this.options.maximizable){
+                       this.maximizebutton(
+                               ctx2,
+                               this.maximizebuttonX,
+                               7,
+                               options.maximizeBgColor,
+                               1.0,
+                               options.maximizeColor,
+                               1.0
+                       );
+               }
+               if (this.options.minimizable){
+                       this.minimizebutton(
+                               ctx2,
+                               this.minimizebuttonX,
+                               7,
+                               options.minimizeBgColor,
+                               1.0,
+                               options.minimizeColor,
+                               1.0
+                       );
+               }
+               
+       },
+       drawBox: function(ctx, width, height, shadowBlur, shadowOffset, shadows){
+
+               var shadowBlur2x = shadowBlur * 2;
+               var cornerRadius = this.options.cornerRadius;
+
+               // This is the drop shadow. It is created onion style.
+               if ( shadows != false ) {       
+                       for (var x = 0; x <= shadowBlur; x++){
+                               MochaUI.roundedRect(
+                                       ctx,
+                                       shadowOffset.x + x,
+                                       shadowOffset.y + x,
+                                       width - (x * 2) - shadowOffset.x,
+                                       height - (x * 2) - shadowOffset.y,
+                                       cornerRadius + (shadowBlur - x),
+                                       [0, 0, 0],
+                                       x == shadowBlur ? .29 : .065 + (x * .01)
+                               );
+                       }
+               }
+               // Window body.
+               this.bodyRoundedRect(
+                       ctx,                          // context
+                       shadowBlur - shadowOffset.x,  // x
+                       shadowBlur - shadowOffset.y,  // y
+                       width - shadowBlur2x,         // width
+                       height - shadowBlur2x,        // height
+                       cornerRadius,                 // corner radius
+                       this.options.bodyBgColor      // Footer color
+               );
+
+               if (this.options.type != 'notification'){
+               // Window header.
+                       this.topRoundedRect(
+                               ctx,                            // context
+                               shadowBlur - shadowOffset.x,    // x
+                               shadowBlur - shadowOffset.y,    // y
+                               width - shadowBlur2x,           // width
+                               this.options.headerHeight,      // height
+                               cornerRadius,                   // corner radius
+                               this.options.headerStartColor,  // Header gradient's top color
+                               this.options.headerStopColor    // Header gradient's bottom color
+                       );
+               }       
+       },
+       drawBoxCollapsed: function(ctx, width, height, shadowBlur, shadowOffset, shadows){
+
+               var options = this.options;
+               var shadowBlur2x = shadowBlur * 2;
+               var cornerRadius = options.cornerRadius;
+       
+               // This is the drop shadow. It is created onion style.
+               if ( shadows != false ){
+                       for (var x = 0; x <= shadowBlur; x++){
+                               MochaUI.roundedRect(
+                                       ctx,
+                                       shadowOffset.x + x,
+                                       shadowOffset.y + x,
+                                       width - (x * 2) - shadowOffset.x,
+                                       height - (x * 2) - shadowOffset.y,
+                                       cornerRadius + (shadowBlur - x),
+                                       [0, 0, 0],
+                                       x == shadowBlur ? .3 : .06 + (x * .01)
+                               );
+                       }
+               }
+
+               // Window header
+               this.topRoundedRect2(
+                       ctx,                          // context
+                       shadowBlur - shadowOffset.x,  // x
+                       shadowBlur - shadowOffset.y,  // y
+                       width - shadowBlur2x,         // width
+                       options.headerHeight + 2,     // height
+                       cornerRadius,                 // corner radius
+                       options.headerStartColor,     // Header gradient's top color
+                       options.headerStopColor       // Header gradient's bottom color
+               );
+
+       },      
+       drawGauge: function(ctx, width, height, shadowBlur, shadowOffset, shadows){
+               var options = this.options;
+               var radius = (width * .5) - (shadowBlur) + 16;
+               if (shadows != false) { 
+                       for (var x = 0; x <= shadowBlur; x++){
+                               MochaUI.circle(
+                                       ctx,
+                                       width * .5 + shadowOffset.x,
+                                       (height  + options.headerHeight) * .5 + shadowOffset.x,
+                                       (width *.5) - (x * 2) - shadowOffset.x,
+                                       [0, 0, 0],
+                                       x == shadowBlur ? .75 : .075 + (x * .04)
+                               );
+                       }
+               }
+               MochaUI.circle(
+                       ctx,
+                       width * .5  - shadowOffset.x,
+                       (height + options.headerHeight) * .5  - shadowOffset.y,
+                       (width *.5) - shadowBlur,
+                       options.bodyBgColor,
+                       1
+               );
+
+               // Draw gauge header
+               this.canvasHeaderEl.setStyles({
+                       'top': shadowBlur - shadowOffset.y,
+                       'left': shadowBlur - shadowOffset.x
+               });             
+               var ctx = this.canvasHeaderEl.getContext('2d');
+               ctx.clearRect(0, 0, width, 100);
+               ctx.beginPath();
+               ctx.lineWidth = 24;
+               ctx.lineCap = 'round';
+               ctx.moveTo(13, 13);
+               ctx.lineTo(width - (shadowBlur*2) - 13, 13);
+               ctx.strokeStyle = 'rgba(0, 0, 0, .65)';
+               ctx.stroke();
+       },
+       bodyRoundedRect: function(ctx, x, y, width, height, radius, rgb){
+               ctx.fillStyle = 'rgba(' + rgb.join(',') + ', 100)';
+               ctx.beginPath();
+               ctx.moveTo(x, y + radius);
+               ctx.lineTo(x, y + height - radius);
+               ctx.quadraticCurveTo(x, y + height, x + radius, y + height);
+               ctx.lineTo(x + width - radius, y + height);
+               ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
+               ctx.lineTo(x + width, y + radius);
+               ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
+               ctx.lineTo(x + radius, y);
+               ctx.quadraticCurveTo(x, y, x, y + radius);
+               ctx.fill();
+
+       },
+       topRoundedRect: function(ctx, x, y, width, height, radius, headerStartColor, headerStopColor){
+               var lingrad = ctx.createLinearGradient(0, 0, 0, height);
+               lingrad.addColorStop(0, 'rgba(' + headerStartColor.join(',') + ', 1)');
+               lingrad.addColorStop(1, 'rgba(' + headerStopColor.join(',') + ', 1)');          
+               ctx.fillStyle = lingrad;
+               ctx.beginPath();
+               ctx.moveTo(x, y);
+               ctx.lineTo(x, y + height);
+               ctx.lineTo(x + width, y + height);
+               ctx.lineTo(x + width, y + radius);
+               ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
+               ctx.lineTo(x + radius, y);
+               ctx.quadraticCurveTo(x, y, x, y + radius);
+               ctx.fill();
+               /*
+               ctx.beginPath();
+               ctx.strokeStyle = '#000';
+               ctx.lineWidth = 1;
+               ctx.moveTo(x, y + height + .5);
+               ctx.lineTo(x + width, y + height + .5);
+               ctx.stroke();
+               */
+
+       },
+       topRoundedRect2: function(ctx, x, y, width, height, radius, headerStartColor, headerStopColor){
+               var lingrad = ctx.createLinearGradient(0, this.options.shadowBlur - 1, 0, height + this.options.shadowBlur + 3);
+               lingrad.addColorStop(0, 'rgba(' + headerStartColor.join(',') + ', 1)');
+               lingrad.addColorStop(1, 'rgba(' + headerStopColor.join(',') + ', 1)');
+               ctx.fillStyle = lingrad;
+               ctx.beginPath();
+               ctx.moveTo(x, y + radius);
+               ctx.lineTo(x, y + height - radius);
+               ctx.quadraticCurveTo(x, y + height, x + radius, y + height);
+               ctx.lineTo(x + width - radius, y + height);
+               ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
+               ctx.lineTo(x + width, y + radius);
+               ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
+               ctx.lineTo(x + radius, y);
+               ctx.quadraticCurveTo(x, y, x, y + radius);
+               ctx.fill();     
+       },
+       maximizebutton: function(ctx, x, y, rgbBg, aBg, rgb, a){
+               // Circle
+               ctx.beginPath();
+               ctx.moveTo(x, y);
+               ctx.arc(x, y, 7, 0, Math.PI*2, true);
+               ctx.fillStyle = 'rgba(' + rgbBg.join(',') + ',' + aBg + ')';
+               ctx.fill();
+               // X sign
+               ctx.strokeStyle = 'rgba(' + rgb.join(',') + ',' + a + ')';
+               ctx.beginPath();
+               ctx.moveTo(x, y - 4);
+               ctx.lineTo(x, y + 4);
+               ctx.stroke();
+               ctx.beginPath();
+               ctx.moveTo(x - 4, y);
+               ctx.lineTo(x + 4, y);
+               ctx.stroke();
+       },
+       closebutton: function(ctx, x, y, rgbBg, aBg, rgb, a){
+               // Circle
+               ctx.beginPath();
+               ctx.moveTo(x, y);
+               ctx.arc(x, y, 7, 0, Math.PI*2, true);
+               ctx.fillStyle = 'rgba(' + rgbBg.join(',') + ',' + aBg + ')';
+               ctx.fill();
+               // Plus sign
+               ctx.strokeStyle = 'rgba(' + rgb.join(',') + ',' + a + ')';
+               ctx.beginPath();
+               ctx.moveTo(x - 3, y - 3);
+               ctx.lineTo(x + 3, y + 3);
+               ctx.stroke();
+               ctx.beginPath();
+               ctx.moveTo(x + 3, y - 3);
+               ctx.lineTo(x - 3, y + 3);
+               ctx.stroke();
+       },
+       minimizebutton: function(ctx, x, y, rgbBg, aBg, rgb, a){
+               // Circle
+               ctx.beginPath();
+               ctx.moveTo(x,y);
+               ctx.arc(x,y,7,0,Math.PI*2,true);
+               ctx.fillStyle = 'rgba(' + rgbBg.join(',') + ',' + aBg + ')';
+               ctx.fill();
+               // Minus sign
+               ctx.strokeStyle = 'rgba(' + rgb.join(',') + ',' + a + ')';
+               ctx.beginPath();
+               ctx.moveTo(x - 4, y);
+               ctx.lineTo(x + 4, y);
+               ctx.stroke();
+       },
+       /*
+
+       Function: hideSpinner
+               Hides the spinner.
+               
+       */      
+       hideSpinner: function(spinner) {
+               if ($(spinner)) $(spinner).setStyle('visibility', 'hidden');
+       },
+       /*
+
+       Function: showSpinner
+               Shows the spinner.
+       
+       */      
+       showSpinner: function(spinner){
+               if (!this.options.useSpinner || this.options.shape == 'gauge' || this.options.type == 'notification') return;
+               $(spinner).setStyles({
+                       'visibility': 'visible'
+               });
+       },
+       setMochaControlsWidth: function(){
+               this.mochaControlsWidth = 0;
+               var options = this.options;
+               if (options.minimizable){
+                       this.mochaControlsWidth += (this.minimizeButtonEl.getStyle('margin-left').toInt() + this.minimizeButtonEl.getStyle('width').toInt());
+               }
+               if (options.maximizable){
+                       this.mochaControlsWidth += (this.maximizeButtonEl.getStyle('margin-left').toInt() + this.maximizeButtonEl.getStyle('width').toInt());
+               }
+               if (options.closable){
+                       this.mochaControlsWidth += (this.closeButtonEl.getStyle('margin-left').toInt() + this.closeButtonEl.getStyle('width').toInt());
+               }
+               this.controlsEl.setStyle('width', this.mochaControlsWidth);
+               if (options.useCanvasControls == true){
+                       this.canvasControlsEl.setProperty('width', this.mochaControlsWidth);
+               }
+       }
+});
+MochaUI.Window.implement(new Options, new Events);
+/*
+
+Script: Modal.js
+       Create modal dialog windows.
+
+Copyright:
+       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.  
+
+License:
+       MIT-style license.      
+
+Requires:
+       Core.js, Window.js
+
+See Also:
+       <Window>        
+       
+*/
+
+MochaUI.Modal = new Class({
+
+       Extends: MochaUI.Window,
+
+       Implements: [Events, Options],
+
+       initialize: function(options){
+
+               this.modalInitialize();
+               
+               window.addEvent('resize', function(){
+                       this.setModalSize();
+               }.bind(this));
+
+       },
+       modalInitialize: function(){
+               var modalOverlay = new Element('div', {
+                       'id': 'modalOverlay',
+                       'styles': {
+                               'height': document.getCoordinates().height,
+                               'opacity': .6
+                       }
+               }).inject(document.body);
+               
+               modalOverlay.addEvent('click', function(e){
+                       MochaUI.closeWindow(MochaUI.currentModal);
+               });
+               
+               if (Browser.Engine.trident4){
+                       var modalFix = new Element('iframe', {
+                               'id': 'modalFix',
+                               'scrolling': 'no',
+                               'marginWidth': 0,
+                               'marginHeight': 0,
+                               'src': '',
+                               'styles': {
+                                       'height': document.getCoordinates().height
+                               }
+                       }).inject(document.body);
+               }
+
+               this.modalOverlayOpenMorph = new Fx.Morph($('modalOverlay'), {
+                               'duration': 150
+                               });
+               this.modalOverlayCloseMorph = new Fx.Morph($('modalOverlay'), {
+                       'duration': 150,
+                       onComplete: function(){
+                               $('modalOverlay').setStyle('display', 'none');
+                               if (Browser.Engine.trident4){
+                                       $('modalFix').setStyle('display', 'none');
+                               }
+                       }.bind(this)
+               });
+       },
+       setModalSize: function(){
+               $('modalOverlay').setStyle('height', document.getCoordinates().height);
+               if (Browser.Engine.trident4){
+                       $('modalFix').setStyle('height', document.getCoordinates().height);
+               }
+       }
+});
+MochaUI.Modal.implement(new Options, new Events);
+/*
+
+Script: Windows-from-html.js
+       Create windows from html markup in page.
+
+Copyright:
+       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.  
+
+License:
+       MIT-style license.      
+
+Requires:
+       Core.js, Window.js
+
+Example:
+       HTML markup.
+       (start code)
+<div class="mocha" id="mywindow" style="width:300px;height:255px;top:50px;left:350px">
+       <h3 class="mochaTitle">My Window</h3>
+       <p>My Window Content</p>
+</div> 
+       (end)
+
+See Also:
+       <Window>
+
+*/
+
+MochaUI.extend({
+       NewWindowsFromHTML: function(){
+               $$('div.mocha').each(function(el) {
+                       // Get the window title and destroy that element, so it does not end up in window content
+                       if ( Browser.Engine.presto || Browser.Engine.trident5 ){
+                               el.setStyle('display','block'); // Required by Opera, and probably IE7
+                       }
+                       var title = el.getElement('h3.mochaTitle');
+                       var elDimensions = el.getStyles('height', 'width');
+                       var properties = {
+                               id: el.getProperty('id'),
+                               height: elDimensions.height.toInt(),
+                               width: elDimensions.width.toInt(),
+                               x: el.getStyle('left').toInt(),
+                               y: el.getStyle('top').toInt()
+                       };
+                       // If there is a title element, set title and destroy the element so it does not end up in window content
+                       if ( title ) {
+                               properties.title = title.innerHTML;
+                               title.destroy();
+                       }
+               
+                       // Get content and destroy the element
+                       properties.content = el.innerHTML;
+                       el.destroy();
+                       
+                       // Create window
+                       new MochaUI.Window(properties, true);
+               }.bind(this));
+       }
+});
+/*
+
+Script: Windows-from-json.js
+       Create one or more windows from JSON data. You can define all the same properties as you can for new MochaUI.Window(). Undefined properties are set to their defaults.
+
+Copyright:
+       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.  
+
+License:
+       MIT-style license.      
+
+Syntax:
+       (start code)
+       MochaUI.newWindowsFromJSON(properties);
+       (end)
+
+Example:
+       (start code)
+       MochaUI.jsonWindows = function(){
+               var url = 'data/json-windows-data.js';
+               var request = new Request.JSON({
+                       url: url,
+                       method: 'get',
+                       onComplete: function(properties) {
+                               MochaUI.newWindowsFromJSON(properties.windows);
+                       }
+               }).send();
+       }
+       (end)
+
+Note: 
+       Windows created from JSON are not compatible with the current cookie based version
+       of Save and Load Workspace.     
+
+See Also:
+       <Window>
+
+*/
+
+MochaUI.extend({       
+       newWindowsFromJSON: function(properties){
+               properties.each(function(properties) {
+                               new MochaUI.Window(properties);
+               }.bind(this));
+       }
+});
+/*
+
+Script: Arrange-cascade.js
+       Cascade windows.
+
+Copyright:
+       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.  
+
+License:
+       MIT-style license.      
+
+Requires:
+       Core.js, Window.js
+
+Syntax:
+       (start code)
+       MochaUI.arrangeCascade();
+       (end)
+
+*/
+
+MochaUI.options.extend({
+       viewportTopOffset:  30,    // Use a negative number if neccessary to place first window where you want it
+       viewportLeftOffset: 20,
+       windowTopOffset:    50,    // Initial vertical spacing of each window
+       windowLeftOffset:   40     // Initial horizontal spacing of each window 
+});
+
+MochaUI.extend({   
+       arrangeCascade: function(){
+               // See how much space we have to work with
+               var coordinates = document.getCoordinates();
+               
+               var openWindows = 0;
+               MochaUI.Windows.instances.each(function(instance){
+                       if (!instance.isMinimized) openWindows ++; 
+               });
+               
+               if ((this.options.windowTopOffset * (openWindows + 1)) >= (coordinates.height - this.options.viewportTopOffset)) {
+                       var topOffset = (coordinates.height - this.options.viewportTopOffset) / (openWindows + 1);
+               }
+               else {
+                       var topOffset = this.options.windowTopOffset;
+               }
+               
+               if ((this.options.windowLeftOffset * (openWindows + 1)) >= (coordinates.width - this.options.viewportLeftOffset - 20)) {
+                       var leftOffset = (coordinates.width - this.options.viewportLeftOffset - 20) / (openWindows + 1);
+               }
+               else {
+                       var leftOffset = this.options.windowLeftOffset;
+               }
+
+               var x = this.options.viewportLeftOffset;
+               var y = this.options.viewportTopOffset;
+               $$('div.mocha').each(function(windowEl){
+                       var currentWindowClass = MochaUI.Windows.instances.get(windowEl.id);
+                       if (!currentWindowClass.isMinimized && !currentWindowClass.isMaximized){
+                               id = windowEl.id;
+                               MochaUI.focusWindow(windowEl);
+                               x += leftOffset;
+                               y += topOffset;
+
+                               if (MochaUI.options.useEffects == false){
+                                       windowEl.setStyles({
+                                               'top': y,
+                                               'left': x
+                                       });
+                               }
+                               else {
+                                       var cascadeMorph = new Fx.Morph(windowEl, {
+                                               'duration': 550
+                                       });
+                                       cascadeMorph.start({
+                                               'top': y,
+                                               'left': x
+                                       });
+                               }
+                       }
+               }.bind(this));
+       }
+});
+/*
+
+Script: Arrange-tile.js
+       Cascade windows.
+
+Authors:
+       Harry Roberts and Greg Houston
+
+License:
+       MIT-style license.      
+
+Requires:
+       Core.js, Window.js
+
+Syntax:
+       (start code)
+       MochaUI.arrangeTile();
+       (end)
+
+*/
+MochaUI.extend({
+       arrangeTile: function(){
+               var x = 10;
+               var y = 10;
+       
+               var instances =  MochaUI.Windows.instances;
+
+               var windowsNum = 0;
+
+               instances.each(function(instance){
+                       if (!instance.isMinimized && !instance.isMaximized){
+                               windowsNum++;
+                       }
+               });
+
+               var cols = 3;
+               var rows = Math.ceil(windowsNum / cols);
+               
+               var coordinates = document.getCoordinates();
+       
+               var col_width = ((coordinates.width - this.options.viewportLeftOffset) / cols);
+               var col_height = ((coordinates.height - this.options.viewportTopOffset) / rows);
+               
+               var row = 0;
+               var col = 0;
+               
+               instances.each(function(instance){
+                       if (!instance.isMinimized && !instance.isMaximized){
+                               
+                               var content = instance.contentWrapperEl;
+                               var content_coords = content.getCoordinates();
+                               var window_coords = instance.windowEl.getCoordinates();
+                               
+                               // Calculate the amount of padding around the content window
+                               var padding_top = content_coords.top - window_coords.top;
+                               var padding_bottom = window_coords.height - content_coords.height - padding_top;
+                               var padding_left = content_coords.left - window_coords.left;
+                               var padding_right = window_coords.width - content_coords.width - padding_left;
+
+                               /*
+
+                               // This resizes the windows
+                               if (instance.options.shape != 'gauge' && instance.options.resizable == true){
+                                       var width = (col_width - 3 - padding_left - padding_right);
+                                       var height = (col_height - 3 - padding_top - padding_bottom);
+
+                                       if (width > instance.options.resizeLimit.x[0] && width < instance.options.resizeLimit.x[1]){
+                                               content.setStyle('width', width);
+                                       }
+                                       if (height > instance.options.resizeLimit.y[0] && height < instance.options.resizeLimit.y[1]){
+                                               content.setStyle('height', height);
+                                       }
+
+                               }*/
+
+                               var left = (x + (col * col_width));
+                               var top = (y + (row * col_height));
+
+                               instance.windowEl.setStyles({
+                                       'left': left,
+                                       'top': top
+                               });
+
+                               instance.drawWindow(instance.windowEl);
+
+                               MochaUI.focusWindow(instance.windowEl);
+
+                               if (++col === cols) {
+                                       row++;
+                                       col = 0;
+                               }
+                       }
+               }.bind(this));
+       }
+});/*
+
+Script: Tabs.js
+       Functionality for window tabs.
+
+Copyright:
+       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.  
+
+License:
+       MIT-style license.
+
+Requires:
+       Core.js, Window.js (for tabbed windows) or Layout.js (for tabbed panels)
+
+*/
+
+MochaUI.extend({
+       /*
+
+       Function: initializeTabs
+               Add click event to each list item that fires the selected function.
+
+       */
+       initializeTabs: function(el){
+               $(el).getElements('li').each(function(listitem){
+                       listitem.addEvent('click', function(e){
+                               MochaUI.selected(this, el);
+                       });
+               });
+       },
+       /*
+
+       Function: selected
+               Add "selected" class to current list item and remove it from sibling list items.
+
+       Syntax:
+               (start code)
+                       selected(el, parent);
+               (end)
+
+Arguments:
+       el - the list item
+       parent - the ul
+
+       */
+       selected: function(el, parent){
+               $(parent).getChildren().each(function(listitem){
+                       listitem.removeClass('selected');
+               });
+               el.addClass('selected');
+       }
+});
+
+/*
+
+Script: Layout.js
+       Create web application layouts. Enables window maximize.
+       
+Copyright:
+       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.
+
+License:
+       MIT-style license.      
+
+Requires:
+       Core.js, Window.js
+       
+*/
+
+MochaUI.Desktop = new Class({
+
+       Extends: MochaUI.Window,
+
+       Implements: [Events, Options],
+       
+       options: {
+               // Naming options:
+               // If you change the IDs of the Mocha Desktop containers in your HTML, you need to change them here as well.
+               desktop:             'desktop',
+               desktopHeader:       'desktopHeader',
+               desktopFooter:       'desktopFooter',
+               desktopNavBar:       'desktopNavbar',
+               pageWrapper:         'pageWrapper',
+               page:                'page',
+               desktopFooter:       'desktopFooterWrapper'
+       },      
+       initialize: function(options){
+               this.setOptions(options);
+               this.desktop         = $(this.options.desktop);
+               this.desktopHeader   = $(this.options.desktopHeader);
+               this.desktopNavBar   = $(this.options.desktopNavBar);
+               this.pageWrapper     = $(this.options.pageWrapper);
+               this.page            = $(this.options.page);
+               this.desktopFooter   = $(this.options.desktopFooter);           
+       
+               // This is run on dock initialize so no need to do it twice.
+               if (!MochaUI.Dock.dockWrapper){
+                       this.setDesktopSize();
+               }
+               this.menuInitialize();          
+
+               // Resize desktop, page wrapper, modal overlay, and maximized windows when browser window is resized
+               window.addEvent('resize', function(e){
+                       this.onBrowserResize();
+               }.bind(this));
+       },
+       menuInitialize: function(){
+               // Fix for dropdown menus in IE6
+               if (Browser.Engine.trident4 && this.desktopNavBar){
+                       this.desktopNavBar.getElements('li').each(function(element) {
+                               element.addEvent('mouseenter', function(){
+                                       this.addClass('ieHover');
+                               });
+                               element.addEvent('mouseleave', function(){
+                                       this.removeClass('ieHover');
+                               });
+                       });
+               };
+       },
+       onBrowserResize: function(){
+               this.setDesktopSize();
+               // Resize maximized windows to fit new browser window size
+               setTimeout( function(){
+                       MochaUI.Windows.instances.each(function(instance){
+                               if (instance.isMaximized){
+
+                                       // Hide iframe while resize for better performance
+                                       if ( instance.iframeEl ){
+                                               instance.iframeEl.setStyle('visibility', 'hidden');
+                                       }
+
+                                       var coordinates = document.getCoordinates();
+                                       var borderHeight = instance.contentBorderEl.getStyle('border-top').toInt() + instance.contentBorderEl.getStyle('border-bottom').toInt();
+                                       var toolbarHeight = instance.toolbarWrapperEl ? instance.toolbarWrapperEl.getStyle('height').toInt() + instance.toolbarWrapperEl.getStyle('border-top').toInt() : 0;
+                                       instance.contentWrapperEl.setStyles({
+                                               'height': coordinates.height - instance.options.headerHeight - instance.options.footerHeight - borderHeight - toolbarHeight,
+                                               'width': coordinates.width
+                                       });
+
+                                       instance.drawWindow($(instance.options.id));
+                                       if ( instance.iframeEl ){
+                                               instance.iframeEl.setStyles({
+                                                       'height': instance.contentWrapperEl.getStyle('height')
+                                               });
+                                               instance.iframeEl.setStyle('visibility', 'visible');
+                                       }
+
+                               }
+                       }.bind(this));
+               }.bind(this), 100);
+       },
+       setDesktopSize: function(){
+               var windowDimensions = window.getCoordinates();
+
+               // var dock = $(MochaUI.options.dock);
+               var dockWrapper = $(MochaUI.options.dockWrapper);
+               
+               // Setting the desktop height may only be needed by IE7
+               if (this.desktop){
+                       this.desktop.setStyle('height', windowDimensions.height);
+               }
+
+               // Set pageWrapper height so the dock doesn't cover the pageWrapper scrollbars.
+               if (this.pageWrapper) {
+
+                       var dockOffset = MochaUI.dockVisible ? dockWrapper.offsetHeight : 0;
+                       var pageWrapperHeight = windowDimensions.height;
+                       pageWrapperHeight -= this.pageWrapper.getStyle('border-top').toInt();
+                       pageWrapperHeight -= this.pageWrapper.getStyle('border-bottom').toInt();
+                       if (this.desktopHeader){ pageWrapperHeight -= this.desktopHeader.offsetHeight; }
+                       if (this.desktopFooter){ pageWrapperHeight -= this.desktopFooter.offsetHeight; }
+                       pageWrapperHeight -= dockOffset;
+
+                       if (pageWrapperHeight < 0){
+                               pageWrapperHeight = 0;
+                       }
+                       this.pageWrapper.setStyle('height', pageWrapperHeight);
+               }
+
+               if (MochaUI.Columns.instances.getKeys().length > 0){ // Conditional is a fix for a bug in IE6 in the no toolbars demo.
+                       MochaUI.Desktop.resizePanels();
+               }               
+       },
+       resizePanels: function(){
+               if (Browser.Engine.trident4){
+                       $$('.pad').setStyle('display', 'none');
+                       $$('.rHeight').setStyle('height', 1);
+               }
+               MochaUI.panelHeight();
+               MochaUI.rWidth();
+               if (Browser.Engine.trident4) $$('.pad').setStyle('display', 'block');
+       },
+       /*
+       
+       Function: maximizeWindow
+               Maximize a window.
+       
+       Syntax:
+               (start code)
+               MochaUI.Desktop.maximizeWindow(windowEl);
+               (end)   
+
+       */      
+       maximizeWindow: function(windowEl){
+
+               var currentInstance = MochaUI.Windows.instances.get(windowEl.id);
+               var options = currentInstance.options;
+               var windowDrag = currentInstance.windowDrag;
+
+               // If window no longer exists or is maximized, stop
+               if (windowEl != $(windowEl) || currentInstance.isMaximized ) return;
+               
+               if (currentInstance.isCollapsed){
+                       MochaUI.collapseToggle(windowEl);       
+               }
+
+               currentInstance.isMaximized = true;
+               
+               // If window is restricted to a container, it should not be draggable when maximized.
+               if (currentInstance.options.restrict){
+                       windowDrag.detach();
+                       if (options.resizable) {
+                               currentInstance.detachResizable();
+                       }
+                       currentInstance.titleBarEl.setStyle('cursor', 'default');
+               }       
+
+               // If the window has a container that is not the desktop
+               // temporarily move the window to the desktop while it is minimized.
+               if (options.container != this.desktop){
+                       this.desktop.grab(windowEl);
+                       if (this.options.restrict){
+                       windowDrag.container = this.desktop;
+                       }
+               }               
+
+               // Save original position
+               currentInstance.oldTop = windowEl.getStyle('top');
+               currentInstance.oldLeft = windowEl.getStyle('left');
+
+               var contentWrapperEl = currentInstance.contentWrapperEl;
+
+               // Save original dimensions
+               contentWrapperEl.oldWidth = contentWrapperEl.getStyle('width');
+               contentWrapperEl.oldHeight = contentWrapperEl.getStyle('height');
+
+               // Hide iframe
+               // Iframe should be hidden when minimizing, maximizing, and moving for performance and Flash issues
+               if ( currentInstance.iframeEl ) {
+                       currentInstance.iframeEl.setStyle('visibility', 'hidden');
+               }
+
+               var windowDimensions = document.getCoordinates();
+               var options = currentInstance.options;
+               var shadowBlur = options.shadowBlur;
+               var shadowOffset = options.shadowOffset;
+               var newHeight = windowDimensions.height - options.headerHeight - options.footerHeight;
+               newHeight -= currentInstance.contentBorderEl.getStyle('border-top').toInt();
+               newHeight -= currentInstance.contentBorderEl.getStyle('border-bottom').toInt();
+               newHeight -= (  currentInstance.toolbarWrapperEl ? currentInstance.toolbarWrapperEl.getStyle('height').toInt() + currentInstance.toolbarWrapperEl.getStyle('border-top').toInt() : 0);
+
+               if (MochaUI.options.useEffects == false){
+                       windowEl.setStyles({
+                               'top': shadowOffset.y - shadowBlur,
+                               'left': shadowOffset.x - shadowBlur
+                       });
+                       currentInstance.contentWrapperEl.setStyles({
+                               'height': newHeight,
+                               'width':  windowDimensions.width
+                       });
+                       currentInstance.drawWindow(windowEl);
+                       // Show iframe
+                       if ( currentInstance.iframeEl ) {
+                               currentInstance.iframeEl.setStyle('visibility', 'visible');
+                       }
+                       currentInstance.fireEvent('onMaximize', windowEl);
+               }
+               else {
+
+                       // Todo: Initialize the variables for these morphs once in an initialize function and reuse them
+
+                       var maximizeMorph = new Fx.Elements([contentWrapperEl, windowEl], { 
+                               duration: 70,
+                               onStart: function(windowEl){
+                                       currentInstance.maximizeAnimation = currentInstance.drawWindow.periodical(20, currentInstance, windowEl);
+                               }.bind(this),
+                               onComplete: function(windowEl){
+                                       $clear(currentInstance.maximizeAnimation);
+                                       currentInstance.drawWindow(windowEl);
+                                       // Show iframe
+                                       if ( currentInstance.iframeEl ) {
+                                               currentInstance.iframeEl.setStyle('visibility', 'visible');
+                                       }
+                                       currentInstance.fireEvent('onMaximize', windowEl);      
+                               }.bind(this)
+                       });
+                       maximizeMorph.start({
+                               '0': {  'height': newHeight,
+                                               'width':  windowDimensions.width
+                               },
+                               '1': {  'top': shadowOffset.y - shadowBlur,
+                                               'left': shadowOffset.x - shadowBlur 
+                               }
+                       });             
+               }
+               currentInstance.maximizeButtonEl.setProperty('title', 'Restore');
+               MochaUI.focusWindow(windowEl);
+
+       },
+       /*
+
+       Function: restoreWindow
+               Restore a maximized window.
+
+       Syntax:
+               (start code)
+               MochaUI.Desktop.restoreWindow(windowEl);
+               (end)   
+
+       */      
+       restoreWindow: function(windowEl){      
+       
+               var currentInstance = MochaUI.Windows.instances.get(windowEl.id);
+               
+               // Window exists and is maximized ?
+               if (windowEl != $(windowEl) || !currentInstance.isMaximized) return;
+                       
+               var options = currentInstance.options;
+               currentInstance.isMaximized = false;
+               
+               if (options.restrict){
+                       currentInstance.windowDrag.attach();
+                       if (options.resizable){
+                               currentInstance.reattachResizable();
+                       }                       
+                       currentInstance.titleBarEl.setStyle('cursor', 'move');
+               }               
+               
+               // Hide iframe
+               // Iframe should be hidden when minimizing, maximizing, and moving for performance and Flash issues
+               if ( currentInstance.iframeEl ) {
+                       currentInstance.iframeEl.setStyle('visibility', 'hidden');
+               }
+               
+               var contentWrapperEl = currentInstance.contentWrapperEl;
+               
+               if (MochaUI.options.useEffects == false){
+                       contentWrapperEl.setStyles({
+                               'width':  contentWrapperEl.oldWidth,
+                               'height': contentWrapperEl.oldHeight
+                       });
+                       currentInstance.drawWindow(windowEl);
+                       windowEl.setStyles({
+                               'top': currentInstance.oldTop,
+                               'left': currentInstance.oldLeft
+                       });
+                       if ( currentInstance.iframeEl ) {
+                               currentInstance.iframeEl.setStyle('visibility', 'visible');
+                       }                       
+                       if (options.container != this.desktop){
+                               $(options.container).grab(windowEl);
+                               if (options.restrict){
+                                       currentInstance.windowDrag.container = $(options.container);
+                               }
+                       }
+                       currentInstance.fireEvent('onRestore', windowEl);
+               }
+               else {
+                       var restoreMorph = new Fx.Elements([contentWrapperEl, windowEl], { 
+                               'duration':   150,
+                               'onStart': function(windowEl){
+                                       currentInstance.maximizeAnimation = currentInstance.drawWindow.periodical(20, currentInstance, windowEl);
+                               }.bind(this),
+                               'onComplete': function(el){
+                                       $clear(currentInstance.maximizeAnimation);
+                                       currentInstance.drawWindow(windowEl);
+                                       if (currentInstance.iframeEl){
+                                               currentInstance.iframeEl.setStyle('visibility', 'visible');
+                                       }
+                                       if (options.container != this.desktop){
+                                               $(options.container).grab(windowEl);
+                                               if (options.restrict){  
+                                                       currentInstance.windowDrag.container = $(options.container);
+                                               }
+                                       }
+                                       currentInstance.fireEvent('onRestore', windowEl);
+                               }.bind(this)
+                       });
+                       restoreMorph.start({ 
+                               '0': {  'height': contentWrapperEl.oldHeight,
+                                               'width':  contentWrapperEl.oldWidth
+                               },
+                               '1': {  'top':  currentInstance.oldTop,
+                                               'left': currentInstance.oldLeft
+                               }
+                       });
+               }
+               currentInstance.maximizeButtonEl.setProperty('title', 'Maximize');
+       }
+});
+MochaUI.Desktop.implement(new Options, new Events);
+
+/*
+
+Class: Column
+       Create a column. Columns should be created from left to right.
+
+Syntax:
+(start code)
+       MochaUI.Panel();
+(end)
+
+Arguments:
+       options
+
+Options:
+       id - The ID of the column. This must be set when creating the column.   
+       placement - Can be 'right', 'main', or 'left'. There must be at least one column with the 'main' option.
+       width - 'main' column is fluid and should not be given a width.
+       resizeLimit - resizelimit of a 'right' or 'left' column.
+       onResize - (function) Fired when the column is resized.
+       onCollapse - (function) Fired when the column is collapsed.
+       onExpand - (function) Fired when the column is expanded.
+               
+*/
+MochaUI.Column = new Class({
+
+       Extends: MochaUI.Desktop,
+
+       Implements: [Events, Options],
+
+       options: {
+               id:            null, 
+               placement:     null, 
+               width:         null,
+               resizeLimit:   [],
+
+               // Events
+               onResize:     $empty, 
+               onCollapse:   $empty,
+               onExpand:     $empty
+
+       },
+       initialize: function(options){
+               this.setOptions(options);
+               
+               $extend(this, {
+                       timestamp: $time(),
+                       isCollapsed: false,
+                       oldWidth: 0
+               });
+
+               // Shorten object chain
+               var options = this.options;
+               var instances = MochaUI.Columns.instances;
+               var instanceID = instances.get(options.id);
+
+               // Check to see if there is already a class instance for this Column
+               if (instanceID){
+                       var currentInstance = instanceID;
+               }
+
+               // Check if column already exists
+               if ( this.columnEl ){
+                       return;
+               }
+               else {                  
+                       instances.set(options.id, this);
+               }               
+                               
+               this.columnEl = new Element('div', {
+                       'id': this.options.id,
+                       'class': 'column expanded',
+                       'styles': {
+                               'width': options.placement == 'main' ? null : options.width
+                       }
+               }).inject($(MochaUI.Desktop.pageWrapper));
+
+               var parent = this.columnEl.getParent();
+               var columnHeight = parent.getStyle('height').toInt();
+               this.columnEl.setStyle('height', columnHeight);
+
+               if (options.placement == 'main'){
+                       this.columnEl.addClass('rWidth');
+               }
+
+               this.spacerEl = new Element('div', {
+                       'id': this.options.id + '_spacer',
+                       'class': 'horizontalHandle'
+               }).inject(this.columnEl);
+
+               switch (this.options.placement) {
+                       case 'left':
+                               this.handleEl = new Element('div', {
+                                       'id': this.options.id + '_handle',
+                                       'class': 'columnHandle'
+                               }).inject(this.columnEl, 'after');
+
+                               this.handleIconEl = new Element('div', {
+                                       'id': options.id + '_handle_icon',
+                                       'class': 'handleIcon'
+                               }).inject(this.handleEl);
+
+                               addResizeRight(this.columnEl, options.resizeLimit[0], options.resizeLimit[1]);
+                               break;
+                       case 'right':
+                               this.handleEl = new Element('div', {
+                                       'id': this.options.id + '_handle',
+                                       'class': 'columnHandle'
+                               }).inject(this.columnEl, 'before');
+
+                               this.handleIconEl = new Element('div', {
+                                       'id': options.id + '_handle_icon',
+                                       'class': 'handleIcon'
+                               }).inject(this.handleEl);
+                               addResizeLeft(this.columnEl, options.resizeLimit[0], options.resizeLimit[1]);
+                               break;
+               }
+
+               if (this.handleEl != null){
+                       this.handleEl.addEvent('dblclick', function(){
+                               this.columnToggle();
+                       }.bind(this));
+               }
+
+               MochaUI.rWidth();
+
+       },
+       columnToggle: function(){
+               var column= this.columnEl;
+               
+               // Collapse
+               if (this.isCollapsed == false){
+                       this.oldWidth = column.getStyle('width').toInt();
+
+                       this.resize.detach();
+                       this.handleEl.removeEvents('dblclick');
+                       this.handleEl.addEvent('click', function(){
+                               this.columnToggle();
+                       }.bind(this));
+                       this.handleEl.setStyle('cursor', 'pointer').addClass('detached');
+                       
+                       column.setStyle('width', 0);
+                       this.isCollapsed = true;
+                       column.addClass('collapsed');
+                       column.removeClass('expanded');
+
+                       MochaUI.rWidth();
+                       this.fireEvent('onCollapse');
+               }
+               // Expand
+               else {
+                       column.setStyle('width', this.oldWidth);
+                       this.isCollapsed = false;
+                       column.addClass('expanded');
+                       column.removeClass('collapsed');
+
+                       this.handleEl.removeEvents('click');
+                       this.handleEl.addEvent('dblclick', function(){
+                               this.columnToggle();
+                       }.bind(this));
+                       this.resize.attach();
+                       this.handleEl.setStyle('cursor', 'e-resize').addClass('attached');
+
+                       MochaUI.rWidth();
+                       this.fireEvent('onExpand');
+               }
+       }
+});
+MochaUI.Column.implement(new Options, new Events);
+
+/*
+
+Class: Panel
+       Create a panel. Panels go one on top of another in columns. Create your columns first and then add your panels. Panels should be created from top to bottom, left to right.
+
+Syntax:
+(start code)
+       MochaUI.Panel();
+(end)
+
+Arguments:
+       options
+
+Options:
+       id - The ID of the panel. This must be set when creating the panel.
+       column - Where to inject the panel. This must be set when creating the panel.
+       loadMethod - ('html', 'xhr', or 'iframe')
+       contentURL - Used if loadMethod is set to 'xhr' or 'iframe'.
+       evalScripts - (boolean) An xhr loadMethod option. Defaults to true.
+       evalResponse - (boolean) An xhr loadMethod option. Defaults to false.
+       content - (string or element) An html loadMethod option.
+       tabsURL - (url) 
+       footer - (boolean)
+       footerURL - (url)
+       height - (number) Height of content area.
+       addClass - (string) Add a class to the panel.
+       scrollbars - (boolean)
+       padding - (object)
+       panelBackground - CSS background property for the panel.
+       onBeforeBuild - (function) Fired before the panel is created.
+       onContentLoaded - (function) Fired after the panel's conten is loaded.
+       onResize - (function) Fired when the panel is resized.
+       onCollapse - (function) Fired when the panel is collapsed.
+       onExpand - (function) Fired when the panel is expanded.
+               
+*/
+MochaUI.Panel = new Class({
+                                                       
+       Extends: MochaUI.Desktop,
+       
+       Implements: [Events, Options],
+       
+       options: {
+               id:               null,
+               title:            'New Panel',
+               column:           null,
+               loadMethod:       'html',
+               contentURL:       'pages/lipsum.html',
+       
+               // xhr options
+               evalScripts:      true,
+               evalResponse:     false,
+       
+               // html options
+               content:          'Panel content',
+               
+               // Tabs
+               tabsURL:          null,
+
+               footer:           false,
+               footerURL:        'pages/lipsum.html',
+               
+               // Style options:
+               height:           125,
+               addClass:         '',
+               scrollbars:       true,
+               padding:                  { top: 8, right: 8, bottom: 8, left: 8 },
+
+               // Color options:               
+               panelBackground:   '#f8f8f8',
+
+               // Events
+               onBeforeBuild:     $empty,
+               onContentLoaded:   $empty,
+               onResize:          $empty,
+               onCollapse:        $empty,
+               onExpand:          $empty
+
+       },      
+       initialize: function(options){
+               this.setOptions(options);
+
+               $extend(this, {
+                       timestamp: $time(),
+                       isCollapsed: false,
+                       oldHeight: 0,
+                       partner: null
+               });
+
+               // Shorten object chain
+               var instances = MochaUI.Panels.instances;
+               var instanceID = instances.get(this.options.id);
+       
+               // Check to see if there is already a class instance for this panel
+               if (instanceID){
+                       var currentInstance = instanceID;
+               }
+
+               // Check if panel already exists
+               if ( this.panelEl ){
+                       return;
+               }
+               else {                  
+                       instances.set(this.options.id, this);
+               }
+
+               this.fireEvent('onBeforeBuild');
+               
+               if (this.options.loadMethod == 'iframe') {
+                       // Iframes have their own scrollbars and padding.
+                       this.options.scrollbars = false;
+                       this.options.padding = { top: 0, right: 0, bottom: 0, left: 0 };
+               }
+
+               this.showHandle = true;
+               if ($(this.options.column).getChildren().length == 0){
+                       this.showHandle = false;
+               }
+
+               this.panelEl = new Element('div', {
+                       'id': this.options.id,
+                       'class': 'panel expanded',
+                       'styles': {
+                               'height': this.options.height,
+                               'background': this.options.panelBackground
+                       }
+               }).inject($(this.options.column));
+
+               this.panelEl.addClass(this.options.addClass);
+
+               this.contentEl = new Element('div', {
+                       'id': this.options.id + '_pad',
+                       'class': 'pad'
+               }).inject(this.panelEl);
+
+               if (this.options.footer){
+                       this.footerWrapperEl = new Element('div', {
+                               'id': this.options.id + '_panelFooterWrapper',
+                               'class': 'panel-footerWrapper'
+                       }).inject(this.panelEl);
+                       
+                       this.footerEl = new Element('div', {
+                               'id': this.options.id + '_panelFooter',
+                               'class': 'panel-footer'
+                       }).inject(this.footerWrapperEl);
+
+
+                       MochaUI.updateContent({
+                               'element':       this.panelEl,
+                               'childElement':  this.footerEl,
+                               'loadMethod':    'xhr',
+                               'url':           this.options.footerURL
+                       });
+                       
+               }
+
+               // This is in order to use the same variable as the windows do in updateContent.
+               // May rethink this.
+               this.contentWrapperEl = this.panelEl;
+               
+               // Set scrollbars, always use 'hidden' for iframe windows
+               this.contentWrapperEl.setStyles({
+                       'overflow': this.options.scrollbars && !this.iframeEl ? 'auto' : 'hidden'
+               });
+
+               this.contentEl.setStyles({
+                       'padding-top': this.options.padding.top,
+                       'padding-bottom': this.options.padding.bottom,
+                       'padding-left': this.options.padding.left,
+                       'padding-right': this.options.padding.right
+               });                     
+               
+               this.panelHeaderEl = new Element('div', {
+                       'id': this.options.id + '_header',
+                       'class': 'panel-header'
+               }).inject(this.panelEl, 'before');
+               
+               this.panelHeaderToolboxEl = new Element('div', {
+                       'id': this.options.id + '_headerToolbox',
+                       'class': 'panel-header-toolbox'
+               }).inject(this.panelHeaderEl);
+
+               this.collapseToggleEl = new Element('div', {
+                       'id': this.options.id + '_minmize',
+                       'class': 'panel-collapse icon16',
+                       'styles': {
+                               'width': 16,
+                               'height': 16
+                       },
+                       'title': 'Collapse Panel'
+               }).inject(this.panelHeaderToolboxEl);
+
+               this.collapseToggleEl.addEvent('click', function(event){
+                       var panel = this.panelEl;
+                       
+                       // Get siblings and make sure they are not all collapsed.
+                       var instances = MochaUI.Panels.instances;
+                       var expandedSiblings = [];
+                       panel.getAllPrevious('.panel').each(function(sibling){
+                               var currentInstance = instances.get(sibling.id);
+                               if (currentInstance.isCollapsed == false){
+                                       expandedSiblings.push(sibling);
+                               }
+                       });
+                       panel.getAllNext('.panel').each(function(sibling){
+                               var currentInstance = instances.get(sibling.id);
+                               if (currentInstance.isCollapsed == false){
+                                       expandedSiblings.push(sibling);
+                               }
+                       });
+
+                       if (this.isCollapsed == false) {
+                               var currentColumn = MochaUI.Columns.instances.get($(this.options.column).id);
+
+                               if (expandedSiblings.length == 0 && currentColumn.options.placement != 'main'){
+                                       var currentColumn = MochaUI.Columns.instances.get($(this.options.column).id);
+                                       currentColumn.columnToggle();
+                                       return;
+                               }
+                               else if (expandedSiblings.length == 0 && currentColumn.options.placement == 'main'){
+                                       return;
+                               }
+                               this.oldHeight = panel.getStyle('height').toInt();
+                               if (this.oldHeight < 10) this.oldHeight = 20;
+                               panel.setStyle('height', 0);
+                               this.isCollapsed = true;
+                               panel.addClass('collapsed');
+                               panel.removeClass('expanded');
+                               MochaUI.panelHeight(this.options.column, panel, 'collapsing');
+                               this.collapseToggleEl.removeClass('panel-collapsed');
+                               this.collapseToggleEl.addClass('panel-expand');
+                               this.collapseToggleEl.setProperty('title','Expand Panel');
+                               this.fireEvent('onCollapse');
+                       }
+                       else {
+                               panel.setStyle('height', this.oldHeight);
+                               this.isCollapsed = false;
+                               panel.addClass('expanded');
+                               panel.removeClass('collapsed');
+                               MochaUI.panelHeight(this.options.column, panel, 'expanding');
+                               this.collapseToggleEl.removeClass('panel-expand');
+                               this.collapseToggleEl.addClass('panel-collapsed');
+                               this.collapseToggleEl.setProperty('title','Collapse Panel');
+                               this.fireEvent('onExpand');
+                       }
+               }
+               .bind(this));
+               
+               this.panelHeaderContentEl = new Element('div', {
+                       'id': this.options.id + '_headerContent',
+                       'class': 'panel-headerContent'
+               }).inject(this.panelHeaderEl);
+
+               this.titleEl = new Element('h2', {
+                       'id': this.options.id + '_title'
+               }).inject(this.panelHeaderContentEl);
+
+               if (this.options.tabsURL == null){
+                       this.titleEl.set('html', this.options.title);
+               }               
+               else {
+                       this.panelHeaderContentEl.addClass('tabs');
+                       MochaUI.updateContent({
+                               'element':      this.panelEl,
+                               'childElement': this.panelHeaderContentEl,
+                               'loadMethod':   'xhr',
+                               'url':          this.options.tabsURL
+                       });
+               }
+
+               this.handleEl = new Element('div', {
+                       'id': this.options.id + '_handle',
+                       'class': 'horizontalHandle',
+                       'styles': {
+                               'display': this.showHandle == true ? 'block' : 'none'
+                       }
+               }).inject(this.panelEl, 'after');
+               
+               this.handleIconEl = new Element('div', {
+                       'id': this.options.id + '_handle_icon',
+                       'class': 'handleIcon'
+               }).inject(this.handleEl);
+
+               addResizeBottom(this.options.id);
+
+               // Add content to panel.
+               MochaUI.updateContent({
+                       'element': this.panelEl,
+                       'content':  this.options.content,
+                       'url':      this.options.contentURL
+               });
+
+               MochaUI.panelHeight(this.options.column, this.panelEl, 'new');
+
+       }
+});
+MochaUI.Panel.implement(new Options, new Events);
+
+
+MochaUI.extend({
+       // Panel Height 
+       panelHeight: function(column, changing, action){
+               if (column != null) {
+                       MochaUI.panelHeight2($(column), changing, action);
+               }
+               else {
+                       $$('.column').each(function(column){
+                               MochaUI.panelHeight2(column);
+                       }.bind(this));
+               }
+       },
+       /*
+
+       actions can be new, collapsing or expanding.
+
+       */
+       panelHeight2: function(column, changing, action){
+
+                       var instances = MochaUI.Panels.instances;
+                       
+                       var parent = column.getParent();
+                       var columnHeight = parent.getStyle('height').toInt();
+                       if (Browser.Engine.trident4){
+                               columnHeight -= 1;
+                       }
+                       column.setStyle('height', columnHeight);
+
+                       var panels = column.getChildren('.panel');            // All the panels in the column.
+                       var panelsExpanded = column.getChildren('.expanded'); // All the expanded panels in the column.
+                       var panelsToResize = [];    // All the panels in the column whose height will be effected.
+                       var tallestPanel;           // The panel with the greatest height
+                       var tallestPanelHeight = 0;
+
+                       this.panelsHeight = 0;          // Height of all the panels in the column       
+                       this.height = 0;            // Height of all the elements in the column 
+
+                       // Set panel resize partners
+                       panels.each(function(panel){
+                               currentInstance = instances.get(panel.id);
+                               if (panel.hasClass('expanded') && panel.getNext('.expanded')){
+                                       currentInstance.partner = panel.getNext('.expanded');
+                                       currentInstance.resize.attach();
+                                       currentInstance.handleEl.setStyles({
+                                               'display': 'block',
+                                               'cursor': 'n-resize'
+                                       }).removeClass('detached');
+                               }
+                               else {
+                                       currentInstance.resize.detach();
+                                       currentInstance.handleEl.setStyle('cursor', null).addClass('detached');
+                               }
+                               if (panel.getNext('.panel') == null){
+                                       currentInstance.handleEl.setStyle('display', 'none');
+                               }
+                       }.bind(this));
+                       
+                       // Get the total height of all the column's children
+                       column.getChildren().each(function(el){
+
+                               if (el.hasClass('panel')){
+                                       var currentInstance = instances.get(el.id);
+
+                                       // Are any next siblings Expanded?
+                                       areAnyNextSiblingsExpanded = function(el){
+                                               var test;
+                                               el.getAllNext('.panel').each(function(sibling){
+                                                       var siblingInstance = instances.get(sibling.id);
+                                                       if (siblingInstance.isCollapsed == false){
+                                                               test = true;
+                                                       }
+                                               }.bind(this));
+                                               return test;
+                                       }.bind(this);
+
+                                       // If a next sibling is expanding, are any of the nexts siblings of the expanding sibling Expanded?
+                                       areAnyExpandingNextSiblingsExpanded = function(){
+                                               var test;
+                                               changing.getAllNext('.panel').each(function(sibling){
+                                                       var siblingInstance = instances.get(sibling.id);
+                                                       if (siblingInstance.isCollapsed == false){
+                                                               test = true;
+                                                       }
+                                               }.bind(this));
+                                               return test;
+                                       }.bind(this);
+                                       
+                                       // Resize panels that are not collapsed or "new"
+                                       if (action == 'new' ) {
+                                               if (currentInstance.isCollapsed != true && el != changing) {
+                                                       panelsToResize.push(el);
+                                               }
+                                               
+                                               // Height of panels that can be resized
+                                               if (currentInstance.isCollapsed != true && el != changing) {
+                                                       this.panelsHeight += el.offsetHeight.toInt();
+                                               }
+                                       }
+                                       // Resize panels that are not collapsed. If a panel is collapsing
+                                       // resize any expanded panels below. If there are no expanded panels
+                                       // below it, resize the expanded panels above it.
+                                       else if (action == null || action == 'collapsing' ){
+                                               if (currentInstance.isCollapsed != true && (el.getAllNext('.panel').contains(changing) != true || areAnyNextSiblingsExpanded(el) != true)){
+                                                       panelsToResize.push(el);
+                                               }
+                                               
+                                               // Height of panels that can be resized
+                                               if (currentInstance.isCollapsed != true && (el.getAllNext('.panel').contains(changing) != true || areAnyNextSiblingsExpanded(el) != true)){
+                                                       this.panelsHeight += el.offsetHeight.toInt();
+                                               }
+                                       }
+                                       // Resize panels that are not collapsed and are not expanding.
+                                       // Resize any expanded panels below the expanding panel. If there are no expanded panels
+                                       // below it, resize the first expanded panel above it.
+                                       else if (action == 'expanding'){
+                                                  
+                                               if (currentInstance.isCollapsed != true && (el.getAllNext('.panel').contains(changing) != true || (areAnyExpandingNextSiblingsExpanded() != true && el.getNext('.expanded') == changing)) && el != changing){
+                                                       panelsToResize.push(el);
+                                               }
+                                               // Height of panels that can be resized
+                                               if (currentInstance.isCollapsed != true && (el.getAllNext('.panel').contains(changing) != true || (areAnyExpandingNextSiblingsExpanded() != true && el.getNext('.expanded') == changing)) && el != changing){
+                                                       this.panelsHeight += el.offsetHeight.toInt();
+                                               }
+                                       }
+
+                                       if (el.style.height){
+                                               this.height += el.getStyle('height').toInt();
+                                       }
+                               }
+                               else {
+                                       this.height += el.offsetHeight.toInt();
+                               }
+                       }.bind(this));
+
+                       // Get the remaining height
+                       var remainingHeight = column.offsetHeight.toInt() - this.height;
+                       
+                       this.height = 0;
+
+                       // Get height of all the column's children
+                       column.getChildren().each(function(el){
+                               this.height += el.offsetHeight.toInt();
+                       }.bind(this));
+                               
+                       var remainingHeight = column.offsetHeight.toInt() - this.height;
+
+                       panelsToResize.each(function(panel){
+                               var ratio = this.panelsHeight / panel.offsetHeight.toInt();
+                               var newPanelHeight = panel.getStyle('height').toInt() + (remainingHeight / ratio);
+                               if (newPanelHeight < 1){
+                                       newPanelHeight = 0;
+                               }
+                               panel.setStyle('height', newPanelHeight);
+                       }.bind(this));  
+
+                       // Make sure the remaining height is 0. If not add/subtract the
+                       // remaining height to the tallest panel. This makes up for browser resizing,
+                       // off ratios, and users trying to give panels too much height.
+                       
+                       // Get height of all the column's children
+                       this.height = 0;
+                       column.getChildren().each(function(el){
+                               this.height += el.offsetHeight.toInt();
+                               if (el.hasClass('panel') && el.getStyle('height').toInt() > tallestPanelHeight){
+                                       tallestPanel = el;
+                                       tallestPanelHeight = el.getStyle('height').toInt();
+                               }
+                       }.bind(this));
+
+                       var remainingHeight = column.offsetHeight.toInt() - this.height;
+
+                       if ((remainingHeight > 0 || remainingHeight < 0) && tallestPanelHeight > 0){
+                               tallestPanel.setStyle('height', tallestPanel.getStyle('height').toInt() + remainingHeight );
+                               if (tallestPanel.getStyle('height') < 1){
+                                       tallestPanel.setStyle('height', 0 );
+                               }
+                       }
+
+                       $$('.columnHandle').each(function(handle){
+                               var handleHeight = parent.getStyle('height').toInt() - handle.getStyle('border-top').toInt() - handle.getStyle('border-bottom').toInt();
+                               if (Browser.Engine.trident4){
+                                       handleHeight -= 1;
+                               }
+                               handle.setStyle('height', handleHeight);
+                       });
+                       
+                       panelsExpanded.each(function(panel){
+                               MochaUI.resizeChildren(panel);
+                       }.bind(this));
+       },
+       // May rename this resizeIframeEl()
+       resizeChildren: function(panel){
+               var instances = MochaUI.Panels.instances;
+               var currentInstance = instances.get(panel.id);
+               var contentWrapperEl = currentInstance.contentWrapperEl;
+
+               if (currentInstance.iframeEl){
+                       currentInstance.iframeEl.setStyles({
+                               'height': contentWrapperEl.getStyle('height'),
+                               'width': contentWrapperEl.offsetWidth - contentWrapperEl.getStyle('border-left').toInt() - contentWrapperEl.getStyle('border-right').toInt()
+                       });
+               }
+       },
+       // Remaining Width
+       rWidth: function(){     
+               $$('.rWidth').each(function(column){
+                       var currentWidth = column.offsetWidth.toInt();
+                       currentWidth -= column.getStyle('border-left').toInt();
+                       currentWidth -= column.getStyle('border-right').toInt();
+
+                       var parent = column.getParent();
+                       this.width = 0;
+                       
+                       // Get the total width of all the parent element's children
+                       parent.getChildren().each(function(el){
+                               if (el.hasClass('mocha') != true){
+                                       this.width += el.offsetWidth.toInt();
+                               }
+                       }.bind(this));
+               
+                       // Add the remaining width to the current element
+                       var remainingWidth = parent.offsetWidth.toInt() - this.width;
+                       var newWidth =  currentWidth + remainingWidth;
+                       if (newWidth < 1) newWidth = 0;
+                       column.setStyle('width', newWidth);
+                       column.getChildren('.panel').each(function(panel){
+                               panel.setStyle('width', newWidth - panel.getStyle('border-left').toInt() - panel.getStyle('border-right').toInt());
+                               MochaUI.resizeChildren(panel);
+                       }.bind(this));
+               });
+       }
+
+});
+
+function addResizeRight(element, min, max){
+       if (!$(element)) return;
+       element = $(element);
+
+       var instances = MochaUI.Columns.instances;
+       var currentInstance = instances.get(element.id);
+
+       var handle = element.getNext('.columnHandle');
+       handle.setStyle('cursor', 'e-resize');  
+       if (!min) min = 50;
+       if (!max) max = 250;
+       if (Browser.Engine.trident){
+               handle.addEvents({
+                       'mousedown': function(){
+                               handle.setCapture();
+                       },
+                       'mouseup': function(){
+                               handle.releaseCapture();
+                       }
+               });
+       }
+       currentInstance.resize = element.makeResizable({
+               handle: handle,
+               modifiers: {x: 'width', y: false},
+               limit: { x: [min, max] },
+               onStart: function(){
+                       element.getElements('iframe').setStyle('visibility','hidden');
+                       element.getNext('.column').getElements('iframe').setStyle('visibility','hidden');
+               }.bind(this),
+               onDrag: function(){
+                       MochaUI.rWidth();
+                       if (Browser.Engine.trident4){
+                               element.getChildren().each(function(el){
+                                       var width = $(element).getStyle('width').toInt();
+                                       width -= el.getStyle('border-right').toInt();
+                                       width -= el.getStyle('border-left').toInt();
+                                       width -= el.getStyle('padding-right').toInt();
+                                       width -= el.getStyle('padding-left').toInt();
+                                       el.setStyle('width', width);
+                               }.bind(this));
+                       }                                               
+               }.bind(this),
+               onComplete: function(){
+                       MochaUI.rWidth();
+                       element.getElements('iframe').setStyle('visibility','visible');
+                       element.getNext('.column').getElements('iframe').setStyle('visibility','visible');
+                       currentInstance.fireEvent('onResize');
+               }.bind(this)
+       });     
+}
+
+function addResizeLeft(element, min, max){
+       if (!$(element)) return;
+       element = $(element);
+
+       var instances = MochaUI.Columns.instances;
+       var currentInstance = instances.get(element.id);
+
+       var handle = element.getPrevious('.columnHandle');
+       handle.setStyle('cursor', 'e-resize');
+       var partner = element.getPrevious('.column');
+       if (!min) min = 50;
+       if (!max) max = 250;
+       if (Browser.Engine.trident){    
+               handle.addEvents({
+                       'mousedown': function(){
+                               handle.setCapture();
+                       },      
+                       'mouseup': function(){
+                               handle.releaseCapture();
+                       }
+               });
+       }
+       currentInstance.resize = element.makeResizable({
+               handle: handle,
+               modifiers: {x: 'width' , y: false},
+               invert: true,
+               limit: { x: [min, max] },
+               onStart: function(){
+                       $(element).getElements('iframe').setStyle('visibility','hidden');
+                       partner.getElements('iframe').setStyle('visibility','hidden');
+               }.bind(this),
+               onDrag: function(){
+                       MochaUI.rWidth();
+               }.bind(this),
+               onComplete: function(){
+                       MochaUI.rWidth();
+                       $(element).getElements('iframe').setStyle('visibility','visible');
+                       partner.getElements('iframe').setStyle('visibility','visible');
+                       currentInstance.fireEvent('onResize');
+               }.bind(this)
+       });
+}
+
+function addResizeBottom(element){
+       if (!$(element)) return;
+       var element = $(element);
+       
+       var instances = MochaUI.Panels.instances;
+       var currentInstance = instances.get(element.id);
+       var handle = currentInstance.handleEl;
+       handle.setStyle('cursor', 'n-resize');
+       partner = currentInstance.partner;
+       min = 0;
+       max = function(){
+               return element.getStyle('height').toInt() + partner.getStyle('height').toInt();
+       }.bind(this);
+       
+       if (Browser.Engine.trident){    
+               handle.addEvents({
+                       'mousedown': function(){
+                               handle.setCapture();
+                       },      
+                       'mouseup': function(){
+                               handle.releaseCapture();
+                       }
+               });
+       }
+       currentInstance.resize = element.makeResizable({
+               handle: handle,
+               modifiers: {x: false, y: 'height'},
+               limit: { y: [min, max] },
+               invert: false,
+               onBeforeStart: function(){
+                       partner = currentInstance.partner;
+                       this.originalHeight = element.getStyle('height').toInt();
+                       this.partnerOriginalHeight = partner.getStyle('height').toInt();
+               }.bind(this),
+               onStart: function(){
+                       if (currentInstance.iframeEl) {
+                               currentInstance.iframeEl.setStyle('visibility', 'hidden');
+                       }
+                       partner.getElements('iframe').setStyle('visibility','hidden');
+               }.bind(this),
+               onDrag: function(){
+                       partnerHeight = partnerOriginalHeight + (this.originalHeight - element.getStyle('height').toInt());
+                       partner.setStyle('height', partnerHeight);
+                       MochaUI.resizeChildren(element, element.getStyle('height').toInt());
+                       MochaUI.resizeChildren(partner, partnerHeight);
+               }.bind(this),
+               onComplete: function(){
+                       partnerHeight = partnerOriginalHeight + (this.originalHeight - element.getStyle('height').toInt());
+                       partner.setStyle('height', partnerHeight);
+                       MochaUI.resizeChildren(element, element.getStyle('height').toInt());
+                       MochaUI.resizeChildren(partner, partnerHeight);
+                       if (currentInstance.iframeEl) {
+                               currentInstance.iframeEl.setStyle('visibility', 'visible');
+                       }
+                       partner.getElements('iframe').setStyle('visibility','visible');
+                       currentInstance.fireEvent('onResize');
+               }.bind(this)
+       });
+}
+/*
+
+Script: Dock.js
+       Implements the dock/taskbar. Enables window minimize.
+
+Copyright:
+       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.  
+
+License:
+       MIT-style license.
+
+Requires:
+       Core.js, Window.js, Layout.js   
+
+Todo:
+       - Make it so the dock requires no initial html markup.
+
+*/
+
+MochaUI.options.extend({
+               // Naming options:
+               // If you change the IDs of the Mocha Desktop containers in your HTML, you need to change them here as well.
+               dockWrapper: 'dockWrapper',
+               dock:        'dock'
+});
+
+// Used by Desktop.js before MochaUI.Dock is initialized.
+window.addEvent('domready', function(){        
+       if ($('dockWrapper')) {
+               MochaUI.dockVisible = true;
+       }
+});
+
+MochaUI.extend({
+       /*
+
+       Function: minimizeAll
+               Minimize all windows that are minimizable.
+
+       */      
+       minimizeAll: function() {
+               $$('div.mocha').each(function(windowEl){
+               var currentInstance = MochaUI.Windows.instances.get(windowEl.id);
+                       if (!currentInstance.isMinimized && currentInstance.options.minimizable == true){
+                               MochaUI.Dock.minimizeWindow(windowEl);
+                       }
+               }.bind(this));
+       }
+});
+
+MochaUI.Dock = new Class({
+       Extends: MochaUI.Window,
+
+       Implements: [Events, Options],
+
+       options: {
+               useControls:          true,      // Toggles autohide and dock placement controls.
+               dockPosition:         'top',     // Position the dock starts in, top or bottom.
+               // Style options
+               dockTabColor:         [255, 255, 255],
+               trueButtonColor:      [70, 245, 70],     // Color for autohide on
+               enabledButtonColor:   [125, 208, 250], 
+               disabledButtonColor:  [170, 170, 170]
+       },
+       initialize: function(options){
+               // Stops if MochaUI.Desktop is not implemented
+               if (!MochaUI.Desktop) return;
+               this.setOptions(options);
+               
+               this.dockWrapper   = $(MochaUI.options.dockWrapper);
+               this.dock          = $(MochaUI.options.dock);
+               this.autoHideEvent = null;              
+               this.dockAutoHide  = false;  // True when dock autohide is set to on, false if set to off
+
+               if (!this.dockWrapper) return;
+
+               if (!this.options.useControls){
+                       if($('dockPlacement')){
+                               $('dockPlacement').setStyle('cursor', 'default');
+                       }
+                       if($('dockAutoHide')){
+                               $('dockAutoHide').setStyle('cursor', 'default');
+                       }
+               }
+
+               this.dockWrapper.setStyles({
+                       'display':  'block',
+                       'position': 'absolute',
+                       'top':      null,
+                       'bottom':   MochaUI.Desktop.desktopFooter ? MochaUI.Desktop.desktopFooter.offsetHeight : 0,
+                       'left':     0
+               });
+               
+               if (this.options.useControls){
+                       this.initializeDockControls();
+               }
+
+               // Add check mark to menu if link exists in menu
+               if ($('dockLinkCheck')){
+                       this.sidebarCheck = new Element('div', {
+                               'class': 'check',
+                               'id': 'dock_check'
+                       }).inject($('dockLinkCheck'));
+               }
+
+               this.dockSortables = new Sortables('#dockSort', {
+                       opacity: Browser.Engine.trident ? 1 : .5,
+                       constrain: true,
+                       clone: false,
+                       revert: false
+               });
+
+               MochaUI.Desktop.setDesktopSize();
+       },
+       initializeDockControls: function(){
+               
+               if (this.options.useControls){
+                       // Insert canvas
+                       var canvas = new Element('canvas', {
+                               'id':     'dockCanvas',
+                               'width':  '15',
+                               'height': '18'
+                       }).inject(this.dock);
+
+                       // Dynamically initialize canvas using excanvas. This is only required by IE
+                       if (Browser.Engine.trident && MochaUI.ieSupport == 'excanvas'){
+                               G_vmlCanvasManager.initElement(canvas);
+                       }
+               }
+               
+               var dockPlacement = $('dockPlacement');
+               var dockAutoHide = $('dockAutoHide');
+
+               // Position top or bottom selector
+               dockPlacement.setProperty('title','Position Dock Top');
+
+               // Attach event
+               dockPlacement.addEvent('click', function(){
+                       this.moveDock();
+               }.bind(this));
+
+               // Auto Hide toggle switch
+               dockAutoHide.setProperty('title','Turn Auto Hide On');
+               
+               // Attach event Auto Hide 
+               dockAutoHide.addEvent('click', function(event){
+                       if ( this.dockWrapper.getProperty('dockPosition') == 'top' )
+                               return false;
+
+                       var ctx = $('dockCanvas').getContext('2d');
+                       this.dockAutoHide = !this.dockAutoHide; // Toggle
+                       if (this.dockAutoHide){
+                               $('dockAutoHide').setProperty('title', 'Turn Auto Hide Off');
+                               //ctx.clearRect(0, 11, 100, 100);
+                               MochaUI.circle(ctx, 5 , 14, 3, this.options.trueButtonColor, 1.0);
+
+                               // Define event
+                               this.autoHideEvent = function(event) {
+                                       if (!this.dockAutoHide)
+                                               return;
+                                       if (!MochaUI.Desktop.desktopFooter) {
+                                               var dockHotspotHeight = this.dockWrapper.offsetHeight;
+                                               if (dockHotspotHeight < 25) dockHotspotHeight = 25;
+                                       }
+                                       else if (MochaUI.Desktop.desktopFooter) {
+                                               var dockHotspotHeight = this.dockWrapper.offsetHeight + MochaUI.Desktop.desktopFooter.offsetHeight;
+                                               if (dockHotspotHeight < 25) dockHotspotHeight = 25;
+                                       }                                               
+                                       if (!MochaUI.Desktop.desktopFooter && event.client.y > (document.getCoordinates().height - dockHotspotHeight)){
+                                               if (!MochaUI.dockVisible){
+                                                       this.dockWrapper.setStyle('display', 'block');
+                                                       MochaUI.dockVisible = true;
+                                                       MochaUI.Desktop.setDesktopSize();
+                                               }
+                                       }
+                                       else if (MochaUI.Desktop.desktopFooter && event.client.y > (document.getCoordinates().height - dockHotspotHeight)){
+                                               if (!MochaUI.dockVisible){
+                                                       this.dockWrapper.setStyle('display', 'block');
+                                                       MochaUI.dockVisible = true;
+                                                       MochaUI.Desktop.setDesktopSize();
+                                               }
+                                       }
+                                       else if (MochaUI.dockVisible){
+                                               this.dockWrapper.setStyle('display', 'none');
+                                               MochaUI.dockVisible = false;
+                                               MochaUI.Desktop.setDesktopSize();
+                                               
+                                       }
+                               }.bind(this);
+
+                               // Add event
+                               document.addEvent('mousemove', this.autoHideEvent);
+
+                       } else {
+                               $('dockAutoHide').setProperty('title', 'Turn Auto Hide On');
+                               //ctx.clearRect(0, 11, 100, 100);
+                               MochaUI.circle(ctx, 5 , 14, 3, this.options.enabledButtonColor, 1.0);
+                               // Remove event
+                               document.removeEvent('mousemove', this.autoHideEvent);
+                       }
+
+               }.bind(this));
+
+               // Draw dock controls
+               var ctx = $('dockCanvas').getContext('2d');
+               ctx.clearRect(0, 0, 100, 100);
+               MochaUI.circle(ctx, 5 , 4, 3, this.options.enabledButtonColor, 1.0);
+               MochaUI.circle(ctx, 5 , 14, 3, this.options.enabledButtonColor, 1.0);
+               
+               if (this.options.dockPosition == 'top'){
+                       this.moveDock();
+               }
+
+       },
+       moveDock: function(){
+                       var ctx = $('dockCanvas').getContext('2d');
+                       // Move dock to top position
+                       if (this.dockWrapper.getStyle('position') != 'relative'){
+                               this.dockWrapper.setStyles({
+                                       'position': 'relative',
+                                       'bottom':   null
+                               });
+                               this.dockWrapper.addClass('top');
+                               MochaUI.Desktop.setDesktopSize();
+                               this.dockWrapper.setProperty('dockPosition','top');
+                               ctx.clearRect(0, 0, 100, 100);
+                               MochaUI.circle(ctx, 5, 4, 3, this.options.enabledButtonColor, 1.0);
+                               MochaUI.circle(ctx, 5, 14, 3, this.options.disabledButtonColor, 1.0);
+                               $('dockPlacement').setProperty('title', 'Position Dock Bottom');
+                               $('dockAutoHide').setProperty('title', 'Auto Hide Disabled in Top Dock Position');
+                               this.dockAutoHide = false;
+                       }
+                       // Move dock to bottom position
+                       else {
+                               this.dockWrapper.setStyles({
+                                       'position':      'absolute',
+                                       'bottom':        MochaUI.Desktop.desktopFooter ? MochaUI.Desktop.desktopFooter.offsetHeight : 0
+                               });
+                               this.dockWrapper.removeClass('top');
+                               MochaUI.Desktop.setDesktopSize();
+                               this.dockWrapper.setProperty('dockPosition', 'bottom');
+                               ctx.clearRect(0, 0, 100, 100);
+                               MochaUI.circle(ctx, 5, 4, 3, this.options.enabledButtonColor, 1.0);
+                               MochaUI.circle(ctx, 5 , 14, 3, this.options.enabledButtonColor, 1.0);
+                               $('dockPlacement').setProperty('title', 'Position Dock Top');
+                               $('dockAutoHide').setProperty('title', 'Turn Auto Hide On');
+                       }
+       },
+       createDockTab: function(windowEl){
+
+               var currentInstance = MochaUI.Windows.instances.get(windowEl.id);
+
+               var dockTab = new Element('div', {
+                       'id': currentInstance.options.id + '_dockTab',
+                       'class': 'dockTab',
+                       'title': titleText
+               }).inject($('dockClear'), 'before');
+               
+               dockTab.addEvent('mousedown', function(e){
+                       new Event(e).stop();
+                       this.timeDown = $time();
+               });
+               
+               dockTab.addEvent('mouseup', function(e){
+                       this.timeUp = $time();
+                       if ((this.timeUp - this.timeDown) < 275){
+                               // If the visibility of the windows on the page are toggled off, toggle visibility on.
+                               if (MochaUI.Windows.windowsVisible == false) {
+                                       MochaUI.toggleWindowVisibility();
+                                       if (currentInstance.isMinimized == true) {
+                                               MochaUI.Dock.restoreMinimized.delay(25, MochaUI.Dock, windowEl);
+                                       }
+                                       else {
+                                               MochaUI.focusWindow(windowEl);
+                                       }
+                                       return;
+                               }
+                               // If window is minimized, restore window.
+                               if (currentInstance.isMinimized == true) {
+                                       MochaUI.Dock.restoreMinimized.delay(25, MochaUI.Dock, windowEl);
+                               }
+                               else{
+                                       // If window is not minimized and is focused, minimize window.
+                                       if (currentInstance.windowEl.hasClass('isFocused') && currentInstance.options.minimizable == true){
+                                               MochaUI.Dock.minimizeWindow(windowEl)
+                                       }
+                                       // If window is not minimized and is not focused, focus window. 
+                                       else{
+                                               MochaUI.focusWindow(windowEl);
+                                       }
+                                       // if the window is not minimized and is outside the viewport, center it in the viewport.
+                                       var coordinates = document.getCoordinates();
+                                       if (windowEl.getStyle('left').toInt() > coordinates.width || windowEl.getStyle('top').toInt() > coordinates.height){
+                                               MochaUI.centerWindow(windowEl); 
+                                       }
+                               }
+                       }
+               });
+
+               this.dockSortables.addItems(dockTab);
+
+               var titleText = currentInstance.titleEl.innerHTML;
+
+               var dockTabText = new Element('div', {
+                       'id': currentInstance.options.id + '_dockTabText',
+                       'class': 'dockText'
+               }).set('html', titleText.substring(0,20) + (titleText.length > 20 ? '...' : '')).inject($(dockTab));
+
+               // If I implement this again, will need to also adjust the titleText truncate and the tab's
+               // left padding.
+               if (currentInstance.options.icon != false){
+                       // dockTabText.setStyle('background', 'url(' + currentInstance.options.icon + ') 4px 4px no-repeat');
+               }
+               
+               // Need to resize everything in case the dock wraps when a new tab is added
+               MochaUI.Desktop.setDesktopSize();
+
+       },
+       makeActiveTab: function(){
+
+               // getWindowWith HighestZindex is used in case the currently focused window
+               // is closed.           
+               var windowEl = MochaUI.getWindowWithHighestZindex();
+               var currentInstance = MochaUI.Windows.instances.get(windowEl.id);
+               
+               $$('div.dockTab').removeClass('activeDockTab');
+               if (currentInstance.isMinimized != true) {
+                       
+                       currentInstance.windowEl.addClass('isFocused');
+
+                       var currentButton = $(currentInstance.options.id + '_dockTab');
+                       if (currentButton != null) {
+                               currentButton.addClass('activeDockTab');
+                       }
+               }
+               else {
+                       currentInstance.windowEl.removeClass('isFocused');
+               }       
+       },      
+       minimizeWindow: function(windowEl){
+               if (windowEl != $(windowEl)) return;
+               
+               var currentInstance = MochaUI.Windows.instances.get(windowEl.id);
+               currentInstance.isMinimized = true;
+
+               // Hide iframe
+               // Iframe should be hidden when minimizing, maximizing, and moving for performance and Flash issues
+               if ( currentInstance.iframeEl ) {
+                       currentInstance.iframeEl.setStyle('visibility', 'hidden');
+               }
+
+               // Hide window and add to dock  
+               currentInstance.contentBorderEl.setStyle('visibility', 'hidden');
+               if(currentInstance.toolbarWrapperEl){           
+                       currentInstance.toolbarWrapperEl.setStyle('visibility', 'hidden');
+               }
+               windowEl.setStyle('visibility', 'hidden');
+
+                // Fixes a scrollbar issue in Mac FF2
+               if (Browser.Platform.mac && Browser.Engine.gecko){
+                       if (/Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent)) {
+                               var ffversion = new Number(RegExp.$1);
+                               if (ffversion < 3) {
+                                       currentInstance.contentWrapperEl.setStyle('overflow', 'hidden');
+                               }
+                       }
+               }
+       
+               MochaUI.Desktop.setDesktopSize();
+
+               // Have to use timeout because window gets focused when you click on the minimize button
+               setTimeout(function(){
+                       windowEl.setStyle('zIndex', 1);
+                       windowEl.removeClass('isFocused');
+                       this.makeActiveTab();   
+               }.bind(this),100);      
+
+               currentInstance.fireEvent('onMinimize', windowEl);
+       },
+       restoreMinimized: function(windowEl) {
+
+               var currentInstance = MochaUI.Windows.instances.get(windowEl.id);
+
+               if (currentInstance.isMinimized == false) return;
+
+               if (MochaUI.Windows.windowsVisible == false){
+                       MochaUI.toggleWindowVisibility();
+               }
+
+               MochaUI.Desktop.setDesktopSize();
+
+                // Part of Mac FF2 scrollbar fix
+               if (currentInstance.options.scrollbars == true && !currentInstance.iframeEl){ 
+                       currentInstance.contentWrapperEl.setStyle('overflow', 'auto');
+               }
+
+               if (currentInstance.isCollapsed) {
+                       MochaUI.collapseToggle(windowEl);
+               }
+
+               windowEl.setStyle('visibility', 'visible');
+               currentInstance.contentBorderEl.setStyle('visibility', 'visible');
+               if(currentInstance.toolbarWrapperEl){
+                       currentInstance.toolbarWrapperEl.setStyle('visibility', 'visible');
+               }
+
+               // Show iframe
+               if ( currentInstance.iframeEl ) {
+                       currentInstance.iframeEl.setStyle('visibility', 'visible');
+               }
+
+               currentInstance.isMinimized = false;
+               MochaUI.focusWindow(windowEl);
+               currentInstance.fireEvent('onRestore', windowEl);
+
+       }
+});
+MochaUI.Dock.implement(new Options, new Events);
+/*
+
+Script: Workspaces.js
+       Save and load workspaces. The Workspaces emulate Adobe Illustrator functionality remembering what windows are open and where they are positioned. There will be two versions, a limited version that saves state to a cookie, and a fully functional version that saves state to a database.
+
+Copyright:
+       Copyright (c) 2007-2008 Greg Houston, <http://greghoustondesign.com/>.
+
+License:
+       MIT-style license.
+
+Requires:
+       Core.js, Window.js
+
+To do:
+       - Move to Window
+
+*/
+
+MochaUI.extend({                          
+       /*
+       
+       Function: saveWorkspace
+               Save the current workspace.
+       
+       Syntax:
+       (start code)
+               MochaUI.saveWorkspace();
+       (end)
+       
+       Notes:
+               This is experimental. This version saves the ID of each open window to a cookie, and reloads those windows using the functions in mocha-init.js. This requires that each window have a function in mocha-init.js used to open them. Functions must be named the windowID + "Window". So if your window is called mywindow, it needs a function called mywindowWindow in mocha-init.js.
+       
+       */
+       saveWorkspace: function(){
+               this.cookie = new Hash.Cookie('mochaUIworkspaceCookie', {duration: 3600});
+               this.cookie.empty();
+               MochaUI.Windows.instances.each(function(instance) {
+                       instance.saveValues();
+                       this.cookie.set(instance.options.id, {
+                               'id': instance.options.id,
+                               'top': instance.options.y,
+                               'left': instance.options.x
+                       });
+               }.bind(this));
+               this.cookie.save();
+
+               new MochaUI.Window({
+                       loadMethod: 'html',
+                       type: 'notification',
+                       addClass: 'notification',
+                       content: 'Workspace saved.',
+                       closeAfter: '1400',
+                       width: 200,
+                       height: 40,
+                       y: 53,
+                       padding:  { top: 10, right: 12, bottom: 10, left: 12 },
+                       shadowBlur: 5,
+                       bodyBgColor: [255, 255, 255]
+               });
+               
+       },
+       windowUnload: function(){
+               if ($$('div.mocha').length == 0 && this.myChain){
+                       this.myChain.callChain();
+               }               
+       },
+       loadWorkspace2: function(workspaceWindows){             
+               workspaceWindows.each(function(instance){
+                       windowFunction = eval('MochaUI.' + instance.id + 'Window');
+                       if (windowFunction){
+                               eval('MochaUI.' + instance.id + 'Window();');
+                               $(instance.id).setStyles({
+                                       top: instance.top,
+                                       left: instance.left
+                               });
+                       }
+               }.bind(this));
+               this.loadingWorkspace = false;
+       },
+       /*
+
+       Function: loadWorkspace
+               Load the saved workspace.
+
+       Syntax:
+       (start code)
+               MochaUI.loadWorkspace();
+       (end)
+
+       */
+       loadWorkspace: function(){
+               cookie = new Hash.Cookie('mochaUIworkspaceCookie', {duration: 3600});
+               workspaceWindows = cookie.load();
+
+               if(!cookie.getKeys().length){
+                       new MochaUI.Window({
+                               loadMethod: 'html',
+                               type: 'notification',
+                               addClass: 'notification',
+                               content: 'You have no saved workspace.',
+                               closeAfter: '1400',
+                               width: 220,
+                               height: 40,
+                               y: 25,
+                               padding:  { top: 10, right: 12, bottom: 10, left: 12 },
+                               shadowBlur: 5,
+                               bodyBgColor: [255, 255, 255]
+                       });
+                       return;
+               }
+
+               if ($$('div.mocha').length != 0){
+                       this.loadingWorkspace = true;
+                       this.myChain = new Chain();
+                       this.myChain.chain(
+                               function(){
+                                       $$('div.mocha').each(function(el) {
+                                               this.closeWindow(el);
+                                       }.bind(this));
+                               }.bind(this),
+                               function(){
+                                       this.loadWorkspace2(workspaceWindows);
+                               }.bind(this)
+                       );
+                       this.myChain.callChain();
+               }
+               else {
+                       this.loadWorkspace2(workspaceWindows);
+               }
+
+       }
+});
index d7227094887a509f79cdb103275159341ffc9f2a..dcf740280f0742a468280117e26ec5517660e0da 100644 (file)
@@ -1,15 +1,15 @@
-/** @license\r
- *\r
- * SoundManager 2: JavaScript Sound for the Web\r
- * ----------------------------------------------\r
- * http://schillmania.com/projects/soundmanager2/\r
- *\r
- * Copyright (c) 2007, Scott Schiller. All rights reserved.\r
- * Code provided under the BSD License:\r
- * http://schillmania.com/projects/soundmanager2/license.txt\r
- *\r
- * V2.97a.20131201\r
- */\r
+/** @license
+ *
+ * SoundManager 2: JavaScript Sound for the Web
+ * ----------------------------------------------
+ * http://schillmania.com/projects/soundmanager2/
+ *
+ * Copyright (c) 2007, Scott Schiller. All rights reserved.
+ * Code provided under the BSD License:
+ * http://schillmania.com/projects/soundmanager2/license.txt
+ *
+ * V2.97a.20131201
+ */
 (function(g,k){function U(U,ka){function V(b){return c.preferFlash&&v&&!c.ignoreFlash&&c.flash[b]!==k&&c.flash[b]}function q(b){return function(c){var d=this._s;return!d||!d._a?null:b.call(this,c)}}this.setupOptions={url:U||null,flashVersion:8,debugMode:!0,debugFlash:!1,useConsole:!0,consoleOnly:!0,waitForWindowLoad:!1,bgColor:"#ffffff",useHighPerformance:!1,flashPollingInterval:null,html5PollingInterval:null,flashLoadTimeout:1E3,wmode:null,allowScriptAccess:"always",useFlashBlock:!1,useHTML5Audio:!0,
 html5Test:/^(probably|maybe)$/i,preferFlash:!1,noSWFCache:!1,idPrefix:"sound"};this.defaultOptions={autoLoad:!1,autoPlay:!1,from:null,loops:1,onid3:null,onload:null,whileloading:null,onplay:null,onpause:null,onresume:null,whileplaying:null,onposition:null,onstop:null,onfailure:null,onfinish:null,multiShot:!0,multiShotEvents:!1,position:null,pan:0,stream:!0,to:null,type:null,usePolicyFile:!1,volume:100};this.flash9Options={isMovieStar:null,usePeakData:!1,useWaveformData:!1,useEQData:!1,onbufferchange:null,
 ondataerror:null};this.movieStarOptions={bufferTime:3,serverURL:null,onconnect:null,duration:null};this.audioFormats={mp3:{type:['audio/mpeg; codecs\x3d"mp3"',"audio/mpeg","audio/mp3","audio/MPA","audio/mpa-robust"],required:!0},mp4:{related:["aac","m4a","m4b"],type:['audio/mp4; codecs\x3d"mp4a.40.2"',"audio/aac","audio/x-m4a","audio/MP4A-LATM","audio/mpeg4-generic"],required:!1},ogg:{type:["audio/ogg; codecs\x3dvorbis"],required:!1},opus:{type:["audio/ogg; codecs\x3dopus","audio/opus"],required:!1},
index ddbe342001e27af6302c1fcd47a701fa7556cccd..e7d15454259ee155a6ab339c9da54d858ea68bfe 100644 (file)
-/** @license\r
- *\r
- * SoundManager 2: JavaScript Sound for the Web\r
- * ----------------------------------------------\r
- * http://schillmania.com/projects/soundmanager2/\r
- *\r
- * Copyright (c) 2007, Scott Schiller. All rights reserved.\r
- * Code provided under the BSD License:\r
- * http://schillmania.com/projects/soundmanager2/license.txt\r
- *\r
- * V2.97a.20131201\r
- */\r
-\r
-/*global window, SM2_DEFER, sm2Debugger, console, document, navigator, setTimeout, setInterval, clearInterval, Audio, opera */\r
-/*jslint regexp: true, sloppy: true, white: true, nomen: true, plusplus: true, todo: true */\r
-\r
-/**\r
- * About this file\r
- * -------------------------------------------------------------------------------------\r
- * This is the fully-commented source version of the SoundManager 2 API,\r
- * recommended for use during development and testing.\r
- *\r
- * See soundmanager2-nodebug-jsmin.js for an optimized build (~11KB with gzip.)\r
- * http://schillmania.com/projects/soundmanager2/doc/getstarted/#basic-inclusion\r
- * Alternately, serve this file with gzip for 75% compression savings (~30KB over HTTP.)\r
- *\r
- * You may notice <d> and </d> comments in this source; these are delimiters for\r
- * debug blocks which are removed in the -nodebug builds, further optimizing code size.\r
- *\r
- * Also, as you may note: Whoa, reliable cross-platform/device audio support is hard! ;)\r
- */\r
-\r
-(function(window, _undefined) {\r
-\r
-"use strict";\r
-\r
-var soundManager = null;\r
-\r
-/**\r
- * The SoundManager constructor.\r
- *\r
- * @constructor\r
- * @param {string} smURL Optional: Path to SWF files\r
- * @param {string} smID Optional: The ID to use for the SWF container element\r
- * @this {SoundManager}\r
- * @return {SoundManager} The new SoundManager instance\r
- */\r
-\r
-function SoundManager(smURL, smID) {\r
-\r
-  /**\r
-   * soundManager configuration options list\r
-   * defines top-level configuration properties to be applied to the soundManager instance (eg. soundManager.flashVersion)\r
-   * to set these properties, use the setup() method - eg., soundManager.setup({url: '/swf/', flashVersion: 9})\r
-   */\r
-\r
-  this.setupOptions = {\r
-\r
-    'url': (smURL || null),             // path (directory) where SoundManager 2 SWFs exist, eg., /path/to/swfs/\r
-    'flashVersion': 8,                  // flash build to use (8 or 9.) Some API features require 9.\r
-    'debugMode': true,                  // enable debugging output (console.log() with HTML fallback)\r
-    'debugFlash': false,                // enable debugging output inside SWF, troubleshoot Flash/browser issues\r
-    'useConsole': true,                 // use console.log() if available (otherwise, writes to #soundmanager-debug element)\r
-    'consoleOnly': true,                // if console is being used, do not create/write to #soundmanager-debug\r
-    'waitForWindowLoad': false,         // force SM2 to wait for window.onload() before trying to call soundManager.onload()\r
-    'bgColor': '#ffffff',               // SWF background color. N/A when wmode = 'transparent'\r
-    'useHighPerformance': false,        // position:fixed flash movie can help increase js/flash speed, minimize lag\r
-    'flashPollingInterval': null,       // msec affecting whileplaying/loading callback frequency. If null, default of 50 msec is used.\r
-    'html5PollingInterval': null,       // msec affecting whileplaying() for HTML5 audio, excluding mobile devices. If null, native HTML5 update events are used.\r
-    'flashLoadTimeout': 1000,           // msec to wait for flash movie to load before failing (0 = infinity)\r
-    'wmode': null,                      // flash rendering mode - null, 'transparent', or 'opaque' (last two allow z-index to work)\r
-    'allowScriptAccess': 'always',      // for scripting the SWF (object/embed property), 'always' or 'sameDomain'\r
-    'useFlashBlock': false,             // *requires flashblock.css, see demos* - allow recovery from flash blockers. Wait indefinitely and apply timeout CSS to SWF, if applicable.\r
-    'useHTML5Audio': true,              // use HTML5 Audio() where API is supported (most Safari, Chrome versions), Firefox (no MP3/MP4.) Ideally, transparent vs. Flash API where possible.\r
-    'html5Test': /^(probably|maybe)$/i, // HTML5 Audio() format support test. Use /^probably$/i; if you want to be more conservative.\r
-    'preferFlash': false,               // overrides useHTML5audio, will use Flash for MP3/MP4/AAC if present. Potential option if HTML5 playback with these formats is quirky.\r
-    'noSWFCache': false,                // if true, appends ?ts={date} to break aggressive SWF caching.\r
-    'idPrefix': 'sound'                 // if an id is not provided to createSound(), this prefix is used for generated IDs - 'sound0', 'sound1' etc.\r
-\r
-  };\r
-\r
-  this.defaultOptions = {\r
-\r
-    /**\r
-     * the default configuration for sound objects made with createSound() and related methods\r
-     * eg., volume, auto-load behaviour and so forth\r
-     */\r
-\r
-    'autoLoad': false,        // enable automatic loading (otherwise .load() will be called on demand with .play(), the latter being nicer on bandwidth - if you want to .load yourself, you also can)\r
-    'autoPlay': false,        // enable playing of file as soon as possible (much faster if "stream" is true)\r
-    'from': null,             // position to start playback within a sound (msec), default = beginning\r
-    'loops': 1,               // how many times to repeat the sound (position will wrap around to 0, setPosition() will break out of loop when >0)\r
-    'onid3': null,            // callback function for "ID3 data is added/available"\r
-    'onload': null,           // callback function for "load finished"\r
-    'whileloading': null,     // callback function for "download progress update" (X of Y bytes received)\r
-    'onplay': null,           // callback for "play" start\r
-    'onpause': null,          // callback for "pause"\r
-    'onresume': null,         // callback for "resume" (pause toggle)\r
-    'whileplaying': null,     // callback during play (position update)\r
-    'onposition': null,       // object containing times and function callbacks for positions of interest\r
-    'onstop': null,           // callback for "user stop"\r
-    'onfailure': null,        // callback function for when playing fails\r
-    'onfinish': null,         // callback function for "sound finished playing"\r
-    'multiShot': true,        // let sounds "restart" or layer on top of each other when played multiple times, rather than one-shot/one at a time\r
-    'multiShotEvents': false, // fire multiple sound events (currently onfinish() only) when multiShot is enabled\r
-    'position': null,         // offset (milliseconds) to seek to within loaded sound data.\r
-    'pan': 0,                 // "pan" settings, left-to-right, -100 to 100\r
-    'stream': true,           // allows playing before entire file has loaded (recommended)\r
-    'to': null,               // position to end playback within a sound (msec), default = end\r
-    'type': null,             // MIME-like hint for file pattern / canPlay() tests, eg. audio/mp3\r
-    'usePolicyFile': false,   // enable crossdomain.xml request for audio on remote domains (for ID3/waveform access)\r
-    'volume': 100             // self-explanatory. 0-100, the latter being the max.\r
-\r
-  };\r
-\r
-  this.flash9Options = {\r
-\r
-    /**\r
-     * flash 9-only options,\r
-     * merged into defaultOptions if flash 9 is being used\r
-     */\r
-\r
-    'isMovieStar': null,      // "MovieStar" MPEG4 audio mode. Null (default) = auto detect MP4, AAC etc. based on URL. true = force on, ignore URL\r
-    'usePeakData': false,     // enable left/right channel peak (level) data\r
-    'useWaveformData': false, // enable sound spectrum (raw waveform data) - NOTE: May increase CPU load.\r
-    'useEQData': false,       // enable sound EQ (frequency spectrum data) - NOTE: May increase CPU load.\r
-    'onbufferchange': null,   // callback for "isBuffering" property change\r
-    'ondataerror': null       // callback for waveform/eq data access error (flash playing audio in other tabs/domains)\r
-\r
-  };\r
-\r
-  this.movieStarOptions = {\r
-\r
-    /**\r
-     * flash 9.0r115+ MPEG4 audio options,\r
-     * merged into defaultOptions if flash 9+movieStar mode is enabled\r
-     */\r
-\r
-    'bufferTime': 3,          // seconds of data to buffer before playback begins (null = flash default of 0.1 seconds - if AAC playback is gappy, try increasing.)\r
-    'serverURL': null,        // rtmp: FMS or FMIS server to connect to, required when requesting media via RTMP or one of its variants\r
-    'onconnect': null,        // rtmp: callback for connection to flash media server\r
-    'duration': null          // rtmp: song duration (msec)\r
-\r
-  };\r
-\r
-  this.audioFormats = {\r
-\r
-    /**\r
-     * determines HTML5 support + flash requirements.\r
-     * if no support (via flash and/or HTML5) for a "required" format, SM2 will fail to start.\r
-     * flash fallback is used for MP3 or MP4 if HTML5 can't play it (or if preferFlash = true)\r
-     */\r
-\r
-    'mp3': {\r
-      'type': ['audio/mpeg; codecs="mp3"', 'audio/mpeg', 'audio/mp3', 'audio/MPA', 'audio/mpa-robust'],\r
-      'required': true\r
-    },\r
-\r
-    'mp4': {\r
-      'related': ['aac','m4a','m4b'], // additional formats under the MP4 container\r
-      'type': ['audio/mp4; codecs="mp4a.40.2"', 'audio/aac', 'audio/x-m4a', 'audio/MP4A-LATM', 'audio/mpeg4-generic'],\r
-      'required': false\r
-    },\r
-\r
-    'ogg': {\r
-      'type': ['audio/ogg; codecs=vorbis'],\r
-      'required': false\r
-    },\r
-\r
-    'opus': {\r
-      'type': ['audio/ogg; codecs=opus', 'audio/opus'],\r
-      'required': false\r
-    },\r
-\r
-    'wav': {\r
-      'type': ['audio/wav; codecs="1"', 'audio/wav', 'audio/wave', 'audio/x-wav'],\r
-      'required': false\r
-    }\r
-\r
-  };\r
-\r
-  // HTML attributes (id + class names) for the SWF container\r
-\r
-  this.movieID = 'sm2-container';\r
-  this.id = (smID || 'sm2movie');\r
-\r
-  this.debugID = 'soundmanager-debug';\r
-  this.debugURLParam = /([#?&])debug=1/i;\r
-\r
-  // dynamic attributes\r
-\r
-  this.versionNumber = 'V2.97a.20131201';\r
-  this.version = null;\r
-  this.movieURL = null;\r
-  this.altURL = null;\r
-  this.swfLoaded = false;\r
-  this.enabled = false;\r
-  this.oMC = null;\r
-  this.sounds = {};\r
-  this.soundIDs = [];\r
-  this.muted = false;\r
-  this.didFlashBlock = false;\r
-  this.filePattern = null;\r
-\r
-  this.filePatterns = {\r
-\r
-    'flash8': /\.mp3(\?.*)?$/i,\r
-    'flash9': /\.mp3(\?.*)?$/i\r
-\r
-  };\r
-\r
-  // support indicators, set at init\r
-\r
-  this.features = {\r
-\r
-    'buffering': false,\r
-    'peakData': false,\r
-    'waveformData': false,\r
-    'eqData': false,\r
-    'movieStar': false\r
-\r
-  };\r
-\r
-  // flash sandbox info, used primarily in troubleshooting\r
-\r
-  this.sandbox = {\r
-\r
-    // <d>\r
-    'type': null,\r
-    'types': {\r
-      'remote': 'remote (domain-based) rules',\r
-      'localWithFile': 'local with file access (no internet access)',\r
-      'localWithNetwork': 'local with network (internet access only, no local access)',\r
-      'localTrusted': 'local, trusted (local+internet access)'\r
-    },\r
-    'description': null,\r
-    'noRemote': null,\r
-    'noLocal': null\r
-    // </d>\r
-\r
-  };\r
-\r
-  /**\r
-   * format support (html5/flash)\r
-   * stores canPlayType() results based on audioFormats.\r
-   * eg. { mp3: boolean, mp4: boolean }\r
-   * treat as read-only.\r
-   */\r
-\r
-  this.html5 = {\r
-    'usingFlash': null // set if/when flash fallback is needed\r
-  };\r
-\r
-  // file type support hash\r
-  this.flash = {};\r
-\r
-  // determined at init time\r
-  this.html5Only = false;\r
-\r
-  // used for special cases (eg. iPad/iPhone/palm OS?)\r
-  this.ignoreFlash = false;\r
-\r
-  /**\r
-   * a few private internals (OK, a lot. :D)\r
-   */\r
-\r
-  var SMSound,\r
-  sm2 = this, globalHTML5Audio = null, flash = null, sm = 'soundManager', smc = sm + ': ', h5 = 'HTML5::', id, ua = navigator.userAgent, wl = window.location.href.toString(), doc = document, doNothing, setProperties, init, fV, on_queue = [], debugOpen = true, debugTS, didAppend = false, appendSuccess = false, didInit = false, disabled = false, windowLoaded = false, _wDS, wdCount = 0, initComplete, mixin, assign, extraOptions, addOnEvent, processOnEvents, initUserOnload, delayWaitForEI, waitForEI, rebootIntoHTML5, setVersionInfo, handleFocus, strings, initMovie, preInit, domContentLoaded, winOnLoad, didDCLoaded, getDocument, createMovie, catchError, setPolling, initDebug, debugLevels = ['log', 'info', 'warn', 'error'], defaultFlashVersion = 8, disableObject, failSafely, normalizeMovieURL, oRemoved = null, oRemovedHTML = null, str, flashBlockHandler, getSWFCSS, swfCSS, toggleDebug, loopFix, policyFix, complain, idCheck, waitingForEI = false, initPending = false, startTimer, stopTimer, timerExecute, h5TimerCount = 0, h5IntervalTimer = null, parseURL, messages = [],\r
-  canIgnoreFlash, needsFlash = null, featureCheck, html5OK, html5CanPlay, html5Ext, html5Unload, domContentLoadedIE, testHTML5, event, slice = Array.prototype.slice, useGlobalHTML5Audio = false, lastGlobalHTML5URL, hasFlash, detectFlash, badSafariFix, html5_events, showSupport, flushMessages, wrapCallback, idCounter = 0,\r
-  is_iDevice = ua.match(/(ipad|iphone|ipod)/i), isAndroid = ua.match(/android/i), isIE = ua.match(/msie/i), isWebkit = ua.match(/webkit/i), isSafari = (ua.match(/safari/i) && !ua.match(/chrome/i)), isOpera = (ua.match(/opera/i)),\r
-  mobileHTML5 = (ua.match(/(mobile|pre\/|xoom)/i) || is_iDevice || isAndroid),\r
-  isBadSafari = (!wl.match(/usehtml5audio/i) && !wl.match(/sm2\-ignorebadua/i) && isSafari && !ua.match(/silk/i) && ua.match(/OS X 10_6_([3-7])/i)), // Safari 4 and 5 (excluding Kindle Fire, "Silk") occasionally fail to load/play HTML5 audio on Snow Leopard 10.6.3 through 10.6.7 due to bug(s) in QuickTime X and/or other underlying frameworks. :/ Confirmed bug. https://bugs.webkit.org/show_bug.cgi?id=32159\r
-  hasConsole = (window.console !== _undefined && console.log !== _undefined), isFocused = (doc.hasFocus !== _undefined?doc.hasFocus():null), tryInitOnFocus = (isSafari && (doc.hasFocus === _undefined || !doc.hasFocus())), okToDisable = !tryInitOnFocus, flashMIME = /(mp3|mp4|mpa|m4a|m4b)/i, msecScale = 1000,\r
-  emptyURL = 'about:blank', // safe URL to unload, or load nothing from (flash 8 + most HTML5 UAs)\r
-  emptyWAV = 'data:audio/wave;base64,/UklGRiYAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQIAAAD//w==', // tiny WAV for HTML5 unloading\r
-  overHTTP = (doc.location?doc.location.protocol.match(/http/i):null),\r
-  http = (!overHTTP ? 'http:/'+'/' : ''),\r
-  // mp3, mp4, aac etc.\r
-  netStreamMimeTypes = /^\s*audio\/(?:x-)?(?:mpeg4|aac|flv|mov|mp4||m4v|m4a|m4b|mp4v|3gp|3g2)\s*(?:$|;)/i,\r
-  // Flash v9.0r115+ "moviestar" formats\r
-  netStreamTypes = ['mpeg4', 'aac', 'flv', 'mov', 'mp4', 'm4v', 'f4v', 'm4a', 'm4b', 'mp4v', '3gp', '3g2'],\r
-  netStreamPattern = new RegExp('\\.(' + netStreamTypes.join('|') + ')(\\?.*)?$', 'i');\r
-\r
-  this.mimePattern = /^\s*audio\/(?:x-)?(?:mp(?:eg|3))\s*(?:$|;)/i; // default mp3 set\r
-\r
-  // use altURL if not "online"\r
-  this.useAltURL = !overHTTP;\r
-\r
-  swfCSS = {\r
-\r
-    'swfBox': 'sm2-object-box',\r
-    'swfDefault': 'movieContainer',\r
-    'swfError': 'swf_error', // SWF loaded, but SM2 couldn't start (other error)\r
-    'swfTimedout': 'swf_timedout',\r
-    'swfLoaded': 'swf_loaded',\r
-    'swfUnblocked': 'swf_unblocked', // or loaded OK\r
-    'sm2Debug': 'sm2_debug',\r
-    'highPerf': 'high_performance',\r
-    'flashDebug': 'flash_debug'\r
-\r
-  };\r
-\r
-  /**\r
-   * basic HTML5 Audio() support test\r
-   * try...catch because of IE 9 "not implemented" nonsense\r
-   * https://github.com/Modernizr/Modernizr/issues/224\r
-   */\r
-\r
-  this.hasHTML5 = (function() {\r
-    try {\r
-      // new Audio(null) for stupid Opera 9.64 case, which throws not_enough_arguments exception otherwise.\r
-      return (Audio !== _undefined && (isOpera && opera !== _undefined && opera.version() < 10 ? new Audio(null) : new Audio()).canPlayType !== _undefined);\r
-    } catch(e) {\r
-      return false;\r
-    }\r
-  }());\r
-\r
-  /**\r
-   * Public SoundManager API\r
-   * -----------------------\r
-   */\r
-\r
-  /**\r
-   * Configures top-level soundManager properties.\r
-   *\r
-   * @param {object} options Option parameters, eg. { flashVersion: 9, url: '/path/to/swfs/' }\r
-   * onready and ontimeout are also accepted parameters. call soundManager.setup() to see the full list.\r
-   */\r
-\r
-  this.setup = function(options) {\r
-\r
-    var noURL = (!sm2.url);\r
-\r
-    // warn if flash options have already been applied\r
-\r
-    if (options !== _undefined && didInit && needsFlash && sm2.ok() && (options.flashVersion !== _undefined || options.url !== _undefined || options.html5Test !== _undefined)) {\r
-      complain(str('setupLate'));\r
-    }\r
-\r
-    // TODO: defer: true?\r
-\r
-    assign(options);\r
-\r
-    // special case 1: "Late setup". SM2 loaded normally, but user didn't assign flash URL eg., setup({url:...}) before SM2 init. Treat as delayed init.\r
-\r
-    if (options) {\r
-\r
-      if (noURL && didDCLoaded && options.url !== _undefined) {\r
-        sm2.beginDelayedInit();\r
-      }\r
-\r
-      // special case 2: If lazy-loading SM2 (DOMContentLoaded has already happened) and user calls setup() with url: parameter, try to init ASAP.\r
-\r
-      if (!didDCLoaded && options.url !== _undefined && doc.readyState === 'complete') {\r
-        setTimeout(domContentLoaded, 1);\r
-      }\r
-\r
-    }\r
-\r
-    return sm2;\r
-\r
-  };\r
-\r
-  this.ok = function() {\r
-\r
-    return (needsFlash ? (didInit && !disabled) : (sm2.useHTML5Audio && sm2.hasHTML5));\r
-\r
-  };\r
-\r
-  this.supported = this.ok; // legacy\r
-\r
-  this.getMovie = function(smID) {\r
-\r
-    // safety net: some old browsers differ on SWF references, possibly related to ExternalInterface / flash version\r
-    return id(smID) || doc[smID] || window[smID];\r
-\r
-  };\r
-\r
-  /**\r
-   * Creates a SMSound sound object instance.\r
-   *\r
-   * @param {object} oOptions Sound options (at minimum, id and url parameters are required.)\r
-   * @return {object} SMSound The new SMSound object.\r
-   */\r
-\r
-  this.createSound = function(oOptions, _url) {\r
-\r
-    var cs, cs_string, options, oSound = null;\r
-\r
-    // <d>\r
-    cs = sm + '.createSound(): ';\r
-    cs_string = cs + str(!didInit?'notReady':'notOK');\r
-    // </d>\r
-\r
-    if (!didInit || !sm2.ok()) {\r
-      complain(cs_string);\r
-      return false;\r
-    }\r
-\r
-    if (_url !== _undefined) {\r
-      // function overloading in JS! :) ..assume simple createSound(id, url) use case\r
-      oOptions = {\r
-        'id': oOptions,\r
-        'url': _url\r
-      };\r
-    }\r
-\r
-    // inherit from defaultOptions\r
-    options = mixin(oOptions);\r
-\r
-    options.url = parseURL(options.url);\r
-\r
-    // generate an id, if needed.\r
-    if (options.id === undefined) {\r
-      options.id = sm2.setupOptions.idPrefix + (idCounter++);\r
-    }\r
-\r
-    // <d>\r
-    if (options.id.toString().charAt(0).match(/^[0-9]$/)) {\r
-      sm2._wD(cs + str('badID', options.id), 2);\r
-    }\r
-\r
-    sm2._wD(cs + options.id + (options.url ? ' (' + options.url + ')' : ''), 1);\r
-    // </d>\r
-\r
-    if (idCheck(options.id, true)) {\r
-      sm2._wD(cs + options.id + ' exists', 1);\r
-      return sm2.sounds[options.id];\r
-    }\r
-\r
-    function make() {\r
-\r
-      options = loopFix(options);\r
-      sm2.sounds[options.id] = new SMSound(options);\r
-      sm2.soundIDs.push(options.id);\r
-      return sm2.sounds[options.id];\r
-\r
-    }\r
-\r
-    if (html5OK(options)) {\r
-\r
-      oSound = make();\r
-      sm2._wD(options.id + ': Using HTML5');\r
-      oSound._setup_html5(options);\r
-\r
-    } else {\r
-\r
-      if (sm2.html5Only) {\r
-        sm2._wD(options.id + ': No HTML5 support for this sound, and no Flash. Exiting.');\r
-        return make();\r
-      }\r
-\r
-      // TODO: Move HTML5/flash checks into generic URL parsing/handling function.\r
-\r
-      if (sm2.html5.usingFlash && options.url && options.url.match(/data\:/i)) {\r
-        // data: URIs not supported by Flash, either.\r
-        sm2._wD(options.id + ': data: URIs not supported via Flash. Exiting.');\r
-        return make();\r
-      }\r
-\r
-      if (fV > 8) {\r
-        if (options.isMovieStar === null) {\r
-          // attempt to detect MPEG-4 formats\r
-          options.isMovieStar = !!(options.serverURL || (options.type ? options.type.match(netStreamMimeTypes) : false) || (options.url && options.url.match(netStreamPattern)));\r
-        }\r
-        // <d>\r
-        if (options.isMovieStar) {\r
-          sm2._wD(cs + 'using MovieStar handling');\r
-          if (options.loops > 1) {\r
-            _wDS('noNSLoop');\r
-          }\r
-        }\r
-        // </d>\r
-      }\r
-\r
-      options = policyFix(options, cs);\r
-      oSound = make();\r
-\r
-      if (fV === 8) {\r
-        flash._createSound(options.id, options.loops||1, options.usePolicyFile);\r
-      } else {\r
-        flash._createSound(options.id, options.url, options.usePeakData, options.useWaveformData, options.useEQData, options.isMovieStar, (options.isMovieStar?options.bufferTime:false), options.loops||1, options.serverURL, options.duration||null, options.autoPlay, true, options.autoLoad, options.usePolicyFile);\r
-        if (!options.serverURL) {\r
-          // We are connected immediately\r
-          oSound.connected = true;\r
-          if (options.onconnect) {\r
-            options.onconnect.apply(oSound);\r
-          }\r
-        }\r
-      }\r
-\r
-      if (!options.serverURL && (options.autoLoad || options.autoPlay)) {\r
-        // call load for non-rtmp streams\r
-        oSound.load(options);\r
-      }\r
-\r
-    }\r
-\r
-    // rtmp will play in onconnect\r
-    if (!options.serverURL && options.autoPlay) {\r
-      oSound.play();\r
-    }\r
-\r
-    return oSound;\r
-\r
-  };\r
-\r
-  /**\r
-   * Destroys a SMSound sound object instance.\r
-   *\r
-   * @param {string} sID The ID of the sound to destroy\r
-   */\r
-\r
-  this.destroySound = function(sID, _bFromSound) {\r
-\r
-    // explicitly destroy a sound before normal page unload, etc.\r
-\r
-    if (!idCheck(sID)) {\r
-      return false;\r
-    }\r
-\r
-    var oS = sm2.sounds[sID], i;\r
-\r
-    // Disable all callbacks while the sound is being destroyed\r
-    oS._iO = {};\r
-\r
-    oS.stop();\r
-    oS.unload();\r
-\r
-    for (i = 0; i < sm2.soundIDs.length; i++) {\r
-      if (sm2.soundIDs[i] === sID) {\r
-        sm2.soundIDs.splice(i, 1);\r
-        break;\r
-      }\r
-    }\r
-\r
-    if (!_bFromSound) {\r
-      // ignore if being called from SMSound instance\r
-      oS.destruct(true);\r
-    }\r
-\r
-    oS = null;\r
-    delete sm2.sounds[sID];\r
-\r
-    return true;\r
-\r
-  };\r
-\r
-  /**\r
-   * Calls the load() method of a SMSound object by ID.\r
-   *\r
-   * @param {string} sID The ID of the sound\r
-   * @param {object} oOptions Optional: Sound options\r
-   */\r
-\r
-  this.load = function(sID, oOptions) {\r
-\r
-    if (!idCheck(sID)) {\r
-      return false;\r
-    }\r
-    return sm2.sounds[sID].load(oOptions);\r
-\r
-  };\r
-\r
-  /**\r
-   * Calls the unload() method of a SMSound object by ID.\r
-   *\r
-   * @param {string} sID The ID of the sound\r
-   */\r
-\r
-  this.unload = function(sID) {\r
-\r
-    if (!idCheck(sID)) {\r
-      return false;\r
-    }\r
-    return sm2.sounds[sID].unload();\r
-\r
-  };\r
-\r
-  /**\r
-   * Calls the onPosition() method of a SMSound object by ID.\r
-   *\r
-   * @param {string} sID The ID of the sound\r
-   * @param {number} nPosition The position to watch for\r
-   * @param {function} oMethod The relevant callback to fire\r
-   * @param {object} oScope Optional: The scope to apply the callback to\r
-   * @return {SMSound} The SMSound object\r
-   */\r
-\r
-  this.onPosition = function(sID, nPosition, oMethod, oScope) {\r
-\r
-    if (!idCheck(sID)) {\r
-      return false;\r
-    }\r
-    return sm2.sounds[sID].onposition(nPosition, oMethod, oScope);\r
-\r
-  };\r
-\r
-  // legacy/backwards-compability: lower-case method name\r
-  this.onposition = this.onPosition;\r
-\r
-  /**\r
-   * Calls the clearOnPosition() method of a SMSound object by ID.\r
-   *\r
-   * @param {string} sID The ID of the sound\r
-   * @param {number} nPosition The position to watch for\r
-   * @param {function} oMethod Optional: The relevant callback to fire\r
-   * @return {SMSound} The SMSound object\r
-   */\r
-\r
-  this.clearOnPosition = function(sID, nPosition, oMethod) {\r
-\r
-    if (!idCheck(sID)) {\r
-      return false;\r
-    }\r
-    return sm2.sounds[sID].clearOnPosition(nPosition, oMethod);\r
-\r
-  };\r
-\r
-  /**\r
-   * Calls the play() method of a SMSound object by ID.\r
-   *\r
-   * @param {string} sID The ID of the sound\r
-   * @param {object} oOptions Optional: Sound options\r
-   * @return {SMSound} The SMSound object\r
-   */\r
-\r
-  this.play = function(sID, oOptions) {\r
-\r
-    var result = null,\r
-        // legacy function-overloading use case: play('mySound', '/path/to/some.mp3');\r
-        overloaded = (oOptions && !(oOptions instanceof Object));\r
-\r
-    if (!didInit || !sm2.ok()) {\r
-      complain(sm + '.play(): ' + str(!didInit?'notReady':'notOK'));\r
-      return false;\r
-    }\r
-\r
-    if (!idCheck(sID, overloaded)) {\r
-\r
-      if (!overloaded) {\r
-        // no sound found for the given ID. Bail.\r
-        return false;\r
-      }\r
-\r
-      if (overloaded) {\r
-        oOptions = {\r
-          url: oOptions\r
-        };\r
-      }\r
-\r
-      if (oOptions && oOptions.url) {\r
-        // overloading use case, create+play: .play('someID', {url:'/path/to.mp3'});\r
-        sm2._wD(sm + '.play(): Attempting to create "' + sID + '"', 1);\r
-        oOptions.id = sID;\r
-        result = sm2.createSound(oOptions).play();\r
-      }\r
-\r
-    } else if (overloaded) {\r
-\r
-      // existing sound object case\r
-      oOptions = {\r
-        url: oOptions\r
-      };\r
-\r
-    }\r
-\r
-    if (result === null) {\r
-      // default case\r
-      result = sm2.sounds[sID].play(oOptions);\r
-    }\r
-\r
-    return result;\r
-\r
-  };\r
-\r
-  this.start = this.play; // just for convenience\r
-\r
-  /**\r
-   * Calls the setPosition() method of a SMSound object by ID.\r
-   *\r
-   * @param {string} sID The ID of the sound\r
-   * @param {number} nMsecOffset Position (milliseconds)\r
-   * @return {SMSound} The SMSound object\r
-   */\r
-\r
-  this.setPosition = function(sID, nMsecOffset) {\r
-\r
-    if (!idCheck(sID)) {\r
-      return false;\r
-    }\r
-    return sm2.sounds[sID].setPosition(nMsecOffset);\r
-\r
-  };\r
-\r
-  /**\r
-   * Calls the stop() method of a SMSound object by ID.\r
-   *\r
-   * @param {string} sID The ID of the sound\r
-   * @return {SMSound} The SMSound object\r
-   */\r
-\r
-  this.stop = function(sID) {\r
-\r
-    if (!idCheck(sID)) {\r
-      return false;\r
-    }\r
-\r
-    sm2._wD(sm + '.stop(' + sID + ')', 1);\r
-    return sm2.sounds[sID].stop();\r
-\r
-  };\r
-\r
-  /**\r
-   * Stops all currently-playing sounds.\r
-   */\r
-\r
-  this.stopAll = function() {\r
-\r
-    var oSound;\r
-    sm2._wD(sm + '.stopAll()', 1);\r
-\r
-    for (oSound in sm2.sounds) {\r
-      if (sm2.sounds.hasOwnProperty(oSound)) {\r
-        // apply only to sound objects\r
-        sm2.sounds[oSound].stop();\r
-      }\r
-    }\r
-\r
-  };\r
-\r
-  /**\r
-   * Calls the pause() method of a SMSound object by ID.\r
-   *\r
-   * @param {string} sID The ID of the sound\r
-   * @return {SMSound} The SMSound object\r
-   */\r
-\r
-  this.pause = function(sID) {\r
-\r
-    if (!idCheck(sID)) {\r
-      return false;\r
-    }\r
-    return sm2.sounds[sID].pause();\r
-\r
-  };\r
-\r
-  /**\r
-   * Pauses all currently-playing sounds.\r
-   */\r
-\r
-  this.pauseAll = function() {\r
-\r
-    var i;\r
-    for (i = sm2.soundIDs.length-1; i >= 0; i--) {\r
-      sm2.sounds[sm2.soundIDs[i]].pause();\r
-    }\r
-\r
-  };\r
-\r
-  /**\r
-   * Calls the resume() method of a SMSound object by ID.\r
-   *\r
-   * @param {string} sID The ID of the sound\r
-   * @return {SMSound} The SMSound object\r
-   */\r
-\r
-  this.resume = function(sID) {\r
-\r
-    if (!idCheck(sID)) {\r
-      return false;\r
-    }\r
-    return sm2.sounds[sID].resume();\r
-\r
-  };\r
-\r
-  /**\r
-   * Resumes all currently-paused sounds.\r
-   */\r
-\r
-  this.resumeAll = function() {\r
-\r
-    var i;\r
-    for (i = sm2.soundIDs.length-1; i >= 0; i--) {\r
-      sm2.sounds[sm2.soundIDs[i]].resume();\r
-    }\r
-\r
-  };\r
-\r
-  /**\r
-   * Calls the togglePause() method of a SMSound object by ID.\r
-   *\r
-   * @param {string} sID The ID of the sound\r
-   * @return {SMSound} The SMSound object\r
-   */\r
-\r
-  this.togglePause = function(sID) {\r
-\r
-    if (!idCheck(sID)) {\r
-      return false;\r
-    }\r
-    return sm2.sounds[sID].togglePause();\r
-\r
-  };\r
-\r
-  /**\r
-   * Calls the setPan() method of a SMSound object by ID.\r
-   *\r
-   * @param {string} sID The ID of the sound\r
-   * @param {number} nPan The pan value (-100 to 100)\r
-   * @return {SMSound} The SMSound object\r
-   */\r
-\r
-  this.setPan = function(sID, nPan) {\r
-\r
-    if (!idCheck(sID)) {\r
-      return false;\r
-    }\r
-    return sm2.sounds[sID].setPan(nPan);\r
-\r
-  };\r
-\r
-  /**\r
-   * Calls the setVolume() method of a SMSound object by ID.\r
-   *\r
-   * @param {string} sID The ID of the sound\r
-   * @param {number} nVol The volume value (0 to 100)\r
-   * @return {SMSound} The SMSound object\r
-   */\r
-\r
-  this.setVolume = function(sID, nVol) {\r
-\r
-    if (!idCheck(sID)) {\r
-      return false;\r
-    }\r
-    return sm2.sounds[sID].setVolume(nVol);\r
-\r
-  };\r
-\r
-  /**\r
-   * Calls the mute() method of either a single SMSound object by ID, or all sound objects.\r
-   *\r
-   * @param {string} sID Optional: The ID of the sound (if omitted, all sounds will be used.)\r
-   */\r
-\r
-  this.mute = function(sID) {\r
-\r
-    var i = 0;\r
-\r
-    if (sID instanceof String) {\r
-      sID = null;\r
-    }\r
-\r
-    if (!sID) {\r
-\r
-      sm2._wD(sm + '.mute(): Muting all sounds');\r
-      for (i = sm2.soundIDs.length-1; i >= 0; i--) {\r
-        sm2.sounds[sm2.soundIDs[i]].mute();\r
-      }\r
-      sm2.muted = true;\r
-\r
-    } else {\r
-\r
-      if (!idCheck(sID)) {\r
-        return false;\r
-      }\r
-      sm2._wD(sm + '.mute(): Muting "' + sID + '"');\r
-      return sm2.sounds[sID].mute();\r
-\r
-    }\r
-\r
-    return true;\r
-\r
-  };\r
-\r
-  /**\r
-   * Mutes all sounds.\r
-   */\r
-\r
-  this.muteAll = function() {\r
-\r
-    sm2.mute();\r
-\r
-  };\r
-\r
-  /**\r
-   * Calls the unmute() method of either a single SMSound object by ID, or all sound objects.\r
-   *\r
-   * @param {string} sID Optional: The ID of the sound (if omitted, all sounds will be used.)\r
-   */\r
-\r
-  this.unmute = function(sID) {\r
-\r
-    var i;\r
-\r
-    if (sID instanceof String) {\r
-      sID = null;\r
-    }\r
-\r
-    if (!sID) {\r
-\r
-      sm2._wD(sm + '.unmute(): Unmuting all sounds');\r
-      for (i = sm2.soundIDs.length-1; i >= 0; i--) {\r
-        sm2.sounds[sm2.soundIDs[i]].unmute();\r
-      }\r
-      sm2.muted = false;\r
-\r
-    } else {\r
-\r
-      if (!idCheck(sID)) {\r
-        return false;\r
-      }\r
-      sm2._wD(sm + '.unmute(): Unmuting "' + sID + '"');\r
-      return sm2.sounds[sID].unmute();\r
-\r
-    }\r
-\r
-    return true;\r
-\r
-  };\r
-\r
-  /**\r
-   * Unmutes all sounds.\r
-   */\r
-\r
-  this.unmuteAll = function() {\r
-\r
-    sm2.unmute();\r
-\r
-  };\r
-\r
-  /**\r
-   * Calls the toggleMute() method of a SMSound object by ID.\r
-   *\r
-   * @param {string} sID The ID of the sound\r
-   * @return {SMSound} The SMSound object\r
-   */\r
-\r
-  this.toggleMute = function(sID) {\r
-\r
-    if (!idCheck(sID)) {\r
-      return false;\r
-    }\r
-    return sm2.sounds[sID].toggleMute();\r
-\r
-  };\r
-\r
-  /**\r
-   * Retrieves the memory used by the flash plugin.\r
-   *\r
-   * @return {number} The amount of memory in use\r
-   */\r
-\r
-  this.getMemoryUse = function() {\r
-\r
-    // flash-only\r
-    var ram = 0;\r
-\r
-    if (flash && fV !== 8) {\r
-      ram = parseInt(flash._getMemoryUse(), 10);\r
-    }\r
-\r
-    return ram;\r
-\r
-  };\r
-\r
-  /**\r
-   * Undocumented: NOPs soundManager and all SMSound objects.\r
-   */\r
-\r
-  this.disable = function(bNoDisable) {\r
-\r
-    // destroy all functions\r
-    var i;\r
-\r
-    if (bNoDisable === _undefined) {\r
-      bNoDisable = false;\r
-    }\r
-\r
-    if (disabled) {\r
-      return false;\r
-    }\r
-\r
-    disabled = true;\r
-    _wDS('shutdown', 1);\r
-\r
-    for (i = sm2.soundIDs.length-1; i >= 0; i--) {\r
-      disableObject(sm2.sounds[sm2.soundIDs[i]]);\r
-    }\r
-\r
-    // fire "complete", despite fail\r
-    initComplete(bNoDisable);\r
-    event.remove(window, 'load', initUserOnload);\r
-\r
-    return true;\r
-\r
-  };\r
-\r
-  /**\r
-   * Determines playability of a MIME type, eg. 'audio/mp3'.\r
-   */\r
-\r
-  this.canPlayMIME = function(sMIME) {\r
-\r
-    var result;\r
-\r
-    if (sm2.hasHTML5) {\r
-      result = html5CanPlay({type:sMIME});\r
-    }\r
-\r
-    if (!result && needsFlash) {\r
-      // if flash 9, test netStream (movieStar) types as well.\r
-      result = (sMIME && sm2.ok() ? !!((fV > 8 ? sMIME.match(netStreamMimeTypes) : null) || sMIME.match(sm2.mimePattern)) : null); // TODO: make less "weird" (per JSLint)\r
-    }\r
-\r
-    return result;\r
-\r
-  };\r
-\r
-  /**\r
-   * Determines playability of a URL based on audio support.\r
-   *\r
-   * @param {string} sURL The URL to test\r
-   * @return {boolean} URL playability\r
-   */\r
-\r
-  this.canPlayURL = function(sURL) {\r
-\r
-    var result;\r
-\r
-    if (sm2.hasHTML5) {\r
-      result = html5CanPlay({url: sURL});\r
-    }\r
-\r
-    if (!result && needsFlash) {\r
-      result = (sURL && sm2.ok() ? !!(sURL.match(sm2.filePattern)) : null);\r
-    }\r
-\r
-    return result;\r
-\r
-  };\r
-\r
-  /**\r
-   * Determines playability of an HTML DOM &lt;a&gt; object (or similar object literal) based on audio support.\r
-   *\r
-   * @param {object} oLink an HTML DOM &lt;a&gt; object or object literal including href and/or type attributes\r
-   * @return {boolean} URL playability\r
-   */\r
-\r
-  this.canPlayLink = function(oLink) {\r
-\r
-    if (oLink.type !== _undefined && oLink.type) {\r
-      if (sm2.canPlayMIME(oLink.type)) {\r
-        return true;\r
-      }\r
-    }\r
-\r
-    return sm2.canPlayURL(oLink.href);\r
-\r
-  };\r
-\r
-  /**\r
-   * Retrieves a SMSound object by ID.\r
-   *\r
-   * @param {string} sID The ID of the sound\r
-   * @return {SMSound} The SMSound object\r
-   */\r
-\r
-  this.getSoundById = function(sID, _suppressDebug) {\r
-\r
-    if (!sID) {\r
-      return null;\r
-    }\r
-\r
-    var result = sm2.sounds[sID];\r
-\r
-    // <d>\r
-    if (!result && !_suppressDebug) {\r
-      sm2._wD(sm + '.getSoundById(): Sound "' + sID + '" not found.', 2);\r
-    }\r
-    // </d>\r
-\r
-    return result;\r
-\r
-  };\r
-\r
-  /**\r
-   * Queues a callback for execution when SoundManager has successfully initialized.\r
-   *\r
-   * @param {function} oMethod The callback method to fire\r
-   * @param {object} oScope Optional: The scope to apply to the callback\r
-   */\r
-\r
-  this.onready = function(oMethod, oScope) {\r
-\r
-    var sType = 'onready',\r
-        result = false;\r
-\r
-    if (typeof oMethod === 'function') {\r
-\r
-      // <d>\r
-      if (didInit) {\r
-        sm2._wD(str('queue', sType));\r
-      }\r
-      // </d>\r
-\r
-      if (!oScope) {\r
-        oScope = window;\r
-      }\r
-\r
-      addOnEvent(sType, oMethod, oScope);\r
-      processOnEvents();\r
-\r
-      result = true;\r
-\r
-    } else {\r
-\r
-      throw str('needFunction', sType);\r
-\r
-    }\r
-\r
-    return result;\r
-\r
-  };\r
-\r
-  /**\r
-   * Queues a callback for execution when SoundManager has failed to initialize.\r
-   *\r
-   * @param {function} oMethod The callback method to fire\r
-   * @param {object} oScope Optional: The scope to apply to the callback\r
-   */\r
-\r
-  this.ontimeout = function(oMethod, oScope) {\r
-\r
-    var sType = 'ontimeout',\r
-        result = false;\r
-\r
-    if (typeof oMethod === 'function') {\r
-\r
-      // <d>\r
-      if (didInit) {\r
-        sm2._wD(str('queue', sType));\r
-      }\r
-      // </d>\r
-\r
-      if (!oScope) {\r
-        oScope = window;\r
-      }\r
-\r
-      addOnEvent(sType, oMethod, oScope);\r
-      processOnEvents({type:sType});\r
-\r
-      result = true;\r
-\r
-    } else {\r
-\r
-      throw str('needFunction', sType);\r
-\r
-    }\r
-\r
-    return result;\r
-\r
-  };\r
-\r
-  /**\r
-   * Writes console.log()-style debug output to a console or in-browser element.\r
-   * Applies when debugMode = true\r
-   *\r
-   * @param {string} sText The console message\r
-   * @param {object} nType Optional log level (number), or object. Number case: Log type/style where 0 = 'info', 1 = 'warn', 2 = 'error'. Object case: Object to be dumped.\r
-   */\r
-\r
-  this._writeDebug = function(sText, sTypeOrObject) {\r
-\r
-    // pseudo-private console.log()-style output\r
-    // <d>\r
-\r
-    var sDID = 'soundmanager-debug', o, oItem;\r
-\r
-    if (!sm2.debugMode) {\r
-      return false;\r
-    }\r
-\r
-    if (hasConsole && sm2.useConsole) {\r
-      if (sTypeOrObject && typeof sTypeOrObject === 'object') {\r
-        // object passed; dump to console.\r
-        console.log(sText, sTypeOrObject);\r
-      } else if (debugLevels[sTypeOrObject] !== _undefined) {\r
-        console[debugLevels[sTypeOrObject]](sText);\r
-      } else {\r
-        console.log(sText);\r
-      }\r
-      if (sm2.consoleOnly) {\r
-        return true;\r
-      }\r
-    }\r
-\r
-    o = id(sDID);\r
-\r
-    if (!o) {\r
-      return false;\r
-    }\r
-\r
-    oItem = doc.createElement('div');\r
-\r
-    if (++wdCount % 2 === 0) {\r
-      oItem.className = 'sm2-alt';\r
-    }\r
-\r
-    if (sTypeOrObject === _undefined) {\r
-      sTypeOrObject = 0;\r
-    } else {\r
-      sTypeOrObject = parseInt(sTypeOrObject, 10);\r
-    }\r
-\r
-    oItem.appendChild(doc.createTextNode(sText));\r
-\r
-    if (sTypeOrObject) {\r
-      if (sTypeOrObject >= 2) {\r
-        oItem.style.fontWeight = 'bold';\r
-      }\r
-      if (sTypeOrObject === 3) {\r
-        oItem.style.color = '#ff3333';\r
-      }\r
-    }\r
-\r
-    // top-to-bottom\r
-    // o.appendChild(oItem);\r
-\r
-    // bottom-to-top\r
-    o.insertBefore(oItem, o.firstChild);\r
-\r
-    o = null;\r
-    // </d>\r
-\r
-    return true;\r
-\r
-  };\r
-\r
-  // <d>\r
-  // last-resort debugging option\r
-  if (wl.indexOf('sm2-debug=alert') !== -1) {\r
-    this._writeDebug = function(sText) {\r
-      window.alert(sText);\r
-    };\r
-  }\r
-  // </d>\r
-\r
-  // alias\r
-  this._wD = this._writeDebug;\r
-\r
-  /**\r
-   * Provides debug / state information on all SMSound objects.\r
-   */\r
-\r
-  this._debug = function() {\r
-\r
-    // <d>\r
-    var i, j;\r
-    _wDS('currentObj', 1);\r
-\r
-    for (i = 0, j = sm2.soundIDs.length; i < j; i++) {\r
-      sm2.sounds[sm2.soundIDs[i]]._debug();\r
-    }\r
-    // </d>\r
-\r
-  };\r
-\r
-  /**\r
-   * Restarts and re-initializes the SoundManager instance.\r
-   *\r
-   * @param {boolean} resetEvents Optional: When true, removes all registered onready and ontimeout event callbacks.\r
-   * @param {boolean} excludeInit Options: When true, does not call beginDelayedInit() (which would restart SM2).\r
-   * @return {object} soundManager The soundManager instance.\r
-   */\r
-\r
-  this.reboot = function(resetEvents, excludeInit) {\r
-\r
-    // reset some (or all) state, and re-init unless otherwise specified.\r
-\r
-    // <d>\r
-    if (sm2.soundIDs.length) {\r
-      sm2._wD('Destroying ' + sm2.soundIDs.length + ' SMSound object' + (sm2.soundIDs.length !== 1 ? 's' : '') + '...');\r
-    }\r
-    // </d>\r
-\r
-    var i, j, k;\r
-\r
-    for (i = sm2.soundIDs.length-1; i >= 0; i--) {\r
-      sm2.sounds[sm2.soundIDs[i]].destruct();\r
-    }\r
-\r
-    // trash ze flash (remove from the DOM)\r
-\r
-    if (flash) {\r
-\r
-      try {\r
-\r
-        if (isIE) {\r
-          oRemovedHTML = flash.innerHTML;\r
-        }\r
-\r
-        oRemoved = flash.parentNode.removeChild(flash);\r
-\r
-      } catch(e) {\r
-\r
-        // Remove failed? May be due to flash blockers silently removing the SWF object/embed node from the DOM. Warn and continue.\r
-\r
-        _wDS('badRemove', 2);\r
-\r
-      }\r
-\r
-    }\r
-\r
-    // actually, force recreate of movie.\r
-\r
-    oRemovedHTML = oRemoved = needsFlash = flash = null;\r
-\r
-    sm2.enabled = didDCLoaded = didInit = waitingForEI = initPending = didAppend = appendSuccess = disabled = useGlobalHTML5Audio = sm2.swfLoaded = false;\r
-\r
-    sm2.soundIDs = [];\r
-    sm2.sounds = {};\r
-\r
-    idCounter = 0;\r
-\r
-    if (!resetEvents) {\r
-      // reset callbacks for onready, ontimeout etc. so that they will fire again on re-init\r
-      for (i in on_queue) {\r
-        if (on_queue.hasOwnProperty(i)) {\r
-          for (j = 0, k = on_queue[i].length; j < k; j++) {\r
-            on_queue[i][j].fired = false;\r
-          }\r
-        }\r
-      }\r
-    } else {\r
-      // remove all callbacks entirely\r
-      on_queue = [];\r
-    }\r
-\r
-    // <d>\r
-    if (!excludeInit) {\r
-      sm2._wD(sm + ': Rebooting...');\r
-    }\r
-    // </d>\r
-\r
-    // reset HTML5 and flash canPlay test results\r
-\r
-    sm2.html5 = {\r
-      'usingFlash': null\r
-    };\r
-\r
-    sm2.flash = {};\r
-\r
-    // reset device-specific HTML/flash mode switches\r
-\r
-    sm2.html5Only = false;\r
-    sm2.ignoreFlash = false;\r
-\r
-    window.setTimeout(function() {\r
-\r
-      preInit();\r
-\r
-      // by default, re-init\r
-\r
-      if (!excludeInit) {\r
-        sm2.beginDelayedInit();\r
-      }\r
-\r
-    }, 20);\r
-\r
-    return sm2;\r
-\r
-  };\r
-\r
-  this.reset = function() {\r
-\r
-    /**\r
-     * Shuts down and restores the SoundManager instance to its original loaded state, without an explicit reboot. All onready/ontimeout handlers are removed.\r
-     * After this call, SM2 may be re-initialized via soundManager.beginDelayedInit().\r
-     * @return {object} soundManager The soundManager instance.\r
-     */\r
-\r
-    _wDS('reset');\r
-    return sm2.reboot(true, true);\r
-\r
-  };\r
-\r
-  /**\r
-   * Undocumented: Determines the SM2 flash movie's load progress.\r
-   *\r
-   * @return {number or null} Percent loaded, or if invalid/unsupported, null.\r
-   */\r
-\r
-  this.getMoviePercent = function() {\r
-\r
-    /**\r
-     * Interesting syntax notes...\r
-     * Flash/ExternalInterface (ActiveX/NPAPI) bridge methods are not typeof "function" nor instanceof Function, but are still valid.\r
-     * Additionally, JSLint dislikes ('PercentLoaded' in flash)-style syntax and recommends hasOwnProperty(), which does not work in this case.\r
-     * Furthermore, using (flash && flash.PercentLoaded) causes IE to throw "object doesn't support this property or method".\r
-     * Thus, 'in' syntax must be used.\r
-     */\r
-\r
-    return (flash && 'PercentLoaded' in flash ? flash.PercentLoaded() : null); // Yes, JSLint. See nearby comment in source for explanation.\r
-\r
-  };\r
-\r
-  /**\r
-   * Additional helper for manually invoking SM2's init process after DOM Ready / window.onload().\r
-   */\r
-\r
-  this.beginDelayedInit = function() {\r
-\r
-    windowLoaded = true;\r
-    domContentLoaded();\r
-\r
-    setTimeout(function() {\r
-\r
-      if (initPending) {\r
-        return false;\r
-      }\r
-\r
-      createMovie();\r
-      initMovie();\r
-      initPending = true;\r
-\r
-      return true;\r
-\r
-    }, 20);\r
-\r
-    delayWaitForEI();\r
-\r
-  };\r
-\r
-  /**\r
-   * Destroys the SoundManager instance and all SMSound instances.\r
-   */\r
-\r
-  this.destruct = function() {\r
-\r
-    sm2._wD(sm + '.destruct()');\r
-    sm2.disable(true);\r
-\r
-  };\r
-\r
-  /**\r
-   * SMSound() (sound object) constructor\r
-   * ------------------------------------\r
-   *\r
-   * @param {object} oOptions Sound options (id and url are required attributes)\r
-   * @return {SMSound} The new SMSound object\r
-   */\r
-\r
-  SMSound = function(oOptions) {\r
-\r
-    var s = this, resetProperties, add_html5_events, remove_html5_events, stop_html5_timer, start_html5_timer, attachOnPosition, onplay_called = false, onPositionItems = [], onPositionFired = 0, detachOnPosition, applyFromTo, lastURL = null, lastHTML5State, urlOmitted;\r
-\r
-    lastHTML5State = {\r
-      // tracks duration + position (time)\r
-      duration: null,\r
-      time: null\r
-    };\r
-\r
-    this.id = oOptions.id;\r
-\r
-    // legacy\r
-    this.sID = this.id;\r
-\r
-    this.url = oOptions.url;\r
-    this.options = mixin(oOptions);\r
-\r
-    // per-play-instance-specific options\r
-    this.instanceOptions = this.options;\r
-\r
-    // short alias\r
-    this._iO = this.instanceOptions;\r
-\r
-    // assign property defaults\r
-    this.pan = this.options.pan;\r
-    this.volume = this.options.volume;\r
-\r
-    // whether or not this object is using HTML5\r
-    this.isHTML5 = false;\r
-\r
-    // internal HTML5 Audio() object reference\r
-    this._a = null;\r
-\r
-    // for flash 8 special-case createSound() without url, followed by load/play with url case\r
-    urlOmitted = (this.url ? false : true);\r
-\r
-    /**\r
-     * SMSound() public methods\r
-     * ------------------------\r
-     */\r
-\r
-    this.id3 = {};\r
-\r
-    /**\r
-     * Writes SMSound object parameters to debug console\r
-     */\r
-\r
-    this._debug = function() {\r
-\r
-      // <d>\r
-      sm2._wD(s.id + ': Merged options:', s.options);\r
-      // </d>\r
-\r
-    };\r
-\r
-    /**\r
-     * Begins loading a sound per its *url*.\r
-     *\r
-     * @param {object} oOptions Optional: Sound options\r
-     * @return {SMSound} The SMSound object\r
-     */\r
-\r
-    this.load = function(oOptions) {\r
-\r
-      var oSound = null, instanceOptions;\r
-\r
-      if (oOptions !== _undefined) {\r
-        s._iO = mixin(oOptions, s.options);\r
-      } else {\r
-        oOptions = s.options;\r
-        s._iO = oOptions;\r
-        if (lastURL && lastURL !== s.url) {\r
-          _wDS('manURL');\r
-          s._iO.url = s.url;\r
-          s.url = null;\r
-        }\r
-      }\r
-\r
-      if (!s._iO.url) {\r
-        s._iO.url = s.url;\r
-      }\r
-\r
-      s._iO.url = parseURL(s._iO.url);\r
-\r
-      // ensure we're in sync\r
-      s.instanceOptions = s._iO;\r
-\r
-      // local shortcut\r
-      instanceOptions = s._iO;\r
-\r
-      sm2._wD(s.id + ': load (' + instanceOptions.url + ')');\r
-\r
-      if (!instanceOptions.url && !s.url) {\r
-        sm2._wD(s.id + ': load(): url is unassigned. Exiting.', 2);\r
-        return s;\r
-      }\r
-\r
-      // <d>\r
-      if (!s.isHTML5 && fV === 8 && !s.url && !instanceOptions.autoPlay) {\r
-        // flash 8 load() -> play() won't work before onload has fired.\r
-        sm2._wD(s.id + ': Flash 8 load() limitation: Wait for onload() before calling play().', 1);\r
-      }\r
-      // </d>\r
-\r
-      if (instanceOptions.url === s.url && s.readyState !== 0 && s.readyState !== 2) {\r
-        _wDS('onURL', 1);\r
-        // if loaded and an onload() exists, fire immediately.\r
-        if (s.readyState === 3 && instanceOptions.onload) {\r
-          // assume success based on truthy duration.\r
-          wrapCallback(s, function() {\r
-            instanceOptions.onload.apply(s, [(!!s.duration)]);\r
-          });\r
-        }\r
-        return s;\r
-      }\r
-\r
-      // reset a few state properties\r
-\r
-      s.loaded = false;\r
-      s.readyState = 1;\r
-      s.playState = 0;\r
-      s.id3 = {};\r
-\r
-      // TODO: If switching from HTML5 -> flash (or vice versa), stop currently-playing audio.\r
-\r
-      if (html5OK(instanceOptions)) {\r
-\r
-        oSound = s._setup_html5(instanceOptions);\r
-\r
-        if (!oSound._called_load) {\r
-\r
-          s._html5_canplay = false;\r
-\r
-          // TODO: review called_load / html5_canplay logic\r
-\r
-          // if url provided directly to load(), assign it here.\r
-\r
-          if (s.url !== instanceOptions.url) {\r
-\r
-            sm2._wD(_wDS('manURL') + ': ' + instanceOptions.url);\r
-\r
-            s._a.src = instanceOptions.url;\r
-\r
-            // TODO: review / re-apply all relevant options (volume, loop, onposition etc.)\r
-\r
-            // reset position for new URL\r
-            s.setPosition(0);\r
-\r
-          }\r
-\r
-          // given explicit load call, try to preload.\r
-\r
-          // early HTML5 implementation (non-standard)\r
-          s._a.autobuffer = 'auto';\r
-\r
-          // standard property, values: none / metadata / auto\r
-          // reference: http://msdn.microsoft.com/en-us/library/ie/ff974759%28v=vs.85%29.aspx\r
-          s._a.preload = 'auto';\r
-\r
-          s._a._called_load = true;\r
-\r
-        } else {\r
-\r
-          sm2._wD(s.id + ': Ignoring request to load again');\r
-\r
-        }\r
-\r
-      } else {\r
-\r
-        if (sm2.html5Only) {\r
-          sm2._wD(s.id + ': No flash support. Exiting.');\r
-          return s;\r
-        }\r
-\r
-        if (s._iO.url && s._iO.url.match(/data\:/i)) {\r
-          // data: URIs not supported by Flash, either.\r
-          sm2._wD(s.id + ': data: URIs not supported via Flash. Exiting.');\r
-          return s;\r
-        }\r
-\r
-        try {\r
-          s.isHTML5 = false;\r
-          s._iO = policyFix(loopFix(instanceOptions));\r
-          // re-assign local shortcut\r
-          instanceOptions = s._iO;\r
-          if (fV === 8) {\r
-            flash._load(s.id, instanceOptions.url, instanceOptions.stream, instanceOptions.autoPlay, instanceOptions.usePolicyFile);\r
-          } else {\r
-            flash._load(s.id, instanceOptions.url, !!(instanceOptions.stream), !!(instanceOptions.autoPlay), instanceOptions.loops||1, !!(instanceOptions.autoLoad), instanceOptions.usePolicyFile);\r
-          }\r
-        } catch(e) {\r
-          _wDS('smError', 2);\r
-          debugTS('onload', false);\r
-          catchError({type:'SMSOUND_LOAD_JS_EXCEPTION', fatal:true});\r
-        }\r
-\r
-      }\r
-\r
-      // after all of this, ensure sound url is up to date.\r
-      s.url = instanceOptions.url;\r
-\r
-      return s;\r
-\r
-    };\r
-\r
-    /**\r
-     * Unloads a sound, canceling any open HTTP requests.\r
-     *\r
-     * @return {SMSound} The SMSound object\r
-     */\r
-\r
-    this.unload = function() {\r
-\r
-      // Flash 8/AS2 can't "close" a stream - fake it by loading an empty URL\r
-      // Flash 9/AS3: Close stream, preventing further load\r
-      // HTML5: Most UAs will use empty URL\r
-\r
-      if (s.readyState !== 0) {\r
-\r
-        sm2._wD(s.id + ': unload()');\r
-\r
-        if (!s.isHTML5) {\r
-\r
-          if (fV === 8) {\r
-            flash._unload(s.id, emptyURL);\r
-          } else {\r
-            flash._unload(s.id);\r
-          }\r
-\r
-        } else {\r
-\r
-          stop_html5_timer();\r
-\r
-          if (s._a) {\r
-\r
-            s._a.pause();\r
-\r
-            // update empty URL, too\r
-            lastURL = html5Unload(s._a);\r
-\r
-          }\r
-\r
-        }\r
-\r
-        // reset load/status flags\r
-        resetProperties();\r
-\r
-      }\r
-\r
-      return s;\r
-\r
-    };\r
-\r
-    /**\r
-     * Unloads and destroys a sound.\r
-     */\r
-\r
-    this.destruct = function(_bFromSM) {\r
-\r
-      sm2._wD(s.id + ': Destruct');\r
-\r
-      if (!s.isHTML5) {\r
-\r
-        // kill sound within Flash\r
-        // Disable the onfailure handler\r
-        s._iO.onfailure = null;\r
-        flash._destroySound(s.id);\r
-\r
-      } else {\r
-\r
-        stop_html5_timer();\r
-\r
-        if (s._a) {\r
-          s._a.pause();\r
-          html5Unload(s._a);\r
-          if (!useGlobalHTML5Audio) {\r
-            remove_html5_events();\r
-          }\r
-          // break obvious circular reference\r
-          s._a._s = null;\r
-          s._a = null;\r
-        }\r
-\r
-      }\r
-\r
-      if (!_bFromSM) {\r
-        // ensure deletion from controller\r
-        sm2.destroySound(s.id, true);\r
-      }\r
-\r
-    };\r
-\r
-    /**\r
-     * Begins playing a sound.\r
-     *\r
-     * @param {object} oOptions Optional: Sound options\r
-     * @return {SMSound} The SMSound object\r
-     */\r
-\r
-    this.play = function(oOptions, _updatePlayState) {\r
-\r
-      var fN, allowMulti, a, onready,\r
-          audioClone, onended, oncanplay,\r
-          startOK = true,\r
-          exit = null;\r
-\r
-      // <d>\r
-      fN = s.id + ': play(): ';\r
-      // </d>\r
-\r
-      // default to true\r
-      _updatePlayState = (_updatePlayState === _undefined ? true : _updatePlayState);\r
-\r
-      if (!oOptions) {\r
-        oOptions = {};\r
-      }\r
-\r
-      // first, use local URL (if specified)\r
-      if (s.url) {\r
-        s._iO.url = s.url;\r
-      }\r
-\r
-      // mix in any options defined at createSound()\r
-      s._iO = mixin(s._iO, s.options);\r
-\r
-      // mix in any options specific to this method\r
-      s._iO = mixin(oOptions, s._iO);\r
-\r
-      s._iO.url = parseURL(s._iO.url);\r
-\r
-      s.instanceOptions = s._iO;\r
-\r
-      // RTMP-only\r
-      if (!s.isHTML5 && s._iO.serverURL && !s.connected) {\r
-        if (!s.getAutoPlay()) {\r
-          sm2._wD(fN +' Netstream not connected yet - setting autoPlay');\r
-          s.setAutoPlay(true);\r
-        }\r
-        // play will be called in onconnect()\r
-        return s;\r
-      }\r
-\r
-      if (html5OK(s._iO)) {\r
-        s._setup_html5(s._iO);\r
-        start_html5_timer();\r
-      }\r
-\r
-      if (s.playState === 1 && !s.paused) {\r
-        allowMulti = s._iO.multiShot;\r
-        if (!allowMulti) {\r
-          sm2._wD(fN + 'Already playing (one-shot)', 1);\r
-          if (s.isHTML5) {\r
-            // go back to original position.\r
-            s.setPosition(s._iO.position);\r
-          }\r
-          exit = s;\r
-        } else {\r
-          sm2._wD(fN + 'Already playing (multi-shot)', 1);\r
-        }\r
-      }\r
-\r
-      if (exit !== null) {\r
-        return exit;\r
-      }\r
-\r
-      // edge case: play() with explicit URL parameter\r
-      if (oOptions.url && oOptions.url !== s.url) {\r
-\r
-        // special case for createSound() followed by load() / play() with url; avoid double-load case.\r
-        if (!s.readyState && !s.isHTML5 && fV === 8 && urlOmitted) {\r
-\r
-          urlOmitted = false;\r
-\r
-        } else {\r
-\r
-          // load using merged options\r
-          s.load(s._iO);\r
-\r
-        }\r
-\r
-      }\r
-\r
-      if (!s.loaded) {\r
-\r
-        if (s.readyState === 0) {\r
-\r
-          sm2._wD(fN + 'Attempting to load');\r
-\r
-          // try to get this sound playing ASAP\r
-          if (!s.isHTML5 && !sm2.html5Only) {\r
-\r
-            // flash: assign directly because setAutoPlay() increments the instanceCount\r
-            s._iO.autoPlay = true;\r
-            s.load(s._iO);\r
-\r
-          } else if (s.isHTML5) {\r
-\r
-            // iOS needs this when recycling sounds, loading a new URL on an existing object.\r
-            s.load(s._iO);\r
-\r
-          } else {\r
-\r
-            sm2._wD(fN + 'Unsupported type. Exiting.');\r
-            exit = s;\r
-\r
-          }\r
-\r
-          // HTML5 hack - re-set instanceOptions?\r
-          s.instanceOptions = s._iO;\r
-\r
-        } else if (s.readyState === 2) {\r
-\r
-          sm2._wD(fN + 'Could not load - exiting', 2);\r
-          exit = s;\r
-\r
-        } else {\r
-\r
-          sm2._wD(fN + 'Loading - attempting to play...');\r
-\r
-        }\r
-\r
-      } else {\r
-\r
-        // "play()"\r
-        sm2._wD(fN.substr(0, fN.lastIndexOf(':')));\r
-\r
-      }\r
-\r
-      if (exit !== null) {\r
-        return exit;\r
-      }\r
-\r
-      if (!s.isHTML5 && fV === 9 && s.position > 0 && s.position === s.duration) {\r
-        // flash 9 needs a position reset if play() is called while at the end of a sound.\r
-        sm2._wD(fN + 'Sound at end, resetting to position:0');\r
-        oOptions.position = 0;\r
-      }\r
-\r
-      /**\r
-       * Streams will pause when their buffer is full if they are being loaded.\r
-       * In this case paused is true, but the song hasn't started playing yet.\r
-       * If we just call resume() the onplay() callback will never be called.\r
-       * So only call resume() if the position is > 0.\r
-       * Another reason is because options like volume won't have been applied yet.\r
-       * For normal sounds, just resume.\r
-       */\r
-\r
-      if (s.paused && s.position >= 0 && (!s._iO.serverURL || s.position > 0)) {\r
-\r
-        // https://gist.github.com/37b17df75cc4d7a90bf6\r
-        sm2._wD(fN + 'Resuming from paused state', 1);\r
-        s.resume();\r
-\r
-      } else {\r
-\r
-        s._iO = mixin(oOptions, s._iO);\r
-\r
-        // apply from/to parameters, if they exist (and not using RTMP)\r
-        if (s._iO.from !== null && s._iO.to !== null && s.instanceCount === 0 && s.playState === 0 && !s._iO.serverURL) {\r
-\r
-          onready = function() {\r
-            // sound "canplay" or onload()\r
-            // re-apply from/to to instance options, and start playback\r
-            s._iO = mixin(oOptions, s._iO);\r
-            s.play(s._iO);\r
-          };\r
-\r
-          // HTML5 needs to at least have "canplay" fired before seeking.\r
-          if (s.isHTML5 && !s._html5_canplay) {\r
-\r
-            // this hasn't been loaded yet. load it first, and then do this again.\r
-            sm2._wD(fN + 'Beginning load for from/to case');\r
-\r
-            s.load({\r
-              // note: custom HTML5-only event added for from/to implementation.\r
-              _oncanplay: onready\r
-            });\r
-\r
-            exit = false;\r
-\r
-          } else if (!s.isHTML5 && !s.loaded && (!s.readyState || s.readyState !== 2)) {\r
-\r
-            // to be safe, preload the whole thing in Flash.\r
-\r
-            sm2._wD(fN + 'Preloading for from/to case');\r
-\r
-            s.load({\r
-              onload: onready\r
-            });\r
-\r
-            exit = false;\r
-\r
-          }\r
-\r
-          if (exit !== null) {\r
-            return exit;\r
-          }\r
-\r
-          // otherwise, we're ready to go. re-apply local options, and continue\r
-\r
-          s._iO = applyFromTo();\r
-\r
-        }\r
-\r
-        // sm2._wD(fN + 'Starting to play');\r
-\r
-        // increment instance counter, where enabled + supported\r
-        if (!s.instanceCount || s._iO.multiShotEvents || (s.isHTML5 && s._iO.multiShot && !useGlobalHTML5Audio) || (!s.isHTML5 && fV > 8 && !s.getAutoPlay())) {\r
-          s.instanceCount++;\r
-        }\r
-\r
-        // if first play and onposition parameters exist, apply them now\r
-        if (s._iO.onposition && s.playState === 0) {\r
-          attachOnPosition(s);\r
-        }\r
-\r
-        s.playState = 1;\r
-        s.paused = false;\r
-\r
-        s.position = (s._iO.position !== _undefined && !isNaN(s._iO.position) ? s._iO.position : 0);\r
-\r
-        if (!s.isHTML5) {\r
-          s._iO = policyFix(loopFix(s._iO));\r
-        }\r
-\r
-        if (s._iO.onplay && _updatePlayState) {\r
-          s._iO.onplay.apply(s);\r
-          onplay_called = true;\r
-        }\r
-\r
-        s.setVolume(s._iO.volume, true);\r
-        s.setPan(s._iO.pan, true);\r
-\r
-        if (!s.isHTML5) {\r
-\r
-          startOK = flash._start(s.id, s._iO.loops || 1, (fV === 9 ? s.position : s.position / msecScale), s._iO.multiShot || false);\r
-\r
-          if (fV === 9 && !startOK) {\r
-            // edge case: no sound hardware, or 32-channel flash ceiling hit.\r
-            // applies only to Flash 9, non-NetStream/MovieStar sounds.\r
-            // http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/Sound.html#play%28%29\r
-            sm2._wD(fN + 'No sound hardware, or 32-sound ceiling hit', 2);\r
-            if (s._iO.onplayerror) {\r
-              s._iO.onplayerror.apply(s);\r
-            }\r
-\r
-          }\r
-\r
-        } else {\r
-\r
-          if (s.instanceCount < 2) {\r
-\r
-            // HTML5 single-instance case\r
-\r
-            start_html5_timer();\r
-\r
-            a = s._setup_html5();\r
-\r
-            s.setPosition(s._iO.position);\r
-\r
-            a.play();\r
-\r
-          } else {\r
-\r
-            // HTML5 multi-shot case\r
-\r
-            sm2._wD(s.id + ': Cloning Audio() for instance #' + s.instanceCount + '...');\r
-\r
-            audioClone = new Audio(s._iO.url);\r
-\r
-            onended = function() {\r
-              event.remove(audioClone, 'ended', onended);\r
-              s._onfinish(s);\r
-              // cleanup\r
-              html5Unload(audioClone);\r
-              audioClone = null;\r
-            };\r
-\r
-            oncanplay = function() {\r
-              event.remove(audioClone, 'canplay', oncanplay);\r
-              try {\r
-                audioClone.currentTime = s._iO.position/msecScale;\r
-              } catch(err) {\r
-                complain(s.id + ': multiShot play() failed to apply position of ' + (s._iO.position/msecScale));\r
-              }\r
-              audioClone.play();\r
-            };\r
-\r
-            event.add(audioClone, 'ended', onended);\r
-\r
-            // apply volume to clones, too\r
-            if (s._iO.volume !== undefined) {\r
-              audioClone.volume = Math.max(0, Math.min(1, s._iO.volume/100));\r
-            }\r
-\r
-            // playing multiple muted sounds? if you do this, you're weird ;) - but let's cover it.\r
-            if (s.muted) {\r
-              audioClone.muted = true;\r
-            }\r
-\r
-            if (s._iO.position) {\r
-              // HTML5 audio can't seek before onplay() event has fired.\r
-              // wait for canplay, then seek to position and start playback.\r
-              event.add(audioClone, 'canplay', oncanplay);\r
-            } else {\r
-              // begin playback at currentTime: 0\r
-              audioClone.play();\r
-            }\r
-\r
-          }\r
-\r
-        }\r
-\r
-      }\r
-\r
-      return s;\r
-\r
-    };\r
-\r
-    // just for convenience\r
-    this.start = this.play;\r
-\r
-    /**\r
-     * Stops playing a sound (and optionally, all sounds)\r
-     *\r
-     * @param {boolean} bAll Optional: Whether to stop all sounds\r
-     * @return {SMSound} The SMSound object\r
-     */\r
-\r
-    this.stop = function(bAll) {\r
-\r
-      var instanceOptions = s._iO,\r
-          originalPosition;\r
-\r
-      if (s.playState === 1) {\r
-\r
-        sm2._wD(s.id + ': stop()');\r
-\r
-        s._onbufferchange(0);\r
-        s._resetOnPosition(0);\r
-        s.paused = false;\r
-\r
-        if (!s.isHTML5) {\r
-          s.playState = 0;\r
-        }\r
-\r
-        // remove onPosition listeners, if any\r
-        detachOnPosition();\r
-\r
-        // and "to" position, if set\r
-        if (instanceOptions.to) {\r
-          s.clearOnPosition(instanceOptions.to);\r
-        }\r
-\r
-        if (!s.isHTML5) {\r
-\r
-          flash._stop(s.id, bAll);\r
-\r
-          // hack for netStream: just unload\r
-          if (instanceOptions.serverURL) {\r
-            s.unload();\r
-          }\r
-\r
-        } else {\r
-\r
-          if (s._a) {\r
-\r
-            originalPosition = s.position;\r
-\r
-            // act like Flash, though\r
-            s.setPosition(0);\r
-\r
-            // hack: reflect old position for onstop() (also like Flash)\r
-            s.position = originalPosition;\r
-\r
-            // html5 has no stop()\r
-            // NOTE: pausing means iOS requires interaction to resume.\r
-            s._a.pause();\r
-\r
-            s.playState = 0;\r
-\r
-            // and update UI\r
-            s._onTimer();\r
-\r
-            stop_html5_timer();\r
-\r
-          }\r
-\r
-        }\r
-\r
-        s.instanceCount = 0;\r
-        s._iO = {};\r
-\r
-        if (instanceOptions.onstop) {\r
-          instanceOptions.onstop.apply(s);\r
-        }\r
-\r
-      }\r
-\r
-      return s;\r
-\r
-    };\r
-\r
-    /**\r
-     * Undocumented/internal: Sets autoPlay for RTMP.\r
-     *\r
-     * @param {boolean} autoPlay state\r
-     */\r
-\r
-    this.setAutoPlay = function(autoPlay) {\r
-\r
-      sm2._wD(s.id + ': Autoplay turned ' + (autoPlay ? 'on' : 'off'));\r
-      s._iO.autoPlay = autoPlay;\r
-\r
-      if (!s.isHTML5) {\r
-        flash._setAutoPlay(s.id, autoPlay);\r
-        if (autoPlay) {\r
-          // only increment the instanceCount if the sound isn't loaded (TODO: verify RTMP)\r
-          if (!s.instanceCount && s.readyState === 1) {\r
-            s.instanceCount++;\r
-            sm2._wD(s.id + ': Incremented instance count to '+s.instanceCount);\r
-          }\r
-        }\r
-      }\r
-\r
-    };\r
-\r
-    /**\r
-     * Undocumented/internal: Returns the autoPlay boolean.\r
-     *\r
-     * @return {boolean} The current autoPlay value\r
-     */\r
-\r
-    this.getAutoPlay = function() {\r
-\r
-      return s._iO.autoPlay;\r
-\r
-    };\r
-\r
-    /**\r
-     * Sets the position of a sound.\r
-     *\r
-     * @param {number} nMsecOffset Position (milliseconds)\r
-     * @return {SMSound} The SMSound object\r
-     */\r
-\r
-    this.setPosition = function(nMsecOffset) {\r
-\r
-      if (nMsecOffset === _undefined) {\r
-        nMsecOffset = 0;\r
-      }\r
-\r
-      var position, position1K,\r
-          // Use the duration from the instance options, if we don't have a track duration yet.\r
-          // position >= 0 and <= current available (loaded) duration\r
-          offset = (s.isHTML5 ? Math.max(nMsecOffset, 0) : Math.min(s.duration || s._iO.duration, Math.max(nMsecOffset, 0)));\r
-\r
-      s.position = offset;\r
-      position1K = s.position/msecScale;\r
-      s._resetOnPosition(s.position);\r
-      s._iO.position = offset;\r
-\r
-      if (!s.isHTML5) {\r
-\r
-        position = (fV === 9 ? s.position : position1K);\r
-\r
-        if (s.readyState && s.readyState !== 2) {\r
-          // if paused or not playing, will not resume (by playing)\r
-          flash._setPosition(s.id, position, (s.paused || !s.playState), s._iO.multiShot);\r
-        }\r
-\r
-      } else if (s._a) {\r
-\r
-        // Set the position in the canplay handler if the sound is not ready yet\r
-        if (s._html5_canplay) {\r
-\r
-          if (s._a.currentTime !== position1K) {\r
-\r
-            /**\r
-             * DOM/JS errors/exceptions to watch out for:\r
-             * if seek is beyond (loaded?) position, "DOM exception 11"\r
-             * "INDEX_SIZE_ERR": DOM exception 1\r
-             */\r
-            sm2._wD(s.id + ': setPosition('+position1K+')');\r
-\r
-            try {\r
-              s._a.currentTime = position1K;\r
-              if (s.playState === 0 || s.paused) {\r
-                // allow seek without auto-play/resume\r
-                s._a.pause();\r
-              }\r
-            } catch(e) {\r
-              sm2._wD(s.id + ': setPosition(' + position1K + ') failed: ' + e.message, 2);\r
-            }\r
-\r
-          }\r
-\r
-        } else if (position1K) {\r
-\r
-          // warn on non-zero seek attempts\r
-          sm2._wD(s.id + ': setPosition(' + position1K + '): Cannot seek yet, sound not ready', 2);\r
-          return s;\r
-\r
-        }\r
-\r
-        if (s.paused) {\r
-\r
-          // if paused, refresh UI right away\r
-          // force update\r
-          s._onTimer(true);\r
-\r
-        }\r
-\r
-      }\r
-\r
-      return s;\r
-\r
-    };\r
-\r
-    /**\r
-     * Pauses sound playback.\r
-     *\r
-     * @return {SMSound} The SMSound object\r
-     */\r
-\r
-    this.pause = function(_bCallFlash) {\r
-\r
-      if (s.paused || (s.playState === 0 && s.readyState !== 1)) {\r
-        return s;\r
-      }\r
-\r
-      sm2._wD(s.id + ': pause()');\r
-      s.paused = true;\r
-\r
-      if (!s.isHTML5) {\r
-        if (_bCallFlash || _bCallFlash === _undefined) {\r
-          flash._pause(s.id, s._iO.multiShot);\r
-        }\r
-      } else {\r
-        s._setup_html5().pause();\r
-        stop_html5_timer();\r
-      }\r
-\r
-      if (s._iO.onpause) {\r
-        s._iO.onpause.apply(s);\r
-      }\r
-\r
-      return s;\r
-\r
-    };\r
-\r
-    /**\r
-     * Resumes sound playback.\r
-     *\r
-     * @return {SMSound} The SMSound object\r
-     */\r
-\r
-    /**\r
-     * When auto-loaded streams pause on buffer full they have a playState of 0.\r
-     * We need to make sure that the playState is set to 1 when these streams "resume".\r
-     * When a paused stream is resumed, we need to trigger the onplay() callback if it\r
-     * hasn't been called already. In this case since the sound is being played for the\r
-     * first time, I think it's more appropriate to call onplay() rather than onresume().\r
-     */\r
-\r
-    this.resume = function() {\r
-\r
-      var instanceOptions = s._iO;\r
-\r
-      if (!s.paused) {\r
-        return s;\r
-      }\r
-\r
-      sm2._wD(s.id + ': resume()');\r
-      s.paused = false;\r
-      s.playState = 1;\r
-\r
-      if (!s.isHTML5) {\r
-        if (instanceOptions.isMovieStar && !instanceOptions.serverURL) {\r
-          // Bizarre Webkit bug (Chrome reported via 8tracks.com dudes): AAC content paused for 30+ seconds(?) will not resume without a reposition.\r
-          s.setPosition(s.position);\r
-        }\r
-        // flash method is toggle-based (pause/resume)\r
-        flash._pause(s.id, instanceOptions.multiShot);\r
-      } else {\r
-        s._setup_html5().play();\r
-        start_html5_timer();\r
-      }\r
-\r
-      if (!onplay_called && instanceOptions.onplay) {\r
-        instanceOptions.onplay.apply(s);\r
-        onplay_called = true;\r
-      } else if (instanceOptions.onresume) {\r
-        instanceOptions.onresume.apply(s);\r
-      }\r
-\r
-      return s;\r
-\r
-    };\r
-\r
-    /**\r
-     * Toggles sound playback.\r
-     *\r
-     * @return {SMSound} The SMSound object\r
-     */\r
-\r
-    this.togglePause = function() {\r
-\r
-      sm2._wD(s.id + ': togglePause()');\r
-\r
-      if (s.playState === 0) {\r
-        s.play({\r
-          position: (fV === 9 && !s.isHTML5 ? s.position : s.position / msecScale)\r
-        });\r
-        return s;\r
-      }\r
-\r
-      if (s.paused) {\r
-        s.resume();\r
-      } else {\r
-        s.pause();\r
-      }\r
-\r
-      return s;\r
-\r
-    };\r
-\r
-    /**\r
-     * Sets the panning (L-R) effect.\r
-     *\r
-     * @param {number} nPan The pan value (-100 to 100)\r
-     * @return {SMSound} The SMSound object\r
-     */\r
-\r
-    this.setPan = function(nPan, bInstanceOnly) {\r
-\r
-      if (nPan === _undefined) {\r
-        nPan = 0;\r
-      }\r
-\r
-      if (bInstanceOnly === _undefined) {\r
-        bInstanceOnly = false;\r
-      }\r
-\r
-      if (!s.isHTML5) {\r
-        flash._setPan(s.id, nPan);\r
-      } // else { no HTML5 pan? }\r
-\r
-      s._iO.pan = nPan;\r
-\r
-      if (!bInstanceOnly) {\r
-        s.pan = nPan;\r
-        s.options.pan = nPan;\r
-      }\r
-\r
-      return s;\r
-\r
-    };\r
-\r
-    /**\r
-     * Sets the volume.\r
-     *\r
-     * @param {number} nVol The volume value (0 to 100)\r
-     * @return {SMSound} The SMSound object\r
-     */\r
-\r
-    this.setVolume = function(nVol, _bInstanceOnly) {\r
-\r
-      /**\r
-       * Note: Setting volume has no effect on iOS "special snowflake" devices.\r
-       * Hardware volume control overrides software, and volume\r
-       * will always return 1 per Apple docs. (iOS 4 + 5.)\r
-       * http://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/AddingSoundtoCanvasAnimations/AddingSoundtoCanvasAnimations.html\r
-       */\r
-\r
-      if (nVol === _undefined) {\r
-        nVol = 100;\r
-      }\r
-\r
-      if (_bInstanceOnly === _undefined) {\r
-        _bInstanceOnly = false;\r
-      }\r
-\r
-      if (!s.isHTML5) {\r
-        flash._setVolume(s.id, (sm2.muted && !s.muted) || s.muted?0:nVol);\r
-      } else if (s._a) {\r
-        if (sm2.muted && !s.muted) {\r
-          s.muted = true;\r
-          s._a.muted = true;\r
-        }\r
-        // valid range: 0-1\r
-        s._a.volume = Math.max(0, Math.min(1, nVol/100));\r
-      }\r
-\r
-      s._iO.volume = nVol;\r
-\r
-      if (!_bInstanceOnly) {\r
-        s.volume = nVol;\r
-        s.options.volume = nVol;\r
-      }\r
-\r
-      return s;\r
-\r
-    };\r
-\r
-    /**\r
-     * Mutes the sound.\r
-     *\r
-     * @return {SMSound} The SMSound object\r
-     */\r
-\r
-    this.mute = function() {\r
-\r
-      s.muted = true;\r
-\r
-      if (!s.isHTML5) {\r
-        flash._setVolume(s.id, 0);\r
-      } else if (s._a) {\r
-        s._a.muted = true;\r
-      }\r
-\r
-      return s;\r
-\r
-    };\r
-\r
-    /**\r
-     * Unmutes the sound.\r
-     *\r
-     * @return {SMSound} The SMSound object\r
-     */\r
-\r
-    this.unmute = function() {\r
-\r
-      s.muted = false;\r
-      var hasIO = (s._iO.volume !== _undefined);\r
-\r
-      if (!s.isHTML5) {\r
-        flash._setVolume(s.id, hasIO?s._iO.volume:s.options.volume);\r
-      } else if (s._a) {\r
-        s._a.muted = false;\r
-      }\r
-\r
-      return s;\r
-\r
-    };\r
-\r
-    /**\r
-     * Toggles the muted state of a sound.\r
-     *\r
-     * @return {SMSound} The SMSound object\r
-     */\r
-\r
-    this.toggleMute = function() {\r
-\r
-      return (s.muted?s.unmute():s.mute());\r
-\r
-    };\r
-\r
-    /**\r
-     * Registers a callback to be fired when a sound reaches a given position during playback.\r
-     *\r
-     * @param {number} nPosition The position to watch for\r
-     * @param {function} oMethod The relevant callback to fire\r
-     * @param {object} oScope Optional: The scope to apply the callback to\r
-     * @return {SMSound} The SMSound object\r
-     */\r
-\r
-    this.onPosition = function(nPosition, oMethod, oScope) {\r
-\r
-      // TODO: basic dupe checking?\r
-\r
-      onPositionItems.push({\r
-        position: parseInt(nPosition, 10),\r
-        method: oMethod,\r
-        scope: (oScope !== _undefined ? oScope : s),\r
-        fired: false\r
-      });\r
-\r
-      return s;\r
-\r
-    };\r
-\r
-    // legacy/backwards-compability: lower-case method name\r
-    this.onposition = this.onPosition;\r
-\r
-    /**\r
-     * Removes registered callback(s) from a sound, by position and/or callback.\r
-     *\r
-     * @param {number} nPosition The position to clear callback(s) for\r
-     * @param {function} oMethod Optional: Identify one callback to be removed when multiple listeners exist for one position\r
-     * @return {SMSound} The SMSound object\r
-     */\r
-\r
-    this.clearOnPosition = function(nPosition, oMethod) {\r
-\r
-      var i;\r
-\r
-      nPosition = parseInt(nPosition, 10);\r
-\r
-      if (isNaN(nPosition)) {\r
-        // safety check\r
-        return false;\r
-      }\r
-\r
-      for (i=0; i < onPositionItems.length; i++) {\r
-\r
-        if (nPosition === onPositionItems[i].position) {\r
-          // remove this item if no method was specified, or, if the method matches\r
-          if (!oMethod || (oMethod === onPositionItems[i].method)) {\r
-            if (onPositionItems[i].fired) {\r
-              // decrement "fired" counter, too\r
-              onPositionFired--;\r
-            }\r
-            onPositionItems.splice(i, 1);\r
-          }\r
-        }\r
-\r
-      }\r
-\r
-    };\r
-\r
-    this._processOnPosition = function() {\r
-\r
-      var i, item, j = onPositionItems.length;\r
-               \r
-      if (!j || !s.playState || onPositionFired >= j) {\r
-        return false;\r
-      }\r
-\r
-      for (i=j-1; i >= 0; i--) {\r
-        item = onPositionItems[i];\r
-        if (!item.fired && s.position >= item.position) {\r
-          item.fired = true;\r
-          onPositionFired++;\r
-          item.method.apply(item.scope, [item.position]);\r
-                 j = onPositionItems.length; //  reset j -- onPositionItems.length can be changed in the item callback above... occasionally breaking the loop.\r
-        }\r
-      }\r
-       \r
-      return true;\r
-\r
-    };\r
-\r
-    this._resetOnPosition = function(nPosition) {\r
-\r
-      // reset "fired" for items interested in this position\r
-      var i, item, j = onPositionItems.length;\r
-\r
-      if (!j) {\r
-        return false;\r
-      }\r
-\r
-      for (i=j-1; i >= 0; i--) {\r
-        item = onPositionItems[i];\r
-        if (item.fired && nPosition <= item.position) {\r
-          item.fired = false;\r
-          onPositionFired--;\r
-        }\r
-      }\r
-\r
-      return true;\r
-\r
-    };\r
-\r
-    /**\r
-     * SMSound() private internals\r
-     * --------------------------------\r
-     */\r
-\r
-    applyFromTo = function() {\r
-\r
-      var instanceOptions = s._iO,\r
-          f = instanceOptions.from,\r
-          t = instanceOptions.to,\r
-          start, end;\r
-\r
-      end = function() {\r
-\r
-        // end has been reached.\r
-        sm2._wD(s.id + ': "To" time of ' + t + ' reached.');\r
-\r
-        // detach listener\r
-        s.clearOnPosition(t, end);\r
-\r
-        // stop should clear this, too\r
-        s.stop();\r
-\r
-      };\r
-\r
-      start = function() {\r
-\r
-        sm2._wD(s.id + ': Playing "from" ' + f);\r
-\r
-        // add listener for end\r
-        if (t !== null && !isNaN(t)) {\r
-          s.onPosition(t, end);\r
-        }\r
-\r
-      };\r
-\r
-      if (f !== null && !isNaN(f)) {\r
-\r
-        // apply to instance options, guaranteeing correct start position.\r
-        instanceOptions.position = f;\r
-\r
-        // multiShot timing can't be tracked, so prevent that.\r
-        instanceOptions.multiShot = false;\r
-\r
-        start();\r
-\r
-      }\r
-\r
-      // return updated instanceOptions including starting position\r
-      return instanceOptions;\r
-\r
-    };\r
-\r
-    attachOnPosition = function() {\r
-\r
-      var item,\r
-          op = s._iO.onposition;\r
-\r
-      // attach onposition things, if any, now.\r
-\r
-      if (op) {\r
-\r
-        for (item in op) {\r
-          if (op.hasOwnProperty(item)) {\r
-            s.onPosition(parseInt(item, 10), op[item]);\r
-          }\r
-        }\r
-\r
-      }\r
-\r
-    };\r
-\r
-    detachOnPosition = function() {\r
-\r
-      var item,\r
-          op = s._iO.onposition;\r
-\r
-      // detach any onposition()-style listeners.\r
-\r
-      if (op) {\r
-\r
-        for (item in op) {\r
-          if (op.hasOwnProperty(item)) {\r
-            s.clearOnPosition(parseInt(item, 10));\r
-          }\r
-        }\r
-\r
-      }\r
-\r
-    };\r
-\r
-    start_html5_timer = function() {\r
-\r
-      if (s.isHTML5) {\r
-        startTimer(s);\r
-      }\r
-\r
-    };\r
-\r
-    stop_html5_timer = function() {\r
-\r
-      if (s.isHTML5) {\r
-        stopTimer(s);\r
-      }\r
-\r
-    };\r
-\r
-    resetProperties = function(retainPosition) {\r
-\r
-      if (!retainPosition) {\r
-        onPositionItems = [];\r
-        onPositionFired = 0;\r
-      }\r
-\r
-      onplay_called = false;\r
-\r
-      s._hasTimer = null;\r
-      s._a = null;\r
-      s._html5_canplay = false;\r
-      s.bytesLoaded = null;\r
-      s.bytesTotal = null;\r
-      s.duration = (s._iO && s._iO.duration ? s._iO.duration : null);\r
-      s.durationEstimate = null;\r
-      s.buffered = [];\r
-\r
-      // legacy: 1D array\r
-      s.eqData = [];\r
-\r
-      s.eqData.left = [];\r
-      s.eqData.right = [];\r
-\r
-      s.failures = 0;\r
-      s.isBuffering = false;\r
-      s.instanceOptions = {};\r
-      s.instanceCount = 0;\r
-      s.loaded = false;\r
-      s.metadata = {};\r
-\r
-      // 0 = uninitialised, 1 = loading, 2 = failed/error, 3 = loaded/success\r
-      s.readyState = 0;\r
-\r
-      s.muted = false;\r
-      s.paused = false;\r
-\r
-      s.peakData = {\r
-        left: 0,\r
-        right: 0\r
-      };\r
-\r
-      s.waveformData = {\r
-        left: [],\r
-        right: []\r
-      };\r
-\r
-      s.playState = 0;\r
-      s.position = null;\r
-\r
-      s.id3 = {};\r
-\r
-    };\r
-\r
-    resetProperties();\r
-\r
-    /**\r
-     * Pseudo-private SMSound internals\r
-     * --------------------------------\r
-     */\r
-\r
-    this._onTimer = function(bForce) {\r
-\r
-      /**\r
-       * HTML5-only _whileplaying() etc.\r
-       * called from both HTML5 native events, and polling/interval-based timers\r
-       * mimics flash and fires only when time/duration change, so as to be polling-friendly\r
-       */\r
-\r
-      var duration, isNew = false, time, x = {};\r
-\r
-      if (s._hasTimer || bForce) {\r
-\r
-        // TODO: May not need to track readyState (1 = loading)\r
-\r
-        if (s._a && (bForce || ((s.playState > 0 || s.readyState === 1) && !s.paused))) {\r
-\r
-          duration = s._get_html5_duration();\r
-\r
-          if (duration !== lastHTML5State.duration) {\r
-\r
-            lastHTML5State.duration = duration;\r
-            s.duration = duration;\r
-            isNew = true;\r
-\r
-          }\r
-\r
-          // TODO: investigate why this goes wack if not set/re-set each time.\r
-          s.durationEstimate = s.duration;\r
-\r
-          time = (s._a.currentTime * msecScale || 0);\r
-\r
-          if (time !== lastHTML5State.time) {\r
-\r
-            lastHTML5State.time = time;\r
-            isNew = true;\r
-\r
-          }\r
-\r
-          if (isNew || bForce) {\r
-\r
-            s._whileplaying(time,x,x,x,x);\r
-\r
-          }\r
-\r
-        }/* else {\r
-\r
-          // sm2._wD('_onTimer: Warn for "'+s.id+'": '+(!s._a?'Could not find element. ':'')+(s.playState === 0?'playState bad, 0?':'playState = '+s.playState+', OK'));\r
-\r
-          return false;\r
-\r
-        }*/\r
-\r
-        return isNew;\r
-\r
-      }\r
-\r
-    };\r
-\r
-    this._get_html5_duration = function() {\r
-\r
-      var instanceOptions = s._iO,\r
-          // if audio object exists, use its duration - else, instance option duration (if provided - it's a hack, really, and should be retired) OR null\r
-          d = (s._a && s._a.duration ? s._a.duration*msecScale : (instanceOptions && instanceOptions.duration ? instanceOptions.duration : null)),\r
-          result = (d && !isNaN(d) && d !== Infinity ? d : null);\r
-\r
-      return result;\r
-\r
-    };\r
-\r
-    this._apply_loop = function(a, nLoops) {\r
-\r
-      /**\r
-       * boolean instead of "loop", for webkit? - spec says string. http://www.w3.org/TR/html-markup/audio.html#audio.attrs.loop\r
-       * note that loop is either off or infinite under HTML5, unlike Flash which allows arbitrary loop counts to be specified.\r
-       */\r
-\r
-      // <d>\r
-      if (!a.loop && nLoops > 1) {\r
-        sm2._wD('Note: Native HTML5 looping is infinite.', 1);\r
-      }\r
-      // </d>\r
-\r
-      a.loop = (nLoops > 1 ? 'loop' : '');\r
-\r
-    };\r
-\r
-    this._setup_html5 = function(oOptions) {\r
-\r
-      var instanceOptions = mixin(s._iO, oOptions),\r
-          a = useGlobalHTML5Audio ? globalHTML5Audio : s._a,\r
-          dURL = decodeURI(instanceOptions.url),\r
-          sameURL;\r
-\r
-      /**\r
-       * "First things first, I, Poppa..." (reset the previous state of the old sound, if playing)\r
-       * Fixes case with devices that can only play one sound at a time\r
-       * Otherwise, other sounds in mid-play will be terminated without warning and in a stuck state\r
-       */\r
-\r
-      if (useGlobalHTML5Audio) {\r
-\r
-        if (dURL === decodeURI(lastGlobalHTML5URL)) {\r
-          // global HTML5 audio: re-use of URL\r
-          sameURL = true;\r
-        }\r
-\r
-      } else if (dURL === decodeURI(lastURL)) {\r
-\r
-        // options URL is the same as the "last" URL, and we used (loaded) it\r
-        sameURL = true;\r
-\r
-      }\r
-\r
-      if (a) {\r
-\r
-        if (a._s) {\r
-\r
-          if (useGlobalHTML5Audio) {\r
-\r
-            if (a._s && a._s.playState && !sameURL) {\r
-\r
-              // global HTML5 audio case, and loading a new URL. stop the currently-playing one.\r
-              a._s.stop();\r
-\r
-            }\r
-\r
-          } else if (!useGlobalHTML5Audio && dURL === decodeURI(lastURL)) {\r
-\r
-            // non-global HTML5 reuse case: same url, ignore request\r
-            s._apply_loop(a, instanceOptions.loops);\r
-\r
-            return a;\r
-\r
-          }\r
-\r
-        }\r
-\r
-        if (!sameURL) {\r
-\r
-          // don't retain onPosition() stuff with new URLs.\r
-\r
-          if (lastURL) {\r
-            resetProperties(false);\r
-          }\r
-\r
-          // assign new HTML5 URL\r
-\r
-          a.src = instanceOptions.url;\r
-\r
-          s.url = instanceOptions.url;\r
-\r
-          lastURL = instanceOptions.url;\r
-\r
-          lastGlobalHTML5URL = instanceOptions.url;\r
-\r
-          a._called_load = false;\r
-\r
-        }\r
-\r
-      } else {\r
-\r
-        if (instanceOptions.autoLoad || instanceOptions.autoPlay) {\r
-\r
-          s._a = new Audio(instanceOptions.url);\r
-          s._a.load();\r
-\r
-        } else {\r
-\r
-          // null for stupid Opera 9.64 case\r
-          s._a = (isOpera && opera.version() < 10 ? new Audio(null) : new Audio());\r
-\r
-        }\r
-\r
-        // assign local reference\r
-        a = s._a;\r
-\r
-        a._called_load = false;\r
-\r
-        if (useGlobalHTML5Audio) {\r
-\r
-          globalHTML5Audio = a;\r
-\r
-        }\r
-\r
-      }\r
-\r
-      s.isHTML5 = true;\r
-\r
-      // store a ref on the track\r
-      s._a = a;\r
-\r
-      // store a ref on the audio\r
-      a._s = s;\r
-\r
-      add_html5_events();\r
-\r
-      s._apply_loop(a, instanceOptions.loops);\r
-\r
-      if (instanceOptions.autoLoad || instanceOptions.autoPlay) {\r
-\r
-        s.load();\r
-\r
-      } else {\r
-\r
-        // early HTML5 implementation (non-standard)\r
-        a.autobuffer = false;\r
-\r
-        // standard ('none' is also an option.)\r
-        a.preload = 'auto';\r
-\r
-      }\r
-\r
-      return a;\r
-\r
-    };\r
-\r
-    add_html5_events = function() {\r
-\r
-      if (s._a._added_events) {\r
-        return false;\r
-      }\r
-\r
-      var f;\r
-\r
-      function add(oEvt, oFn, bCapture) {\r
-        return s._a ? s._a.addEventListener(oEvt, oFn, bCapture||false) : null;\r
-      }\r
-\r
-      s._a._added_events = true;\r
-\r
-      for (f in html5_events) {\r
-        if (html5_events.hasOwnProperty(f)) {\r
-          add(f, html5_events[f]);\r
-        }\r
-      }\r
-\r
-      return true;\r
-\r
-    };\r
-\r
-    remove_html5_events = function() {\r
-\r
-      // Remove event listeners\r
-\r
-      var f;\r
-\r
-      function remove(oEvt, oFn, bCapture) {\r
-        return (s._a ? s._a.removeEventListener(oEvt, oFn, bCapture||false) : null);\r
-      }\r
-\r
-      sm2._wD(s.id + ': Removing event listeners');\r
-      s._a._added_events = false;\r
-\r
-      for (f in html5_events) {\r
-        if (html5_events.hasOwnProperty(f)) {\r
-          remove(f, html5_events[f]);\r
-        }\r
-      }\r
-\r
-    };\r
-\r
-    /**\r
-     * Pseudo-private event internals\r
-     * ------------------------------\r
-     */\r
-\r
-    this._onload = function(nSuccess) {\r
-\r
-      var fN,\r
-          // check for duration to prevent false positives from flash 8 when loading from cache.\r
-          loadOK = !!nSuccess || (!s.isHTML5 && fV === 8 && s.duration);\r
-\r
-      // <d>\r
-      fN = s.id + ': ';\r
-      sm2._wD(fN + (loadOK ? 'onload()' : 'Failed to load / invalid sound?' + (!s.duration ? ' Zero-length duration reported.' : ' -') + ' (' + s.url + ')'), (loadOK ? 1 : 2));\r
-      if (!loadOK && !s.isHTML5) {\r
-        if (sm2.sandbox.noRemote === true) {\r
-          sm2._wD(fN + str('noNet'), 1);\r
-        }\r
-        if (sm2.sandbox.noLocal === true) {\r
-          sm2._wD(fN + str('noLocal'), 1);\r
-        }\r
-      }\r
-      // </d>\r
-\r
-      s.loaded = loadOK;\r
-      s.readyState = loadOK?3:2;\r
-      s._onbufferchange(0);\r
-\r
-      if (s._iO.onload) {\r
-        wrapCallback(s, function() {\r
-          s._iO.onload.apply(s, [loadOK]);\r
-        });\r
-      }\r
-\r
-      return true;\r
-\r
-    };\r
-\r
-    this._onbufferchange = function(nIsBuffering) {\r
-\r
-      if (s.playState === 0) {\r
-        // ignore if not playing\r
-        return false;\r
-      }\r
-\r
-      if ((nIsBuffering && s.isBuffering) || (!nIsBuffering && !s.isBuffering)) {\r
-        return false;\r
-      }\r
-\r
-      s.isBuffering = (nIsBuffering === 1);\r
-      if (s._iO.onbufferchange) {\r
-        sm2._wD(s.id + ': Buffer state change: ' + nIsBuffering);\r
-        s._iO.onbufferchange.apply(s);\r
-      }\r
-\r
-      return true;\r
-\r
-    };\r
-\r
-    /**\r
-     * Playback may have stopped due to buffering, or related reason.\r
-     * This state can be encountered on iOS < 6 when auto-play is blocked.\r
-     */\r
-\r
-    this._onsuspend = function() {\r
-\r
-      if (s._iO.onsuspend) {\r
-        sm2._wD(s.id + ': Playback suspended');\r
-        s._iO.onsuspend.apply(s);\r
-      }\r
-\r
-      return true;\r
-\r
-    };\r
-\r
-    /**\r
-     * flash 9/movieStar + RTMP-only method, should fire only once at most\r
-     * at this point we just recreate failed sounds rather than trying to reconnect\r
-     */\r
-\r
-    this._onfailure = function(msg, level, code) {\r
-\r
-      s.failures++;\r
-      sm2._wD(s.id + ': Failures = ' + s.failures);\r
-\r
-      if (s._iO.onfailure && s.failures === 1) {\r
-        s._iO.onfailure(s, msg, level, code);\r
-      } else {\r
-        sm2._wD(s.id + ': Ignoring failure');\r
-      }\r
-\r
-    };\r
-\r
-    this._onfinish = function() {\r
-\r
-      // store local copy before it gets trashed...\r
-      var io_onfinish = s._iO.onfinish;\r
-\r
-      s._onbufferchange(0);\r
-      s._resetOnPosition(0);\r
-\r
-      // reset some state items\r
-      if (s.instanceCount) {\r
-\r
-        s.instanceCount--;\r
-\r
-        if (!s.instanceCount) {\r
-\r
-          // remove onPosition listeners, if any\r
-          detachOnPosition();\r
-\r
-          // reset instance options\r
-          s.playState = 0;\r
-          s.paused = false;\r
-          s.instanceCount = 0;\r
-          s.instanceOptions = {};\r
-          s._iO = {};\r
-          stop_html5_timer();\r
-\r
-          // reset position, too\r
-          if (s.isHTML5) {\r
-            s.position = 0;\r
-          }\r
-\r
-        }\r
-\r
-        if (!s.instanceCount || s._iO.multiShotEvents) {\r
-          // fire onfinish for last, or every instance\r
-          if (io_onfinish) {\r
-            sm2._wD(s.id + ': onfinish()');\r
-            wrapCallback(s, function() {\r
-              io_onfinish.apply(s);\r
-            });\r
-          }\r
-        }\r
-\r
-      }\r
-\r
-    };\r
-\r
-    this._whileloading = function(nBytesLoaded, nBytesTotal, nDuration, nBufferLength) {\r
-\r
-      var instanceOptions = s._iO;\r
-\r
-      s.bytesLoaded = nBytesLoaded;\r
-      s.bytesTotal = nBytesTotal;\r
-      s.duration = Math.floor(nDuration);\r
-      s.bufferLength = nBufferLength;\r
-\r
-      if (!s.isHTML5 && !instanceOptions.isMovieStar) {\r
-\r
-        if (instanceOptions.duration) {\r
-          // use duration from options, if specified and larger. nobody should be specifying duration in options, actually, and it should be retired.\r
-          s.durationEstimate = (s.duration > instanceOptions.duration) ? s.duration : instanceOptions.duration;\r
-        } else {\r
-          s.durationEstimate = parseInt((s.bytesTotal / s.bytesLoaded) * s.duration, 10);\r
-        }\r
-\r
-      } else {\r
-\r
-        s.durationEstimate = s.duration;\r
-\r
-      }\r
-\r
-      // for flash, reflect sequential-load-style buffering\r
-      if (!s.isHTML5) {\r
-        s.buffered = [{\r
-          'start': 0,\r
-          'end': s.duration\r
-        }];\r
-      }\r
-\r
-      // allow whileloading to fire even if "load" fired under HTML5, due to HTTP range/partials\r
-      if ((s.readyState !== 3 || s.isHTML5) && instanceOptions.whileloading) {\r
-        instanceOptions.whileloading.apply(s);\r
-      }\r
-\r
-    };\r
-\r
-    this._whileplaying = function(nPosition, oPeakData, oWaveformDataLeft, oWaveformDataRight, oEQData) {\r
-\r
-      var instanceOptions = s._iO,\r
-          eqLeft;\r
-\r
-      if (isNaN(nPosition) || nPosition === null) {\r
-        // flash safety net\r
-        return false;\r
-      }\r
-\r
-      // Safari HTML5 play() may return small -ve values when starting from position: 0, eg. -50.120396875. Unexpected/invalid per W3, I think. Normalize to 0.\r
-      s.position = Math.max(0, nPosition);\r
-\r
-      s._processOnPosition();\r
-\r
-      if (!s.isHTML5 && fV > 8) {\r
-\r
-        if (instanceOptions.usePeakData && oPeakData !== _undefined && oPeakData) {\r
-          s.peakData = {\r
-            left: oPeakData.leftPeak,\r
-            right: oPeakData.rightPeak\r
-          };\r
-        }\r
-\r
-        if (instanceOptions.useWaveformData && oWaveformDataLeft !== _undefined && oWaveformDataLeft) {\r
-          s.waveformData = {\r
-            left: oWaveformDataLeft.split(','),\r
-            right: oWaveformDataRight.split(',')\r
-          };\r
-        }\r
-\r
-        if (instanceOptions.useEQData) {\r
-          if (oEQData !== _undefined && oEQData && oEQData.leftEQ) {\r
-            eqLeft = oEQData.leftEQ.split(',');\r
-            s.eqData = eqLeft;\r
-            s.eqData.left = eqLeft;\r
-            if (oEQData.rightEQ !== _undefined && oEQData.rightEQ) {\r
-              s.eqData.right = oEQData.rightEQ.split(',');\r
-            }\r
-          }\r
-        }\r
-\r
-      }\r
-\r
-      if (s.playState === 1) {\r
-\r
-        // special case/hack: ensure buffering is false if loading from cache (and not yet started)\r
-        if (!s.isHTML5 && fV === 8 && !s.position && s.isBuffering) {\r
-          s._onbufferchange(0);\r
-        }\r
-\r
-        if (instanceOptions.whileplaying) {\r
-          // flash may call after actual finish\r
-          instanceOptions.whileplaying.apply(s);\r
-        }\r
-\r
-      }\r
-\r
-      return true;\r
-\r
-    };\r
-\r
-    this._oncaptiondata = function(oData) {\r
-\r
-      /**\r
-       * internal: flash 9 + NetStream (MovieStar/RTMP-only) feature\r
-       *\r
-       * @param {object} oData\r
-       */\r
-\r
-      sm2._wD(s.id + ': Caption data received.');\r
-\r
-      s.captiondata = oData;\r
-\r
-      if (s._iO.oncaptiondata) {\r
-        s._iO.oncaptiondata.apply(s, [oData]);\r
-      }\r
-\r
-    };\r
-\r
-    this._onmetadata = function(oMDProps, oMDData) {\r
-\r
-      /**\r
-       * internal: flash 9 + NetStream (MovieStar/RTMP-only) feature\r
-       * RTMP may include song title, MovieStar content may include encoding info\r
-       *\r
-       * @param {array} oMDProps (names)\r
-       * @param {array} oMDData (values)\r
-       */\r
-\r
-      sm2._wD(s.id + ': Metadata received.');\r
-\r
-      var oData = {}, i, j;\r
-\r
-      for (i = 0, j = oMDProps.length; i < j; i++) {\r
-        oData[oMDProps[i]] = oMDData[i];\r
-      }\r
-      s.metadata = oData;\r
-\r
-      if (s._iO.onmetadata) {\r
-        s._iO.onmetadata.apply(s);\r
-      }\r
-\r
-    };\r
-\r
-    this._onid3 = function(oID3Props, oID3Data) {\r
-\r
-      /**\r
-       * internal: flash 8 + flash 9 ID3 feature\r
-       * may include artist, song title etc.\r
-       *\r
-       * @param {array} oID3Props (names)\r
-       * @param {array} oID3Data (values)\r
-       */\r
-\r
-      sm2._wD(s.id + ': ID3 data received.');\r
-\r
-      var oData = [], i, j;\r
-\r
-      for (i = 0, j = oID3Props.length; i < j; i++) {\r
-        oData[oID3Props[i]] = oID3Data[i];\r
-      }\r
-      s.id3 = mixin(s.id3, oData);\r
-\r
-      if (s._iO.onid3) {\r
-        s._iO.onid3.apply(s);\r
-      }\r
-\r
-    };\r
-\r
-    // flash/RTMP-only\r
-\r
-    this._onconnect = function(bSuccess) {\r
-\r
-      bSuccess = (bSuccess === 1);\r
-      sm2._wD(s.id + ': ' + (bSuccess ? 'Connected.' : 'Failed to connect? - ' + s.url), (bSuccess ? 1 : 2));\r
-      s.connected = bSuccess;\r
-\r
-      if (bSuccess) {\r
-\r
-        s.failures = 0;\r
-\r
-        if (idCheck(s.id)) {\r
-          if (s.getAutoPlay()) {\r
-            // only update the play state if auto playing\r
-            s.play(_undefined, s.getAutoPlay());\r
-          } else if (s._iO.autoLoad) {\r
-            s.load();\r
-          }\r
-        }\r
-\r
-        if (s._iO.onconnect) {\r
-          s._iO.onconnect.apply(s, [bSuccess]);\r
-        }\r
-\r
-      }\r
-\r
-    };\r
-\r
-    this._ondataerror = function(sError) {\r
-\r
-      // flash 9 wave/eq data handler\r
-      // hack: called at start, and end from flash at/after onfinish()\r
-      if (s.playState > 0) {\r
-        sm2._wD(s.id + ': Data error: ' + sError);\r
-        if (s._iO.ondataerror) {\r
-          s._iO.ondataerror.apply(s);\r
-        }\r
-      }\r
-\r
-    };\r
-\r
-    // <d>\r
-    this._debug();\r
-    // </d>\r
-\r
-  }; // SMSound()\r
-\r
-  /**\r
-   * Private SoundManager internals\r
-   * ------------------------------\r
-   */\r
-\r
-  getDocument = function() {\r
-\r
-    return (doc.body || doc.getElementsByTagName('div')[0]);\r
-\r
-  };\r
-\r
-  id = function(sID) {\r
-\r
-    return doc.getElementById(sID);\r
-\r
-  };\r
-\r
-  mixin = function(oMain, oAdd) {\r
-\r
-    // non-destructive merge\r
-    var o1 = (oMain || {}), o2, o;\r
-\r
-    // if unspecified, o2 is the default options object\r
-    o2 = (oAdd === _undefined ? sm2.defaultOptions : oAdd);\r
-\r
-    for (o in o2) {\r
-\r
-      if (o2.hasOwnProperty(o) && o1[o] === _undefined) {\r
-\r
-        if (typeof o2[o] !== 'object' || o2[o] === null) {\r
-\r
-          // assign directly\r
-          o1[o] = o2[o];\r
-\r
-        } else {\r
-\r
-          // recurse through o2\r
-          o1[o] = mixin(o1[o], o2[o]);\r
-\r
-        }\r
-\r
-      }\r
-\r
-    }\r
-\r
-    return o1;\r
-\r
-  };\r
-\r
-  wrapCallback = function(oSound, callback) {\r
-\r
-    /**\r
-     * 03/03/2013: Fix for Flash Player 11.6.602.171 + Flash 8 (flashVersion = 8) SWF issue\r
-     * setTimeout() fix for certain SMSound callbacks like onload() and onfinish(), where subsequent calls like play() and load() fail when Flash Player 11.6.602.171 is installed, and using soundManager with flashVersion = 8 (which is the default).\r
-     * Not sure of exact cause. Suspect race condition and/or invalid (NaN-style) position argument trickling down to the next JS -> Flash _start() call, in the play() case.\r
-     * Fix: setTimeout() to yield, plus safer null / NaN checking on position argument provided to Flash.\r
-     * https://getsatisfaction.com/schillmania/topics/recent_chrome_update_seems_to_have_broken_my_sm2_audio_player\r
-     */\r
-    if (!oSound.isHTML5 && fV === 8) {\r
-      window.setTimeout(callback, 0);\r
-    } else {\r
-      callback();\r
-    }\r
-\r
-  };\r
-\r
-  // additional soundManager properties that soundManager.setup() will accept\r
-\r
-  extraOptions = {\r
-    'onready': 1,\r
-    'ontimeout': 1,\r
-    'defaultOptions': 1,\r
-    'flash9Options': 1,\r
-    'movieStarOptions': 1\r
-  };\r
-\r
-  assign = function(o, oParent) {\r
-\r
-    /**\r
-     * recursive assignment of properties, soundManager.setup() helper\r
-     * allows property assignment based on whitelist\r
-     */\r
-\r
-    var i,\r
-        result = true,\r
-        hasParent = (oParent !== _undefined),\r
-        setupOptions = sm2.setupOptions,\r
-        bonusOptions = extraOptions;\r
-\r
-    // <d>\r
-\r
-    // if soundManager.setup() called, show accepted parameters.\r
-\r
-    if (o === _undefined) {\r
-\r
-      result = [];\r
-\r
-      for (i in setupOptions) {\r
-\r
-        if (setupOptions.hasOwnProperty(i)) {\r
-          result.push(i);\r
-        }\r
-\r
-      }\r
-\r
-      for (i in bonusOptions) {\r
-\r
-        if (bonusOptions.hasOwnProperty(i)) {\r
-\r
-          if (typeof sm2[i] === 'object') {\r
-\r
-            result.push(i+': {...}');\r
-\r
-          } else if (sm2[i] instanceof Function) {\r
-\r
-            result.push(i+': function() {...}');\r
-\r
-          } else {\r
-\r
-            result.push(i);\r
-\r
-          }\r
-\r
-        }\r
-\r
-      }\r
-\r
-      sm2._wD(str('setup', result.join(', ')));\r
-\r
-      return false;\r
-\r
-    }\r
-\r
-    // </d>\r
-\r
-    for (i in o) {\r
-\r
-      if (o.hasOwnProperty(i)) {\r
-\r
-        // if not an {object} we want to recurse through...\r
-\r
-        if (typeof o[i] !== 'object' || o[i] === null || o[i] instanceof Array || o[i] instanceof RegExp) {\r
-\r
-          // check "allowed" options\r
-\r
-          if (hasParent && bonusOptions[oParent] !== _undefined) {\r
-\r
-            // valid recursive / nested object option, eg., { defaultOptions: { volume: 50 } }\r
-            sm2[oParent][i] = o[i];\r
-\r
-          } else if (setupOptions[i] !== _undefined) {\r
-\r
-            // special case: assign to setupOptions object, which soundManager property references\r
-            sm2.setupOptions[i] = o[i];\r
-\r
-            // assign directly to soundManager, too\r
-            sm2[i] = o[i];\r
-\r
-          } else if (bonusOptions[i] === _undefined) {\r
-\r
-            // invalid or disallowed parameter. complain.\r
-            complain(str((sm2[i] === _undefined ? 'setupUndef' : 'setupError'), i), 2);\r
-\r
-            result = false;\r
-\r
-          } else {\r
-\r
-            /**\r
-             * valid extraOptions (bonusOptions) parameter.\r
-             * is it a method, like onready/ontimeout? call it.\r
-             * multiple parameters should be in an array, eg. soundManager.setup({onready: [myHandler, myScope]});\r
-             */\r
-\r
-            if (sm2[i] instanceof Function) {\r
-\r
-              sm2[i].apply(sm2, (o[i] instanceof Array? o[i] : [o[i]]));\r
-\r
-            } else {\r
-\r
-              // good old-fashioned direct assignment\r
-              sm2[i] = o[i];\r
-\r
-            }\r
-\r
-          }\r
-\r
-        } else {\r
-\r
-          // recursion case, eg., { defaultOptions: { ... } }\r
-\r
-          if (bonusOptions[i] === _undefined) {\r
-\r
-            // invalid or disallowed parameter. complain.\r
-            complain(str((sm2[i] === _undefined ? 'setupUndef' : 'setupError'), i), 2);\r
-\r
-            result = false;\r
-\r
-          } else {\r
-\r
-            // recurse through object\r
-            return assign(o[i], i);\r
-\r
-          }\r
-\r
-        }\r
-\r
-      }\r
-\r
-    }\r
-\r
-    return result;\r
-\r
-  };\r
-\r
-  function preferFlashCheck(kind) {\r
-\r
-    // whether flash should play a given type\r
-    return (sm2.preferFlash && hasFlash && !sm2.ignoreFlash && (sm2.flash[kind] !== _undefined && sm2.flash[kind]));\r
-\r
-  }\r
-\r
-  /**\r
-   * Internal DOM2-level event helpers\r
-   * ---------------------------------\r
-   */\r
-\r
-  event = (function() {\r
-\r
-    // normalize event methods\r
-    var old = (window.attachEvent),\r
-    evt = {\r
-      add: (old?'attachEvent':'addEventListener'),\r
-      remove: (old?'detachEvent':'removeEventListener')\r
-    };\r
-\r
-    // normalize "on" event prefix, optional capture argument\r
-    function getArgs(oArgs) {\r
-\r
-      var args = slice.call(oArgs),\r
-          len = args.length;\r
-\r
-      if (old) {\r
-        // prefix\r
-        args[1] = 'on' + args[1];\r
-        if (len > 3) {\r
-          // no capture\r
-          args.pop();\r
-        }\r
-      } else if (len === 3) {\r
-        args.push(false);\r
-      }\r
-\r
-      return args;\r
-\r
-    }\r
-\r
-    function apply(args, sType) {\r
-\r
-      // normalize and call the event method, with the proper arguments\r
-      var element = args.shift(),\r
-          method = [evt[sType]];\r
-\r
-      if (old) {\r
-        // old IE can't do apply().\r
-        element[method](args[0], args[1]);\r
-      } else {\r
-        element[method].apply(element, args);\r
-      }\r
-\r
-    }\r
-\r
-    function add() {\r
-\r
-      apply(getArgs(arguments), 'add');\r
-\r
-    }\r
-\r
-    function remove() {\r
-\r
-      apply(getArgs(arguments), 'remove');\r
-\r
-    }\r
-\r
-    return {\r
-      'add': add,\r
-      'remove': remove\r
-    };\r
-\r
-  }());\r
-\r
-  /**\r
-   * Internal HTML5 event handling\r
-   * -----------------------------\r
-   */\r
-\r
-  function html5_event(oFn) {\r
-\r
-    // wrap html5 event handlers so we don't call them on destroyed and/or unloaded sounds\r
-\r
-    return function(e) {\r
-\r
-      var s = this._s,\r
-          result;\r
-\r
-      if (!s || !s._a) {\r
-        // <d>\r
-        if (s && s.id) {\r
-          sm2._wD(s.id + ': Ignoring ' + e.type);\r
-        } else {\r
-          sm2._wD(h5 + 'Ignoring ' + e.type);\r
-        }\r
-        // </d>\r
-        result = null;\r
-      } else {\r
-        result = oFn.call(this, e);\r
-      }\r
-\r
-      return result;\r
-\r
-    };\r
-\r
-  }\r
-\r
-  html5_events = {\r
-\r
-    // HTML5 event-name-to-handler map\r
-\r
-    abort: html5_event(function() {\r
-\r
-      sm2._wD(this._s.id + ': abort');\r
-\r
-    }),\r
-\r
-    // enough has loaded to play\r
-\r
-    canplay: html5_event(function() {\r
-\r
-      var s = this._s,\r
-          position1K;\r
-\r
-      if (s._html5_canplay) {\r
-        // this event has already fired. ignore.\r
-        return true;\r
-      }\r
-\r
-      s._html5_canplay = true;\r
-      sm2._wD(s.id + ': canplay');\r
-      s._onbufferchange(0);\r
-\r
-      // position according to instance options\r
-      position1K = (s._iO.position !== _undefined && !isNaN(s._iO.position)?s._iO.position/msecScale:null);\r
-\r
-      // set the position if position was set before the sound loaded\r
-      if (s.position && this.currentTime !== position1K) {\r
-        sm2._wD(s.id + ': canplay: Setting position to ' + position1K);\r
-        try {\r
-          this.currentTime = position1K;\r
-        } catch(ee) {\r
-          sm2._wD(s.id + ': canplay: Setting position of ' + position1K + ' failed: ' + ee.message, 2);\r
-        }\r
-      }\r
-\r
-      // hack for HTML5 from/to case\r
-      if (s._iO._oncanplay) {\r
-        s._iO._oncanplay();\r
-      }\r
-\r
-    }),\r
-\r
-    canplaythrough: html5_event(function() {\r
-\r
-      var s = this._s;\r
-\r
-      if (!s.loaded) {\r
-        s._onbufferchange(0);\r
-        s._whileloading(s.bytesLoaded, s.bytesTotal, s._get_html5_duration());\r
-        s._onload(true);\r
-      }\r
-\r
-    }),\r
-\r
-    // TODO: Reserved for potential use\r
-    /*\r
-    emptied: html5_event(function() {\r
-\r
-      sm2._wD(this._s.id + ': emptied');\r
-\r
-    }),\r
-    */\r
-\r
-    ended: html5_event(function() {\r
-\r
-      var s = this._s;\r
-\r
-      sm2._wD(s.id + ': ended');\r
-\r
-      s._onfinish();\r
-\r
-    }),\r
-\r
-    error: html5_event(function() {\r
-\r
-      sm2._wD(this._s.id + ': HTML5 error, code ' + this.error.code);\r
-      /**\r
-       * HTML5 error codes, per W3C\r
-       * Error 1: Client aborted download at user's request.\r
-       * Error 2: Network error after load started.\r
-       * Error 3: Decoding issue.\r
-       * Error 4: Media (audio file) not supported.\r
-       * Reference: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#error-codes\r
-       */\r
-      // call load with error state?\r
-      this._s._onload(false);\r
-\r
-    }),\r
-\r
-    loadeddata: html5_event(function() {\r
-\r
-      var s = this._s;\r
-\r
-      sm2._wD(s.id + ': loadeddata');\r
-\r
-      // safari seems to nicely report progress events, eventually totalling 100%\r
-      if (!s._loaded && !isSafari) {\r
-        s.duration = s._get_html5_duration();\r
-      }\r
-\r
-    }),\r
-\r
-    loadedmetadata: html5_event(function() {\r
-\r
-      sm2._wD(this._s.id + ': loadedmetadata');\r
-\r
-    }),\r
-\r
-    loadstart: html5_event(function() {\r
-\r
-      sm2._wD(this._s.id + ': loadstart');\r
-      // assume buffering at first\r
-      this._s._onbufferchange(1);\r
-\r
-    }),\r
-\r
-    play: html5_event(function() {\r
-\r
-      // sm2._wD(this._s.id + ': play()');\r
-      // once play starts, no buffering\r
-      this._s._onbufferchange(0);\r
-\r
-    }),\r
-\r
-    playing: html5_event(function() {\r
-\r
-      sm2._wD(this._s.id + ': playing');\r
-      // once play starts, no buffering\r
-      this._s._onbufferchange(0);\r
-\r
-    }),\r
-\r
-    progress: html5_event(function(e) {\r
-\r
-      // note: can fire repeatedly after "loaded" event, due to use of HTTP range/partials\r
-\r
-      var s = this._s,\r
-          i, j, progStr, buffered = 0,\r
-          isProgress = (e.type === 'progress'),\r
-          ranges = e.target.buffered,\r
-          // firefox 3.6 implements e.loaded/total (bytes)\r
-          loaded = (e.loaded||0),\r
-          total = (e.total||1);\r
-\r
-      // reset the "buffered" (loaded byte ranges) array\r
-      s.buffered = [];\r
-\r
-      if (ranges && ranges.length) {\r
-\r
-        // if loaded is 0, try TimeRanges implementation as % of load\r
-        // https://developer.mozilla.org/en/DOM/TimeRanges\r
-\r
-        // re-build "buffered" array\r
-        // HTML5 returns seconds. SM2 API uses msec for setPosition() etc., whether Flash or HTML5.\r
-        for (i=0, j=ranges.length; i<j; i++) {\r
-          s.buffered.push({\r
-            'start': ranges.start(i) * msecScale,\r
-            'end': ranges.end(i) * msecScale\r
-          });\r
-        }\r
-\r
-        // use the last value locally\r
-        buffered = (ranges.end(0) - ranges.start(0)) * msecScale;\r
-\r
-        // linear case, buffer sum; does not account for seeking and HTTP partials / byte ranges\r
-        loaded = Math.min(1, buffered/(e.target.duration*msecScale));\r
-\r
-        // <d>\r
-        if (isProgress && ranges.length > 1) {\r
-          progStr = [];\r
-          j = ranges.length;\r
-          for (i=0; i<j; i++) {\r
-            progStr.push(e.target.buffered.start(i)*msecScale +'-'+ e.target.buffered.end(i)*msecScale);\r
-          }\r
-          sm2._wD(this._s.id + ': progress, timeRanges: ' + progStr.join(', '));\r
-        }\r
-\r
-        if (isProgress && !isNaN(loaded)) {\r
-          sm2._wD(this._s.id + ': progress, ' + Math.floor(loaded*100) + '% loaded');\r
-        }\r
-        // </d>\r
-\r
-      }\r
-\r
-      if (!isNaN(loaded)) {\r
-\r
-        // if progress, likely not buffering\r
-        s._onbufferchange(0);\r
-        // TODO: prevent calls with duplicate values.\r
-        s._whileloading(loaded, total, s._get_html5_duration());\r
-        if (loaded && total && loaded === total) {\r
-          // in case "onload" doesn't fire (eg. gecko 1.9.2)\r
-          html5_events.canplaythrough.call(this, e);\r
-        }\r
-\r
-      }\r
-\r
-    }),\r
-\r
-    ratechange: html5_event(function() {\r
-\r
-      sm2._wD(this._s.id + ': ratechange');\r
-\r
-    }),\r
-\r
-    suspend: html5_event(function(e) {\r
-\r
-      // download paused/stopped, may have finished (eg. onload)\r
-      var s = this._s;\r
-\r
-      sm2._wD(this._s.id + ': suspend');\r
-      html5_events.progress.call(this, e);\r
-      s._onsuspend();\r
-\r
-    }),\r
-\r
-    stalled: html5_event(function() {\r
-\r
-      sm2._wD(this._s.id + ': stalled');\r
-\r
-    }),\r
-\r
-    timeupdate: html5_event(function() {\r
-\r
-      this._s._onTimer();\r
-\r
-    }),\r
-\r
-    waiting: html5_event(function() {\r
-\r
-      var s = this._s;\r
-\r
-      // see also: seeking\r
-      sm2._wD(this._s.id + ': waiting');\r
-\r
-      // playback faster than download rate, etc.\r
-      s._onbufferchange(1);\r
-\r
-    })\r
-\r
-  };\r
-\r
-  html5OK = function(iO) {\r
-\r
-    // playability test based on URL or MIME type\r
-\r
-    var result;\r
-\r
-    if (!iO || (!iO.type && !iO.url && !iO.serverURL)) {\r
-\r
-      // nothing to check\r
-      result = false;\r
-\r
-    } else if (iO.serverURL || (iO.type && preferFlashCheck(iO.type))) {\r
-\r
-      // RTMP, or preferring flash\r
-      result = false;\r
-\r
-    } else {\r
-\r
-      // Use type, if specified. Pass data: URIs to HTML5. If HTML5-only mode, no other options, so just give 'er\r
-      result = ((iO.type ? html5CanPlay({type:iO.type}) : html5CanPlay({url:iO.url}) || sm2.html5Only || iO.url.match(/data\:/i)));\r
-\r
-    }\r
-\r
-    return result;\r
-\r
-  };\r
-\r
-  html5Unload = function(oAudio) {\r
-\r
-    /**\r
-     * Internal method: Unload media, and cancel any current/pending network requests.\r
-     * Firefox can load an empty URL, which allegedly destroys the decoder and stops the download.\r
-     * https://developer.mozilla.org/En/Using_audio_and_video_in_Firefox#Stopping_the_download_of_media\r
-     * However, Firefox has been seen loading a relative URL from '' and thus requesting the hosting page on unload.\r
-     * Other UA behaviour is unclear, so everyone else gets an about:blank-style URL.\r
-     */\r
-\r
-    var url;\r
-\r
-    if (oAudio) {\r
-\r
-      // Firefox and Chrome accept short WAVe data: URIs. Chome dislikes audio/wav, but accepts audio/wav for data: MIME.\r
-      // Desktop Safari complains / fails on data: URI, so it gets about:blank.\r
-      url = (isSafari ? emptyURL : (sm2.html5.canPlayType('audio/wav') ? emptyWAV : emptyURL));\r
-\r
-      oAudio.src = url;\r
-\r
-      // reset some state, too\r
-      if (oAudio._called_unload !== undefined) {\r
-        oAudio._called_load = false;\r
-      }\r
-\r
-    }\r
-\r
-    if (useGlobalHTML5Audio) {\r
-\r
-      // ensure URL state is trashed, also\r
-      lastGlobalHTML5URL = null;\r
-\r
-    }\r
-\r
-    return url;\r
-\r
-  };\r
-\r
-  html5CanPlay = function(o) {\r
-\r
-    /**\r
-     * Try to find MIME, test and return truthiness\r
-     * o = {\r
-     *  url: '/path/to/an.mp3',\r
-     *  type: 'audio/mp3'\r
-     * }\r
-     */\r
-\r
-    if (!sm2.useHTML5Audio || !sm2.hasHTML5) {\r
-      return false;\r
-    }\r
-\r
-    var url = (o.url || null),\r
-        mime = (o.type || null),\r
-        aF = sm2.audioFormats,\r
-        result,\r
-        offset,\r
-        fileExt,\r
-        item;\r
-\r
-    // account for known cases like audio/mp3\r
-\r
-    if (mime && sm2.html5[mime] !== _undefined) {\r
-      return (sm2.html5[mime] && !preferFlashCheck(mime));\r
-    }\r
-\r
-    if (!html5Ext) {\r
-      html5Ext = [];\r
-      for (item in aF) {\r
-        if (aF.hasOwnProperty(item)) {\r
-          html5Ext.push(item);\r
-          if (aF[item].related) {\r
-            html5Ext = html5Ext.concat(aF[item].related);\r
-          }\r
-        }\r
-      }\r
-      html5Ext = new RegExp('\\.('+html5Ext.join('|')+')(\\?.*)?$','i');\r
-    }\r
-\r
-    // TODO: Strip URL queries, etc.\r
-    fileExt = (url ? url.toLowerCase().match(html5Ext) : null);\r
-\r
-    if (!fileExt || !fileExt.length) {\r
-      if (!mime) {\r
-        result = false;\r
-      } else {\r
-        // audio/mp3 -> mp3, result should be known\r
-        offset = mime.indexOf(';');\r
-        // strip "audio/X; codecs..."\r
-        fileExt = (offset !== -1?mime.substr(0,offset):mime).substr(6);\r
-      }\r
-    } else {\r
-      // match the raw extension name - "mp3", for example\r
-      fileExt = fileExt[1];\r
-    }\r
-\r
-    if (fileExt && sm2.html5[fileExt] !== _undefined) {\r
-      // result known\r
-      result = (sm2.html5[fileExt] && !preferFlashCheck(fileExt));\r
-    } else {\r
-      mime = 'audio/'+fileExt;\r
-      result = sm2.html5.canPlayType({type:mime});\r
-      sm2.html5[fileExt] = result;\r
-      // sm2._wD('canPlayType, found result: ' + result);\r
-      result = (result && sm2.html5[mime] && !preferFlashCheck(mime));\r
-    }\r
-\r
-    return result;\r
-\r
-  };\r
-\r
-  testHTML5 = function() {\r
-\r
-    /**\r
-     * Internal: Iterates over audioFormats, determining support eg. audio/mp3, audio/mpeg and so on\r
-     * assigns results to html5[] and flash[].\r
-     */\r
-\r
-    if (!sm2.useHTML5Audio || !sm2.hasHTML5) {\r
-      // without HTML5, we need Flash.\r
-      sm2.html5.usingFlash = true;\r
-      needsFlash = true;\r
-      return false;\r
-    }\r
-\r
-    // double-whammy: Opera 9.64 throws WRONG_ARGUMENTS_ERR if no parameter passed to Audio(), and Webkit + iOS happily tries to load "null" as a URL. :/\r
-    var a = (Audio !== _undefined ? (isOpera && opera.version() < 10 ? new Audio(null) : new Audio()) : null),\r
-        item, lookup, support = {}, aF, i;\r
-\r
-    function cp(m) {\r
-\r
-      var canPlay, j,\r
-          result = false,\r
-          isOK = false;\r
-\r
-      if (!a || typeof a.canPlayType !== 'function') {\r
-        return result;\r
-      }\r
-\r
-      if (m instanceof Array) {\r
-        // iterate through all mime types, return any successes\r
-        for (i=0, j=m.length; i<j; i++) {\r
-          if (sm2.html5[m[i]] || a.canPlayType(m[i]).match(sm2.html5Test)) {\r
-            isOK = true;\r
-            sm2.html5[m[i]] = true;\r
-            // note flash support, too\r
-            sm2.flash[m[i]] = !!(m[i].match(flashMIME));\r
-          }\r
-        }\r
-        result = isOK;\r
-      } else {\r
-        canPlay = (a && typeof a.canPlayType === 'function' ? a.canPlayType(m) : false);\r
-        result = !!(canPlay && (canPlay.match(sm2.html5Test)));\r
-      }\r
-\r
-      return result;\r
-\r
-    }\r
-\r
-    // test all registered formats + codecs\r
-\r
-    aF = sm2.audioFormats;\r
-\r
-    for (item in aF) {\r
-\r
-      if (aF.hasOwnProperty(item)) {\r
-\r
-        lookup = 'audio/' + item;\r
-\r
-        support[item] = cp(aF[item].type);\r
-\r
-        // write back generic type too, eg. audio/mp3\r
-        support[lookup] = support[item];\r
-\r
-        // assign flash\r
-        if (item.match(flashMIME)) {\r
-\r
-          sm2.flash[item] = true;\r
-          sm2.flash[lookup] = true;\r
-\r
-        } else {\r
-\r
-          sm2.flash[item] = false;\r
-          sm2.flash[lookup] = false;\r
-\r
-        }\r
-\r
-        // assign result to related formats, too\r
-\r
-        if (aF[item] && aF[item].related) {\r
-\r
-          for (i=aF[item].related.length-1; i >= 0; i--) {\r
-\r
-            // eg. audio/m4a\r
-            support['audio/'+aF[item].related[i]] = support[item];\r
-            sm2.html5[aF[item].related[i]] = support[item];\r
-            sm2.flash[aF[item].related[i]] = support[item];\r
-\r
-          }\r
-\r
-        }\r
-\r
-      }\r
-\r
-    }\r
-\r
-    support.canPlayType = (a?cp:null);\r
-    sm2.html5 = mixin(sm2.html5, support);\r
-\r
-    sm2.html5.usingFlash = featureCheck();\r
-    needsFlash = sm2.html5.usingFlash;\r
-\r
-    return true;\r
-\r
-  };\r
-\r
-  strings = {\r
-\r
-    // <d>\r
-    notReady: 'Unavailable - wait until onready() has fired.',\r
-    notOK: 'Audio support is not available.',\r
-    domError: sm + 'exception caught while appending SWF to DOM.',\r
-    spcWmode: 'Removing wmode, preventing known SWF loading issue(s)',\r
-    swf404: smc + 'Verify that %s is a valid path.',\r
-    tryDebug: 'Try ' + sm + '.debugFlash = true for more security details (output goes to SWF.)',\r
-    checkSWF: 'See SWF output for more debug info.',\r
-    localFail: smc + 'Non-HTTP page (' + doc.location.protocol + ' URL?) Review Flash player security settings for this special case:\nhttp://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html\nMay need to add/allow path, eg. c:/sm2/ or /users/me/sm2/',\r
-    waitFocus: smc + 'Special case: Waiting for SWF to load with window focus...',\r
-    waitForever: smc + 'Waiting indefinitely for Flash (will recover if unblocked)...',\r
-    waitSWF: smc + 'Waiting for 100% SWF load...',\r
-    needFunction: smc + 'Function object expected for %s',\r
-    badID: 'Sound ID "%s" should be a string, starting with a non-numeric character',\r
-    currentObj: smc + '_debug(): Current sound objects',\r
-    waitOnload: smc + 'Waiting for window.onload()',\r
-    docLoaded: smc + 'Document already loaded',\r
-    onload: smc + 'initComplete(): calling soundManager.onload()',\r
-    onloadOK: sm + '.onload() complete',\r
-    didInit: smc + 'init(): Already called?',\r
-    secNote: 'Flash security note: Network/internet URLs will not load due to security restrictions. Access can be configured via Flash Player Global Security Settings Page: http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html',\r
-    badRemove: smc + 'Failed to remove Flash node.',\r
-    shutdown: sm + '.disable(): Shutting down',\r
-    queue: smc + 'Queueing %s handler',\r
-    smError: 'SMSound.load(): Exception: JS-Flash communication failed, or JS error.',\r
-    fbTimeout: 'No flash response, applying .'+swfCSS.swfTimedout+' CSS...',\r
-    fbLoaded: 'Flash loaded',\r
-    fbHandler: smc + 'flashBlockHandler()',\r
-    manURL: 'SMSound.load(): Using manually-assigned URL',\r
-    onURL: sm + '.load(): current URL already assigned.',\r
-    badFV: sm + '.flashVersion must be 8 or 9. "%s" is invalid. Reverting to %s.',\r
-    as2loop: 'Note: Setting stream:false so looping can work (flash 8 limitation)',\r
-    noNSLoop: 'Note: Looping not implemented for MovieStar formats',\r
-    needfl9: 'Note: Switching to flash 9, required for MP4 formats.',\r
-    mfTimeout: 'Setting flashLoadTimeout = 0 (infinite) for off-screen, mobile flash case',\r
-    needFlash: smc + 'Fatal error: Flash is needed to play some required formats, but is not available.',\r
-    gotFocus: smc + 'Got window focus.',\r
-    policy: 'Enabling usePolicyFile for data access',\r
-    setup: sm + '.setup(): allowed parameters: %s',\r
-    setupError: sm + '.setup(): "%s" cannot be assigned with this method.',\r
-    setupUndef: sm + '.setup(): Could not find option "%s"',\r
-    setupLate: sm + '.setup(): url, flashVersion and html5Test property changes will not take effect until reboot().',\r
-    noURL: smc + 'Flash URL required. Call soundManager.setup({url:...}) to get started.',\r
-    sm2Loaded: 'SoundManager 2: Ready.',\r
-    reset: sm + '.reset(): Removing event callbacks',\r
-    mobileUA: 'Mobile UA detected, preferring HTML5 by default.',\r
-    globalHTML5: 'Using singleton HTML5 Audio() pattern for this device.'\r
-    // </d>\r
-\r
-  };\r
-\r
-  str = function() {\r
-\r
-    // internal string replace helper.\r
-    // arguments: o [,items to replace]\r
-    // <d>\r
-\r
-    var args,\r
-        i, j, o,\r
-        sstr;\r
-\r
-    // real array, please\r
-    args = slice.call(arguments);\r
-\r
-    // first argument\r
-    o = args.shift();\r
-\r
-    sstr = (strings && strings[o] ? strings[o] : '');\r
-\r
-    if (sstr && args && args.length) {\r
-      for (i = 0, j = args.length; i < j; i++) {\r
-        sstr = sstr.replace('%s', args[i]);\r
-      }\r
-    }\r
-\r
-    return sstr;\r
-    // </d>\r
-\r
-  };\r
-\r
-  loopFix = function(sOpt) {\r
-\r
-    // flash 8 requires stream = false for looping to work\r
-    if (fV === 8 && sOpt.loops > 1 && sOpt.stream) {\r
-      _wDS('as2loop');\r
-      sOpt.stream = false;\r
-    }\r
-\r
-    return sOpt;\r
-\r
-  };\r
-\r
-  policyFix = function(sOpt, sPre) {\r
-\r
-    if (sOpt && !sOpt.usePolicyFile && (sOpt.onid3 || sOpt.usePeakData || sOpt.useWaveformData || sOpt.useEQData)) {\r
-      sm2._wD((sPre || '') + str('policy'));\r
-      sOpt.usePolicyFile = true;\r
-    }\r
-\r
-    return sOpt;\r
-\r
-  };\r
-\r
-  complain = function(sMsg) {\r
-\r
-    // <d>\r
-    if (hasConsole && console.warn !== _undefined) {\r
-      console.warn(sMsg);\r
-    } else {\r
-      sm2._wD(sMsg);\r
-    }\r
-    // </d>\r
-\r
-  };\r
-\r
-  doNothing = function() {\r
-\r
-    return false;\r
-\r
-  };\r
-\r
-  disableObject = function(o) {\r
-\r
-    var oProp;\r
-\r
-    for (oProp in o) {\r
-      if (o.hasOwnProperty(oProp) && typeof o[oProp] === 'function') {\r
-        o[oProp] = doNothing;\r
-      }\r
-    }\r
-\r
-    oProp = null;\r
-\r
-  };\r
-\r
-  failSafely = function(bNoDisable) {\r
-\r
-    // general failure exception handler\r
-\r
-    if (bNoDisable === _undefined) {\r
-      bNoDisable = false;\r
-    }\r
-\r
-    if (disabled || bNoDisable) {\r
-      sm2.disable(bNoDisable);\r
-    }\r
-\r
-  };\r
-\r
-  normalizeMovieURL = function(smURL) {\r
-\r
-    var urlParams = null, url;\r
-\r
-    if (smURL) {\r
-      if (smURL.match(/\.swf(\?.*)?$/i)) {\r
-        urlParams = smURL.substr(smURL.toLowerCase().lastIndexOf('.swf?') + 4);\r
-        if (urlParams) {\r
-          // assume user knows what they're doing\r
-          return smURL;\r
-        }\r
-      } else if (smURL.lastIndexOf('/') !== smURL.length - 1) {\r
-        // append trailing slash, if needed\r
-        smURL += '/';\r
-      }\r
-    }\r
-\r
-    url = (smURL && smURL.lastIndexOf('/') !== - 1 ? smURL.substr(0, smURL.lastIndexOf('/') + 1) : './') + sm2.movieURL;\r
-\r
-    if (sm2.noSWFCache) {\r
-      url += ('?ts=' + new Date().getTime());\r
-    }\r
-\r
-    return url;\r
-\r
-  };\r
-\r
-  setVersionInfo = function() {\r
-\r
-    // short-hand for internal use\r
-\r
-    fV = parseInt(sm2.flashVersion, 10);\r
-\r
-    if (fV !== 8 && fV !== 9) {\r
-      sm2._wD(str('badFV', fV, defaultFlashVersion));\r
-      sm2.flashVersion = fV = defaultFlashVersion;\r
-    }\r
-\r
-    // debug flash movie, if applicable\r
-\r
-    var isDebug = (sm2.debugMode || sm2.debugFlash?'_debug.swf':'.swf');\r
-\r
-    if (sm2.useHTML5Audio && !sm2.html5Only && sm2.audioFormats.mp4.required && fV < 9) {\r
-      sm2._wD(str('needfl9'));\r
-      sm2.flashVersion = fV = 9;\r
-    }\r
-\r
-    sm2.version = sm2.versionNumber + (sm2.html5Only?' (HTML5-only mode)':(fV === 9?' (AS3/Flash 9)':' (AS2/Flash 8)'));\r
-\r
-    // set up default options\r
-    if (fV > 8) {\r
-      // +flash 9 base options\r
-      sm2.defaultOptions = mixin(sm2.defaultOptions, sm2.flash9Options);\r
-      sm2.features.buffering = true;\r
-      // +moviestar support\r
-      sm2.defaultOptions = mixin(sm2.defaultOptions, sm2.movieStarOptions);\r
-      sm2.filePatterns.flash9 = new RegExp('\\.(mp3|' + netStreamTypes.join('|') + ')(\\?.*)?$', 'i');\r
-      sm2.features.movieStar = true;\r
-    } else {\r
-      sm2.features.movieStar = false;\r
-    }\r
-\r
-    // regExp for flash canPlay(), etc.\r
-    sm2.filePattern = sm2.filePatterns[(fV !== 8?'flash9':'flash8')];\r
-\r
-    // if applicable, use _debug versions of SWFs\r
-    sm2.movieURL = (fV === 8?'soundmanager2.swf':'soundmanager2_flash9.swf').replace('.swf', isDebug);\r
-\r
-    sm2.features.peakData = sm2.features.waveformData = sm2.features.eqData = (fV > 8);\r
-\r
-  };\r
-\r
-  setPolling = function(bPolling, bHighPerformance) {\r
-\r
-    if (!flash) {\r
-      return false;\r
-    }\r
-\r
-    flash._setPolling(bPolling, bHighPerformance);\r
-\r
-  };\r
-\r
-  initDebug = function() {\r
-\r
-    // starts debug mode, creating output <div> for UAs without console object\r
-\r
-    // allow force of debug mode via URL\r
-    // <d>\r
-    if (sm2.debugURLParam.test(wl)) {\r
-      sm2.debugMode = true;\r
-    }\r
-\r
-    if (id(sm2.debugID)) {\r
-      return false;\r
-    }\r
-\r
-    var oD, oDebug, oTarget, oToggle, tmp;\r
-\r
-    if (sm2.debugMode && !id(sm2.debugID) && (!hasConsole || !sm2.useConsole || !sm2.consoleOnly)) {\r
-\r
-      oD = doc.createElement('div');\r
-      oD.id = sm2.debugID + '-toggle';\r
-\r
-      oToggle = {\r
-        'position': 'fixed',\r
-        'bottom': '0px',\r
-        'right': '0px',\r
-        'width': '1.2em',\r
-        'height': '1.2em',\r
-        'lineHeight': '1.2em',\r
-        'margin': '2px',\r
-        'textAlign': 'center',\r
-        'border': '1px solid #999',\r
-        'cursor': 'pointer',\r
-        'background': '#fff',\r
-        'color': '#333',\r
-        'zIndex': 10001\r
-      };\r
-\r
-      oD.appendChild(doc.createTextNode('-'));\r
-      oD.onclick = toggleDebug;\r
-      oD.title = 'Toggle SM2 debug console';\r
-\r
-      if (ua.match(/msie 6/i)) {\r
-        oD.style.position = 'absolute';\r
-        oD.style.cursor = 'hand';\r
-      }\r
-\r
-      for (tmp in oToggle) {\r
-        if (oToggle.hasOwnProperty(tmp)) {\r
-          oD.style[tmp] = oToggle[tmp];\r
-        }\r
-      }\r
-\r
-      oDebug = doc.createElement('div');\r
-      oDebug.id = sm2.debugID;\r
-      oDebug.style.display = (sm2.debugMode?'block':'none');\r
-\r
-      if (sm2.debugMode && !id(oD.id)) {\r
-        try {\r
-          oTarget = getDocument();\r
-          oTarget.appendChild(oD);\r
-        } catch(e2) {\r
-          throw new Error(str('domError')+' \n'+e2.toString());\r
-        }\r
-        oTarget.appendChild(oDebug);\r
-      }\r
-\r
-    }\r
-\r
-    oTarget = null;\r
-    // </d>\r
-\r
-  };\r
-\r
-  idCheck = this.getSoundById;\r
-\r
-  // <d>\r
-  _wDS = function(o, errorLevel) {\r
-\r
-    return (!o ? '' : sm2._wD(str(o), errorLevel));\r
-\r
-  };\r
-\r
-  toggleDebug = function() {\r
-\r
-    var o = id(sm2.debugID),\r
-    oT = id(sm2.debugID + '-toggle');\r
-\r
-    if (!o) {\r
-      return false;\r
-    }\r
-\r
-    if (debugOpen) {\r
-      // minimize\r
-      oT.innerHTML = '+';\r
-      o.style.display = 'none';\r
-    } else {\r
-      oT.innerHTML = '-';\r
-      o.style.display = 'block';\r
-    }\r
-\r
-    debugOpen = !debugOpen;\r
-\r
-  };\r
-\r
-  debugTS = function(sEventType, bSuccess, sMessage) {\r
-\r
-    // troubleshooter debug hooks\r
-\r
-    if (window.sm2Debugger !== _undefined) {\r
-      try {\r
-        sm2Debugger.handleEvent(sEventType, bSuccess, sMessage);\r
-      } catch(e) {\r
-        // oh well\r
-        return false;\r
-      }\r
-    }\r
-\r
-    return true;\r
-\r
-  };\r
-  // </d>\r
-\r
-  getSWFCSS = function() {\r
-\r
-    var css = [];\r
-\r
-    if (sm2.debugMode) {\r
-      css.push(swfCSS.sm2Debug);\r
-    }\r
-\r
-    if (sm2.debugFlash) {\r
-      css.push(swfCSS.flashDebug);\r
-    }\r
-\r
-    if (sm2.useHighPerformance) {\r
-      css.push(swfCSS.highPerf);\r
-    }\r
-\r
-    return css.join(' ');\r
-\r
-  };\r
-\r
-  flashBlockHandler = function() {\r
-\r
-    // *possible* flash block situation.\r
-\r
-    var name = str('fbHandler'),\r
-        p = sm2.getMoviePercent(),\r
-        css = swfCSS,\r
-        error = {type:'FLASHBLOCK'};\r
-\r
-    if (sm2.html5Only) {\r
-      // no flash, or unused\r
-      return false;\r
-    }\r
-\r
-    if (!sm2.ok()) {\r
-\r
-      if (needsFlash) {\r
-        // make the movie more visible, so user can fix\r
-        sm2.oMC.className = getSWFCSS() + ' ' + css.swfDefault + ' ' + (p === null?css.swfTimedout:css.swfError);\r
-        sm2._wD(name + ': ' + str('fbTimeout') + (p ? ' (' + str('fbLoaded') + ')' : ''));\r
-      }\r
-\r
-      sm2.didFlashBlock = true;\r
-\r
-      // fire onready(), complain lightly\r
-      processOnEvents({type:'ontimeout', ignoreInit:true, error:error});\r
-      catchError(error);\r
-\r
-    } else {\r
-\r
-      // SM2 loaded OK (or recovered)\r
-\r
-      // <d>\r
-      if (sm2.didFlashBlock) {\r
-        sm2._wD(name + ': Unblocked');\r
-      }\r
-      // </d>\r
-\r
-      if (sm2.oMC) {\r
-        sm2.oMC.className = [getSWFCSS(), css.swfDefault, css.swfLoaded + (sm2.didFlashBlock?' '+css.swfUnblocked:'')].join(' ');\r
-      }\r
-\r
-    }\r
-\r
-  };\r
-\r
-  addOnEvent = function(sType, oMethod, oScope) {\r
-\r
-    if (on_queue[sType] === _undefined) {\r
-      on_queue[sType] = [];\r
-    }\r
-\r
-    on_queue[sType].push({\r
-      'method': oMethod,\r
-      'scope': (oScope || null),\r
-      'fired': false\r
-    });\r
-\r
-  };\r
-\r
-  processOnEvents = function(oOptions) {\r
-\r
-    // if unspecified, assume OK/error\r
-\r
-    if (!oOptions) {\r
-      oOptions = {\r
-        type: (sm2.ok() ? 'onready' : 'ontimeout')\r
-      };\r
-    }\r
-\r
-    if (!didInit && oOptions && !oOptions.ignoreInit) {\r
-      // not ready yet.\r
-      return false;\r
-    }\r
-\r
-    if (oOptions.type === 'ontimeout' && (sm2.ok() || (disabled && !oOptions.ignoreInit))) {\r
-      // invalid case\r
-      return false;\r
-    }\r
-\r
-    var status = {\r
-          success: (oOptions && oOptions.ignoreInit?sm2.ok():!disabled)\r
-        },\r
-\r
-        // queue specified by type, or none\r
-        srcQueue = (oOptions && oOptions.type?on_queue[oOptions.type]||[]:[]),\r
-\r
-        queue = [], i, j,\r
-        args = [status],\r
-        canRetry = (needsFlash && !sm2.ok());\r
-\r
-    if (oOptions.error) {\r
-      args[0].error = oOptions.error;\r
-    }\r
-\r
-    for (i = 0, j = srcQueue.length; i < j; i++) {\r
-      if (srcQueue[i].fired !== true) {\r
-        queue.push(srcQueue[i]);\r
-      }\r
-    }\r
-\r
-    if (queue.length) {\r
-      // sm2._wD(sm + ': Firing ' + queue.length + ' ' + oOptions.type + '() item' + (queue.length === 1 ? '' : 's'));\r
-      for (i = 0, j = queue.length; i < j; i++) {\r
-        if (queue[i].scope) {\r
-          queue[i].method.apply(queue[i].scope, args);\r
-        } else {\r
-          queue[i].method.apply(this, args);\r
-        }\r
-        if (!canRetry) {\r
-          // useFlashBlock and SWF timeout case doesn't count here.\r
-          queue[i].fired = true;\r
-        }\r
-      }\r
-    }\r
-\r
-    return true;\r
-\r
-  };\r
-\r
-  initUserOnload = function() {\r
-\r
-    window.setTimeout(function() {\r
-\r
-      if (sm2.useFlashBlock) {\r
-        flashBlockHandler();\r
-      }\r
-\r
-      processOnEvents();\r
-\r
-      // call user-defined "onload", scoped to window\r
-\r
-      if (typeof sm2.onload === 'function') {\r
-        _wDS('onload', 1);\r
-        sm2.onload.apply(window);\r
-        _wDS('onloadOK', 1);\r
-      }\r
-\r
-      if (sm2.waitForWindowLoad) {\r
-        event.add(window, 'load', initUserOnload);\r
-      }\r
-\r
-    },1);\r
-\r
-  };\r
-\r
-  detectFlash = function() {\r
-\r
-    // hat tip: Flash Detect library (BSD, (C) 2007) by Carl "DocYes" S. Yestrau - http://featureblend.com/javascript-flash-detection-library.html / http://featureblend.com/license.txt\r
-\r
-    if (hasFlash !== _undefined) {\r
-      // this work has already been done.\r
-      return hasFlash;\r
-    }\r
-\r
-    var hasPlugin = false, n = navigator, nP = n.plugins, obj, type, types, AX = window.ActiveXObject;\r
-\r
-    if (nP && nP.length) {\r
-      type = 'application/x-shockwave-flash';\r
-      types = n.mimeTypes;\r
-      if (types && types[type] && types[type].enabledPlugin && types[type].enabledPlugin.description) {\r
-        hasPlugin = true;\r
-      }\r
-    } else if (AX !== _undefined && !ua.match(/MSAppHost/i)) {\r
-      // Windows 8 Store Apps (MSAppHost) are weird (compatibility?) and won't complain here, but will barf if Flash/ActiveX object is appended to the DOM.\r
-      try {\r
-        obj = new AX('ShockwaveFlash.ShockwaveFlash');\r
-      } catch(e) {\r
-        // oh well\r
-        obj = null;\r
-      }\r
-      hasPlugin = (!!obj);\r
-      // cleanup, because it is ActiveX after all\r
-      obj = null;\r
-    }\r
-\r
-    hasFlash = hasPlugin;\r
-\r
-    return hasPlugin;\r
-\r
-  };\r
-\r
-  featureCheck = function() {\r
-\r
-    var flashNeeded,\r
-        item,\r
-        formats = sm2.audioFormats,\r
-        // iPhone <= 3.1 has broken HTML5 audio(), but firmware 3.2 (original iPad) + iOS4 works.\r
-        isSpecial = (is_iDevice && !!(ua.match(/os (1|2|3_0|3_1)/i)));\r
-\r
-    if (isSpecial) {\r
-\r
-      // has Audio(), but is broken; let it load links directly.\r
-      sm2.hasHTML5 = false;\r
-\r
-      // ignore flash case, however\r
-      sm2.html5Only = true;\r
-\r
-      // hide the SWF, if present\r
-      if (sm2.oMC) {\r
-        sm2.oMC.style.display = 'none';\r
-      }\r
-\r
-    } else {\r
-\r
-      if (sm2.useHTML5Audio) {\r
-\r
-        if (!sm2.html5 || !sm2.html5.canPlayType) {\r
-          sm2._wD('SoundManager: No HTML5 Audio() support detected.');\r
-          sm2.hasHTML5 = false;\r
-        }\r
-\r
-        // <d>\r
-        if (isBadSafari) {\r
-          sm2._wD(smc + 'Note: Buggy HTML5 Audio in Safari on this OS X release, see https://bugs.webkit.org/show_bug.cgi?id=32159 - ' + (!hasFlash ?' would use flash fallback for MP3/MP4, but none detected.' : 'will use flash fallback for MP3/MP4, if available'), 1);\r
-        }\r
-        // </d>\r
-\r
-      }\r
-\r
-    }\r
-\r
-    if (sm2.useHTML5Audio && sm2.hasHTML5) {\r
-\r
-      // sort out whether flash is optional, required or can be ignored.\r
-\r
-      // innocent until proven guilty.\r
-      canIgnoreFlash = true;\r
-\r
-      for (item in formats) {\r
-        if (formats.hasOwnProperty(item)) {\r
-          if (formats[item].required) {\r
-            if (!sm2.html5.canPlayType(formats[item].type)) {\r
-              // 100% HTML5 mode is not possible.\r
-              canIgnoreFlash = false;\r
-              flashNeeded = true;\r
-            } else if (sm2.preferFlash && (sm2.flash[item] || sm2.flash[formats[item].type])) {\r
-              // flash may be required, or preferred for this format.\r
-              flashNeeded = true;\r
-            }\r
-          }\r
-        }\r
-      }\r
-\r
-    }\r
-\r
-    // sanity check...\r
-    if (sm2.ignoreFlash) {\r
-      flashNeeded = false;\r
-      canIgnoreFlash = true;\r
-    }\r
-\r
-    sm2.html5Only = (sm2.hasHTML5 && sm2.useHTML5Audio && !flashNeeded);\r
-\r
-    return (!sm2.html5Only);\r
-\r
-  };\r
-\r
-  parseURL = function(url) {\r
-\r
-    /**\r
-     * Internal: Finds and returns the first playable URL (or failing that, the first URL.)\r
-     * @param {string or array} url A single URL string, OR, an array of URL strings or {url:'/path/to/resource', type:'audio/mp3'} objects.\r
-     */\r
-\r
-    var i, j, urlResult = 0, result;\r
-\r
-    if (url instanceof Array) {\r
-\r
-      // find the first good one\r
-      for (i=0, j=url.length; i<j; i++) {\r
-\r
-        if (url[i] instanceof Object) {\r
-          // MIME check\r
-          if (sm2.canPlayMIME(url[i].type)) {\r
-            urlResult = i;\r
-            break;\r
-          }\r
-\r
-        } else if (sm2.canPlayURL(url[i])) {\r
-          // URL string check\r
-          urlResult = i;\r
-          break;\r
-        }\r
-\r
-      }\r
-\r
-      // normalize to string\r
-      if (url[urlResult].url) {\r
-        url[urlResult] = url[urlResult].url;\r
-      }\r
-\r
-      result = url[urlResult];\r
-\r
-    } else {\r
-\r
-      // single URL case\r
-      result = url;\r
-\r
-    }\r
-\r
-    return result;\r
-\r
-  };\r
-\r
-\r
-  startTimer = function(oSound) {\r
-\r
-    /**\r
-     * attach a timer to this sound, and start an interval if needed\r
-     */\r
-\r
-    if (!oSound._hasTimer) {\r
-\r
-      oSound._hasTimer = true;\r
-\r
-      if (!mobileHTML5 && sm2.html5PollingInterval) {\r
-\r
-        if (h5IntervalTimer === null && h5TimerCount === 0) {\r
-\r
-          h5IntervalTimer = setInterval(timerExecute, sm2.html5PollingInterval);\r
-\r
-        }\r
-\r
-        h5TimerCount++;\r
-\r
-      }\r
-\r
-    }\r
-\r
-  };\r
-\r
-  stopTimer = function(oSound) {\r
-\r
-    /**\r
-     * detach a timer\r
-     */\r
-\r
-    if (oSound._hasTimer) {\r
-\r
-      oSound._hasTimer = false;\r
-\r
-      if (!mobileHTML5 && sm2.html5PollingInterval) {\r
-\r
-        // interval will stop itself at next execution.\r
-\r
-        h5TimerCount--;\r
-\r
-      }\r
-\r
-    }\r
-\r
-  };\r
-\r
-  timerExecute = function() {\r
-\r
-    /**\r
-     * manual polling for HTML5 progress events, ie., whileplaying() (can achieve greater precision than conservative default HTML5 interval)\r
-     */\r
-\r
-    var i;\r
-\r
-    if (h5IntervalTimer !== null && !h5TimerCount) {\r
-\r
-      // no active timers, stop polling interval.\r
-\r
-      clearInterval(h5IntervalTimer);\r
-\r
-      h5IntervalTimer = null;\r
-\r
-      return false;\r
-\r
-    }\r
-\r
-    // check all HTML5 sounds with timers\r
-\r
-    for (i = sm2.soundIDs.length-1; i >= 0; i--) {\r
-\r
-      if (sm2.sounds[sm2.soundIDs[i]].isHTML5 && sm2.sounds[sm2.soundIDs[i]]._hasTimer) {\r
-\r
-        sm2.sounds[sm2.soundIDs[i]]._onTimer();\r
-\r
-      }\r
-\r
-    }\r
-\r
-  };\r
-\r
-  catchError = function(options) {\r
-\r
-    options = (options !== _undefined ? options : {});\r
-\r
-    if (typeof sm2.onerror === 'function') {\r
-      sm2.onerror.apply(window, [{type:(options.type !== _undefined ? options.type : null)}]);\r
-    }\r
-\r
-    if (options.fatal !== _undefined && options.fatal) {\r
-      sm2.disable();\r
-    }\r
-\r
-  };\r
-\r
-  badSafariFix = function() {\r
-\r
-    // special case: "bad" Safari (OS X 10.3 - 10.7) must fall back to flash for MP3/MP4\r
-    if (!isBadSafari || !detectFlash()) {\r
-      // doesn't apply\r
-      return false;\r
-    }\r
-\r
-    var aF = sm2.audioFormats, i, item;\r
-\r
-    for (item in aF) {\r
-      if (aF.hasOwnProperty(item)) {\r
-        if (item === 'mp3' || item === 'mp4') {\r
-          sm2._wD(sm + ': Using flash fallback for ' + item + ' format');\r
-          sm2.html5[item] = false;\r
-          // assign result to related formats, too\r
-          if (aF[item] && aF[item].related) {\r
-            for (i = aF[item].related.length-1; i >= 0; i--) {\r
-              sm2.html5[aF[item].related[i]] = false;\r
-            }\r
-          }\r
-        }\r
-      }\r
-    }\r
-\r
-  };\r
-\r
-  /**\r
-   * Pseudo-private flash/ExternalInterface methods\r
-   * ----------------------------------------------\r
-   */\r
-\r
-  this._setSandboxType = function(sandboxType) {\r
-\r
-    // <d>\r
-    var sb = sm2.sandbox;\r
-\r
-    sb.type = sandboxType;\r
-    sb.description = sb.types[(sb.types[sandboxType] !== _undefined?sandboxType:'unknown')];\r
-\r
-    if (sb.type === 'localWithFile') {\r
-\r
-      sb.noRemote = true;\r
-      sb.noLocal = false;\r
-      _wDS('secNote', 2);\r
-\r
-    } else if (sb.type === 'localWithNetwork') {\r
-\r
-      sb.noRemote = false;\r
-      sb.noLocal = true;\r
-\r
-    } else if (sb.type === 'localTrusted') {\r
-\r
-      sb.noRemote = false;\r
-      sb.noLocal = false;\r
-\r
-    }\r
-    // </d>\r
-\r
-  };\r
-\r
-  this._externalInterfaceOK = function(swfVersion) {\r
-\r
-    // flash callback confirming flash loaded, EI working etc.\r
-    // swfVersion: SWF build string\r
-\r
-    if (sm2.swfLoaded) {\r
-      return false;\r
-    }\r
-\r
-    var e;\r
-\r
-    debugTS('swf', true);\r
-    debugTS('flashtojs', true);\r
-    sm2.swfLoaded = true;\r
-    tryInitOnFocus = false;\r
-\r
-    if (isBadSafari) {\r
-      badSafariFix();\r
-    }\r
-\r
-    // complain if JS + SWF build/version strings don't match, excluding +DEV builds\r
-    // <d>\r
-    if (!swfVersion || swfVersion.replace(/\+dev/i,'') !== sm2.versionNumber.replace(/\+dev/i, '')) {\r
-\r
-      e = sm + ': Fatal: JavaScript file build "' + sm2.versionNumber + '" does not match Flash SWF build "' + swfVersion + '" at ' + sm2.url + '. Ensure both are up-to-date.';\r
-\r
-      // escape flash -> JS stack so this error fires in window.\r
-      setTimeout(function versionMismatch() {\r
-        throw new Error(e);\r
-      }, 0);\r
-\r
-      // exit, init will fail with timeout\r
-      return false;\r
-\r
-    }\r
-    // </d>\r
-\r
-    // IE needs a larger timeout\r
-    setTimeout(init, isIE ? 100 : 1);\r
-\r
-  };\r
-\r
-  /**\r
-   * Private initialization helpers\r
-   * ------------------------------\r
-   */\r
-\r
-  createMovie = function(smID, smURL) {\r
-\r
-    if (didAppend && appendSuccess) {\r
-      // ignore if already succeeded\r
-      return false;\r
-    }\r
-\r
-    function initMsg() {\r
-\r
-      // <d>\r
-\r
-      var options = [],\r
-          title,\r
-          msg = [],\r
-          delimiter = ' + ';\r
-\r
-      title = 'SoundManager ' + sm2.version + (!sm2.html5Only && sm2.useHTML5Audio ? (sm2.hasHTML5 ? ' + HTML5 audio' : ', no HTML5 audio support') : '');\r
-\r
-      if (!sm2.html5Only) {\r
-\r
-        if (sm2.preferFlash) {\r
-          options.push('preferFlash');\r
-        }\r
-\r
-        if (sm2.useHighPerformance) {\r
-          options.push('useHighPerformance');\r
-        }\r
-\r
-        if (sm2.flashPollingInterval) {\r
-          options.push('flashPollingInterval (' + sm2.flashPollingInterval + 'ms)');\r
-        }\r
-\r
-        if (sm2.html5PollingInterval) {\r
-          options.push('html5PollingInterval (' + sm2.html5PollingInterval + 'ms)');\r
-        }\r
-\r
-        if (sm2.wmode) {\r
-          options.push('wmode (' + sm2.wmode + ')');\r
-        }\r
-\r
-        if (sm2.debugFlash) {\r
-          options.push('debugFlash');\r
-        }\r
-\r
-        if (sm2.useFlashBlock) {\r
-          options.push('flashBlock');\r
-        }\r
-\r
-      } else {\r
-\r
-        if (sm2.html5PollingInterval) {\r
-          options.push('html5PollingInterval (' + sm2.html5PollingInterval + 'ms)');\r
-        }\r
-\r
-      }\r
-\r
-      if (options.length) {\r
-        msg = msg.concat([options.join(delimiter)]);\r
-      }\r
-\r
-      sm2._wD(title + (msg.length ? delimiter + msg.join(', ') : ''), 1);\r
-\r
-      showSupport();\r
-\r
-      // </d>\r
-\r
-    }\r
-\r
-    if (sm2.html5Only) {\r
-\r
-      // 100% HTML5 mode\r
-      setVersionInfo();\r
-\r
-      initMsg();\r
-      sm2.oMC = id(sm2.movieID);\r
-      init();\r
-\r
-      // prevent multiple init attempts\r
-      didAppend = true;\r
-\r
-      appendSuccess = true;\r
-\r
-      return false;\r
-\r
-    }\r
-\r
-    // flash path\r
-    var remoteURL = (smURL || sm2.url),\r
-    localURL = (sm2.altURL || remoteURL),\r
-    swfTitle = 'JS/Flash audio component (SoundManager 2)',\r
-    oTarget = getDocument(),\r
-    extraClass = getSWFCSS(),\r
-    isRTL = null,\r
-    html = doc.getElementsByTagName('html')[0],\r
-    oEmbed, oMovie, tmp, movieHTML, oEl, s, x, sClass;\r
-\r
-    isRTL = (html && html.dir && html.dir.match(/rtl/i));\r
-    smID = (smID === _undefined?sm2.id:smID);\r
-\r
-    function param(name, value) {\r
-      return '<param name="'+name+'" value="'+value+'" />';\r
-    }\r
-\r
-    // safety check for legacy (change to Flash 9 URL)\r
-    setVersionInfo();\r
-    sm2.url = normalizeMovieURL(overHTTP?remoteURL:localURL);\r
-    smURL = sm2.url;\r
-\r
-    sm2.wmode = (!sm2.wmode && sm2.useHighPerformance ? 'transparent' : sm2.wmode);\r
-\r
-    if (sm2.wmode !== null && (ua.match(/msie 8/i) || (!isIE && !sm2.useHighPerformance)) && navigator.platform.match(/win32|win64/i)) {\r
-      /**\r
-       * extra-special case: movie doesn't load until scrolled into view when using wmode = anything but 'window' here\r
-       * does not apply when using high performance (position:fixed means on-screen), OR infinite flash load timeout\r
-       * wmode breaks IE 8 on Vista + Win7 too in some cases, as of January 2011 (?)\r
-       */\r
-       messages.push(strings.spcWmode);\r
-      sm2.wmode = null;\r
-    }\r
-\r
-    oEmbed = {\r
-      'name': smID,\r
-      'id': smID,\r
-      'src': smURL,\r
-      'quality': 'high',\r
-      'allowScriptAccess': sm2.allowScriptAccess,\r
-      'bgcolor': sm2.bgColor,\r
-      'pluginspage': http+'www.macromedia.com/go/getflashplayer',\r
-      'title': swfTitle,\r
-      'type': 'application/x-shockwave-flash',\r
-      'wmode': sm2.wmode,\r
-      // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html\r
-      'hasPriority': 'true'\r
-    };\r
-\r
-    if (sm2.debugFlash) {\r
-      oEmbed.FlashVars = 'debug=1';\r
-    }\r
-\r
-    if (!sm2.wmode) {\r
-      // don't write empty attribute\r
-      delete oEmbed.wmode;\r
-    }\r
-\r
-    if (isIE) {\r
-\r
-      // IE is "special".\r
-      oMovie = doc.createElement('div');\r
-      movieHTML = [\r
-        '<object id="' + smID + '" data="' + smURL + '" type="' + oEmbed.type + '" title="' + oEmbed.title +'" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="' + http+'download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0">',\r
-        param('movie', smURL),\r
-        param('AllowScriptAccess', sm2.allowScriptAccess),\r
-        param('quality', oEmbed.quality),\r
-        (sm2.wmode? param('wmode', sm2.wmode): ''),\r
-        param('bgcolor', sm2.bgColor),\r
-        param('hasPriority', 'true'),\r
-        (sm2.debugFlash ? param('FlashVars', oEmbed.FlashVars) : ''),\r
-        '</object>'\r
-      ].join('');\r
-\r
-    } else {\r
-\r
-      oMovie = doc.createElement('embed');\r
-      for (tmp in oEmbed) {\r
-        if (oEmbed.hasOwnProperty(tmp)) {\r
-          oMovie.setAttribute(tmp, oEmbed[tmp]);\r
-        }\r
-      }\r
-\r
-    }\r
-\r
-    initDebug();\r
-    extraClass = getSWFCSS();\r
-    oTarget = getDocument();\r
-\r
-    if (oTarget) {\r
-\r
-      sm2.oMC = (id(sm2.movieID) || doc.createElement('div'));\r
-\r
-      if (!sm2.oMC.id) {\r
-\r
-        sm2.oMC.id = sm2.movieID;\r
-        sm2.oMC.className = swfCSS.swfDefault + ' ' + extraClass;\r
-        s = null;\r
-        oEl = null;\r
-\r
-        if (!sm2.useFlashBlock) {\r
-          if (sm2.useHighPerformance) {\r
-            // on-screen at all times\r
-            s = {\r
-              'position': 'fixed',\r
-              'width': '8px',\r
-              'height': '8px',\r
-              // >= 6px for flash to run fast, >= 8px to start up under Firefox/win32 in some cases. odd? yes.\r
-              'bottom': '0px',\r
-              'left': '0px',\r
-              'overflow': 'hidden'\r
-            };\r
-          } else {\r
-            // hide off-screen, lower priority\r
-            s = {\r
-              'position': 'absolute',\r
-              'width': '6px',\r
-              'height': '6px',\r
-              'top': '-9999px',\r
-              'left': '-9999px'\r
-            };\r
-            if (isRTL) {\r
-              s.left = Math.abs(parseInt(s.left,10))+'px';\r
-            }\r
-          }\r
-        }\r
-\r
-        if (isWebkit) {\r
-          // soundcloud-reported render/crash fix, safari 5\r
-          sm2.oMC.style.zIndex = 10000;\r
-        }\r
-\r
-        if (!sm2.debugFlash) {\r
-          for (x in s) {\r
-            if (s.hasOwnProperty(x)) {\r
-              sm2.oMC.style[x] = s[x];\r
-            }\r
-          }\r
-        }\r
-\r
-        try {\r
-          if (!isIE) {\r
-            sm2.oMC.appendChild(oMovie);\r
-          }\r
-          oTarget.appendChild(sm2.oMC);\r
-          if (isIE) {\r
-            oEl = sm2.oMC.appendChild(doc.createElement('div'));\r
-            oEl.className = swfCSS.swfBox;\r
-            oEl.innerHTML = movieHTML;\r
-          }\r
-          appendSuccess = true;\r
-        } catch(e) {\r
-          throw new Error(str('domError')+' \n'+e.toString());\r
-        }\r
-\r
-      } else {\r
-\r
-        // SM2 container is already in the document (eg. flashblock use case)\r
-        sClass = sm2.oMC.className;\r
-        sm2.oMC.className = (sClass?sClass+' ':swfCSS.swfDefault) + (extraClass?' '+extraClass:'');\r
-        sm2.oMC.appendChild(oMovie);\r
-        if (isIE) {\r
-          oEl = sm2.oMC.appendChild(doc.createElement('div'));\r
-          oEl.className = swfCSS.swfBox;\r
-          oEl.innerHTML = movieHTML;\r
-        }\r
-        appendSuccess = true;\r
-\r
-      }\r
-\r
-    }\r
-\r
-    didAppend = true;\r
-    initMsg();\r
-    // sm2._wD(sm + ': Trying to load ' + smURL + (!overHTTP && sm2.altURL ? ' (alternate URL)' : ''), 1);\r
-\r
-    return true;\r
-\r
-  };\r
-\r
-  initMovie = function() {\r
-\r
-    if (sm2.html5Only) {\r
-      createMovie();\r
-      return false;\r
-    }\r
-\r
-    // attempt to get, or create, movie (may already exist)\r
-    if (flash) {\r
-      return false;\r
-    }\r
-\r
-    if (!sm2.url) {\r
-\r
-      /**\r
-       * Something isn't right - we've reached init, but the soundManager url property has not been set.\r
-       * User has not called setup({url: ...}), or has not set soundManager.url (legacy use case) directly before init time.\r
-       * Notify and exit. If user calls setup() with a url: property, init will be restarted as in the deferred loading case.\r
-       */\r
-\r
-       _wDS('noURL');\r
-       return false;\r
-\r
-    }\r
-\r
-    // inline markup case\r
-    flash = sm2.getMovie(sm2.id);\r
-\r
-    if (!flash) {\r
-      if (!oRemoved) {\r
-        // try to create\r
-        createMovie(sm2.id, sm2.url);\r
-      } else {\r
-        // try to re-append removed movie after reboot()\r
-        if (!isIE) {\r
-          sm2.oMC.appendChild(oRemoved);\r
-        } else {\r
-          sm2.oMC.innerHTML = oRemovedHTML;\r
-        }\r
-        oRemoved = null;\r
-        didAppend = true;\r
-      }\r
-      flash = sm2.getMovie(sm2.id);\r
-    }\r
-\r
-    if (typeof sm2.oninitmovie === 'function') {\r
-      setTimeout(sm2.oninitmovie, 1);\r
-    }\r
-\r
-    // <d>\r
-    flushMessages();\r
-    // </d>\r
-\r
-    return true;\r
-\r
-  };\r
-\r
-  delayWaitForEI = function() {\r
-\r
-    setTimeout(waitForEI, 1000);\r
-\r
-  };\r
-\r
-  rebootIntoHTML5 = function() {\r
-\r
-    // special case: try for a reboot with preferFlash: false, if 100% HTML5 mode is possible and useFlashBlock is not enabled.\r
-\r
-    window.setTimeout(function() {\r
-\r
-      complain(smc + 'useFlashBlock is false, 100% HTML5 mode is possible. Rebooting with preferFlash: false...');\r
-\r
-      sm2.setup({\r
-        preferFlash: false\r
-      }).reboot();\r
-\r
-      // if for some reason you want to detect this case, use an ontimeout() callback and look for html5Only and didFlashBlock == true.\r
-      sm2.didFlashBlock = true;\r
-\r
-      sm2.beginDelayedInit();\r
-\r
-    }, 1);\r
-\r
-  };\r
-\r
-  waitForEI = function() {\r
-\r
-    var p,\r
-        loadIncomplete = false;\r
-\r
-    if (!sm2.url) {\r
-      // No SWF url to load (noURL case) - exit for now. Will be retried when url is set.\r
-      return false;\r
-    }\r
-\r
-    if (waitingForEI) {\r
-      return false;\r
-    }\r
-\r
-    waitingForEI = true;\r
-    event.remove(window, 'load', delayWaitForEI);\r
-\r
-    if (hasFlash && tryInitOnFocus && !isFocused) {\r
-      // Safari won't load flash in background tabs, only when focused.\r
-      _wDS('waitFocus');\r
-      return false;\r
-    }\r
-\r
-    if (!didInit) {\r
-      p = sm2.getMoviePercent();\r
-      if (p > 0 && p < 100) {\r
-        loadIncomplete = true;\r
-      }\r
-    }\r
-\r
-    setTimeout(function() {\r
-\r
-      p = sm2.getMoviePercent();\r
-\r
-      if (loadIncomplete) {\r
-        // special case: if movie *partially* loaded, retry until it's 100% before assuming failure.\r
-        waitingForEI = false;\r
-        sm2._wD(str('waitSWF'));\r
-        window.setTimeout(delayWaitForEI, 1);\r
-        return false;\r
-      }\r
-\r
-      // <d>\r
-      if (!didInit) {\r
-\r
-        sm2._wD(sm + ': No Flash response within expected time. Likely causes: ' + (p === 0 ? 'SWF load failed, ':'') + 'Flash blocked or JS-Flash security error.' + (sm2.debugFlash?' ' + str('checkSWF'):''), 2);\r
-\r
-        if (!overHTTP && p) {\r
-\r
-          _wDS('localFail', 2);\r
-\r
-          if (!sm2.debugFlash) {\r
-            _wDS('tryDebug', 2);\r
-          }\r
-\r
-        }\r
-\r
-        if (p === 0) {\r
-\r
-          // if 0 (not null), probably a 404.\r
-          sm2._wD(str('swf404', sm2.url), 1);\r
-\r
-        }\r
-\r
-        debugTS('flashtojs', false, ': Timed out' + overHTTP?' (Check flash security or flash blockers)':' (No plugin/missing SWF?)');\r
-\r
-      }\r
-      // </d>\r
-\r
-      // give up / time-out, depending\r
-\r
-      if (!didInit && okToDisable) {\r
-\r
-        if (p === null) {\r
-\r
-          // SWF failed to report load progress. Possibly blocked.\r
-\r
-          if (sm2.useFlashBlock || sm2.flashLoadTimeout === 0) {\r
-\r
-            if (sm2.useFlashBlock) {\r
-\r
-              flashBlockHandler();\r
-\r
-            }\r
-\r
-            _wDS('waitForever');\r
-\r
-          } else {\r
-\r
-            // no custom flash block handling, but SWF has timed out. Will recover if user unblocks / allows SWF load.\r
-\r
-            if (!sm2.useFlashBlock && canIgnoreFlash) {\r
-\r
-              rebootIntoHTML5();\r
-\r
-            } else {\r
-\r
-              _wDS('waitForever');\r
-\r
-              // fire any regular registered ontimeout() listeners.\r
-              processOnEvents({type:'ontimeout', ignoreInit: true, error: {type: 'INIT_FLASHBLOCK'}});\r
-\r
-            }\r
-\r
-          }\r
-\r
-        } else {\r
-\r
-          // SWF loaded? Shouldn't be a blocking issue, then.\r
-\r
-          if (sm2.flashLoadTimeout === 0) {\r
-\r
-            _wDS('waitForever');\r
-\r
-          } else {\r
-\r
-            if (!sm2.useFlashBlock && canIgnoreFlash) {\r
-\r
-              rebootIntoHTML5();\r
-\r
-            } else {\r
-\r
-              failSafely(true);\r
-\r
-            }\r
-\r
-          }\r
-\r
-        }\r
-\r
-      }\r
-\r
-    }, sm2.flashLoadTimeout);\r
-\r
-  };\r
-\r
-  handleFocus = function() {\r
-\r
-    function cleanup() {\r
-      event.remove(window, 'focus', handleFocus);\r
-    }\r
-\r
-    if (isFocused || !tryInitOnFocus) {\r
-      // already focused, or not special Safari background tab case\r
-      cleanup();\r
-      return true;\r
-    }\r
-\r
-    okToDisable = true;\r
-    isFocused = true;\r
-    _wDS('gotFocus');\r
-\r
-    // allow init to restart\r
-    waitingForEI = false;\r
-\r
-    // kick off ExternalInterface timeout, now that the SWF has started\r
-    delayWaitForEI();\r
-\r
-    cleanup();\r
-    return true;\r
-\r
-  };\r
-\r
-  flushMessages = function() {\r
-\r
-    // <d>\r
-\r
-    // SM2 pre-init debug messages\r
-    if (messages.length) {\r
-      sm2._wD('SoundManager 2: ' + messages.join(' '), 1);\r
-      messages = [];\r
-    }\r
-\r
-    // </d>\r
-\r
-  };\r
-\r
-  showSupport = function() {\r
-\r
-    // <d>\r
-\r
-    flushMessages();\r
-\r
-    var item, tests = [];\r
-\r
-    if (sm2.useHTML5Audio && sm2.hasHTML5) {\r
-      for (item in sm2.audioFormats) {\r
-        if (sm2.audioFormats.hasOwnProperty(item)) {\r
-          tests.push(item + ' = ' + sm2.html5[item] + (!sm2.html5[item] && needsFlash && sm2.flash[item] ? ' (using flash)' : (sm2.preferFlash && sm2.flash[item] && needsFlash ? ' (preferring flash)': (!sm2.html5[item] ? ' (' + (sm2.audioFormats[item].required ? 'required, ':'') + 'and no flash support)' : ''))));\r
-        }\r
-      }\r
-      sm2._wD('SoundManager 2 HTML5 support: ' + tests.join(', '), 1);\r
-    }\r
-\r
-    // </d>\r
-\r
-  };\r
-\r
-  initComplete = function(bNoDisable) {\r
-\r
-    if (didInit) {\r
-      return false;\r
-    }\r
-\r
-    if (sm2.html5Only) {\r
-      // all good.\r
-      _wDS('sm2Loaded');\r
-      didInit = true;\r
-      initUserOnload();\r
-      debugTS('onload', true);\r
-      return true;\r
-    }\r
-\r
-    var wasTimeout = (sm2.useFlashBlock && sm2.flashLoadTimeout && !sm2.getMoviePercent()),\r
-        result = true,\r
-        error;\r
-\r
-    if (!wasTimeout) {\r
-      didInit = true;\r
-    }\r
-\r
-    error = {type: (!hasFlash && needsFlash ? 'NO_FLASH' : 'INIT_TIMEOUT')};\r
-\r
-    sm2._wD('SoundManager 2 ' + (disabled ? 'failed to load' : 'loaded') + ' (' + (disabled ? 'Flash security/load error' : 'OK') + ')', disabled ? 2: 1);\r
-\r
-    if (disabled || bNoDisable) {\r
-      if (sm2.useFlashBlock && sm2.oMC) {\r
-        sm2.oMC.className = getSWFCSS() + ' ' + (sm2.getMoviePercent() === null?swfCSS.swfTimedout:swfCSS.swfError);\r
-      }\r
-      processOnEvents({type:'ontimeout', error:error, ignoreInit: true});\r
-      debugTS('onload', false);\r
-      catchError(error);\r
-      result = false;\r
-    } else {\r
-      debugTS('onload', true);\r
-    }\r
-\r
-    if (!disabled) {\r
-      if (sm2.waitForWindowLoad && !windowLoaded) {\r
-        _wDS('waitOnload');\r
-        event.add(window, 'load', initUserOnload);\r
-      } else {\r
-        // <d>\r
-        if (sm2.waitForWindowLoad && windowLoaded) {\r
-          _wDS('docLoaded');\r
-        }\r
-        // </d>\r
-        initUserOnload();\r
-      }\r
-    }\r
-\r
-    return result;\r
-\r
-  };\r
-\r
-  /**\r
-   * apply top-level setupOptions object as local properties, eg., this.setupOptions.flashVersion -> this.flashVersion (soundManager.flashVersion)\r
-   * this maintains backward compatibility, and allows properties to be defined separately for use by soundManager.setup().\r
-   */\r
-\r
-  setProperties = function() {\r
-\r
-    var i,\r
-        o = sm2.setupOptions;\r
-\r
-    for (i in o) {\r
-\r
-      if (o.hasOwnProperty(i)) {\r
-\r
-        // assign local property if not already defined\r
-\r
-        if (sm2[i] === _undefined) {\r
-\r
-          sm2[i] = o[i];\r
-\r
-        } else if (sm2[i] !== o[i]) {\r
-\r
-          // legacy support: write manually-assigned property (eg., soundManager.url) back to setupOptions to keep things in sync\r
-          sm2.setupOptions[i] = sm2[i];\r
-\r
-        }\r
-\r
-      }\r
-\r
-    }\r
-\r
-  };\r
-\r
-\r
-  init = function() {\r
-\r
-    // called after onload()\r
-\r
-    if (didInit) {\r
-      _wDS('didInit');\r
-      return false;\r
-    }\r
-\r
-    function cleanup() {\r
-      event.remove(window, 'load', sm2.beginDelayedInit);\r
-    }\r
-\r
-    if (sm2.html5Only) {\r
-      if (!didInit) {\r
-        // we don't need no steenking flash!\r
-        cleanup();\r
-        sm2.enabled = true;\r
-        initComplete();\r
-      }\r
-      return true;\r
-    }\r
-\r
-    // flash path\r
-    initMovie();\r
-\r
-    try {\r
-\r
-      // attempt to talk to Flash\r
-      flash._externalInterfaceTest(false);\r
-\r
-      // apply user-specified polling interval, OR, if "high performance" set, faster vs. default polling\r
-      // (determines frequency of whileloading/whileplaying callbacks, effectively driving UI framerates)\r
-      setPolling(true, (sm2.flashPollingInterval || (sm2.useHighPerformance ? 10 : 50)));\r
-\r
-      if (!sm2.debugMode) {\r
-        // stop the SWF from making debug output calls to JS\r
-        flash._disableDebug();\r
-      }\r
-\r
-      sm2.enabled = true;\r
-      debugTS('jstoflash', true);\r
-\r
-      if (!sm2.html5Only) {\r
-        // prevent browser from showing cached page state (or rather, restoring "suspended" page state) via back button, because flash may be dead\r
-        // http://www.webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/\r
-        event.add(window, 'unload', doNothing);\r
-      }\r
-\r
-    } catch(e) {\r
-\r
-      sm2._wD('js/flash exception: ' + e.toString());\r
-      debugTS('jstoflash', false);\r
-      catchError({type:'JS_TO_FLASH_EXCEPTION', fatal:true});\r
-      // don't disable, for reboot()\r
-      failSafely(true);\r
-      initComplete();\r
-\r
-      return false;\r
-\r
-    }\r
-\r
-    initComplete();\r
-\r
-    // disconnect events\r
-    cleanup();\r
-\r
-    return true;\r
-\r
-  };\r
-\r
-  domContentLoaded = function() {\r
-\r
-    if (didDCLoaded) {\r
-      return false;\r
-    }\r
-\r
-    didDCLoaded = true;\r
-\r
-    // assign top-level soundManager properties eg. soundManager.url\r
-    setProperties();\r
-\r
-    initDebug();\r
-\r
-    /**\r
-     * Temporary feature: allow force of HTML5 via URL params: sm2-usehtml5audio=0 or 1\r
-     * Ditto for sm2-preferFlash, too.\r
-     */\r
-    // <d>\r
-    (function(){\r
-\r
-      var a = 'sm2-usehtml5audio=',\r
-          a2 = 'sm2-preferflash=',\r
-          b = null,\r
-          b2 = null,\r
-          l = wl.toLowerCase();\r
-\r
-      if (l.indexOf(a) !== -1) {\r
-        b = (l.charAt(l.indexOf(a)+a.length) === '1');\r
-        if (hasConsole) {\r
-          console.log((b?'Enabling ':'Disabling ')+'useHTML5Audio via URL parameter');\r
-        }\r
-        sm2.setup({\r
-          'useHTML5Audio': b\r
-        });\r
-      }\r
-\r
-      if (l.indexOf(a2) !== -1) {\r
-        b2 = (l.charAt(l.indexOf(a2)+a2.length) === '1');\r
-        if (hasConsole) {\r
-          console.log((b2?'Enabling ':'Disabling ')+'preferFlash via URL parameter');\r
-        }\r
-        sm2.setup({\r
-          'preferFlash': b2\r
-        });\r
-      }\r
-\r
-    }());\r
-    // </d>\r
-\r
-    if (!hasFlash && sm2.hasHTML5) {\r
-      sm2._wD('SoundManager 2: No Flash detected' + (!sm2.useHTML5Audio ? ', enabling HTML5.' : '. Trying HTML5-only mode.'), 1);\r
-      sm2.setup({\r
-        'useHTML5Audio': true,\r
-        // make sure we aren't preferring flash, either\r
-        // TODO: preferFlash should not matter if flash is not installed. Currently, stuff breaks without the below tweak.\r
-        'preferFlash': false\r
-      });\r
-    }\r
-\r
-    testHTML5();\r
-\r
-    if (!hasFlash && needsFlash) {\r
-      messages.push(strings.needFlash);\r
-      // TODO: Fatal here vs. timeout approach, etc.\r
-      // hack: fail sooner.\r
-      sm2.setup({\r
-        'flashLoadTimeout': 1\r
-      });\r
-    }\r
-\r
-    if (doc.removeEventListener) {\r
-      doc.removeEventListener('DOMContentLoaded', domContentLoaded, false);\r
-    }\r
-\r
-    initMovie();\r
-\r
-    return true;\r
-\r
-  };\r
-\r
-  domContentLoadedIE = function() {\r
-\r
-    if (doc.readyState === 'complete') {\r
-      domContentLoaded();\r
-      doc.detachEvent('onreadystatechange', domContentLoadedIE);\r
-    }\r
-\r
-    return true;\r
-\r
-  };\r
-\r
-  winOnLoad = function() {\r
-\r
-    // catch edge case of initComplete() firing after window.load()\r
-    windowLoaded = true;\r
-    event.remove(window, 'load', winOnLoad);\r
-\r
-  };\r
-\r
-  /**\r
-   * miscellaneous run-time, pre-init stuff\r
-   */\r
-\r
-  preInit = function() {\r
-\r
-    if (mobileHTML5) {\r
-\r
-      // prefer HTML5 for mobile + tablet-like devices, probably more reliable vs. flash at this point.\r
-\r
-      // <d>\r
-      if (!sm2.setupOptions.useHTML5Audio || sm2.setupOptions.preferFlash) {\r
-        // notify that defaults are being changed.\r
-        messages.push(strings.mobileUA);\r
-      }\r
-      // </d>\r
-\r
-      sm2.setupOptions.useHTML5Audio = true;\r
-      sm2.setupOptions.preferFlash = false;\r
-\r
-      if (is_iDevice || (isAndroid && !ua.match(/android\s2\.3/i))) {\r
-        // iOS and Android devices tend to work better with a single audio instance, specifically for chained playback of sounds in sequence.\r
-        // common use case: exiting sound onfinish() -> createSound() -> play()\r
-        // <d>\r
-        messages.push(strings.globalHTML5);\r
-        // </d>\r
-        if (is_iDevice) {\r
-          sm2.ignoreFlash = true;\r
-        }\r
-        useGlobalHTML5Audio = true;\r
-      }\r
-\r
-    }\r
-\r
-  };\r
-\r
-  preInit();\r
-\r
-  // sniff up-front\r
-  detectFlash();\r
-\r
-  // focus and window load, init (primarily flash-driven)\r
-  event.add(window, 'focus', handleFocus);\r
-  event.add(window, 'load', delayWaitForEI);\r
-  event.add(window, 'load', winOnLoad);\r
-\r
-  if (doc.addEventListener) {\r
-\r
-    doc.addEventListener('DOMContentLoaded', domContentLoaded, false);\r
-\r
-  } else if (doc.attachEvent) {\r
-\r
-    doc.attachEvent('onreadystatechange', domContentLoadedIE);\r
-\r
-  } else {\r
-\r
-    // no add/attachevent support - safe to assume no JS -> Flash either\r
-    debugTS('onload', false);\r
-    catchError({type:'NO_DOM2_EVENTS', fatal:true});\r
-\r
-  }\r
-\r
-} // SoundManager()\r
-\r
-// SM2_DEFER details: http://www.schillmania.com/projects/soundmanager2/doc/getstarted/#lazy-loading\r
-\r
-if (window.SM2_DEFER === undefined || !SM2_DEFER) {\r
-  soundManager = new SoundManager();\r
-}\r
-\r
-/**\r
- * SoundManager public interfaces\r
- * ------------------------------\r
- */\r
-\r
-window.SoundManager = SoundManager; // constructor\r
-window.soundManager = soundManager; // public API, flash callbacks etc.\r
-\r
-}(window));\r
+/** @license
+ *
+ * SoundManager 2: JavaScript Sound for the Web
+ * ----------------------------------------------
+ * http://schillmania.com/projects/soundmanager2/
+ *
+ * Copyright (c) 2007, Scott Schiller. All rights reserved.
+ * Code provided under the BSD License:
+ * http://schillmania.com/projects/soundmanager2/license.txt
+ *
+ * V2.97a.20131201
+ */
+
+/*global window, SM2_DEFER, sm2Debugger, console, document, navigator, setTimeout, setInterval, clearInterval, Audio, opera */
+/*jslint regexp: true, sloppy: true, white: true, nomen: true, plusplus: true, todo: true */
+
+/**
+ * About this file
+ * -------------------------------------------------------------------------------------
+ * This is the fully-commented source version of the SoundManager 2 API,
+ * recommended for use during development and testing.
+ *
+ * See soundmanager2-nodebug-jsmin.js for an optimized build (~11KB with gzip.)
+ * http://schillmania.com/projects/soundmanager2/doc/getstarted/#basic-inclusion
+ * Alternately, serve this file with gzip for 75% compression savings (~30KB over HTTP.)
+ *
+ * You may notice <d> and </d> comments in this source; these are delimiters for
+ * debug blocks which are removed in the -nodebug builds, further optimizing code size.
+ *
+ * Also, as you may note: Whoa, reliable cross-platform/device audio support is hard! ;)
+ */
+
+(function(window, _undefined) {
+
+"use strict";
+
+var soundManager = null;
+
+/**
+ * The SoundManager constructor.
+ *
+ * @constructor
+ * @param {string} smURL Optional: Path to SWF files
+ * @param {string} smID Optional: The ID to use for the SWF container element
+ * @this {SoundManager}
+ * @return {SoundManager} The new SoundManager instance
+ */
+
+function SoundManager(smURL, smID) {
+
+  /**
+   * soundManager configuration options list
+   * defines top-level configuration properties to be applied to the soundManager instance (eg. soundManager.flashVersion)
+   * to set these properties, use the setup() method - eg., soundManager.setup({url: '/swf/', flashVersion: 9})
+   */
+
+  this.setupOptions = {
+
+    'url': (smURL || null),             // path (directory) where SoundManager 2 SWFs exist, eg., /path/to/swfs/
+    'flashVersion': 8,                  // flash build to use (8 or 9.) Some API features require 9.
+    'debugMode': true,                  // enable debugging output (console.log() with HTML fallback)
+    'debugFlash': false,                // enable debugging output inside SWF, troubleshoot Flash/browser issues
+    'useConsole': true,                 // use console.log() if available (otherwise, writes to #soundmanager-debug element)
+    'consoleOnly': true,                // if console is being used, do not create/write to #soundmanager-debug
+    'waitForWindowLoad': false,         // force SM2 to wait for window.onload() before trying to call soundManager.onload()
+    'bgColor': '#ffffff',               // SWF background color. N/A when wmode = 'transparent'
+    'useHighPerformance': false,        // position:fixed flash movie can help increase js/flash speed, minimize lag
+    'flashPollingInterval': null,       // msec affecting whileplaying/loading callback frequency. If null, default of 50 msec is used.
+    'html5PollingInterval': null,       // msec affecting whileplaying() for HTML5 audio, excluding mobile devices. If null, native HTML5 update events are used.
+    'flashLoadTimeout': 1000,           // msec to wait for flash movie to load before failing (0 = infinity)
+    'wmode': null,                      // flash rendering mode - null, 'transparent', or 'opaque' (last two allow z-index to work)
+    'allowScriptAccess': 'always',      // for scripting the SWF (object/embed property), 'always' or 'sameDomain'
+    'useFlashBlock': false,             // *requires flashblock.css, see demos* - allow recovery from flash blockers. Wait indefinitely and apply timeout CSS to SWF, if applicable.
+    'useHTML5Audio': true,              // use HTML5 Audio() where API is supported (most Safari, Chrome versions), Firefox (no MP3/MP4.) Ideally, transparent vs. Flash API where possible.
+    'html5Test': /^(probably|maybe)$/i, // HTML5 Audio() format support test. Use /^probably$/i; if you want to be more conservative.
+    'preferFlash': false,               // overrides useHTML5audio, will use Flash for MP3/MP4/AAC if present. Potential option if HTML5 playback with these formats is quirky.
+    'noSWFCache': false,                // if true, appends ?ts={date} to break aggressive SWF caching.
+    'idPrefix': 'sound'                 // if an id is not provided to createSound(), this prefix is used for generated IDs - 'sound0', 'sound1' etc.
+
+  };
+
+  this.defaultOptions = {
+
+    /**
+     * the default configuration for sound objects made with createSound() and related methods
+     * eg., volume, auto-load behaviour and so forth
+     */
+
+    'autoLoad': false,        // enable automatic loading (otherwise .load() will be called on demand with .play(), the latter being nicer on bandwidth - if you want to .load yourself, you also can)
+    'autoPlay': false,        // enable playing of file as soon as possible (much faster if "stream" is true)
+    'from': null,             // position to start playback within a sound (msec), default = beginning
+    'loops': 1,               // how many times to repeat the sound (position will wrap around to 0, setPosition() will break out of loop when >0)
+    'onid3': null,            // callback function for "ID3 data is added/available"
+    'onload': null,           // callback function for "load finished"
+    'whileloading': null,     // callback function for "download progress update" (X of Y bytes received)
+    'onplay': null,           // callback for "play" start
+    'onpause': null,          // callback for "pause"
+    'onresume': null,         // callback for "resume" (pause toggle)
+    'whileplaying': null,     // callback during play (position update)
+    'onposition': null,       // object containing times and function callbacks for positions of interest
+    'onstop': null,           // callback for "user stop"
+    'onfailure': null,        // callback function for when playing fails
+    'onfinish': null,         // callback function for "sound finished playing"
+    'multiShot': true,        // let sounds "restart" or layer on top of each other when played multiple times, rather than one-shot/one at a time
+    'multiShotEvents': false, // fire multiple sound events (currently onfinish() only) when multiShot is enabled
+    'position': null,         // offset (milliseconds) to seek to within loaded sound data.
+    'pan': 0,                 // "pan" settings, left-to-right, -100 to 100
+    'stream': true,           // allows playing before entire file has loaded (recommended)
+    'to': null,               // position to end playback within a sound (msec), default = end
+    'type': null,             // MIME-like hint for file pattern / canPlay() tests, eg. audio/mp3
+    'usePolicyFile': false,   // enable crossdomain.xml request for audio on remote domains (for ID3/waveform access)
+    'volume': 100             // self-explanatory. 0-100, the latter being the max.
+
+  };
+
+  this.flash9Options = {
+
+    /**
+     * flash 9-only options,
+     * merged into defaultOptions if flash 9 is being used
+     */
+
+    'isMovieStar': null,      // "MovieStar" MPEG4 audio mode. Null (default) = auto detect MP4, AAC etc. based on URL. true = force on, ignore URL
+    'usePeakData': false,     // enable left/right channel peak (level) data
+    'useWaveformData': false, // enable sound spectrum (raw waveform data) - NOTE: May increase CPU load.
+    'useEQData': false,       // enable sound EQ (frequency spectrum data) - NOTE: May increase CPU load.
+    'onbufferchange': null,   // callback for "isBuffering" property change
+    'ondataerror': null       // callback for waveform/eq data access error (flash playing audio in other tabs/domains)
+
+  };
+
+  this.movieStarOptions = {
+
+    /**
+     * flash 9.0r115+ MPEG4 audio options,
+     * merged into defaultOptions if flash 9+movieStar mode is enabled
+     */
+
+    'bufferTime': 3,          // seconds of data to buffer before playback begins (null = flash default of 0.1 seconds - if AAC playback is gappy, try increasing.)
+    'serverURL': null,        // rtmp: FMS or FMIS server to connect to, required when requesting media via RTMP or one of its variants
+    'onconnect': null,        // rtmp: callback for connection to flash media server
+    'duration': null          // rtmp: song duration (msec)
+
+  };
+
+  this.audioFormats = {
+
+    /**
+     * determines HTML5 support + flash requirements.
+     * if no support (via flash and/or HTML5) for a "required" format, SM2 will fail to start.
+     * flash fallback is used for MP3 or MP4 if HTML5 can't play it (or if preferFlash = true)
+     */
+
+    'mp3': {
+      'type': ['audio/mpeg; codecs="mp3"', 'audio/mpeg', 'audio/mp3', 'audio/MPA', 'audio/mpa-robust'],
+      'required': true
+    },
+
+    'mp4': {
+      'related': ['aac','m4a','m4b'], // additional formats under the MP4 container
+      'type': ['audio/mp4; codecs="mp4a.40.2"', 'audio/aac', 'audio/x-m4a', 'audio/MP4A-LATM', 'audio/mpeg4-generic'],
+      'required': false
+    },
+
+    'ogg': {
+      'type': ['audio/ogg; codecs=vorbis'],
+      'required': false
+    },
+
+    'opus': {
+      'type': ['audio/ogg; codecs=opus', 'audio/opus'],
+      'required': false
+    },
+
+    'wav': {
+      'type': ['audio/wav; codecs="1"', 'audio/wav', 'audio/wave', 'audio/x-wav'],
+      'required': false
+    }
+
+  };
+
+  // HTML attributes (id + class names) for the SWF container
+
+  this.movieID = 'sm2-container';
+  this.id = (smID || 'sm2movie');
+
+  this.debugID = 'soundmanager-debug';
+  this.debugURLParam = /([#?&])debug=1/i;
+
+  // dynamic attributes
+
+  this.versionNumber = 'V2.97a.20131201';
+  this.version = null;
+  this.movieURL = null;
+  this.altURL = null;
+  this.swfLoaded = false;
+  this.enabled = false;
+  this.oMC = null;
+  this.sounds = {};
+  this.soundIDs = [];
+  this.muted = false;
+  this.didFlashBlock = false;
+  this.filePattern = null;
+
+  this.filePatterns = {
+
+    'flash8': /\.mp3(\?.*)?$/i,
+    'flash9': /\.mp3(\?.*)?$/i
+
+  };
+
+  // support indicators, set at init
+
+  this.features = {
+
+    'buffering': false,
+    'peakData': false,
+    'waveformData': false,
+    'eqData': false,
+    'movieStar': false
+
+  };
+
+  // flash sandbox info, used primarily in troubleshooting
+
+  this.sandbox = {
+
+    // <d>
+    'type': null,
+    'types': {
+      'remote': 'remote (domain-based) rules',
+      'localWithFile': 'local with file access (no internet access)',
+      'localWithNetwork': 'local with network (internet access only, no local access)',
+      'localTrusted': 'local, trusted (local+internet access)'
+    },
+    'description': null,
+    'noRemote': null,
+    'noLocal': null
+    // </d>
+
+  };
+
+  /**
+   * format support (html5/flash)
+   * stores canPlayType() results based on audioFormats.
+   * eg. { mp3: boolean, mp4: boolean }
+   * treat as read-only.
+   */
+
+  this.html5 = {
+    'usingFlash': null // set if/when flash fallback is needed
+  };
+
+  // file type support hash
+  this.flash = {};
+
+  // determined at init time
+  this.html5Only = false;
+
+  // used for special cases (eg. iPad/iPhone/palm OS?)
+  this.ignoreFlash = false;
+
+  /**
+   * a few private internals (OK, a lot. :D)
+   */
+
+  var SMSound,
+  sm2 = this, globalHTML5Audio = null, flash = null, sm = 'soundManager', smc = sm + ': ', h5 = 'HTML5::', id, ua = navigator.userAgent, wl = window.location.href.toString(), doc = document, doNothing, setProperties, init, fV, on_queue = [], debugOpen = true, debugTS, didAppend = false, appendSuccess = false, didInit = false, disabled = false, windowLoaded = false, _wDS, wdCount = 0, initComplete, mixin, assign, extraOptions, addOnEvent, processOnEvents, initUserOnload, delayWaitForEI, waitForEI, rebootIntoHTML5, setVersionInfo, handleFocus, strings, initMovie, preInit, domContentLoaded, winOnLoad, didDCLoaded, getDocument, createMovie, catchError, setPolling, initDebug, debugLevels = ['log', 'info', 'warn', 'error'], defaultFlashVersion = 8, disableObject, failSafely, normalizeMovieURL, oRemoved = null, oRemovedHTML = null, str, flashBlockHandler, getSWFCSS, swfCSS, toggleDebug, loopFix, policyFix, complain, idCheck, waitingForEI = false, initPending = false, startTimer, stopTimer, timerExecute, h5TimerCount = 0, h5IntervalTimer = null, parseURL, messages = [],
+  canIgnoreFlash, needsFlash = null, featureCheck, html5OK, html5CanPlay, html5Ext, html5Unload, domContentLoadedIE, testHTML5, event, slice = Array.prototype.slice, useGlobalHTML5Audio = false, lastGlobalHTML5URL, hasFlash, detectFlash, badSafariFix, html5_events, showSupport, flushMessages, wrapCallback, idCounter = 0,
+  is_iDevice = ua.match(/(ipad|iphone|ipod)/i), isAndroid = ua.match(/android/i), isIE = ua.match(/msie/i), isWebkit = ua.match(/webkit/i), isSafari = (ua.match(/safari/i) && !ua.match(/chrome/i)), isOpera = (ua.match(/opera/i)),
+  mobileHTML5 = (ua.match(/(mobile|pre\/|xoom)/i) || is_iDevice || isAndroid),
+  isBadSafari = (!wl.match(/usehtml5audio/i) && !wl.match(/sm2\-ignorebadua/i) && isSafari && !ua.match(/silk/i) && ua.match(/OS X 10_6_([3-7])/i)), // Safari 4 and 5 (excluding Kindle Fire, "Silk") occasionally fail to load/play HTML5 audio on Snow Leopard 10.6.3 through 10.6.7 due to bug(s) in QuickTime X and/or other underlying frameworks. :/ Confirmed bug. https://bugs.webkit.org/show_bug.cgi?id=32159
+  hasConsole = (window.console !== _undefined && console.log !== _undefined), isFocused = (doc.hasFocus !== _undefined?doc.hasFocus():null), tryInitOnFocus = (isSafari && (doc.hasFocus === _undefined || !doc.hasFocus())), okToDisable = !tryInitOnFocus, flashMIME = /(mp3|mp4|mpa|m4a|m4b)/i, msecScale = 1000,
+  emptyURL = 'about:blank', // safe URL to unload, or load nothing from (flash 8 + most HTML5 UAs)
+  emptyWAV = 'data:audio/wave;base64,/UklGRiYAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQIAAAD//w==', // tiny WAV for HTML5 unloading
+  overHTTP = (doc.location?doc.location.protocol.match(/http/i):null),
+  http = (!overHTTP ? 'http:/'+'/' : ''),
+  // mp3, mp4, aac etc.
+  netStreamMimeTypes = /^\s*audio\/(?:x-)?(?:mpeg4|aac|flv|mov|mp4||m4v|m4a|m4b|mp4v|3gp|3g2)\s*(?:$|;)/i,
+  // Flash v9.0r115+ "moviestar" formats
+  netStreamTypes = ['mpeg4', 'aac', 'flv', 'mov', 'mp4', 'm4v', 'f4v', 'm4a', 'm4b', 'mp4v', '3gp', '3g2'],
+  netStreamPattern = new RegExp('\\.(' + netStreamTypes.join('|') + ')(\\?.*)?$', 'i');
+
+  this.mimePattern = /^\s*audio\/(?:x-)?(?:mp(?:eg|3))\s*(?:$|;)/i; // default mp3 set
+
+  // use altURL if not "online"
+  this.useAltURL = !overHTTP;
+
+  swfCSS = {
+
+    'swfBox': 'sm2-object-box',
+    'swfDefault': 'movieContainer',
+    'swfError': 'swf_error', // SWF loaded, but SM2 couldn't start (other error)
+    'swfTimedout': 'swf_timedout',
+    'swfLoaded': 'swf_loaded',
+    'swfUnblocked': 'swf_unblocked', // or loaded OK
+    'sm2Debug': 'sm2_debug',
+    'highPerf': 'high_performance',
+    'flashDebug': 'flash_debug'
+
+  };
+
+  /**
+   * basic HTML5 Audio() support test
+   * try...catch because of IE 9 "not implemented" nonsense
+   * https://github.com/Modernizr/Modernizr/issues/224
+   */
+
+  this.hasHTML5 = (function() {
+    try {
+      // new Audio(null) for stupid Opera 9.64 case, which throws not_enough_arguments exception otherwise.
+      return (Audio !== _undefined && (isOpera && opera !== _undefined && opera.version() < 10 ? new Audio(null) : new Audio()).canPlayType !== _undefined);
+    } catch(e) {
+      return false;
+    }
+  }());
+
+  /**
+   * Public SoundManager API
+   * -----------------------
+   */
+
+  /**
+   * Configures top-level soundManager properties.
+   *
+   * @param {object} options Option parameters, eg. { flashVersion: 9, url: '/path/to/swfs/' }
+   * onready and ontimeout are also accepted parameters. call soundManager.setup() to see the full list.
+   */
+
+  this.setup = function(options) {
+
+    var noURL = (!sm2.url);
+
+    // warn if flash options have already been applied
+
+    if (options !== _undefined && didInit && needsFlash && sm2.ok() && (options.flashVersion !== _undefined || options.url !== _undefined || options.html5Test !== _undefined)) {
+      complain(str('setupLate'));
+    }
+
+    // TODO: defer: true?
+
+    assign(options);
+
+    // special case 1: "Late setup". SM2 loaded normally, but user didn't assign flash URL eg., setup({url:...}) before SM2 init. Treat as delayed init.
+
+    if (options) {
+
+      if (noURL && didDCLoaded && options.url !== _undefined) {
+        sm2.beginDelayedInit();
+      }
+
+      // special case 2: If lazy-loading SM2 (DOMContentLoaded has already happened) and user calls setup() with url: parameter, try to init ASAP.
+
+      if (!didDCLoaded && options.url !== _undefined && doc.readyState === 'complete') {
+        setTimeout(domContentLoaded, 1);
+      }
+
+    }
+
+    return sm2;
+
+  };
+
+  this.ok = function() {
+
+    return (needsFlash ? (didInit && !disabled) : (sm2.useHTML5Audio && sm2.hasHTML5));
+
+  };
+
+  this.supported = this.ok; // legacy
+
+  this.getMovie = function(smID) {
+
+    // safety net: some old browsers differ on SWF references, possibly related to ExternalInterface / flash version
+    return id(smID) || doc[smID] || window[smID];
+
+  };
+
+  /**
+   * Creates a SMSound sound object instance.
+   *
+   * @param {object} oOptions Sound options (at minimum, id and url parameters are required.)
+   * @return {object} SMSound The new SMSound object.
+   */
+
+  this.createSound = function(oOptions, _url) {
+
+    var cs, cs_string, options, oSound = null;
+
+    // <d>
+    cs = sm + '.createSound(): ';
+    cs_string = cs + str(!didInit?'notReady':'notOK');
+    // </d>
+
+    if (!didInit || !sm2.ok()) {
+      complain(cs_string);
+      return false;
+    }
+
+    if (_url !== _undefined) {
+      // function overloading in JS! :) ..assume simple createSound(id, url) use case
+      oOptions = {
+        'id': oOptions,
+        'url': _url
+      };
+    }
+
+    // inherit from defaultOptions
+    options = mixin(oOptions);
+
+    options.url = parseURL(options.url);
+
+    // generate an id, if needed.
+    if (options.id === undefined) {
+      options.id = sm2.setupOptions.idPrefix + (idCounter++);
+    }
+
+    // <d>
+    if (options.id.toString().charAt(0).match(/^[0-9]$/)) {
+      sm2._wD(cs + str('badID', options.id), 2);
+    }
+
+    sm2._wD(cs + options.id + (options.url ? ' (' + options.url + ')' : ''), 1);
+    // </d>
+
+    if (idCheck(options.id, true)) {
+      sm2._wD(cs + options.id + ' exists', 1);
+      return sm2.sounds[options.id];
+    }
+
+    function make() {
+
+      options = loopFix(options);
+      sm2.sounds[options.id] = new SMSound(options);
+      sm2.soundIDs.push(options.id);
+      return sm2.sounds[options.id];
+
+    }
+
+    if (html5OK(options)) {
+
+      oSound = make();
+      sm2._wD(options.id + ': Using HTML5');
+      oSound._setup_html5(options);
+
+    } else {
+
+      if (sm2.html5Only) {
+        sm2._wD(options.id + ': No HTML5 support for this sound, and no Flash. Exiting.');
+        return make();
+      }
+
+      // TODO: Move HTML5/flash checks into generic URL parsing/handling function.
+
+      if (sm2.html5.usingFlash && options.url && options.url.match(/data\:/i)) {
+        // data: URIs not supported by Flash, either.
+        sm2._wD(options.id + ': data: URIs not supported via Flash. Exiting.');
+        return make();
+      }
+
+      if (fV > 8) {
+        if (options.isMovieStar === null) {
+          // attempt to detect MPEG-4 formats
+          options.isMovieStar = !!(options.serverURL || (options.type ? options.type.match(netStreamMimeTypes) : false) || (options.url && options.url.match(netStreamPattern)));
+        }
+        // <d>
+        if (options.isMovieStar) {
+          sm2._wD(cs + 'using MovieStar handling');
+          if (options.loops > 1) {
+            _wDS('noNSLoop');
+          }
+        }
+        // </d>
+      }
+
+      options = policyFix(options, cs);
+      oSound = make();
+
+      if (fV === 8) {
+        flash._createSound(options.id, options.loops||1, options.usePolicyFile);
+      } else {
+        flash._createSound(options.id, options.url, options.usePeakData, options.useWaveformData, options.useEQData, options.isMovieStar, (options.isMovieStar?options.bufferTime:false), options.loops||1, options.serverURL, options.duration||null, options.autoPlay, true, options.autoLoad, options.usePolicyFile);
+        if (!options.serverURL) {
+          // We are connected immediately
+          oSound.connected = true;
+          if (options.onconnect) {
+            options.onconnect.apply(oSound);
+          }
+        }
+      }
+
+      if (!options.serverURL && (options.autoLoad || options.autoPlay)) {
+        // call load for non-rtmp streams
+        oSound.load(options);
+      }
+
+    }
+
+    // rtmp will play in onconnect
+    if (!options.serverURL && options.autoPlay) {
+      oSound.play();
+    }
+
+    return oSound;
+
+  };
+
+  /**
+   * Destroys a SMSound sound object instance.
+   *
+   * @param {string} sID The ID of the sound to destroy
+   */
+
+  this.destroySound = function(sID, _bFromSound) {
+
+    // explicitly destroy a sound before normal page unload, etc.
+
+    if (!idCheck(sID)) {
+      return false;
+    }
+
+    var oS = sm2.sounds[sID], i;
+
+    // Disable all callbacks while the sound is being destroyed
+    oS._iO = {};
+
+    oS.stop();
+    oS.unload();
+
+    for (i = 0; i < sm2.soundIDs.length; i++) {
+      if (sm2.soundIDs[i] === sID) {
+        sm2.soundIDs.splice(i, 1);
+        break;
+      }
+    }
+
+    if (!_bFromSound) {
+      // ignore if being called from SMSound instance
+      oS.destruct(true);
+    }
+
+    oS = null;
+    delete sm2.sounds[sID];
+
+    return true;
+
+  };
+
+  /**
+   * Calls the load() method of a SMSound object by ID.
+   *
+   * @param {string} sID The ID of the sound
+   * @param {object} oOptions Optional: Sound options
+   */
+
+  this.load = function(sID, oOptions) {
+
+    if (!idCheck(sID)) {
+      return false;
+    }
+    return sm2.sounds[sID].load(oOptions);
+
+  };
+
+  /**
+   * Calls the unload() method of a SMSound object by ID.
+   *
+   * @param {string} sID The ID of the sound
+   */
+
+  this.unload = function(sID) {
+
+    if (!idCheck(sID)) {
+      return false;
+    }
+    return sm2.sounds[sID].unload();
+
+  };
+
+  /**
+   * Calls the onPosition() method of a SMSound object by ID.
+   *
+   * @param {string} sID The ID of the sound
+   * @param {number} nPosition The position to watch for
+   * @param {function} oMethod The relevant callback to fire
+   * @param {object} oScope Optional: The scope to apply the callback to
+   * @return {SMSound} The SMSound object
+   */
+
+  this.onPosition = function(sID, nPosition, oMethod, oScope) {
+
+    if (!idCheck(sID)) {
+      return false;
+    }
+    return sm2.sounds[sID].onposition(nPosition, oMethod, oScope);
+
+  };
+
+  // legacy/backwards-compability: lower-case method name
+  this.onposition = this.onPosition;
+
+  /**
+   * Calls the clearOnPosition() method of a SMSound object by ID.
+   *
+   * @param {string} sID The ID of the sound
+   * @param {number} nPosition The position to watch for
+   * @param {function} oMethod Optional: The relevant callback to fire
+   * @return {SMSound} The SMSound object
+   */
+
+  this.clearOnPosition = function(sID, nPosition, oMethod) {
+
+    if (!idCheck(sID)) {
+      return false;
+    }
+    return sm2.sounds[sID].clearOnPosition(nPosition, oMethod);
+
+  };
+
+  /**
+   * Calls the play() method of a SMSound object by ID.
+   *
+   * @param {string} sID The ID of the sound
+   * @param {object} oOptions Optional: Sound options
+   * @return {SMSound} The SMSound object
+   */
+
+  this.play = function(sID, oOptions) {
+
+    var result = null,
+        // legacy function-overloading use case: play('mySound', '/path/to/some.mp3');
+        overloaded = (oOptions && !(oOptions instanceof Object));
+
+    if (!didInit || !sm2.ok()) {
+      complain(sm + '.play(): ' + str(!didInit?'notReady':'notOK'));
+      return false;
+    }
+
+    if (!idCheck(sID, overloaded)) {
+
+      if (!overloaded) {
+        // no sound found for the given ID. Bail.
+        return false;
+      }
+
+      if (overloaded) {
+        oOptions = {
+          url: oOptions
+        };
+      }
+
+      if (oOptions && oOptions.url) {
+        // overloading use case, create+play: .play('someID', {url:'/path/to.mp3'});
+        sm2._wD(sm + '.play(): Attempting to create "' + sID + '"', 1);
+        oOptions.id = sID;
+        result = sm2.createSound(oOptions).play();
+      }
+
+    } else if (overloaded) {
+
+      // existing sound object case
+      oOptions = {
+        url: oOptions
+      };
+
+    }
+
+    if (result === null) {
+      // default case
+      result = sm2.sounds[sID].play(oOptions);
+    }
+
+    return result;
+
+  };
+
+  this.start = this.play; // just for convenience
+
+  /**
+   * Calls the setPosition() method of a SMSound object by ID.
+   *
+   * @param {string} sID The ID of the sound
+   * @param {number} nMsecOffset Position (milliseconds)
+   * @return {SMSound} The SMSound object
+   */
+
+  this.setPosition = function(sID, nMsecOffset) {
+
+    if (!idCheck(sID)) {
+      return false;
+    }
+    return sm2.sounds[sID].setPosition(nMsecOffset);
+
+  };
+
+  /**
+   * Calls the stop() method of a SMSound object by ID.
+   *
+   * @param {string} sID The ID of the sound
+   * @return {SMSound} The SMSound object
+   */
+
+  this.stop = function(sID) {
+
+    if (!idCheck(sID)) {
+      return false;
+    }
+
+    sm2._wD(sm + '.stop(' + sID + ')', 1);
+    return sm2.sounds[sID].stop();
+
+  };
+
+  /**
+   * Stops all currently-playing sounds.
+   */
+
+  this.stopAll = function() {
+
+    var oSound;
+    sm2._wD(sm + '.stopAll()', 1);
+
+    for (oSound in sm2.sounds) {
+      if (sm2.sounds.hasOwnProperty(oSound)) {
+        // apply only to sound objects
+        sm2.sounds[oSound].stop();
+      }
+    }
+
+  };
+
+  /**
+   * Calls the pause() method of a SMSound object by ID.
+   *
+   * @param {string} sID The ID of the sound
+   * @return {SMSound} The SMSound object
+   */
+
+  this.pause = function(sID) {
+
+    if (!idCheck(sID)) {
+      return false;
+    }
+    return sm2.sounds[sID].pause();
+
+  };
+
+  /**
+   * Pauses all currently-playing sounds.
+   */
+
+  this.pauseAll = function() {
+
+    var i;
+    for (i = sm2.soundIDs.length-1; i >= 0; i--) {
+      sm2.sounds[sm2.soundIDs[i]].pause();
+    }
+
+  };
+
+  /**
+   * Calls the resume() method of a SMSound object by ID.
+   *
+   * @param {string} sID The ID of the sound
+   * @return {SMSound} The SMSound object
+   */
+
+  this.resume = function(sID) {
+
+    if (!idCheck(sID)) {
+      return false;
+    }
+    return sm2.sounds[sID].resume();
+
+  };
+
+  /**
+   * Resumes all currently-paused sounds.
+   */
+
+  this.resumeAll = function() {
+
+    var i;
+    for (i = sm2.soundIDs.length-1; i >= 0; i--) {
+      sm2.sounds[sm2.soundIDs[i]].resume();
+    }
+
+  };
+
+  /**
+   * Calls the togglePause() method of a SMSound object by ID.
+   *
+   * @param {string} sID The ID of the sound
+   * @return {SMSound} The SMSound object
+   */
+
+  this.togglePause = function(sID) {
+
+    if (!idCheck(sID)) {
+      return false;
+    }
+    return sm2.sounds[sID].togglePause();
+
+  };
+
+  /**
+   * Calls the setPan() method of a SMSound object by ID.
+   *
+   * @param {string} sID The ID of the sound
+   * @param {number} nPan The pan value (-100 to 100)
+   * @return {SMSound} The SMSound object
+   */
+
+  this.setPan = function(sID, nPan) {
+
+    if (!idCheck(sID)) {
+      return false;
+    }
+    return sm2.sounds[sID].setPan(nPan);
+
+  };
+
+  /**
+   * Calls the setVolume() method of a SMSound object by ID.
+   *
+   * @param {string} sID The ID of the sound
+   * @param {number} nVol The volume value (0 to 100)
+   * @return {SMSound} The SMSound object
+   */
+
+  this.setVolume = function(sID, nVol) {
+
+    if (!idCheck(sID)) {
+      return false;
+    }
+    return sm2.sounds[sID].setVolume(nVol);
+
+  };
+
+  /**
+   * Calls the mute() method of either a single SMSound object by ID, or all sound objects.
+   *
+   * @param {string} sID Optional: The ID of the sound (if omitted, all sounds will be used.)
+   */
+
+  this.mute = function(sID) {
+
+    var i = 0;
+
+    if (sID instanceof String) {
+      sID = null;
+    }
+
+    if (!sID) {
+
+      sm2._wD(sm + '.mute(): Muting all sounds');
+      for (i = sm2.soundIDs.length-1; i >= 0; i--) {
+        sm2.sounds[sm2.soundIDs[i]].mute();
+      }
+      sm2.muted = true;
+
+    } else {
+
+      if (!idCheck(sID)) {
+        return false;
+      }
+      sm2._wD(sm + '.mute(): Muting "' + sID + '"');
+      return sm2.sounds[sID].mute();
+
+    }
+
+    return true;
+
+  };
+
+  /**
+   * Mutes all sounds.
+   */
+
+  this.muteAll = function() {
+
+    sm2.mute();
+
+  };
+
+  /**
+   * Calls the unmute() method of either a single SMSound object by ID, or all sound objects.
+   *
+   * @param {string} sID Optional: The ID of the sound (if omitted, all sounds will be used.)
+   */
+
+  this.unmute = function(sID) {
+
+    var i;
+
+    if (sID instanceof String) {
+      sID = null;
+    }
+
+    if (!sID) {
+
+      sm2._wD(sm + '.unmute(): Unmuting all sounds');
+      for (i = sm2.soundIDs.length-1; i >= 0; i--) {
+        sm2.sounds[sm2.soundIDs[i]].unmute();
+      }
+      sm2.muted = false;
+
+    } else {
+
+      if (!idCheck(sID)) {
+        return false;
+      }
+      sm2._wD(sm + '.unmute(): Unmuting "' + sID + '"');
+      return sm2.sounds[sID].unmute();
+
+    }
+
+    return true;
+
+  };
+
+  /**
+   * Unmutes all sounds.
+   */
+
+  this.unmuteAll = function() {
+
+    sm2.unmute();
+
+  };
+
+  /**
+   * Calls the toggleMute() method of a SMSound object by ID.
+   *
+   * @param {string} sID The ID of the sound
+   * @return {SMSound} The SMSound object
+   */
+
+  this.toggleMute = function(sID) {
+
+    if (!idCheck(sID)) {
+      return false;
+    }
+    return sm2.sounds[sID].toggleMute();
+
+  };
+
+  /**
+   * Retrieves the memory used by the flash plugin.
+   *
+   * @return {number} The amount of memory in use
+   */
+
+  this.getMemoryUse = function() {
+
+    // flash-only
+    var ram = 0;
+
+    if (flash && fV !== 8) {
+      ram = parseInt(flash._getMemoryUse(), 10);
+    }
+
+    return ram;
+
+  };
+
+  /**
+   * Undocumented: NOPs soundManager and all SMSound objects.
+   */
+
+  this.disable = function(bNoDisable) {
+
+    // destroy all functions
+    var i;
+
+    if (bNoDisable === _undefined) {
+      bNoDisable = false;
+    }
+
+    if (disabled) {
+      return false;
+    }
+
+    disabled = true;
+    _wDS('shutdown', 1);
+
+    for (i = sm2.soundIDs.length-1; i >= 0; i--) {
+      disableObject(sm2.sounds[sm2.soundIDs[i]]);
+    }
+
+    // fire "complete", despite fail
+    initComplete(bNoDisable);
+    event.remove(window, 'load', initUserOnload);
+
+    return true;
+
+  };
+
+  /**
+   * Determines playability of a MIME type, eg. 'audio/mp3'.
+   */
+
+  this.canPlayMIME = function(sMIME) {
+
+    var result;
+
+    if (sm2.hasHTML5) {
+      result = html5CanPlay({type:sMIME});
+    }
+
+    if (!result && needsFlash) {
+      // if flash 9, test netStream (movieStar) types as well.
+      result = (sMIME && sm2.ok() ? !!((fV > 8 ? sMIME.match(netStreamMimeTypes) : null) || sMIME.match(sm2.mimePattern)) : null); // TODO: make less "weird" (per JSLint)
+    }
+
+    return result;
+
+  };
+
+  /**
+   * Determines playability of a URL based on audio support.
+   *
+   * @param {string} sURL The URL to test
+   * @return {boolean} URL playability
+   */
+
+  this.canPlayURL = function(sURL) {
+
+    var result;
+
+    if (sm2.hasHTML5) {
+      result = html5CanPlay({url: sURL});
+    }
+
+    if (!result && needsFlash) {
+      result = (sURL && sm2.ok() ? !!(sURL.match(sm2.filePattern)) : null);
+    }
+
+    return result;
+
+  };
+
+  /**
+   * Determines playability of an HTML DOM &lt;a&gt; object (or similar object literal) based on audio support.
+   *
+   * @param {object} oLink an HTML DOM &lt;a&gt; object or object literal including href and/or type attributes
+   * @return {boolean} URL playability
+   */
+
+  this.canPlayLink = function(oLink) {
+
+    if (oLink.type !== _undefined && oLink.type) {
+      if (sm2.canPlayMIME(oLink.type)) {
+        return true;
+      }
+    }
+
+    return sm2.canPlayURL(oLink.href);
+
+  };
+
+  /**
+   * Retrieves a SMSound object by ID.
+   *
+   * @param {string} sID The ID of the sound
+   * @return {SMSound} The SMSound object
+   */
+
+  this.getSoundById = function(sID, _suppressDebug) {
+
+    if (!sID) {
+      return null;
+    }
+
+    var result = sm2.sounds[sID];
+
+    // <d>
+    if (!result && !_suppressDebug) {
+      sm2._wD(sm + '.getSoundById(): Sound "' + sID + '" not found.', 2);
+    }
+    // </d>
+
+    return result;
+
+  };
+
+  /**
+   * Queues a callback for execution when SoundManager has successfully initialized.
+   *
+   * @param {function} oMethod The callback method to fire
+   * @param {object} oScope Optional: The scope to apply to the callback
+   */
+
+  this.onready = function(oMethod, oScope) {
+
+    var sType = 'onready',
+        result = false;
+
+    if (typeof oMethod === 'function') {
+
+      // <d>
+      if (didInit) {
+        sm2._wD(str('queue', sType));
+      }
+      // </d>
+
+      if (!oScope) {
+        oScope = window;
+      }
+
+      addOnEvent(sType, oMethod, oScope);
+      processOnEvents();
+
+      result = true;
+
+    } else {
+
+      throw str('needFunction', sType);
+
+    }
+
+    return result;
+
+  };
+
+  /**
+   * Queues a callback for execution when SoundManager has failed to initialize.
+   *
+   * @param {function} oMethod The callback method to fire
+   * @param {object} oScope Optional: The scope to apply to the callback
+   */
+
+  this.ontimeout = function(oMethod, oScope) {
+
+    var sType = 'ontimeout',
+        result = false;
+
+    if (typeof oMethod === 'function') {
+
+      // <d>
+      if (didInit) {
+        sm2._wD(str('queue', sType));
+      }
+      // </d>
+
+      if (!oScope) {
+        oScope = window;
+      }
+
+      addOnEvent(sType, oMethod, oScope);
+      processOnEvents({type:sType});
+
+      result = true;
+
+    } else {
+
+      throw str('needFunction', sType);
+
+    }
+
+    return result;
+
+  };
+
+  /**
+   * Writes console.log()-style debug output to a console or in-browser element.
+   * Applies when debugMode = true
+   *
+   * @param {string} sText The console message
+   * @param {object} nType Optional log level (number), or object. Number case: Log type/style where 0 = 'info', 1 = 'warn', 2 = 'error'. Object case: Object to be dumped.
+   */
+
+  this._writeDebug = function(sText, sTypeOrObject) {
+
+    // pseudo-private console.log()-style output
+    // <d>
+
+    var sDID = 'soundmanager-debug', o, oItem;
+
+    if (!sm2.debugMode) {
+      return false;
+    }
+
+    if (hasConsole && sm2.useConsole) {
+      if (sTypeOrObject && typeof sTypeOrObject === 'object') {
+        // object passed; dump to console.
+        console.log(sText, sTypeOrObject);
+      } else if (debugLevels[sTypeOrObject] !== _undefined) {
+        console[debugLevels[sTypeOrObject]](sText);
+      } else {
+        console.log(sText);
+      }
+      if (sm2.consoleOnly) {
+        return true;
+      }
+    }
+
+    o = id(sDID);
+
+    if (!o) {
+      return false;
+    }
+
+    oItem = doc.createElement('div');
+
+    if (++wdCount % 2 === 0) {
+      oItem.className = 'sm2-alt';
+    }
+
+    if (sTypeOrObject === _undefined) {
+      sTypeOrObject = 0;
+    } else {
+      sTypeOrObject = parseInt(sTypeOrObject, 10);
+    }
+
+    oItem.appendChild(doc.createTextNode(sText));
+
+    if (sTypeOrObject) {
+      if (sTypeOrObject >= 2) {
+        oItem.style.fontWeight = 'bold';
+      }
+      if (sTypeOrObject === 3) {
+        oItem.style.color = '#ff3333';
+      }
+    }
+
+    // top-to-bottom
+    // o.appendChild(oItem);
+
+    // bottom-to-top
+    o.insertBefore(oItem, o.firstChild);
+
+    o = null;
+    // </d>
+
+    return true;
+
+  };
+
+  // <d>
+  // last-resort debugging option
+  if (wl.indexOf('sm2-debug=alert') !== -1) {
+    this._writeDebug = function(sText) {
+      window.alert(sText);
+    };
+  }
+  // </d>
+
+  // alias
+  this._wD = this._writeDebug;
+
+  /**
+   * Provides debug / state information on all SMSound objects.
+   */
+
+  this._debug = function() {
+
+    // <d>
+    var i, j;
+    _wDS('currentObj', 1);
+
+    for (i = 0, j = sm2.soundIDs.length; i < j; i++) {
+      sm2.sounds[sm2.soundIDs[i]]._debug();
+    }
+    // </d>
+
+  };
+
+  /**
+   * Restarts and re-initializes the SoundManager instance.
+   *
+   * @param {boolean} resetEvents Optional: When true, removes all registered onready and ontimeout event callbacks.
+   * @param {boolean} excludeInit Options: When true, does not call beginDelayedInit() (which would restart SM2).
+   * @return {object} soundManager The soundManager instance.
+   */
+
+  this.reboot = function(resetEvents, excludeInit) {
+
+    // reset some (or all) state, and re-init unless otherwise specified.
+
+    // <d>
+    if (sm2.soundIDs.length) {
+      sm2._wD('Destroying ' + sm2.soundIDs.length + ' SMSound object' + (sm2.soundIDs.length !== 1 ? 's' : '') + '...');
+    }
+    // </d>
+
+    var i, j, k;
+
+    for (i = sm2.soundIDs.length-1; i >= 0; i--) {
+      sm2.sounds[sm2.soundIDs[i]].destruct();
+    }
+
+    // trash ze flash (remove from the DOM)
+
+    if (flash) {
+
+      try {
+
+        if (isIE) {
+          oRemovedHTML = flash.innerHTML;
+        }
+
+        oRemoved = flash.parentNode.removeChild(flash);
+
+      } catch(e) {
+
+        // Remove failed? May be due to flash blockers silently removing the SWF object/embed node from the DOM. Warn and continue.
+
+        _wDS('badRemove', 2);
+
+      }
+
+    }
+
+    // actually, force recreate of movie.
+
+    oRemovedHTML = oRemoved = needsFlash = flash = null;
+
+    sm2.enabled = didDCLoaded = didInit = waitingForEI = initPending = didAppend = appendSuccess = disabled = useGlobalHTML5Audio = sm2.swfLoaded = false;
+
+    sm2.soundIDs = [];
+    sm2.sounds = {};
+
+    idCounter = 0;
+
+    if (!resetEvents) {
+      // reset callbacks for onready, ontimeout etc. so that they will fire again on re-init
+      for (i in on_queue) {
+        if (on_queue.hasOwnProperty(i)) {
+          for (j = 0, k = on_queue[i].length; j < k; j++) {
+            on_queue[i][j].fired = false;
+          }
+        }
+      }
+    } else {
+      // remove all callbacks entirely
+      on_queue = [];
+    }
+
+    // <d>
+    if (!excludeInit) {
+      sm2._wD(sm + ': Rebooting...');
+    }
+    // </d>
+
+    // reset HTML5 and flash canPlay test results
+
+    sm2.html5 = {
+      'usingFlash': null
+    };
+
+    sm2.flash = {};
+
+    // reset device-specific HTML/flash mode switches
+
+    sm2.html5Only = false;
+    sm2.ignoreFlash = false;
+
+    window.setTimeout(function() {
+
+      preInit();
+
+      // by default, re-init
+
+      if (!excludeInit) {
+        sm2.beginDelayedInit();
+      }
+
+    }, 20);
+
+    return sm2;
+
+  };
+
+  this.reset = function() {
+
+    /**
+     * Shuts down and restores the SoundManager instance to its original loaded state, without an explicit reboot. All onready/ontimeout handlers are removed.
+     * After this call, SM2 may be re-initialized via soundManager.beginDelayedInit().
+     * @return {object} soundManager The soundManager instance.
+     */
+
+    _wDS('reset');
+    return sm2.reboot(true, true);
+
+  };
+
+  /**
+   * Undocumented: Determines the SM2 flash movie's load progress.
+   *
+   * @return {number or null} Percent loaded, or if invalid/unsupported, null.
+   */
+
+  this.getMoviePercent = function() {
+
+    /**
+     * Interesting syntax notes...
+     * Flash/ExternalInterface (ActiveX/NPAPI) bridge methods are not typeof "function" nor instanceof Function, but are still valid.
+     * Additionally, JSLint dislikes ('PercentLoaded' in flash)-style syntax and recommends hasOwnProperty(), which does not work in this case.
+     * Furthermore, using (flash && flash.PercentLoaded) causes IE to throw "object doesn't support this property or method".
+     * Thus, 'in' syntax must be used.
+     */
+
+    return (flash && 'PercentLoaded' in flash ? flash.PercentLoaded() : null); // Yes, JSLint. See nearby comment in source for explanation.
+
+  };
+
+  /**
+   * Additional helper for manually invoking SM2's init process after DOM Ready / window.onload().
+   */
+
+  this.beginDelayedInit = function() {
+
+    windowLoaded = true;
+    domContentLoaded();
+
+    setTimeout(function() {
+
+      if (initPending) {
+        return false;
+      }
+
+      createMovie();
+      initMovie();
+      initPending = true;
+
+      return true;
+
+    }, 20);
+
+    delayWaitForEI();
+
+  };
+
+  /**
+   * Destroys the SoundManager instance and all SMSound instances.
+   */
+
+  this.destruct = function() {
+
+    sm2._wD(sm + '.destruct()');
+    sm2.disable(true);
+
+  };
+
+  /**
+   * SMSound() (sound object) constructor
+   * ------------------------------------
+   *
+   * @param {object} oOptions Sound options (id and url are required attributes)
+   * @return {SMSound} The new SMSound object
+   */
+
+  SMSound = function(oOptions) {
+
+    var s = this, resetProperties, add_html5_events, remove_html5_events, stop_html5_timer, start_html5_timer, attachOnPosition, onplay_called = false, onPositionItems = [], onPositionFired = 0, detachOnPosition, applyFromTo, lastURL = null, lastHTML5State, urlOmitted;
+
+    lastHTML5State = {
+      // tracks duration + position (time)
+      duration: null,
+      time: null
+    };
+
+    this.id = oOptions.id;
+
+    // legacy
+    this.sID = this.id;
+
+    this.url = oOptions.url;
+    this.options = mixin(oOptions);
+
+    // per-play-instance-specific options
+    this.instanceOptions = this.options;
+
+    // short alias
+    this._iO = this.instanceOptions;
+
+    // assign property defaults
+    this.pan = this.options.pan;
+    this.volume = this.options.volume;
+
+    // whether or not this object is using HTML5
+    this.isHTML5 = false;
+
+    // internal HTML5 Audio() object reference
+    this._a = null;
+
+    // for flash 8 special-case createSound() without url, followed by load/play with url case
+    urlOmitted = (this.url ? false : true);
+
+    /**
+     * SMSound() public methods
+     * ------------------------
+     */
+
+    this.id3 = {};
+
+    /**
+     * Writes SMSound object parameters to debug console
+     */
+
+    this._debug = function() {
+
+      // <d>
+      sm2._wD(s.id + ': Merged options:', s.options);
+      // </d>
+
+    };
+
+    /**
+     * Begins loading a sound per its *url*.
+     *
+     * @param {object} oOptions Optional: Sound options
+     * @return {SMSound} The SMSound object
+     */
+
+    this.load = function(oOptions) {
+
+      var oSound = null, instanceOptions;
+
+      if (oOptions !== _undefined) {
+        s._iO = mixin(oOptions, s.options);
+      } else {
+        oOptions = s.options;
+        s._iO = oOptions;
+        if (lastURL && lastURL !== s.url) {
+          _wDS('manURL');
+          s._iO.url = s.url;
+          s.url = null;
+        }
+      }
+
+      if (!s._iO.url) {
+        s._iO.url = s.url;
+      }
+
+      s._iO.url = parseURL(s._iO.url);
+
+      // ensure we're in sync
+      s.instanceOptions = s._iO;
+
+      // local shortcut
+      instanceOptions = s._iO;
+
+      sm2._wD(s.id + ': load (' + instanceOptions.url + ')');
+
+      if (!instanceOptions.url && !s.url) {
+        sm2._wD(s.id + ': load(): url is unassigned. Exiting.', 2);
+        return s;
+      }
+
+      // <d>
+      if (!s.isHTML5 && fV === 8 && !s.url && !instanceOptions.autoPlay) {
+        // flash 8 load() -> play() won't work before onload has fired.
+        sm2._wD(s.id + ': Flash 8 load() limitation: Wait for onload() before calling play().', 1);
+      }
+      // </d>
+
+      if (instanceOptions.url === s.url && s.readyState !== 0 && s.readyState !== 2) {
+        _wDS('onURL', 1);
+        // if loaded and an onload() exists, fire immediately.
+        if (s.readyState === 3 && instanceOptions.onload) {
+          // assume success based on truthy duration.
+          wrapCallback(s, function() {
+            instanceOptions.onload.apply(s, [(!!s.duration)]);
+          });
+        }
+        return s;
+      }
+
+      // reset a few state properties
+
+      s.loaded = false;
+      s.readyState = 1;
+      s.playState = 0;
+      s.id3 = {};
+
+      // TODO: If switching from HTML5 -> flash (or vice versa), stop currently-playing audio.
+
+      if (html5OK(instanceOptions)) {
+
+        oSound = s._setup_html5(instanceOptions);
+
+        if (!oSound._called_load) {
+
+          s._html5_canplay = false;
+
+          // TODO: review called_load / html5_canplay logic
+
+          // if url provided directly to load(), assign it here.
+
+          if (s.url !== instanceOptions.url) {
+
+            sm2._wD(_wDS('manURL') + ': ' + instanceOptions.url);
+
+            s._a.src = instanceOptions.url;
+
+            // TODO: review / re-apply all relevant options (volume, loop, onposition etc.)
+
+            // reset position for new URL
+            s.setPosition(0);
+
+          }
+
+          // given explicit load call, try to preload.
+
+          // early HTML5 implementation (non-standard)
+          s._a.autobuffer = 'auto';
+
+          // standard property, values: none / metadata / auto
+          // reference: http://msdn.microsoft.com/en-us/library/ie/ff974759%28v=vs.85%29.aspx
+          s._a.preload = 'auto';
+
+          s._a._called_load = true;
+
+        } else {
+
+          sm2._wD(s.id + ': Ignoring request to load again');
+
+        }
+
+      } else {
+
+        if (sm2.html5Only) {
+          sm2._wD(s.id + ': No flash support. Exiting.');
+          return s;
+        }
+
+        if (s._iO.url && s._iO.url.match(/data\:/i)) {
+          // data: URIs not supported by Flash, either.
+          sm2._wD(s.id + ': data: URIs not supported via Flash. Exiting.');
+          return s;
+        }
+
+        try {
+          s.isHTML5 = false;
+          s._iO = policyFix(loopFix(instanceOptions));
+          // re-assign local shortcut
+          instanceOptions = s._iO;
+          if (fV === 8) {
+            flash._load(s.id, instanceOptions.url, instanceOptions.stream, instanceOptions.autoPlay, instanceOptions.usePolicyFile);
+          } else {
+            flash._load(s.id, instanceOptions.url, !!(instanceOptions.stream), !!(instanceOptions.autoPlay), instanceOptions.loops||1, !!(instanceOptions.autoLoad), instanceOptions.usePolicyFile);
+          }
+        } catch(e) {
+          _wDS('smError', 2);
+          debugTS('onload', false);
+          catchError({type:'SMSOUND_LOAD_JS_EXCEPTION', fatal:true});
+        }
+
+      }
+
+      // after all of this, ensure sound url is up to date.
+      s.url = instanceOptions.url;
+
+      return s;
+
+    };
+
+    /**
+     * Unloads a sound, canceling any open HTTP requests.
+     *
+     * @return {SMSound} The SMSound object
+     */
+
+    this.unload = function() {
+
+      // Flash 8/AS2 can't "close" a stream - fake it by loading an empty URL
+      // Flash 9/AS3: Close stream, preventing further load
+      // HTML5: Most UAs will use empty URL
+
+      if (s.readyState !== 0) {
+
+        sm2._wD(s.id + ': unload()');
+
+        if (!s.isHTML5) {
+
+          if (fV === 8) {
+            flash._unload(s.id, emptyURL);
+          } else {
+            flash._unload(s.id);
+          }
+
+        } else {
+
+          stop_html5_timer();
+
+          if (s._a) {
+
+            s._a.pause();
+
+            // update empty URL, too
+            lastURL = html5Unload(s._a);
+
+          }
+
+        }
+
+        // reset load/status flags
+        resetProperties();
+
+      }
+
+      return s;
+
+    };
+
+    /**
+     * Unloads and destroys a sound.
+     */
+
+    this.destruct = function(_bFromSM) {
+
+      sm2._wD(s.id + ': Destruct');
+
+      if (!s.isHTML5) {
+
+        // kill sound within Flash
+        // Disable the onfailure handler
+        s._iO.onfailure = null;
+        flash._destroySound(s.id);
+
+      } else {
+
+        stop_html5_timer();
+
+        if (s._a) {
+          s._a.pause();
+          html5Unload(s._a);
+          if (!useGlobalHTML5Audio) {
+            remove_html5_events();
+          }
+          // break obvious circular reference
+          s._a._s = null;
+          s._a = null;
+        }
+
+      }
+
+      if (!_bFromSM) {
+        // ensure deletion from controller
+        sm2.destroySound(s.id, true);
+      }
+
+    };
+
+    /**
+     * Begins playing a sound.
+     *
+     * @param {object} oOptions Optional: Sound options
+     * @return {SMSound} The SMSound object
+     */
+
+    this.play = function(oOptions, _updatePlayState) {
+
+      var fN, allowMulti, a, onready,
+          audioClone, onended, oncanplay,
+          startOK = true,
+          exit = null;
+
+      // <d>
+      fN = s.id + ': play(): ';
+      // </d>
+
+      // default to true
+      _updatePlayState = (_updatePlayState === _undefined ? true : _updatePlayState);
+
+      if (!oOptions) {
+        oOptions = {};
+      }
+
+      // first, use local URL (if specified)
+      if (s.url) {
+        s._iO.url = s.url;
+      }
+
+      // mix in any options defined at createSound()
+      s._iO = mixin(s._iO, s.options);
+
+      // mix in any options specific to this method
+      s._iO = mixin(oOptions, s._iO);
+
+      s._iO.url = parseURL(s._iO.url);
+
+      s.instanceOptions = s._iO;
+
+      // RTMP-only
+      if (!s.isHTML5 && s._iO.serverURL && !s.connected) {
+        if (!s.getAutoPlay()) {
+          sm2._wD(fN +' Netstream not connected yet - setting autoPlay');
+          s.setAutoPlay(true);
+        }
+        // play will be called in onconnect()
+        return s;
+      }
+
+      if (html5OK(s._iO)) {
+        s._setup_html5(s._iO);
+        start_html5_timer();
+      }
+
+      if (s.playState === 1 && !s.paused) {
+        allowMulti = s._iO.multiShot;
+        if (!allowMulti) {
+          sm2._wD(fN + 'Already playing (one-shot)', 1);
+          if (s.isHTML5) {
+            // go back to original position.
+            s.setPosition(s._iO.position);
+          }
+          exit = s;
+        } else {
+          sm2._wD(fN + 'Already playing (multi-shot)', 1);
+        }
+      }
+
+      if (exit !== null) {
+        return exit;
+      }
+
+      // edge case: play() with explicit URL parameter
+      if (oOptions.url && oOptions.url !== s.url) {
+
+        // special case for createSound() followed by load() / play() with url; avoid double-load case.
+        if (!s.readyState && !s.isHTML5 && fV === 8 && urlOmitted) {
+
+          urlOmitted = false;
+
+        } else {
+
+          // load using merged options
+          s.load(s._iO);
+
+        }
+
+      }
+
+      if (!s.loaded) {
+
+        if (s.readyState === 0) {
+
+          sm2._wD(fN + 'Attempting to load');
+
+          // try to get this sound playing ASAP
+          if (!s.isHTML5 && !sm2.html5Only) {
+
+            // flash: assign directly because setAutoPlay() increments the instanceCount
+            s._iO.autoPlay = true;
+            s.load(s._iO);
+
+          } else if (s.isHTML5) {
+
+            // iOS needs this when recycling sounds, loading a new URL on an existing object.
+            s.load(s._iO);
+
+          } else {
+
+            sm2._wD(fN + 'Unsupported type. Exiting.');
+            exit = s;
+
+          }
+
+          // HTML5 hack - re-set instanceOptions?
+          s.instanceOptions = s._iO;
+
+        } else if (s.readyState === 2) {
+
+          sm2._wD(fN + 'Could not load - exiting', 2);
+          exit = s;
+
+        } else {
+
+          sm2._wD(fN + 'Loading - attempting to play...');
+
+        }
+
+      } else {
+
+        // "play()"
+        sm2._wD(fN.substr(0, fN.lastIndexOf(':')));
+
+      }
+
+      if (exit !== null) {
+        return exit;
+      }
+
+      if (!s.isHTML5 && fV === 9 && s.position > 0 && s.position === s.duration) {
+        // flash 9 needs a position reset if play() is called while at the end of a sound.
+        sm2._wD(fN + 'Sound at end, resetting to position:0');
+        oOptions.position = 0;
+      }
+
+      /**
+       * Streams will pause when their buffer is full if they are being loaded.
+       * In this case paused is true, but the song hasn't started playing yet.
+       * If we just call resume() the onplay() callback will never be called.
+       * So only call resume() if the position is > 0.
+       * Another reason is because options like volume won't have been applied yet.
+       * For normal sounds, just resume.
+       */
+
+      if (s.paused && s.position >= 0 && (!s._iO.serverURL || s.position > 0)) {
+
+        // https://gist.github.com/37b17df75cc4d7a90bf6
+        sm2._wD(fN + 'Resuming from paused state', 1);
+        s.resume();
+
+      } else {
+
+        s._iO = mixin(oOptions, s._iO);
+
+        // apply from/to parameters, if they exist (and not using RTMP)
+        if (s._iO.from !== null && s._iO.to !== null && s.instanceCount === 0 && s.playState === 0 && !s._iO.serverURL) {
+
+          onready = function() {
+            // sound "canplay" or onload()
+            // re-apply from/to to instance options, and start playback
+            s._iO = mixin(oOptions, s._iO);
+            s.play(s._iO);
+          };
+
+          // HTML5 needs to at least have "canplay" fired before seeking.
+          if (s.isHTML5 && !s._html5_canplay) {
+
+            // this hasn't been loaded yet. load it first, and then do this again.
+            sm2._wD(fN + 'Beginning load for from/to case');
+
+            s.load({
+              // note: custom HTML5-only event added for from/to implementation.
+              _oncanplay: onready
+            });
+
+            exit = false;
+
+          } else if (!s.isHTML5 && !s.loaded && (!s.readyState || s.readyState !== 2)) {
+
+            // to be safe, preload the whole thing in Flash.
+
+            sm2._wD(fN + 'Preloading for from/to case');
+
+            s.load({
+              onload: onready
+            });
+
+            exit = false;
+
+          }
+
+          if (exit !== null) {
+            return exit;
+          }
+
+          // otherwise, we're ready to go. re-apply local options, and continue
+
+          s._iO = applyFromTo();
+
+        }
+
+        // sm2._wD(fN + 'Starting to play');
+
+        // increment instance counter, where enabled + supported
+        if (!s.instanceCount || s._iO.multiShotEvents || (s.isHTML5 && s._iO.multiShot && !useGlobalHTML5Audio) || (!s.isHTML5 && fV > 8 && !s.getAutoPlay())) {
+          s.instanceCount++;
+        }
+
+        // if first play and onposition parameters exist, apply them now
+        if (s._iO.onposition && s.playState === 0) {
+          attachOnPosition(s);
+        }
+
+        s.playState = 1;
+        s.paused = false;
+
+        s.position = (s._iO.position !== _undefined && !isNaN(s._iO.position) ? s._iO.position : 0);
+
+        if (!s.isHTML5) {
+          s._iO = policyFix(loopFix(s._iO));
+        }
+
+        if (s._iO.onplay && _updatePlayState) {
+          s._iO.onplay.apply(s);
+          onplay_called = true;
+        }
+
+        s.setVolume(s._iO.volume, true);
+        s.setPan(s._iO.pan, true);
+
+        if (!s.isHTML5) {
+
+          startOK = flash._start(s.id, s._iO.loops || 1, (fV === 9 ? s.position : s.position / msecScale), s._iO.multiShot || false);
+
+          if (fV === 9 && !startOK) {
+            // edge case: no sound hardware, or 32-channel flash ceiling hit.
+            // applies only to Flash 9, non-NetStream/MovieStar sounds.
+            // http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/Sound.html#play%28%29
+            sm2._wD(fN + 'No sound hardware, or 32-sound ceiling hit', 2);
+            if (s._iO.onplayerror) {
+              s._iO.onplayerror.apply(s);
+            }
+
+          }
+
+        } else {
+
+          if (s.instanceCount < 2) {
+
+            // HTML5 single-instance case
+
+            start_html5_timer();
+
+            a = s._setup_html5();
+
+            s.setPosition(s._iO.position);
+
+            a.play();
+
+          } else {
+
+            // HTML5 multi-shot case
+
+            sm2._wD(s.id + ': Cloning Audio() for instance #' + s.instanceCount + '...');
+
+            audioClone = new Audio(s._iO.url);
+
+            onended = function() {
+              event.remove(audioClone, 'ended', onended);
+              s._onfinish(s);
+              // cleanup
+              html5Unload(audioClone);
+              audioClone = null;
+            };
+
+            oncanplay = function() {
+              event.remove(audioClone, 'canplay', oncanplay);
+              try {
+                audioClone.currentTime = s._iO.position/msecScale;
+              } catch(err) {
+                complain(s.id + ': multiShot play() failed to apply position of ' + (s._iO.position/msecScale));
+              }
+              audioClone.play();
+            };
+
+            event.add(audioClone, 'ended', onended);
+
+            // apply volume to clones, too
+            if (s._iO.volume !== undefined) {
+              audioClone.volume = Math.max(0, Math.min(1, s._iO.volume/100));
+            }
+
+            // playing multiple muted sounds? if you do this, you're weird ;) - but let's cover it.
+            if (s.muted) {
+              audioClone.muted = true;
+            }
+
+            if (s._iO.position) {
+              // HTML5 audio can't seek before onplay() event has fired.
+              // wait for canplay, then seek to position and start playback.
+              event.add(audioClone, 'canplay', oncanplay);
+            } else {
+              // begin playback at currentTime: 0
+              audioClone.play();
+            }
+
+          }
+
+        }
+
+      }
+
+      return s;
+
+    };
+
+    // just for convenience
+    this.start = this.play;
+
+    /**
+     * Stops playing a sound (and optionally, all sounds)
+     *
+     * @param {boolean} bAll Optional: Whether to stop all sounds
+     * @return {SMSound} The SMSound object
+     */
+
+    this.stop = function(bAll) {
+
+      var instanceOptions = s._iO,
+          originalPosition;
+
+      if (s.playState === 1) {
+
+        sm2._wD(s.id + ': stop()');
+
+        s._onbufferchange(0);
+        s._resetOnPosition(0);
+        s.paused = false;
+
+        if (!s.isHTML5) {
+          s.playState = 0;
+        }
+
+        // remove onPosition listeners, if any
+        detachOnPosition();
+
+        // and "to" position, if set
+        if (instanceOptions.to) {
+          s.clearOnPosition(instanceOptions.to);
+        }
+
+        if (!s.isHTML5) {
+
+          flash._stop(s.id, bAll);
+
+          // hack for netStream: just unload
+          if (instanceOptions.serverURL) {
+            s.unload();
+          }
+
+        } else {
+
+          if (s._a) {
+
+            originalPosition = s.position;
+
+            // act like Flash, though
+            s.setPosition(0);
+
+            // hack: reflect old position for onstop() (also like Flash)
+            s.position = originalPosition;
+
+            // html5 has no stop()
+            // NOTE: pausing means iOS requires interaction to resume.
+            s._a.pause();
+
+            s.playState = 0;
+
+            // and update UI
+            s._onTimer();
+
+            stop_html5_timer();
+
+          }
+
+        }
+
+        s.instanceCount = 0;
+        s._iO = {};
+
+        if (instanceOptions.onstop) {
+          instanceOptions.onstop.apply(s);
+        }
+
+      }
+
+      return s;
+
+    };
+
+    /**
+     * Undocumented/internal: Sets autoPlay for RTMP.
+     *
+     * @param {boolean} autoPlay state
+     */
+
+    this.setAutoPlay = function(autoPlay) {
+
+      sm2._wD(s.id + ': Autoplay turned ' + (autoPlay ? 'on' : 'off'));
+      s._iO.autoPlay = autoPlay;
+
+      if (!s.isHTML5) {
+        flash._setAutoPlay(s.id, autoPlay);
+        if (autoPlay) {
+          // only increment the instanceCount if the sound isn't loaded (TODO: verify RTMP)
+          if (!s.instanceCount && s.readyState === 1) {
+            s.instanceCount++;
+            sm2._wD(s.id + ': Incremented instance count to '+s.instanceCount);
+          }
+        }
+      }
+
+    };
+
+    /**
+     * Undocumented/internal: Returns the autoPlay boolean.
+     *
+     * @return {boolean} The current autoPlay value
+     */
+
+    this.getAutoPlay = function() {
+
+      return s._iO.autoPlay;
+
+    };
+
+    /**
+     * Sets the position of a sound.
+     *
+     * @param {number} nMsecOffset Position (milliseconds)
+     * @return {SMSound} The SMSound object
+     */
+
+    this.setPosition = function(nMsecOffset) {
+
+      if (nMsecOffset === _undefined) {
+        nMsecOffset = 0;
+      }
+
+      var position, position1K,
+          // Use the duration from the instance options, if we don't have a track duration yet.
+          // position >= 0 and <= current available (loaded) duration
+          offset = (s.isHTML5 ? Math.max(nMsecOffset, 0) : Math.min(s.duration || s._iO.duration, Math.max(nMsecOffset, 0)));
+
+      s.position = offset;
+      position1K = s.position/msecScale;
+      s._resetOnPosition(s.position);
+      s._iO.position = offset;
+
+      if (!s.isHTML5) {
+
+        position = (fV === 9 ? s.position : position1K);
+
+        if (s.readyState && s.readyState !== 2) {
+          // if paused or not playing, will not resume (by playing)
+          flash._setPosition(s.id, position, (s.paused || !s.playState), s._iO.multiShot);
+        }
+
+      } else if (s._a) {
+
+        // Set the position in the canplay handler if the sound is not ready yet
+        if (s._html5_canplay) {
+
+          if (s._a.currentTime !== position1K) {
+
+            /**
+             * DOM/JS errors/exceptions to watch out for:
+             * if seek is beyond (loaded?) position, "DOM exception 11"
+             * "INDEX_SIZE_ERR": DOM exception 1
+             */
+            sm2._wD(s.id + ': setPosition('+position1K+')');
+
+            try {
+              s._a.currentTime = position1K;
+              if (s.playState === 0 || s.paused) {
+                // allow seek without auto-play/resume
+                s._a.pause();
+              }
+            } catch(e) {
+              sm2._wD(s.id + ': setPosition(' + position1K + ') failed: ' + e.message, 2);
+            }
+
+          }
+
+        } else if (position1K) {
+
+          // warn on non-zero seek attempts
+          sm2._wD(s.id + ': setPosition(' + position1K + '): Cannot seek yet, sound not ready', 2);
+          return s;
+
+        }
+
+        if (s.paused) {
+
+          // if paused, refresh UI right away
+          // force update
+          s._onTimer(true);
+
+        }
+
+      }
+
+      return s;
+
+    };
+
+    /**
+     * Pauses sound playback.
+     *
+     * @return {SMSound} The SMSound object
+     */
+
+    this.pause = function(_bCallFlash) {
+
+      if (s.paused || (s.playState === 0 && s.readyState !== 1)) {
+        return s;
+      }
+
+      sm2._wD(s.id + ': pause()');
+      s.paused = true;
+
+      if (!s.isHTML5) {
+        if (_bCallFlash || _bCallFlash === _undefined) {
+          flash._pause(s.id, s._iO.multiShot);
+        }
+      } else {
+        s._setup_html5().pause();
+        stop_html5_timer();
+      }
+
+      if (s._iO.onpause) {
+        s._iO.onpause.apply(s);
+      }
+
+      return s;
+
+    };
+
+    /**
+     * Resumes sound playback.
+     *
+     * @return {SMSound} The SMSound object
+     */
+
+    /**
+     * When auto-loaded streams pause on buffer full they have a playState of 0.
+     * We need to make sure that the playState is set to 1 when these streams "resume".
+     * When a paused stream is resumed, we need to trigger the onplay() callback if it
+     * hasn't been called already. In this case since the sound is being played for the
+     * first time, I think it's more appropriate to call onplay() rather than onresume().
+     */
+
+    this.resume = function() {
+
+      var instanceOptions = s._iO;
+
+      if (!s.paused) {
+        return s;
+      }
+
+      sm2._wD(s.id + ': resume()');
+      s.paused = false;
+      s.playState = 1;
+
+      if (!s.isHTML5) {
+        if (instanceOptions.isMovieStar && !instanceOptions.serverURL) {
+          // Bizarre Webkit bug (Chrome reported via 8tracks.com dudes): AAC content paused for 30+ seconds(?) will not resume without a reposition.
+          s.setPosition(s.position);
+        }
+        // flash method is toggle-based (pause/resume)
+        flash._pause(s.id, instanceOptions.multiShot);
+      } else {
+        s._setup_html5().play();
+        start_html5_timer();
+      }
+
+      if (!onplay_called && instanceOptions.onplay) {
+        instanceOptions.onplay.apply(s);
+        onplay_called = true;
+      } else if (instanceOptions.onresume) {
+        instanceOptions.onresume.apply(s);
+      }
+
+      return s;
+
+    };
+
+    /**
+     * Toggles sound playback.
+     *
+     * @return {SMSound} The SMSound object
+     */
+
+    this.togglePause = function() {
+
+      sm2._wD(s.id + ': togglePause()');
+
+      if (s.playState === 0) {
+        s.play({
+          position: (fV === 9 && !s.isHTML5 ? s.position : s.position / msecScale)
+        });
+        return s;
+      }
+
+      if (s.paused) {
+        s.resume();
+      } else {
+        s.pause();
+      }
+
+      return s;
+
+    };
+
+    /**
+     * Sets the panning (L-R) effect.
+     *
+     * @param {number} nPan The pan value (-100 to 100)
+     * @return {SMSound} The SMSound object
+     */
+
+    this.setPan = function(nPan, bInstanceOnly) {
+
+      if (nPan === _undefined) {
+        nPan = 0;
+      }
+
+      if (bInstanceOnly === _undefined) {
+        bInstanceOnly = false;
+      }
+
+      if (!s.isHTML5) {
+        flash._setPan(s.id, nPan);
+      } // else { no HTML5 pan? }
+
+      s._iO.pan = nPan;
+
+      if (!bInstanceOnly) {
+        s.pan = nPan;
+        s.options.pan = nPan;
+      }
+
+      return s;
+
+    };
+
+    /**
+     * Sets the volume.
+     *
+     * @param {number} nVol The volume value (0 to 100)
+     * @return {SMSound} The SMSound object
+     */
+
+    this.setVolume = function(nVol, _bInstanceOnly) {
+
+      /**
+       * Note: Setting volume has no effect on iOS "special snowflake" devices.
+       * Hardware volume control overrides software, and volume
+       * will always return 1 per Apple docs. (iOS 4 + 5.)
+       * http://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/AddingSoundtoCanvasAnimations/AddingSoundtoCanvasAnimations.html
+       */
+
+      if (nVol === _undefined) {
+        nVol = 100;
+      }
+
+      if (_bInstanceOnly === _undefined) {
+        _bInstanceOnly = false;
+      }
+
+      if (!s.isHTML5) {
+        flash._setVolume(s.id, (sm2.muted && !s.muted) || s.muted?0:nVol);
+      } else if (s._a) {
+        if (sm2.muted && !s.muted) {
+          s.muted = true;
+          s._a.muted = true;
+        }
+        // valid range: 0-1
+        s._a.volume = Math.max(0, Math.min(1, nVol/100));
+      }
+
+      s._iO.volume = nVol;
+
+      if (!_bInstanceOnly) {
+        s.volume = nVol;
+        s.options.volume = nVol;
+      }
+
+      return s;
+
+    };
+
+    /**
+     * Mutes the sound.
+     *
+     * @return {SMSound} The SMSound object
+     */
+
+    this.mute = function() {
+
+      s.muted = true;
+
+      if (!s.isHTML5) {
+        flash._setVolume(s.id, 0);
+      } else if (s._a) {
+        s._a.muted = true;
+      }
+
+      return s;
+
+    };
+
+    /**
+     * Unmutes the sound.
+     *
+     * @return {SMSound} The SMSound object
+     */
+
+    this.unmute = function() {
+
+      s.muted = false;
+      var hasIO = (s._iO.volume !== _undefined);
+
+      if (!s.isHTML5) {
+        flash._setVolume(s.id, hasIO?s._iO.volume:s.options.volume);
+      } else if (s._a) {
+        s._a.muted = false;
+      }
+
+      return s;
+
+    };
+
+    /**
+     * Toggles the muted state of a sound.
+     *
+     * @return {SMSound} The SMSound object
+     */
+
+    this.toggleMute = function() {
+
+      return (s.muted?s.unmute():s.mute());
+
+    };
+
+    /**
+     * Registers a callback to be fired when a sound reaches a given position during playback.
+     *
+     * @param {number} nPosition The position to watch for
+     * @param {function} oMethod The relevant callback to fire
+     * @param {object} oScope Optional: The scope to apply the callback to
+     * @return {SMSound} The SMSound object
+     */
+
+    this.onPosition = function(nPosition, oMethod, oScope) {
+
+      // TODO: basic dupe checking?
+
+      onPositionItems.push({
+        position: parseInt(nPosition, 10),
+        method: oMethod,
+        scope: (oScope !== _undefined ? oScope : s),
+        fired: false
+      });
+
+      return s;
+
+    };
+
+    // legacy/backwards-compability: lower-case method name
+    this.onposition = this.onPosition;
+
+    /**
+     * Removes registered callback(s) from a sound, by position and/or callback.
+     *
+     * @param {number} nPosition The position to clear callback(s) for
+     * @param {function} oMethod Optional: Identify one callback to be removed when multiple listeners exist for one position
+     * @return {SMSound} The SMSound object
+     */
+
+    this.clearOnPosition = function(nPosition, oMethod) {
+
+      var i;
+
+      nPosition = parseInt(nPosition, 10);
+
+      if (isNaN(nPosition)) {
+        // safety check
+        return false;
+      }
+
+      for (i=0; i < onPositionItems.length; i++) {
+
+        if (nPosition === onPositionItems[i].position) {
+          // remove this item if no method was specified, or, if the method matches
+          if (!oMethod || (oMethod === onPositionItems[i].method)) {
+            if (onPositionItems[i].fired) {
+              // decrement "fired" counter, too
+              onPositionFired--;
+            }
+            onPositionItems.splice(i, 1);
+          }
+        }
+
+      }
+
+    };
+
+    this._processOnPosition = function() {
+
+      var i, item, j = onPositionItems.length;
+               
+      if (!j || !s.playState || onPositionFired >= j) {
+        return false;
+      }
+
+      for (i=j-1; i >= 0; i--) {
+        item = onPositionItems[i];
+        if (!item.fired && s.position >= item.position) {
+          item.fired = true;
+          onPositionFired++;
+          item.method.apply(item.scope, [item.position]);
+                 j = onPositionItems.length; //  reset j -- onPositionItems.length can be changed in the item callback above... occasionally breaking the loop.
+        }
+      }
+       
+      return true;
+
+    };
+
+    this._resetOnPosition = function(nPosition) {
+
+      // reset "fired" for items interested in this position
+      var i, item, j = onPositionItems.length;
+
+      if (!j) {
+        return false;
+      }
+
+      for (i=j-1; i >= 0; i--) {
+        item = onPositionItems[i];
+        if (item.fired && nPosition <= item.position) {
+          item.fired = false;
+          onPositionFired--;
+        }
+      }
+
+      return true;
+
+    };
+
+    /**
+     * SMSound() private internals
+     * --------------------------------
+     */
+
+    applyFromTo = function() {
+
+      var instanceOptions = s._iO,
+          f = instanceOptions.from,
+          t = instanceOptions.to,
+          start, end;
+
+      end = function() {
+
+        // end has been reached.
+        sm2._wD(s.id + ': "To" time of ' + t + ' reached.');
+
+        // detach listener
+        s.clearOnPosition(t, end);
+
+        // stop should clear this, too
+        s.stop();
+
+      };
+
+      start = function() {
+
+        sm2._wD(s.id + ': Playing "from" ' + f);
+
+        // add listener for end
+        if (t !== null && !isNaN(t)) {
+          s.onPosition(t, end);
+        }
+
+      };
+
+      if (f !== null && !isNaN(f)) {
+
+        // apply to instance options, guaranteeing correct start position.
+        instanceOptions.position = f;
+
+        // multiShot timing can't be tracked, so prevent that.
+        instanceOptions.multiShot = false;
+
+        start();
+
+      }
+
+      // return updated instanceOptions including starting position
+      return instanceOptions;
+
+    };
+
+    attachOnPosition = function() {
+
+      var item,
+          op = s._iO.onposition;
+
+      // attach onposition things, if any, now.
+
+      if (op) {
+
+        for (item in op) {
+          if (op.hasOwnProperty(item)) {
+            s.onPosition(parseInt(item, 10), op[item]);
+          }
+        }
+
+      }
+
+    };
+
+    detachOnPosition = function() {
+
+      var item,
+          op = s._iO.onposition;
+
+      // detach any onposition()-style listeners.
+
+      if (op) {
+
+        for (item in op) {
+          if (op.hasOwnProperty(item)) {
+            s.clearOnPosition(parseInt(item, 10));
+          }
+        }
+
+      }
+
+    };
+
+    start_html5_timer = function() {
+
+      if (s.isHTML5) {
+        startTimer(s);
+      }
+
+    };
+
+    stop_html5_timer = function() {
+
+      if (s.isHTML5) {
+        stopTimer(s);
+      }
+
+    };
+
+    resetProperties = function(retainPosition) {
+
+      if (!retainPosition) {
+        onPositionItems = [];
+        onPositionFired = 0;
+      }
+
+      onplay_called = false;
+
+      s._hasTimer = null;
+      s._a = null;
+      s._html5_canplay = false;
+      s.bytesLoaded = null;
+      s.bytesTotal = null;
+      s.duration = (s._iO && s._iO.duration ? s._iO.duration : null);
+      s.durationEstimate = null;
+      s.buffered = [];
+
+      // legacy: 1D array
+      s.eqData = [];
+
+      s.eqData.left = [];
+      s.eqData.right = [];
+
+      s.failures = 0;
+      s.isBuffering = false;
+      s.instanceOptions = {};
+      s.instanceCount = 0;
+      s.loaded = false;
+      s.metadata = {};
+
+      // 0 = uninitialised, 1 = loading, 2 = failed/error, 3 = loaded/success
+      s.readyState = 0;
+
+      s.muted = false;
+      s.paused = false;
+
+      s.peakData = {
+        left: 0,
+        right: 0
+      };
+
+      s.waveformData = {
+        left: [],
+        right: []
+      };
+
+      s.playState = 0;
+      s.position = null;
+
+      s.id3 = {};
+
+    };
+
+    resetProperties();
+
+    /**
+     * Pseudo-private SMSound internals
+     * --------------------------------
+     */
+
+    this._onTimer = function(bForce) {
+
+      /**
+       * HTML5-only _whileplaying() etc.
+       * called from both HTML5 native events, and polling/interval-based timers
+       * mimics flash and fires only when time/duration change, so as to be polling-friendly
+       */
+
+      var duration, isNew = false, time, x = {};
+
+      if (s._hasTimer || bForce) {
+
+        // TODO: May not need to track readyState (1 = loading)
+
+        if (s._a && (bForce || ((s.playState > 0 || s.readyState === 1) && !s.paused))) {
+
+          duration = s._get_html5_duration();
+
+          if (duration !== lastHTML5State.duration) {
+
+            lastHTML5State.duration = duration;
+            s.duration = duration;
+            isNew = true;
+
+          }
+
+          // TODO: investigate why this goes wack if not set/re-set each time.
+          s.durationEstimate = s.duration;
+
+          time = (s._a.currentTime * msecScale || 0);
+
+          if (time !== lastHTML5State.time) {
+
+            lastHTML5State.time = time;
+            isNew = true;
+
+          }
+
+          if (isNew || bForce) {
+
+            s._whileplaying(time,x,x,x,x);
+
+          }
+
+        }/* else {
+
+          // sm2._wD('_onTimer: Warn for "'+s.id+'": '+(!s._a?'Could not find element. ':'')+(s.playState === 0?'playState bad, 0?':'playState = '+s.playState+', OK'));
+
+          return false;
+
+        }*/
+
+        return isNew;
+
+      }
+
+    };
+
+    this._get_html5_duration = function() {
+
+      var instanceOptions = s._iO,
+          // if audio object exists, use its duration - else, instance option duration (if provided - it's a hack, really, and should be retired) OR null
+          d = (s._a && s._a.duration ? s._a.duration*msecScale : (instanceOptions && instanceOptions.duration ? instanceOptions.duration : null)),
+          result = (d && !isNaN(d) && d !== Infinity ? d : null);
+
+      return result;
+
+    };
+
+    this._apply_loop = function(a, nLoops) {
+
+      /**
+       * boolean instead of "loop", for webkit? - spec says string. http://www.w3.org/TR/html-markup/audio.html#audio.attrs.loop
+       * note that loop is either off or infinite under HTML5, unlike Flash which allows arbitrary loop counts to be specified.
+       */
+
+      // <d>
+      if (!a.loop && nLoops > 1) {
+        sm2._wD('Note: Native HTML5 looping is infinite.', 1);
+      }
+      // </d>
+
+      a.loop = (nLoops > 1 ? 'loop' : '');
+
+    };
+
+    this._setup_html5 = function(oOptions) {
+
+      var instanceOptions = mixin(s._iO, oOptions),
+          a = useGlobalHTML5Audio ? globalHTML5Audio : s._a,
+          dURL = decodeURI(instanceOptions.url),
+          sameURL;
+
+      /**
+       * "First things first, I, Poppa..." (reset the previous state of the old sound, if playing)
+       * Fixes case with devices that can only play one sound at a time
+       * Otherwise, other sounds in mid-play will be terminated without warning and in a stuck state
+       */
+
+      if (useGlobalHTML5Audio) {
+
+        if (dURL === decodeURI(lastGlobalHTML5URL)) {
+          // global HTML5 audio: re-use of URL
+          sameURL = true;
+        }
+
+      } else if (dURL === decodeURI(lastURL)) {
+
+        // options URL is the same as the "last" URL, and we used (loaded) it
+        sameURL = true;
+
+      }
+
+      if (a) {
+
+        if (a._s) {
+
+          if (useGlobalHTML5Audio) {
+
+            if (a._s && a._s.playState && !sameURL) {
+
+              // global HTML5 audio case, and loading a new URL. stop the currently-playing one.
+              a._s.stop();
+
+            }
+
+          } else if (!useGlobalHTML5Audio && dURL === decodeURI(lastURL)) {
+
+            // non-global HTML5 reuse case: same url, ignore request
+            s._apply_loop(a, instanceOptions.loops);
+
+            return a;
+
+          }
+
+        }
+
+        if (!sameURL) {
+
+          // don't retain onPosition() stuff with new URLs.
+
+          if (lastURL) {
+            resetProperties(false);
+          }
+
+          // assign new HTML5 URL
+
+          a.src = instanceOptions.url;
+
+          s.url = instanceOptions.url;
+
+          lastURL = instanceOptions.url;
+
+          lastGlobalHTML5URL = instanceOptions.url;
+
+          a._called_load = false;
+
+        }
+
+      } else {
+
+        if (instanceOptions.autoLoad || instanceOptions.autoPlay) {
+
+          s._a = new Audio(instanceOptions.url);
+          s._a.load();
+
+        } else {
+
+          // null for stupid Opera 9.64 case
+          s._a = (isOpera && opera.version() < 10 ? new Audio(null) : new Audio());
+
+        }
+
+        // assign local reference
+        a = s._a;
+
+        a._called_load = false;
+
+        if (useGlobalHTML5Audio) {
+
+          globalHTML5Audio = a;
+
+        }
+
+      }
+
+      s.isHTML5 = true;
+
+      // store a ref on the track
+      s._a = a;
+
+      // store a ref on the audio
+      a._s = s;
+
+      add_html5_events();
+
+      s._apply_loop(a, instanceOptions.loops);
+
+      if (instanceOptions.autoLoad || instanceOptions.autoPlay) {
+
+        s.load();
+
+      } else {
+
+        // early HTML5 implementation (non-standard)
+        a.autobuffer = false;
+
+        // standard ('none' is also an option.)
+        a.preload = 'auto';
+
+      }
+
+      return a;
+
+    };
+
+    add_html5_events = function() {
+
+      if (s._a._added_events) {
+        return false;
+      }
+
+      var f;
+
+      function add(oEvt, oFn, bCapture) {
+        return s._a ? s._a.addEventListener(oEvt, oFn, bCapture||false) : null;
+      }
+
+      s._a._added_events = true;
+
+      for (f in html5_events) {
+        if (html5_events.hasOwnProperty(f)) {
+          add(f, html5_events[f]);
+        }
+      }
+
+      return true;
+
+    };
+
+    remove_html5_events = function() {
+
+      // Remove event listeners
+
+      var f;
+
+      function remove(oEvt, oFn, bCapture) {
+        return (s._a ? s._a.removeEventListener(oEvt, oFn, bCapture||false) : null);
+      }
+
+      sm2._wD(s.id + ': Removing event listeners');
+      s._a._added_events = false;
+
+      for (f in html5_events) {
+        if (html5_events.hasOwnProperty(f)) {
+          remove(f, html5_events[f]);
+        }
+      }
+
+    };
+
+    /**
+     * Pseudo-private event internals
+     * ------------------------------
+     */
+
+    this._onload = function(nSuccess) {
+
+      var fN,
+          // check for duration to prevent false positives from flash 8 when loading from cache.
+          loadOK = !!nSuccess || (!s.isHTML5 && fV === 8 && s.duration);
+
+      // <d>
+      fN = s.id + ': ';
+      sm2._wD(fN + (loadOK ? 'onload()' : 'Failed to load / invalid sound?' + (!s.duration ? ' Zero-length duration reported.' : ' -') + ' (' + s.url + ')'), (loadOK ? 1 : 2));
+      if (!loadOK && !s.isHTML5) {
+        if (sm2.sandbox.noRemote === true) {
+          sm2._wD(fN + str('noNet'), 1);
+        }
+        if (sm2.sandbox.noLocal === true) {
+          sm2._wD(fN + str('noLocal'), 1);
+        }
+      }
+      // </d>
+
+      s.loaded = loadOK;
+      s.readyState = loadOK?3:2;
+      s._onbufferchange(0);
+
+      if (s._iO.onload) {
+        wrapCallback(s, function() {
+          s._iO.onload.apply(s, [loadOK]);
+        });
+      }
+
+      return true;
+
+    };
+
+    this._onbufferchange = function(nIsBuffering) {
+
+      if (s.playState === 0) {
+        // ignore if not playing
+        return false;
+      }
+
+      if ((nIsBuffering && s.isBuffering) || (!nIsBuffering && !s.isBuffering)) {
+        return false;
+      }
+
+      s.isBuffering = (nIsBuffering === 1);
+      if (s._iO.onbufferchange) {
+        sm2._wD(s.id + ': Buffer state change: ' + nIsBuffering);
+        s._iO.onbufferchange.apply(s);
+      }
+
+      return true;
+
+    };
+
+    /**
+     * Playback may have stopped due to buffering, or related reason.
+     * This state can be encountered on iOS < 6 when auto-play is blocked.
+     */
+
+    this._onsuspend = function() {
+
+      if (s._iO.onsuspend) {
+        sm2._wD(s.id + ': Playback suspended');
+        s._iO.onsuspend.apply(s);
+      }
+
+      return true;
+
+    };
+
+    /**
+     * flash 9/movieStar + RTMP-only method, should fire only once at most
+     * at this point we just recreate failed sounds rather than trying to reconnect
+     */
+
+    this._onfailure = function(msg, level, code) {
+
+      s.failures++;
+      sm2._wD(s.id + ': Failures = ' + s.failures);
+
+      if (s._iO.onfailure && s.failures === 1) {
+        s._iO.onfailure(s, msg, level, code);
+      } else {
+        sm2._wD(s.id + ': Ignoring failure');
+      }
+
+    };
+
+    this._onfinish = function() {
+
+      // store local copy before it gets trashed...
+      var io_onfinish = s._iO.onfinish;
+
+      s._onbufferchange(0);
+      s._resetOnPosition(0);
+
+      // reset some state items
+      if (s.instanceCount) {
+
+        s.instanceCount--;
+
+        if (!s.instanceCount) {
+
+          // remove onPosition listeners, if any
+          detachOnPosition();
+
+          // reset instance options
+          s.playState = 0;
+          s.paused = false;
+          s.instanceCount = 0;
+          s.instanceOptions = {};
+          s._iO = {};
+          stop_html5_timer();
+
+          // reset position, too
+          if (s.isHTML5) {
+            s.position = 0;
+          }
+
+        }
+
+        if (!s.instanceCount || s._iO.multiShotEvents) {
+          // fire onfinish for last, or every instance
+          if (io_onfinish) {
+            sm2._wD(s.id + ': onfinish()');
+            wrapCallback(s, function() {
+              io_onfinish.apply(s);
+            });
+          }
+        }
+
+      }
+
+    };
+
+    this._whileloading = function(nBytesLoaded, nBytesTotal, nDuration, nBufferLength) {
+
+      var instanceOptions = s._iO;
+
+      s.bytesLoaded = nBytesLoaded;
+      s.bytesTotal = nBytesTotal;
+      s.duration = Math.floor(nDuration);
+      s.bufferLength = nBufferLength;
+
+      if (!s.isHTML5 && !instanceOptions.isMovieStar) {
+
+        if (instanceOptions.duration) {
+          // use duration from options, if specified and larger. nobody should be specifying duration in options, actually, and it should be retired.
+          s.durationEstimate = (s.duration > instanceOptions.duration) ? s.duration : instanceOptions.duration;
+        } else {
+          s.durationEstimate = parseInt((s.bytesTotal / s.bytesLoaded) * s.duration, 10);
+        }
+
+      } else {
+
+        s.durationEstimate = s.duration;
+
+      }
+
+      // for flash, reflect sequential-load-style buffering
+      if (!s.isHTML5) {
+        s.buffered = [{
+          'start': 0,
+          'end': s.duration
+        }];
+      }
+
+      // allow whileloading to fire even if "load" fired under HTML5, due to HTTP range/partials
+      if ((s.readyState !== 3 || s.isHTML5) && instanceOptions.whileloading) {
+        instanceOptions.whileloading.apply(s);
+      }
+
+    };
+
+    this._whileplaying = function(nPosition, oPeakData, oWaveformDataLeft, oWaveformDataRight, oEQData) {
+
+      var instanceOptions = s._iO,
+          eqLeft;
+
+      if (isNaN(nPosition) || nPosition === null) {
+        // flash safety net
+        return false;
+      }
+
+      // Safari HTML5 play() may return small -ve values when starting from position: 0, eg. -50.120396875. Unexpected/invalid per W3, I think. Normalize to 0.
+      s.position = Math.max(0, nPosition);
+
+      s._processOnPosition();
+
+      if (!s.isHTML5 && fV > 8) {
+
+        if (instanceOptions.usePeakData && oPeakData !== _undefined && oPeakData) {
+          s.peakData = {
+            left: oPeakData.leftPeak,
+            right: oPeakData.rightPeak
+          };
+        }
+
+        if (instanceOptions.useWaveformData && oWaveformDataLeft !== _undefined && oWaveformDataLeft) {
+          s.waveformData = {
+            left: oWaveformDataLeft.split(','),
+            right: oWaveformDataRight.split(',')
+          };
+        }
+
+        if (instanceOptions.useEQData) {
+          if (oEQData !== _undefined && oEQData && oEQData.leftEQ) {
+            eqLeft = oEQData.leftEQ.split(',');
+            s.eqData = eqLeft;
+            s.eqData.left = eqLeft;
+            if (oEQData.rightEQ !== _undefined && oEQData.rightEQ) {
+              s.eqData.right = oEQData.rightEQ.split(',');
+            }
+          }
+        }
+
+      }
+
+      if (s.playState === 1) {
+
+        // special case/hack: ensure buffering is false if loading from cache (and not yet started)
+        if (!s.isHTML5 && fV === 8 && !s.position && s.isBuffering) {
+          s._onbufferchange(0);
+        }
+
+        if (instanceOptions.whileplaying) {
+          // flash may call after actual finish
+          instanceOptions.whileplaying.apply(s);
+        }
+
+      }
+
+      return true;
+
+    };
+
+    this._oncaptiondata = function(oData) {
+
+      /**
+       * internal: flash 9 + NetStream (MovieStar/RTMP-only) feature
+       *
+       * @param {object} oData
+       */
+
+      sm2._wD(s.id + ': Caption data received.');
+
+      s.captiondata = oData;
+
+      if (s._iO.oncaptiondata) {
+        s._iO.oncaptiondata.apply(s, [oData]);
+      }
+
+    };
+
+    this._onmetadata = function(oMDProps, oMDData) {
+
+      /**
+       * internal: flash 9 + NetStream (MovieStar/RTMP-only) feature
+       * RTMP may include song title, MovieStar content may include encoding info
+       *
+       * @param {array} oMDProps (names)
+       * @param {array} oMDData (values)
+       */
+
+      sm2._wD(s.id + ': Metadata received.');
+
+      var oData = {}, i, j;
+
+      for (i = 0, j = oMDProps.length; i < j; i++) {
+        oData[oMDProps[i]] = oMDData[i];
+      }
+      s.metadata = oData;
+
+      if (s._iO.onmetadata) {
+        s._iO.onmetadata.apply(s);
+      }
+
+    };
+
+    this._onid3 = function(oID3Props, oID3Data) {
+
+      /**
+       * internal: flash 8 + flash 9 ID3 feature
+       * may include artist, song title etc.
+       *
+       * @param {array} oID3Props (names)
+       * @param {array} oID3Data (values)
+       */
+
+      sm2._wD(s.id + ': ID3 data received.');
+
+      var oData = [], i, j;
+
+      for (i = 0, j = oID3Props.length; i < j; i++) {
+        oData[oID3Props[i]] = oID3Data[i];
+      }
+      s.id3 = mixin(s.id3, oData);
+
+      if (s._iO.onid3) {
+        s._iO.onid3.apply(s);
+      }
+
+    };
+
+    // flash/RTMP-only
+
+    this._onconnect = function(bSuccess) {
+
+      bSuccess = (bSuccess === 1);
+      sm2._wD(s.id + ': ' + (bSuccess ? 'Connected.' : 'Failed to connect? - ' + s.url), (bSuccess ? 1 : 2));
+      s.connected = bSuccess;
+
+      if (bSuccess) {
+
+        s.failures = 0;
+
+        if (idCheck(s.id)) {
+          if (s.getAutoPlay()) {
+            // only update the play state if auto playing
+            s.play(_undefined, s.getAutoPlay());
+          } else if (s._iO.autoLoad) {
+            s.load();
+          }
+        }
+
+        if (s._iO.onconnect) {
+          s._iO.onconnect.apply(s, [bSuccess]);
+        }
+
+      }
+
+    };
+
+    this._ondataerror = function(sError) {
+
+      // flash 9 wave/eq data handler
+      // hack: called at start, and end from flash at/after onfinish()
+      if (s.playState > 0) {
+        sm2._wD(s.id + ': Data error: ' + sError);
+        if (s._iO.ondataerror) {
+          s._iO.ondataerror.apply(s);
+        }
+      }
+
+    };
+
+    // <d>
+    this._debug();
+    // </d>
+
+  }; // SMSound()
+
+  /**
+   * Private SoundManager internals
+   * ------------------------------
+   */
+
+  getDocument = function() {
+
+    return (doc.body || doc.getElementsByTagName('div')[0]);
+
+  };
+
+  id = function(sID) {
+
+    return doc.getElementById(sID);
+
+  };
+
+  mixin = function(oMain, oAdd) {
+
+    // non-destructive merge
+    var o1 = (oMain || {}), o2, o;
+
+    // if unspecified, o2 is the default options object
+    o2 = (oAdd === _undefined ? sm2.defaultOptions : oAdd);
+
+    for (o in o2) {
+
+      if (o2.hasOwnProperty(o) && o1[o] === _undefined) {
+
+        if (typeof o2[o] !== 'object' || o2[o] === null) {
+
+          // assign directly
+          o1[o] = o2[o];
+
+        } else {
+
+          // recurse through o2
+          o1[o] = mixin(o1[o], o2[o]);
+
+        }
+
+      }
+
+    }
+
+    return o1;
+
+  };
+
+  wrapCallback = function(oSound, callback) {
+
+    /**
+     * 03/03/2013: Fix for Flash Player 11.6.602.171 + Flash 8 (flashVersion = 8) SWF issue
+     * setTimeout() fix for certain SMSound callbacks like onload() and onfinish(), where subsequent calls like play() and load() fail when Flash Player 11.6.602.171 is installed, and using soundManager with flashVersion = 8 (which is the default).
+     * Not sure of exact cause. Suspect race condition and/or invalid (NaN-style) position argument trickling down to the next JS -> Flash _start() call, in the play() case.
+     * Fix: setTimeout() to yield, plus safer null / NaN checking on position argument provided to Flash.
+     * https://getsatisfaction.com/schillmania/topics/recent_chrome_update_seems_to_have_broken_my_sm2_audio_player
+     */
+    if (!oSound.isHTML5 && fV === 8) {
+      window.setTimeout(callback, 0);
+    } else {
+      callback();
+    }
+
+  };
+
+  // additional soundManager properties that soundManager.setup() will accept
+
+  extraOptions = {
+    'onready': 1,
+    'ontimeout': 1,
+    'defaultOptions': 1,
+    'flash9Options': 1,
+    'movieStarOptions': 1
+  };
+
+  assign = function(o, oParent) {
+
+    /**
+     * recursive assignment of properties, soundManager.setup() helper
+     * allows property assignment based on whitelist
+     */
+
+    var i,
+        result = true,
+        hasParent = (oParent !== _undefined),
+        setupOptions = sm2.setupOptions,
+        bonusOptions = extraOptions;
+
+    // <d>
+
+    // if soundManager.setup() called, show accepted parameters.
+
+    if (o === _undefined) {
+
+      result = [];
+
+      for (i in setupOptions) {
+
+        if (setupOptions.hasOwnProperty(i)) {
+          result.push(i);
+        }
+
+      }
+
+      for (i in bonusOptions) {
+
+        if (bonusOptions.hasOwnProperty(i)) {
+
+          if (typeof sm2[i] === 'object') {
+
+            result.push(i+': {...}');
+
+          } else if (sm2[i] instanceof Function) {
+
+            result.push(i+': function() {...}');
+
+          } else {
+
+            result.push(i);
+
+          }
+
+        }
+
+      }
+
+      sm2._wD(str('setup', result.join(', ')));
+
+      return false;
+
+    }
+
+    // </d>
+
+    for (i in o) {
+
+      if (o.hasOwnProperty(i)) {
+
+        // if not an {object} we want to recurse through...
+
+        if (typeof o[i] !== 'object' || o[i] === null || o[i] instanceof Array || o[i] instanceof RegExp) {
+
+          // check "allowed" options
+
+          if (hasParent && bonusOptions[oParent] !== _undefined) {
+
+            // valid recursive / nested object option, eg., { defaultOptions: { volume: 50 } }
+            sm2[oParent][i] = o[i];
+
+          } else if (setupOptions[i] !== _undefined) {
+
+            // special case: assign to setupOptions object, which soundManager property references
+            sm2.setupOptions[i] = o[i];
+
+            // assign directly to soundManager, too
+            sm2[i] = o[i];
+
+          } else if (bonusOptions[i] === _undefined) {
+
+            // invalid or disallowed parameter. complain.
+            complain(str((sm2[i] === _undefined ? 'setupUndef' : 'setupError'), i), 2);
+
+            result = false;
+
+          } else {
+
+            /**
+             * valid extraOptions (bonusOptions) parameter.
+             * is it a method, like onready/ontimeout? call it.
+             * multiple parameters should be in an array, eg. soundManager.setup({onready: [myHandler, myScope]});
+             */
+
+            if (sm2[i] instanceof Function) {
+
+              sm2[i].apply(sm2, (o[i] instanceof Array? o[i] : [o[i]]));
+
+            } else {
+
+              // good old-fashioned direct assignment
+              sm2[i] = o[i];
+
+            }
+
+          }
+
+        } else {
+
+          // recursion case, eg., { defaultOptions: { ... } }
+
+          if (bonusOptions[i] === _undefined) {
+
+            // invalid or disallowed parameter. complain.
+            complain(str((sm2[i] === _undefined ? 'setupUndef' : 'setupError'), i), 2);
+
+            result = false;
+
+          } else {
+
+            // recurse through object
+            return assign(o[i], i);
+
+          }
+
+        }
+
+      }
+
+    }
+
+    return result;
+
+  };
+
+  function preferFlashCheck(kind) {
+
+    // whether flash should play a given type
+    return (sm2.preferFlash && hasFlash && !sm2.ignoreFlash && (sm2.flash[kind] !== _undefined && sm2.flash[kind]));
+
+  }
+
+  /**
+   * Internal DOM2-level event helpers
+   * ---------------------------------
+   */
+
+  event = (function() {
+
+    // normalize event methods
+    var old = (window.attachEvent),
+    evt = {
+      add: (old?'attachEvent':'addEventListener'),
+      remove: (old?'detachEvent':'removeEventListener')
+    };
+
+    // normalize "on" event prefix, optional capture argument
+    function getArgs(oArgs) {
+
+      var args = slice.call(oArgs),
+          len = args.length;
+
+      if (old) {
+        // prefix
+        args[1] = 'on' + args[1];
+        if (len > 3) {
+          // no capture
+          args.pop();
+        }
+      } else if (len === 3) {
+        args.push(false);
+      }
+
+      return args;
+
+    }
+
+    function apply(args, sType) {
+
+      // normalize and call the event method, with the proper arguments
+      var element = args.shift(),
+          method = [evt[sType]];
+
+      if (old) {
+        // old IE can't do apply().
+        element[method](args[0], args[1]);
+      } else {
+        element[method].apply(element, args);
+      }
+
+    }
+
+    function add() {
+
+      apply(getArgs(arguments), 'add');
+
+    }
+
+    function remove() {
+
+      apply(getArgs(arguments), 'remove');
+
+    }
+
+    return {
+      'add': add,
+      'remove': remove
+    };
+
+  }());
+
+  /**
+   * Internal HTML5 event handling
+   * -----------------------------
+   */
+
+  function html5_event(oFn) {
+
+    // wrap html5 event handlers so we don't call them on destroyed and/or unloaded sounds
+
+    return function(e) {
+
+      var s = this._s,
+          result;
+
+      if (!s || !s._a) {
+        // <d>
+        if (s && s.id) {
+          sm2._wD(s.id + ': Ignoring ' + e.type);
+        } else {
+          sm2._wD(h5 + 'Ignoring ' + e.type);
+        }
+        // </d>
+        result = null;
+      } else {
+        result = oFn.call(this, e);
+      }
+
+      return result;
+
+    };
+
+  }
+
+  html5_events = {
+
+    // HTML5 event-name-to-handler map
+
+    abort: html5_event(function() {
+
+      sm2._wD(this._s.id + ': abort');
+
+    }),
+
+    // enough has loaded to play
+
+    canplay: html5_event(function() {
+
+      var s = this._s,
+          position1K;
+
+      if (s._html5_canplay) {
+        // this event has already fired. ignore.
+        return true;
+      }
+
+      s._html5_canplay = true;
+      sm2._wD(s.id + ': canplay');
+      s._onbufferchange(0);
+
+      // position according to instance options
+      position1K = (s._iO.position !== _undefined && !isNaN(s._iO.position)?s._iO.position/msecScale:null);
+
+      // set the position if position was set before the sound loaded
+      if (s.position && this.currentTime !== position1K) {
+        sm2._wD(s.id + ': canplay: Setting position to ' + position1K);
+        try {
+          this.currentTime = position1K;
+        } catch(ee) {
+          sm2._wD(s.id + ': canplay: Setting position of ' + position1K + ' failed: ' + ee.message, 2);
+        }
+      }
+
+      // hack for HTML5 from/to case
+      if (s._iO._oncanplay) {
+        s._iO._oncanplay();
+      }
+
+    }),
+
+    canplaythrough: html5_event(function() {
+
+      var s = this._s;
+
+      if (!s.loaded) {
+        s._onbufferchange(0);
+        s._whileloading(s.bytesLoaded, s.bytesTotal, s._get_html5_duration());
+        s._onload(true);
+      }
+
+    }),
+
+    // TODO: Reserved for potential use
+    /*
+    emptied: html5_event(function() {
+
+      sm2._wD(this._s.id + ': emptied');
+
+    }),
+    */
+
+    ended: html5_event(function() {
+
+      var s = this._s;
+
+      sm2._wD(s.id + ': ended');
+
+      s._onfinish();
+
+    }),
+
+    error: html5_event(function() {
+
+      sm2._wD(this._s.id + ': HTML5 error, code ' + this.error.code);
+      /**
+       * HTML5 error codes, per W3C
+       * Error 1: Client aborted download at user's request.
+       * Error 2: Network error after load started.
+       * Error 3: Decoding issue.
+       * Error 4: Media (audio file) not supported.
+       * Reference: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#error-codes
+       */
+      // call load with error state?
+      this._s._onload(false);
+
+    }),
+
+    loadeddata: html5_event(function() {
+
+      var s = this._s;
+
+      sm2._wD(s.id + ': loadeddata');
+
+      // safari seems to nicely report progress events, eventually totalling 100%
+      if (!s._loaded && !isSafari) {
+        s.duration = s._get_html5_duration();
+      }
+
+    }),
+
+    loadedmetadata: html5_event(function() {
+
+      sm2._wD(this._s.id + ': loadedmetadata');
+
+    }),
+
+    loadstart: html5_event(function() {
+
+      sm2._wD(this._s.id + ': loadstart');
+      // assume buffering at first
+      this._s._onbufferchange(1);
+
+    }),
+
+    play: html5_event(function() {
+
+      // sm2._wD(this._s.id + ': play()');
+      // once play starts, no buffering
+      this._s._onbufferchange(0);
+
+    }),
+
+    playing: html5_event(function() {
+
+      sm2._wD(this._s.id + ': playing');
+      // once play starts, no buffering
+      this._s._onbufferchange(0);
+
+    }),
+
+    progress: html5_event(function(e) {
+
+      // note: can fire repeatedly after "loaded" event, due to use of HTTP range/partials
+
+      var s = this._s,
+          i, j, progStr, buffered = 0,
+          isProgress = (e.type === 'progress'),
+          ranges = e.target.buffered,
+          // firefox 3.6 implements e.loaded/total (bytes)
+          loaded = (e.loaded||0),
+          total = (e.total||1);
+
+      // reset the "buffered" (loaded byte ranges) array
+      s.buffered = [];
+
+      if (ranges && ranges.length) {
+
+        // if loaded is 0, try TimeRanges implementation as % of load
+        // https://developer.mozilla.org/en/DOM/TimeRanges
+
+        // re-build "buffered" array
+        // HTML5 returns seconds. SM2 API uses msec for setPosition() etc., whether Flash or HTML5.
+        for (i=0, j=ranges.length; i<j; i++) {
+          s.buffered.push({
+            'start': ranges.start(i) * msecScale,
+            'end': ranges.end(i) * msecScale
+          });
+        }
+
+        // use the last value locally
+        buffered = (ranges.end(0) - ranges.start(0)) * msecScale;
+
+        // linear case, buffer sum; does not account for seeking and HTTP partials / byte ranges
+        loaded = Math.min(1, buffered/(e.target.duration*msecScale));
+
+        // <d>
+        if (isProgress && ranges.length > 1) {
+          progStr = [];
+          j = ranges.length;
+          for (i=0; i<j; i++) {
+            progStr.push(e.target.buffered.start(i)*msecScale +'-'+ e.target.buffered.end(i)*msecScale);
+          }
+          sm2._wD(this._s.id + ': progress, timeRanges: ' + progStr.join(', '));
+        }
+
+        if (isProgress && !isNaN(loaded)) {
+          sm2._wD(this._s.id + ': progress, ' + Math.floor(loaded*100) + '% loaded');
+        }
+        // </d>
+
+      }
+
+      if (!isNaN(loaded)) {
+
+        // if progress, likely not buffering
+        s._onbufferchange(0);
+        // TODO: prevent calls with duplicate values.
+        s._whileloading(loaded, total, s._get_html5_duration());
+        if (loaded && total && loaded === total) {
+          // in case "onload" doesn't fire (eg. gecko 1.9.2)
+          html5_events.canplaythrough.call(this, e);
+        }
+
+      }
+
+    }),
+
+    ratechange: html5_event(function() {
+
+      sm2._wD(this._s.id + ': ratechange');
+
+    }),
+
+    suspend: html5_event(function(e) {
+
+      // download paused/stopped, may have finished (eg. onload)
+      var s = this._s;
+
+      sm2._wD(this._s.id + ': suspend');
+      html5_events.progress.call(this, e);
+      s._onsuspend();
+
+    }),
+
+    stalled: html5_event(function() {
+
+      sm2._wD(this._s.id + ': stalled');
+
+    }),
+
+    timeupdate: html5_event(function() {
+
+      this._s._onTimer();
+
+    }),
+
+    waiting: html5_event(function() {
+
+      var s = this._s;
+
+      // see also: seeking
+      sm2._wD(this._s.id + ': waiting');
+
+      // playback faster than download rate, etc.
+      s._onbufferchange(1);
+
+    })
+
+  };
+
+  html5OK = function(iO) {
+
+    // playability test based on URL or MIME type
+
+    var result;
+
+    if (!iO || (!iO.type && !iO.url && !iO.serverURL)) {
+
+      // nothing to check
+      result = false;
+
+    } else if (iO.serverURL || (iO.type && preferFlashCheck(iO.type))) {
+
+      // RTMP, or preferring flash
+      result = false;
+
+    } else {
+
+      // Use type, if specified. Pass data: URIs to HTML5. If HTML5-only mode, no other options, so just give 'er
+      result = ((iO.type ? html5CanPlay({type:iO.type}) : html5CanPlay({url:iO.url}) || sm2.html5Only || iO.url.match(/data\:/i)));
+
+    }
+
+    return result;
+
+  };
+
+  html5Unload = function(oAudio) {
+
+    /**
+     * Internal method: Unload media, and cancel any current/pending network requests.
+     * Firefox can load an empty URL, which allegedly destroys the decoder and stops the download.
+     * https://developer.mozilla.org/En/Using_audio_and_video_in_Firefox#Stopping_the_download_of_media
+     * However, Firefox has been seen loading a relative URL from '' and thus requesting the hosting page on unload.
+     * Other UA behaviour is unclear, so everyone else gets an about:blank-style URL.
+     */
+
+    var url;
+
+    if (oAudio) {
+
+      // Firefox and Chrome accept short WAVe data: URIs. Chome dislikes audio/wav, but accepts audio/wav for data: MIME.
+      // Desktop Safari complains / fails on data: URI, so it gets about:blank.
+      url = (isSafari ? emptyURL : (sm2.html5.canPlayType('audio/wav') ? emptyWAV : emptyURL));
+
+      oAudio.src = url;
+
+      // reset some state, too
+      if (oAudio._called_unload !== undefined) {
+        oAudio._called_load = false;
+      }
+
+    }
+
+    if (useGlobalHTML5Audio) {
+
+      // ensure URL state is trashed, also
+      lastGlobalHTML5URL = null;
+
+    }
+
+    return url;
+
+  };
+
+  html5CanPlay = function(o) {
+
+    /**
+     * Try to find MIME, test and return truthiness
+     * o = {
+     *  url: '/path/to/an.mp3',
+     *  type: 'audio/mp3'
+     * }
+     */
+
+    if (!sm2.useHTML5Audio || !sm2.hasHTML5) {
+      return false;
+    }
+
+    var url = (o.url || null),
+        mime = (o.type || null),
+        aF = sm2.audioFormats,
+        result,
+        offset,
+        fileExt,
+        item;
+
+    // account for known cases like audio/mp3
+
+    if (mime && sm2.html5[mime] !== _undefined) {
+      return (sm2.html5[mime] && !preferFlashCheck(mime));
+    }
+
+    if (!html5Ext) {
+      html5Ext = [];
+      for (item in aF) {
+        if (aF.hasOwnProperty(item)) {
+          html5Ext.push(item);
+          if (aF[item].related) {
+            html5Ext = html5Ext.concat(aF[item].related);
+          }
+        }
+      }
+      html5Ext = new RegExp('\\.('+html5Ext.join('|')+')(\\?.*)?$','i');
+    }
+
+    // TODO: Strip URL queries, etc.
+    fileExt = (url ? url.toLowerCase().match(html5Ext) : null);
+
+    if (!fileExt || !fileExt.length) {
+      if (!mime) {
+        result = false;
+      } else {
+        // audio/mp3 -> mp3, result should be known
+        offset = mime.indexOf(';');
+        // strip "audio/X; codecs..."
+        fileExt = (offset !== -1?mime.substr(0,offset):mime).substr(6);
+      }
+    } else {
+      // match the raw extension name - "mp3", for example
+      fileExt = fileExt[1];
+    }
+
+    if (fileExt && sm2.html5[fileExt] !== _undefined) {
+      // result known
+      result = (sm2.html5[fileExt] && !preferFlashCheck(fileExt));
+    } else {
+      mime = 'audio/'+fileExt;
+      result = sm2.html5.canPlayType({type:mime});
+      sm2.html5[fileExt] = result;
+      // sm2._wD('canPlayType, found result: ' + result);
+      result = (result && sm2.html5[mime] && !preferFlashCheck(mime));
+    }
+
+    return result;
+
+  };
+
+  testHTML5 = function() {
+
+    /**
+     * Internal: Iterates over audioFormats, determining support eg. audio/mp3, audio/mpeg and so on
+     * assigns results to html5[] and flash[].
+     */
+
+    if (!sm2.useHTML5Audio || !sm2.hasHTML5) {
+      // without HTML5, we need Flash.
+      sm2.html5.usingFlash = true;
+      needsFlash = true;
+      return false;
+    }
+
+    // double-whammy: Opera 9.64 throws WRONG_ARGUMENTS_ERR if no parameter passed to Audio(), and Webkit + iOS happily tries to load "null" as a URL. :/
+    var a = (Audio !== _undefined ? (isOpera && opera.version() < 10 ? new Audio(null) : new Audio()) : null),
+        item, lookup, support = {}, aF, i;
+
+    function cp(m) {
+
+      var canPlay, j,
+          result = false,
+          isOK = false;
+
+      if (!a || typeof a.canPlayType !== 'function') {
+        return result;
+      }
+
+      if (m instanceof Array) {
+        // iterate through all mime types, return any successes
+        for (i=0, j=m.length; i<j; i++) {
+          if (sm2.html5[m[i]] || a.canPlayType(m[i]).match(sm2.html5Test)) {
+            isOK = true;
+            sm2.html5[m[i]] = true;
+            // note flash support, too
+            sm2.flash[m[i]] = !!(m[i].match(flashMIME));
+          }
+        }
+        result = isOK;
+      } else {
+        canPlay = (a && typeof a.canPlayType === 'function' ? a.canPlayType(m) : false);
+        result = !!(canPlay && (canPlay.match(sm2.html5Test)));
+      }
+
+      return result;
+
+    }
+
+    // test all registered formats + codecs
+
+    aF = sm2.audioFormats;
+
+    for (item in aF) {
+
+      if (aF.hasOwnProperty(item)) {
+
+        lookup = 'audio/' + item;
+
+        support[item] = cp(aF[item].type);
+
+        // write back generic type too, eg. audio/mp3
+        support[lookup] = support[item];
+
+        // assign flash
+        if (item.match(flashMIME)) {
+
+          sm2.flash[item] = true;
+          sm2.flash[lookup] = true;
+
+        } else {
+
+          sm2.flash[item] = false;
+          sm2.flash[lookup] = false;
+
+        }
+
+        // assign result to related formats, too
+
+        if (aF[item] && aF[item].related) {
+
+          for (i=aF[item].related.length-1; i >= 0; i--) {
+
+            // eg. audio/m4a
+            support['audio/'+aF[item].related[i]] = support[item];
+            sm2.html5[aF[item].related[i]] = support[item];
+            sm2.flash[aF[item].related[i]] = support[item];
+
+          }
+
+        }
+
+      }
+
+    }
+
+    support.canPlayType = (a?cp:null);
+    sm2.html5 = mixin(sm2.html5, support);
+
+    sm2.html5.usingFlash = featureCheck();
+    needsFlash = sm2.html5.usingFlash;
+
+    return true;
+
+  };
+
+  strings = {
+
+    // <d>
+    notReady: 'Unavailable - wait until onready() has fired.',
+    notOK: 'Audio support is not available.',
+    domError: sm + 'exception caught while appending SWF to DOM.',
+    spcWmode: 'Removing wmode, preventing known SWF loading issue(s)',
+    swf404: smc + 'Verify that %s is a valid path.',
+    tryDebug: 'Try ' + sm + '.debugFlash = true for more security details (output goes to SWF.)',
+    checkSWF: 'See SWF output for more debug info.',
+    localFail: smc + 'Non-HTTP page (' + doc.location.protocol + ' URL?) Review Flash player security settings for this special case:\nhttp://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html\nMay need to add/allow path, eg. c:/sm2/ or /users/me/sm2/',
+    waitFocus: smc + 'Special case: Waiting for SWF to load with window focus...',
+    waitForever: smc + 'Waiting indefinitely for Flash (will recover if unblocked)...',
+    waitSWF: smc + 'Waiting for 100% SWF load...',
+    needFunction: smc + 'Function object expected for %s',
+    badID: 'Sound ID "%s" should be a string, starting with a non-numeric character',
+    currentObj: smc + '_debug(): Current sound objects',
+    waitOnload: smc + 'Waiting for window.onload()',
+    docLoaded: smc + 'Document already loaded',
+    onload: smc + 'initComplete(): calling soundManager.onload()',
+    onloadOK: sm + '.onload() complete',
+    didInit: smc + 'init(): Already called?',
+    secNote: 'Flash security note: Network/internet URLs will not load due to security restrictions. Access can be configured via Flash Player Global Security Settings Page: http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html',
+    badRemove: smc + 'Failed to remove Flash node.',
+    shutdown: sm + '.disable(): Shutting down',
+    queue: smc + 'Queueing %s handler',
+    smError: 'SMSound.load(): Exception: JS-Flash communication failed, or JS error.',
+    fbTimeout: 'No flash response, applying .'+swfCSS.swfTimedout+' CSS...',
+    fbLoaded: 'Flash loaded',
+    fbHandler: smc + 'flashBlockHandler()',
+    manURL: 'SMSound.load(): Using manually-assigned URL',
+    onURL: sm + '.load(): current URL already assigned.',
+    badFV: sm + '.flashVersion must be 8 or 9. "%s" is invalid. Reverting to %s.',
+    as2loop: 'Note: Setting stream:false so looping can work (flash 8 limitation)',
+    noNSLoop: 'Note: Looping not implemented for MovieStar formats',
+    needfl9: 'Note: Switching to flash 9, required for MP4 formats.',
+    mfTimeout: 'Setting flashLoadTimeout = 0 (infinite) for off-screen, mobile flash case',
+    needFlash: smc + 'Fatal error: Flash is needed to play some required formats, but is not available.',
+    gotFocus: smc + 'Got window focus.',
+    policy: 'Enabling usePolicyFile for data access',
+    setup: sm + '.setup(): allowed parameters: %s',
+    setupError: sm + '.setup(): "%s" cannot be assigned with this method.',
+    setupUndef: sm + '.setup(): Could not find option "%s"',
+    setupLate: sm + '.setup(): url, flashVersion and html5Test property changes will not take effect until reboot().',
+    noURL: smc + 'Flash URL required. Call soundManager.setup({url:...}) to get started.',
+    sm2Loaded: 'SoundManager 2: Ready.',
+    reset: sm + '.reset(): Removing event callbacks',
+    mobileUA: 'Mobile UA detected, preferring HTML5 by default.',
+    globalHTML5: 'Using singleton HTML5 Audio() pattern for this device.'
+    // </d>
+
+  };
+
+  str = function() {
+
+    // internal string replace helper.
+    // arguments: o [,items to replace]
+    // <d>
+
+    var args,
+        i, j, o,
+        sstr;
+
+    // real array, please
+    args = slice.call(arguments);
+
+    // first argument
+    o = args.shift();
+
+    sstr = (strings && strings[o] ? strings[o] : '');
+
+    if (sstr && args && args.length) {
+      for (i = 0, j = args.length; i < j; i++) {
+        sstr = sstr.replace('%s', args[i]);
+      }
+    }
+
+    return sstr;
+    // </d>
+
+  };
+
+  loopFix = function(sOpt) {
+
+    // flash 8 requires stream = false for looping to work
+    if (fV === 8 && sOpt.loops > 1 && sOpt.stream) {
+      _wDS('as2loop');
+      sOpt.stream = false;
+    }
+
+    return sOpt;
+
+  };
+
+  policyFix = function(sOpt, sPre) {
+
+    if (sOpt && !sOpt.usePolicyFile && (sOpt.onid3 || sOpt.usePeakData || sOpt.useWaveformData || sOpt.useEQData)) {
+      sm2._wD((sPre || '') + str('policy'));
+      sOpt.usePolicyFile = true;
+    }
+
+    return sOpt;
+
+  };
+
+  complain = function(sMsg) {
+
+    // <d>
+    if (hasConsole && console.warn !== _undefined) {
+      console.warn(sMsg);
+    } else {
+      sm2._wD(sMsg);
+    }
+    // </d>
+
+  };
+
+  doNothing = function() {
+
+    return false;
+
+  };
+
+  disableObject = function(o) {
+
+    var oProp;
+
+    for (oProp in o) {
+      if (o.hasOwnProperty(oProp) && typeof o[oProp] === 'function') {
+        o[oProp] = doNothing;
+      }
+    }
+
+    oProp = null;
+
+  };
+
+  failSafely = function(bNoDisable) {
+
+    // general failure exception handler
+
+    if (bNoDisable === _undefined) {
+      bNoDisable = false;
+    }
+
+    if (disabled || bNoDisable) {
+      sm2.disable(bNoDisable);
+    }
+
+  };
+
+  normalizeMovieURL = function(smURL) {
+
+    var urlParams = null, url;
+
+    if (smURL) {
+      if (smURL.match(/\.swf(\?.*)?$/i)) {
+        urlParams = smURL.substr(smURL.toLowerCase().lastIndexOf('.swf?') + 4);
+        if (urlParams) {
+          // assume user knows what they're doing
+          return smURL;
+        }
+      } else if (smURL.lastIndexOf('/') !== smURL.length - 1) {
+        // append trailing slash, if needed
+        smURL += '/';
+      }
+    }
+
+    url = (smURL && smURL.lastIndexOf('/') !== - 1 ? smURL.substr(0, smURL.lastIndexOf('/') + 1) : './') + sm2.movieURL;
+
+    if (sm2.noSWFCache) {
+      url += ('?ts=' + new Date().getTime());
+    }
+
+    return url;
+
+  };
+
+  setVersionInfo = function() {
+
+    // short-hand for internal use
+
+    fV = parseInt(sm2.flashVersion, 10);
+
+    if (fV !== 8 && fV !== 9) {
+      sm2._wD(str('badFV', fV, defaultFlashVersion));
+      sm2.flashVersion = fV = defaultFlashVersion;
+    }
+
+    // debug flash movie, if applicable
+
+    var isDebug = (sm2.debugMode || sm2.debugFlash?'_debug.swf':'.swf');
+
+    if (sm2.useHTML5Audio && !sm2.html5Only && sm2.audioFormats.mp4.required && fV < 9) {
+      sm2._wD(str('needfl9'));
+      sm2.flashVersion = fV = 9;
+    }
+
+    sm2.version = sm2.versionNumber + (sm2.html5Only?' (HTML5-only mode)':(fV === 9?' (AS3/Flash 9)':' (AS2/Flash 8)'));
+
+    // set up default options
+    if (fV > 8) {
+      // +flash 9 base options
+      sm2.defaultOptions = mixin(sm2.defaultOptions, sm2.flash9Options);
+      sm2.features.buffering = true;
+      // +moviestar support
+      sm2.defaultOptions = mixin(sm2.defaultOptions, sm2.movieStarOptions);
+      sm2.filePatterns.flash9 = new RegExp('\\.(mp3|' + netStreamTypes.join('|') + ')(\\?.*)?$', 'i');
+      sm2.features.movieStar = true;
+    } else {
+      sm2.features.movieStar = false;
+    }
+
+    // regExp for flash canPlay(), etc.
+    sm2.filePattern = sm2.filePatterns[(fV !== 8?'flash9':'flash8')];
+
+    // if applicable, use _debug versions of SWFs
+    sm2.movieURL = (fV === 8?'soundmanager2.swf':'soundmanager2_flash9.swf').replace('.swf', isDebug);
+
+    sm2.features.peakData = sm2.features.waveformData = sm2.features.eqData = (fV > 8);
+
+  };
+
+  setPolling = function(bPolling, bHighPerformance) {
+
+    if (!flash) {
+      return false;
+    }
+
+    flash._setPolling(bPolling, bHighPerformance);
+
+  };
+
+  initDebug = function() {
+
+    // starts debug mode, creating output <div> for UAs without console object
+
+    // allow force of debug mode via URL
+    // <d>
+    if (sm2.debugURLParam.test(wl)) {
+      sm2.debugMode = true;
+    }
+
+    if (id(sm2.debugID)) {
+      return false;
+    }
+
+    var oD, oDebug, oTarget, oToggle, tmp;
+
+    if (sm2.debugMode && !id(sm2.debugID) && (!hasConsole || !sm2.useConsole || !sm2.consoleOnly)) {
+
+      oD = doc.createElement('div');
+      oD.id = sm2.debugID + '-toggle';
+
+      oToggle = {
+        'position': 'fixed',
+        'bottom': '0px',
+        'right': '0px',
+        'width': '1.2em',
+        'height': '1.2em',
+        'lineHeight': '1.2em',
+        'margin': '2px',
+        'textAlign': 'center',
+        'border': '1px solid #999',
+        'cursor': 'pointer',
+        'background': '#fff',
+        'color': '#333',
+        'zIndex': 10001
+      };
+
+      oD.appendChild(doc.createTextNode('-'));
+      oD.onclick = toggleDebug;
+      oD.title = 'Toggle SM2 debug console';
+
+      if (ua.match(/msie 6/i)) {
+        oD.style.position = 'absolute';
+        oD.style.cursor = 'hand';
+      }
+
+      for (tmp in oToggle) {
+        if (oToggle.hasOwnProperty(tmp)) {
+          oD.style[tmp] = oToggle[tmp];
+        }
+      }
+
+      oDebug = doc.createElement('div');
+      oDebug.id = sm2.debugID;
+      oDebug.style.display = (sm2.debugMode?'block':'none');
+
+      if (sm2.debugMode && !id(oD.id)) {
+        try {
+          oTarget = getDocument();
+          oTarget.appendChild(oD);
+        } catch(e2) {
+          throw new Error(str('domError')+' \n'+e2.toString());
+        }
+        oTarget.appendChild(oDebug);
+      }
+
+    }
+
+    oTarget = null;
+    // </d>
+
+  };
+
+  idCheck = this.getSoundById;
+
+  // <d>
+  _wDS = function(o, errorLevel) {
+
+    return (!o ? '' : sm2._wD(str(o), errorLevel));
+
+  };
+
+  toggleDebug = function() {
+
+    var o = id(sm2.debugID),
+    oT = id(sm2.debugID + '-toggle');
+
+    if (!o) {
+      return false;
+    }
+
+    if (debugOpen) {
+      // minimize
+      oT.innerHTML = '+';
+      o.style.display = 'none';
+    } else {
+      oT.innerHTML = '-';
+      o.style.display = 'block';
+    }
+
+    debugOpen = !debugOpen;
+
+  };
+
+  debugTS = function(sEventType, bSuccess, sMessage) {
+
+    // troubleshooter debug hooks
+
+    if (window.sm2Debugger !== _undefined) {
+      try {
+        sm2Debugger.handleEvent(sEventType, bSuccess, sMessage);
+      } catch(e) {
+        // oh well
+        return false;
+      }
+    }
+
+    return true;
+
+  };
+  // </d>
+
+  getSWFCSS = function() {
+
+    var css = [];
+
+    if (sm2.debugMode) {
+      css.push(swfCSS.sm2Debug);
+    }
+
+    if (sm2.debugFlash) {
+      css.push(swfCSS.flashDebug);
+    }
+
+    if (sm2.useHighPerformance) {
+      css.push(swfCSS.highPerf);
+    }
+
+    return css.join(' ');
+
+  };
+
+  flashBlockHandler = function() {
+
+    // *possible* flash block situation.
+
+    var name = str('fbHandler'),
+        p = sm2.getMoviePercent(),
+        css = swfCSS,
+        error = {type:'FLASHBLOCK'};
+
+    if (sm2.html5Only) {
+      // no flash, or unused
+      return false;
+    }
+
+    if (!sm2.ok()) {
+
+      if (needsFlash) {
+        // make the movie more visible, so user can fix
+        sm2.oMC.className = getSWFCSS() + ' ' + css.swfDefault + ' ' + (p === null?css.swfTimedout:css.swfError);
+        sm2._wD(name + ': ' + str('fbTimeout') + (p ? ' (' + str('fbLoaded') + ')' : ''));
+      }
+
+      sm2.didFlashBlock = true;
+
+      // fire onready(), complain lightly
+      processOnEvents({type:'ontimeout', ignoreInit:true, error:error});
+      catchError(error);
+
+    } else {
+
+      // SM2 loaded OK (or recovered)
+
+      // <d>
+      if (sm2.didFlashBlock) {
+        sm2._wD(name + ': Unblocked');
+      }
+      // </d>
+
+      if (sm2.oMC) {
+        sm2.oMC.className = [getSWFCSS(), css.swfDefault, css.swfLoaded + (sm2.didFlashBlock?' '+css.swfUnblocked:'')].join(' ');
+      }
+
+    }
+
+  };
+
+  addOnEvent = function(sType, oMethod, oScope) {
+
+    if (on_queue[sType] === _undefined) {
+      on_queue[sType] = [];
+    }
+
+    on_queue[sType].push({
+      'method': oMethod,
+      'scope': (oScope || null),
+      'fired': false
+    });
+
+  };
+
+  processOnEvents = function(oOptions) {
+
+    // if unspecified, assume OK/error
+
+    if (!oOptions) {
+      oOptions = {
+        type: (sm2.ok() ? 'onready' : 'ontimeout')
+      };
+    }
+
+    if (!didInit && oOptions && !oOptions.ignoreInit) {
+      // not ready yet.
+      return false;
+    }
+
+    if (oOptions.type === 'ontimeout' && (sm2.ok() || (disabled && !oOptions.ignoreInit))) {
+      // invalid case
+      return false;
+    }
+
+    var status = {
+          success: (oOptions && oOptions.ignoreInit?sm2.ok():!disabled)
+        },
+
+        // queue specified by type, or none
+        srcQueue = (oOptions && oOptions.type?on_queue[oOptions.type]||[]:[]),
+
+        queue = [], i, j,
+        args = [status],
+        canRetry = (needsFlash && !sm2.ok());
+
+    if (oOptions.error) {
+      args[0].error = oOptions.error;
+    }
+
+    for (i = 0, j = srcQueue.length; i < j; i++) {
+      if (srcQueue[i].fired !== true) {
+        queue.push(srcQueue[i]);
+      }
+    }
+
+    if (queue.length) {
+      // sm2._wD(sm + ': Firing ' + queue.length + ' ' + oOptions.type + '() item' + (queue.length === 1 ? '' : 's'));
+      for (i = 0, j = queue.length; i < j; i++) {
+        if (queue[i].scope) {
+          queue[i].method.apply(queue[i].scope, args);
+        } else {
+          queue[i].method.apply(this, args);
+        }
+        if (!canRetry) {
+          // useFlashBlock and SWF timeout case doesn't count here.
+          queue[i].fired = true;
+        }
+      }
+    }
+
+    return true;
+
+  };
+
+  initUserOnload = function() {
+
+    window.setTimeout(function() {
+
+      if (sm2.useFlashBlock) {
+        flashBlockHandler();
+      }
+
+      processOnEvents();
+
+      // call user-defined "onload", scoped to window
+
+      if (typeof sm2.onload === 'function') {
+        _wDS('onload', 1);
+        sm2.onload.apply(window);
+        _wDS('onloadOK', 1);
+      }
+
+      if (sm2.waitForWindowLoad) {
+        event.add(window, 'load', initUserOnload);
+      }
+
+    },1);
+
+  };
+
+  detectFlash = function() {
+
+    // hat tip: Flash Detect library (BSD, (C) 2007) by Carl "DocYes" S. Yestrau - http://featureblend.com/javascript-flash-detection-library.html / http://featureblend.com/license.txt
+
+    if (hasFlash !== _undefined) {
+      // this work has already been done.
+      return hasFlash;
+    }
+
+    var hasPlugin = false, n = navigator, nP = n.plugins, obj, type, types, AX = window.ActiveXObject;
+
+    if (nP && nP.length) {
+      type = 'application/x-shockwave-flash';
+      types = n.mimeTypes;
+      if (types && types[type] && types[type].enabledPlugin && types[type].enabledPlugin.description) {
+        hasPlugin = true;
+      }
+    } else if (AX !== _undefined && !ua.match(/MSAppHost/i)) {
+      // Windows 8 Store Apps (MSAppHost) are weird (compatibility?) and won't complain here, but will barf if Flash/ActiveX object is appended to the DOM.
+      try {
+        obj = new AX('ShockwaveFlash.ShockwaveFlash');
+      } catch(e) {
+        // oh well
+        obj = null;
+      }
+      hasPlugin = (!!obj);
+      // cleanup, because it is ActiveX after all
+      obj = null;
+    }
+
+    hasFlash = hasPlugin;
+
+    return hasPlugin;
+
+  };
+
+  featureCheck = function() {
+
+    var flashNeeded,
+        item,
+        formats = sm2.audioFormats,
+        // iPhone <= 3.1 has broken HTML5 audio(), but firmware 3.2 (original iPad) + iOS4 works.
+        isSpecial = (is_iDevice && !!(ua.match(/os (1|2|3_0|3_1)/i)));
+
+    if (isSpecial) {
+
+      // has Audio(), but is broken; let it load links directly.
+      sm2.hasHTML5 = false;
+
+      // ignore flash case, however
+      sm2.html5Only = true;
+
+      // hide the SWF, if present
+      if (sm2.oMC) {
+        sm2.oMC.style.display = 'none';
+      }
+
+    } else {
+
+      if (sm2.useHTML5Audio) {
+
+        if (!sm2.html5 || !sm2.html5.canPlayType) {
+          sm2._wD('SoundManager: No HTML5 Audio() support detected.');
+          sm2.hasHTML5 = false;
+        }
+
+        // <d>
+        if (isBadSafari) {
+          sm2._wD(smc + 'Note: Buggy HTML5 Audio in Safari on this OS X release, see https://bugs.webkit.org/show_bug.cgi?id=32159 - ' + (!hasFlash ?' would use flash fallback for MP3/MP4, but none detected.' : 'will use flash fallback for MP3/MP4, if available'), 1);
+        }
+        // </d>
+
+      }
+
+    }
+
+    if (sm2.useHTML5Audio && sm2.hasHTML5) {
+
+      // sort out whether flash is optional, required or can be ignored.
+
+      // innocent until proven guilty.
+      canIgnoreFlash = true;
+
+      for (item in formats) {
+        if (formats.hasOwnProperty(item)) {
+          if (formats[item].required) {
+            if (!sm2.html5.canPlayType(formats[item].type)) {
+              // 100% HTML5 mode is not possible.
+              canIgnoreFlash = false;
+              flashNeeded = true;
+            } else if (sm2.preferFlash && (sm2.flash[item] || sm2.flash[formats[item].type])) {
+              // flash may be required, or preferred for this format.
+              flashNeeded = true;
+            }
+          }
+        }
+      }
+
+    }
+
+    // sanity check...
+    if (sm2.ignoreFlash) {
+      flashNeeded = false;
+      canIgnoreFlash = true;
+    }
+
+    sm2.html5Only = (sm2.hasHTML5 && sm2.useHTML5Audio && !flashNeeded);
+
+    return (!sm2.html5Only);
+
+  };
+
+  parseURL = function(url) {
+
+    /**
+     * Internal: Finds and returns the first playable URL (or failing that, the first URL.)
+     * @param {string or array} url A single URL string, OR, an array of URL strings or {url:'/path/to/resource', type:'audio/mp3'} objects.
+     */
+
+    var i, j, urlResult = 0, result;
+
+    if (url instanceof Array) {
+
+      // find the first good one
+      for (i=0, j=url.length; i<j; i++) {
+
+        if (url[i] instanceof Object) {
+          // MIME check
+          if (sm2.canPlayMIME(url[i].type)) {
+            urlResult = i;
+            break;
+          }
+
+        } else if (sm2.canPlayURL(url[i])) {
+          // URL string check
+          urlResult = i;
+          break;
+        }
+
+      }
+
+      // normalize to string
+      if (url[urlResult].url) {
+        url[urlResult] = url[urlResult].url;
+      }
+
+      result = url[urlResult];
+
+    } else {
+
+      // single URL case
+      result = url;
+
+    }
+
+    return result;
+
+  };
+
+
+  startTimer = function(oSound) {
+
+    /**
+     * attach a timer to this sound, and start an interval if needed
+     */
+
+    if (!oSound._hasTimer) {
+
+      oSound._hasTimer = true;
+
+      if (!mobileHTML5 && sm2.html5PollingInterval) {
+
+        if (h5IntervalTimer === null && h5TimerCount === 0) {
+
+          h5IntervalTimer = setInterval(timerExecute, sm2.html5PollingInterval);
+
+        }
+
+        h5TimerCount++;
+
+      }
+
+    }
+
+  };
+
+  stopTimer = function(oSound) {
+
+    /**
+     * detach a timer
+     */
+
+    if (oSound._hasTimer) {
+
+      oSound._hasTimer = false;
+
+      if (!mobileHTML5 && sm2.html5PollingInterval) {
+
+        // interval will stop itself at next execution.
+
+        h5TimerCount--;
+
+      }
+
+    }
+
+  };
+
+  timerExecute = function() {
+
+    /**
+     * manual polling for HTML5 progress events, ie., whileplaying() (can achieve greater precision than conservative default HTML5 interval)
+     */
+
+    var i;
+
+    if (h5IntervalTimer !== null && !h5TimerCount) {
+
+      // no active timers, stop polling interval.
+
+      clearInterval(h5IntervalTimer);
+
+      h5IntervalTimer = null;
+
+      return false;
+
+    }
+
+    // check all HTML5 sounds with timers
+
+    for (i = sm2.soundIDs.length-1; i >= 0; i--) {
+
+      if (sm2.sounds[sm2.soundIDs[i]].isHTML5 && sm2.sounds[sm2.soundIDs[i]]._hasTimer) {
+
+        sm2.sounds[sm2.soundIDs[i]]._onTimer();
+
+      }
+
+    }
+
+  };
+
+  catchError = function(options) {
+
+    options = (options !== _undefined ? options : {});
+
+    if (typeof sm2.onerror === 'function') {
+      sm2.onerror.apply(window, [{type:(options.type !== _undefined ? options.type : null)}]);
+    }
+
+    if (options.fatal !== _undefined && options.fatal) {
+      sm2.disable();
+    }
+
+  };
+
+  badSafariFix = function() {
+
+    // special case: "bad" Safari (OS X 10.3 - 10.7) must fall back to flash for MP3/MP4
+    if (!isBadSafari || !detectFlash()) {
+      // doesn't apply
+      return false;
+    }
+
+    var aF = sm2.audioFormats, i, item;
+
+    for (item in aF) {
+      if (aF.hasOwnProperty(item)) {
+        if (item === 'mp3' || item === 'mp4') {
+          sm2._wD(sm + ': Using flash fallback for ' + item + ' format');
+          sm2.html5[item] = false;
+          // assign result to related formats, too
+          if (aF[item] && aF[item].related) {
+            for (i = aF[item].related.length-1; i >= 0; i--) {
+              sm2.html5[aF[item].related[i]] = false;
+            }
+          }
+        }
+      }
+    }
+
+  };
+
+  /**
+   * Pseudo-private flash/ExternalInterface methods
+   * ----------------------------------------------
+   */
+
+  this._setSandboxType = function(sandboxType) {
+
+    // <d>
+    var sb = sm2.sandbox;
+
+    sb.type = sandboxType;
+    sb.description = sb.types[(sb.types[sandboxType] !== _undefined?sandboxType:'unknown')];
+
+    if (sb.type === 'localWithFile') {
+
+      sb.noRemote = true;
+      sb.noLocal = false;
+      _wDS('secNote', 2);
+
+    } else if (sb.type === 'localWithNetwork') {
+
+      sb.noRemote = false;
+      sb.noLocal = true;
+
+    } else if (sb.type === 'localTrusted') {
+
+      sb.noRemote = false;
+      sb.noLocal = false;
+
+    }
+    // </d>
+
+  };
+
+  this._externalInterfaceOK = function(swfVersion) {
+
+    // flash callback confirming flash loaded, EI working etc.
+    // swfVersion: SWF build string
+
+    if (sm2.swfLoaded) {
+      return false;
+    }
+
+    var e;
+
+    debugTS('swf', true);
+    debugTS('flashtojs', true);
+    sm2.swfLoaded = true;
+    tryInitOnFocus = false;
+
+    if (isBadSafari) {
+      badSafariFix();
+    }
+
+    // complain if JS + SWF build/version strings don't match, excluding +DEV builds
+    // <d>
+    if (!swfVersion || swfVersion.replace(/\+dev/i,'') !== sm2.versionNumber.replace(/\+dev/i, '')) {
+
+      e = sm + ': Fatal: JavaScript file build "' + sm2.versionNumber + '" does not match Flash SWF build "' + swfVersion + '" at ' + sm2.url + '. Ensure both are up-to-date.';
+
+      // escape flash -> JS stack so this error fires in window.
+      setTimeout(function versionMismatch() {
+        throw new Error(e);
+      }, 0);
+
+      // exit, init will fail with timeout
+      return false;
+
+    }
+    // </d>
+
+    // IE needs a larger timeout
+    setTimeout(init, isIE ? 100 : 1);
+
+  };
+
+  /**
+   * Private initialization helpers
+   * ------------------------------
+   */
+
+  createMovie = function(smID, smURL) {
+
+    if (didAppend && appendSuccess) {
+      // ignore if already succeeded
+      return false;
+    }
+
+    function initMsg() {
+
+      // <d>
+
+      var options = [],
+          title,
+          msg = [],
+          delimiter = ' + ';
+
+      title = 'SoundManager ' + sm2.version + (!sm2.html5Only && sm2.useHTML5Audio ? (sm2.hasHTML5 ? ' + HTML5 audio' : ', no HTML5 audio support') : '');
+
+      if (!sm2.html5Only) {
+
+        if (sm2.preferFlash) {
+          options.push('preferFlash');
+        }
+
+        if (sm2.useHighPerformance) {
+          options.push('useHighPerformance');
+        }
+
+        if (sm2.flashPollingInterval) {
+          options.push('flashPollingInterval (' + sm2.flashPollingInterval + 'ms)');
+        }
+
+        if (sm2.html5PollingInterval) {
+          options.push('html5PollingInterval (' + sm2.html5PollingInterval + 'ms)');
+        }
+
+        if (sm2.wmode) {
+          options.push('wmode (' + sm2.wmode + ')');
+        }
+
+        if (sm2.debugFlash) {
+          options.push('debugFlash');
+        }
+
+        if (sm2.useFlashBlock) {
+          options.push('flashBlock');
+        }
+
+      } else {
+
+        if (sm2.html5PollingInterval) {
+          options.push('html5PollingInterval (' + sm2.html5PollingInterval + 'ms)');
+        }
+
+      }
+
+      if (options.length) {
+        msg = msg.concat([options.join(delimiter)]);
+      }
+
+      sm2._wD(title + (msg.length ? delimiter + msg.join(', ') : ''), 1);
+
+      showSupport();
+
+      // </d>
+
+    }
+
+    if (sm2.html5Only) {
+
+      // 100% HTML5 mode
+      setVersionInfo();
+
+      initMsg();
+      sm2.oMC = id(sm2.movieID);
+      init();
+
+      // prevent multiple init attempts
+      didAppend = true;
+
+      appendSuccess = true;
+
+      return false;
+
+    }
+
+    // flash path
+    var remoteURL = (smURL || sm2.url),
+    localURL = (sm2.altURL || remoteURL),
+    swfTitle = 'JS/Flash audio component (SoundManager 2)',
+    oTarget = getDocument(),
+    extraClass = getSWFCSS(),
+    isRTL = null,
+    html = doc.getElementsByTagName('html')[0],
+    oEmbed, oMovie, tmp, movieHTML, oEl, s, x, sClass;
+
+    isRTL = (html && html.dir && html.dir.match(/rtl/i));
+    smID = (smID === _undefined?sm2.id:smID);
+
+    function param(name, value) {
+      return '<param name="'+name+'" value="'+value+'" />';
+    }
+
+    // safety check for legacy (change to Flash 9 URL)
+    setVersionInfo();
+    sm2.url = normalizeMovieURL(overHTTP?remoteURL:localURL);
+    smURL = sm2.url;
+
+    sm2.wmode = (!sm2.wmode && sm2.useHighPerformance ? 'transparent' : sm2.wmode);
+
+    if (sm2.wmode !== null && (ua.match(/msie 8/i) || (!isIE && !sm2.useHighPerformance)) && navigator.platform.match(/win32|win64/i)) {
+      /**
+       * extra-special case: movie doesn't load until scrolled into view when using wmode = anything but 'window' here
+       * does not apply when using high performance (position:fixed means on-screen), OR infinite flash load timeout
+       * wmode breaks IE 8 on Vista + Win7 too in some cases, as of January 2011 (?)
+       */
+       messages.push(strings.spcWmode);
+      sm2.wmode = null;
+    }
+
+    oEmbed = {
+      'name': smID,
+      'id': smID,
+      'src': smURL,
+      'quality': 'high',
+      'allowScriptAccess': sm2.allowScriptAccess,
+      'bgcolor': sm2.bgColor,
+      'pluginspage': http+'www.macromedia.com/go/getflashplayer',
+      'title': swfTitle,
+      'type': 'application/x-shockwave-flash',
+      'wmode': sm2.wmode,
+      // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
+      'hasPriority': 'true'
+    };
+
+    if (sm2.debugFlash) {
+      oEmbed.FlashVars = 'debug=1';
+    }
+
+    if (!sm2.wmode) {
+      // don't write empty attribute
+      delete oEmbed.wmode;
+    }
+
+    if (isIE) {
+
+      // IE is "special".
+      oMovie = doc.createElement('div');
+      movieHTML = [
+        '<object id="' + smID + '" data="' + smURL + '" type="' + oEmbed.type + '" title="' + oEmbed.title +'" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="' + http+'download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0">',
+        param('movie', smURL),
+        param('AllowScriptAccess', sm2.allowScriptAccess),
+        param('quality', oEmbed.quality),
+        (sm2.wmode? param('wmode', sm2.wmode): ''),
+        param('bgcolor', sm2.bgColor),
+        param('hasPriority', 'true'),
+        (sm2.debugFlash ? param('FlashVars', oEmbed.FlashVars) : ''),
+        '</object>'
+      ].join('');
+
+    } else {
+
+      oMovie = doc.createElement('embed');
+      for (tmp in oEmbed) {
+        if (oEmbed.hasOwnProperty(tmp)) {
+          oMovie.setAttribute(tmp, oEmbed[tmp]);
+        }
+      }
+
+    }
+
+    initDebug();
+    extraClass = getSWFCSS();
+    oTarget = getDocument();
+
+    if (oTarget) {
+
+      sm2.oMC = (id(sm2.movieID) || doc.createElement('div'));
+
+      if (!sm2.oMC.id) {
+
+        sm2.oMC.id = sm2.movieID;
+        sm2.oMC.className = swfCSS.swfDefault + ' ' + extraClass;
+        s = null;
+        oEl = null;
+
+        if (!sm2.useFlashBlock) {
+          if (sm2.useHighPerformance) {
+            // on-screen at all times
+            s = {
+              'position': 'fixed',
+              'width': '8px',
+              'height': '8px',
+              // >= 6px for flash to run fast, >= 8px to start up under Firefox/win32 in some cases. odd? yes.
+              'bottom': '0px',
+              'left': '0px',
+              'overflow': 'hidden'
+            };
+          } else {
+            // hide off-screen, lower priority
+            s = {
+              'position': 'absolute',
+              'width': '6px',
+              'height': '6px',
+              'top': '-9999px',
+              'left': '-9999px'
+            };
+            if (isRTL) {
+              s.left = Math.abs(parseInt(s.left,10))+'px';
+            }
+          }
+        }
+
+        if (isWebkit) {
+          // soundcloud-reported render/crash fix, safari 5
+          sm2.oMC.style.zIndex = 10000;
+        }
+
+        if (!sm2.debugFlash) {
+          for (x in s) {
+            if (s.hasOwnProperty(x)) {
+              sm2.oMC.style[x] = s[x];
+            }
+          }
+        }
+
+        try {
+          if (!isIE) {
+            sm2.oMC.appendChild(oMovie);
+          }
+          oTarget.appendChild(sm2.oMC);
+          if (isIE) {
+            oEl = sm2.oMC.appendChild(doc.createElement('div'));
+            oEl.className = swfCSS.swfBox;
+            oEl.innerHTML = movieHTML;
+          }
+          appendSuccess = true;
+        } catch(e) {
+          throw new Error(str('domError')+' \n'+e.toString());
+        }
+
+      } else {
+
+        // SM2 container is already in the document (eg. flashblock use case)
+        sClass = sm2.oMC.className;
+        sm2.oMC.className = (sClass?sClass+' ':swfCSS.swfDefault) + (extraClass?' '+extraClass:'');
+        sm2.oMC.appendChild(oMovie);
+        if (isIE) {
+          oEl = sm2.oMC.appendChild(doc.createElement('div'));
+          oEl.className = swfCSS.swfBox;
+          oEl.innerHTML = movieHTML;
+        }
+        appendSuccess = true;
+
+      }
+
+    }
+
+    didAppend = true;
+    initMsg();
+    // sm2._wD(sm + ': Trying to load ' + smURL + (!overHTTP && sm2.altURL ? ' (alternate URL)' : ''), 1);
+
+    return true;
+
+  };
+
+  initMovie = function() {
+
+    if (sm2.html5Only) {
+      createMovie();
+      return false;
+    }
+
+    // attempt to get, or create, movie (may already exist)
+    if (flash) {
+      return false;
+    }
+
+    if (!sm2.url) {
+
+      /**
+       * Something isn't right - we've reached init, but the soundManager url property has not been set.
+       * User has not called setup({url: ...}), or has not set soundManager.url (legacy use case) directly before init time.
+       * Notify and exit. If user calls setup() with a url: property, init will be restarted as in the deferred loading case.
+       */
+
+       _wDS('noURL');
+       return false;
+
+    }
+
+    // inline markup case
+    flash = sm2.getMovie(sm2.id);
+
+    if (!flash) {
+      if (!oRemoved) {
+        // try to create
+        createMovie(sm2.id, sm2.url);
+      } else {
+        // try to re-append removed movie after reboot()
+        if (!isIE) {
+          sm2.oMC.appendChild(oRemoved);
+        } else {
+          sm2.oMC.innerHTML = oRemovedHTML;
+        }
+        oRemoved = null;
+        didAppend = true;
+      }
+      flash = sm2.getMovie(sm2.id);
+    }
+
+    if (typeof sm2.oninitmovie === 'function') {
+      setTimeout(sm2.oninitmovie, 1);
+    }
+
+    // <d>
+    flushMessages();
+    // </d>
+
+    return true;
+
+  };
+
+  delayWaitForEI = function() {
+
+    setTimeout(waitForEI, 1000);
+
+  };
+
+  rebootIntoHTML5 = function() {
+
+    // special case: try for a reboot with preferFlash: false, if 100% HTML5 mode is possible and useFlashBlock is not enabled.
+
+    window.setTimeout(function() {
+
+      complain(smc + 'useFlashBlock is false, 100% HTML5 mode is possible. Rebooting with preferFlash: false...');
+
+      sm2.setup({
+        preferFlash: false
+      }).reboot();
+
+      // if for some reason you want to detect this case, use an ontimeout() callback and look for html5Only and didFlashBlock == true.
+      sm2.didFlashBlock = true;
+
+      sm2.beginDelayedInit();
+
+    }, 1);
+
+  };
+
+  waitForEI = function() {
+
+    var p,
+        loadIncomplete = false;
+
+    if (!sm2.url) {
+      // No SWF url to load (noURL case) - exit for now. Will be retried when url is set.
+      return false;
+    }
+
+    if (waitingForEI) {
+      return false;
+    }
+
+    waitingForEI = true;
+    event.remove(window, 'load', delayWaitForEI);
+
+    if (hasFlash && tryInitOnFocus && !isFocused) {
+      // Safari won't load flash in background tabs, only when focused.
+      _wDS('waitFocus');
+      return false;
+    }
+
+    if (!didInit) {
+      p = sm2.getMoviePercent();
+      if (p > 0 && p < 100) {
+        loadIncomplete = true;
+      }
+    }
+
+    setTimeout(function() {
+
+      p = sm2.getMoviePercent();
+
+      if (loadIncomplete) {
+        // special case: if movie *partially* loaded, retry until it's 100% before assuming failure.
+        waitingForEI = false;
+        sm2._wD(str('waitSWF'));
+        window.setTimeout(delayWaitForEI, 1);
+        return false;
+      }
+
+      // <d>
+      if (!didInit) {
+
+        sm2._wD(sm + ': No Flash response within expected time. Likely causes: ' + (p === 0 ? 'SWF load failed, ':'') + 'Flash blocked or JS-Flash security error.' + (sm2.debugFlash?' ' + str('checkSWF'):''), 2);
+
+        if (!overHTTP && p) {
+
+          _wDS('localFail', 2);
+
+          if (!sm2.debugFlash) {
+            _wDS('tryDebug', 2);
+          }
+
+        }
+
+        if (p === 0) {
+
+          // if 0 (not null), probably a 404.
+          sm2._wD(str('swf404', sm2.url), 1);
+
+        }
+
+        debugTS('flashtojs', false, ': Timed out' + overHTTP?' (Check flash security or flash blockers)':' (No plugin/missing SWF?)');
+
+      }
+      // </d>
+
+      // give up / time-out, depending
+
+      if (!didInit && okToDisable) {
+
+        if (p === null) {
+
+          // SWF failed to report load progress. Possibly blocked.
+
+          if (sm2.useFlashBlock || sm2.flashLoadTimeout === 0) {
+
+            if (sm2.useFlashBlock) {
+
+              flashBlockHandler();
+
+            }
+
+            _wDS('waitForever');
+
+          } else {
+
+            // no custom flash block handling, but SWF has timed out. Will recover if user unblocks / allows SWF load.
+
+            if (!sm2.useFlashBlock && canIgnoreFlash) {
+
+              rebootIntoHTML5();
+
+            } else {
+
+              _wDS('waitForever');
+
+              // fire any regular registered ontimeout() listeners.
+              processOnEvents({type:'ontimeout', ignoreInit: true, error: {type: 'INIT_FLASHBLOCK'}});
+
+            }
+
+          }
+
+        } else {
+
+          // SWF loaded? Shouldn't be a blocking issue, then.
+
+          if (sm2.flashLoadTimeout === 0) {
+
+            _wDS('waitForever');
+
+          } else {
+
+            if (!sm2.useFlashBlock && canIgnoreFlash) {
+
+              rebootIntoHTML5();
+
+            } else {
+
+              failSafely(true);
+
+            }
+
+          }
+
+        }
+
+      }
+
+    }, sm2.flashLoadTimeout);
+
+  };
+
+  handleFocus = function() {
+
+    function cleanup() {
+      event.remove(window, 'focus', handleFocus);
+    }
+
+    if (isFocused || !tryInitOnFocus) {
+      // already focused, or not special Safari background tab case
+      cleanup();
+      return true;
+    }
+
+    okToDisable = true;
+    isFocused = true;
+    _wDS('gotFocus');
+
+    // allow init to restart
+    waitingForEI = false;
+
+    // kick off ExternalInterface timeout, now that the SWF has started
+    delayWaitForEI();
+
+    cleanup();
+    return true;
+
+  };
+
+  flushMessages = function() {
+
+    // <d>
+
+    // SM2 pre-init debug messages
+    if (messages.length) {
+      sm2._wD('SoundManager 2: ' + messages.join(' '), 1);
+      messages = [];
+    }
+
+    // </d>
+
+  };
+
+  showSupport = function() {
+
+    // <d>
+
+    flushMessages();
+
+    var item, tests = [];
+
+    if (sm2.useHTML5Audio && sm2.hasHTML5) {
+      for (item in sm2.audioFormats) {
+        if (sm2.audioFormats.hasOwnProperty(item)) {
+          tests.push(item + ' = ' + sm2.html5[item] + (!sm2.html5[item] && needsFlash && sm2.flash[item] ? ' (using flash)' : (sm2.preferFlash && sm2.flash[item] && needsFlash ? ' (preferring flash)': (!sm2.html5[item] ? ' (' + (sm2.audioFormats[item].required ? 'required, ':'') + 'and no flash support)' : ''))));
+        }
+      }
+      sm2._wD('SoundManager 2 HTML5 support: ' + tests.join(', '), 1);
+    }
+
+    // </d>
+
+  };
+
+  initComplete = function(bNoDisable) {
+
+    if (didInit) {
+      return false;
+    }
+
+    if (sm2.html5Only) {
+      // all good.
+      _wDS('sm2Loaded');
+      didInit = true;
+      initUserOnload();
+      debugTS('onload', true);
+      return true;
+    }
+
+    var wasTimeout = (sm2.useFlashBlock && sm2.flashLoadTimeout && !sm2.getMoviePercent()),
+        result = true,
+        error;
+
+    if (!wasTimeout) {
+      didInit = true;
+    }
+
+    error = {type: (!hasFlash && needsFlash ? 'NO_FLASH' : 'INIT_TIMEOUT')};
+
+    sm2._wD('SoundManager 2 ' + (disabled ? 'failed to load' : 'loaded') + ' (' + (disabled ? 'Flash security/load error' : 'OK') + ')', disabled ? 2: 1);
+
+    if (disabled || bNoDisable) {
+      if (sm2.useFlashBlock && sm2.oMC) {
+        sm2.oMC.className = getSWFCSS() + ' ' + (sm2.getMoviePercent() === null?swfCSS.swfTimedout:swfCSS.swfError);
+      }
+      processOnEvents({type:'ontimeout', error:error, ignoreInit: true});
+      debugTS('onload', false);
+      catchError(error);
+      result = false;
+    } else {
+      debugTS('onload', true);
+    }
+
+    if (!disabled) {
+      if (sm2.waitForWindowLoad && !windowLoaded) {
+        _wDS('waitOnload');
+        event.add(window, 'load', initUserOnload);
+      } else {
+        // <d>
+        if (sm2.waitForWindowLoad && windowLoaded) {
+          _wDS('docLoaded');
+        }
+        // </d>
+        initUserOnload();
+      }
+    }
+
+    return result;
+
+  };
+
+  /**
+   * apply top-level setupOptions object as local properties, eg., this.setupOptions.flashVersion -> this.flashVersion (soundManager.flashVersion)
+   * this maintains backward compatibility, and allows properties to be defined separately for use by soundManager.setup().
+   */
+
+  setProperties = function() {
+
+    var i,
+        o = sm2.setupOptions;
+
+    for (i in o) {
+
+      if (o.hasOwnProperty(i)) {
+
+        // assign local property if not already defined
+
+        if (sm2[i] === _undefined) {
+
+          sm2[i] = o[i];
+
+        } else if (sm2[i] !== o[i]) {
+
+          // legacy support: write manually-assigned property (eg., soundManager.url) back to setupOptions to keep things in sync
+          sm2.setupOptions[i] = sm2[i];
+
+        }
+
+      }
+
+    }
+
+  };
+
+
+  init = function() {
+
+    // called after onload()
+
+    if (didInit) {
+      _wDS('didInit');
+      return false;
+    }
+
+    function cleanup() {
+      event.remove(window, 'load', sm2.beginDelayedInit);
+    }
+
+    if (sm2.html5Only) {
+      if (!didInit) {
+        // we don't need no steenking flash!
+        cleanup();
+        sm2.enabled = true;
+        initComplete();
+      }
+      return true;
+    }
+
+    // flash path
+    initMovie();
+
+    try {
+
+      // attempt to talk to Flash
+      flash._externalInterfaceTest(false);
+
+      // apply user-specified polling interval, OR, if "high performance" set, faster vs. default polling
+      // (determines frequency of whileloading/whileplaying callbacks, effectively driving UI framerates)
+      setPolling(true, (sm2.flashPollingInterval || (sm2.useHighPerformance ? 10 : 50)));
+
+      if (!sm2.debugMode) {
+        // stop the SWF from making debug output calls to JS
+        flash._disableDebug();
+      }
+
+      sm2.enabled = true;
+      debugTS('jstoflash', true);
+
+      if (!sm2.html5Only) {
+        // prevent browser from showing cached page state (or rather, restoring "suspended" page state) via back button, because flash may be dead
+        // http://www.webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/
+        event.add(window, 'unload', doNothing);
+      }
+
+    } catch(e) {
+
+      sm2._wD('js/flash exception: ' + e.toString());
+      debugTS('jstoflash', false);
+      catchError({type:'JS_TO_FLASH_EXCEPTION', fatal:true});
+      // don't disable, for reboot()
+      failSafely(true);
+      initComplete();
+
+      return false;
+
+    }
+
+    initComplete();
+
+    // disconnect events
+    cleanup();
+
+    return true;
+
+  };
+
+  domContentLoaded = function() {
+
+    if (didDCLoaded) {
+      return false;
+    }
+
+    didDCLoaded = true;
+
+    // assign top-level soundManager properties eg. soundManager.url
+    setProperties();
+
+    initDebug();
+
+    /**
+     * Temporary feature: allow force of HTML5 via URL params: sm2-usehtml5audio=0 or 1
+     * Ditto for sm2-preferFlash, too.
+     */
+    // <d>
+    (function(){
+
+      var a = 'sm2-usehtml5audio=',
+          a2 = 'sm2-preferflash=',
+          b = null,
+          b2 = null,
+          l = wl.toLowerCase();
+
+      if (l.indexOf(a) !== -1) {
+        b = (l.charAt(l.indexOf(a)+a.length) === '1');
+        if (hasConsole) {
+          console.log((b?'Enabling ':'Disabling ')+'useHTML5Audio via URL parameter');
+        }
+        sm2.setup({
+          'useHTML5Audio': b
+        });
+      }
+
+      if (l.indexOf(a2) !== -1) {
+        b2 = (l.charAt(l.indexOf(a2)+a2.length) === '1');
+        if (hasConsole) {
+          console.log((b2?'Enabling ':'Disabling ')+'preferFlash via URL parameter');
+        }
+        sm2.setup({
+          'preferFlash': b2
+        });
+      }
+
+    }());
+    // </d>
+
+    if (!hasFlash && sm2.hasHTML5) {
+      sm2._wD('SoundManager 2: No Flash detected' + (!sm2.useHTML5Audio ? ', enabling HTML5.' : '. Trying HTML5-only mode.'), 1);
+      sm2.setup({
+        'useHTML5Audio': true,
+        // make sure we aren't preferring flash, either
+        // TODO: preferFlash should not matter if flash is not installed. Currently, stuff breaks without the below tweak.
+        'preferFlash': false
+      });
+    }
+
+    testHTML5();
+
+    if (!hasFlash && needsFlash) {
+      messages.push(strings.needFlash);
+      // TODO: Fatal here vs. timeout approach, etc.
+      // hack: fail sooner.
+      sm2.setup({
+        'flashLoadTimeout': 1
+      });
+    }
+
+    if (doc.removeEventListener) {
+      doc.removeEventListener('DOMContentLoaded', domContentLoaded, false);
+    }
+
+    initMovie();
+
+    return true;
+
+  };
+
+  domContentLoadedIE = function() {
+
+    if (doc.readyState === 'complete') {
+      domContentLoaded();
+      doc.detachEvent('onreadystatechange', domContentLoadedIE);
+    }
+
+    return true;
+
+  };
+
+  winOnLoad = function() {
+
+    // catch edge case of initComplete() firing after window.load()
+    windowLoaded = true;
+    event.remove(window, 'load', winOnLoad);
+
+  };
+
+  /**
+   * miscellaneous run-time, pre-init stuff
+   */
+
+  preInit = function() {
+
+    if (mobileHTML5) {
+
+      // prefer HTML5 for mobile + tablet-like devices, probably more reliable vs. flash at this point.
+
+      // <d>
+      if (!sm2.setupOptions.useHTML5Audio || sm2.setupOptions.preferFlash) {
+        // notify that defaults are being changed.
+        messages.push(strings.mobileUA);
+      }
+      // </d>
+
+      sm2.setupOptions.useHTML5Audio = true;
+      sm2.setupOptions.preferFlash = false;
+
+      if (is_iDevice || (isAndroid && !ua.match(/android\s2\.3/i))) {
+        // iOS and Android devices tend to work better with a single audio instance, specifically for chained playback of sounds in sequence.
+        // common use case: exiting sound onfinish() -> createSound() -> play()
+        // <d>
+        messages.push(strings.globalHTML5);
+        // </d>
+        if (is_iDevice) {
+          sm2.ignoreFlash = true;
+        }
+        useGlobalHTML5Audio = true;
+      }
+
+    }
+
+  };
+
+  preInit();
+
+  // sniff up-front
+  detectFlash();
+
+  // focus and window load, init (primarily flash-driven)
+  event.add(window, 'focus', handleFocus);
+  event.add(window, 'load', delayWaitForEI);
+  event.add(window, 'load', winOnLoad);
+
+  if (doc.addEventListener) {
+
+    doc.addEventListener('DOMContentLoaded', domContentLoaded, false);
+
+  } else if (doc.attachEvent) {
+
+    doc.attachEvent('onreadystatechange', domContentLoadedIE);
+
+  } else {
+
+    // no add/attachevent support - safe to assume no JS -> Flash either
+    debugTS('onload', false);
+    catchError({type:'NO_DOM2_EVENTS', fatal:true});
+
+  }
+
+} // SoundManager()
+
+// SM2_DEFER details: http://www.schillmania.com/projects/soundmanager2/doc/getstarted/#lazy-loading
+
+if (window.SM2_DEFER === undefined || !SM2_DEFER) {
+  soundManager = new SoundManager();
+}
+
+/**
+ * SoundManager public interfaces
+ * ------------------------------
+ */
+
+window.SoundManager = SoundManager; // constructor
+window.soundManager = soundManager; // public API, flash callbacks etc.
+
+}(window));