// cognopod - DHTML software for cognitive psychology experiments
// (C) 2005-2006 Karl G. D. Bailey
// http://www.growlers.org/
// this is the basic and default function library
// it must be loaded in all application instances

// check for browser capabilities
nn4 = (document.layers) ? true:false
ie4 = (document.all) ? true:false
ie5 = (document.getElementById) ? true:false
nn6 = (document.addEventListener) ? true:false

// capture key press for NN 4.x
// pointless, given that the rest of this code won't work in NN 4.x
if (nn4) document.captureEvents(Event.KEYPRESS);
// capture key press for everyone else
document.onkeypress = doKey;

// kill the backspace key in internet explorer
// hackerrific.
function good_boy_stay() {
   if (window.event && window.event.keyCode == 8) { // try to cancel the backspace
      window.event.cancelBubble = true;
      window.event.returnValue = false;
      return false;
   }
// alert("foo!");
}

// montior for key press to stop backspace in IE. (really redundant)
document.onkeydown = good_boy_stay;

// initialize this_dir variable
var this_dir = "";
var this_list = 0;
// a variable for has an error already been caught?
var caught_error = 0;

// variable for random subject number
var sub_id = "";

// function to load experiment and summary files from a directory!!!!
function load_expt() {
// set sub directory
this_dir = location.search.substring(1,location.search.length) + "/";

// make sure that only subdirectories can be accessed by this program
// otherwise malicious data could be obtained from elsewhere (i suppose)

// set up test regex
// regex_dir = RegExp("^[a-zA-Z0-9_/]+(/+[0-9]+)?/$", "i");
var regex_dir = /^([a-zA-Z0-9_\/]+)\+?([0-9]+)?/;
var dub_slash = /\/+/g;
var temp_dir = "";

var dir_match = this_dir.match(regex_dir);
debug_out("this_dir: " + dir_match[1]);

// this the search query is only composed of letters, numbers, and the underscore
if (this_dir.match(regex_dir)) {
// do nothing else
temp_dir = dir_match[1] + "/";
this_dir = temp_dir.replace(dub_slash, "/");
if (this_dir == "/") {
   this_dir = "";
}
if (dir_match[2]) {
   this_list = dir_match[2];
}

} else {
// go with the default directory
this_dir = "";
}

try {
// get the summary js file
get_script(this_dir + "summary.js");
// get the expt js file
get_script(this_dir + "expt.js");
	} catch(e) {
// warn user about what just happened
caught_error = 1;
message_out("Failed to load summary or experiment files. Check filenames or URL and try again.");
// don't overwrite this message
	} finally {
// random subject number
var characters = "0123456789abcdef";
    for (ch = 1; ch <= 6; ch++) {
        var index = Math.floor(Math.random() * characters.length);
        sub_id = sub_id + characters.charAt(index);
    }

debug_out(sub_id);
document.dataform.subj_num.value = sub_id;
// initialize the experiment!!!!
first_run();
	}

}

// this function gets friendly javscripts
function get_script(url)
	{
// get the head tag node
	var get_head_tag = document.getElementsByTagName('head')[0];
// create a new scripty node
	this_script = document.createElement('script');
// ... for javascript
	this_script.setAttribute('type', 'text/javascript');
// put that node under the head tag node
	get_head_tag.appendChild(this_script);
// create a new request instancec
	var scriptify = (window.ActiveXObject) ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
// open a channel and get the javascript
	scriptify.open('GET', url, false);
// send nothing back
        scriptify.send(null);
// and insert the text into the new scripty node
	this_script.text = scriptify.responseText;
}

// error handler:
window.onerror = handle_error; // safety net to trap all errors

function handle_error(message, URI, line) {
        // alert the user that this page may not respond properly
if (caught_error == 0) {
        message_out("This experiment did not load properly. You will not be able to run this experiment. [" + message + "]");
} else {
        caught_error = 0;
}

	return false; // this will allow the default message to be passed for debugging
}

function doKey(evt) {


// disable almost all buttons
// in firefox (and, to some degree, in IE (for instance the spacebar))
  foovt = evt||event;
  if (foovt.preventDefault) {
    foovt.preventDefault(); // The W3C DOM way
  } else {
    foovt.returnValue = false; // The IE way
  }


// function to identify the key pressed
// for firefox and mozilla (?) browsers
   if ((navigator.product)&&(navigator.product.toLowerCase()=="gecko")) {
      whichASC = (evt.charCode) ? evt.charCode : ((evt.keyCode) ? evt.keyCode : ((evt.which) ? evt.which : 0));
// otherwise, for IE6
    } else {
      whichASC = document.layers ? e.which : document.all ? event.keyCode : document.getElementById ? evt.charCode : 0;

    }
// lower case for everything
   whichKey = String.fromCharCode(whichASC).toLowerCase();

// launch function for deciding what to do based on key
// this needs to be defined in the user file
   response(whichKey);

}

// this adds a function to arrays so that I can shuffle them
// shazam!
Array.prototype.shuffle = function(){
// the length of the array
  var shuffle_length = this.length;
// holds a random number
  var shuffle_rand;
// holds a temporary variable, while we cycle
  var shuffle_temp;
// do this the number of times equla to the elements in the array
  for(var i = 0; i < shuffle_length; i++){
// pick a random spot in the array
    shuffle_rand = Math.floor(Math.random()*shuffle_length);
// now we cycle information
// first, put this element's data into the temp variable
    shuffle_temp = this[i];
// now put a random element into this element's slot
    this[i] = this[shuffle_rand];
// then put the temp variable in the random element's slot
    this[shuffle_rand] = shuffle_temp;
  }
}


//this outputs information to the debugger window, without deleting current text
function debug_out(info) {
    document.getElementById("infos").innerHTML += info + "<br />";
}


// clear the debugger window
function debug_clr() {
    document.getElementById("infos").innerHTML = "<a href='javascript:void(debug_clr())'>clear debug</a><br />";
}

//this outputs information to the message window, and deletes all current information
function message_out(info) {
    document.getElementById("message").innerHTML = info + "<br />";
// unless we're deleting all text, bring the message window to the front
    if (info != "") {
       show_message();
    }
}

//this outputs information to the message window, without deleting current text.
function multi_out(info) {
    document.getElementById("message").innerHTML += info + "<br />";
// unless we're deleting all text, bring the message window to the front
    if (info != "") {
       show_message();
    }
}


// this outputs information to the textarea in the data window. This won't delete anything.
function data_out(info) {
    document.dataform.dataarea.value += info + "\n";
}

// clear text area in the data window.
function data_clr(info) {
var answer = confirm ("This will erase all of the data in the box below!\nDo you want to continue?\n(Choose 'Cancel' to keep your data.)")
if (answer) {
document.dataform.dataarea.value = "";
} else {

}
}

// display text in the stimulus display window
function tachi_out(info, erase, color) {
// if we are supposed to erase the background
// (the text will be deleted as a matter of course)
    if (erase == "true") {
// set image to null URL
       document.getElementById("expt").style.backgroundImage = "url('')";
    }
// set color for this stimulus
    document.getElementById("expt").style.color = color;
// set text content.
    document.getElementById("tachi").innerHTML = info;
}


// display image in the stimulus display window
// it'll be centered according to the current style
function image_out(info, erase) {
// if we are supposed to erase the background
// (the image will, of course, be erased)
    if (erase == "true") {
// set the text string to null
       document.getElementById("tachi").innerHTML = "";
    }
// set the background image to the content - I think that this can be either a relative or an absolute URL
    document.getElementById("expt").style.backgroundImage = "url(" + this_dir + info + ")";
}


// set up variables for the entire program
// these can be reset during first run. Don't reset them here, or your other programs may not work.
// version of program
var version = "0.82";
// how often the clock is checked for timeouts.
var clock_check = 50;
// time count starts with what?
var ms = 0;
// false start watcher - 0 means no delay has been started
var state = 0;
// prevent starting before starting - false means trial is not ongoing
var isready = false;
// how much delay before stimulus presentation?
var msDelay = 0;
// initialize the number of critical stimuli - this will be set based on stimulus xml file
var stim_length = 0;
// initialize the object that holds the stimulus xml file
var stim_doc = "";
// initialize the object that holds the stimulus tree
var stimulus = "";
// initialize the number of trial stimuli - this will be set based on the trial xml file
var trial_length = 0;
// initialize the object that holds the trial xml file
var trial_doc = "";
// initialize the object that holds the trial tree
var trial = "";
// set default timeout before the stimulus presentation ends
var timeout = 10000;
// initialize array for holding preloaded images from the stimulus xml file
var stimulus_img = new Array;
var question_img = new Array;
// initialize array for holding preloaded images from the trial xml file
var trial_img = new Array;

// set all of the current stimulus variables
  var stim_name = "";
  var stim_content = "";
  var stim_type = "";
  var stim_c_one = "";
  var stim_c_two = "";
  var stim_c_three = "";
  var stim_correct = "";
  var stim_incorrect = "";
  var stim_delay = "";
  var stim_timeout = "";
  var stim_record = "";
  var stim_align = "";
  var stim_erase = "";
  var stim_color = "";
  var stim_size = "";
  var stim_question = "";
  var stim_qcorr = "";
  var stim_qinc = "";
  var stim_qname = "";
  var stim_q_one = "";
  var stim_q_two = "";
  var stim_q_three = "";
  var stim_qtype = "";
  var stim_roi = new Array;

// set loop variables for experiment
var trial_loop = 0;
var stim_loop = 0;

// are we still running the experiment? apparently, 'cause this variable is set to true
var oktogo = false;

// set variable for current regular expressions for correct and incorrect responses
var regex_c = "";
var regex_i = "";
// if there are no appropriate responses for correct or incorrect, certain processing will need to be disallowed
// these variables do that
var allow_c = false;
var allow_i = false;

var stim_order = new Array;

//this initializes the stimulus cycle
function wait_for_it(how_long){

//if the cycle hasn't been started ...
  if(!isready){
//clear the stopwatch...
    state = 0;
    ms = 0;
// clear message window
    message_out("")
// set isready to true (internal memory that the cycle has started...)
    isready = true;

    if (stim_erase == "true") {
// set image to null URL
       document.getElementById("expt").style.backgroundImage = "url('')";
       document.getElementById("tachi").innerHTML = "";
    }

// delay some amount of time before beginning: could be zero!
delay(how_long);
  } else {
// if the stimulus cycle has already started
// uncomment next to warn the user.
//    message_out("Already running.");
  }
}

// delay function before presenting stimulus. The last stimulus will continue to be displayed
// this adds a random amount of time to [msDelay], so use it to prevent users from guessing when the stimulus will appear
function delay(x_time){
// if a time has been specified
   if (x_time > -1) {
// start clock after that time
//      msDelay = x_time;
// use a random number between 0 and x_time ms
     msDelay = parseInt(x_time*Math.random());
   } else {
// use a random number between 500 and 2000 ms
     msDelay = parseInt(500+2000*Math.random());
   }
// start the stimulus presentation after [msDelay]
   timerID=setTimeout('start_watch()',msDelay);
}

// present the stimulus
function start_watch(){
// make sure that the delay is ongoing, and that the watch hasn't already been started
// why would we need to do this? I do not know.
   if(state == 0){
// given that it's time for stimulus presentation
// display content contingent on type
// let's start wth text stimuli
   if (stim_type == "text") {
// set text alignment
      document.getElementById("expt").style.textAlign = stim_align;
// set font size
      document.getElementById("spacer").style.lineHeight = (stim_valign - stim_size) + "px";
      document.getElementById("tachi").style.lineHeight = (2 * stim_size) + "px";
      document.getElementById("tachi").style.fontSize = stim_size + "px";
// send the stimulus to the presentation window
      tachi_out(stim_content, stim_erase, stim_color);
// now, image stimuli
} else if (stim_type == "img") {
// send the stimulus to the presentation window
    image_out(stim_content, stim_erase);
}

// the next variable indicates that we've started the stimulus presentation segment
// these variable names are not straightforward, because the code has gone through about five iterations now
    state = 1;
// start the stopwatch.
// heh. start. STOPwatch.
    start_time = new Date();
// get the start time in milliseconds
    start_time.setTime(start_time.getTime() - ms);
   }
}

// this variable hold the type of response
var this_response;
var switch_num = 0;
var switch_expt = 0;
var switch_time = 0;

// this function stops the stopwatch, and then does various things conntingent on what the demands of the trial are
function stop_watch(resp_type, resp_key, handler){
// if the stop watch hasn't started, we can't really stop it, can we?
  if(state==0) {
// if the delay is greater than zero.
    if (stim_delay > 0) {
// handle false starts...
// turn off the delay timer
    clearTimeout(timerID);
// deal with false starts
     false_start();
// declare that the stimulus presentation cycle is not ongoing
    isready=false;
// if the stop watch has started
    }
  } else {
// turn off the stimulus presentation marker
    state = 0;
// get current time and update clock display in debug window.
    now = new Date();
    ms = now.getTime() - start_time.getTime();
    document.getElementById("timer").innerHTML = ms;
// special circumstance if you want to hack the ms field
// hacktacular!!!
   if (switch_expt == 1) {
      ms = switch_num;
   } else if (switch_expt == 2) {
      ms = ms + switch_num * switch_time;
   }

// stop extra button presses by setting stimulus presentation cycle marker to false
// but first, deal with some end of cycle wrap up
// given that this is really a cycle
if (isready == true) {
this_response = resp_type;
//write out some data if the stimulus calls for it
if (stim_record == "true") {
// NAME; RT; RESP; KEY; C1; C2; C3;
// NAME/t/RT/tRESP/tKEY/tC1/tC2/tC3/n
data_out(stim_name + "\t" + ms + "\t" + resp_type + "\t" + resp_key + "\t" + stim_c_one + "\t" + stim_c_two + "\t" + stim_c_three + "\t" + sub_id);
// if the stimulus doesn't call for data to be recorded
} else {

}

// handle incremeting of counters for trial and stimulus list
// this is by default increment_loop()
// it needs to be passed to stopwatch by the key press call
handler();
}
// reset readiness for next stimulus presentation cycle
isready=false;
// this calls a function from the user defined .js file that starts the next stimulus presentation cycle
prep_next();
}
}

// this displays the current time on the stopwatch every [clock_check] seconds
// so it's probably a bit vaguely named
// this is mostly for debugging, given that the debug bar is usually hidden for subjects
function display() {
// this line sets the time before the next timeout
// the default is 50 ms, so up to 50 ms could be added to values - be aware of this if precise (-ish) timing is important.
// note that the best time resolution for javascript seems to be between 10 and 11 milliseconds,
// so any value less than this (e..g 5 ms) will probably not increase resolution
// (I don't think.)
  setTimeout("display(" + timeout + ");", clock_check);
// only do sommething if the watch is running
  if (state == 1){
// right out the information to the timer
    now = new Date();
    ms = now.getTime() - start_time.getTime();
    document.getElementById("timer").innerHTML = ms;
// stop the watch if the current time is greater than or equal to the timeout value for the trial
    if (ms >= timeout) {
// this goes to the user defined function that stops the watch
// why? because the incrementing function needs to be passed by the call
// and this allows user defined functions to replace the default
         times_up();
    }
  }
}

// this function sets up an HTTP object.
function getHTTPObject() {

  var xmlhttp;
  /*@cc_on
  @if (@_jscript_version >= 5)
    try {
      xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e) {
      try {
        xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (E) {
        xmlhttp = false;
      }
    }
  @else
  xmlhttp = false;
  @end @*/
  if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
    try {
      xmlhttp = new XMLHttpRequest();
	  xmlhttp.overrideMimeType("text/xml");
    } catch (e) {
      xmlhttp = false;
    }
  }
  return xmlhttp;
}

// we need two HTTP objects: one for stimuli and one for trial
var stim_req = getHTTPObject();
var trial_req = getHTTPObject();

// we also need a holder for the final objects
var stim_doc;
var trial_doc;

// this is the handler for stimulus files
function handle_stim() {
// multi_out("stim: " + stim_req.readyState);
  if (stim_req.readyState == 4) {
    if (stim_req.responseText.indexOf('invalid') == -1) {
//       debug_out(stim_req.getAllResponseHeaders());
      stim_doc = stim_req.responseXML;
      proc_stim();
    }
  }
}

// this is the handler for trial files
function handle_trial() {
// multi_out("trial: " + trial_req.readyState);
  if (trial_req.readyState == 4) {
    if (trial_req.responseText.indexOf('invalid') == -1) {
//       debug_out(stim_req.getAllResponseHeaders());
      trial_doc = trial_req.responseXML;
      proc_trial();
    }
  }
}


// load the stimulus list XML file
function load_stim(url, handler) {
if (stim_req) {
//      multi_out(this_dir + url);
     stim_req.open("GET", this_dir + url, true);
     stim_req.onreadystatechange = handle_stim;
     stim_req.send(null);
//      var stim_doc = stim_req.responseXML;
//       handler(stim_doc, url);

} else {

}

}

// load the trial list XML file
function load_trial(url, handler) {
if (trial_req) {
//      multi_out(this_dir + url);
     trial_req.open("GET", this_dir + url, true);
     trial_req.onreadystatechange = handle_trial;
     trial_req.send(null);
//      var stim_doc = stim_req.responseXML;
//       handler(stim_doc, url);

} else {

}

}


// set up stimulus object and get the length.
// preload all images
function proc_stim () {
// declare stimulus list file loaded
         multi_out("stimulus file loaded...");
// how many stimulus tags are there?
// (the stimulus tag is the base unit for this application)
         stimulus = stim_doc.getElementsByTagName("stimulus");
// the length of the array is the number of stimuli
         stim_length = stimulus.length;
         stim_length_all = stimulus.length;
         for (var ii = 0; ii < stimulus.length; ii++) {
             if (get_tag(stimulus, ii, "qtype") == "img") {
               stim_length++;
             }
         }
// set up the counter that tracks loading
         multi_out("Loaded <span id='stimulus'>0</span> of " + stim_length + " critical stimuli...");
// loop through the stimuli
         for(var i = 0; i < stimulus.length; i++) {
// if the stimulus is an image,
             if (get_tag(stimulus, i, "type") == "img") {
// create an image holder for the preloading
                stimulus_img[i] = new Image();
// if there's an image loading error
                stimulus_img[i].onerror = error_stim;
// if the image loads properly, increment the stimulus loaded number in the text we just wrote out above
                stimulus_img[i].onload = incr_stimulus;
// OK, preload that image!
                stimulus_img[i].src = this_dir + get_tag(stimulus, i, "content");
// if it's not an image
// then it's pretty much already preloaded (from the XML file, genius)

             } else {
// so just increment the stimulus loaded number
                incr_stimulus();
             }
// now for possible question images
                if(get_tag(stimulus, i, "qtype") == "img") {
// create an image holder for the preloading
                   question_img[i] = new Image();
// if there's an image loading error
                   question_img[i].onerror = error_stim;
// if the image loads properly, increment the stimulus loaded number in the text we just wrote out above
                   question_img[i].onload = incr_stimulus;
// OK, preload that image!
                   question_img[i].src = this_dir + get_tag(stimulus, i, "question");
//                    debug_out("foof");
// if it's not an image
// then it's pretty much already preloaded (from the XML file, genius)
                }

         }
}

// this increments the stimulus loaded number for proc_stim()
function incr_stimulus () {
// i'm not error checking, so how about that
// (i don't think that users can break this, anyhow. Well, they can, but they'd have to just hit keys randomly.
// I should just turn the whole thing off until all images preload.
document.getElementById("stimulus").innerHTML++;
}

// if the image doesn't load, we probably should let everyone know
function error_stim () {
multi_out("Unable to load all stimuli. Freak out!");
}



// set up trial object and get the length.
// preload all images
function proc_trial () {
// declare trial list file loaded
         multi_out("trial schema loaded...");
// how many stimulus tags are there?
// (the stimulus tag is the base unit for this application)
         trial = trial_doc.getElementsByTagName("stimulus");
// the length of the array is the number of stimuli
         trial_length = trial.length;
// set up the counter that tracks loading
         multi_out("Loaded <span id='trial'>0</span> of " + trial.length + " trial stimuli...");
// loop through the stimuli
         for(var i = 0; i < trial.length; i++) {
// if the stimulus is an image,
             if (get_tag(trial, i, "type") == "img") {
// create an image holder for the preloading
                trial_img[i] = new Image();
// if there's an image loading error
                trial_img[i].onerror = error_stim;
// if the image loads properly, increment the stimulus loaded number in the text we just wrote out above
                trial_img[i].onload = incr_trial;
// OK, preload that image!
                trial_img[i].src = this_dir + get_tag(trial, i, "content");
// if it's not an image
// then it's pretty much already preloaded (from the XML file, genius)
             } else {
// so just increment the stimulus loaded number
                incr_trial();
             }
         }
}

// this increments the stimulus loaded number for proc_trial()
function incr_trial () {
// i'm not error checking, so how about that
// (i don't think that users can break this, anyhow. Well, they can, but they'd have to just hit keys randomly.
// I should just turn the whole thing off until all images preload.
document.getElementById("trial").innerHTML++;
}

// this function grabs tag content from the xml tree for a single stimulus item
// [this_xml] is the array of stimulus objects; [idx] is the particular object; [tag name is the tag within the object that we want content from
function get_tag(this_xml, idx, tag_name) {
// test if tag is in the tree
   if (this_xml[idx].getElementsByTagName(tag_name)[0]) {
// if the tag exists in the tree
//check if the tag contains any data
      if (this_xml[idx].getElementsByTagName(tag_name)[0].firstChild) {
// if data exists, return the string.
        return this_xml[idx].getElementsByTagName(tag_name)[0].firstChild.data;
      } else {
// otherwise return an empty string
        return "";
      }
   } else {
// if the tag doesn't exist, warn and return empty string.
      debug_out("&lt;" + tag_name + "&gt; tag missing from file.");
      return "";
   }
}

// this function grabs ROI content from the xml tree for a single stimulus item
// [this_xml] is the array of stimulus objects; [idx] is the particular object; [tag name is the tag within the object that we want content from
function get_ROI(this_xml, idx, tag_name) {
// test if tag is in the tree
   if (this_xml[idx].getElementsByTagName(tag_name)[0]) {
// if the tag exists in the tree
//check if the tag contains any data
      if (this_xml[idx].getElementsByTagName(tag_name)[0].firstChild) {
// if data exists, return the string.
     var temp_roi = new Array;
     for(var i = 0; i < this_xml[idx].getElementsByTagName(tag_name).length; i++) {
//      debug_out(this_xml[idx].getElementsByTagName(tag_name).length);
//         debug_out(this_xml[idx].getElementsByTagName(tag_name)[i].firstChild.data);
        temp_roi[i] = this_xml[idx].getElementsByTagName(tag_name)[i].firstChild.data;
     }
//       return this_xml[idx].getElementsByTagName(tag_name)[3].firstChild.data;
        return temp_roi;
      } else {
// otherwise return an empty string
        return "";
      }
   } else {
// if the tag doesn't exist, warn and return empty string.
//       debug_out("&lt;" + tag_name + "&gt; tag missing from file.");
      return "";
   }
}

// this will seem crazy, but this function doesn't actually display anything
// it used to, but then things developed
// so it's a vestigal function name
// [this_xml] is the array of stimulus objects; [idx] is the particular object
function display_stim(this_xml, order) {
// choose the particular stimulus
// if this is a stimulus
if (this_xml == stimulus) {
// get the randomized order number
// or not, you know, if it wasn't randomized
    var idx = stim_order[order];
} else {
// for trials, don't bother
    var idx = order;
}

var this_stim = this_xml[idx];

// aquire stimulus variables for this iteration
// uncomment debug lines to see what variables are set to what
// i think that everything is self explanatory here, right?
// these are global variables, by the way, so you don't have to pass the information to other functions
// see declarations above
  stim_name = this_stim.getAttribute("name");
//   debug_out("name: " + stim_name);
  stim_content = get_tag(this_xml, idx, "content");
//   debug_out("content: " + stim_content);
  stim_type = get_tag(this_xml, idx, "type");
        regex_vartest = RegExp("^(text|img|stim|quest)$", "i");
// test for correct values
        if (stim_type == "" || !stim_type.match(regex_vartest)) {
// was the error due to an empty tag or due to bizarro data
            if (!stim_type.match(regex_vartest) && stim_type != "") {
// if it's bad data, warn the user
               debug_out("Invalid type for [" + stim_name + "].");
            }
// and use the default in either case.
            stim_type = "text";
        }
//   debug_out("type: " + stim_type);
  stim_c_one = get_tag(this_xml, idx, "c_one");
        regex_vartest = RegExp("^[0-9A-Za-z]{1,4}$", "i");
        if (stim_c_one == "" || !stim_c_one.match(regex_vartest)) {
            if (!stim_c_one.match(regex_vartest) && stim_c_one != "") {
               debug_out("Invalid condition #1 for [" + stim_name + "].");
            }
            stim_c_one = "NULL";
        }
//   debug_out("c_one: " + stim_c_one);
  stim_c_two = get_tag(this_xml, idx, "c_two");
        regex_vartest = RegExp("^[0-9A-Za-z]{1,4}$", "i");
        if (stim_c_two == "" || !stim_c_two.match(regex_vartest)) {
            if (!stim_c_two.match(regex_vartest) && stim_c_two != "") {
               debug_out("Invalid condition #2 for [" + stim_name + "].");
            }
            stim_c_two = "NULL";
        }
//   debug_out("c_two: " + stim_c_two);
  stim_c_three = get_tag(this_xml, idx, "c_three");
// this is the freeform condition field.
//         regex_vartest = RegExp("^[0-9A-Za-z]{1,4}$", "i");
//         if (stim_c_three == "" || !stim_c_three.match(regex_vartest)) {
//             if (!stim_c_three.match(regex_vartest) && stim_c_three != "") {
//                debug_out("Invalid condition #3 for [" + stim_name + "].");
//             }
//             stim_c_three = "NULL";
//         }
//   debug_out("c_three: " + stim_c_three);
  stim_correct = get_tag(this_xml, idx, "correct");
// no default is needed, because a lack of content is meaningful
//   debug_out("correct: " + stim_correct);
  stim_incorrect = get_tag(this_xml, idx, "incorrect");
// no default is needed, because a lack of content is meaningful
//   debug_out("incorrect: " + stim_incorrect);
  stim_delay = get_tag(this_xml, idx, "delay");
        regex_vartest = RegExp("^[0-9]+$", "i");
        if (stim_delay == "" || !stim_delay.match(regex_vartest)) {
            if (!stim_delay.match(regex_vartest) && stim_delay != "") {
               debug_out("Invalid delay value for [" + stim_name + "].");
            }
            stim_delay = "0";
        }
//   debug_out("delay: " + stim_delay);
  stim_timeout = get_tag(this_xml, idx,"timeout");
        regex_vartest = RegExp("^[0-9]+$", "i");
        if (stim_timeout == "" || !stim_timeout.match(regex_vartest)) {
            if (!stim_timeout.match(regex_vartest) && stim_timeout != "") {
               debug_out("Invalid timeout value for [" + stim_name + "].");
            }
            stim_timeout = "1800000";
        }
//   debug_out("timeout: " + stim_timeout);
  stim_record = get_tag(this_xml, idx, "record");
        regex_vartest = RegExp("^(true|false)$", "i");
        if (stim_record == "" || !stim_record.match(regex_vartest)) {
            if (!stim_record.match(regex_vartest) && stim_record != "") {
               debug_out("Invalid record value for [" + stim_name + "].");
            }
            stim_record = "false";
        }
//   debug_out("record: " + stim_record);
  stim_align = get_tag(this_xml, idx, "textalign");
        regex_vartest = RegExp("^(center|left|right)$", "i");
        if (stim_align == "" || !stim_align.match(regex_vartest)) {
            if (!stim_align.match(regex_vartest) && stim_align != "") {
               debug_out("Invalid text alignment value for [" + stim_name + "].");
            }
            stim_align = "center";
        }
//   debug_out("textAlign: " + stim_align);
  stim_valign = get_tag(this_xml, idx, "valign");
        regex_vartest = RegExp("^[0-9]+$", "i");
        if (stim_valign == "" || !stim_valign.match(regex_vartest)) {
            if (!stim_valign.match(regex_vartest) && stim_valign != "") {
               debug_out("Invalid text vertical alignment value for [" + stim_name + "].");
            }
            stim_valign = "240";
        }
//   debug_out("vAlign: " + stim_align);
  stim_erase = get_tag(this_xml, idx, "erase");
        regex_vartest = RegExp("^(true|false)$", "i");
        if (stim_erase == "" || !stim_erase.match(regex_vartest)) {
            if (!stim_erase.match(regex_vartest) && stim_erase != "") {
               debug_out("Invalid erase value for [" + stim_name + "].");
            }
            stim_erase = "true";
        }
//   debug_out("erase: " + stim_erase);
  stim_color = get_tag(this_xml, idx, "color");
        regex_vartest = RegExp("^#[0-9a-f]{6}$", "i");
        if (stim_color == "" || !stim_color.match(regex_vartest)) {
            if (!stim_color.match(regex_vartest) && stim_color != "") {
               debug_out("Invalid color value for [" + stim_name + "].");
            }
            stim_color = "#000000";
        }
//   debug_out("color: " + stim_color);

  stim_size = get_tag(this_xml, idx, "size");
        regex_vartest = RegExp("^[0-9]+$", "i");
        if (stim_size == "" || !stim_size.match(regex_vartest)) {
            if (!stim_size.match(regex_vartest) && stim_size != "") {
               debug_out("Invalid size value for [" + stim_name + "].");
            }
            stim_size = "18";
        }
//   debug_out("size: " + stim_size);

// if this is a stimulus, load the question information (if present).
//   debug_out("type: " + stim_type);
if (this_xml == stimulus) {
  stim_qname = prefix + stim_name;
if (stim_c_one != "NULL") {
  stim_q_one = prefix + stim_c_one;
} else {
  stim_q_one = stim_c_one;
}
if (stim_c_two != "NULL") {
  stim_q_two = prefix + stim_c_two;
} else {
  stim_q_two = stim_c_two;
}
if (stim_c_three != "NULL") {
  stim_q_three = prefix + stim_c_three;
} else {
  stim_q_three = stim_c_three;
}
  stim_question = get_tag(this_xml, idx, "question");
//   debug_out("question: " + stim_question);
  stim_qcorr = get_tag(this_xml, idx, "qcorr");
// no default is needed, because a lack of content is meaningful
//   debug_out("qcorr: " + stim_qcorr);
  stim_qinc = get_tag(this_xml, idx, "qinc");
// no default is needed, because a lack of content is meaningful
//   debug_out("qinc: " + stim_qinc);
  stim_alter = get_tag(this_xml, idx, "alter");
//   debug_out("content: " + stim_content);
// question type (duh!)
  stim_qtype = get_tag(this_xml, idx, "qtype");
        regex_vartest = RegExp("^(text|img)$", "i");
// test for correct values
        if (stim_qtype == "" || !stim_qtype.match(regex_vartest)) {
// was the error due to an empty tag or due to bizarro data
            if (!stim_qtype.match(regex_vartest) && stim_qtype != "") {
// if it's bad data, warn the user
               debug_out("Invalid qtype for [" + stim_name + "].");
            }
// and use the default in either case.
            stim_qtype = "text";
        }

// question erase (duh!)
  stim_qerase = get_tag(this_xml, idx, "qerase");
        regex_vartest = RegExp("^(true|false)$", "i");
// test for correct values
        if (stim_qerase == "" || !stim_qerase.match(regex_vartest)) {
// was the error due to an empty tag or due to bizarro data
            if (!stim_qerase.match(regex_vartest) && stim_qerase != "") {
// if it's bad data, warn the user
//                debug_out("Invalid qerase for [" + stim_name + "].");
            }
// and use the default in either case.
            stim_qerase = "none";
        }

}
stim_roi = new Array;
// debug_out(stim_roi.length);
stim_roi = get_ROI(this_xml, idx, "roi");
// debug_out(stim_roi.length);
// if this is a question, replace the content, and correct/incorrect keys with previous stimulus values
if (stim_type == "quest") {
   stim_c_one = stim_q_one;
   stim_c_two = stim_q_two;
   stim_c_three = stim_q_three;
   stim_name = stim_qname;
   stim_type = stim_qtype;
   stim_content = stim_question;
   stim_correct = stim_qcorr;
   stim_incorrect = stim_qinc;
   if (stim_qerase != "none") {
      stim_erase = stim_qerase;
   }
}

// let's see how long this string is.
// if (stim_type == "text") {
//    debug_out(stim_name + ": " + stim_content.length);
//    var substrings = stim_content.split(" ");
//    debug_out(stim_name + ": " + (substrings.length- 1));
// }


// reset timeout (which activates stopwatch if subject does respond quickly enough
  timeout = stim_timeout;

// set up correct and incorrect responses as regular expressions
// if no responses are listed, then disallow regular expression checking later on
//   debug_out ("'" + stim_correct + "' ''");
//   debug_out (stim_correct.length);
  if (stim_correct == "") {
      allow_c = false;
  } else {
      allow_c = true;
  }
  regex_c = RegExp(stim_correct, "i");

  if (stim_incorrect == "") {
      allow_i = false;
  } else {
      allow_i = true;
  }
  regex_i = RegExp(stim_incorrect, "i");

mod_stim();

// start that watch!
// stuff will be actually displayed in start_watch
wait_for_it(stim_delay);

}

// this is the default stimulus presentation loop initiator function
// it must be called from the user defined .js file
function expt_loop () {
// if we're still running the experiment
if (oktogo == true) {
   // do something with the current trial
   // if it's a stimulus, set up the next stimulus (from the stimulus list) for display
   if (get_tag(trial, trial_loop, "type") == "stim") {
      display_stim(stimulus, stim_loop);
   // otherwise, send the next stimulus (from the trial list) for display
   } else {
      display_stim(trial, trial_loop);
   }
}
}

// this is the default stimulus presentation loop closer function
// mostly, it just increments the loop counters that mark where we are in the trial
function increment_loop () {
// if we're still running the experiment
if (oktogo == true) {
// if it's a stimulus, send the stimulus for display
   if (get_tag(trial, trial_loop, "type") == "stim") {
// increment the stimulus list counter
    stim_loop++;
   }
// increment the trial list counter
   trial_loop++;

// if we're out of stimuli and at the end of the trial loop, get ready to end
   if (stim_loop == stim_length_all && trial_loop == trial_length) {
// experiment off
      oktogo = false;
// tell the user via a
// user defined function for end of experiment wrap up.
      end_of_time();
   }
// if we're at the end of a trial, start a new trial
   if (trial_loop == trial_length) {
// reset the trial
      trial_loop = 0;
  }
}
}

function e_data() {
var answer = confirm ("This will submit your data to the experimenter for further analysis!\nDo you want to continue?\n(Choose 'Cancel' to hoard your data.)")
if (answer) {
document.dataform.submit();
} else {

}
}

var mousex = 0;
var mousey = 0;
var grabx = 0;
var graby = 0;
var orix = 0;
var oriy = 0;
var elex = 0;
var eley = 0;
var algor = 0;
var dragobj = null;

function falsefunc() { return false; } // used to block cascading events

function init()
{
  document.onmousemove = update; // update(event) implied on NS, update(null) implied on IE
  update();
}

function getMouseXY(e) // works on IE6,FF,Moz,Opera7
{
  if (!e) e = window.event; // works on IE, but not NS (we rely on NS passing us the event)

  if (e)
  {
    if (e.pageX || e.pageY)
    { // this doesn't work on IE6!! (works on FF,Moz,Opera7)
      mousex = e.pageX;
      mousey = e.pageY;
      algor = '[e.pageX]';
      if (e.clientX || e.clientY) algor += ' [e.clientX] '
    }
    else if (e.clientX || e.clientY)
    { // works on IE6,FF,Moz,Opera7
      mousex = e.clientX + document.body.scrollLeft;
      mousey = e.clientY + document.body.scrollTop;
      algor = '[e.clientX]';
      if (e.pageX || e.pageY) algor += ' [e.pageX] '
    }
  }
}

function update(e)
{
  getMouseXY(e); // NS is passing (event), while IE is passing (null)

  document.getElementById('span_browser').innerHTML = navigator.appName;
  document.getElementById('span_winevent').innerHTML = window.event ? window.event.type : '(na)';
  document.getElementById('span_autevent').innerHTML = e ? e.type : '(na)';
  document.getElementById('span_mousex').innerHTML = mousex;
  document.getElementById('span_mousey').innerHTML = mousey;
  document.getElementById('span_grabx').innerHTML = grabx;
  document.getElementById('span_graby').innerHTML = graby;
  document.getElementById('span_orix').innerHTML = orix;
  document.getElementById('span_oriy').innerHTML = oriy;
  document.getElementById('span_elex').innerHTML = elex;
  document.getElementById('span_eley').innerHTML = eley;
  document.getElementById('span_algor').innerHTML = algor;
  document.getElementById('span_dragobj').innerHTML = dragobj ? (dragobj.id ? dragobj.id : 'unnamed object') : '(null)';
}

function grab(context)
{
  document.onmousedown = falsefunc; // in NS this prevents cascading of events, thus disabling text selection
  dragobj = context;
//   dragobj.style.zIndex = 10; // move it to the top
//   document.onmousemove = drag;
//   document.onmouseup = drop;
  grabx = mousex;
  graby = mousey;
  orix = dragobj.offsetLeft;
  oriy = dragobj.offsetTop;
  elex = grabx - (orix + 11);
  eley = graby - (oriy + 11);
  check_roi(elex, eley);
  update();
}
