// Look for the Class.com frameset window, and set the global variable CCFS
CCFS = get_ccfs_r(self, null)
if (CCFS==null)
  alert("Couldn't find the Class.com frameset.  Some features may not work correctly.")
//--------------------------------------------------------------------------------------------------------
function get_ccfs_r(theWindow, fromWindow)
{
  /*
    Searches recursively for an open window or frame which has defined a variable called I_AM_THE_CCFS.
    The search includes theWindow, and its "relatives."  Relatives are defined as follows:
    - A parent is a relative;
    - A "child" (element of frames array) is a relative;
    - An opener is a relative;
    - A relative of a relative is a relative.
    Parameters:
      theWindow  - A reference to a window.  This window and all its relatives will be searched.
      fromWindow - Either null, or a relative of theWindow that has already been (or is being)
                   searched in another recursion, and therefore should be excluded from additional
                   searching.  This is to prevent infinite recursion back and forth between parent and child.
    
    If the window with the I_AM_THE_CCFS variable is found, the function returns a reference to
    that window.  Otherwise, the function returns null
  */
  var try_ccfs, w_parent, w_opener

  if (typeof theWindow.I_AM_THE_CCFS != "undefined" && !theWindow.closed)
  {
  	//In Safari, children elements inherit the global variables and functions of their ancestors
  	//Thus, we must return the oldest ancestor with I_AM_THE_CCFS defined
    if (navigator.userAgent.indexOf("Safari") != -1)
    {
      while(theWindow.parent ? (theWindow.parent.I_AM_THE_CCFS && theWindow.parent != theWindow) : false)
      {
        theWindow = theWindow.parent
      }
    }
    return theWindow
  }
  else
  {
    // 1. Test parent (and its relatives):
    try
    { w_parent = theWindow.parent } //In NS 7, this generates an error if parent's in a different domain
    catch(x) // (apparently ignored in Safari, but only applicable for the NS-generated error anyway)
    { w_parent = null }
    if (w_parent != null && w_parent != theWindow && w_parent != fromWindow)
    {
      try_ccfs = get_ccfs_r(w_parent, theWindow)
      if (try_ccfs) return try_ccfs
    }
    // 2. Test opener (and its relatives):
    try
    { w_opener = theWindow.opener } //In NS 7, this generates an error if opener's in a different domain
    catch(x)
    { w_opener = null }
    if (w_opener != null && w_opener != fromWindow)
    {
      try_ccfs = get_ccfs_r(w_opener, theWindow)
      if (try_ccfs) return try_ccfs
    }
    // 3. Test frames (and their relatives):
    for (var i=0; i<theWindow.frames.length; i++)
    {
      var theFrame = theWindow.frames[i]
      if (theFrame != fromWindow)
      {
        try_ccfs = get_ccfs_r(theFrame, theWindow)
        if (try_ccfs) return try_ccfs
      }
    }
    // 4. If we got this far, it wasn't found:
    return null
  }
}
//-----------------------------------------------------------------------------------------------------------
function playMedia(target, locn_ref, filename, attributes, serverOnly, closeDoc)
{
  /*------------------------------------------------------------------------------------------
  This function will load and/or "play" (or display) various kinds of media, including
  graphics images, audio clips and video clips.  Use this function to present media
  in any of the following situations:
  * When the media piece may reside on the course CD; or:
  * When a piece needs to be loaded into a different frame (e.g. a hidden frame).
  * When you want to avoid the Windows IE "ActiveX alert" for media in <object>, <embed> and <applet> elements.

  ----------------
  Input Parameters
  ----------------
  
  target
  ------
  This should be one of the following:

  * A window or frame object.  In this case, the media piece will be loaded into the indicated
    window or frame.  Specify "self" (without quotes) to load the media into the current window or frame.

  * A 2-element array.  The first element should be a reference to a window or frame object; the second
    element should be a string which is the id of a <div> element that's in the indicated window or frame.
    In this case, the media piece will be loaded into the <div> element.

  * null.  In this case, The function will open a new popup window and will load the piece
    there.  The popup window will include a "Close Window" button.  The dimensions of the
    window will be determined by looking at the "attributes" parameter.

  locn_ref
  --------
  This is used to help find the path to the media file.  It should be one of the following:
  * A 1- or 2-element array.  The first element should always be self (i.e. a reference to the
    window that called playMedia).  The 2nd element, if present, should be a string which is a
    relative path from the calling document to the folder that contains the media file.  If the
    2nd element is omitted, it's assumed that the media file is in the same folder as the calling
    document.  Examples:

        [self]                    <- Indicates the media file is in same folder as calling document
        [self, '../01abc/media/'] <- 2nd element indicates relative path from document to media folder

  * self (i.e. a reference to the window that called playMedia).  In this case, it's assumed
    that there is a folder called "media" at the same directory level as the calling document's
    location.  The function will look in that folder (or in the analogous CD folder) to find
    the media file.  Note that, unlike the previous case, the "self" identifier does not appear
    in an array here.  Specifying locn_ref=self is the same as specifying locn_ref=[self,'media/'].
    Example:

        self    <- Indicates there's a folder called "media" at same level as document.
                   This form of locn_ref is identical to [self,'media/']

  * A partial path string.  This should be a partial path from the course's ROOT folder to the
    folder that contains the media piece; e.g. "00common/media/".  It should NOT be a partial
    path from the calling document's location! (unless the calling document happens to reside
    in the course's root folder).
    Example:

        '00common/01abc/media/'  <- Path from COURSE ROOT folder to media folder

  filename
  --------
  The name of the media file.  Don't include any path information here.  However, it is permissible
  to include a querystring following the file's name, if the media piece requires querystring parameters.
  Examples:

        'spinning_globe.mov'
        'activity1.swf?xml=../support/a1.xml&fruit=banana'

  attributes (optional)
  ----------
  This parameter is interpreted differently for different types of media, according to the
  file's extension.  The following interpretations are currently defined:

     extension   attributes parameter
     ---------   --------------------
       .mov      Either null, or an object with some or all of the following optional properties:

                 height:     height of movie rectangle
                 width:      width of movie rectangle
                 autoplay:   true or false.  Default is true.
                 controller: true or false.  Default is true.

                 If one of height or width is specified, the other must be specified also.
                 Height/width *must* be specified if the <target> parameter is null (i.e., if the
                 target is a new popup).  If height/width are not specified, the movie is assumed
                 to be an audio clip.

      .jpeg,
      .jpg,
      .gif       Either null, or an object with some or all of the following properties:

                 height: height of the image rectangle
                 width:  width of the image rectangle
                 usemap: name of a map to be associated with the image

                 If one of height or width is specified, the other must be specified also.
                 If height & width are not specified, the image is drawn in its default size rectangle.
                 Height/width *must* be specified if the <target> parameter is null (i.e., if the
                 target is a new popup).

      .swf       Either null, or an object with some or all of the following optional properties:

                 height:     height of movie rectangle
                 width:      width of movie rectangle
                 autoplay:   true or false.  Default is true.
                 version:    a string that should be appended to the URL for the Flash download
                             site, indicating the version of the Flash plug-in to be downloaded
                             in case the user doesn't have it.  Example: "5,0,0,0".  If this
                             property is omitted, and the user doesn't have Flash, I think that
                             by default the latest available Flash version is downloaded.

                 If one of height or width is specified, the other must be specified also.
                 Height/width *must* be specified if the <target> parameter is null (i.e., if the
                 target is a new popup).

      .dcr       Either null, or an object with both of the following properties:

                 height: Height of the director piece's rectangle
                 width:  Width of the director piece's rectangle

      .class     Either null, or an object with both of the following properties:

                 height: Height of the director piece's rectangle
                 width:  Width of the director piece's rectangle
                 
      .mp3       Either null, or an object one or both of the following optional properties:
                 
                 width:      width of movie controller (if any)
                 autoplay:   true or false.  Default is true.

                 A controller will be present if and only if width is specified.  If the
                 controller is present, its height will always be 16.  The width *must* be
                 specified if the target is null (i.e. the target is a new popup).

      .ggb       An object specifying at least a "width" and "height" property.

  The attributes parameter is ignored for file extensions other than those listed above.  Also,
  it is an error to set the target parameter to null unless the file's extension is in this list,
  and the attributes parameter is specified.

  The attributes parameter may also contain other properties besides those listed above.  In most
  such cases, playMedia will add the specified attributes to the <object>, <embed>, <param>, etc.
  tags as appropriate.  It is not necessary to specify "standard" attributes like "src", "codebase",
  etc., as playMedia determines these from the other parameters.  But if you need to specify special
  miscellaneous attributes (like "bgcolor", "title", etc.) you can add them to the attributes parameter.

  serverOnly (optional)
  ----------
  This is a boolean.  If true, the function assumes that the media file does NOT exist on the
  CD, and will look only on the server.  If it's false, the function assumes that the CD does
  contain the media file, and will look on the CD (if the user indicated they're using a CD).

  If serverOnly is omitted or is null, the function treats it as false.

  closeDoc (optional)
  --------
  This is a boolean.  It's significant only if the target parameter indicates a window or frame object.
  If closeDoc is true, the function closes the document in the target window (i.e., it
  calls target.document.close) after writing the HTML to present the media piece.  Otherwise,
  it leaves the document open.  Please note that closing the document is NOT the same as closing
  the window.  It just indicates that no other HTML statements will follow the stuff written by
  the playMedia function.  In general, you should set this to true if the target is different from
  the window or frame that called the function.

  Note:
  * If target is null (i.e. if the function opens a new popup window), the closeDoc parameter
    is ignored.  The function always closes the document in this case.
  * If target is a 2-element array (i.e., if the media is being presented in a "div" structure),
    the closeDoc parameter is ignored.  The function never closes the document in this case.

  If closeDoc is omitted or is null, and target is a window or frame object, the function behaves as follows:
  * If the target indicates the special "aux1" frame, the function closes the document.
  * If the target incicates any other window or frame, the function does not close the document.

  -------------
  Return Values
  -------------
  The function does not return any value.

  ----------------
  Global Variables
  ----------------
  The function does not alter any global variables.  It does read the following global variables:
  * CCFS
  -------------------------------------------------------------------------------------------*/

  //===================================== DEBUGGING ===================================
  //alert("Rev 1.0\nplayMedia called for " + filename)
  //=================================== END DEBUGGING =================================

  var supported_types = new Array("gif", "jpeg", "jpg", "mov", "swf", "dcr", "class", "mp3", "ggb")
  var i, found, movie_width, movie_height, media_type, media_url, base, temp
  var html, window_width, window_height, autoplay_value, controller_value
  var picture_width, picture_height, flash_version, usemap_value, q_media_url
  var qstring, rex, matches, filename_proper, theDiv, divWindow, divID
  var useCD, CDRoot, using_yuna, misc_attributes, archive
  var q_char, path_rex, matches, q_base, q_filename

  var isNS  = (window.navigator.appName.indexOf("Netscape") >= 0);
  var isIE  = (window.navigator.appName.indexOf("Explorer") >= 0);
  var isWin  = (window.navigator.userAgent.indexOf("Win") >= 0); 
  var isMac  = (window.navigator.userAgent.indexOf("Mac") >= 0);

  using_yuna = (typeof CCFS=="object" && (IsWindow(CCFS)) && CCFS != self)
  if (using_yuna && CCFS.useCD) {
    useCD = true
    CDRoot = CCFS.CDRoot
  }
  else {
    useCD = false
    CDRoot = null
  }

  // Initialize values for certain variables:
  var errMsg = ""
  var window_title = ""

  // 1. Trim leading/trailing whitespace from any string arguments
  // -------------------------------------------------------------
  for (i in arguments)
  {
    if (typeof arguments[i] == "string" && arguments[i] != "")
    {
      // Would've been nice to do this via replace() method, but could not discover an
      // appropriate regular expression that worked consistently on all browsers.
      temp = arguments[i]
      while (temp.length > 0 && temp.substring(0,1)==" ") temp = temp.substring(1) // leading spaces
      while (temp.length > 0 && temp.substring(temp.length-1)==" ") temp = temp.substring(0,temp.length-1) // trailing spaces
      arguments[i] = temp
    }
  }

  // 2. Make sure all parameters look reasonable
  // -------------------------------------------
  if (!(target == null || IsWindow(target) || IsArray(target)))
    errMsg = "The target parameter should be null, or a window object, or an array."
  else if (IsArray(target) && !(IsWindow(target[0]) && typeof target[1]=="string"))
    errMsg = "When target parameter is an array, its 1st element should be a window reference and its 2nd element should be a string."
  else if (!(typeof locn_ref == "string" || IsWindow(locn_ref) || IsArray(locn_ref)))
    errMsg = "The locn_ref parameter should be a string, or a window object, or an array."
  else if (IsArray(locn_ref) && !IsWindow(locn_ref[0]))
    errMsg = "The first element in the locn_ref array should be a window reference (self)."
  else if (IsArray(locn_ref) && locn_ref.length > 1 && typeof locn_ref[1] != "string")
    errMsg = "The second element in the locn_ref array should be a string."
  else if (typeof locn_ref == "string" && (locn_ref.indexOf("/")==0 || locn_ref.indexOf("..")==0))
    errMsg = "\"" + locn_ref + "\" is an invalid locn_ref string.  Please specify a path relative to the course root directory."
  else if (typeof filename != "string")
    errMsg = "The filename parameter should be a string."
  else if (!(serverOnly==null || typeof serverOnly == "boolean"))
    errMsg = "The serverOnly parameter should be true or false."
  else if (!(closeDoc==null || typeof closeDoc == "boolean"))
    errMsg = "The closeDoc parameter should be true or false."

  // 3. Check the media type, based on file extension
  // ------------------------------------------------
  if (errMsg=="")
  {
    rex = /([^?]*\.)(\w+)(\?.*)?$/
    matches = rex.exec(filename)
    if (matches==null)
      errMsg = "Can't tell what type of media file this is."
    else
    {
      media_type = matches[2].toLowerCase()
      qstring = matches[3]; if (qstring==null) qstring=""
      filename_proper = matches[1] + matches[2] //excludes querystring, if any
      if (filename_proper.indexOf("/") > -1) errMsg = "The filename parameter should not include any path information"
    }
  }
  if (errMsg=="")
  {
    found = false
    for (i in supported_types)
    {
      if (media_type == supported_types[i]) found = true
    }
    if (found)
    {
      // Check whether the attributes parameter is OK
      switch (media_type)
      {
        case "mov":
          // Initialize default values
          autoplay_value = "true"
          controller_value = "true"
          movie_width = null
          movie_height = null

          if (attributes == null)
            {} //(keep default values)
          else if (typeof attributes == "object")
          {
            attributes = squashProps(attributes)
            movie_width = attributes.width
            movie_height = attributes.height

            if (typeof attributes.autoplay=="boolean")
              autoplay_value = attributes.autoplay.toString()
            else if (typeof attributes.autoplay=="string")
              autoplay_value = attributes.autoplay

            if (typeof attributes.controller=="boolean")
              controller_value = attributes.controller.toString()
            else if (typeof attributes.controller=="string")
              controller_value = attributes.controller

            if ((movie_height==null) != (movie_width==null))
              errMsg = "Caller specified one of the height/width attributes, but not the other."
            else if (movie_height==null && target==null)
              errMsg = "Couldn't open a new popup window, because no height/width information was given."
          }
          else
            errMsg = "For .mov files, the attributes parameter should either be null or an object."
          break;          
        case "gif": case "jpeg": case "jpg":
          // Initialize default values
          picture_width = null
          picture_height = null
          if (attributes == null)
            {} //(keep default values)
          else if (typeof attributes == "object")
          {
            attributes = squashProps(attributes)
            usemap_value = attributes.usemap
            if (usemap_value != null)
            {
              usemap_value = String(usemap_value)
              if (usemap_value.charAt(0) != "#") usemap_value = "#" + usemap_value
            }
            picture_width = attributes.width
            picture_height = attributes.height
            if ((picture_height==null) != (picture_width==null))
              errMsg = "Caller specified one of the height/width attributes, but not the other."
            else if (picture_height==null && target==null)
              errMsg = "Couldn't open a new popup window, because no height/width information was given."
          }
          else
            errMsg = "For image files, the attributes parameter should either be null or an object."
          break;
        case "swf":
          // Initialize default values
          autoplay_value = "true"
          flash_version = null
          movie_width = null
          movie_height = null

          if (attributes == null)
            {} //(keep default values)
          else if (typeof attributes == "object")
          {
            attributes = squashProps(attributes)
            movie_width = attributes.width
            movie_height = attributes.height

            if (typeof attributes.autoplay=="boolean")
              autoplay_value = attributes.autoplay.toString()
            else if (typeof attributes.autoplay=="string")
              autoplay_value = attributes.autoplay

            if (attributes.version) flash_version = attributes.version

            if ((movie_height==null) != (movie_width==null))
              errMsg = "Caller specified one of the height/width attributes, but not the other."
            else if (movie_height==null && target==null)
              errMsg = "Couldn't open a new popup window, because no height/width information was given."
          }
          else
            errMsg = "For .swf files, the attributes parameter should either be null or an object."
          break;
        case "dcr":
          // Initialize default values
          movie_width = null
          movie_height = null

          if (attributes==null)
            {} //(keep default values)
          else if (typeof attributes=="object")
          {
            attributes = squashProps(attributes)
            movie_width = attributes.width
            movie_height = attributes.height
            if ((movie_height==null) != (movie_width==null))
              errMsg = "Caller specified one of the height/width attributes, but not the other."
            else if (movie_height==null && target==null)
              errMsg = "Couldn't open a new popup window, because no height/width information was given."
          }
          else
            errMsg = "For .dcr files, the attributes parameter should either be null or an object."
          break;
        case "class":
          // Initialize default values
          movie_width = null
          movie_height = null

          if (attributes==null)
            {} //(keep default values)
          else if (typeof attributes=="object")
          {
            attributes = squashProps(attributes)
            movie_width = attributes.width
            movie_height = attributes.height
            archive = attributes.archive
            if ((movie_height==null) != (movie_width==null))
              errMsg = "Caller specified one of the height/width attributes, but not the other."
            else if (movie_height==null && target==null)
              errMsg = "Couldn't open a new popup window, because no height/width information was given."
          }
          else
            errMsg = "For .class files, the attributes parameter should either be null or an object."
          break;
        case "mp3":
          // Initialize default values
          autoplay_value = "true"
          controller_value = "false"
          movie_width = null
          movie_height = null

          if (attributes == null)
            {} //(keep default values)
          else if (typeof attributes == "object")
          {
            attributes = squashProps(attributes)
            movie_width = attributes.width
            if (movie_width != null) {
              controller_value = "true"
              movie_height = 16
            }

            if (typeof attributes.autoplay=="boolean")
              autoplay_value = attributes.autoplay.toString()
            else if (typeof attributes.autoplay=="string")
              autoplay_value = attributes.autoplay

            if (movie_width==null && target==null)
              errMsg = "Couldn't open a new popup window, because no width information was given."
          }
          else
            errMsg = "For .mp3 files, the attributes parameter should either be null or an object."
          break;          
        case "ggb":
          if (attributes==null)
            errMsg = "For .ggb files, the attributes parameter should be an object specifying (at least) 'width' and 'height'."
          else if (typeof attributes=="object")
          {
            attributes = squashProps(attributes)
            movie_width = attributes.width
            movie_height = attributes.height
            //archive = attributes.archive || "http://www.geogebra.org/webstart/geogebra.jar"
			archive = attributes.archive || "http://content.class.com/c3_content/package_common/media/geogebra.jar"
            if ((movie_height==null) || (movie_width==null))
              errMsg = "For .ggb files, the attributes parameter should specify (at least) 'width' and 'height'."
          }
          else
            errMsg = "For .ggb files, the attributes parameter should be an object specifying (at least) 'width' and 'height'."
          break;
      } // end switch
    }
    else
      errMsg = "The function does not support files of this type."
  } // end if (errMsg=="")

  if (errMsg != "")
  {
    playMedia_error(filename, errMsg)
    return
  }

  // 4. Use default values for any undefined parameters
  // --------------------------------------------------
  if (serverOnly==null) serverOnly = false
  if (target==null) closeDoc = true
  if (closeDoc==null) closeDoc = (using_yuna && target==CCFS.aux1)

  // 5. Get the URL for the media file
  // ---------------------------------
  if (typeof locn_ref=="string") { // path relative to course root
    if (using_yuna)
      var server_media_folder = URLglue(folderURL(CCFS), locn_ref)
    else
      playMedia_error(filename, "Can't figure out where the \"" + locn_ref + "\" folder is.  It is supposed to be relative to the Course Root; but where is the Course Root?")
  }
  else if (IsWindow(locn_ref)) // self (window object of caller)
    server_media_folder = folderURL(locn_ref) + "media/"
  else if (locn_ref.length == 1) // [self] (window object of caller, in an array)
    server_media_folder = folderURL(locn_ref[0])
  else // [self, path_relative_to_document]
    server_media_folder = URLglue(folderURL(locn_ref[0]), locn_ref[1])

  if (serverOnly || !useCD)
  {
    // Get the file from the server
    base = server_media_folder
  }
  else
  {
    // Get the file from the CD
    var server_course_root = folderURL(CCFS)
    var media_target_course = CCFS.targetCourse(server_media_folder)
    if (media_target_course.toLowerCase() != CCFS.STARTUP_PARMS.course.toLowerCase())
    {
      // The media is in a different course than the "home" course, which means it's not in the CDRoot folder.
      // Instead, it's in a "sister" folder of CDRoot, which should have the same name as media_target_course.
      var cyclops_folder_url = server_course_root.substr(0, server_course_root.length-CCFS.STARTUP_PARMS.course.length-1)
      if (server_media_folder.substr(0,cyclops_folder_url.length) != cyclops_folder_url)
      {
        playMedia_error(filename, "Can't figure out how to translate media path (" + server_media_folder + ") into a path on the CD.")
        return
      }
      else
        base = URLglue(CDRoot, "../") + server_media_folder.substr(cyclops_folder_url.length)
    }
    else
    {
      // The media is in the CDRoot folder.
      // Chop the beginning off of server_media_folder, leaving a relative path:
      var rel_path = server_media_folder.substring(server_course_root.length)
      base = CDRoot + rel_path
    }
  }
  if (base.substring(base.length-1) != "/") base += "/"
  if (navigator.userAgent.indexOf("Safari") != -1  && media_type=="mov")
    media_url = base + filename_proper + qstring // "unescaped" URL won't work in Safari QuickTime plugin
  else
    media_url = unescape(base + filename_proper) + qstring // "escaped" URL won't work in some plugins (e.g. Win IE Flash)

  if (media_url.indexOf("'")==-1)
    q_media_url = "'" + media_url + "'"
  else if (media_url.indexOf('"')==-1)
    q_media_url = '"' + media_url + '"'
  else
  {
    // Yikes! This URL contains BOTH kinds of quotes!
    playMedia_error(filename, "Can't handle the quotes in this URL:\n" + media_url)
    return
  }
  //===================================== DEBUGGING ==============================
  // alert("Gonna play:\n" + media_url)
  //=================================== END DEBUGGING ============================

  // 6. Generate the HTML to write to the target
  // -------------------------------------------
  switch (media_type)
  {
    case "gif": case "jpeg": case "jpg":
      html = "<"+"img src=" + q_media_url
      if (picture_width != null) html += " width='" + picture_width + "' height='" + picture_height + "'"
      if (usemap_value != null) html += " usemap='" + usemap_value + "'"
      if ((misc_attributes=stripProps(attributes,new Array("src","width","height","usemap")))!=null) html+=buildAttrString(misc_attributes)
      html += ">\n"
      if (target==null) //(i.e., if new popup)
      {
        window_width = picture_width + 10
        window_height = picture_height + 70
      }
      break
    case "mov": case "mp3":
      if (using_yuna && target == CCFS.aux1)
      {
        // Plays in hidden frame
        autoplay_value = "true"
        controller_value = "false"
      }
      if (movie_width==null)
      {
        // Assume it's an audio clip.  Use Apple-recommended width/height:
        movie_width = 16
        movie_height = 16
      }
      else if (target==null) //(i.e., if new popup)
      {
        window_title = "Movie Window"
        window_width = movie_width + 10
        window_height = movie_height + 70
      }
      
      // The following is according to the Apple Quicktime website:
      // http://developer.apple.com/quicktime/compatibility.html
      html = "<"+"OBJECT CLASSID='clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B' WIDTH='" + movie_width +"' HEIGHT='" + movie_height + "' CODEBASE='http://www.apple.com/qtactivex/qtplugin.cab'>\n"
      html += "<"+"PARAM name='SRC' VALUE=" + q_media_url + ">\n"
      html += "<"+"PARAM name='AUTOPLAY' VALUE='" + autoplay_value + "'>\n"
      html += "<"+"PARAM name='CONTROLLER' VALUE='" + controller_value + "'>\n"
      if ((misc_attributes=stripProps(attributes,new Array("src","width","height","autoplay","controller","classid","codebase","type","pluginspage")))!=null)
        html += buildParamTags(misc_attributes)
      // Note: Use "video/quicktime" as the type, even for mp3 audio.  Other values won't work in Firefox.
      html += "<"+"EMBED SRC=" + q_media_url + " WIDTH='" + movie_width + "' HEIGHT='" + movie_height +"' AUTOPLAY='" + autoplay_value + "' CONTROLLER='" + controller_value + "' type='video/quicktime' PLUGINSPAGE='http://www.apple.com/quicktime/download/'" + buildAttrString(misc_attributes) + ">\n"
      html += "<"+"/EMBED>\n"
      html += "<"+"/OBJECT>\n"
      break
    case "swf":
      if (target==null) //(i.e., if new popup)
      {
        window_title = "Movie Window"
        window_width = movie_width + 10
        window_height = movie_height + 70
      }
      html = "<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000' codebase='http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab"
      if (flash_version) html += "#version=" + escape(flash_version)
      html += "'"
      if (movie_width) html += " WIDTH='" + movie_width + "'"
      if (movie_height) html += " HEIGHT='" + movie_height + "'"
      html += ">\n"
      html += "<"+"PARAM name='movie' VALUE=" + q_media_url + ">\n"
      html += "<"+"PARAM name='SRC' VALUE=" + q_media_url + ">\n"
      html += "<"+"PARAM name='PLAY' VALUE='" + autoplay_value + "'>\n"
      if ((misc_attributes=stripProps(attributes,new Array("src","width","height","movie","play","autoplay","classid","codebase","version","type","pluginspage")))!=null)
        html += buildParamTags(misc_attributes)
      html += "<"+"EMBED SRC=" + q_media_url + " QUALITY='high' pluginspage='http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash' type='application/x-shockwave-flash' PLAY='" + autoplay_value + "'"
      if (movie_width) html += " WIDTH='" + movie_width + "'"
      if (movie_height) html += " HEIGHT='" + movie_height + "'"
      if (misc_attributes) html += buildAttrString(misc_attributes)
      html += ">\n"
      html += "<"+"/EMBED>\n"
      html += "<"+"/OBJECT>\n"
      break
    case "dcr":
      if (target==null) //(i.e., if new popup)
      {
        window_title = "Movie Window"
        window_width = movie_width + 10
        window_height = movie_height + 70
      }
      html = "<"+"EMBED SRC=" + q_media_url + " type='application/x-director'"
      if (movie_width) html += " WIDTH='" + movie_width + "'"
      if (movie_height) html += " HEIGHT='" + movie_height + "'"
      if ((misc_attributes=stripProps(attributes,new Array("src","width","height","type")))!=null) html += buildAttrString(misc_attributes)
      html += "><"+"/EMBED>\n"
      break
    case "class":
      if (target==null) //(i.e., if new popup)
      {
        window_title = "Movie Window"
        window_width = movie_width + 10
        window_height = movie_height + 70
      }
      q_char = q_media_url.substr(0,1) //(single or double quote)
      path_rex = /^(.*)\/([^\/]+)$/
      matches = path_rex.exec(q_media_url)
      q_base = matches[1] + q_char
      q_filename = q_char + matches[2]
      // The <applet> tag is deprecated in favor of <object>.  However, current versions of IE and Safari
      // won't do applets with <object>; so use the deprecated form instead!
      html = "<"+"applet codebase=" + q_base + " code=" + q_filename
      if (movie_width) html += " WIDTH='" + movie_width + "'"
      if (movie_height) html += " HEIGHT='" + movie_height + "'"
      if (archive) html+= " archive='" + archive + "'"
      if ((misc_attributes=stripProps(attributes,new Array("codebase","code","width","height","type","archive")))!=null) html += buildAttrString(misc_attributes)
      html += ">\n"
      if (misc_attributes) html += buildParamTags(misc_attributes)
      html += "You need a Java-enabled browser to see this applet.\n"
      html += "<"+"/applet>\n"
      break;
    case "ggb":
      if (target==null) //(i.e., if new popup)
      {
        window_title = "GeoGebra Window"
        window_width = movie_width + 10
        window_height = movie_height + 70
      }
      q_char = q_media_url.substr(0,1) //(single or double quote)
      path_rex = /^(.*)\/([^\/]+)$/
      matches = path_rex.exec(q_media_url)
      q_base = matches[1] + q_char
      q_filename = q_char + matches[2]
      // The <applet> tag is deprecated in favor of <object>.  However, current versions of IE and Safari
      // won't do applets with <object>; so use the deprecated form instead!
      html = "<"+"applet name='ggbApplet' code='geogebra.GeoGebraApplet' codebase=" + q_base
      if (movie_width) html += " WIDTH='" + movie_width + "'"
      if (movie_height) html += " HEIGHT='" + movie_height + "'"
      if (archive) html+= " archive='" + archive + "'"
      if ((misc_attributes=stripProps(attributes,new Array("codebase","code","width","height","type","archive","filename")))!=null) html += buildAttrString(misc_attributes)
      html += ">\n"
      html += "<param name='filename' value=" + q_filename + ">\n"
      html += buildParamTags(misc_attributes,{framePossible:false, showResetIcon:true, enableRightClick:false, showMenuBar:false, showToolBar:false, showToolBarHelp:false, showAlgebraInput:false})
      html += "Sorry, the GeoGebra Applet could not be started.  Please make sure that Java 1.4.2 (or later) is installed and active in your browser (<"+"a href=\"http://java.sun.com/getjava\" target=\"_blank\">Click here to install Java now</a>)\n"
      html += "<"+"/applet>\n"
      break;
    default:
      playMedia_error(filename, "Internal error: this media type IS supported, but no handler was found!")
      return
  }

  // 7. Open a popup window if necessary
  // -----------------------------------
  if (target==null)
  {
    // Redefine the "target" variable
    var features = "height=" + window_height + ",width=" + window_width + ",status=yes,scrollbars=yes,resizable=yes,toolbar=no"
    target = window.open("", "", features)

    // Add to the "html" variable
    var header = "<"+"html>\n"
    header += "<"+"head><"+"title>" + window_title + "<"+"/title><"+"/head>\n"
    header += "<"+"body bgcolor='white' marginwidth='0' marginheight='0' topmargin='0' leftmargin='0'>\n"
    header += "<"+"center>\n"
    var footer = "<"+"form>\n"
    footer += "<"+"INPUT TYPE='button' name = 'close' value='Close window' onclick = 'self.close()'>\n"
    footer += "<"+"/form>\n"
    footer += "<"+"/center>\n"
    footer += "<"+"/body>\n"
    footer += "<"+"/html>\n"
    html = header + html + footer
  }
  else if (closeDoc)
  {
    // Surround with HTML tags for good measure
    html = "<"+"html>\n" + html + "<"+"/html>\n"
  }

  // 8. Write to the target
  // ----------------------
  if (IsArray(target))
  {
    // target[0] is a window reference; target[1] is the ID of a <div> in that window.
    divWindow = target[0]; divID = target[1]
    if (document.layers)
    {
       // This browser supports layers; treat the <div> like a window object:
      theDiv = divWindow.document.layers[divID]
      if (theDiv)
      {
        theDiv.document.open();
        theDiv.document.write(html);
        theDiv.document.close();
      }
    }
    else if (document.all)
    {
      // This browser supports an MSIE-like DOM
      theDiv = divWindow.document.all[divID]
      if (theDiv) theDiv.innerHTML = html
    }
    else if (document.getElementById)
    {
      // This browser supports getElementByID
      theDiv = divWindow.document.getElementById(divID)
      if (theDiv) theDiv.innerHTML = html
    }
    if (theDiv==null)
    {
      playMedia_error(filename, "Could not find a <div> with ID '" + divID + "' inside the specified window.")
      return
    }
  }
  else
  {
    // target is a window reference (possibly to newly-opened popup)
    target.document.write(html)
    if (closeDoc) target.document.close()
  }
  return
}
//------------------------------------------------------------------------------------------------
function IsWindow(item)
{
  // Performs a rough test to determine whether the passed item is a reference to a window object.
  // If item is a window object, the function returns true.
  // It item is not a window object, the function *almost* always returns false, but it's possible
  // to fool it by passing any object which has a "window" property equal to the object itself.
  if (item==null)
    return false
  else
    return (item.window==item) // This syntax works, even if item's not an object
}
//------------------------------------------------------------------------------------------------
function playMedia_error(filename, errMsg)
{
  var msg = "The PlayMedia function was unable to process the file, \"" + filename + "\"."
  msg += "\nReason: " + errMsg
  alert(msg)
}
//------------------------------------------------------------------------------------------------
function squashProps(theObject)
{
  // Returns a copy of theObject in which all the property names have been forced to lower case
  var newObject = new Object()
  for (var prop in theObject) newObject[prop.toLowerCase()] = theObject[prop]
  return newObject
}
//------------------------------------------------------------------------------------------------
function playAudio(locn_ref, filename)
{
  // Loads and plays an audio file in the "aux1" frame
  var using_yuna = (typeof CCFS=="object" && (IsWindow(CCFS)) && CCFS != self)
  if (using_yuna)
    playMedia(CCFS.aux1, locn_ref, filename)
  else
    alert("playAudio can't play this audio, because there is no hidden frame to play it in.")
}
//------------------------------------------------------------------------------------------------
function playServerAudio(locn_ref, filename)
{
  // Loads and plays a "server only" audio file in the "aux1" frame
  var using_yuna = (typeof CCFS=="object" && (IsWindow(CCFS)) && CCFS != self)
  if (using_yuna)
    playMedia(CCFS.aux1, locn_ref, filename, null, true)
  else
    alert("playServerAudio can't play this audio, because there is no hidden frame to play it in.")
}
//------------------------------------------------------------------------------------------------
function stopAudio()
{
  var using_yuna = (typeof CCFS=="object" && (IsWindow(CCFS)) && CCFS != self)
  if (using_yuna) {
    // Loads dummy contents into the "aux1" frame, thus stopping any sound that may be playing there.
    CCFS.aux1.document.write("<html></html>")
    CCFS.aux1.document.close()
  }
}
//------------------------------------------------------------------------------------------------
function URLglue(base, rel_path)
{
  /*
    Builds and returns a URL to the resource indicated by base and rel_path.  Parameters:
      base     - A relative or absolute URL that indicates a folder.  A trailing "/" is optional.
                 May also be blank, in which case the returned URL is the same as rel_path.
      rel_path - A relative URL.  May also be blank, in which case the returned URL is the same
                 as base (possibly with a trailing "/" appended).
    If there are no errors in the parameters, the function returns a URL for rel_path which assumes
    rel_path is relative to base.  If base is an absolute URL, the returned URL will also be absolute.
    If an error is found in the parameters (for example, if rel_path can't be interpreted as relative
    to base), an alert is displayed, and the function returns null.

    The function assumes there are no leading or trailing spaces in base or in rel_path.
  */
  var error, match, dds_prefix
  var old_base = base
  var old_rel_path = rel_path
  var isRoot = base.charAt(0)=="/" || base.indexOf("://") != -1
  if ((match=/^(\.\.\/)+/.exec(base))!=null)
  {
    dds_prefix = match[0] // leading occurrence(s) of "../"
    base = base.substring(dds_prefix.length) // strip all leading "../"'s from base
  }
  else dds_prefix = ""
  if (base.charAt(base.length-1) == "/") base = base.substring(0, base.length-1) // Strip trailing "/", if any
  if (rel_path.charAt(0)=="/" || rel_path.indexOf("://") != -1) error = true // rel_path is an absolute URL
  var done = (rel_path.substring(0,3) != "../" || base.length == 0 || error)
  while (!done)
  {
    rel_path = rel_path.substring(3) // Chop off first 3 characters
    base = base.substring(0, base.lastIndexOf("/")) // Chop trailing directory and the preceding "/"
    if (base.length == 0)
    {
      done = true
      if (isRoot && rel_path.substring(0,3) == "../") error = true
    }
    else if (/(\w|[:$-.+!'()])$/.test(base)==false) // if base does not end with a colon or a valid filename character
    {
      done = true
      error = true
    }
    else if (rel_path.substring(0,3) != "../")
      done = true
  }
  if (error)
  {
    alert("Invalid arguments passed to URLglue:\n" + old_base + "\n" + old_rel_path)
    return null
  }
  else
  {
    if (base.length > 0 || isRoot) base += "/" //Put the slash back on
    return dds_prefix + base + rel_path
  }
}
//------------------------------------------------------------------------------------------------
function folderURL(theWindow)
{
  // Returns a string which is a URL pointing to the ENCLOSING FOLDER of the
  // location of theWindow.  The returned string includes the trailing "/".
  // DON'T use theWindow.location here.  There are known problems with trying
  // to read the location object in Windows IE version 6.
  var url = theWindow.document.URL.replace(/\\/g, "/")  // Change all "\" to "/"
  if (url.indexOf("#")!=-1) url = url.substring(0,url.lastIndexOf("#")) // Chop off hash (if any)
  if (url.indexOf("?")!=-1) url = url.substring(0,url.lastIndexOf("?")) // Chop off querystring (if any)
  return url.substring(0,url.lastIndexOf("/")+1)
}
//-------------------------------------------
function IsArray(theItem)
{
  // Returns true if theItem is an array
  if (theItem!=null && typeof theItem=="object" && theItem.constructor!=null && (theItem.constructor==Array || theItem.constructor.toString().indexOf("function Array(")>-1))
    return true
  else if (inSafari())
    // In Safari, window1.Array != window2.Array, so the test (theItem.constructor==Array) might fail, even if theItem is an array.
    return IsProbablyArray(theItem)
  else
    return false
}
//------------------------------------------------------------------------------------------------
function IsProbablyArray(item)
{
  // Performs a very rough test to determine whether the passed item is an array.
  // To do this, it checks that the item is an object which has a numeric "length" property,
  // and may have numeric-indexed properties from 0 to length-1 (provided length > 0), and has
  // no other enumerable properties.
  //
  // The function returns true or false.  It returns a "false positive" if it receives
  // a non-array object that happens to have the abovementioned characteristics.  It
  // returns a "false negative" if it receives an array to which the user has also assigned
  // additional properties

  if (item==null || typeof item != "object" || typeof item.length != "number")
    return false
  else
  {
    for (var prop in item)
    {
      if (!isNaN(prop)) // It's numeric
        {if (parseInt(prop) < 0 || parseInt(prop) >= item.length) return false}
      else if (prop != "length")
        return false
    }
    // Passed all tests.
    return true
  }
}
//----------------------------------------------------------------------------------------
function inSafari()
{
	//Returns true if the user is using the Safari web browser.
	return navigator.userAgent.indexOf("Safari") != -1
	
}
//----------------------------------------------------------------------------------------
function stripProps(theObject, propArray)
{
  /*
    Returns a copy of an object from which selected properties have been removed.
    Parameters:
      theObject - The original object from which the properties are to be stripped.
                  May also be null, in which case the function returns null.
      propArray - An array of strings representing the names of properties which are
                  to be removed.  The names are treated case-insensitively.  If a given
                  name is not represented in theObject, it's ignored.
    
    Note: if the resulting object has no properties, the function returns null (not an empty object).
  */
  var propName, propName_lc, found, i
  var hasProps = false
  if (theObject==null) return null
  var newObject = new Object()
  for (propName in theObject) {
    propName_lc = propName.toLowerCase()
    found = false
    for (i=0; i<propArray.length && !found; i++) {
      if (propArray[i].toLowerCase()==propName_lc) found=true
    }
    if (!found) {
      newObject[propName] = theObject[propName]
      hasProps = true
    }
  }
  if (hasProps)
    return newObject
  else
    return null
}
//----------------------------------------------------------------------------------------
function buildAttrString(attrObj)
{
  /*
    Uses the names and values of properties in attrObj to build a string of attribute/value pairs
    suitable for insertion into an HTML/XML tag.  If attrObj is null or has no enumarable properties,
    the function returns an empty string.  Otherwise, it returns a space-delimited list of attribute/value
    pairs, with a leading space as the string's first character.  Example: if attrObj is {x:120,y:'blue'},
    the output string will look like: ' x="120" y="blue"'
  */
  var outString = ""
  var propName
  if (attrObj!=null) {
    for (propName in attrObj) outString += ' ' + propName + '="' + htmlencode(String(attrObj[propName])) + '"'
  }
  return outString
}
//---------------------------------------------------------------------------------
function buildParamTags(attrObj, defaultValues)
{
  /*
    Uses the names and values of properties in attrObj and defaultValues to build a series of <param> tags suitable for
    insertion in (for example) an HTML <object> or <applet> element.
    Parameters:
      attrObj       - null, or an object representing name/value pairs that should go into the <param> elements.
      defaultValues - null, or an object representing name/value pairs that should go into the <param> elements.

    The resulting <param> tags will represent the UNION of the properties in attrObj and defaultValues. In cases
    where the same property (the same <param> name) is found in both attrObj and defaultValues, the value
    in attrObj is used.  (NOTE: Property names are treated case-insensitively for this purpose.)

    If both attrObj and defaultValue are null or neither has any enumerable properties, the function returns an
    empty string.  Otherwise, it returns a string consisting of a newline-delimited sequence of <param> elements,
    one per unique enumerable property.  Example, if attrObj is {x:120, y:'blue'} and defaultValues is {y:'green', z:false},
    the output string will look like:
      <param name="x" value="120">
      <param name="y" value="blue">
      <param name="z" value="false">
  */
  var outString = ""
  var propName, propName2, defaultClone
  if (attrObj==null && defaultValues==null) {
    return ""
  }
  else if (attrObj==null || defaultValues==null) {
    var oneObject = attrObj || defaultValues
    for (propName in oneObject) outString += '<param name="' + propName + '" value="' + htmlencode(String(oneObject[propName])) + '">\n'
  }
  else {
    defaultClone = new Object()
    for (propName2 in defaultValues) defaultClone[propName2] = defaultValues[propName2]
    for (propName in attrObj) {
      outString += '<param name="' + propName + '" value="' + htmlencode(String(attrObj[propName])) + '">\n'
      for (propName2 in defaultClone) {
        if (propName.toLowerCase()==propName2.toLowerCase()) {
          delete defaultClone[propName2]
          break
        }
      }
    }
    for (propName2 in defaultClone) {
      outString += '<param name="' + propName2 + '" value="' + htmlencode(String(defaultClone[propName2])) + '">\n'
    }
  }
  return outString
}
//---------------------------------------------------------------------------------
function htmlencode(theText)
{
  // Returns a version of theText in which certain characters have been replaced by HTML character entities.
  theText = theText.replace(/&/g, "&amp;")
  theText = theText.replace(/</g, "&lt;")
  theText = theText.replace(/>/g, "&gt;")
  theText = theText.replace(/"/g, "&quot;")
  theText = theText.replace(/\n/g, "<br>")
  return theText
}
