///$Revision: 1.105 $   $Date: 2008-02-05 02:20:55 $

// This file contains JavaScript functions that are referenced by the
// .html output generated by the SofCheck Inspector.  It is explictly
// 'SRC'-ed by the index.html file, as well as messages.html (not yet
// fully implemented).

// The web output of the SofCheck Inspector is implemented using "frames".
// As such, the traditional "back" and "forward" buttons do not behave as
// expected.  We maintain our own array of "UI_State", to provide a history
// of the display that can be walked both backwards (BACK button) and 
// forwards (FORWARD button).

// In order to set the display based on a UI_State, we keep track of
// the following pieces of information :
//
//  kind => There are two kinds of UI_State :
//              Complete_View
//              Src_Msg_View
//          The difference has to do with the display of the UI_State :
//            For a Compelete_View, we reload the .html file into the
//            window.
//          
//            For a Src_Msg_View, we check to see if the .html (frameset) file
//            is already loaded into the window.  If so, we simply set the
//            "location.hash" positions in both the source and the message
//            windows.
//  src_line => This is the "anchor" in the .java.html file 
//              (for source window).
//  src_id_pfx => This distingushes whether the error or the P/P on the
//                source-line is highlighted.
//  msg_anchor => This is the "anchor" in the .java-msgs.html file 
//                (for message window).
//  sum_anchor => This is the "anchor" in the .java-sum.html file 
//                (for summary).
//  overview_anchor => This is the "anchor" in the overview.html file
//                     (for overview).
//  tabs_anchor => This is the anchor in the overview_tabls.html file
//                 (for overview/summary/file-source "tabs"). 
//  file => This is the file that gets loaded into the main_frame window.
//          It may be a frameset.

//  Some variables of note :
//    top.UI_States : array of UI_State (see above)
//    top.Cur_UI_Index : index into top.UI_States, for current display
//    top.Cur_UI_State : points to the current UI_State (i.e.,
//                        top.UI_States[top.Cur_UI_Index])
//    top.UI_Highest_Index : keep track of the high-point in the UI_States
//                           array; this is how far forward we can go.
//
//    top.Popups  : array of window-handles (i.e., the result of "open");
//                  currently only used to access the message-status window.
//

var global_title='SofCheck Inspector';

function Increment_Index()
    // Increment top.Cur_UI_Index.
    // If this is the first user-click, then enable the "back" button.
{
    
    top.Cur_UI_Index = top.Cur_UI_Index + 1;

    // first user-click 
    if (top.Cur_UI_Index == 1) {
	top.frames["title_bar_" +
	  top.unique_suffix].document.forms[0].back_button.disabled = false;
    }
}

function Decrement_Index() {
    // Decrement top.Cur_UI_Index.
    // If this is as far back as we can go, then disable the "back" button.

    if (top.Cur_UI_Index >= 1) {
	top.Cur_UI_Index = top.Cur_UI_Index - 1;

	if (top.Cur_UI_Index == 0) {
	    top.frames["title_bar_" +
	      top.unique_suffix].document.forms[0].back_button.disabled = true;
        }
    }
}

function highlight_window_element(win, element_id)
{
    // highlight the specified element in the given window

    var id_elem = "ID_" + element_id;

    if (win.document.highlighted_elem != null) {
        // first clear highlight on the old element
        var old_elem = win.document.getElementById(
	  win.document.highlighted_elem);
        if (old_elem != null) {
            old_elem.style.backgroundColor = 'white';
        }
        win.document.highlighted_elem = null;
    }

    var new_elem = win.document.getElementById(id_elem);
    if (new_elem != null) {
        win.document.highlighted_elem = id_elem;
        new_elem.style.backgroundColor = 'yellow';
//  } else {
//      alert('elem ' + id_elem + ' not found');
    }
}

function highlight_source_element(element_id)
{
    // highlight the specified element in the source file

    var src_window =
        top.frames["main_frame_" + top.unique_suffix]
	  .frames["source_text_" + top.unique_suffix];

    highlight_window_element(src_window, element_id);
}

function New_UI_State(kind)
    // Initialize a new UI_State record.
{
    this.kind = kind;
    this.src_line = null;
    this.src_id_pfx = null;
    this.msg_anchor = null;
    this.sum_anchor = null;
    this.overview_anchor = null;
    this.tabs_anchor = null;
    this.file = null;
}


function Handle_Highest_Index() 
{
    // This is called ONLY after Increment_Index.

    if (top.Cur_UI_Index <= top.UI_Highest_Index) {
	// We were going backwards, but now the user has click'ed again.
	// So disable the forward button; a user-click is "in the present",
	// so you can't go "forward" to the future!
	top.frames["title_bar_" +
	  top.unique_suffix].document.forms[0].forward_button.disabled = true;
    }
    top.UI_Highest_Index = top.Cur_UI_Index;
}

function push_complete_view(src_line, src_id_pfx, sum_anchor,
  msg_anchor, file)
{
    // Push a "Complete_View" UI_State on the UI_States history array.

    Increment_Index();
    Handle_Highest_Index();

    top.UI_States[top.Cur_UI_Index] = new New_UI_State("Complete_View");
    top.UI_States[top.Cur_UI_Index].src_line = src_line;
    top.UI_States[top.Cur_UI_Index].src_id_pfx = src_id_pfx;
    top.UI_States[top.Cur_UI_Index].sum_anchor = sum_anchor;
    top.UI_States[top.Cur_UI_Index].msg_anchor = msg_anchor;
    top.UI_States[top.Cur_UI_Index].file = file;
    top.UI_States[top.Cur_UI_Index].tabs_anchor = "#" + file;

    top.Cur_UI_State = top.UI_States[top.Cur_UI_Index];

}

function push_new_sum_anchor(sum_anchor)
{
    // When the file summary is showing, and the user clicks to go
    // somewhere else in the file summary, the UI_State that we push
    // is the same as the current UI_State, with the exception of the
    // new "sum_anchor".

    push_complete_view(top.Cur_UI_State.src_line, top.Cur_UI_State.src_id_pfx,
      sum_anchor, top.Cur_UI_State.msg_anchor, top.Cur_UI_State.file);
}

function push_new_overview_anchor(overview_anchor)
{
    // When the file overview is showing, and the user clicks to go
    // somewhere else in the file overview, the UI_State that we push
    // is the same as the current UI_State, with the exception of the
    // new "overview_anchor".

    push_complete_view(top.Cur_UI_State.src_line, top.Cur_UI_State.src_id_pfx,
      top.Cur_UI_State.sum_anchor, top.Cur_UI_State.msg_anchor,
      top.Cur_UI_State.file);
}


function push_src_and_msg_view(src_line, msg_anchor, src_id_pfx, file)
{
    // Push a "Src_Msg_View" UI_State on the UI_States history array.

    // Restore the current summary anchor.
    var sum_anchor = top.Cur_UI_State.sum_anchor;

    Increment_Index();
    Handle_Highest_Index();

    top.UI_States[top.Cur_UI_Index] = new New_UI_State("Src_Msg_View");
    top.UI_States[top.Cur_UI_Index].src_line = src_line;
    top.UI_States[top.Cur_UI_Index].src_id_pfx = src_id_pfx;
    top.UI_States[top.Cur_UI_Index].sum_anchor = sum_anchor;
    top.UI_States[top.Cur_UI_Index].msg_anchor = "#" + msg_anchor.toString();
    top.UI_States[top.Cur_UI_Index].file = file;
    top.UI_States[top.Cur_UI_Index].tabs_anchor = "#" + file;

    top.Cur_UI_State = top.UI_States[top.Cur_UI_Index];
}

function push_first_view(kind, file)
{
    // This is the first UI_State that is being "pushed". 
    // At start-up, it will be for the error-overview,
    // but it could also be different, right after the use
    // hits "reload".

    top.UI_States[top.Cur_UI_Index] = new New_UI_State(kind);
    top.UI_States[top.Cur_UI_Index].file = file;
    top.UI_States[top.Cur_UI_Index].tabs_anchor = "#" + file;

    top.Cur_UI_State = top.UI_States[top.Cur_UI_Index];
}

function display_current_state(prev_cur_file)
    // Set the display to be that of the Cur_UI_State.
    // "prev_cur_file" is the file that was displayed in the main_frame,
    // just before we moved to the current one.
{

    top.frames["index_tabs_" + top.unique_suffix].location.hash = 
	top.Cur_UI_State.tabs_anchor;

    if (top.Cur_UI_State.kind == "Complete_View") {
        top.frames["main_frame_" + top.unique_suffix].location.href =
	  top.Cur_UI_State.file;

    } else if (top.Cur_UI_State.kind == "Src_Msg_View") {

	if (prev_cur_file == top.Cur_UI_State.file) {
	    // simply update the positions within the 
	    // source and message windows

	    top.frames["main_frame_" + top.unique_suffix].frames[
	      "source_text_" + top.unique_suffix].location.hash =
                "#" + top.Cur_UI_State.src_id_pfx + top.Cur_UI_State.src_line;

	    top.frames["main_frame_" + top.unique_suffix].frames[
	      "source_text_" + top.unique_suffix].scrollBy(0, -40);

            top.frames["main_frame_" + top.unique_suffix].frames[
              "messages_" + top.unique_suffix].location.hash =
		    top.Cur_UI_State.msg_anchor;

	    highlight_source_element(top.Cur_UI_State.src_id_pfx +
	      top.Cur_UI_State.src_line);

        } else {
	    // reload the file into the main_frame; the onLoad for the
	    // listing will take care of the highlighting.
            top.frames["main_frame_" + top.unique_suffix].location.href =
              top.Cur_UI_State.file;
        }

    //} else {
        //alert("what view do i have??");
    }
}

function go_back()
    // Handle when the user clicks the "back" button.
    // If this is the first time that the user has clicked "back",
    // after a series of user-clicks, then enable the "forward"
    // button.
{

    var cur_file = top.Cur_UI_State.file;

    if (top.Cur_UI_Index == top.UI_Highest_Index) {
	top.frames["title_bar_" +
	  top.unique_suffix].document.forms[0].forward_button.disabled = false;
    }

    Decrement_Index();
    top.Cur_UI_State = top.UI_States[top.Cur_UI_Index];

    display_current_state(cur_file);
}

function go_forward()
    // Handle when the user clicks the "forward" button.
    // If we have gone as far forward as we can, then disable
    // the "forward" button.
{

    var cur_file = top.Cur_UI_State.file;

    Increment_Index();

    if (top.Cur_UI_Index == top.UI_Highest_Index) {
	top.frames["title_bar_" +
	  top.unique_suffix].document.forms[0].forward_button.disabled = true;
    }

    top.Cur_UI_State = top.UI_States[top.Cur_UI_Index];

    display_current_state(cur_file);
}

function push_index_tabs(file)
    // Called when the user clicks on the index_tabs.
    // Basically, preserve all the existing anchors,
    // but the file and tabs_anchor changes.
{

    // save the current anchors
    var src_line = top.Cur_UI_State.src_line;
    var src_id_pfx = top.Cur_UI_State.src_id_pfx;
    var msg_anchor = top.Cur_UI_State.msg_anchor;
    var sum_anchor = top.Cur_UI_State.sum_anchor;
    var overview_anchor = top.Cur_UI_State.overview_anchor;

    Increment_Index();
    Handle_Highest_Index();

    top.UI_States[top.Cur_UI_Index] = new New_UI_State("Complete_View");

    top.UI_States[top.Cur_UI_Index].src_line = src_line;
    top.UI_States[top.Cur_UI_Index].src_id_pfx = src_id_pfx;
    top.UI_States[top.Cur_UI_Index].msg_anchor = msg_anchor;
    top.UI_States[top.Cur_UI_Index].sum_anchor = sum_anchor;
    top.UI_States[top.Cur_UI_Index].overview_anchor = overview_anchor;

    if (file.indexOf("-overview") != -1)  {
	top.UI_States[top.Cur_UI_Index].file = "overview.html";
    } else {
	top.UI_States[top.Cur_UI_Index].file = file.toString();
    }

    top.UI_States[top.Cur_UI_Index].tabs_anchor = "#" + file.toString();

    top.Cur_UI_State = top.UI_States[top.Cur_UI_Index];
}

function verify_UI_States_inited(kind, file, unique_suffix)
    // Verify that the UI_States history array has been initialized;
    // initialize it, if necessary.
{
    if (top.UI_States == null) {
        top.UI_States = new Array();
	top.Cur_UI_Index = 0;
        top.UI_Highest_Index = 0;
	top.unique_suffix = unique_suffix;

	push_first_view(kind, file);
    }

    // TBD : We do NOT want this to happen "onLoad" of the
    // Message-Status window.  That's because if
    // the user closes the Message-Status window, and then
    // re-opens it, we want his changes to persist.
    if (top.Message_Status_Array == null) {
	top.Message_Status_Array = new Array();

	// Start out indicating that there are no changes in the
	// Message-Status window that need to be saved.
	// TBD : We don't have this mechanism anymore.
        //top.message_status_mods_to_save = false;

	// By default, the save/restore functionality for the message status 
	// is disabled.
	// TBD : We don't have this mechanism anymore
	//top.status_save_restore_enabled = false;
    }
}

///// Other random Javascript functions used by other Inspector windows 

function ConfirmExit()
{
    if (top.message_status_mods_to_save) {
      return("You have edited the message status, but did not save " +
      "your changes.\nTo save, use the \"Save Message Status\" " +
	"button in the Messages window.");
    }
}


function index_onload(timestamp_string, shared_database)
{
    // This function is called "onLoad" of the index.html "frameset".
    // "timestamp_string" and "shared_database" are both used for the
    // Google-Gears implementation of the message-edit functionality.
    //
    // "timestamp_string" is (roughly) the timestamp of when db_lister
    // was most recently run; we need this information to check the 
    // local Google-Gears database for message-status mods that were made
    // *after* db_lister was most-recently run.
    //
    // "shared_database" is used as an "id" into the Google-Gears database.

    var search_string;
    var file_name;
    var status_file_name;
    var strlen;
    var fn;
    var prefix;

    top.shared_database = shared_database;

    // Note : Time-stamp strings that this Javascript file generates have
    // a "/" separating the year/month/day, but "timestamp_string" (which is
    // generated by db_lister) has "-" as the separator.  Convert to the
    // appropriate format, for time-stamp comparisons.
    top.db_lister_timestamp = timestamp_string.replace(/-/g,"/");
    //alert("top.db_lister_timestamp is " + top.db_lister_timestamp);;

    // We used to perform the Google-Gears initialization on the first time
    // that we opened up a message-status window.  However, we now have to
    // do it sooner (and this seems like a logical spot!), since opening a
    // "File Source" view causes us to query the local Googe-Gears database 
    // for mods that were made since db_lister was last run.
    if (top.Already_Inited_Google_Gears != true) {
        // init google-gears
        Init_Google_Gears();
        top.Already_Inited_Google_Gears = true;
    }

    // Above comment applies, about how this used to be done on the first time
    // that we opened a message-status window, and why we now need to do it
    // here.
    if (top.Msg_And_Line_Array == null) {
	Initialize_Msg_And_Line_Array();
    }

    // Note : A "search string" after the specification of "index.html"
    // is part of our "GPS"-integration, which allows us to know which
    // per-file message-status window we should open.  For example :
    // "...index.html?gps=foo.ada" means we should open the message-status
    // window for foo.ada.  For Ada, we have removed the package names,
    // such that all html files are stored directly under the 
    // "html" directory.
    search_string = window.location.search;
    
    if (search_string != '') {
	strlen = search_string.length;
	if (strlen > 5) {
	    prefix = search_string.substring(0,5);
	    if (prefix == "?gps=") {
	        file_name = search_string.substring(5,strlen);
		status_file_name = file_name + "-status.html";
	        //alert("i am here, file-name is " + fn);
                top.gps_msg_status_popup_or_give_focus('.', status_file_name,
	           true, '#', 'message_status_' + top.unique_suffix,
	             'not_set', 'not_set');
	    }
	}
    }
}

function gps_show_message_status(file_name)
{
    var status_file;

    status_file = file_name + "-status.html";
    //alert("In gps_show_message_status, status file is " + status_file);
    top.gps_msg_status_popup_or_give_focus('.', status_file,
      true, '#', 'message_status_' + top.unique_suffix,
      'not_set', 'not_set');
}

  function titlebar_onload(win)
  {
    //alert("in titlebar_onload");
    win.document.forms[0].back_button.disabled = true;
    win.document.forms[0].forward_button.disabled = true;

  }

  function keep_track_of_popups(win_name, win_handle) {
    // Keep track of the window handle ("win_handle") in the "Popups" array,
    // which is indexed via the name of the window ("win_name").

    if (top.Popups == null) {
        top.Popups = new Array();
    }
    top.Popups[win_name] = win_handle;
  }

  function popup_or_give_focus(win_url, win_target, win_width, win_height)
  {
    var new_win;

    // open a new window, or get a handle on the existing window
    new_win = open('', win_target,
      'scrollbars,status,menubar,resizable,toolbar,height=' + 
      win_height + ',width=' + win_width);

    // if the window is closed or empty, then set the url
    if (new_win.closed || !new_win.document.URL || 
     new_win.document.URL == '' ||
     new_win.document.URL.indexOf("about") == 0) {
      new_win.location=win_url;
    } else {
      // else just give the existing window focus
      new_win.focus();
    } 


    // Keep track of the window handle in the "Popups" array,
    // which is indexed via the name of the window.
    keep_track_of_popups(win_target, new_win);

    return false;
  }


  function popup_or_give_focus_with_anchor(win_url, win_target, win_width,
    win_height, anchor)
      // Like "popup_or_give_focus", but if the window is already opened,
      // then reposition the view to the specified "anchor".
  {
    var new_win;

    // open a new window, or get a handle on the existing window
    new_win = open('', win_target,
      'scrollbars,status,menubar,resizable,toolbar,height=' + 
      win_height + ',width=' + win_width);

    // if the window is closed or empty, then set the url
    if (new_win.closed || !new_win.document.URL || 
     new_win.document.URL == '' ||
     new_win.document.URL.indexOf("about") == 0) {
      new_win.location.href = win_url + anchor;
    } else {
      // else just give the existing window focus
      // AND set the viewing position to the "anchor"
      new_win.focus();
      new_win.location.hash = anchor;
    } 

    // Keep track of the window handle in the "Popups" array,
    // which is indexed via the name of the window.
    keep_track_of_popups(win_target, new_win);

    return false;
  }


  function msg_status_popup_or_give_focus(win_url_dir, win_url_file, use_dir,
    anchor, win_target, msg_lineage, orig_prob) 
      // Open the message-status window (named "win_target"), or give focus
      // to it, if it is already open.  The file to be loaded into the window
      // is specified in two pieces, "win_url_dir" and "win_url_file".
      // If "use_dir" is true, then combine both win_url_dir and win_url_file;
      // else, just use win_url_file.
      // Store the "combined" file-name in top.loaded_msg_status_file, to keep
      // track of what file is loaded in an already-opened message-status
      // window.
      // Set the viewing position to "anchor".
      // Note : The Inspector creates a message-status file (e.g.,
      // foo.java-status.html) for every file that has error messages.
      // There is, however, only one message-status window.  So, even if
      // the message-status window is open, we need to check if the 
      // correct file is loaded in it.  If not, we have to load it;
      // merely giving focus to the already-open window is not enough.
      //
      // If we reach here because the user has clicked on the editicon
      // for a message, in the File-Source-view-messages-pane, then 
      // "msg_lineage" and "orig_prob" are the lineage/probability for the
      // message at which we want to position the display (i.e. "anchor").
      // We store these values (along with the "anchor") in "top." variables,
      // so we can access them from the msg_status_onload code.  If the
      // Filters, at the top of the message-status window, are set such
      // that they would filter-out the message, we need to SET them so that
      // the message will be displayed; else, we won't be able to position the
      // display appropriately.
  {
    var new_win;
    var MSG_WINDOW_HEIGHT = 600;
    var MSG_WINDOW_WIDTH  = 900;
    var win_url;
    var full_url = win_url_dir + "/" + win_url_file;

    // Store these parameters in "top." variables, to be accessed from
    // the msg_status_onload() code.  We need to make sure that the
    // "Filters" (at the top of the message-status window), are set such
    // that, if the "anchor" specifies a particular message, then the 
    // message will NOT be filtered out.
    top.msg_lineage = msg_lineage;
    top.orig_prob = orig_prob;
    top.msg_status_anchor = anchor;

    // Set "win_url" to either the combined directory/file name, or simply
    // the file name, based on the value of "use_dir".
    if (use_dir == true) {
	win_url = full_url;
    } else {
	win_url = win_url_file;
    }
    //alert("full url is " + full_url + ";  opening up " + win_url);

    // open a new window, or get a handle on the existing window
    new_win = open('', win_target,
      'scrollbars,status,menubar,resizable,toolbar,height=' + 
      MSG_WINDOW_HEIGHT + ',width=' + MSG_WINDOW_WIDTH);

    // if the window is closed or empty, then set the url
    if (new_win.closed || !new_win.document.URL || 
      new_win.document.URL == '' ||
      new_win.document.URL.indexOf("about") == 0) {
	new_win.location.href = win_url + anchor;
    } else {
        // message-status window is open; is the correct file loaded?
	if (top.loaded_msg_status_file == full_url) {
	    //alert("win is the same, set hash");

	    // Make sure that the "Filters" at the top of the message-status
	    // window are set, such that, if we are trying to position the
	    // display at a message, that message will NOT be filtered out.
            Make_Sure_Filters_For_Message_Are_Set();

	    // the correct file is already loaded; set only hash
	    new_win.location.hash = anchor;
        } else {
	    // need to load a new file
	    new_win.location.href = win_url + anchor;
	    //alert("nope not the same, load");
        }
        // give focus
        new_win.focus();
    }

    // Keep track of the window handle in the "Popups" array,
    // which is indexed via the name of the window.
    keep_track_of_popups(win_target, new_win);

    // Keep track of the file that is loaded in the message-status window.
    // Use the "full_url", to make sure the file is uniquely identified.
    top.loaded_msg_status_file = full_url;

    return false;
  }


  function gps_close_message_status_window(win_target)
  {
      // For GPS : If the Message Status window is open, then close it.
      // As such, it doesn't matter what properties we give to it.

      var msg_status_win;

      msg_status_win = open('', win_target,
        'scrollbars,status,menubar,resizable,toolbar');

      if (msg_status_win.closed || !msg_status_win.document.URL ||
        msg_status_win.document.URL == '' ||
          msg_status_win.document.URL.indexOf("about") == 0) {
	  // the window wasn't already open, so do nothing.
	  // alert("the window was not open");
      } else {
	  // the window was already open; close it.
	  //alert("the window was open; close it");
	  msg_status_win.close();
      }
  }
  


  function gps_msg_status_popup_or_give_focus(win_url_dir, win_url_file,
    use_dir, anchor, win_target, msg_lineage, orig_prob) 
      // With GPS, we are having trouble giving focus to the Message Status
      // window, if it is already open.
      // So, if the window is already open, CLOSE IT, and then call
      // msg_status_popup_or_give_focus.  Since we are making sure to
      // first close the window (TBD: any timing issues?!), then it should
      // necessarily do the "popup" (which should necessarily give it focus)!
  {

    gps_close_message_status_window(win_target);

    msg_status_popup_or_give_focus(win_url_dir, win_url_file,
      use_dir, anchor, win_target, msg_lineage, orig_prob) ;
  }


//// Used by overview_tabs.html

  function index_tabs_onload(win)
  {
    //alert("in index_tabs_onload");
    // Set initial anchor
    if (win.location.hash != '#overview.html') {
      win.location.hash = '#overview.html';
    }
  }

  function click_on_index_tab(win, anchor)
  {
    win.location.hash = "#" + anchor;
    top.push_index_tabs(anchor);
  }


//// Used by sourcelist*.html (and overview.html)

  function set_index_tabs_anchor(anchor_name)
  {
    top.push_complete_view(0, 'E', '#', '#', anchor_name);
    top.frames["index_tabs_" + top.unique_suffix].location.hash =  
      "#" + anchor_name;
  }

//// used by overview.html

  function overview_onload(win, unique_suffix)
  {
    // alert("in overview_onload");
    // Set title and scroll to initial anchor
    top.document.title=global_title;
    top.verify_UI_States_inited("Complete_View", "overview.html", 
      unique_suffix);
    if (top.Cur_UI_State.overview_anchor != null &&
     top.Cur_UI_State.overview_anchor != "#" &&
     top.Cur_UI_State.overview_anchor != win.location.hash) {
      win.location.hash = top.Cur_UI_State.overview_anchor;
    }
  }

  function set_overview_anchor(line)
  {
    top.Cur_UI_State.overview_anchor = "#" + line.toString();
  }

//// Used by *-sum.html

  function summary_onload_common(win, file, unique_suffix, title)
  {
    top.verify_UI_States_inited("Complete_View", file, unique_suffix);

    // Scroll to initial anchor and set title
    if (top.Cur_UI_State.sum_anchor != null && 
     top.Cur_UI_State.sum_anchor != "#" &&
     top.Cur_UI_State.sum_anchor != win.location.hash) {
      win.location.hash = top.Cur_UI_State.sum_anchor;
    }
    top.document.title=global_title + ': ' + title + ' summary';
    // NOTE: We are setting title *after* any scrolling
    //       in an attempt to get history to show correct title

  }

//// Used by source listings

  function listing_onload_common(win, file, unique_suffix, title)
  {
    top.verify_UI_States_inited("Complete_View", file, unique_suffix);

    // Scroll to initial anchor and set title
    if (top.Cur_UI_State.src_line != null && top.Cur_UI_State.src_line != 0) {
      var src_anchor = "#" + 
        top.Cur_UI_State.src_id_pfx + top.Cur_UI_State.src_line;
      if (win.location.hash == null || win.location.hash != src_anchor) {
        win.location.hash = src_anchor;
        win.scrollBy(0, -40);
      }
      top.highlight_window_element(win, top.Cur_UI_State.src_id_pfx + 
        top.Cur_UI_State.src_line);

    }
    top.frames["index_tabs_" + top.unique_suffix].location.hash = "#" + file;
    top.document.title=global_title + ': ' + title;
    // NOTE: We are setting title *after* any scrolling
    //       in an attempt to get history to show correct title

  }

  function set_src_and_msg_anchors_common(src_line,
    msg_anchor, src_id_pfx, file)
  {
    top.push_src_and_msg_view(src_line, msg_anchor,
      src_id_pfx, file);
    highlight_source_element(src_id_pfx + src_line);
  }

//// Used by *-msgs.html

  function msg_onload_common(win, file, unique_suffix)
  {
    top.verify_UI_States_inited("Complete_View", file, unique_suffix);

    // scroll to initial anchor
    if (top.Cur_UI_State.msg_anchor != null && 
     top.Cur_UI_State.msg_anchor != "#" &&
     top.Cur_UI_State.msg_anchor != win.location.hash) {
      win.location.hash = top.Cur_UI_State.msg_anchor;
    }
  }

  function scroll_src_and_set_msg_anchor_common(src_line,
    msg_anchor, src_id_pfx, file)
  {
    var source_win = top.frames["main_frame_" + top.unique_suffix].frames[
	               "source_text_" + top.unique_suffix];

    top.push_src_and_msg_view(src_line, msg_anchor,
      src_id_pfx, file);
    source_win.location.hash = "#" + src_id_pfx + src_line;
    source_win.scrollBy(0, -40);
    highlight_window_element(source_win, src_id_pfx + src_line);
  }

//// Used by race_conditions.html

  function thread_popup(win, win_url, win_target, win_width, win_height)
  {
    var new_win;

    // (Re)open the window and give it focus
    // NOTE: We do the "open" using the passed-in "win" window,
    //       because that determines what is the "opener" at
    //       least in some browsers.  Without the "win." prefix,
    //       the opener might become index.html, which isn't what
    //       is expected for the thread-spec window.
    new_win = win.open(win_url, win_target,
     'scrollbars,status,resizable,toolbar,height=' + 
      win_height + ',width=' + win_width);

    new_win.focus();

    return false;
  }

//--------------------------------------------------------------------------
// This section of the code services the Message Status window, i.e.,
// "messages.html".  The Message Status window contains a table of all
// of the messages in the system.
//--------------------------------------------------------------------------

// The user is allowed to change the status of a message (i.e., is-a-bug/
// not-a-bug) in the Message-Status window.  We keep track of these modified
// messages in :
//     top.Message_Status_Array
// This array is indexed by file-name (e.g.,
//   net/neurotech/quotes/QuoteFactory.java-frameset.html).
// Each entry contains a "message_array"; for each "modified" message, 
// there is a msg-id and the new bug_status.  (Eventually, we will also
// associate user-comments with a message.)


function New_Msg(msg_id, bug_status, comments, orig_status)
    // Create a new "Msg".   A message consists of the following fields:
    //   msg_id : a unique message-id
    //   bug_status : set from a pull-down menu; for example, "Not A Bug",
    //                "High", etc.
    //   comments   : comments that the user has typed in
    //   orig_status : TBD
{
    this.msg_id = msg_id;
    this.bug_status = bug_status;
    this.comments = comments;
    this.orig_status = orig_status;
}


function New_Message_Status_Per_File(file_name)
    // Create an entry in the top.Message_Status_Array.
{
    this.file_name = file_name;
    this.message_array = new Array();

    // TBD : Do we need this field?

    // Have we checked the Google-Gears database for message-changes
    // that were made to this file after the most-recent run of the
    // db_lister?
    this.inited_from_gg_database = false;
}

function New_Handled_Line(line) {
    // This is used when we are going thru all of the messages in a
    // file, and determining the color-coding. 
    this.handled_line = line;
}


function Dump_Message_Status_Array()
    // This function is for debugging purposes; it loops through
    // top.Message_Status_Array and outputs the data.
{
    alert("In Dump_Message_Status_Array!")
    for (var i=0; i < top.Message_Status_Array.length; i++) {
	alert("filename is " +  top.Message_Status_Array[i].file_name);
	alert("and message array len is " +
	     top.Message_Status_Array[i].message_array.length);
	for (var j=0; j < top.Message_Status_Array[i].message_array.length; j++) {
	    alert("msg-id, status : " + 
	        top.Message_Status_Array[i].message_array[j].msg_id +
	        top.Message_Status_Array[i].message_array[j].bug_status);
	}
    }
}

function Update_Source_Line(src_window, line, message, file_name)
{
    // "file_name" contains "message", on the specified "line".
    // This file is currently being displayed in the "src_window".
    // Update the display so that the source-line is properly 
    // color-coded (taking into account *all* of the messages on the
    // line, and not just "message").
    var elem;
    var font_elem;
    var msg_id;

    msg_id = message.msg_id;

    // this is the anchor name with which the source-line happens 
    // to be identified (e.g., ID_E126, for line 126).
    elem = src_window.document.getElementById("ID_E" + line);
    if (elem == null) {
	// something's wrong...
	alert("couldnt find the ID_E element for line " + line);
    } else {
	// To set the color, we can't just use "elem"; we have to
	// get into the "FONT" tag, in the HTML.
	font_elem = elem.getElementsByTagName("FONT")

	if (font_elem == null) {
	    // something's wrong...
	    alert("couldn't find the font tag");
	}

        font_elem[0].style.color =
	  color_coding_for_source_win(file_name, message);

	// TBD : this is for debugging
	// elem.style.fontSize = "20px";
    }
}

function Update_Msg_Line(msg_window, message, file_name)
  // "file_name" contains "message".  Update the display in the 
  // "msg_window", with message's status.
{
    var elem;
    var msg_id;

    // get the unique msg_id for this message
    msg_id = message.msg_id;

    // the place where we want to change the "ranking" level
    // for this message has been associated with an id whose
    // naming-convention is "msg_id_<id>".

    elem = msg_window.document.getElementById("msg_id_" + msg_id);
    if (elem == null) {
	// something is amiss
	alert("Couldn't find the msg_id_ element.");
    } else {
	if (message.bug_status == top.NOT_YET_EXAMINED) {
	    // The user has set the message-status, and then
	    // set it back to "Not Yet Examined".  Display
	    // the original-probability.
	     var orig_prob;

	    orig_prob = 
	      get_original_probability(file_name, message);

	    // TBD : The original-probability is displayed in
	    // the Message-Status window as High/Medium/Low,
	    // but in the Inspector, in the message-window, 
	    // we refer to the status as High Prob/Medium Prob/
	    // Low-Prob.
	    elem.innerHTML = orig_prob + " Prob";
	} else {
	    elem.innerHTML = message.bug_status;

	}
    }
}

function Line_From_Msg_Id(msg_id, file_name)
    // Given the "msg_id" in "file_name", return the line number
    // on which the message resides.
{
    var line_elem;
    var line;
    var entry;

    entry = top.Msg_And_Line_Array[file_name];
    if (entry == null) {
        alert("unexpected null Msg_And_Line_Array in Line_From_Msg_Id");
    }

    line_elem = entry.Msg_To_Line_Array["id_" + msg_id];
    if (line_elem == null) {
	alert("unexpected null line_elem in Line_From_Msg_Id");
    }
    line = line_elem.line;

    return line;
}

function Abbrev_For_Status(bug_status) {
    // "bug_status" is a user-defined status (as set in the Message Status
    // window).  Return an abbreviation for it, which is used as part of 
    // an "ID" in the generated html.

    //----------------------------------------------------------
    // NOTE : Currentlty, this functionality should be removed 
    // from the product.
    alert("Unexpected call to Abbrev_For_Status");
    //----------------------------------------------------------

    switch (bug_status) {
      case top.NOT_YET_EXAMINED :
	  return("nye");
      case top.NOT_A_BUG :
	  return("nab");
      case top.IS_A_BUG :
	  return("iab");
      case top.POSSIBLE_BUG_HIGH :
      case "High" :
	  return("H");
      case top.POSSIBLE_BUG_MED :
      case "Medium" :
	  return("M");
      case top.POSSIBLE_BUG_LOW :
      case "Low" :
	  return("L");
      default :
	  alert("should not get here in Abbrev_For_Status!");
	  return("XX");
    }
}

function replaceAll(string,text,by) {
   // For every occurrence of "text" in "string", replace with "by".
   // TBD : gotten off the web, credit site?

    var strLength = string.length, txtLength = text.length;
    if ((strLength == 0) || (txtLength == 0)) return string;

    var i = string.indexOf(text);
    if ((!i) && (text != string.substring(0,txtLength))) return string;
    if (i == -1) return string;

    var newstr = string.substring(0,i) + by;

    if (i+txtLength < strLength)
        newstr += replaceAll(string.substring(i+txtLength,strLength),text,by);

    return newstr;
}

function Get_Category_For_Msg(file_name, msg_id) {
    // Return the message category of "msg_id", which is in "file_name".
    var entry;
    var index;
    var msg_rec;

    entry = top.Msg_And_Line_Array[file_name];
    if (entry == null) {
	alert("Unexpected null entry in Get_Category_For_Msg for " 
	  + file_name);
    }

    index = "id_" + msg_id;
    msg_rec = entry.Msg_To_Line_Array[index];
    if (msg_rec == null) {
	alert("Unexpected null msg_rec in Get_Category_For_Msg.");
    }
    return (msg_rec.category);

}

function Get_Orig_Prob_For_Msg(file_name, msg_id) {
    var entry;
    var index;
    var msg_rec;

    entry = top.Msg_And_Line_Array[file_name];
    if (entry == null) {
	alert("Unexpected null entry in Get_Orig_Prob_For_Msg");
    }

    index = "id_" + msg_id;
    msg_rec = entry.Msg_To_Line_Array[index];
    if (msg_rec == null) {
	alert("Unexpected null msg_rec in Get_Orig_Prob_For_Msg.");
    }
    return (msg_rec.orig_prob);
}


function Modify_One (status_overview_win, base_id_name, status, operation) {
    var id_name;
    var elem;
    var val;

    //----------------------------------------------------------
    // NOTE : Currentlty, this functionality should be removed 
    // from the product.
    alert("Unexpected call to Modify_One");
    //----------------------------------------------------------

    id_name = base_id_name + Abbrev_For_Status(status);
    // alert("getElemId is called with " + id_name);
    elem = status_overview_win.document.getElementById(id_name);
    if (elem != null) {
	elem.style.color = "red";
	// alert("old elem value is " + old_elem.innerHTML);
	val = elem.innerHTML;
	if (operation == "add") {
	    val = parseInt(val) + 1;
	} else {
	    val = parseInt(val) - 1;
	}
	// alert("old new  val is " + val);
	elem.innerHTML = val;
    } // else {
      // TBD : Is this an error condition, or simply that the file mentioned
      // in the "saved-status" file is no longer in the current inspection?
      // alert("elem is null");
      // }
}

function update_one_status_overview_display(file_name, msg_id,
  prev_bug_status, bug_status) {
    // TBD : Does the caller have to decide if the status-overview window
    // if open, or should it be done here?
    // Update the "status overview" window.
    // TBD : The window might not be open!
    // Here is the general methodolgy :
    //  -- subtract "1" from the old status, add one to the new status,
    //     and subtract 1 from the not-yet-examined
    // TBD : How will this work with new/old?!
    // To consider :
    //   If the old_status is Not Yet Examined, then we instead subtract "1"
    //   from the original status.
    //   If the new_status is Not Yet Examined, then we add "1" back to the
    //    original status.  Do NOT subtract 1 from the not-yet-examined.
    //   If changing TO not-a-bug, don't add-one to anything; we are not
    //   displaying not-a-bug.
    //   If changing FROM not-a-bug, nothing to do for "old".
    //   Need a category for Is-a-bug.
    var category;
    var category_no_spaces;
    var base_id_name;
    var old_status;
    var id_name;

    var status_overview_win = 
      top.Popups["status_overview_" + top.unique_suffix];

    //----------------------------------------------------------
    // NOTE : Currentlty, this functionality should be removed 
    // from the product.
    // alert("Unexpected call to update_one_status_overview_display");
    //----------------------------------------------------------

    if (status_overview_win == null) {
	// alert("status overview was never opened");
	 return;
    }
    if (status_overview_win.closed) {
	 //alert("status window is closed");
	 return;
    }

    // If we reach here, we know that the status-overview window is open.
    // Update it.

    // To update the window, we have to get at the correct "ID".
    category = Get_Category_For_Msg(file_name, msg_id);
    category_no_spaces = replaceAll(category," ", "_");

    id_name = category_no_spaces + "_";
    base_id_name = file_name +  "_" + category_no_spaces + "_";

    // Take care of "old"...changing from old, so in most cases, take away 1.
    switch (prev_bug_status) {
      case top.NOT_YET_EXAMINED :
      case top.IS_A_BUG :
      case top.POSSIBLE_BUG_HIGH :
      case "High" :
      case top.POSSIBLE_BUG_MED :
      case "Medium" :
      case top.POSSIBLE_BUG_LOW :
      case "Low":
	  Modify_One(status_overview_win,
	    base_id_name, prev_bug_status,"subtract");

	  // Handle error-kind table, too.
	  Modify_One(status_overview_win,
	    id_name, prev_bug_status,"subtract");

	  if (prev_bug_status == top.NOT_YET_EXAMINED) {
	      // also take away from the orig
	      orig_status = Get_Orig_Prob_For_Msg(file_name, msg_id);
	      Modify_One(status_overview_win, 
		base_id_name, orig_status,"subtract");

	      // Handle error-kind table, too.
	      Modify_One(status_overview_win, 
		id_name, orig_status,"subtract");
	  }
	  break;
      case top.NOT_A_BUG :
	  // alert("old is not_a_bug, nothing to change from");
	  break;
      default :
	  alert("Unexpected prev_bug_status in Update_Status_Overview");
    }

    // Take care of new...changing to new, so in most cases, add 1.
    switch (bug_status) {
      case top.NOT_YET_EXAMINED :
      case top.IS_A_BUG :
      case top.POSSIBLE_BUG_HIGH :
      case top.POSSIBLE_BUG_MED :
      case top.POSSIBLE_BUG_LOW :
	  Modify_One(status_overview_win, base_id_name, bug_status, "add");
	  // Handle error-kind table, too.
	  Modify_One(status_overview_win, id_name, bug_status, "add");

	  if (bug_status == top.NOT_YET_EXAMINED) {
	      orig_status = Get_Orig_Prob_For_Msg(file_name, msg_id);
	      Modify_One(status_overview_win, base_id_name, orig_status, "add");

	      // Handle error-kind table, too.
	      Modify_One(status_overview_win, id_name, orig_status, "add");
	  } 
	  break;
      case top.NOT_A_BUG :
	  // alert("new is not_a_bug, do nothing");
	  break;
      default :
	  alert("Unexpected bug_status " + bug_status
	    + " in Update_Status_Overview");
    }

}

function status_overview_onload(ov_win)
    // This method is called "onLoad" for the Status Overview window.
{
    var win_name = "status_overview_" + top.unique_suffix;

    // Make sure that the top.Popups array is set up with the window handle
    // ("ov_win") for the Status Overview window.
    // Note : this could be redundant; we also do this in popup_or_give_focus, 
    // which is called when the user clicks on the titlebar's "Messages"
    // button, to open up the Status Overview window.  However, due to
    // timing issues, the call to keep_track_of_popups (in popup_or_give_-
    // focus) might not be called before *this* method is called, so we also
    // need to do it here.
    keep_track_of_popups(win_name, ov_win);

    
    // Note : Since the message-status is no longer editable from the 
    // html output, there is no need to update the Status Overview
    // window "on the fly".
    // update_status_overview_display();

    // some constants, for the layout of the status overview window
    top.OV_FILE_NAME_COLUMN = 0;
    top.OV_CATEGORY_COLUMN = 1;
    top.OV_HIGH_COLUMN = 2;
    top.OV_MEDIUM_COLUMN = 3;
    top.OV_LOW_COLUMN = 4;

    // Set the initial state of "sorting" to false.
    top.OV_sorting = false;
    top.OV_abort_sorting = false;

    // For the status-overview window, set up internal data structures
    // to keep track of the "filter" checkboxes (i.e., the checkboxes 
    // that control/filter the data which is displayed).
    Set_Up_OV_Filter_Data();

    // Set the initial state of the save/restore display.
    // Note : We have gotten ride of the save/restore functionality
    // from the html output.
    // OV_Update_For_Save_Restore_Only(top.status_save_restore_enabled);
}


function Change_Message_Status(file_name, msg_id, bug_status, comments, 
  orig_status)
    // TBD : Comments are out-of-date and need to be updated!
    //
    // This method changes the status of a message (which occurs in
    // "file_name", and is uniquely identified by "msg_id").
    // It modifies top.Message_Status_Array.
    //
    // This method is called in two scenarios :
    //    a) when the user changes the status of a message, in the 
    //       Message Status window (i.e., foo.java-status.html).
    //       Currently, the fields which the user may change are the 
    //       "bug_status" and "comments"
    //       If "bug_status" is the empty-string, then we reach here because
    //       the comments field was modified; if "comments" is the 
    //       empty-string, then we reach here because the bug_status field was
    //       modified.  (This matters, because we only want to update the 
    //       field that the user has changed.)
    //    b) when the user clicks on the restore-message-status button,
    //       to restore message-status from a previously-saved file
    // 
    // Note : if you change the bug_status of a message, and that file is
    // currently showing in the src/message window, then you have to
    // update its display so that the source-window has the appropriate
    // color-coding and the correct probability is displayed in the
    // msg-window.
{
    var Entry_For_File;
    var Msg;
    var elem;
    var query;
    var bug_status_modified;
    var prev_bug_status;
    var main_window =
        top.frames["main_frame_" + top.unique_suffix];
    var status_overview_win;
    var old_elem;
    var new_elem;
    var category;
    var category_no_spaces;
    var orig;
    var other_elem;
    var other_id_name;

    //----------------------------------------------------------
    // NOTE : Currentlty, this functionality should be removed 
    // from the product.
    // alert("Unexpected call to Change_Message_Status");
    //----------------------------------------------------------

    // alert("In Change_Message_Status, file is " + file_name);

    // Keep track if bug_status is being modified.
    // Have to check here, since there are circumstances (that follow)
    // under which we may reset bug_status.
    bug_status_modified = (bug_status != "");

    // What frameset is loaded in the main-window?
    // If we are currently displaying the source to which the
    // modified-message belongs, then we need to update both
    // the source and msg-windows accordingly.
    // TBD : Is this an ok way to check?  This will give us something
    // like "net/neurotech/finance/Options.java-frameset.html", which
    // is exactly what we want.
    query = main_window.location.href.split("html/");
      

    Entry_For_File = top.Message_Status_Array[file_name];
    if (Entry_For_File == null) {
        // We haven't modified a message in this file yet.
	Entry_For_File =
	  new New_Message_Status_Per_File(file_name);

	// Set things up so that top.Message_Status_Array can be indexed
	// either numerically, or by the name of the file.
	top.Message_Status_Array[top.Message_Status_Array.length] =
	  Entry_For_File;
	top.Message_Status_Array[file_name] = Entry_For_File;
    } else {
       //alert("We already saw this file.");
    }

    // Has the user previously changed the status of this message?
    // If so, we update the existing entry with the new status;
    // else, we create a new entry.
    Msg = Entry_For_File.message_array["id_" + msg_id.toString()];

    if (Msg == null) {
	// If we have never seen this message before, and bug_status is "",
        // then we are here because we are first changing comments.
	// The bug_status hasn't changed, so we default to "not examined".

	prev_bug_status = top.NOT_YET_EXAMINED;

	if (bug_status == "") {
	    bug_status = top.NOT_YET_EXAMINED;
	}
	Msg = new New_Msg(msg_id, bug_status, comments, orig_status);

	// Set things up so that the "message_array" can be indexed 
	// either numerically, or by the msg-id string (with the
	// "id_" prefix, else the msg-id would end up erroneously treated 
	// like a numeric index).
	Entry_For_File.message_array[Entry_For_File.message_array.length] = Msg;
	Entry_For_File.message_array["id_" + msg_id.toString()] = Msg;
    } else  {
	prev_bug_status = Msg.bug_status;

	// alert("bug status was " + prev_bug_status + 
	//   " and is now " + bug_status);

	// Only set the field that the user actually changed (as indicated
	// by a non-empty string).
	if (bug_status != "") {
	    Msg.bug_status = bug_status;
	}
	if (comments != "") {
	    Msg.comments = comments;
	}
    }

    // Check to see if the relevant source is showing.  If so, 
    // we have to update both the source-window and the msg-window.
    // Note : This only applies if we are changing the bug_status.
    // Perhaps, one day, we may want to add a "comments" icon, in
    // either the source and/or msg window, to indicate that the
    // message has an associated comment.

    if (bug_status_modified == true) {
        if (query[1] == file_name)  {
	    var msg_window =
	        top.frames["main_frame_" + top.unique_suffix]
	          .frames["messages_" + top.unique_suffix];
	    var src_window =
	        top.frames["main_frame_" + top.unique_suffix]
	          .frames["source_text_" + top.unique_suffix];
	    var line = Line_From_Msg_Id(msg_id, file_name);

	    Update_Source_Line(src_window, line, Msg, file_name);
	    Update_Msg_Line(msg_window, Msg, file_name);

        }

	// If the Status Overview window is open, and we changed the
	// bug-status (as opposed to the comments), then we have to update
	// that display, too.
	// TBD : (Jan. '08) This code doesn't work right anymore, since
	// the contents/layout of the Status Overview window has changed.
	// That's ok, because after all, we don't update the Inspection
	// Overview window, either.
        // update_one_status_overview_display(file_name,
          // msg_id, prev_bug_status, bug_status);
    }


    // Indicate that a change has been made to the Message-Status window.
    top.message_status_mods_to_save = true;


}

function color_coding(bug_status) {
    // Return the color in which "bug_status" should be displayed.
    // TBD : Is there anything else that we need to take into
    // consideration?  Can this function operate off of the
    // bug_status string? What about the non-user-given properties,
    // i.e., High, Med, Low?

    // TBD : Are these the correct color schemes?
    // Should we distinguish between "is a bug" vs. "possbile bug high",
    // or should they both be red?

    //----------------------------------------------------------
    // NOTE : Currentlty, this functionality should be removed 
    // from the product.
    // alert("Unexpected call to color_coding");
    //----------------------------------------------------------

    switch (bug_status) {
      case top.NOT_YET_EXAMINED :
	  // We don't expect to see "not yet examined" here,
	  // since we should have already handled this case by
	  // setting it back to the original-probability.
	  alert("Unexpected 'not yet examined' status in color_coding.");
          return("purple")
      case top.NOT_A_BUG :
	  // web-safe light gray
          return("#CCCCCC");
      case top.IS_A_BUG :
          return("red");
      case top.POSSIBLE_BUG_HIGH :
          return("red");
      case top.POSSIBLE_BUG_MED :
	  // web-safe orange
          return("#FF9900");
      case top.POSSIBLE_BUG_LOW :
          return("green");
      default :
          // We shouldn't get here.
	  // TBD : handle gracefully.
	  alert("Unexpected bug_status " + bug_status +
	    "in color_coding");
	  return("red");
    }
}

function compute_status(Entry_For_File, message) {
    // If the user modified the status of "message",
    // then return the modified-status; else, return
    // the original-probablity of the message.
    // "Entry_For_File" is the element in top.Message_Status_Array
    // for the file in which the "message" appears.

    var elem;
    var msg_id;

    // alert("in compute_status for " + msg_id);

    msg_id = message.msg_id;
    elem = Entry_For_File.message_array["id_" + msg_id];

    // TBD : the following alert prints out "undefined" if the message
    // is not there; but the check for null, below, seems valid. 
    // alert("elem is " + elem);

    if (elem == null) {
	// message was not modified by the user;
	// go with the original probablity

	return message.original_prob;
    } else {
	if (elem.bug_status == top.NOT_YET_EXAMINED) {
	    // If the user sets the status to "not yet examined",
	    // then go with the original probability.

	    return message.original_prob;
	} else {
	    // return the modified status

	    return elem.bug_status;
	}
    }
}

function is_high_sev(sev) {
    // Return true if "sev" is considered to be "high"
    // (i.e., to be color-coded in red).

    if (sev == top.POSSIBLE_BUG_HIGH  ||
	sev == top.ORIG_HIGH ||
	sev == top.IS_A_BUG) {
	return true;
    } else {
	return false;
    }
}

function is_med_sev(sev) {
    // Return true if "sev" is considered to be "medium"
    // (i.e., to be color-coded in orange).

    if (sev == top.POSSIBLE_BUG_MED  ||
	sev == top.ORIG_MED) {
	return true;
    } else {
	return false;
    }
}

function is_low_sev(sev) {
    // Return true if "sev" is considered to be "low"
    // (i.e., to be color-coded in green).

    if (sev == top.POSSIBLE_BUG_LOW  ||
	sev == top.ORIG_LOW) {
	return true;
    } else {
	return false;
    }
}

function max_severity(sev1, sev2) {
    // Return the maximun severity, either "sev1" or "sev2".

    // alert("sev1 is " + sev1);
    // alert("sev2 is " + sev2);

    switch(sev1) {
	case top.ORIG_HIGH :
	case top.POSSIBLE_BUG_HIGH:
	case top.IS_A_BUG :
	    // nothing trumps the highest severity :
	    return top.POSSIBLE_BUG_HIGH;
	case top.POSSIBLE_BUG_MED :
	case top.ORIG_MED:
	    if (is_high_sev(sev2)) {
		// high trumps medium, else medium trumps
		return top.POSSIBLE_BUG_HIGH;
	    } else {
		return top.POSSIBLE_BUG_MED;
	    }
	case top.POSSIBLE_BUG_LOW:
	case top.ORIG_LOW :
	    if (is_high_sev(sev2)) {
		return top.POSSIBLE_BUG_HIGH;
	    } else if (is_med_sev(sev2)) {
		return top.POSSIBLE_BUG_MED;
	    } else {
		return top.POSSIBLE_BUG_LOW;
	    }
	case top.NOT_A_BUG :
	    if (is_high_sev(sev2)) {
		return top.POSSIBLE_BUG_HIGH;
	    } else if (is_med_sev(sev2)) {
		return top.POSSIBLE_BUG_MED;
	    } else if (is_low_sev(sev2)) {
		return top.POSSIBLE_BUG_LOW;
	    } else {
		return top.NOT_A_BUG;
	    }
    }
}

function color_coding_for_source_win(file_name, message) {
    // In the source-window, getting the "color-coding" correct,
    // on the line that contains "message", can be confusing.
    // There could be multiple messages on the same line, and the
    // color-coding should be for the "highest" severity on that line.
    // We have to look at all of the messages on the line; if they
    // have been modified, we have to look at their "new" bug_status,
    // else, we have to look at their original-probabilty.  In this way,
    // we find the "highest" severity, and color-code according to that.

    var bug_status;
    var msg_id;
    var file_rec;
    var msg_id_array;
    var highest_severity;
    var Entry_For_File;

    bug_status = message.bug_status;
    msg_id = message.msg_id;

    // This is the line that contains "message".
    line = Line_From_Msg_Id(msg_id, file_name);
    // alert("line is " + line);

    // Get an array of *all* messages on this line.  At a minimum, we
    // should find message.msg_id in this array.
    file_rec = top.Msg_And_Line_Array[file_name];
    msg_id_array = file_rec.Line_To_Msgs_Array["ln_" + line].msg_array;

    Entry_For_File = top.Message_Status_Array[file_name];
    if (Entry_For_File == null) {
	alert("unexpected null Entry_For_File in color_coding_for_source_win");
    }

    // default the "highest_severity" to the lowest-severity
    highest_severity = top.NOT_A_BUG;

    for (var i = 0; i < msg_id_array.length; i++) {
	var computed;

	computed = compute_status(Entry_For_File, msg_id_array[i]);
	highest_severity = max_severity(highest_severity, computed);
    }

    return (color_coding(highest_severity));
}

function get_original_probability(file_name, message) {
    // "message" is a message in "file_name"; get its original ranking.
    //
    // Currently, this routine is only called when the user 
    // modifies the status of a message but *then* sets it back
    // to "Not Yet Examined".
    // 
    // The implementation details are :
    //     a) get the msg-id of the message
    //     b) index into file_name's Msg_To_Line_Array 
    //        to get the message's line-number
    //     c) use the above line-number to index into
    //        file_elem's Line_To_Msg_Array.  This step
    //        yields an array of message-ids on that line
    //        (including the "message" we're interested in).
    //        The original-probability for the message is located
    //        in the elements of the array of message-ids.
    //
    //        TBD : We may want to *also* put the original-probability
    //        into the Msg_To_Line_Array, for easier access (while
    //        also keeping it in the Line_To_Msg_Array, since it is
    //        very handy there when color-coding lines in the source-win).
    //        However, as the opening comment indicates, this routine
    //        probably won't be called very often, so it's probably ok
    //        to leave things as is.

    var msg_id;
    var line;
    var orig_prob;
    var file_rec;

    msg_id = message.msg_id;
    // This is the line that contains "message".
    line = Line_From_Msg_Id(msg_id, file_name);

    // Get array of *all* messages on this line.
    file_rec = top.Msg_And_Line_Array[file_name];
    msg_id_array = file_rec.Line_To_Msgs_Array["ln_" + line].msg_array;

    orig_prob = "uninitialized";
    for (var i = 0; i < msg_id_array.length; i++) {
        if (msg_id_array[i].msg_id == msg_id) {
	    orig_prob = msg_id_array[i].original_prob;
	    break;
	}
    }
    if (orig_prob != "uninitialized") {
	return (orig_prob);
    } else {
	alert("Unable to find original ranking for " + msg_id);
	return top.NOT_YET_EXAMINED;
    }
}

function Msg_Window_Update(file_name) {
    // This routine is called "onLoad" for a msg-file (i.e.,
    // foo.java-msgs.html).  For each message in the file that has had its
    // status modified, update the "ranking" field for the message,
    // in the message-window.

    var file_elem;
    var messages;
    var elem;
    var msg_window =
        top.frames["main_frame_" + top.unique_suffix]
	  .frames["messages_" + top.unique_suffix];

    // There may not be any modified messages associated with this file.
    file_elem = top.Message_Status_Array[file_name];

    if (file_elem != null) {
	// Get the modified messages for this file.
	messages = file_elem.message_array;

	for (var i=0; i < messages.length; i++) {
	    Update_Msg_Line(msg_window, messages[i], file_name);
	}

	// Update_Msg_Line (see above) changes the display via "innerHTML",
	// and somehow, in some circumstances, this causes the window to 
	// scroll slightly, such that the display to the "msg_anchor" 
	// that we set earlier, in msg_onload_common, is now wrong.
	// So reset the display here.  (If the display was ok, then this
	// is merely redundant.)
	if (messages.length > 0) {
	    // NOTE: We only want to do this if it is really necessary
	    //       since it seems to cause an infinite re-load loop
	    //       in some cases on Firefox (e.g. CLArgsParser.java 
	    //       in jmeter).
	    if (top.Cur_UI_State.msg_anchor != null && 
	      top.Cur_UI_State.msg_anchor != "#" &&
	      top.Cur_UI_State.msg_anchor != "") {
	        msg_window.location.hash = top.Cur_UI_State.msg_anchor;
	    }
        }
    }
}

function Update_Message_Status(file_name, pkg_name, short_file) {
    // This routine is called "onLoad" for the source-file "file_name"
    // (i.e., net/neurotech/foo.java.html).  "pkg_name" and "short_file" 
    // could be be derived from "file_name" (i.e., "net/neurotech" and
    // "foo.java"), but are passed separately, and are used in the
    // Google-Gears message-edit functionality.
    //
    // For each message in the file that has had its
    // status modified, update the color-coding of the corresponding
    // source-line to match the new status.
    // A source-line may have several messages on it, so the 
    // color-coding has to take into account ALL messages on that line,
    // and not just the status of the newly-modified message.

    // Some implementation details :
    //   * top.Message_Status_Array is the array that contains the
    //     changed messages. 
    //     It is indexed by file-name, which is actually the html-frameset
    //     filename (e.g., "net/neurotech/finance/Options.java-frameset.html").
    //   * Each entry in top.Message_Status_Array contains an array of modified
    //     messages.  For each message in this array, calculate the new
    //     color-coding for the associated source line, and then update it.
    //   * Messages are uniquely identified via a msg-id.  To get the
    //     line-number associated with a msg-id, we need to go through
    //     top.Msg_And_Line_Array.
    //     top.Msg_And_Line_Array is also indexed via the same file-name
    //     as the Message_Status_Array.
    //     For each file with messages, Msg_And_Line_Array contains 
    //     two mappings :
    //        Msg_To_Line_Array (maps from msg-id to line-num)
    //        Line_To_Msg_Array (maps from line-nume to msg-id(s) on that line)
    //  
    // Note : Call "Check_GG_Database" to check if there are any messages
    // in this file that were modified since the last time that the db_lister
    // was run.  If so, Check_GG_Database will set the 
    // top.Message_Status_Array[file_name] entry (which keeps tracks of all
    // the current "deltas"). 
    // However, note that the corresponding entry in the "top.Msg_And_Line"
    // array will NOT be set until the message-status window for this file
    // is opened.  As such, we will NOT be able to perform an accurate 
    // color-coding of source-lines.
    // TBD : It might be more correct to call Check_GG_Database in 
    // Msg_Window_Update, which updates the lower "messages" area of the 
    // "File Source" view.  But one day, we might do the right thing for
    // the color-coding, so leave the code here.

    var file_elem;
    var messages;
    var elem;
    var entry;
    var my_line;
    var msg_id;
    var my_line_elem;
    var font_elem;
    var handled_lines;

    // alert("In Update_Message_Status, file_name is " + file_name);

    var src_window =
        top.frames["main_frame_" + top.unique_suffix]
	  .frames["source_text_" + top.unique_suffix];


    // There may not be any modified messages associated with this file.
    file_elem = top.Message_Status_Array[file_name];

    if (file_elem == null) {
	// Check the Google-Gears database, to see if there are changes
	// for messages in this file that were made after the db_lister
	// was last run.
        Check_GG_Database(file_name, pkg_name, short_file);
	
	// top.Message_Status_Array[file_name] should no longer be null.
	// Even if we didn't find any relevant messages, the
	// "inited_from_gg_database" field will be true.
        file_elem = top.Message_Status_Array[file_name];
    }


    // Note : all the following code deals with color-coding of source-lines.
    // As mentioned above, this won't be possible if we are in the
    // situation where we've just gone to the Google-Gears database for
    // changes that were made after the db_lister was last run, *and* 
    // we haven't opened the corresponding message-status window yet. 
    // (Note that if we *had* already brought up the message-status window,
    // we would NOT have done gone to the GG database here, since 
    // top.Message_Status_Array[file_name] would not have been null!)

    if (file_elem != null) {
	// TBD : Better variable names.
	// Get the msg-and-line array associated with this file.
	entry = top.Msg_And_Line_Array[file_name];
	if (entry == null) {
            // We would get here if we've already checked the Google-Gears
            // database for this file (when bringing up the "File Source"
	    // view), but we haven't brought up the corresponding
	    // -status.html file.
	    return;
	}

	// Get the modified messages for this file.
	messages = file_elem.message_array;

	handled_lines = new Array();

	for (var i=0; i < messages.length; i++) {
	    var handled_line_elem;

	    // get the unique msg_id for this message
	    msg_id = messages[i].msg_id;

	    // get the corresponding line-number (used to compute the
	    // "id" that represents the "element" whose color-coding
	    // needs to be modified)

	    my_line_elem = entry.Msg_To_Line_Array["id_" + msg_id];
	    my_line = my_line_elem.line;

	    // Don't do anything, if we have already handled
	    // the color-coding for this line when we handled
	    // another message on the same line.
	    handled_line_elem = handled_lines["ln_" + my_line];
	    if (handled_line_elem == null) {
		// We haven't handled the coloring for this line yet.
		// Set up so we can index via "ln_" + my_line.
		handled_line_elem = new New_Handled_Line(my_line);
		handled_lines[handled_lines.length] = handled_line_elem;
		handled_lines["ln_" + my_line] = handled_line_elem;

		Update_Source_Line(
		  src_window, my_line, messages[i], file_name);
	    } else {
		// We already color-coded this line.
		// alert("we already color-coded line " +
		    // my_line + "msg-id : " + msg_id);
	    }
	}
    }
}

function file_loaded_in_win(win) {
    // Return the name of the file that is loaded in "win".
    // We want the part of the name that is relative to the
    // html directory, e.g.,
    //   net/neurotech/currency/Converter.java-frameset.html
    var query;

    query = win.location.href.split("html/");
    return query[1];
}


function get_selected_index(form_elem, status)
  // TBD : New comments!
  // This function is called as part of restoring the saved message
  // "deltas".
  // The Message Status Window has to be updated, to reflected the
  // restored message-status.  Part of this updating is to correctly
  // set the message-status categorization, which is the pull-down
  // list of Not-Examined/Not A Bug/Is A Bug, etc.
  //
  // Internally, this pull-down list is represented as a Javascript
  // "Form", and the way that you set the "selected" value is via
  // the "index" (i.e., "Not Examined Yet" is 0, "Not A Bug" is 1,
  // "Is A Bug" is 2, etc).
  //
  // This function takes the "status" (e.g., "Not A Bug", which is
  // almost the same (but not quite) as the text that shows up in the
  // pull-down menu; it is what shows up in the src/message window),
  // and matches it up with the corresponding "option" in the
  // the "form_elem"ent.  This allows us to get the appropriate 
  // "selected_Index", in order to set the pull-down to the correct value.
{
   for (var j=0; j < form_elem.options.length; j++) {
      if (form_elem.options[j].value == status) {
          return j;
      }
   }

   // TBD : What happens when we set the selected_Index to -1?
   return -1;
}


function update_one_msg_display(status_win, id, status) {
    // Update the display of one message, which is currently loaded
    // in "status_win".  "id" specifies which message, and "status"
    // is the value to use in the update.
    var index;
    var elem;

     elem = status_win.document.getElementById("msg_id_" + id);
     if (elem == null) {
         // something is amiss
         alert("Unexpected null elem in update_one_msg_display.");
    } else {
        elem.innerHTML = status;
    }
    // TBD : We also need to change the name of the button to
    // "Edit", if it was "Review"!
    // TBD : Is it easier to just set it, or check and see if it needs
    // to be set?
    status_win.document.forms["msg_status_" + id].edit.value = "Edit";
}

function update_message_status_display(file_name)
{
    // This function is called onLoad of the Message Status window.
    // The user may have made changes to some messages, but then
    // dismissed the window.  The changes that the user has made
    // are still in top.Message_Status_Array.  When we re-open the
    // Message Status window, we want the display to "match" the
    // state of the internal data structure.
    // Note : at any given time, the Message Status window contains
    // only the messages for one file.  Only update the display for
    // the messages for "file_name", which is currently displayed
    // in the Message Status window.

    // Also : note that the per-file Message-Status window only displays
    // the severity, and not the comments.

    // TBD TBD : For whatever file is displayed in the main_window,
    // we need to update that window as well (i.e., just as
    // Change_Message_Status does).

    var id;
    var status;
    var comments;
    var index;
    var status_win;
    var Entry_For_File;
    var messages;

    status_win = top.Popups["message_status_" + top.unique_suffix];

    // Has the user changed any messages in this file?
    Entry_For_File = top.Message_Status_Array[file_name];
    if (Entry_For_File != null) {
	// Loop through the user-modified messages in this file,
	// and update the display in the Message Status window.
	messages = top.Message_Status_Array[file_name].message_array;

	for (var j=0; j < messages.length; j++) {
	    id = messages[j].msg_id;
	    status = messages[j].bug_status;

	    // update the display for this message
	    update_one_msg_display(status_win, id, status);

	}
    }
}

function Set_Severity_Pulldown(win, severity)
{
    // Set the selected option in the message-severity pulldown
    // to "severity".

    var index;

    // TBD : Perhaps we will change the name of the form from "form_1".
    index = get_selected_index(win.document.form_1.status,severity);
    win.document.form_1.status.selectedIndex = index;
}


function Message_Has_User_Edit(new_win, file_name, message_id)
{
    var id;
    var status;
    var comments;
    var index;
    var status_win;
    var Entry_For_File;
    var messages;
    var found_message;

    // TBD : It would be better if we could index directly into this array
    // with the message_id, rather than walking through all of the edited 
    // messages in this file.  Hopefully, there won't be too many.
    found_message = false;
    Entry_For_File = top.Message_Status_Array[file_name];
    if (Entry_For_File != null) {
	// Loop through the user-modified messages in this file,
	// looking for "message_id".
	messages = top.Message_Status_Array[file_name].message_array;

	for (var j=0; j < messages.length; j++) {
	    id = messages[j].msg_id;
	    if (id == message_id) {
		found_message = true;
	        break;
	    }

	}
    }
    return found_message;

}

function Update_One_Message_Edit(new_win, file_name, message_id)
{
    // This function is called after the edit-message-status window is
    // popped-up.  Initialize the message-severity pull-down with the
    // current severity.  If the user has added any comments in this 
    // inspection session, then display them, too.  (Note: any changes
    // that the user has made IN THIS SESSION are in 
    // top.Message_Status_Array.)

    // TBD TBD : For whatever file is displayed in the main_window,
    // we need to update that window as well (i.e., just as
    // Change_Message_Status does).

    var id;
    var status;
    var comments;
    var index;
    var status_win;
    var Entry_For_File;
    var messages;
    var found_message;

    // TBD : It would be better if we could index directly into this array
    // with the message_id, rather than walking through all of the edited 
    // messages in this file.  Hopefully, there won't be too many.
    found_message = false;

    // alert("In Update_One_Message_Edit, message_id is " + message_id +
     // " and file-name is " + file_name);

    Entry_For_File = top.Message_Status_Array[file_name];
    if (Entry_For_File != null) {
	// Loop through the user-modified messages in this file,
	// looking for "message_id".
	// alert("I found the file in Update_One_Message_Edit");
	messages = top.Message_Status_Array[file_name].message_array;

	for (var j=0; j < messages.length; j++) {
	    id = messages[j].msg_id;
           //alert("id is " + id);
	    if (id == message_id) {
	        status = messages[j].bug_status;
	        comments = messages[j].comments;
		//alert("comments is " + comments);

		Set_Severity_Pulldown(new_win, status);
                new_win.document.comments_1.text_1.value = comments;
		found_message = true;
	        break;
	    }

	}
    } else {
	// alert("I did NOT find the file in Update_One_Message_Edit");
    }

    status_win = top.Popups["message_status_" + top.unique_suffix];
    if (found_message == false) {
        // The user didn't modify this message in this session, so we need
	// to initialize the pull-down menu with the current severity (i.e.,
	// on display in the "Status" column of the per-file Message Status
	// window).
	elem = status_win.document.getElementById("msg_id_" + message_id);
	if (elem == null) {
	    // something is amiss
	    alert("Unexpected null elem in Popup_Edit_Message_Status");
	} else {
            Set_Severity_Pulldown(new_win, elem.innerHTML);
        }
    }
}


function update_status_overview_display()
{
    // This function is called onLoad of the Status Overview window.
    // The user may have made changes to some messages, but then
    // dismissed the Status Overiew window.  The changes that the user 
    // has made are still in top.Message_Status_Array.  When we re-open the
    // Status Overview window, we want the display to "match" the
    // state of the internal data structure.

    // TBD : For now (Dec '07), we don't want to update the Status Overview
    // window, based on user mods within an inspection session.

    var id;
    var status;
    var Entry_For_File;
    var messages;

    // Return, if top.Message_Status_Array hasn't been initialized.
    // This could happen if the user clicks on the "Messages" button,
    // before the browser finishes loading the inspector output.
    if (top.Message_Status_Array == null) {
        return;
    }

    for (var i = 0; i < top.Message_Status_Array.length; i++) {
        Entry_For_File = top.Message_Status_Array[i];

        // Loop through the user-modified messages in this file,
        // and update the display in the Status Overview window.
        messages = Entry_For_File.message_array;
        file_name = Entry_For_File.file_name;

        for (var j=0; j < messages.length; j++) {
	  id = messages[j].msg_id;
	  status = messages[j].bug_status;
	  orig_status =  messages[j].orig_status;

	  // update the display for this message
          update_one_status_overview_display(file_name, id,
	    //orig_status, status);
	    "not examined", status);
	}
    }
}


function get_full_directory_part(full_url) {
    // "full_url" is a complete url (as returned by window.location.href);
    // return the full directory part (i.e., all but the last "/index.html")
    // TBD : does the result have to go into a global?

    var full_url_array;

    full_url_array = full_url.split("/");
    delete full_url_array[(full_url_array.length-1)];
    top.full_url_dir = full_url_array.join("/");
    return top.full_url_dir;
}


//--------------------------------------------------------------------------
// There are a few areas of functionality here :
// 1) sorting
//    There are several columns in the Message Status table.  Some of 
//    the column headers are "links", and when you click on such a link,
//    the table is sorted "as appropriate".
//
// 2) building up internal data structures from messages.html 
//    When messages.html is loaded, it builds up the following data 
//    structure.  This data structure is used when "applying" message-status
//    changes to the display of the corresponding "frameset" (i.e., the
//    split-screen source/msg window).
//
//    The data structure is :
//        top.Msg_And_Line_Array
//    It is an array, with one entry per .java file (actually,
//    per .java-frameset.html file).  Only files that contain
//    error messages are in this array, since it is built up from the 
//    table of system-wide error messages.
//
//    Each file is associated with the following data :
//        Msg_To_Line_Array  : mapping array from msg-id to line-number
//          (i.e., line number that contains the message)
//
//        Line_To_Msgs_Array  : mapping array from line-number to an array
//          of msg-ids on that line.  Also, for each msg-id, we store the
//          "original_probability" of the message.
//
//    Via the Message-Status table, the user is allowed to change the status
//    (i.e., is-a-bug/not-a-bug, etc) of any message.  This change needs to be
//    reflected in the display of the corresponding source/message window.
//    In the source window, that means making the color of the source line
//    containing the message(s) match-up with the user-selected probability.
//    There may be more than one message on the same line, however, so the 
//    color-coding of the line needs to take into account both new *and*
//    original probabilities for that line, in order to recompute the max.
//    severity and the corresponding color-coding.
//          
//--------------------------------------------------------------------------

function New_Msg_Line_Entry_Per_File(file_name)
    // This is an element of the top.Msg_And_Line_Array.
    // There is one such element per file (i.e., only files which contain
    // error messages).
    // For each file, we have a mapping array from msg-id to line number,
    // and a mapping array from line-number to msg-ids-on-that-line.
{
    this.file_name = file_name;
    this.Msg_To_Line_Array = new Array();
    this.Line_To_Msgs_Array = new Array();
}

function New_Msg_To_Line_Elem(msg_id, line)
    // This is an element in the Msg_To_Line_Array, which "maps"
    // a msg-id to line-number.
{
    this.msg_id = msg_id;
    this.line = line;
}

function New_Msg_To_Line_Elem_Super(msg_id, line, category, orig_prob)
    // This is an element in the Msg_To_Line_Array, which "maps"
    // a msg-id to line-number.
{
    this.msg_id = msg_id;
    this.line = line;
    this.category = category;
    this.orig_prob = orig_prob;
}

function New_Line_Elem(line)
    // This is an element in the Line_To_Msgs_Array, which "maps"
    // a line-number to the msg-id(s) on that line.
{
    this.line_num = line;
    this.msg_array = new Array();
}

function New_Msg_Elem(msg_id, original_prob)
{
    // This is an element in the Line_To_Msgs_Array.msg_array (see above),
    // which is an array of msg_ids (and the "original probability" is
    // associated with each message-id).
    this.msg_id = msg_id;

    // NOTE : The meaning of "original_prob" has changed since the first
    // implementation of this code.  It is no longer the "original probability"
    // that the Inspector assigned to the message.  Rather, it is the 
    // "starting probability" for this Inspector session.  The user could have
    // changed the status of the message in prior Inspector sessions,
    // and that status change gets put into the database "Audit Trail"...
    // and then, gets incorporated into the static html that we generate.
    this.original_prob = original_prob;
}


function Debug_Stuff ()
{
    var the_array = top.Msg_And_Line_Array;

    // For debugging
    alert("in Debug_Stuff!");
    for (var i=0; i < the_array.length; i++) {
        alert("filename is " + the_array[i].file_name);
        for (var j=0; j < the_array[i].Line_To_Msgs_Array.length; j++) {
	    alert("line is " + the_array[i].Line_To_Msgs_Array[j].line_num);
	    for ( var k=0;
	      k < the_array[i].Line_To_Msgs_Array[j].msg_array.length; k++) {
                alert("msg_id is " +
	          the_array[i].Line_To_Msgs_Array[j].msg_array[k].msg_id);
	    }
	}

	alert("line/message : ");
	for (var t=0; t < the_array[i].Msg_To_Line_Array.length; t++) {
	  alert("msg-id " + the_array[i].Msg_To_Line_Array[t].msg_id +
	      ", line " + the_array[i].Msg_To_Line_Array[t].line);
	}
    }
}


function get_msg_id_from_row(row) {
  // "row" is a row of the table in the "Message-Status" window.
  // Return the "msg-id" for the row.
  // NOTE : this routine is HIGHLY dependent of the internal layout
  // of the DOM!
  // TBD : How much error-checking should we have here?
    var col;
    var anchor;
    var msg_id;

    col = row.cells[top.MSG_ID_COLUMN];
    anchor = col.getElementsByTagName("A");

    msg_id = anchor[0].firstChild.nodeValue;
    //alert("msg_id is " + msg_id);

    return msg_id;
}


function get_status_from_row(row) {
  // "row" is a row of the table in the "Message-Status" window.
  // Return the user-specified "status" for the row.
  // NOTE : this routine is HIGHLY dependent of the internal layout
  // of the DOM!
  // TBD : How much error-checking should we have here?
    var col;
    var table_elem;
    var status;
    var ret_val;

    // Currently, the user-defined status is specified in the html as follows:
    // <TABLE>
    //   <TR>
    //     <TD align="center">Not Yet Examined</TD>
    //   </TR>
    //   ...... (another row, if there is a change-history to view...)
    // </TABLE>
    // Note : If there IS a user-defined status (and not "Not Yet Examined"),
    // it will be displayed in bold, as in :
    //     <TD align="center"><B>High</B></TD>
    // Because of this, we have to use ts_getInnerText() to get at the 
    // text for the status.  We try to avoid using this method because
    // of its highly recursive and therefore time-consuming nature, but 
    // in this case, I don't see a way around it.  Hopefully it won't be
    // too bad, for this structure.

    col = row.cells[top.STATUS_COLUMN];
    table_elem = col.getElementsByTagName("TABLE");
    status = table_elem[0].rows[0].cells[0];
    ret_val = ts_getInnerText(status);

    //alert("return val is " + ret_val);

    return ret_val;
}


function get_hidden_file_name_from_row(row) {
  // "row" is a row of the table in the "Message-Status" window.
  // Return the "hidden" file-name for the row.  This is the
  // the file-name that is used to index into the internal-arrays,
  // and NOT the file-name that is displayed in the Message-Status
  // window.
  // NOTE : this routine is HIGHLY dependent of the internal layout
  // of the DOM!
  // TBD : How much error-checking should we have here?

    var col;
    var hidden_form;
    var hidden_file_name;

    col = row.cells[top.FILE_NAME_COLUMN];
    hidden_form = col.getElementsByTagName("FORM");
    hidden_file_name = hidden_form[0].file_name.value;

    // alert("hidden file name is " + hidden_file_name);
    return hidden_file_name;
}

function get_file_name_from_row(row) {
  // "row" is a row of the table in the "Message-Status" window.
  // Return the "file-name" for the row.
  // NOTE : this routine is HIGHLY dependent of the internal layout
  // of the DOM!
  // TBD : How much error-checking should we have here?
  // TBD : Could there be embedded <WBR> tags?  If so, we should call
  // "get_text_string", as we do when we are getting the method-name.

    var file_name_node;

    file_name_node = row.cells[top.FILE_NAME_COLUMN];

    // TBD : childNode[0] appears to be a text-node which is a 
    // newline.  Not sure why.  childNode[1] is the "FORM" tag
    // (for the hidden file name).  childNode[2] is the file-name
    // that is displayed.

    return file_name_node.childNodes[2].nodeValue;
}

function get_text_string(node)
{
    // The child(ren) of "node" make up a text string.
    // If "node" has only one child, then the child's
    // "nodeValue" is what we're after.
    // Else, we're dealing with an HTML string containing
    // embedded <WBR> tags, such as :
    //     net.neurotech.<wbr>finance.Options
    // and we have to build up the text string from the
    // non-Tag nodes.

    var children;
    var text_str;

    children = node.childNodes;
    if (children.length == 1) {
        text_str = node.firstChild.nodeValue;
    } else {
        text_str = "";
        for (var i = 0; i < children.length; i++) {
            if (children[i].nodeName == "#text") {
                text_str = text_str + children[i].nodeValue;
            }
        }
    }
    return text_str;
}

function get_method_from_row(row) {
  // "row" is a row of the table in the "Message-Status" window.
  // Return the "method-name" for the row.
  // Note : the method name-could contain embedded <WBR> tags;
  // we want to return a fully-concatenated method name (i.e.,
  // without the <WBR> tags.
  // NOTE : this routine is HIGHLY dependent of the internal layout
  // of the DOM!
  // TBD : How much error-checking should we have here?

    var method_node;
    var ret_val;

    method_node = row.cells[top.METHOD_COLUMN];
    ret_val = get_text_string(method_node);

    return ret_val;
}

function get_line_num_from_row(row) {
  // "row" is a row of the table in the "Message-Status" window.
  // Return the "line-number" for the row.
  // NOTE : this routine is HIGHLY dependent of the internal layout
  // of the DOM!
  // TBD : How much error-checking should we have here?

    var line_node;

    line_node = row.cells[top.LINE_NUM_COLUMN];
    return line_node.firstChild.nodeValue;
}

function get_category_from_row(row) {
  // "row" is a row of the table in the "Message-Status" window.
  // Return the "message-category" for the row.
  // NOTE : this routine is HIGHLY dependent of the internal layout
  // of the DOM!
  // TBD : How much error-checking should we have here?

    var category_node;

    category_node = row.cells[top.CATEGORY_COLUMN];

    return category_node.firstChild.nodeValue;
}


//function get_original_prob_from_row(row) {
  // "row" is a row of the table in the "Message-Status" window.
  // Return the "original-probability" for the row.
  // NOTE : this routine is HIGHLY dependent of the internal layout
  // of the DOM!
  // TBD : How much error-checking should we have here?
    var prob_node;

    //prob_node = row.cells[top.ORIGINAL_PROB_COLUMN];
//
    //return prob_node.firstChild.nodeValue;
//}

function get_message_text_from_row(row) {
  // "row" is a row of the table in the "Message-Status" window.
  // Return the "method-text" for the row.
  // Note : the message-text could contain embedded <WBR> tags;
  // we want to return the fully-concatenated mesage-text (i.e.,
  // without the <WBR> tags).
  // NOTE : this routine is HIGHLY dependent of the internal layout
  // of the DOM!
  // TBD : How much error-checking should we have here?

    var message_text;
    var ret_val;

    message_text = row.cells[top.MESSAGE_TEXT_COLUMN];
    ret_val = get_text_string(message_text);

    return ret_val;
}


function OV_get_file_from_row(row) {
  // In the "Status Overview" window, return the file-name in the "row".
    var col;
    var anchor;

    col = row.cells[top.OV_FILE_NAME_COLUMN];
    anchor = col.getElementsByTagName("A");

    // TBD : could there be embedded WBR tags in the file-name?
    // See get_method_from_row for details.
    return anchor[0].firstChild.nodeValue;
}


function OV_get_category_from_row(row) {
  // In the "Status Overview" window, return the category in the "row".
    var col;

    col = row.cells[top.OV_CATEGORY_COLUMN];
    return col.firstChild.nodeValue;
}


function OV_get_high_from_row(row) {
  // In the "Status Overview" window, return the probability
  // "high" count in the "row".
    var col;

    col = row.cells[top.OV_HIGH_COLUMN];
    return col.firstChild.nodeValue;
}

function OV_get_med_from_row(row) {
  // In the "Status Overview" window, return the probability
  // "medium" count in the "row".
    var col;

    col = row.cells[top.OV_MEDIUM_COLUMN];
    return col.firstChild.nodeValue;
}


function OV_get_low_from_row(row) {
  // In the "Status Overview" window, return the probability
  // "low" count in the "row".
    var col;

    col = row.cells[top.OV_LOW_COLUMN];
    return col.firstChild.nodeValue;
}


function Init_Google_Gears() {
    var success = false;
    var rs;

    // alert("In Init_Google_Gears");

    //if (!window.google || !google.gears) {
    //location.href = "http://gears.google.com/?action=install&message=Welcome to SofCheck Inspector!" +
        //"&return=www.sofcheck.com";
  //} 
  
    top.gg_working = false;
    if (window.google && google.gears) {
        try {
          top.db = google.gears.factory.create('beta.database', '1.0');
          if (top.db) {
            top.db.open('Audits');
            success = true;
	    //alert("i created the database"); 
	    top.db.execute("CREATE TABLE IF NOT EXISTS Audit_Trail_Dbs(" +
	      "Db_Id INTEGER PRIMARY KEY AUTOINCREMENT ," +
	      "Db_Path VARCHAR(200)" + ");");

    	    top.db.execute("CREATE TABLE IF NOT EXISTS Audit_Trail (" +
	      "Audit_Id INTEGER PRIMARY KEY AUTOINCREMENT ," +
	      "Tstamp VARCHAR(15) ," +
	      "Db_Id INTEGER," +
	      "File_Id INTEGER," +
	      "Sev VARCHAR(20)," +
	      "Msg_Id INTEGER ," +
	      "Comment TEXT" +
	      ");");

    	    top.db.execute("CREATE TABLE IF NOT EXISTS Message_Files (" +
              "File_Id INTEGER PRIMARY KEY AUTOINCREMENT ," +
              "Db_Id INTEGER," +
              "Short_File_Name VARCHAR(30)," + 
              "Pkg_Name VARCHAR(30)" +
              ");");

            top.gg_working = true;
          }

    } catch (ex) {
      alert('Could not create database: ' + ex.message);
      return;
   }

   // Now, deal with the Db_Id.  Was this particular database already entered?
   rs = top.db.execute("SELECT Db_Id FROM Audit_Trail_Dbs WHERE DB_Path='" +
       top.shared_database + "'");
   if (rs.isValidRow()) {
       // not a new database
       top.databaseId = rs.field(0);
       //alert("top.databaseId is " + top.databaseId);
    } else {
        // we need to enter a new database, and get its id.
        rs = top.db.execute("INSERT INTO Audit_Trail_Dbs (Db_Path) " +
	  "VALUES(?)", [top.shared_database]);
        // the id was automatically generated, find it
        rs = top.db.execute("SELECT Db_Id FROM Audit_Trail_Dbs WHERE " +
	  "Db_Path='" + top.shared_database + "'");
        if (!rs.isValidRow()) {
	    alert("unexpected google gears error for db_id");
	} else {
            top.databaseId = rs.field(0);
        }
    }
   //alert("top.databaseId is " + top.databaseId);

 } 

}

function Init_Stuff()
{
    // These constants are also used for sorting the columns in the
    // Message-Status window.
    //top.STATUS_COLUMN = 0;
    //top.LINEAGE_COLUMN = 1;
    //top.MSG_ID_COLUMN = 2;
    //top.FILE_NAME_COLUMN = 3;  // hidden field
    //top.METHOD_COLUMN = 3;
    //top.LINE_NUM_COLUMN = 4;
    //top.CATEGORY_COLUMN = 5;
    //top.ORIGINAL_PROB_COLUMN = 6;
    //top.MESSAGE_TEXT_COLUMN = 7;

    top.STATUS_COLUMN = 0;
    top.LINEAGE_COLUMN = 1;
    top.MSG_ID_COLUMN = 2;
    top.FILE_NAME_COLUMN = 3;  // hidden field
    top.METHOD_COLUMN = 3;
    top.LINE_NUM_COLUMN = 4;
    top.CATEGORY_COLUMN = 5;
    top.MESSAGE_TEXT_COLUMN = 6;

    // These constants are used for message "severity", 
    // for the pull-down menu.
    // TBD: The pull-down menu is no longer there, but we'll keep
    // these constants defined here, since there is existing javascript
    // code that uses them.  Such Javascript code should NOT be called,
    // but it seems safer to leave the code in (for now), rather than
    // yanking out huge chunks of code.
    top.NOT_YET_EXAMINED = "not examined";
    top.NOT_A_BUG = "Not A Bug";
    top.IS_A_BUG = "is a bug";
    top.POSSIBLE_BUG_HIGH = "High";
    top.POSSIBLE_BUG_MED = "Medium";
    top.POSSIBLE_BUG_LOW = "Low";

    // These constants are used for the original probability
    top.ORIG_HIGH = "High";
    top.ORIG_MED = "Medium";
    top.ORIG_LOW = "Low";

    // Here's the default "Not Yet Examined", which we use in the
    // per-file message-status window.
    top.ORIG_NOT_YET_EXAMINED = "Not Yet Examined";
}

function Annotation_Report_Init_Stuff()
{
    // This method is a place-holder.  We used to define some constants
    // here, but we didn't need them anymore.
}


function Initialize_Msg_And_Line_Array()
{
    // Initialize top.Msg_And_Line_Array.  This method used to be called
    // on the open of the first -status.html file, but now we call it from
    // index_onload.  This is because the "Google-Gears database" (and the
    // internal data structures used to hold "deltas") might need to be
    // accessed before the message-status window comes up (i.e., when 
    // opening the "File Source" view and checking to see if any messages
    // in the file have changed since the last time that db_lister was run).

    top.Msg_And_Line_Array = new Array();

    Init_Stuff();
}


function Set_Up_Entry_In_Msg_And_Line_Array(file_name, msg_id,
  line_num, category, original_prob) 
  // Set up an entry in top.Msg_And_Line_Array[file_name], using the
  // data passed to this function.
{
    var entry;
    var index;
    var line_elem;
    var msg_elem;
    var msg_to_line_elem;

    entry = top.Msg_And_Line_Array[file_name];
    if (entry == null) {
        entry = new New_Msg_Line_Entry_Per_File(file_name);

	// Set up so Msg_And_Line_Array can be indexed by either
	// numerical index or file_name string.
	top.Msg_And_Line_Array[top.Msg_And_Line_Array.length] = entry;
	top.Msg_And_Line_Array[file_name] = entry;
    }

    // The Line_To_Msgs_Array can be indexed via standard numerical
    // indexing, or with a string representing the line-number.
    // NOTE : the line-number string needs some sort of prefix : e.g.,
    // "ln_222", instead of "222"; else, it gets confused with the 
    // numerical index of 222.
    line_elem = entry.Line_To_Msgs_Array["ln_" + line_num];
    if (line_elem == null) {
        // Set up so Line_To_Msgs_Array can be indexed by either
        // numerical index or line-number string.
        line_elem = new New_Line_Elem(line_num);
        entry.Line_To_Msgs_Array[entry.Line_To_Msgs_Array.length] = line_elem;
        entry.Line_To_Msgs_Array["ln_" + line_num] = line_elem;
    }

    // In the Line_To_Msgs_Array, the msg-ids are stored in an array,
    // since there may be more than one message on a given line.
    // Each msg-id is also associated with its original-probability.
    msg_elem = new New_Msg_Elem(msg_id, original_prob);
    line_elem.msg_array[line_elem.msg_array.length] = msg_elem;


    // Allocate a new entry in the Msg_To_Line_Elem array :
    // i.e. this msg-id is on this line.
    // msg_to_line_elem = new New_Msg_To_Line_Elem(msg_id, line_num);
    msg_to_line_elem = new New_Msg_To_Line_Elem_Super(msg_id,
      line_num, category, original_prob);

    // Set up so Msg_To_Line_Array can be indexed by either
    // numerical index or msg-id string.
    // NOTE : the msg-id string needs some sort of prefix : e.g.,
    // "id_5769", instead of "5769"; else, it gets confused with the 
    // numerical index of 5769.
    entry.Msg_To_Line_Array[entry.Msg_To_Line_Array.length] = msg_to_line_elem;
    index = "id_" + msg_id;
    entry.Msg_To_Line_Array[index] = msg_to_line_elem;
}

// TBD : Change name of function.
function Set_Up_Line_Numbers(file_name)
    // This function is called "onLoad" of the Message-Status window,
    // for "file_name".  (There is a separate message-status file, for
    // each file that contains messages.)
    // It "parses" the html Message-Status table, and builds up
    // up some internal data structures (with data that the user will
    // NOT change!), such as top.Msg_And_Line_Array (which maps
    // msg-id to line-number, as well as line-number to {msg-ids-on-that-line}.
    // Note : We only do this, the first-time that we open "file_name"'s
    // message-status file.  After that, the internal data structure persists, 
    // even if the user closes the window and then reopens it.
    // NOTE : this routine is HIGHLY dependent of the internal layout
    // of the DOM!
{
    var msg_status_win;


    msg_status_win = top.Popups["message_status_" + top.unique_suffix];

    // Initialize top-level data structure, the first time the
    // Message Status window is open.  After the first time, the
    // data persists; the line-to-message-id correspondence (and
    // vice versa) won't change when the user changes message status.
    //if (top.Msg_And_Line_Array == null) {
	//Initialize_Msg_And_Line_Array();
    //}

    // alert("the file name is set up line numbers is " + file_name);
    // Get the root of the Message-Status table.
    // TBD : Change name of ID
    table = msg_status_win.document.getElementById("mytable");

    // TBD : Error-handling?
    if (table == null) {
	alert("Unexpected null table in Set_Up_Line_Numbers");
    }


    // The first row (i.e., 0th index) is the header, so start iterating
    // with the second (i.e., 1st index) row.
    for (var i=1; i < table.rows.length; i++)  {
        // NOTE : the following code is HIGHLY dependent of the internal layout
        // of the DOM!

        var file_name;
        var line_num;
        var msg_id;
	var status;
	var category;
        var table;

	// Get the status.  (This is the status with which this Inspector
	// session starts.)
	status = get_status_from_row(table.rows[i]);

        // Get the msg-id.
        msg_id = get_msg_id_from_row(table.rows[i]);
	//alert("msg_id is " + msg_id);

	// The correct file-name, which we use for indexing all these arrays,
	// is in the "hidden form" (not the file-name that is displayed!).
        file_name = get_hidden_file_name_from_row(table.rows[i]);

        // Get the line-number.
        line_num = get_line_num_from_row(table.rows[i]);
        //alert("line_num is " + line_num);

	//original_prob = get_original_prob_from_row(table.rows[i]);
	// alert("original_prob is " + original_prob);

	 category = get_category_from_row(table.rows[i]);

	 Set_Up_Entry_In_Msg_And_Line_Array(file_name,
	   msg_id, line_num, category, status);
     }
}

// SORTING
// TBD : Some of the sorting algorithms may need to be fine-tuned.

function rank_status(status) {
    // Associate a numerical ranking for the user-specified "status"
    // (for ordering during sort).

    // The possible values for the user-defined status are currently
    // taken from (some of) the BE_Messages.Message_Severity_Level-types.
    // Note that we should only see a "Suppressed" message as part of
    // the audit-trail, but not as the most-recent user-defined status;
    // a "Suppressed" message will NOT be part of the generated .html,
    // thanks to the message-patterns.
    switch(status) {
	case top.ORIG_HIGH:
	    return 1;
	case top.ORIG_MED :
	    return 2;
	case top.ORIG_LOW:
	    return 3;
        case top.ORIG_NOT_YET_EXAMINED :
	    return 4;
    }
}

function ONLY_sortByStatus(a,b) {
    // (file-specific) Message Status window : sort ONLY by
    // user-specified status;
    // if the status is the same, then do not attempt to sort
    // by any other criteria.
    // See rank_status for algorithm.

    var a_status;
    var b_status;

    a_status = get_status_from_row(a);
    b_status = get_status_from_row(b);

    if (a_status == b_status) return 0;
    if (rank_status(a_status) < rank_status(b_status)) return -1;
    return 1;
}


function ONLY_sortByStatusAndProbability(a,b) {
    // (file-specific) Message Status window : sort by user-specified status;
    // if both are "Not Yet Examined", then sort ONLY by (original) probability
    // Otherwise, if both are the same, then do not attempt to sort
    // by any other criteria.
    // See rank_status for algorithm.

    var a_status;
    var b_status;
    var result;

    a_status = get_status_from_row(a);
    b_status = get_status_from_row(b);

    if ((a_status == top.NOT_YET_EXAMINED) &&
        (b_status == top.NOT_YET_EXAMINED)) {
        result = ONLY_sortByProbability(a,b);
	return result;
    } else {
        if (a_status == b_status) return 0;
        if (rank_status(a_status) < rank_status(b_status)) return -1;
        return 1;
    }
}

function sortByStatus(a,b) {
    // (file-specific) Message Status window : sort by user-specified status.
    // The sorting order is :
    //  status => line # => category => msg-id

    var result;

    result = ONLY_sortByStatusAndProbability(a,b);
    if (result != 0) {
	// user-specified status does not match
	return result;
    } else {
	// user-specified status matches; sort by line-#
	result = ONLY_sortByLine(a,b);
	if (result != 0) {
	    // line-#s do not match
	    return result;
	} else {
	    // line-#s match; sort by category
	    result = ONLY_sortByCategory(a,b);
	    if (result != 0) {
		// category does not match
		return result;
	    } else {
		// category matches; sort by msg-id
		result = ONLY_sortById(a,b);

		// At this point, the msg-id will be unique,
		// so the sort should NOT return 0.
		return result;
	    }
        }
    }
}

function rank_lineage(lineage) {
    // Associate a numerical ranking for the lineage
    // (i.e., unchanged/added/dropped), for ordering during sort.
    switch(lineage) {
	case "+":
	    return 1;
	case "U":
	    return 2;
	case "-":
	    return 3;
      default :
          alert("Unexpected lineage " + lineage + " in rank_lineage");
	  return 0;
    }
}

function ONLY_sortByLineage(a,b) {
    // (file-specific) Message Status window : sort ONLY by
    // lineage (i.e., unchanged/added/dropped);
    // if the lineage is the same, then do not attempt to sort
    // by any other criteria.
    // See rank_lineage for algorithm.

    var a_lineage;
    var b_lineage;

    a_lineage = get_lineage_from_row(a);
    b_lineage = get_lineage_from_row(b);

    if (a_lineage == b_lineage) return 0;
    if (rank_lineage(a_lineage) < rank_lineage(b_lineage)) return -1;
    return 1;
}

function sortByLineage(a,b) {
    // (file-specific) Message Status window : sort by lineage
    // (i.e., unchanged/added/dropped).
    // The sorting order is :
    //  lineage => status => line # => category => msg_id

    var result;

    result = ONLY_sortByLineage(a,b);
    if (result != 0) {
	// lineage does not match
	return result;
    } else {
        // lineage matches; sort by status
        result = ONLY_sortByStatusAndProbability(a,b);
	if (result != 0) {
	    // status does not match
	    return result;
        } else  {
	    // status matches; sort by line-#
	    result = ONLY_sortByLine(a,b);
	    if (result != 0) {
	        // line-#s do not match
	        return result;
	    } else {
	        // line-#s match; sort by category
	        result = ONLY_sortByCategory(a,b);
	        if (result != 0) {
		    // category does not match
		    return result;
	        } else {
		    // category matches; sort by msg-id
		    result = ONLY_sortById(a,b);

		    // At this point, the msg-id will be unique,
		    // so the sort should NOT return 0.
		    return result;
	        }
	    }
        }
    }
}



function ONLY_sortById(a,b) {
    // (file-specific) Message Status window : sort (numerically) ONLY
    // by message-id.
    // Note : message-ids cannot match, they are necessarily unique.

    var a_msg_id;
    var b_msg_id;

    a_msg_id = get_msg_id_from_row(a);
    b_msg_id = get_msg_id_from_row(b);

    // TBD : I think we could get here, if 'a' and 'b' are the same entry.
    // Is it kosher to return 0 then?
    if (a_msg_id == b_msg_id) return 0;

    // TBD : Tucker says that we should display the higher id first,
    // but I'm not sure I agree with that.  Checking for "lineage" before
    // message-id should make sure we get "added" before "dropped".
    if (parseInt(a_msg_id) < parseInt(b_msg_id)) return -1;
    return 1;
}

function sortById(a,b) {
    // (file-specific) Message Status window : sort by message-id.
    // Note : message-ids cannot match, they are necessarily unique.

    var result;

    result = ONLY_sortById(a,b);

    // message-ids are unique, so result will never be 0.
    // TBD : what if 'a' and 'b' are the same?

    return result;
}


function ONLY_sortByMethod(a,b) {
    // (file-specific) Message Status window : sort (alphabetically) ONLY
    // by method name;
    // if the method names are the same, then do not attempt to sort
    // by any other criteria.
    // TBD : what about case?

    var a_method;
    var b_method;

    a_method = get_method_from_row(a);
    b_method = get_method_from_row(b);

    if (a_method == b_method) {
        return 0;
    } 
    if (a_method < b_method) return -1;
    return 1;
}


function sortByMethod(a,b) {
    // (file-specific) Message Status window : Sort by method name.
    // The sorting order is :
    //   method name => status => line # => category => message-text =>
    //    lineage => msg-id
    var result;

    result = ONLY_sortByMethod(a,b);
    if (result != 0) {
	// method names do not match
	return result;
    } else {
	// method names match; sort by user-defined status
	result = ONLY_sortByStatusAndProbability(a,b);
	if (result != 0) {
	    // status does not match
	    return result;
	} else {
	    // status matches; sort by line-#
	    result = ONLY_sortByLine(a,b);
	    if (result != 0) {
		// line-#s do not match
		return result;
	    } else {
		// line-#s matches, sort by category
		result = ONLY_sortByCategory(a,b);
		if (result != 0) {
		    // category does not match
		    return result;
		} else {
		    // category matches, sort by message-text
		    result = ONLY_sortByMessageText(a,b);
		    if (result != 0) {
		        // message-text does not match
			return result;
		    } else {
		        // message-text matches, sort by lineage
			result = ONLY_sortByLineage(a,b);
			if (result != 0) {
			    // lineage does not match
			    return result;
		        } else {
			    // lineage matches, sort by msg-id
		            result = ONLY_sortById(a,b);

		            // At this point, the msg-id will be unique,
		            // so the sort should NOT return 0.
		            return result;
		 	}
		    }
	        }
	    }
	}
    }
}

function ONLY_sortByLine(a,b) {
    // (file-specific) Message Status window : sort ONLY by line-number; 
    // if the line-numbers are the same, then do not attempt to sort
    // by any other criteria.

    var a_line;
    var b_line;

    a_line = get_line_num_from_row(a);
    b_line = get_line_num_from_row(b);

    if (a_line == b_line) return 0;
    if (parseInt(a_line) < parseInt(b_line)) return -1;
    return 1;
}


function sortByLine(a,b) {
    // (file-specific) Message Status window : sort by line-number.
    // The sorting order is :
    //  line-number => status => category => message-text => 
    //    lineage => msg-id
    var result;

    result = ONLY_sortByLine(a,b);
    if (result != 0) {
	// line-numbers do not match
	return result;
    } else {
	// line-numbers match; sort by status
	result = ONLY_sortByStatusAndProbability(a,b);
	if (result != 0) {
	    // user-specified status does not match
	    return result;
	} else {
	    // user-specified status matches, sort by category
	    result = ONLY_sortByCategory(a,b);
	    if (result != 0) {
		// category does not match
		return result;
            } else {
	        // category matches; sort by message-text
		result = ONLY_sortByMessageText(a,b);
		if (result != 0) {
		    // message text does not match
		    return result;
	        } else {
		    // message text matches; sort by lineage
	            result = ONLY_sortByLineage(a,b);
		    if (result != 0) {
		        // lineage does not match
		        return result;
	            } else {
                        // lineage matches, sort by msg-id
		        result = ONLY_sortById(a,b);
    
		        // At this point, the msg-id will be unique,
		        // so the sort should NOT return 0.
		        return result;
	            }
                }
	    }
        }
    }
}

function rank_orig_prob(prob) {
    // Associate a numerical ranking for the original probability
    // (for ordering during sort).
    switch(prob) {
	case top.ORIG_HIGH :
	    return 1;
	case top.ORIG_MED:
	    return 2;
	case top.ORIG_LOW :
	    return 3;
    }
}

function ONLY_sortByMessageText(a,b) {
    // (file-specific) Message Status window : sort ONLY by 
    // message-text; if the message-text is the same,
    // then do not attempt to sort by any other criteria.

    var a_text;
    var b_text;

    a_text = get_message_text_from_row(a);
    b_text = get_message_text_from_row(b);

    if (a_text == b_text) return 0;
    if (a_text < b_text) return -1;
    return 1;
}

//function ONLY_sortByProbability(a,b) {
    // (file-specific) Message Status window : sort ONLY by 
    // original probability; if the original probabilities are the same,
    // then do not attempt to sort by any other criteria.
    // Note : See rank_orig_prob for algorithm.

    //var a_prob;
    //var b_prob;
//
    //a_prob = get_original_prob_from_row(a);
    //b_prob = get_original_prob_from_row(b);
//
    //if (a_prob == b_prob) return 0;
    //if (rank_orig_prob(a_prob) < rank_orig_prob(b_prob)) return -1;
    //return 1;
//}


//function sortByProbability(a,b) {
    //// (file-specific) Message Status window : Sort by original probability. 
    //// The sorting order is :
    ////   probability =>  status => line # => category => message-text =>
    ////    lineage => msg-id
//
    //var result;
//
    //result = ONLY_sortByProbability(a,b);
    //if (result != 0) {
	//// probabilities do not match
	//return result;
    //} else {
	//// probabilities match; sort by status;
	//result = ONLY_sortByStatus(a,b);
	//if (result != 0) {
	    //// status does not match
	    //return result;
	//} else {
	    //// status matches; sort by line-#
	    //result = ONLY_sortByLine(a,b);
	    //if (result != 0) {
		//// line-#s do not match
		//return result;
	    //} else {
		//// line-#s match; sort by category
		//result = ONLY_sortByCategory(a,b);
		//if (result != 0) {
		    //// categories do not match
		    //return result;
	        //} else {
		    //// categories match; sort by message text
		    //result = ONLY_sortByMessageText(a,b);
		    //if (result != 0) {
		        //// message text does not match
		        //return result;
	            //} else {
		        //// message text matches; sort by lineage
	                //result = ONLY_sortByLineage(a,b);
		        //if (result != 0) {
		            //// lineage does not match
		            //return result;
	                //} else {
			    //// lineage matches; sort by msg-id
			    //result = ONLY_sortById(a,b);
//
			    //// At this point, the msg-id will be unique,
			    //// so the sort should NOT return 0.
			    //return result;
		        //}
	            //}
		//}
	    //}
	//}
    //}
//}


function ONLY_sortByCategory(a,b) {
    // (file-specific) Message Status window : sort (alphabetically) ONLY 
    // by category;
    // if the categories are the same, then do not attempt to sort by 
    // any other criteria.
    var a_category, b_category;

    a_category = get_category_from_row(a);
    b_category = get_category_from_row(b);

    if (a_category == b_category) return 0;
    if (a_category < b_category) return -1;
    return 1;
}


function sortByCategory(a,b) {
    // (file-specific) Message Status window : sort by category.
    // The sorting order is :
    //  category => status => line-# => message-text =>
    //    lineage => msg-id
    var result;

    result = ONLY_sortByCategory(a,b);
    if (result != 0) {
	// categories do not match
	return result;
    } else {
	// categories match; sort by status
	result = ONLY_sortByStatusAndProbability(a,b);
	if (result != 0) {
	    // status does not match
	    return result;
        } else {
	    // status matches; sort by line-#
	    result = ONLY_sortByLine(a,b);
	    if (result != 0) {
		// line-#s do not match
		return result;
	    } else {
	        // line-#s match, sort by message-text
		result = ONLY_sortByMessageText(a,b);
	        if (result != 0) {
		    // message-text does not match
		    return result;
	        } else {
		    // message-text matches; sort by lineage
		    result = ONLY_sortByLineage(a,b);
		    if (result != 0) {
		        // lineage does not match;
			return result;
		    } else {
		        // lineage matches; sort by msg-id
		        result = ONLY_sortById(a,b);

		        // At this point, the msg-id will be unique,
		        // so the sort should NOT return 0.
		        return result;
	            }
	        }
	    }
	}
    }
}


function sortTable(link) {
    // TBD : Give credit where credit is due, for this routine.
    // (Where did I get it?!)
    // The main idea for sorting is to read the rows of the table into
    // a JavaScript array, and then use the array's build-in sort method.

    var my_win;
    var table;

    // TBD : When we change the id, change this.
    my_win = top.Popups["message_status_" + top.unique_suffix];
    table = my_win.document.getElementById("mytable");

    // TBD : Error-handling?
    if (table == null) {
        alert("Unexpected null table in sortTable");
    }

    var firstRow = new Array();

    var newRows = new Array();

    // TBD : I think we don't need to save the "firstRow" header, do we?
    // The first row of the table is the header.
    for (var i=0; i < table.rows[0].length; i++)  {
        firstRow[i] = table.rows[0][i];
    }
    for (var j=1; j < table.rows.length; j++) {
        newRows[j-1] = table.rows[j];
    }

    switch(link.firstChild.nodeValue) {
      case "Status" :
	newRows.sort(sortByStatus);
	break;
      case "+/-" :
	newRows.sort(sortByLineage);
	break;
      case "Msg Id" :
	newRows.sort(sortById);
	break;
      case "Method" :
	newRows.sort(sortByMethod);
	break;
      case "Line" :
	newRows.sort(sortByLine);
	break;
      case "Message Category" :
	newRows.sort(sortByCategory);
	break;
      case "Original Probability" :
	newRows.sort(sortByProbability);
	break;
    }

    // We appendChild rows that already exist to the tbody, so it moves
    // them rather than creating new ones
    // don't do sortbottom rows
    for (var i=0;i<newRows.length;i++) {
        table.tBodies[0].appendChild(newRows[i]);
    }
}


function OV_ONLY_sortByFile(a,b) {
    // Status Overview window : sort ONLY by file-name; if the file names
    // are the same, then do not attempt to sort by any other criteria.
    // Note : Does not include directory name.
    // TBD : Should we lower-case the file-names, for the comparison?

    var a_file;
    var b_file;
    var a_file_lower;
    var b_file_lower;

    a_file = OV_get_file_from_row(a);
    b_file = OV_get_file_from_row(b);

    a_file_lower = a_file.toLowerCase();
    b_file_lower = b_file.toLowerCase();

    if (a_file_lower == b_file_lower) {
        return 0;
    }  
    if (a_file_lower < b_file_lower) return -1;
    return 1;
}

function OV_sortByFile(a,b) {
    // Status Overview window : sort by file name.
    // The sorting order is :
    //    file name => probability => category
    var result;

    result = OV_ONLY_sortByFile(a,b);
    if (result != 0) {
	// file names do not match
        return result;
    } else {
	// file names match; sort by probability
        result = OV_ONLY_sortByProbability(a,b);
        if (result != 0) {
	    // probabilities do not match
	    return result;
	} else {
	    // probabilities match; sort by category
            result = OV_ONLY_sortByCategory(a,b);

	    // TBD : at this point, we don't expect the categories to match,
	    // so the sort should NOT return "0".
	    return result;
	}
    }

}


function OV_ONLY_sortByCategory(a,b) {
    // Status Overview window : sort ONLY by category; if the categories
    // are the same, then do not attempt to sort by any other criteria.
    var a_category, b_category;

    a_category = OV_get_category_from_row(a);
    b_category = OV_get_category_from_row(b);

    if (a_category == b_category) {
        return 0;
    }

    if (a_category < b_category) return -1;
    return 1;
}

function OV_sortByCategory(a,b) {
    // Status Overview window : sort by category.
    // The sorting order is :
    //    category => probability => file name
    var result;

    result = OV_ONLY_sortByCategory(a,b);
    if (result != 0) {
	// categories do not match
        return result;
    } else {
	// categories match; sort by probability
        result = OV_ONLY_sortByProbability(a,b);
	if (result != 0) {
	    // probabilities do not match
	    return result;
        } else {
	    // probabilities match, sort by file name
	    result = OV_ONLY_sortByFile(a,b);

	    // TBD : at this point, we don't expect the file names to match,
	    // so the sort should NOT return "0".
	    return result;
	}
    }
}


function OV_ONLY_sortByProbability(a,b) {
    // Status Overview window : sort ONLY by probability; if the probabilities
    // are the same, then do not attempt to sort by any other criteria.
    // To sort by probabability, have to compare the message counts in the
    // following order :
    //   high => medium => low
    // Note : this is a "reverse" sort, where the higher-ranked count
    // comes first.

    var a_high, b_high;
    var a_med, b_med;
    var a_low, b_low;

    a_high = parseInt(OV_get_high_from_row(a));
    b_high = parseInt(OV_get_high_from_row(b));

    if (a_high == b_high) {
      a_med = parseInt(OV_get_med_from_row(a));
      b_med = parseInt(OV_get_med_from_row(b));

      if (a_med == b_med) {
        a_low = parseInt(OV_get_low_from_row(a));
	b_low = parseInt(OV_get_low_from_row(b));

	if (a_low == b_low) {
	  return 0;
        } else if (a_low < b_low) {
	  return 1;
        } else {
	  return -1;
	}
      } else if (a_med < b_med) {
	return 1;
      } else {
	return -1;
      }
   } else if (a_high < b_high) {
     return 1;
   } else {
     return -1;
   }
} 

function OV_sortByProbability(a,b) {
    // Status Overview window : sort by probability.
    // The sorting order is :
    //    probability => file name => category
    var result;

    result = OV_ONLY_sortByProbability(a,b);
    if (result != 0) {
	// probabilities do not match
        return result;
    } else {
        // probabilities match; sort by file name
        result = OV_ONLY_sortByFile(a,b);
        if (result != 0) {
	    // file names do not match
            return result;
        } else {
	    // file names match, sort by category
            result = OV_ONLY_sortByCategory(a,b);

	    // TBD : at this point, we don't expect the categories to match,
	    // so the sort should NOT return "0".
	    return result;
        }
    }
}

function Sorted(a,b) {
    // This is the comparison-part of the shell-sort routine;
    // "a" and "b" are rows in the table being sorted.
    // This returns -1, 0, or +1 according to whether a should precede b,
    // a and b are the same, or a should follow b.

    switch(top.OV_sort_column) {
      case "File" :
	return OV_sortByFile(a,b);
      case "Category" :
	return OV_sortByCategory(a,b);
      case "Probability" :
	return OV_sortByProbability(a,b);
      default :
	  alert("Unexpected value " + top.OV_sort_column +
	    " in Sorted");
          return -1;
    }
}

function OV_set_visibility_stop_sort(status)
    // In the Status Overview window ("ov_win"), set the "Stop Sorting"
    // button's visibility to "status" (i.e., either "visble" or "hidden").

{
    var ov_win = top.Popups["status_overview_" + top.unique_suffix];
    var stop_sort;

    stop_sort = ov_win.document.getElementById("OV_stop_sort_button");
    stop_sort.style.visibility = status;
}

function OV_set_column_header(sortable_rows)
    // In the Status Overview window, set the text of one of the
    // column-headers to "html_str", and set its background color
    // to "bg_color".
{
    if (sortable_rows > 0) {
	var html_str = top.OV_sort_column + 
          "<BR>" +  "Sorting " + sortable_rows + " rows...";
	top.OV_column_header.innerHTML = html_str;
	top.OV_column_header.style.backgroundColor = "yellow";
    } else {
	top.OV_column_header.innerHTML = top.OV_sort_column;
	top.OV_column_header.style.backgroundColor = "white";
    }
}

function OV_stop_sort(how_stop)
    // In the Status Overview window, the sorting has stopped.
    // "how_stop" is either "user" (i.e., a click on the "Stop Sorting"
    // button, or a request for a sort, while another sort is ongoing), 
    // or "normal" (i.e., the sort ends normally).
{
    var ov_win = top.Popups["status_overview_" + top.unique_suffix];

    // Hide the "Stop Sort" button; it only shows while sorting.
    OV_set_visibility_stop_sort("hidden");

    if (how_stop == "normal") {
	// no longer sorting
	top.OV_sorting = false;
	top.OV_abort_sorting = false;

	// The header for the column that we are sorting is changed
	// during the sort; set it back.
	OV_set_column_header(0);

	// Startup next sort if any
	if (top.OV_next_sort != null) {
	    var next = top.OV_next_sort;

	    top.OV_next_sort = null;
	    // Begin next sort
	    OV_sortTable(next);
	}

    } else if (top.OV_sorting) {
	// Abort current sorting
	top.OV_abort_sorting = true;
    }

}

function OV_sort_part()
    // In the Status Overview window, sort (via column headers) using
    // the "quick sort" algorithm.  We used to use the built-in "sort"
    // method, but it was too CPU-intensive; i.e., for large tables,
    // it cranked and cranked and was not responsive.
    // This implementation uses "setTimeout", so that pending events can
    // be handled during the sort.
    // The sort is done "in place", in the DOM, and not on a copy of the table.
    // So, when the setTimeout timer goes off, you have the visual of the 
    // (so far) sorted rows being updated; you can see the progress,
    // rather than waiting until the end of the whole sort.
    // This method expects the following quick-sort variables to be set :
    //   top.qs_table      => DOM table being sorted
    //   top.qs_array      => javascript array that is a copy of DOM table
    //   top.qs_end        => index of just-after-the-last element
    //   top.qs_start      => index of first element 
    //   top.qs_ptnstarts  => start indices of partitions needing to be sorted
    //   top.qs_ptnends    => just-past-the-end indices of ptns to be sorted
{
    var QS_MOVES_LIMIT = 200;       // we pause every 200 "moves"
    var QS_MOVES_EXTRA = 100;       // we stop immediately if we exceed limit
				    // by this amount
    var QS_PAUSES_PER_DISPLAY = 4;  // we display every 3 pauses
    var QS_MAX_BUILTIN_SORT = 150;  // we use builtin sort when ptn.len <= 150
    var QS_MOVES_PER_BUILTIN_SORT = 1;  
				    // a builtin sort counts as 1 move per elem

    var num_moves;
    var ov_win = top.Popups["status_overview_" + top.unique_suffix];

    // Keep track of how many sorting "moves" we have made.  When we 
    // reach a hard-coded limit, we call ourself recursively via setTimeout, 
    // and then return.  This allows pending events (such as the display
    // of the sorted rows, and user-clicks) to be handled.
    num_moves = 0;

    // This is the non-recursive quick-sort.  Algorithm adapted from
    // "Algorithms + Data Structures = Programs" by
    // Niklaus Wirth, E.T.H. University.

    // The basic idea is that we keep partitioning until we get down
    // to a size that we believe the built-in array sort can handle
    // without freezing the client machine.  We pause now and then during
    // the process.

    if (top.OV_abort_sorting && top.OV_next_sort != null) {
	// Quit immediately
	OV_stop_sort("normal");
	return;
    }


    while (!top.OV_abort_sorting && top.qs_ptnstarts.length > 0) {

        if (num_moves >= QS_MOVES_LIMIT) {
	    // call ourselves recursively with setTimeout;
	    // allows pending events to be handled.
	    top.OV_timer = setTimeout("OV_sort_part()",0);
	    return;  /// pause for breath ///
	}

        // Get partition, and if too big, split it into two
        var ptn_start = top.qs_ptnstarts.pop();
        var ptn_end = top.qs_ptnends.pop();
      
        if (ptn_end - ptn_start > QS_MAX_BUILTIN_SORT) {
	    // need to split partition 
	    var mid = top.qs_array[Math.floor((ptn_start + ptn_end)/ 2)];
	    var left = ptn_start;
	    var right = ptn_end;

	    while (!top.OV_abort_sorting) {

		if (num_moves > QS_MOVES_LIMIT+QS_MOVES_EXTRA) {
		    // Put partition back on list since not yet done
		    top.qs_ptnstarts.push(ptn_start);
		    top.qs_ptnends.push(ptn_end);
		    top.OV_timer = setTimeout("OV_sort_part()",0);
		    return;  /// pause for breath ///
		}

		while (Sorted(top.qs_array[left], mid) < 0) { 
		    ++left; 
		}
		do { 
		    --right; 
		} while (Sorted(mid, top.qs_array[right]) < 0);

		if (left >= right) break;
		    
		// swap the two out-of-order elements
		var temp = top.qs_array[right];
		top.qs_array[right] = top.qs_array[left];
		top.qs_array[left] = temp;

		// Keep track of number of swaps
		++num_moves;
	    }


	    // partition has been split into two
	    if (left > ptn_start + 1) {
		top.qs_ptnstarts.push(ptn_start);
		top.qs_ptnends.push(left);
	    }

	    if (++right < ptn_end - 1) {
		top.qs_ptnstarts.push(right);
		top.qs_ptnends.push(ptn_end);
	    }

	    // One row has been sorted (thanks to the partitioning)
	    --top.OV_num_sortable_rows;
        } else {
	    // do the builtin sort

	    var ptn = top.qs_array.slice(ptn_start, ptn_end);
	    ptn.sort(Sorted);

	    // copy back
	    for (var i = ptn_start; i < ptn_end; i++) {
		top.qs_array[i] = ptn[i-ptn_start];
	    }
	    num_moves += QS_MOVES_PER_BUILTIN_SORT*(ptn_end-ptn_start);

	    // Keep track of number of unsorted rows
	    top.OV_num_sortable_rows -= (ptn_end - ptn_start);

        }
	if (top.OV_num_sortable_rows > 0) {
	    // Update count in header to keep user amused
	    OV_set_column_header(top.OV_num_sortable_rows);
	}

    }

    // If we reach here, then the sort has come to a "normal" conclusion,
    // or has been stopped prematurely.
    // Copy back the sorted array
    // NOTE: We do this by building up a new "tbody" and then replacing
    //       the old one, to try to reduce "churn" in borwsers that
    //       show changes in "real time" (e.g. Internet Explorer)

    if (top.qs_end > 50) {
	// We have a relatively large table, do the replacement
	// "behind" the scenes.
	// First we wipe out the existing tbody
	var empty_tbody = ov_win.document.createElement("tbody");

	// We put a few rows in so it doesn't flash the whole screen
	for (var i = 0; i < 10; i++) {
	    empty_tbody.appendChild(top.qs_array[i].cloneNode(true));
	}
	top.qs_table.replaceChild(empty_tbody, top.qs_table.tBodies[0]);

	// Next we move the data from the array to a new body
	var new_tbody = ov_win.document.createElement("tbody");
	for (var i = 0; i < top.qs_end; i++) {
	   new_tbody.appendChild(top.qs_array[i]);
	}

	// Finally we replace the empty body with the new body
	top.qs_table.replaceChild(new_tbody, empty_tbody);
    } else {
	// No need to play any games, just do the replacement
	for (var i = qs_end; --i > top.qs_start; ) {
	   top.qs_table.tBodies[0].insertBefore(
	     top.qs_array[i-1], top.qs_array[i]);
	}
    }

    //ov_win.alert("num moves is " + num_moves);

    // Make sure everything is marked as complete
    OV_stop_sort("normal");

}

function OV_sortTable(link) {
    // Sort the table in the Status Overview window, based on the "link"
    // (i.e.,  based on the column that has been clicked).

    // Currently, there are two rows in the "header" of the Status Overview
    // window.
    var NUM_ROWS_IN_HEADER = 2;

    var elem_name;
    var sortable_rows;
    var html_str;

    // If we are already sorting, stop it, before we start again.
    if (top.OV_sorting == true) {
	OV_stop_sort("user");
    }

    if (top.OV_sorting == true) {
	// Just record new sort request if we don't stop immediately
	top.OV_next_sort = link;
	return;
    }

    top.OV_next_sort = null;

    // get a handle on the Status Overview window
    var ov_win = top.Popups["status_overview_" + top.unique_suffix];

    // get a handle on the table in the Status Overview window
    top.qs_table = ov_win.document.getElementById("status_overview_table");
    if (top.qs_table == null) {
        alert("Unexpected null table in OV_sortTable");
    }

    // Save the name of the column header that the user has clicked,
    // to keep track of which kind of sorting we are doing.
    // Note : the order of execution is important here.  We don't want
    // to set top.OV_sort_column until we have called OV_stop_sort,
    // above (which needs the previous value of top.OV_sort_column,
    // to stop the previous sort).
    top.OV_sort_column = link.firstChild.nodeValue;

    // When we sort, we modify the column header to illustrate that we are
    // sorting by that column, and also say how many rows we are sorting.
    top.OV_num_sortable_rows = top.qs_table.rows.length - NUM_ROWS_IN_HEADER;
    elem_name = "sort_by_" + link.firstChild.nodeValue;
    top.OV_column_header = ov_win.document.getElementById(elem_name);
    OV_set_column_header(top.OV_num_sortable_rows);

    // set-up variables for shell-sort 
    top.qs_start = NUM_ROWS_IN_HEADER;
    top.qs_end = top.qs_table.rows.length;

    // Copy qs_table into an array for faster sorting
    top.qs_array = new Array();
    for (var i = 0; i < top.qs_end; i++ ) {
        top.qs_array[i] = top.qs_table.rows[i];
    }

    // initialize the partition arrays
    top.qs_ptnstarts = new Array();
    top.qs_ptnends = new Array();

    // set up the initial partition
    top.qs_ptnstarts.push(top.qs_start);
    top.qs_ptnends.push(top.qs_end);

    // count the number of pauses
    top.qs_num_pauses = 0;

    // Keep track of the fact that we're in the middle of sorting.
    top.OV_sorting = true;
    top.OV_abort_sorting = false;

    // When we're sorting, "un-hide" the "Stop Sorting" button.
    OV_set_visibility_stop_sort("visible");

    // Need to call this with "setTimeout" so the above change to the
    // column header will display *before* the sort.
    top.OV_timer = setTimeout("OV_sort_part()",0);
}


// ----------------------------------------------

function go_back_dir_levels(num_levels)
    // Return a string which is a sequence of "../",
    // according to the number of levels ("num_levels").
{
    
    var str = "";

    if (num_levels <= 0) {
	return "";
    } else {
	for (var i = 0; i < num_levels; i++) {
	    str = str + "../";
	}
	return str;
    }
}

function Back_To_Html_Directory(file_name) {
    // "file_name" is something like foo/moo/loo.java-msgmap.html,
    // which is meant to be relative to the html directory.
    // We need to get back to the html directory, so return (a sequence of)
    // "../".  For the above example, it would be "../../".
    var split_filename;

    // "split" file_name into parts, where the parts are separated by '/'
    // (i.e., directory separators).
    split_filename = file_name.split("/");

    num_directories = split_filename.length -1;

    return go_back_dir_levels(num_directories);

}


function Msg_Map_Set_Up_Entry_In_Msg_And_Line_Array(file_name, msg_map_entry) 
    // Extract the relevant data from "msg_map_entry", and use it to set up
    // an entry in top.Msg_And_Line_Array[file_name].
{
    var msg_id;
    var line_num;
    var category;
    var original_prob;

    // TBD : Use constants for these indices?
    msg_id =  msg_map_entry[0];
    line_num =  msg_map_entry[1];
    category =  msg_map_entry[2];
    original_prob =  msg_map_entry[3];

    Set_Up_Entry_In_Msg_And_Line_Array(file_name,
       msg_id, line_num, category, original_prob);
}

function Msg_Map_Restore_File(file_name)
    // Part of the restore-message-status operation :
    // This function is called from the onLoad of a -msgmap.html file,
    // after the global Msg_Map_Array array has been set up via 
    // a call to create_msg_map().
    // It performs the following tasks :
    // a) Using the data in Msg_Map_Array, initialize 
    //    top.Msg_And_Line_Array[file_name].  The data associates
    //    line-number, category, and original-probability with a message-id.
    // b) Once top.Msg_And_Line_Array[file_name] is set up, display the
    //    changed-messages that have been restored.
    // c) Are there any other files, in the Files_To_Restore array, that need
    //    to be handled in this way (i.e., that need to have their
    //    top.Msg_And_Line_Array entry set up, before we can display the
    //    changed messages?)   If so, operate on the next one...
{
    var file_to_restore;

    // The loop only runs to "length - 1", since the Msg_Map_Array
    // is created with a bogus "placeholder" entry as the last 
    // element.
    for (var i = 0; i < top.Msg_Map_Array.length-1; i++) {
        if (top.Msg_And_Line_Array == null) {
	    Initialize_Msg_And_Line_Array();
        }

	// Initialize top.Msg_And_Line_Array[file_name], which is 
	// a precursor to displaying the (restored) changed messages.
	Msg_Map_Set_Up_Entry_In_Msg_And_Line_Array(file_name, 
	  top.Msg_Map_Array[i]);
    }

    // Display the (restored) changed messages for this file_name.
    file_to_restore = top.Files_To_Restore[top.Current_Restore_Index];
    Restore_All_Messages_In_This_File(file_to_restore);

    // Get passed this "file_name", in the Files_to_Restore array.
    top.Current_Restore_Index = top.Current_Restore_Index + 1;

    // Get the next file in the Files_To_Restore array that needs to have
    // its top.Msg_And_Line_Array entry initialized before
    // its changed messages can be displayed.
    Handle_Next_File_To_Restore();
}

//***************************************************************************
// The following functions handle "filtering" in the per-file Message Status
// window.  Checkboxes at the top of the page control which messages are
// displayed.
//***************************************************************************

function New_Filter(name) {
    // Create a new "Filter".   A filter consists of the following fields:
    //   filter_name : the name of the filter; it is a "grouping" under
    //                 which mutiple checkboxes are gathered.  In the HTML,
    //                 these "grouped" checkboxes share the same <INPUT> name,
    //   checkbox_array : an array of "Checkbox"; each "Checkbox" has a value
    //                    and a "checked" status
    this.filter_name = name;
    this.checkbox_array =  new Array();
}

function New_Checkbox(val, checked) {
    // Create a new "Checkbox".  A checkbox consists of the following fields:
    //   condition : the condition which the checkbox represents (e.g., 
    //             "High", "Medium", or "Low", for the "ranking"
    //             filter).  In the HTML, this is the "value" of the <INPUT>
    //             tag.
    //   checked : is the checkbox checked?

    this.condition = val;
    this.checked = checked;
}

function Enable_Filter_Checkboxes()
    // In the html for a per-file Message Status file
    // (i.e., .java-status.html), the filter checkboxes are hardcoded
    // to be "disabled", so that the user cannot modify them 
    // while the page is loading.
    // This function enables the checkboxes.
{
    var msg_status_win;
    var form;

    // Get a handle for the per-file message-status window.
    msg_status_win = top.Popups["message_status_" + top.unique_suffix];

    // All of the filter-checkboxes are defined in an html form 
    // named "filter". (TBD : somehow make it a constant?)
    form = msg_status_win.document.filter;

    for (var i=0; i < form.elements.length; i++) {
	form.elements[i].disabled = false;
    }
}

function Make_Sure_Filters_For_Message_Are_Set()
    // Make sure that the "Filters" at the top of the message-status
    // window are set, such that, if we are trying to position the
    // display at a message, that message will NOT be filtered out.
    // (This happens when the user clicks on the editicon next to a message,
    // in the lower messages pane of the "File-Source" view.)
    //
    // Currently, there are two categories of filter :
    //    "lineage" : +, -, U (added, dropped, unchanged)
    //    "ranking" : Low, Medium, High
{

    var filter_checkbox;

    // Note : If the relevant "top." variables have the value "not_set",
    // then we are NOT trying to position the display at a specific message;
    // this happens when the message-status window is opened by some means
    // other than clicking on the editicon in the messages-pane (i.e., from
    // a file-name link in the Status Overview, or from the editicon in the 
    // "index tabs").

    // We know that the lineage associated with a message in the messages-pane
    // will not be "dropped" (because only new and unchanged messages show up
    // in the messages pane, since it is associated with the current source).

    if (top.msg_lineage != "not_set") {
        filter_checkbox = 
	  top.Filter_Data["lineage"].checkbox_array[top.msg_lineage];
	if (!filter_checkbox.checked) {
	    Filter("lineage", true, top.msg_lineage);
	}
    }

    if (top.orig_prob != "not_set") {
        filter_checkbox =
	  top.Filter_Data["ranking"].checkbox_array[top.orig_prob];
	if (!filter_checkbox.checked) {
	    Filter("ranking", true, top.orig_prob);
	}
    }

}

function Apply_Already_Set_Filters()
    // This function is called when the per-file message-status window 
    // is loaded (but NOT for the first time).  In this case, there 
    // is a "global" state to the filters (i.e., their most recent 
    // settings, whether or not the window has been closed), and we
    // apply them to the data in the per-file message status window.
    // By default, all rows in the message-status table are displayed.
    // This function goes thru the filters, and for any checkbox that
    // is UNCHECKED, it filters out the corresponding rows in the table.
{ 

    var checked;
    var val;
    var filter_name;

    for (var index=0; index < Filter_Data.length; index++) {

        // Get the name of the filter; currently, the two filter names
	// that are supported (from the html) are :
	// "lineage" and "ranking".
        filter_name = Filter_Data[index].filter_name;

	// For each checkbox under this filter, get its "checked" status.
	// If it is NOT checked, then "Filter" the table as appropriate.
	// (If it is checked, do nothing, since by default, all rows of
	// the table are displayed.)
	for (var j=0; j < Filter_Data[index].checkbox_array.length; j++) {
	    checked = Filter_Data[index].checkbox_array[j].checked;

	    // "val" is set to be the associated value of the checkbox;
	    // for example, "Low", "Medium", "High", for the 
	    // "ranking" filter name.
	    val = Filter_Data[index].checkbox_array[j].condition;
	    //alert("Filter name is " + filter_name +
	      //" and val is " + val + " and checked is " + checked);

	    if (checked == false) {
	        Filter(filter_name, false, val);
	    }
	}

    }

    // If we are positioning the message-status window display at a 
    // specific message, make sure that the Filters are set such that
    // the message will *NOT* be filtered out!
    Make_Sure_Filters_For_Message_Are_Set();

    // In the html, the checkboxes are set to be "disabled", so that
    // the user cannot modify them while the page is loading.
    // Now that we are done filtering, enable the checkboxes.
    Enable_Filter_Checkboxes();
}


function Set_Up_Filter_Data()
    // This function is called from the "onLoad" of the per-file message-status
    // file (i.e., foo.java-status.html).  The HTML in that file defines the
    // "filters" that the user may set via checkboxes, to control the 
    // messages that are displayed.
    // 
    // The first time that a per-file message-status window is opened,
    // there is a default setting to these checkboxes (as set in
    // Apply_Default_Filters).  The user may change these checkbox settings;
    // once they are changed, they apply for *all* files that are loaded
    // into the per-file message-status window.  That is, there is a "global"
    // state for these checkboxes, and they apply to whatever is loaded in
    // the per-file message-status window (even if it is closed, and then
    // reopened).
    //
    // Also the first time that a per-file message-status window is opened,
    // this function sets up top.Filter_Data to keep track of these checkboxes
    // (i.e., which ones exist, along with their checked/unchecked status).
    //
    // top.Filter_Data is an array which has one entry per "filter_name"
    // (i.e., per "grouping" of filters).  For example, one "filter_name"
    // is "ranking".
    //
    // Currently-supported filter_names are :
    //     ranking
    //     lineage
    //
    // Furthermore, each entry (i.e., each "filter_name") in top.Filter_Data 
    // contains a "checkbox_array", to identify which checkboxes are under
    // that grouping, and whether or not they are checked.  For example, for
    // "ranking", the checkbox_array is a 3-element array for
    // "High", "Medium", and "Low".
{
    var msg_status_win;
    var table;
    var form;
    var entry;
    var name;
    var val;
    var checked;
    var condition;
    var f_name;
    var cond_array;
    var which_box;

    if (top.Filter_Data != null) {
        // We have already opened the per-file message-status window,
	// so the "Filter_Data" data structure has already been set up,
	// and it containts the filters that were most recently set.
	// "Apply" these filters to the per-file message-status data that
	// has just been loaded into the window.
	Apply_Already_Set_Filters();
        return;
    }

    // Get a handle for the per-file message-status window.
    msg_status_win = top.Popups["message_status_" + top.unique_suffix];


    // All of the filter-checkboxes are defined in an html form 
    // named "filter". (TBD : somehow make it a constant?)
    form = msg_status_win.document.filter;

    // Initialize top.Filter_Data.
    top.Filter_Data = new Array();

    // Add each filter checkbox to the top.Filter_Data array.
    //
    // In the HTML, checkboxes which are logically grouped together
    // have the same <INPUT> "name" (such as "ranking"),
    // but different <INPUT> "values" (e.g., "High"/"Medium"/"Low").
    // The same-"name"d checkboxes are stored in the same entry of
    // top.Filter_Data, while the individual "value"s are stored in 
    // that entry's "checkbox_array".

    for (var i=0; i < form.elements.length; i++) {

	// Get name and value of checkbox, as well as its "checked" status
	// (i.e., is it checked?).
	name = form.elements[i].name;
	val = form.elements[i].value;
	checked = form.elements[i].checked;

	// NOTE : Initially, each checkbox is set to DISABLED, so the user
	// can't modify it before the page is fully loaded.  Enable the 
	// checkbox here.
	form.elements[i].disabled = false;

	// Have we seen a filter with this "name" before?  If so, add the
	// "value" to that filter's "checkbox_array"; else, create a new
	// entry for it in top.Filter_Data. 
	entry = top.Filter_Data[name];
	if (entry == null) {
            entry = new New_Filter(name);

	    // Set up top.Filter_Data so that it can be indexed
	    // by the filter name.
	    top.Filter_Data[top.Filter_Data.length] = entry;
	    top.Filter_Data[name] = entry;
	}

	// Have we seen a checkbox with this "value" before?  
	// (Each checkbox value in a filter "grouping" should be unique,
	// so it is an error condition if we have seen it before!)
	// Add the "which_box" to the checkbox_array.

	which_box = entry.checkbox_array[val];
	if (which_box == null) {
	    which_box = new New_Checkbox(val, checked);

	    // Set up entry.checkbox_array so that it can be indexed
	    // by the value name.
	    entry.checkbox_array[entry.checkbox_array.length] = 
	      which_box;
	    entry.checkbox_array[val] = which_box;
        // } else {
	    // alert("Unexpected duplicate value " + val + " for filter " +
	      // name);
	// NOTE: test-always-goes-same-way is duplicated for Java
	}
    }

    // The first time that the per-file message-status window is opened,
    // apply any default filters.
    Apply_Default_Filters();

}

function Set_Up_OV_Filter_Data()
    // This function is called from the "onLoad" of the Message Status Overview
    // file (status_overview.html).  The HTML in that file defines the
    // "filters" that the user may set via checkboxes, to control the 
    // messages that are displayed.
    // 
    // This function sets up top.OV_Filter_Data to keep track of these 
    // checkboxes
    // (i.e., which ones exist, along with their checked/unchecked status).
    //
    // top.OV_Filter_Data is an array which has one entry per "filter_name"
    // (i.e., per "grouping" of filters).  For example, one "filter_name"
    // is "OV_message_category".
    //
    // Currently-supported filter_names are :
    //     OV_message_category
    //
    // Furthermore, each entry (i.e., each "filter_name") in top.OV_Filter_Data 
    // contains a "checkbox_array", to identify which checkboxes are under
    // that grouping, and whether or not they are checked.  For example, for
    // "originaly_probability", the checkbox_array is a 3-element array for
    // "High", "Medium", and "Low".
    // TBD : other comments are TBD.
{
    var ov_win;
    var table;
    var form;
    var entry;
    var name;
    var val;
    var checked;
    var condition;
    var f_name;
    var cond_array;
    var which_box;

    // Get a handle for the per-file message-status window.
    ov_win = top.Popups["status_overview_" + top.unique_suffix];

    // All of the filter-checkboxes are defined in an html form 
    // named "OV_filter". (TBD : somehow make it a constant?)
    // TBD : Pass the name of the form in as a param.
    form = ov_win.document.OV_filter;

    // Initialize top.OV_Filter_Data.
    top.OV_Filter_Data = new Array();

    // Add each filter checkbox to the top.OV_Filter_Data array.
    //
    // In the HTML, checkboxes which are logically grouped together
    // have the same <INPUT> "name" (such as "ranking"),
    // but different <INPUT> "values" (e.g., "High"/"Medium"/"Low").
    // The same-"name"d checkboxes are stored in the same entry of
    // top.Filter_Data, while the individual "value"s are stored in 
    // that entry's "checkbox_array".

    for (var i=0; i < form.elements.length; i++) {

	// Get name and value of checkbox, as well as its "checked" status
	// (i.e., is it checked?).
	name = form.elements[i].name;
	val = form.elements[i].value;
	checked = form.elements[i].checked;

	// NOTE : Initially, each checkbox is set to DISABLED, so the user
	// can't modify it before the page is fully loaded.  Enable the 
	// checkbox here.
	// TBD : Is this true for the overview as well?
	form.elements[i].disabled = false;

	// Have we seen a filter with this "name" before?  If so, add the
	// "value" to that filter's "checkbox_array"; else, create a new
	// entry for it in top.Filter_Data. 
	entry = top.OV_Filter_Data[name];
	if (entry == null) {
            entry = new New_Filter(name);

	    // Set up top.OV_Filter_Data so that it can be indexed
	    // by the filter name.
	    top.OV_Filter_Data[top.OV_Filter_Data.length] = entry;
	    top.OV_Filter_Data[name] = entry;
	}

	// Have we seen a checkbox with this "value" before?  
	// (Each checkbox value in a filter "grouping" should be unique,
	// so it is an error condition if we have seen it before!)
	// Add the "which_box" to the checkbox_array.

	which_box = entry.checkbox_array[val];
	if (which_box == null) {
	    which_box = new New_Checkbox(val, checked);

	    // Set up entry.checkbox_array so that it can be indexed
	    // by the value name.
	    entry.checkbox_array[entry.checkbox_array.length] = 
	      which_box;
	    entry.checkbox_array[val] = which_box;
        // } else {
	    // alert("Unexpected duplicate value " + val + " for filter " +
	      // name);
	// NOTE: test-always-goes-same-way is duplicated for Java
	}
    }

    //for (i=0; i < top.OV_Filter_Data.length; i++) {
        //f_name = top.OV_Filter_Data[i].filter_name;
        //alert("filter name is " + f_name);
        //cond_array = top.OV_Filter_Data[i].checkbox_array;
        //for (j=0; j < cond_array.length; j++) {
            //alert(cond_array[j].condition + ", "  +  cond_array[j].checked);
        //}
    //}
}

function get_lineage_from_row(row) {
    // "row" is a row of the table in the "Message-Status" window.
    // Return the "lineage" (i.e., unchanged/added/dropped) for the row.

    var lineage_node;
    var ret_val;

    lineage_node = row.cells[top.LINEAGE_COLUMN];

    // TBD : it might have font information in front...
    //ret_val = get_text_string(lineage_node);

    ret_val = ts_getInnerText(lineage_node);

    return ret_val;
}

function Get_Field(filter, row) {
    // Get the field according to the "filter", in the specified "row".
    // This calls functions that access the DOM.

    var field;

    switch (filter) {
	case "lineage" :
	    field = get_lineage_from_row(row);
            return field;
	case "ranking" :
	    // When we filter by "ranking", we first take into account
	    // the user-defined status.  If that hasn't been set, then
	    // we consider the original value.
	    field = get_status_from_row(row);
	    //if (field == top.ORIG_NOT_YET_EXAMINED) {
		//field = get_original_prob_from_row(row);
	    //}
	    return field;
	case "OV_message_category" :
            field = OV_get_category_from_row(row);
	    return field;
	default :
	    alert("Unexpected filter " + filter  + " in Get_Field");
	    return("foo");
    }
}

function Check_Row_Against_All_Filters(Filter_Data,
  row, filter, value_to_match) {
    // The user has just checked the "value_to_match" checkbox, under
    // the "filter" heading.  Now, examine "row", and based on the values
    // of ALL filters, determine if the row should be displayed; return 
    // this boolean value.

    var display_row;
    var filter_name;
    var condition;

    // Start out with "display_row" set to true; if we come across a column
    // whose value matches an "unchecked" filter, then set "display_row" 
    // to false.
    display_row = true;

    for (var index=0; index < Filter_Data.length; index++) {
	filter_name = Filter_Data[index].filter_name;
	if (filter_name == filter) {
	    // The caller has already verified that the value in the column
	    // controlled by "filter" matches "value_to_match".
	    continue;
	}

	// Get the value of the field which is controlled by "filter", for
	// this "row".
	field = Get_Field(filter_name, row);

	// Does the value for this field match an UNCHECKED checkbox?
	// If so, then don't display the row; else, continue checking
	// the other columns of the row that match the filters.
	condition = Filter_Data[index].checkbox_array[field];
	if (condition == null) {
	    alert("Expected to find an entry for " + field +
	      " in the checkbox_array for " + filter);
	} else {
	    if (condition.checked == false) {
		// We shouldn't display this row.
                display_row = false;
		break;
	    }
	}
    }
    return display_row;
}

function Filter(filter, checked_status, value_to_match) {
    // This function is used when filtering the display of the per-file
    // Message-Status window.
    // The user has just modified the "value_to_match" checkbox, under
    // the "filter" grouping.  The status of the checkbox has just been
    // set to "checked_status".  Apply this new filter (and take into
    // consideration, the values of the existing filters).

    var my_win;
    var table;
    var field;
    var display_row;
    var entry;
    var condition;
    var form;
    var elem;

    my_win = top.Popups["message_status_" + top.unique_suffix];
    table = my_win.document.getElementById("mytable");

    // TBD : Error-handling?
    if (table == null) {
        alert("Unexpected null table in Filter");
    }

    // We keep track of the "checked_status" of all of the filter checkboxes,
    // in top.Filter_Data.  Modify the appropriate entry, so that it has
    //the updated "checked_status".

    // First, get the entry for all of the checkboxes that share the same
    // "filter" grouping.
    entry = top.Filter_Data[filter];
    if (entry == null) {
	alert("Unexpected null in Filter, couldn't find " + 
	    filter);
	return;
    }

    // Next, get the actual checkbox.
    condition = entry.checkbox_array[value_to_match];
    if (condition == null) {
	alert("Unexpected null condition in Filter, couldn't find " + 
	    filter + "/" + value_to_match);
	return;
    }

    condition.checked = checked_status;

    // All of the filter-checkboxes are defined in an html form 
    // named "filter". (TBD : somehow make it a constant?)
    form = my_win.document.filter;

    // Update the visual setting of the checkbox; 
    // "filter" is the name of the filter (e.g., "original_probability").
    elem = form[filter];
    //alert("vals are : " + elem[0].value + " , " +
      //elem[1].value + ", " + elem[2].value + " and length is " +
      //elem.length);

    for (var i = 0; i < elem.length; i++) {
        // There are multiple checkboxes which have the same name (e.g.,
	// "ranking"), but different values (e.g., "Low", "Medium" and "High").
	// Go through all of the checkboxes for the specified "filter" name,
	// and when we find the one that matches the specified 
	// "value_to_match", set its "checked" state based on the specified
	// "checked_status".
        if (elem[i].value == value_to_match) {
	    elem[i].checked = checked_status;
	    break;
	}
    }

    Handle_It(table, top.Filter_Data, checked_status, filter, value_to_match);
}

function Filter_Lineage(checked, val) {
    // hand-off to the Filter function.
    Filter("lineage", val, checked);
}

function Filter_Orig_Prob(checked, val) {
    // hand-off to the Filter function.
    Filter("ranking", val, checked);
}

//function match(val1, val2) {
  // val1 is the field that we read in. 
    // alert("val1  is " + val1 + " and val2 is " + val2);
    //if ((val1 == "&ndash;") && (val2 == "-")) {
        //alert("found the match!");
        //return True;
    //} else {
        //return (val1 == val2);
    //}
//}

function Handle_It(table, Filter_Data, checked_status, filter,
  value_to_match) {

    // For a checkbox that is "unchecked", we don't need to look at the
    // settings of the other filters; we simply go through the table,
    // and any row containing the "unchecked" value is *not* to be displayed.
    // alert("handle it!");
    if (checked_status == false) {

	// The first row of the table is the header; skip over it.
	for (var i=1; i < table.rows.length; i++)  {
	    field = Get_Field(filter, table.rows[i]);

	    // TBD : There was an issue with trying to compare with
	    // special character "&mdash;".
	    //if (match(field, value_to_match)) 
	     if (field == value_to_match) {
                table.rows[i].style.display = "none";
	    }
	}
    } else {
	// Go through the table, and for each row, first check to see if
	// the row contains the value corresponding to the checkbox that
	// has just been checked.  If so, then check to see if the row
	// meets the criteria of all of the other filters, and if so, 
	// display the row.  Else, leave the display-property of the row
	// as-is.

        // The first row of the table is the header; skip over it.
        for (var i=1; i < table.rows.length; i++)  {

	    // First, check to see if the row has the value that we just
	    // checked; if not, no need to check the other filters
            field = Get_Field(filter, table.rows[i]);

	    if (field == value_to_match) {
		display_row = Check_Row_Against_All_Filters(
		  Filter_Data,
		  table.rows[i], filter, value_to_match);
		if (display_row == true) {
		    // TBD : optimize, don't do if already set how we want?
		    table.rows[i].style.display = "";
		} else {
		    // TBD : optimize, don't do if already set how we want?
		    table.rows[i].style.display = "none";
		}
            }  // else, leave the display property of the row as-is
        }
    }
}

function OV_Filter(filter, checked_status, value_to_match) {
    // TBD : How much can the status-overview share with the per-file
    // status window?

    var ov_win;
    var ov_table;
    var field;
    var display_row;
    var entry;
    var condition;

    ov_win = top.Popups["status_overview_" + top.unique_suffix];
    ov_table =  ov_win.document.getElementById("status_overview_table");

    // TBD : Error-handling?
    if (ov_table == null) {
        alert("Unexpected null table in OV_Filter");
    }

    // We keep track of the "checked_status" of all of the filter checkboxes,
    // in top.Filter_Data.  Modify the appropriate entry, so that it has
    //the updated "checked_status".

    // First, get the entry for all of the checkboxes that share the same
    // "filter" grouping.

    entry = top.OV_Filter_Data[filter];
    if (entry == null) {
	alert("Unexpected null in OV_Filter, couldn't find " + 
	    filter);
	return;
    }

    // Next, get the actual checkbox.
    condition = entry.checkbox_array[value_to_match];
    if (condition == null) {
	alert("Unexpected null condition in OV_Filter, couldn't find " + 
	    filter + "/" + value_to_match);
	return;
    }

    // Update the status of the chekcbox.
    condition.checked = checked_status;

    Handle_It(ov_table, top.OV_Filter_Data, checked_status, filter, value_to_match);
}


function Apply_Default_Filters()
{
    // The first time that the per-file message status window is loaded,
    // this function is called (from Set_Up_Filter_Data), to apply any
    // "default" filters to the data.  (After this, we maintain a "global"
    // state of the filters, and simply apply *that* state, via
    // Apply_Already_Set_Filters.)

    // Initially, when the .html file is loaded, all of the rows of the table
    // are displayed.  This function "filters" the display by turning *off*
    // some of the rows, for things that we don't want to show to the user
    // by default.  The user can use checkboxes to modify the default filters 
    // (by selecting and/or deselecting checkboxes).

    // Don't display a row for a message with a "Low" original ranking.
    Filter("ranking", false, "Low");

    // If we are positioning the message-status window display at a 
    // specific message, make sure that the Filters are set such that
    // the message will *NOT* be filtered out! 
    // Do this *after* we we apply any "default" filters. 
    // (TBD : Don't set the default filter(s), if it contradicts the
    // properties of the message in question.)
    Make_Sure_Filters_For_Message_Are_Set();
}

function Handle_GG_Change(Entry_For_File, msg_id, sev, comments)
    // Update the "Entry_For_File" entry in top.Message_Status_Array
    // with the message-status changes identified by "msg_id", "sev",
    // and "comments". 

    // TBD : Do we actually use the orig_status field of a message-change?
    // For now, I'm just passing in the "sev".

{
    var Msg;   

    Msg = Entry_For_File.message_array["id_" + msg_id];

    if (Msg == null) {
	// We haven't seen this message before.
	// TBD : What should we do about the "orig_status" field?
	Msg = new New_Msg(msg_id, sev, comments, sev);

	// Set things up so that the "message_array" can be indexed 
	// either numerically, or by the msg-id string (with the
	// "id_" prefix, else the msg-id would end up erroneously treated 
	// like a numeric index).
	Entry_For_File.message_array[Entry_For_File.message_array.length] = Msg;
	Entry_For_File.message_array["id_" + msg_id] = Msg;
    } else  {
	// We've seen a change to this message before; overwrite it.
	Msg.bug_status = sev;
	Msg.comments = comments;
    }
}

function Check_GG_Database(file_name, pkg_name, short_file)
{
    // Check the Google-Gears database : for this "file_name", are there
    // any messages that were changed since the last time that db_lister
    // was run?  If so, we want to set up top.Msg_Status_Array[file_name]
    // to reflect these changes, so that we can still display them
    // (even though they haven't been incorporated yet into the Inspector's
    // database).  Note that if the same message was changed more than once,
    // we only keep track of its most-recent mod.

    var Entry_For_File;
    var rs;
    var file_id;
    var Msg;
    var msg_id;
    var sev;
    var comment;

    // alert("In Check_GG_Database, file_name is " + file_name + 
    // " and pkg_name is " + pkg_name + " and short file is " + short_file);

    // There's nothing to do if there's no Google-Gears database.
    if (!top.gg_working) {
        return;
    }

    // If "top.Message_Status_Array[file_name]" is null, it necessarily means
    // that we haven't done this Google-Gears database check.  Because, 
    // once we do the check, we set 
    // top.Message_Status_Array[file_name].inited_from_gg_database (even if
    // we didn't find any relevant messages).
    Entry_For_File = top.Message_Status_Array[file_name];
    if (Entry_For_File == null) {
	// Set up an entry for top.Message_Status_Array[file_name].
	// There may not be any relevant messages for this file, but
	// we at least want to mark that we have checked the Google-Gears
	// database.
	Entry_For_File = new New_Message_Status_Per_File(file_name);
        Entry_For_File.inited_from_gg_database = true;

	// Set things up so that top.Message_Status_Array can be indexed
	// either numerically, or by the name of the file.
	top.Message_Status_Array[top.Message_Status_Array.length] =
	  Entry_For_File;
	top.Message_Status_Array[file_name] = Entry_For_File;

    } else {
        // We necessarily have checked the Google-Gears database
	// for relevant messages in this file.
        // alert("We've already queried for " + 
	    // file_name + " in Check_GG_Database");
        return;
    }


    try {
        // Query the Google-Gears database for messages in "file_name"
	// that were changed since the last time that the db_lister
	// was run.
    
	// First, get the corresponding "file_id" for the file_name.
        rs = top.db.execute("SELECT File_Id FROM Message_Files WHERE DB_ID='" +
          top.databaseId + "'" +
	    " AND SHORT_FILE_NAME='" + short_file + "'" +
	    " and  PKG_NAME='" + pkg_name + "'");

        if (rs.isValidRow()) {
	    // There *are* messages in this file that have been modified.
	    // Check to see if there are any changes that occurred after
	    // db_lister was last run.
            file_id = rs.field(0);
	

            rs = top.db.execute("SELECT Msg_Id, Tstamp, Sev, Comment " +
	      "FROM Audit_Trail " +
	      "WHERE Db_Id='" + top.databaseId +
	      "' AND File_Id='" + file_id + "'" +
	      "AND Tstamp > '" + top.db_lister_timestamp + "'" +
	      " ORDER BY Tstamp");

	    while (rs.isValidRow()) {
	        // For each relevent message-change, call "Handle_GG_Change"
		// to set up the top.Message_Status_Array[file_name] entry
		// with the message-status data.  Note that the order assures
		// that the most recent changes will come last.  This is what
		// we want, so that the Message_Status_Array will keep track
		// of the most-recent change (which is all that we want to 
		// display).

		 // alert(rs.fieldName(0) + " is " + rs.field(0) + 
		   // " and " + rs.fieldName(1) + " is " + rs.field(1) +
		   // " and " + rs.fieldName(2) + " is " + rs.field(2) +
                   // " and " + rs.fieldName(3) + " is " + rs.field(3));

		msg_id = rs.field(0);
		sev = rs.field(2);
		comment = rs.field(3);

		Handle_GG_Change(Entry_For_File, msg_id, sev, comment);
	        rs.next();
	    }
	    rs.close();
        } else {
	    // alert("No relevant messages found in Check_GG_Database");
	}
    } catch (ex) {
          alert("problem with google-gears operation " +
	      ex.message);

    }
}


function msg_status_onload(msg_status_win, file_name, pkg_name, short_file)
    // This method is called "onLoad" for the per-file Message Status 
    // window. "pkg_name", and "short_file" are all used for the 
    // Google-Gears message-edit functionality.
{

    var win_name = "message_status_" + top.unique_suffix;
    var str;
    var entry;
    var msg_to_line_elem;


    // Make sure that the top.Popups array is set up with the window handle
    // ("msg_status_win") for the per-file Message Status window.
    // Note : this could be redundant; we also do this in msg_status_popup_-
    // or_give_focus, which is called when the user clicks (on an edit-icon, 
    // or a file-name in the Status Overview window) to bring up the
    // per-file Message Status window.  However, due to timing issues,
    // the call to keep_track_of_popups (in msg_status_popup_or_give_-
    // focus) might not be called before *this* method is called, so we also
    // need to do it here.
    keep_track_of_popups(win_name, msg_status_win);

    Set_Up_Line_Numbers(file_name);

    // If we haven't checked the GG database, do so now.
    // Or, TBD, should this be done AFTER the following check?
    // If we haven't already done so, check the Google-Gears database
    // to see if there are messages in "file_name" that were changed
    // after the db_lister was last run.
    Check_GG_Database(file_name, pkg_name, short_file);

    // If any message-status changes have been made to this file, in this
    // inspection session, then update the display with those user mods.
    // Note : in this window, we only display the severity, and not the
    // user-comments.
    update_message_status_display(file_name);

    Set_Up_Filter_Data();

    // Now that the correct file is loaded into the message-status window,
    // set the viewing position to the specified anchor.  This will be
    // redundant in some instances, but we have to do it because
    // the display may change once we "play with" the filters.
    msg_status_win.location.hash = top.msg_status_anchor;

    // We need to set the focus on this window, since the Google Gears code
    // could have popped up its "Do you trust this site" message, and when
    // you click that window away, the focus has been lost.

    msg_status_win.focus();

}

// TBD : Give credit where credit is due!
// TBD : IMPORTANT!!!  We don't really want to use this function, since the
// its recursive nature is very time-consuming!  Right now I only call it
// from get_lineage_from_row, and I would like to get rid of that dependence!
function ts_getInnerText(el) {
    if (typeof el == "string") {return el};
    if (typeof el == "undefined") { return el };
    if (el.innerText) return el.innerText;	//Not needed but it is faster
    var str = "";
	
    var cs = el.childNodes;
    var l = cs.length;
    for (var i = 0; i < l; i++) {
      switch (cs[i].nodeType) {
        case 1: //ELEMENT_NODE
          str += ts_getInnerText(cs[i]);
          break;
        case 3:	//TEXT_NODE
          str += cs[i].nodeValue;
          break;
      }
    }
    return str;
}

function Is_Dropped(lineage) {
    // Return TRUE if "lineage" is "-" (for "dropped").

    return (lineage == "-");
}

function annotation_report_onload(msg_status_win, file_name)
    // This method is called "onLoad" for the per-file Message Status window.
{
    Annotation_Report_Init_Stuff();
}

function Apply_Annotation_Default_Filters(lineage_info)
{
    // This function is called from the "onLoad" of the per-file annotation
    // report.
    // Initially do not display unanalyzed calls, and (if there is a "lineage"
    // filter), do not display "U"nchanged messages (only display deltas).
    // If there is no database, then the output does not have a lineage filter.

    Annot_Filter("annotation_kind", false, "unanalyzed");
    if (lineage_info == "TRUE") {
        Annot_Filter("annotation_lineage", false, "U");
    } 
}

function Apply_Already_Set_Annot_Filters()
    // This function is called when the per-file annotation window
    // is loaded (but NOT for the first time).  In this case, there 
    // is a "global" state to the filters (i.e., their most recent 
    // settings, whether or not the window has been closed), and we
    // apply them to the data in the per-file annotation window.
    // By default, all rows in the method annotation tables are displayed.
    // This function goes thru the filters, and for any checkbox that
    // is UNCHECKED, it filters out the corresponding rows in the table.
    // Note that the Annot_Filter method uses via "style-sheet" manipulation.
{ 
    var checked;
    var val;
    var filter_name;

    for (var index=0; index < top.Annot_Filter_Data.length; index++) {

	// Get the name of the filter; currently, the filter names
	// that are supported (from the html) are :
	//   "annotation_lineage" and "annotation_kind".
        filter_name = top.Annot_Filter_Data[index].filter_name;

	// For each checkbox under this filter, get its "checked" status.
	// If it is NOT checked, then "Filter" the table as appropriate.
	// (If it is checked, do nothing, since by default, all rows of
	// the table are displayed.)
	for (var j=0; j < top.Annot_Filter_Data[index].checkbox_array.length;
	  j++) {
	    checked = top.Annot_Filter_Data[index].checkbox_array[j].checked;

	    // "val" is set to be the associated value of the checkbox;
	    // for example, "+", "-", "U", for the "annotation_lineage"
	    // filter name.
	    val = top.Annot_Filter_Data[index].checkbox_array[j].condition;

	    if (checked == false) {
	        Annot_Filter(filter_name, false, val);
	    }
	}

    }

}

function Set_Up_Annotation_Filter_Data(lineage_info)
    // This function is called from the "onLoad" of the per-file annotation
    // report file (i.e., foo.java-annots.html).  The HTML in that file defines
    //  the "filters" that the user may set via checkboxes, to control the 
    // annotations that are displayed.
    // 
    // This function sets up top.Annot_Filter_Data to keep track of these 
    // checkboxes (i.e., which ones exist, along with their checked/unchecked
    // status).
    //
    // top.Annot_Filter_Data is an array which has one entry per "filter_name"
    // (i.e., per "grouping" of filters).
    //
    // Currently-supported filter_names are :
    //     annotation_lineage
    //     annotation_kind
    //
    // Furthermore, each entry (i.e., each "filter_name") in 
    // top.Annot_Filter_Data 
    // contains a "checkbox_array", to identify which checkboxes are under
    // that grouping, and whether or not they are checked.  For example, for
    // "annotation_kind", the checkbox_array is a 5-element array for
    // "precondition", "postcondition", "presumption", "unanalyzed_calls",
    // and test_vector.
{
    var annot_win;
    var table;
    var form;
    var entry;
    var name;
    var val;
    var checked;
    var condition;
    var f_name;
    var cond_array;
    var which_box;

    if (top.Annot_Filter_Data != null) {
        // We have already set up the "Annot_Filter_Data" data structure,
	// and it contains the filters that were most recently set.
	// "Apply" these filters to the annotation data that has just
	// been loaded into the window.
        Apply_Already_Set_Annot_Filters();
	return;
    }

    // Get a handle for the per-file annotation window.
    annot_win = top.Popups["annotation_" + top.unique_suffix];

    // All of the filter-checkboxes are defined in an html form 
    // named "filter". (TBD : somehow make it a constant?)
    form = annot_win.document.filter;

    // Initialize top.Filter_Data.
    top.Annot_Filter_Data = new Array();

    // Add each filter checkbox to the top.Annot_Filter_Data array.
    //
    // In the HTML, checkboxes which are logically grouped together
    // have the same <INPUT> "name" (such as "annotation_kind"),
    // but different <INPUT> "values" (e.g.,
    // "pre"/"post"/"presumption/"unanalyzed/test_vector").
    // The same-"name"d checkboxes are stored in the same entry of
    // top.Annot_Filter_Data, while the individual "value"s are stored in 
    // that entry's "checkbox_array".

    for (var i=0; i < form.elements.length; i++) {

	// Get name and value of checkbox, as well as its "checked" status
	// (i.e., is it checked?).
	name = form.elements[i].name;
	val = form.elements[i].value;
	checked = form.elements[i].checked;

	// IF each checkbox were initially disabled, we'd set its disabled
	// field to false, here.

	// Have we seen a filter with this "name" before?  If so, add the
	// "value" to that filter's "checkbox_array"; else, create a new
	// entry for it in top.Annot_Filter_Data. 
	entry = top.Annot_Filter_Data[name];
	if (entry == null) {
            entry = new New_Filter(name);

	    // Set up top.Filter_Data so that it can be indexed
	    // by the filter name.
	    top.Annot_Filter_Data[top.Annot_Filter_Data.length] = entry;
	    top.Annot_Filter_Data[name] = entry;
	}

	// Have we seen a checkbox with this "value" before?  
	// (Each checkbox value in a filter "grouping" should be unique,
	// so it is an error condition if we have seen it before!)
	// Add the "which_box" to the checkbox_array.

	which_box = entry.checkbox_array[val];
	if (which_box == null) {
	    which_box = new New_Checkbox(val, checked);

	    // Set up entry.checkbox_array so that it can be indexed
	    // by the value name.
	    entry.checkbox_array[entry.checkbox_array.length] = 
	      which_box;
	    entry.checkbox_array[val] = which_box;
        // } else {
	    // alert("Unexpected duplicate value " + val + " for filter " +
	      // name);
	// NOTE: test-always-goes-same-way is duplicated for Java
	}
    }

    // The first time that the per-file annotation window is opened,
    // apply any default filters.
    Apply_Annotation_Default_Filters(lineage_info);
}

function annotation_file_onload(annot_win, file_name, num_tables, lineage_info)
    // This method is called "onLoad" for the per-file Annotation report window.
    // TBD : Note, we don't need "num_tables" anymore.
{

    var win_name = "annotation_" + top.unique_suffix;

    // without this it does not work in all cases, even though we should always
    // get the "overview" onload first..
    Annotation_Report_Init_Stuff();

    // Make sure that the top.Popups array is set up with the window handle
    // ("annot_win") for the per-file Annotation report window.
    keep_track_of_popups(win_name, annot_win);

    Set_Up_Annotation_Filter_Data(lineage_info);
}

  function annotation_popup_or_give_focus(win_url_dir, win_url_file, use_dir,
    anchor, win_target) 
{
// TBD
}

function To_Css_Name(checkbox_value)
{
    // For the annotation window :
    // "checkbox_value" is the HTML "value" of a filter checkbox.
    // Convert it to its corresponding name in the css rules.
    switch (checkbox_value) {
        case "+" :
	    return "new";
        case "-" :
	    return "dropped";
        case "U" :
	    return "unchanged";
	case "presumption" :
	    return "sumption"
        default :
	    return checkbox_value;
    }
}


function Update_Annot_Checkbox(annot_win, filter, value_to_match,
  checked_status) {

    // In the annotation window ("annot_win"), set the checkbox in 
    // the "filter" family and identified via "value_to_match", to
    // have the specified "checked_status".
    // This actually modifies the "DOM" so you can see/not-see the 
    // check in the checkbox.
    var form;
    var elem;

    // All of the filter-checkboxes in the annotation window are defined 
    // in an html form named "filter". (TBD : somehow make it a constant?)
    form = annot_win.document.filter;

    // Update the visual setting of the checkbox;.
    elem = form[filter];
    for (var i = 0; i < elem.length; i++) {
        // There are multiple checkboxes which have the same name (e.g.,
	// "annotation_lineage"), but different values (e.g., "+", "-", 
	// and "U").
	// Go through all of the checkboxes for the specified "filter" name,
	// and when we find the one that matches the specified 
	// "value_to_match", set its "checked" state based on the specified
	// "checked_status".
        if (elem[i].value == value_to_match) {
	    elem[i].checked = checked_status;
	    break;
	}
    }
}

function Handle_Turn_On_Annot_Filter(which_rule, name_of_rule, filter) {
    // We are considering "which_rule" from the stylesheet; its name is
    // "name_of_rule".  The user has just checked the "filter" checkbox,
    // turning it on.  Based on the value of "filter", and also on the 
    // the values of the other filters, should the rows that fall under
    // "name_of_rule" be displayed, or filtered out?

    var css_name;
    var is_on;
    var filter_group;
    var checkbox;

    for (var qq=0; qq < top.Annot_Filter_Data.length; qq++) {
      filter_group = top.Annot_Filter_Data[qq];
      if (filter != filter_group.filter_name) {
        for (var tt=0; tt < filter_group.checkbox_array.length; tt++) {
	    checkbox = filter_group.checkbox_array[tt];
            css_name = To_Css_Name(checkbox.condition);
            if (name_of_rule.indexOf(css_name) != -1) {
		is_on = checkbox.checked;
                if (is_on == false) {
                  // TBD : Do we want to turn it off or leave as is?
                  which_rule.style.display = "none";
                } else {
                  which_rule.style.display = "";
                }
                break;
            } 
        }
      }
    }
}


function Turn_On_Annot_Filter(rules, filter, css_value) {
    // The user has just turned on the "filter" checkbox; "css_value"
    // is the name that correspondings to this filter, in the
    // css stylesheet "rules".
    // Turn on the display of those css "rules" that correspond to the
    // "filter" (but must also consider the settings of the other filters,
    // too).

    var name_of_rule;

    for (var i=0; i < rules.length; i++) {
      name_of_rule = rules[i].selectorText;
      if (name_of_rule.indexOf(css_value) != -1)  {
          if (top.Annot_Filter_Data.length == 1) {
	      // There is only one filter choice, so turn on any rule
	      // with this name.
              rules[i].style.display = "";
	  } else {
	     Handle_Turn_On_Annot_Filter(rules[i], name_of_rule, filter);
	  }
      } 
    }
}

function Annot_Filter(filter, checked_status, value_to_match) {
    // This function is used when filtering the display of the per-file
    // Annotation Report window.
    // The user has just modified the "value_to_match" checkbox, under
    // the "filter" grouping.  The status of the checkbox has just been
    // set to "checked_status".  Apply this new filter (and take into
    // consideration, the values of the existing filters).

    var my_win;
    var entry;
    var condition;
    var rules;
    var name_of_rule;
    var css_value;

    my_win = top.Popups["annotation_" + top.unique_suffix];

    // TBD : This is hard-coded, where we know that "style.css" is
    // the first (index 0) style-sheet.
    // Note: the following is to eliminate browser-dependencies, where
    // Firefox keeps track of the style-sheets under "cssRules", and IE
    // uses "rules" instead.
    rules = my_win.document.styleSheets[0].cssRules ? 
      my_win.document.styleSheets[0].cssRules:
      my_win.document.styleSheets[0].rules;

    // We want to match-up the checkbox "value" with the names of "rules"
    // in the style-sheet.  However, there is first a name transformation
    // that we have to perform.  For example, stylesheet rules that are
    // associated with the "+" checkbox have the word "new" in them.
    css_value = To_Css_Name(value_to_match);
    if (checked_status == false) {
	// Turning off a checkbox is straightforward : for each css-rule
	// associated with the checkbox, set its display to none.
        for (var i=0; i < rules.length; i++) {
	    name_of_rule = rules[i].selectorText;
            if (name_of_rule.indexOf(css_value) != -1)  {
                rules[i].style.display = "none";
            } 
        }
    } else {
	// Turning on a checkbox is more complicated; you have to take
	// into account the settings of the other checkboxes.
        Turn_On_Annot_Filter(rules, filter, css_value);
    }

    // Modify the internal data structure that keeps track of the value
    // of the checkbox.  This does NOT modify the DOM; that happens,
    // below, in Update_Annot_Checkbox.

    // First, get the entry for all of the checkboxes that share the same
    // "filter" grouping.
    entry = top.Annot_Filter_Data[filter];
    if (entry == null) {
        alert("Unexpected null in Annot_Filter, couldn't find " + filter);
	return;
    }

    // Next, get the actual checkbox.
    condition = entry.checkbox_array[value_to_match];
    if (condition == null) {
        alert("Unexpected null condition in Annot_Filter, couldn't find " + 
	  filter + "/" + value_to_match);
	return;
    }
    // Update the status of the chekcbox.
    condition.checked = checked_status;

    // Update the visual setting of the checkbox.
    Update_Annot_Checkbox(my_win, filter, value_to_match, checked_status);
}

function GetTimestamp()
{
    var currentTime = new Date();
    var month = currentTime.getMonth() + 1;
    if (month<10) { month = "0" + month;}
    var day = currentTime.getDate();
    if (day < 10) { day = "0" + day;}
    var year = currentTime.getFullYear();
    var hours = currentTime.getHours();
    if (hours < 10) { hours = "0" + hours;}
    var minutes = currentTime.getMinutes ();
    if (minutes< 10) { minutes = "0" + minutes;}
    var seconds = currentTime.getSeconds();
    if (seconds< 10) { seconds = "0" + seconds;}

    var currTime =  year + "/" + month + "/" + day + " " + hours  + ":" +
	     minutes +":" + seconds;
    //alert (currTime);
    return currTime;
}

function Save_Message_Status_Changes(file, pkg_name, short_file, message_id,
  orig_prob, severity, comments)
    // This function is called when the "Save Changes" button in the
    // edit-message-status window is clicked.  It then :
    // 1) Updates the per-file Message-Status window with the new severity
    // 2) calls Change_Message_Status, to update internal data structures
    //    and also update the src/msg window to indicate the new severity
    // 3) writes out the changes to the Audit-Trail of the database
    // 4) closes the edit-message-status window
{
    var message_status_win = top.Popups["message_status_" + top.unique_suffix];
    var examined_elem;
    var currTime = new Date().getTime();
    var currTime_str;
    var val;
    var local_comments;
    var rs;
    var file_id;


    // alert("In Save_Message_Status_Changes, pkg_name is " + pkg_name +
     // " and short_file is " + short_file + " and file is " +
     // file);

    currTime_str = GetTimestamp();

    // If the button says "Mark As Reviewed", then build up a comment
    // that says "reviewed on <timestamp>".
    val = top.edit_message_win.document.form_edit_msg.save_button.value;
    if (val == "Mark As Reviewed") {
        local_comments = "Reviewed on " + currTime_str;
    } else {
        local_comments = comments;
    }

    // The per-file Message-Status window should be open, but someone could
    // have conceivably closed it.
    if (!message_status_win.closed) {

        update_one_msg_display(message_status_win, message_id, severity,
	  local_comments);
    } 
    Change_Message_Status(file, message_id, severity, local_comments,
      orig_prob);

    if (top.gg_working) {
      try {
	// Was this file already added to the Message_Files table?
        rs = top.db.execute("SELECT File_Id FROM Message_Files WHERE DB_ID='" +
          top.databaseId + "'" +
	  " AND SHORT_FILE_NAME='" + short_file + "'" +
	  " and  PKG_NAME='" + pkg_name + "'");

        if (rs.isValidRow()) {
            file_id = rs.field(0);
	    // alert("Found the file_id for " + pkg_name + "/" +
	        // short_file + " and it is " + file_id);
        } else {
	    // Enter a new file
	    top.db.execute("INSERT INTO Message_Files(Db_Id, " +
	     "Short_File_Name, Pkg_Name) VALUES(?, ?, ?)",
               [top.databaseId, short_file, pkg_name]);

            rs = top.db.execute("SELECT File_Id FROM Message_Files " +
	      "WHERE DB_ID='" + top.databaseId + "'" +
	      " AND SHORT_FILE_NAME='" + short_file + "'" +
	      " and  PKG_NAME='" + pkg_name + "'");

            if (!rs.isValidRow()) {
	        alert("Unexpected invalid file-id in " +
		  "Save_Message_Status_Changes");
	        return;
	    } else {
                file_id = rs.field(0);

	    }

	}

	// Enter this message-status change into the Audit Trail.
        top.db.execute("INSERT INTO Audit_Trail(Db_Id, File_Id, Tstamp, " +
	  "Sev, Msg_Id," + "Comment) VALUES(?, ?, ?, ?, ?, ?)",
	  [top.databaseId, file_id,
	   currTime_str, severity, message_id, local_comments]);

      } catch (ex) {
          alert("problem with insert into google-gears " + ex.message);
      }
    } else {
        top.edit_message_win.alert(
	    "Google Gears is not installed; unable to write to database");
    }

    top.edit_message_win.close();
}

function Cancel_Message_Status_Changes()
{
    // Close the edit-message-status window, without saving any of the changes
    // (i.e., without updating the database audit trail and without updating
    // the internal data structures that keep track of user mods).
    top.edit_message_win.close();
}

function Go_To_Google_Gears() {

    var new_win;

    // open a new window
    new_win = open('http://gears.google.com','my_win',
      'scrollbars,status,menubar,resizable,toolbar'); 
}

function user_has_made_changes()
{
    // The user is in the edit-message-status window, and has either 
    // set the severity pull-down menu, or has typed a character into the 
    // comments textarea.
    // If this message doesn't have an audit trail and has not had any changes
    // made to it during this browse-output session, then the "change" button
    // actually says "Mark As Reviewed", rather than "Save Changes". 
    // But now, since we are in the middle of making changes to the message,
    // modify the buttn text to say "Save Changes".
    var val;

    val = 
  top.edit_message_win.document.form_edit_msg.save_button.value;
    if (val != "Save Changes") {
      top.edit_message_win.document.form_edit_msg.save_button.value =
        "Save Changes";
    }
}

function Init_Edit_Message_Status_Window(
    new_win, file, pkg_name, short_file,  message_id, orig_prob, audit_trail)
{
    // Initialize the contents of the (pop-up) edit-message-status window.
    // It contains :
    //     the message-id
    //     the original probability of the message
    //     a pull-down menu to select the status (which is initialized
    //       to the current severity)
    //     a textarea to add comments
    //     a "Save Changes" button (or a "Mark As Reviewed" button)
    //     a "Get Google Gears" button (*if* it is not installed)
    //     a Cancel button
    //     a textarea to display the change history (i.e., "audit_trail")
    //
    // "pkg_name" and "short_file" are used when we save the message-status
    // changes to the the Google-Gears database.

    // Reinitialize document associated with window
    var sdoc = new_win.document;
    var has_no_audit_trail;
    var comments;
    var is_first_time;
    var has_user_edit;

    //TBD  : if there is no audit trail, it will be empty
    has_no_audit_trail = (audit_trail == "no_audit_trail");

    has_user_edit = Message_Has_User_Edit(new_win, file, message_id);
    is_first_time = !has_user_edit && has_no_audit_trail;
        
    sdoc.open();
    sdoc.writeln("<html><head><title>Edit Message Window</title>");
    sdoc.writeln("</head>");

    sdoc.writeln("<B>Edit Message-Id: " + message_id + "</B><BR>");

    sdoc.writeln("<I>Original Ranking: " + orig_prob + "</I><BR><BR>");

    sdoc.writeln("Set Current Status:");
    sdoc.writeln("<FORM name=\"form_1\">");
    sdoc.writeln(
      "<select name=\"status\"" +
        " onChange='opener.top.user_has_made_changes()' >"); 

    sdoc.writeln("<option value=\"Not A Bug\">Not A Bug");
    sdoc.writeln("<option value=\"High\">High");
    sdoc.writeln("<option value=\"Medium\">Medium");
    sdoc.writeln("<option value=\"Low\">Low");
    sdoc.writeln("</select>");
    sdoc.writeln("</FORM>");


    sdoc.writeln("Add Comments:");
    sdoc.writeln("<FORM name=\"comments_1\">");
    sdoc.writeln("  <textarea " +
      "onKeyPress='opener.top.user_has_made_changes()' " +
      " name=\"text_1\" rows=\"4\" cols=\"37\">");
    sdoc.writeln("  </textarea>");
    sdoc.writeln("</FORM>");

    sdoc.writeln("<FORM name=\"form_edit_msg\">");
    if (is_first_time) {
        // The difference is whether the button says "Save Changes" or 
	// "Mark As Reviewed".
	// TBD : Why did I break the code up this way, with the
	// redundancy?
    sdoc.writeln(
      "<input type='button' onclick=\"opener.top.Save_Message_Status_Changes(" +
	  "'" + file + "'" + ", " +
          "'" + pkg_name + "'" + ", " + "'" + short_file + "'" + ", " +
	  + message_id +  ", " +
	  "'" + orig_prob + "'" + ", " +
          "window.document.form_1.status.options[window.document.form_1.status.selectedIndex].value," +
          "window.document.comments_1.text_1.value)\" "  +
        "name='save_button' value='Mark As Reviewed'>");
    } else {
    sdoc.writeln(
      "<input type='button' onclick=\"opener.top.Save_Message_Status_Changes(" +
	  "'" + file + "'" + ", " +
          "'" + pkg_name + "'" + ", " + "'" + short_file + "'" + ", " +
	  + message_id +  ", " +
	  "'" + orig_prob + "'" + ", " +
          "window.document.form_1.status.options[window.document.form_1.status.selectedIndex].value," +
          "window.document.comments_1.text_1.value)\" "  +
        "name='save_button' value='Save Changes'>");
    }

    sdoc.writeln(
  "<input type='button' onclick='opener.top.Cancel_Message_Status_Changes()'" +
        "name='cancel_button' value='Cancel'>");
    sdoc.writeln(
  "<input type='button' onclick='opener.top.Go_To_Google_Gears()'" +
        "name='gg_button' value='Get Google Gears'>");
    sdoc.writeln("</FORM>");
    sdoc.writeln("<BR><BR> ");

    sdoc.writeln("Change History:");
    sdoc.writeln("<FORM name=\"form_audits\">");
    sdoc.writeln("  <textarea readonly=\"readonly\" name=\"text_1\" ");
    sdoc.writeln("rows=\"8\" cols=\"37\">");

    // Don't directly write out the audit-trail here; leave it blank,
    // til we assign the textarea's value field, in Popup_Edit_Message_Status.
    // This is to deal with newlines, which are embedded in the "audit_trail"
    // as either "\13\10" or "\10".
    sdoc.writeln("");

    sdoc.writeln("  </textarea>");
    sdoc.writeln("</FORM>");

    sdoc.writeln("</body></html>");
    sdoc.close();
}

function Popup_Edit_Message_Status(
  message_id, win_target, file, pkg_name , short_file,  orig_prob, audit_trail)
{
    // Pop-up a window to allow modification of the message severity and also 
    // to allow comments, for the specified "message_id".  Also display the 
    // text of the "audit_trail".  "orig_prob" is the original probability
    // of the message.
    
    // Note : there is only one edit-message-status window; if it is
    // already open, and you click on the "Edit" button for another 
    // message-id, then the contents of the window will be REPLACED for the
    // new message-id.

    var new_win;
    var elem;
    var index;
    var has_no_audit_trail;
    var local_audit_trail;


    // alert("In Popup_Edit_Message_Status, file is " + file);

    //TBD  : if there is no audit trail, it will be empty
    has_no_audit_trail = (audit_trail == "no_audit_trail");

    // Open the edit-message-status window.  Remember the "handle",
    // so we can close the window when necessary.
    top.edit_message_win = open('', win_target,
      'scrollbars,status,resizable,height=500,width=390');

    new_win = top.edit_message_win;

    // If the window is not already open, then open it, and initialize it
    // with the appropriate data for the specified "message_id".
    if (new_win.closed || !new_win.document.URL || 
      new_win.document.URL == '' ||
      new_win.document.URL.indexOf("about") == 0) {

	Init_Edit_Message_Status_Window(new_win, file,
	  pkg_name, short_file, message_id, orig_prob, audit_trail);
    } else {
        // If the window is already opened, then give it focus.
        // If it is *already* displaying the change-history for
        // the specified message_id, then do no more; else, initialize it
        // with the appropriate data.
        new_win.focus();

	if (top.change_history_id != message_id) {
            // The window is open, but it is displaying the change-history
	    // for a different message.
	    Init_Edit_Message_Status_Window(new_win, file,
	      pkg_name, short_file, message_id, orig_prob, audit_trail);
        }
    } 

    if (! has_no_audit_trail) {
	// TBD : take into account various browser newline sequences.
        local_audit_trail = audit_trail.replace(/\\13\\10/g,"\n");
        local_audit_trail = local_audit_trail.replace(/\\10/g,"\n");

        new_win.document.form_audits.text_1.value = local_audit_trail;
    }

    // The window is now displaying the change-history
    // for the specified message-id.
    top.change_history_id = message_id;

    // Update the window, to reflect any changes made during this
    // inspection session.  Even if there were no changes made
    // to this message, we still need to initialize the message-severity
    // pull-down to display the current severity.
    Update_One_Message_Edit(new_win, file, message_id);

    if (top.gg_working) {
        // Init_Edit_Message_Status_Window displays a "Get Google Gears"
	// button.  If Google-Gears is installed (and working), then
	// hide the button.
	new_win.document.form_edit_msg.gg_button.style.visibility="hidden";
    } else {
        // Google-Gears is not installed (TBD : or not working?). 
	// Disable the message-edit functionality, and tell the user
	// that Google-Gears needs to be installed.
	

	// Disable the pull-down status menu.
        new_win.document.form_1.status.disabled  = true;

	// In the "comments" textbox (which we will make read-only),
	// explain that Google-Gears is not installed.
	new_win.document.comments_1.text_1.value =
	    "Message-edit functionality is disabled.  Either " +
	    "Google Gears is not installed, or was denied " +
	    "write-access during its startup.  " +
	    "To install Google Gears, click the\n" +
	    "\"Get Google Gears\" button below.";
	new_win.document.comments_1.text_1.style.color = "red";
        new_win.document.comments_1.text_1.readOnly  = true;

	// Disable the "Save Changes" button.
        new_win.document.form_edit_msg.save_button.disabled  = true;

	// TBD : We don't have to make the Get-Google-Gears button visible,
	// since it is displayed by default.
	//new_win.document.form_edit_msg.gg_button.style.visibility="visible";
    }
}
