// C++ code Copyright (C) David R. Evans G4AMJ/NQ0I

#include <bdr.h>
#include <edr.h>
#include <pdsdr.h>
#include <sm_mode.h>

#include <math.h>

#ifdef MSDOS
#include <assert.h>
#include <alloc.h>
#include <dos.h>
#include <conio.h>
#endif

// the following hack is needed because on the Suns, we enter most
// member functions through the dispatcher which, being C-based, does
// not pass an implicit object.
#ifdef SUN
#define RECURSE(function) \
  if (this != mode) \
  { mode->function(); \
    return; \
  }
#else
#define RECURSE(function)
#endif

#ifdef SUN
#define RECURSENONVOID(function) \
  if (this != mode) \
  { return (mode->function()); \
  }
#else
#define RECURSENONVOID(function)
#endif

// the fourshort class, used because of DOS memory restrictions. All
// systems use this class.

/*#if defined(SUN) || defined(MSWINDOWS) || defined(MAC)
long farcoreleft(void) { return 10000000L; }
#endif

// constructor
fourshort::fourshort(const int n1, const int n2, const int n3, const int n4,
                     const int start_channel, const int end_channel)
{ // don't take more than half the remaining space
  long target = farcoreleft() / 2;
  target = MAX(target, 100000L);

// try to allocate enough space

  heap_check(sip = new int16* [n2]);
  for (int n = 0; (n == 0) || ((n < n2) && (farcoreleft() > target)); )
  { heap_check(sip[n++] = new int16 [n1 * n3 * n4]);
  }

  { a = n1;
    b = n2;
    c = n3;
    d = n4;
    _start_channel = start_channel;
    if (n == n2) then
		_end_channel = end_channel;
    else
      _end_channel = _start_channel + n - 1;
  }
}

// destructor
fourshort::~fourshort(void)
{ for (int n = 0; n < _end_channel - _start_channel + 1; n++)
    destroy_array(sip[n]);
  destroy_array(sip);
}

// extract address of a datum
int16& fourshort::datum(const int A, const int B, const int C,
                            const int D)
{ // check all the bounds
  if ((A < 0) || (A >= a)) then
    fatal_error("Bound A exceeded in fourshort::datum");
  if ((B < 0) || (B >= b)) then
    fatal_error("Bound B exceeded in fourshort::datum");
  if ((C < 0) || (C >= c)) then
    fatal_error("Bound C exceeded in fourshort::datum");
  if ((D < 0) || (D >= d)) then
    fatal_error("Bound D exceeded in fourshort::datum");

// calculate the actual index
  int32 index = 0;
  index += (int32)D;
  index += (int32)C * d;
  index += (int32)A * c * d;

  return (sip[B][index]); 
} */


// constructor
operating_mode::operating_mode(void) : Autoscale_tomplot(false),
													Binned_record(0),
                                       Brightness(50),
													Cancelled(false),
                                       Comment(""),
													Current_file_name("NO NAME"),
                                       Current_pra_mode(prapollo),
                                       Current_sc_mode(gs3),
                                       Duration(15),
                                       End_channel(200),
                                       End_time(-1),
                                       Filesize(0),
                                       File_record_length(1872),
                                       File_type(fixed),
                                       Gs2_n_data(16000),
                                       Gs2_n_strips(10),
				                           Gmdr(),
				                           High_ignore_value(0),
                                       High_recall_value(0),
                                       High_res_channel(1),
													Is_binned(0),
                                       Low_ignore_value(0),
                                       Low_recall_value(0),
                                       N_tomplot_channels(10),
                                       Next_called_implicitly(false),
                                       Pol_contrast(10),
                                       Pol_intensity_threshold(64),
                                       Printer_name("lw"),
                                       Records_processed(0),
                                       Saturation(50),
#ifdef MSDOS
                                       Start_channel(131),
#else
                                       Start_channel(3),
#endif
                                       Start_tomplot_channel(191),
				                           To_file(false),
				                           Tplotmode(UNBINNED_SPECTRUM),
													Upper_square_value(0),
                                       Version(0),
				                           Edr_f(0),
                                       High_rate_frame_p(0),
                                       Gs2_frame_pds(0),
                                       Gs2_frame_jpl(0),
                                       Pad(0),
                                       Screen_device(0),
                                       Hardcopy_device(0),
                                       Sorted_list(0)
{ // Initialise
  for (int i = 0; i <= 255; i++)
    Valid_data[i] = false;
  for (i = 1; i <= 254; i++)
    Valid_data[i] = true;
  for (i = 1; i <= 200; i++)
  { Tomplot_dn_range[i][0] = 0;
    Tomplot_dn_range[i][1] = 255;
  }

// define equivalences
  for (i = 0; i < N_SC_MODES; i++)
    Equivalent_sc_modes[i] += i;

// oc1 == gs3
  Equivalent_sc_modes[gs3] += oc1;
// gs7 [== pb-3] == gs3
  Equivalent_sc_modes[gs3] += pb3;
// gs6 [== pb-1] == gs3
  Equivalent_sc_modes[gs3] += pb1;
// gs8 == gs3
  Equivalent_sc_modes[gs3] += gs8;

  Equivalent_sc_modes[oc1] = Equivalent_sc_modes[gs3];
  Equivalent_sc_modes[pb3] = Equivalent_sc_modes[gs3];
  Equivalent_sc_modes[pb1] = Equivalent_sc_modes[gs3];
  Equivalent_sc_modes[gs8] = Equivalent_sc_modes[gs3];

// uv5a and cr5a are equivalent
  Equivalent_sc_modes[cr5a] += uv5a;
  Equivalent_sc_modes[uv5a] += cr5a;

// define spectral plotting modes
  Spectral_modes += UNBINNED_SPECTRUM;
  Spectral_modes += BINNED_SPECTRUM;

// define binned modes
  Binned_modes += BINNED_SPECTRUM;
  Binned_modes += BINNED_TOMPLOT;

// define browse modes
  Pds_browse_modes += pds_browse;
  Pds_browse_modes += pds_bbrowse;

// define low rate modes
  Low_rate_modes += UNBINNED_SPECTRUM;
  Low_rate_modes += BINNED_SPECTRUM;
  Low_rate_modes += RAWPLOT;
  Low_rate_modes += UNBINNED_TOMPLOT;
  Low_rate_modes += BINNED_TOMPLOT;

// define high rate modes
  High_rate_modes += GS2_LOW_RES;
  High_rate_modes += GS2_HIGH_RES;

// initialise the static data for binning
  for (int n = 0; n < 256; n++)
    binned_edr::pseudo_watts[n] = exp((float)n / 100);

  for (n = 1; n < 255; n++)
    binned_edr::valid_data[n] = true;
  binned_edr::valid_data[0] = false;
  binned_edr::valid_data[255] = false;

// set default printer; listed in reverse order of priority
/* 
#ifdef LASERJET
  Hardcopy_device = &laserjet;
#endif
#ifdef LASERWRITER
  Hardcopy_device = &lw;
#endif
*/

// Set up the default plotting mode
  _plot_function = &operating_mode::display_spectral;
}

// destructor; try to cleanup after ourselves explicitly
operating_mode::~operating_mode(void)
{ destroy_array(Is_binned);
  destroy(Edr_f);
  destroy(Gs2_frame_jpl);
  destroy(Gs2_frame_pds);

// several pointers are to objects that should be destroyed elsewhere
  High_rate_frame_p = 0;
  Screen_device = 0;
  Hardcopy_device = 0;
  Pad = 0;
}


// private functions

// check that the Pad has reasonable values
void operating_mode::_check_pad(DREstring& function_name)
{ if (!Pad) then
    fatal_error(function_name + ": Pad is uninitialised");
  if (!(Pad->height())) then
     fatal_error(function_name + "Pad has zero height!");
   if (!(Pad->width())) then
     fatal_error(function_name + "Pad has zero width!");
}

// do we currently have a file open?
boolean operating_mode::_file_not_open(void)
{ if (Tplotmode < Low_rate_modes) then
	 if (Edr_f) then
		return 0;

  if (Tplotmode < High_rate_modes) then
	 if (High_rate_frame_p) then
		return 0;

  error("No file open");
  return true;
}


// return the time of a single record
timeclass operating_mode::_time_of_record(const int logical_rec_nr)
{  return (Sorted_list[logical_rec_nr].t);
}

// bin a single record. The record number passed is LOGICAL.
void operating_mode::_bin(const int logical_rec_nr)
{ if (!Is_binned) then
  { heap_check(Is_binned = new boolean [Filesize]);
    for (int n = 0; n < Filesize; n++)
      Is_binned[n] = false;
  }

  if (Is_binned[logical_rec_nr]) then 
    return;
  if (File_type < Pds_browse_modes) then
    return;

  yield();
  DREstring colon(":");
  Edr_f->read(Gmdr, _ltp(logical_rec_nr));
  if (!Gmdr.is_science()) then
    return;

  const int percent = (int)((logical_rec_nr * 100.0 / Filesize) + 0.5);
  display_status(DREstring10(percent) + (DREstring)"% | " +
                 DREstring10(Gmdr.year()) + colon +
                 DREstring10(Gmdr.day()) + colon +
                 DREstring10(Gmdr.hour()) + colon +
                 DREstring10(Gmdr.minute()) + colon +
                 DREstring10(Gmdr.second()));
  Binned_record[logical_rec_nr] = Gmdr;
  Is_binned[logical_rec_nr] = true;
}

// bin an entire file
void operating_mode::_bin(void)
{ if (_file_not_open()) then
    return;

  if (File_type < Pds_browse_modes) then
    return;

  busy("Binning records");
  for (int n = 0; n < Filesize; n++)
    _bin(n);
  unbusy();
  clear_status();
}

void operating_mode::_comment(DREstring& comment_string)
{ const int // string_length = comment_string.length() + 2,
		        y = Pad->image_bottom() + 
                              (3 * (Pad->height() - Pad->image_bottom()) / 4);
  
// The height of a comment is one quarter the bottom margin		 
  const int h = (Pad->height() - Pad->image_bottom()) / 4;
//  while ((3 * (Pad->width() - h * string_length) / 4) < Pad->image_left())
//	 h = (3 * h) / 4;
  *Pad << text(-90, y, h, comment_string);
//  *Pad << text(-90, y, COMMENT_TEXT_SIZE, comment_string);
}

// The following code is common to all derived classes, so I might as
// well put it here instead. Start_time should contain the desired
// time to start a plot
void operating_mode::_execute_low_rate_plot_command(void)
{ if (Start_time < 0) then
    End_time = Start_time - 1;
  else
    End_time = Start_time + timeclass(0, 0, 0, Duration);
  
// if we are doing a rawplot, then start_minutes is a record_nr
  if (Tplotmode == RAWPLOT) then
  { if (End_time >= 0) then
      do_plot(timeint(_first_after(Start_time)), End_time);
    else
      next();
  }
  else
  { if (End_time >= 0) then
      do_plot(Start_time, End_time);
    else
      next();
  }
}

// return the logical number of the first record starting at or after given
// time -- does a binary search on the sorted times
int operating_mode::_first_after(const timeclass& t)
{ const int size = Records_processed;
  if (t < Sorted_list[0].t) then
    return 0;
  if (t > Sorted_list[(size - 1)].t) then
    return -1;

  int delta = size / 4, rec_nr = size / 2;
  while (true)
  { timeclass temp2, temp1 = Sorted_list[rec_nr].t;
    if (rec_nr) then
      temp2 = Sorted_list[rec_nr - 1].t;
    else
      temp2 = -1;
    if ((temp1 >= t) && (temp2 < t)) then
      return rec_nr;
    if ((rec_nr == size - 1) && (temp1 < t)) then
      return -1;			// invalid time
    if (temp1 >= t) then
      rec_nr -= delta;
    else
      rec_nr += delta;
    delta = MAX(delta / 2, 1);
    rec_nr = MAX(rec_nr, 0);
    rec_nr = MIN(rec_nr, size - 1);
  } 
// unreachable
//  return 0;
}

// return the logical number of the last record starting at or before given
// time -- does a binary search on the sorted times
int operating_mode::_last_before(const timeclass& t)
{ const int size = Records_processed;
  if (t < Sorted_list[0].t) then
    return -1;
  if (t > Sorted_list[size - 1].t) then
    return size - 1;

  int delta = size / 4, rec_nr = size / 2;
  while (true)
  { timeclass temp2, temp1 = Sorted_list[rec_nr].t;
    if (rec_nr != size - 1) then
      temp2 = Sorted_list[rec_nr + 1].t;
    else
      temp2 = t + timeclass(0, 0, 0, 1);
    if ((temp1 <= t) && (temp2 > t)) then
      return rec_nr;
    if ((rec_nr == 0) && (temp1 > t)) then
      return -1;			// invalid time
    if (temp1 > t) then
      rec_nr -= delta;
    else
      rec_nr += delta;
    delta = MAX(delta / 2, 1);
    rec_nr = MAX(rec_nr, 0);
    rec_nr = MIN(rec_nr, size - 1);
  }
// unreachable
//  return 0;
}

// Are the current sc_mode and pra_mode in the current file?
boolean operating_mode::_modes_in_file(void)
{ set sc_modes_in_file;
  for (int n = 0; n < N_SC_MODES; n++)
	 if (Sc_modes_present[n]) then
		sc_modes_in_file += Equivalent_sc_modes[n];

  return ((Current_sc_mode < sc_modes_in_file) &&
			 (Current_pra_mode < Pra_modes_present));
}

// return a boolean for Nick; true for file opened OK, false otherwise
boolean operating_mode::_open(DREstring& file_name)
{ destroy(Edr_f);
  Binned_record.resize(0);
  Current_file_name = "NO NAME";
  destroy(Gs2_frame_jpl);
  destroy(Gs2_frame_pds);
  High_rate_frame_p = 0;

  switch (File_type)
    { case jpl_phiex :
		  heap_check(Gs2_frame_jpl = new phiex_frame);
        busy((DREstring)"OPEN " + file_name);
	     Gs2_frame_jpl->read(file_name);
		  High_rate_frame_p = Gs2_frame_jpl;
	     unbusy();
        break;
      case pds_phiex :
		  heap_check(Gs2_frame_pds = new pds_phiex_frame);
        busy((DREstring)"OPEN " + file_name);
        Gs2_frame_pds->read(file_name);
		  High_rate_frame_p = Gs2_frame_pds;
        unbusy();
        break;
      default:
	 { busy((DREstring)"OPEN " + file_name);
      switch (File_type)
      { case fixed : heap_check(Edr_f = new const_edr_file(file_name, "rb",
                                                File_record_length));
                     break;
        case variable:
                     heap_check(Edr_f = new edr_file(file_name));
                     break;
        case pds_browse:
                     heap_check(Edr_f = new bdr_file(file_name));
                     break;
		  case pds_bbrowse:
                     heap_check(Edr_f = new bbdr_file(file_name));
							break;
		  case pds:
							heap_check(Edr_f = new pdsdr_file(file_name));
							break;
		}

		if (!((FILE*)(*Edr_f))) then
		{ unbusy();
		  error((DREstring)"Cannot open file " + DREstring(file_name));
	return false;
		}

      if (!(Edr_f->magic_number_OK())) then
      { unbusy();
        error((DREstring)"File " + DREstring(file_name) +
	       (DREstring)" is not in correct format");
	return false;
		}

/*      if (Sorted_list) then
      { for (int n = 0; n < Filesize / 1000 + 1; n++)
          destroy_array(Sorted_list[n]);
        destroy_array(Sorted_list);
      }
*/

      Filesize = Edr_f->size();
      END(*Edr_f);
      Filesize_in_bytes = ftell((FILE*)(*Edr_f));

// get the spacecraft number
      Edr_f->read(Gmdr, 0);
      Spacecraft = Gmdr.sc();

// if the file contains binnable records, then allocate sufficient space on
// disk
		if (Gmdr.binnable()) then
        Binned_record.resize(Filesize);

      Sorted_list.resize(Filesize);

/* heap_check(Sorted_list = new struct time_index* [Filesize / 1000 + 1]);
      for (int n = 0; n < Filesize / 1000 + 1; n++)
        heap_check(Sorted_list[n] = new struct time_index [1000]);
*/

// reset some internal variables
		for (int n = 0; n < N_SC_MODES; n++)
        Sc_modes_present[n] = 0;
		for (n = 0; n < 16; n++)
		  Pra_submodes_present[n] = 0;
		Pra_modes_present.clear();

		for (int record_nr = 0; record_nr < Filesize; record_nr++)
		{ display_progress(file_percentage());
		  if (!(record_nr % 100)) then
	  yield();
		  Records_processed = record_nr;
		  Edr_f->read(Gmdr, record_nr);

// check for science
		  if (!Gmdr.is_science()) then
			 continue;

// For unknown reasons, JPL decided to mark EDRs with days starting
// from one instead of the more logical zero. MIDAS keeps track of
// time consistently with timeclass objects. Consequently, adjustments
// of one day are needed as times move in and out of the program.

		  if (!(record_nr % 100)) then
			 verbose(DREstring10((int)((long)record_nr * 100 / Filesize)) +
						(DREstring)"%");
// do the sort as we build the array ***
		  if (record_nr == 0) then
		  { Sorted_list[record_nr].t =
				timeclass(Gmdr.year(), Gmdr.day() - 1, Gmdr.hour(),
				Gmdr.minute(), Gmdr.second());
			 if (Sorted_list[record_nr].t < 0) then
				error("Negative time in batch_mode::_open(DREstring&)");
			 Sorted_list[record_nr].n = record_nr;
		  }
		  else                          // all records except first
		  { timeclass record_time(Gmdr.year(), Gmdr.day() - 1,
										  Gmdr.hour(), Gmdr.minute(),
										  Gmdr.second());
			 if (record_time < 0) then
				error("Negative time in batch_mode::_open(DREstring&)");
			 const int index = _first_after(record_time);
// slip if necessary, then insert ***
			 if (index != -1) then
			 { for (int n = Records_processed; n >= index; n--)
				  Sorted_list[n + 1] = Sorted_list[n];
				Sorted_list[index].t = record_time;
				Sorted_list[index].n = record_nr;
			 }
			 else                          // append
			 { Sorted_list[record_nr].t = record_time;
				Sorted_list[record_nr].n = record_nr;
			 }
		  }
	if (Gmdr.sc_mode() >= N_SC_MODES) then
		  { error("Invalid mode");
	  unbusy();
	  return false;
		  }
		  Sc_modes_present[Gmdr.sc_mode()]++;
		  for (n = 1; n <= Gmdr.n_sweeps(); n++)
	     { Pra_submodes_present[Gmdr.pra_submode(n)]++;
	       Pra_modes_present += _pra_mode_fm_submode(Gmdr.pra_submode(n));
        }
		}

		undisplay_progress();
      Records_processed = Filesize;
      Minimum_time = Sorted_list[0].t;
      Maximum_time = Sorted_list[Filesize - 1].t;
// Nick requests the next two lines
		Start_time = Minimum_time;
		Start_time -= timeclass(0, 0, 0, 0, Start_time.second());
    } 					 // end non-gs-2 read-in
  }
  initialise();
  Current_file_name = file_name;
  unbusy();
  return true;
}

void operating_mode::_title(DREstring& title_string)
{ // the height of the title is one half the top margin
  const int string_length = title_string.length() + 2;
  int height = (Pad->image_top() - Pad->usable_top()) / 2, x;
  
// If the title won't fit, then shrink it
  while (((x = ((Pad->width() - height * string_length) / 2)) <
					  Pad->image_left())
			 || ((x + height * string_length) > Pad->image_right()))
	 height = (15 * height) / 16;

// calculate its vertical position
   const int /* half_height = height / 2, */
             y = Pad->usable_top() + (3 * (Pad->image_top() - Pad->usable_top()) 
                                      / 4);
//             y = ((half_height - 1) * (Pad->image_top() -
//                                       Pad->usable_top())) /
//                     half_height;

  Pad->set_font((DREstring)"ROMAN");
  *Pad << text(-(Pad->usable_left() + 50), Pad->usable_top() + y,
			 height, title_string);
//  *Pad << text(-(Pad->usable_left() + 50), Pad->usable_top() + y,
//			 TITLE_TEXT_SIZE, title_string);
}

DREstring operating_mode::_time_range_string(void)
{ // what we produce depends on whether we have a low rate or high rate
  // file in use.

  DREstring colon(":"), rv;
  if (Edr_f) then
  { rv = DREstring10(Minimum_time.year()) + colon +
					  DREstring10(Minimum_time.day() + 1) + colon +
					  DREstring10(Minimum_time.hour()) + colon +
					  DREstring10(Minimum_time.minute()) + colon +
					  DREstring10((int)Minimum_time.second()) + (DREstring) " TO " +
					  DREstring10(Maximum_time.year()) + colon +
					  DREstring10(Maximum_time.day() + 1) + colon +
					  DREstring10(Maximum_time.hour()) + colon +
					  DREstring10(Maximum_time.minute()) + colon +
					  DREstring10((int)Maximum_time.second());
  }
//  if (Gs2_file) then
  if (Gs2_frame_jpl) then
  { rv = DREstring10(Gs2_frame_jpl->year()) + colon +
						  DREstring10(Gs2_frame_jpl->day()) + colon +
						  DREstring10(Gs2_frame_jpl->hour()) + colon +
						  DREstring10(Gs2_frame_jpl->minute()) + colon +
						  DREstring10(Gs2_frame_jpl->second());
  }
  if (Gs2_frame_pds) then
  { rv = DREstring10(Gs2_frame_pds->year()) + colon +
						  DREstring10(Gs2_frame_pds->day()) + colon +
						  DREstring10(Gs2_frame_pds->hour()) + colon +
						  DREstring10(Gs2_frame_pds->minute()) + colon +
						  DREstring10(Gs2_frame_pds->second());
  }
  return rv;
}

DREstring operating_mode::_pra_modes_string(void)
{ int pra_modes_present[N_PRA_MODES];
  for (int n = 0; n < N_PRA_MODES; n++)
    pra_modes_present[n] = 0;
 
  for (n = 0; n < 16; n++)
    if (Pra_submodes_present[n]) then
      pra_modes_present[_pra_mode_fm_submode(n)]++;

// generate the list DREstring
  DREstring rv;

  for (n = 0; n < N_PRA_MODES; n++)
    if (pra_modes_present[n]) then 
      rv += ((DREstring)_pra_mode_name[n] + " ");
  
  return rv;
}

DREstring operating_mode::_sc_modes_string(void)
{ DREstring rv;
  
  for (int n = 0; n < N_SC_MODES; n++)
    if (Sc_modes_present[n]) then
      rv += ((DREstring)_sc_mode_name[n] + (DREstring)" ");
  
  return rv;
}

void operating_mode::_plot_full_frame(void)
{ const int h = (7 * Pad->height()) / 8, w = (7 * Pad->width()) / 8;

  Pad->clear();
  Pad->set_image((Pad->width() - w) / 2,
            ((Pad->height() - h) / 2) + h - 1,
            ((Pad->width() - w) / 2) + w - 1,
            (Pad->height() - h) / 2);
  Pad->surround();
  Pad->update();
  const int label_height = Pad->image_top() / 3,
            x = MAX(Pad->image_left() - 3 * label_height, 
                    Pad->image_left() / 4);
  
  
  *Pad << text(x, Pad->image_top() +
               1 * (Pad->image_bottom() - Pad->image_top()) / 6 + label_height / 2,
               label_height, (DREstring)"HF");
  *Pad << text(x, Pad->image_top() + 
               3 * (Pad->image_bottom() - Pad->image_top()) / 6 + label_height / 2,
               label_height, (DREstring)"LF");
  *Pad << text(x, Pad->image_top() + 
               5 * (Pad->image_bottom() - Pad->image_top()) / 6 + label_height / 2,
               label_height, (DREstring)"P");
  Pad->update();

// draw x axis
  for (int line_number = 0; line_number <= 800; line_number += 100)
  { int x_position = (int)(Pad->image_left() +
							((uint32)Pad->image_width() * line_number / 800));
	 *Pad << LINE(x_position, Pad->image_bottom() + 1, x_position,
					  Pad->image_bottom() + Pad->image_top() / 5);
  }

// draw y axis
  for (int y = 1; y < 3; y++)
  { int y_position = Pad->image_top() +
							y * (Pad->image_bottom() - Pad->image_top()) / 3;
	 *Pad << LINE(Pad->image_right(), y_position, 3 * Pad->image_left()  / 4,
					 y_position);
  }
  *Pad << LINE(Pad->image_left() - 1,
				  Pad->image_top() +
				  5 * (Pad->image_bottom() - Pad->image_top()) / 6,
				  3 * Pad->image_left() / 4,
				  Pad->image_top() +
					 5 * (Pad->image_bottom() - Pad->image_top()) / 6);

  _comment(Comment);
  Pad->update();

  const uint16 fsd = (High_rate_frame_p == Gs2_frame_pds) ? 8000 : 256;
  DREstring calibrate_string = (High_rate_frame_p == Gs2_frame_pds)
										? " Calibrated" : "";

  DREstring space(" "), colon(":");
  DREstring fds_string(High_rate_frame_p->fds_count(), "%8.2f");

  _title((DREstring)"VOYAGER " + DREstring10(High_rate_frame_p->sc()) + space +
		  DREstring(High_rate_frame_p->fds_count(), "%8.2f") + space +
		  DREstring10(High_rate_frame_p->year()) + colon +
		  DREstring10(High_rate_frame_p->day()) + colon +
		  DREstring10(High_rate_frame_p->hour()) + colon +
		  DREstring10(High_rate_frame_p->minute()) + colon +
		  DREstring10(High_rate_frame_p->second()) + calibrate_string);
  Pad->update();
  for (line_number = 0; line_number < 800; line_number++)
  { yield();
	 if (line_number % 10 == 0) then
		Pad->batch_on();

	 int n_elements = Pad->image_height() / 3;
	 boolean *ch1, *ch2, *p;
	 heap_check(ch1 = new boolean [n_elements]);
	 heap_check(ch2 = new boolean [n_elements]);
	 heap_check(p = new boolean [n_elements]);

	 for (int n = 0; n < n_elements; n++)
		ch1[n] = ch2[n] = p[n] = false;
	 int x_position = (int)(Pad->image_left() +
							  ((uint32)(Pad->image_width() * line_number) / 800));

	 for (int datum_number = 0; datum_number < 400; datum_number++)
	 { uint16 lf = High_rate_frame_p->datum(line_number, datum_number, 1),
		       hf = High_rate_frame_p->datum(line_number, datum_number, 2);

/* if (lf || hf)
{ int i;
  i = 1;
} */

// calculation of polarisation is non-trivial
      float l = hf, r = lf;
		if (line_number < 100) then
      { float temp = l;
		  l = r;
		  r = temp;
		}
      if (Gs2_frame_pds) then
		{ l = exp(l / 100.0);
		  r = exp(r / 100.0);
		}

	   const int polarisation = ((l > 0) || (r > 0)) ?
                               (int)(128 + (((l - r) /(l + r)) * 128)) :
                               128;
      if (Gs2_frame_pds) then
	   { lf = MIN(lf, (uint16)fsd);
        hf = MIN(hf, (uint16)fsd);
	   }
		ch1[((int32)lf * (n_elements - 1)) / fsd] = true;
		ch2[((int32)hf * (n_elements - 1)) / fsd] = true;
		p[((int32)polarisation * (n_elements - 1)) / 256] = true;
	 }
	 const int dot_size = Pad->small_dot_size();
	 for (n = 0; n < n_elements; n++)
	 {
// High frequency
		if (ch2[n]) then
	*Pad << dot(x_position,
						  Pad->image_bottom() - (2 * Pad->image_height()) / 3 - n,
						  dot_size);
// Low frequency
		if (ch1[n]) then
		  *Pad << dot(x_position,
						  Pad->image_bottom() - (1 * Pad->image_height()) / 3 - n,
						  dot_size);
// polarisation
		if (p[n]) then
		  *Pad << dot(x_position, Pad->image_bottom() - n,  dot_size);
	 }
	 if (line_number % 10 == 9) then
		Pad->batch_off();
	 destroy_array(ch1);
	 destroy_array(ch2);
	 destroy_array(p);
  }
  if (Pad->hardcopy()) then
	 print();
}

void operating_mode::_plot_gs2_hi_res(const phiex_position& pos,
												  const int n_points,
												  const int n_strips)
{ const int points_per_strip = n_points / n_strips;
  int h = (7 * Pad->height()) / 8, w = (7 * Pad->width()) / 8;
      phiex_position current_position = pos;

// Ensure that position is within bounds
  current_position = MIN(MAX(current_position, phiex_position(0, 0)),
                         phiex_position(799, 399));

  Current_phiex_position = current_position;
  Last_phiex_position = current_position;

  Pad->clear();
  Pad->set_image((Pad->width() - w) / 2,
                 ((Pad->height() - h) / 2) + h - 1,
                 ((Pad->width() - w) / 2) + w - 1,
                 (Pad->height() - h) / 2);

  DREstring space(" "), colon(":");
  DREstring calibrate_string = (High_rate_frame_p == Gs2_frame_pds)
										? " Calibrated" : "";
  _title((DREstring)"VOYAGER " + DREstring10(High_rate_frame_p->sc()) + space +
         DREstring(High_rate_frame_p->fds_count(), "%8.2f") + space +
         DREstring10(High_rate_frame_p->year()) + colon +
         DREstring10(High_rate_frame_p->day()) + colon +
			DREstring10(High_rate_frame_p->hour()) + colon +
			DREstring10(High_rate_frame_p->minute()) + colon +
			DREstring10(High_rate_frame_p->second()) + (DREstring)"    Ch" +
			DREstring10(High_res_channel) + calibrate_string);

  _comment(Comment);
  for (int frame_nr = 0; frame_nr < n_strips; frame_nr++)
  { Pad->batch_on();
	 *Pad << box(Pad->image_left(), Pad->image_top() +
					 frame_nr * Pad->image_height() /
					 n_strips, Pad->image_right(), Pad->image_top() +
					 (frame_nr + 1) *
					 Pad->image_height()/ n_strips);
    *Pad << text(Pad->image_left() / 4,
                 (int)(Pad->image_top() + (frame_nr + 0.25) *
                       Pad->image_height() / n_strips),
                 Pad->image_top() / 4,
                 DREstring10(Current_phiex_position.line()) +
                 (DREstring)":");
    *Pad << text(Pad->image_left() / 4,
                 (int)(Pad->image_top() + (frame_nr + 0.75) *
							  Pad->image_height() / n_strips),
                 Pad->image_top() / 4,
                 DREstring10(Current_phiex_position.datum()));

    int old_x = 0, old_y = 0;

    const int fsd = (High_rate_frame_p == Gs2_frame_pds) ? 8000 : 256;
    for (int point_nr = 1; point_nr <= points_per_strip; point_nr++)
    { const int x_position = Pad->image_left() +
           (((uint32)(point_nr - 1) *
                       (Pad->image_right() - Pad->image_left())) /
                         (points_per_strip - 1));
      int y_position = Pad->image_top() + (frame_nr + 1) *
      Pad->image_height() / n_strips;
		if (High_res_channel < 3) then
      y_position -= (High_rate_frame_p->datum(Current_phiex_position.line(),
                                          Current_phiex_position.datum(),
                                          High_res_channel) *
                     Pad->image_height()) / (n_strips * fsd);
      else
      { uint16 d1 = High_rate_frame_p->datum(Current_phiex_position.line(),
                                          Current_phiex_position.datum(), 1);
        uint16 d2 = High_rate_frame_p->datum(Current_phiex_position.line(),
                                          Current_phiex_position.datum(), 2);
        if (d1 || d2) then
        y_position -= (int)(((d1 - d2) * 127.0) / (1.0 * (d1 + d2)) *
                            Pad->image_height() / (n_strips * 255));
        y_position -= Pad->image_height() / (n_strips * 2);
      }

      if (old_x) then
      *Pad << LINE(old_x, old_y, x_position, y_position);
      old_x = x_position;
      old_y = y_position;

      Current_phiex_position = MIN(Current_phiex_position + 1,
                                     phiex_position(799, 399));

// yield once per line
      if (!(Current_phiex_position.datum())) then
        yield();
    }
    Pad->batch_off();
  }
//  if (!(Pad->hardcopy())) then
//    print();
}

int operating_mode::_pra_mode_fm_submode(int pra_submode)
{ switch (pra_submode)
  { case pollo : case pollo1 : return prapollo;
    case harad : case harad1 : return praharad;
    case level : case level1 : case level2 : case level3 :
      return pralevel;
    case fixlol : case fixloh : return prafixlo;
    case vlobrl : case vlobrh : return pravlobr;
    case xxxxxl : case xxxxxh : return praxxxxx;
    case polhil : case polhih : return prapolhi;
  }
// unreachable
  return -1;
}

int  operating_mode::_rawplot(const timeint& record_nr_proxy, const timeint&)
{ boolean OK_to_plot = false;
  byte datum;
  int return_value = 0, record_nr = record_nr_proxy.i;

// next line not needed when big_array is used
//  record_nr -= 1;			// wrt 0
  record_nr = MAX(record_nr, 0);
  record_nr = MIN(record_nr, Filesize - 1);
  Edr_f->read(Gmdr, _ltp(record_nr));

// check for science edr
  if (!Gmdr.is_science()) then
  { Previous_edr_record_nr++;
    return 0;
  }

// check for correct spacecraft mode
  if (!(Gmdr.sc_mode() < Equivalent_sc_modes[Current_sc_mode])) then
  { Previous_edr_record_nr++;
    return 0;
  }

  int upper_sweep = Gmdr.n_sweeps();
  if ((File_type == pds_browse) || (File_type == pds_bbrowse)) then
    upper_sweep = 2;
  for (int box_nr = 1; box_nr <= upper_sweep; box_nr++)
  { if (Gmdr.pra_mode(box_nr) == Current_pra_mode) then
      OK_to_plot = true;
  }

  if (!OK_to_plot) then
  { Previous_edr_record_nr++;		// wrt 1
    return 0;
  }

  _check_pad("rawplot");
  display_status(DREstring10((int)((record_nr * 100.0 / Filesize) + 0.5)) + (DREstring)"%");

  int max_height = (7 * Pad->height()) / 8, max_width = (7 * Pad->width()) / 8;
  int n_frames = upper_sweep;
  Sq_height = (max_height - n_frames - 1) / n_frames;
  Sq_width = (max_width - 2) / (Gmdr.n_channels() + 1);

//  const int n_minutes = Duration;
  int h = Sq_height * n_frames;    // h, w real height and width imageable
  const int w = Sq_width * (Gmdr.n_channels() + 1);

  Pad->clear();

  for (int n_frame = 0; n_frame < n_frames; n_frame++)
  { *Pad << box((Pad->width() - w) / 2, (Pad->height() - h) / 2 + 1 + n_frame *
               (Sq_height + 1), (Pad->width() - w) / 2 + w,
               (Pad->height() - h) / 2 + 1 + (n_frame + 1) * (Sq_height + 1));
  }

  Pad->set_image((Pad->width() - w) / 2, 
                 (Pad->height() - h) / 2 - 1 + n_frames * Sq_height,
                 ((Pad->width() - w) / 2) + w - 1,
                 (Pad->height() - h) / 2);

// set start_minutes for replot_proc
  Start_time = timeclass(Gmdr.year(), Gmdr.day() - 1,
                         Gmdr.hour(), Gmdr.minute(), Gmdr.second()) - 
               Minimum_time;

  DREstring space(" "), colon(":");
  _title((DREstring)"VOYAGER " + DREstring10(Spacecraft) + space +
         DREstring10(Gmdr.year()) + colon + DREstring10(Gmdr.day()) + colon +
         DREstring10(Gmdr.hour()) + colon + DREstring10(Gmdr.minute()) +
         colon + DREstring10(Gmdr.second()) + (DREstring)"    " +
         (DREstring)_sc_mode_name[Current_sc_mode]);
  verbose(DREstring10(Gmdr.year()) + colon + DREstring10(Gmdr.day()) + colon +
          DREstring10(Gmdr.hour()) + colon + DREstring10(Gmdr.minute()) +
          colon + DREstring10(Gmdr.second()));
  _comment(Comment);
  for (box_nr = 1; box_nr <= n_frames; box_nr++)
  { if (Gmdr.pra_mode(box_nr) == Current_pra_mode) then
    { int fixlo_offset = 0;
      if (Gmdr.fixlo(box_nr)) then
      { int sum[2], n_sum[2];
        for (int n = 0; n < 2; n++)
          sum[n] = n_sum[n] = 0;
        for (int nc = Gmdr.min_channel();
             nc <= Gmdr.max_channel(); nc++)
        { datum = Gmdr.datum(box_nr, nc);
          if (_valid(datum)) then
	  { // assume channel toggling ENABLED		***** +++++ FIX THIS
            int state = ((nc - 1) % 4) / 2;
            sum[state] += datum;
            n_sum[state]++;
          }
          if (n_sum[0] && n_sum[1]) then
            fixlo_offset = (sum[1] / n_sum[1] - sum[0] / n_sum[0]);
        }
      }
		return_value++;
		int temp_x = (int)(Pad->image_right() + (Pad->image_left() / 4)),
			 temp_y = (int)(Pad->image_top() +
								((box_nr - 1) + 0.5) * (Sq_height + 1));
			 h = Pad->image_height() / 50;
//			 h = AXIS_TEXT_SIZE;
		*Pad << text(temp_x, temp_y, h,
						(DREstring)Gmdr.pra_submode_name(box_nr));

// get the frequency information
		const int frequency = Gmdr.frequency(box_nr);

      temp_x += 20;
      temp_y += (int)(Sq_height * 0.3);

// yes, I know the "double" is superfluous. The brain dead mac needs it.
      if (Gmdr.sc_mode() == gs8) then
        *Pad << text(temp_x - 20, temp_y, h,
                     DREstring((double)(Gmdr.fixlo_frequency(box_nr) / 10.0), "%7.1f"));
      else
        *Pad << text(temp_x, temp_y, h, DREstring10(frequency));

      temp_x = (int)(5 * Pad->image_left() / 8);
      temp_y = (int)(Pad->image_top() + 
                    ((box_nr - 1) + 0.95) * (Sq_height + 1));
      *Pad << text(temp_x, temp_y, h, (DREstring)"0");
      temp_y = (int)(Pad->image_top() + ((box_nr - 1) + 0.2) * (Sq_height + 1));
      *Pad << text(temp_x, temp_y, h, (DREstring)"255");

      for (int channel = Gmdr.min_channel();
           channel <= Gmdr.max_channel(); channel++)
      { int tsense = Gmdr.polarisation(box_nr, channel);
        datum = Gmdr.datum(box_nr, channel);
        if (Gmdr.fixlo(box_nr)) then
        { int state = ((channel - 1) % 4) / 2;
          if (state) then
            datum -= fixlo_offset;
        }
        const int x = Pad->image_left() +
                (channel - Gmdr.min_channel() + 1) * Sq_width,
                  y = Pad->image_top() + (box_nr * (Sq_height + 1) + 1)
                                       - (datum * Sq_height / 255);
        int dot_size = Pad->small_dot_size();
        if (!tsense) then
          dot_size *= 2;
        *Pad << dot(x, y, dot_size);
      }
    }
  }
// +1 not needed with big_array
  Previous_edr_record_nr = record_nr /* + 1 */;		// wrt 1
  clear_status();
  Pad->update();
  return return_value;
}

// comparison function used by qsort
int shortintcompare(const void* i, const void* j)
{ return (*(uint16*)i - *(uint16*)j); }

int operating_mode::_scale(timeclass& start_time, timeclass& end_time)
{ int return_value = 0;

  const int lowest_rec_nr = _first_after(MAX(start_time, Minimum_time)),
            highest_rec_nr = _last_before(end_time),
            n_recs = highest_rec_nr - lowest_rec_nr + 1;

  if (highest_rec_nr < lowest_rec_nr) then		// no valid data
    return 0;

  int record_nr = lowest_rec_nr, n_sweeps = 0;

  busy((DREstring)"calculating grey scale");
  while ((record_nr <= highest_rec_nr) && !n_sweeps)
  { // GMDR gmdr;
    Edr_f->read(Gmdr, _ltp(lowest_rec_nr));
    if (!Gmdr.is_science()) then
    { record_nr++;
      continue;
    }

    if (Gmdr.sc_mode() < Equivalent_sc_modes[Current_sc_mode]) then
    { n_sweeps = (Tplotmode < Binned_modes ? 1 : Gmdr.n_sweeps());
      if (Gmdr.vim()) then
        n_sweeps /= 1;
    }
  }
    
  BIG_4_ARRAY(uint16) data_array(n_recs, n_sweeps,
										  End_channel - Start_channel + 1, 2);

  data_array.set_value(0);
// we do some gyrations to handle the cases (I hope) where we are short
// of memory on a nonVM machine

/*  int temp_start_channel = Start_channel,
		temp_end_channel = End_channel;

  while (temp_start_channel < End_channel)
  { fourshort data_array(n_recs, temp_end_channel - temp_start_channel + 1,
                         n_sweeps, 2, temp_start_channel, temp_end_channel);

    temp_start_channel = data_array.actual_start_channel();
    temp_end_channel = data_array.actual_end_channel();

    for (int n1 = 0; n1 < n_recs; n1++)
		for (int n2 = 0; n2 < (temp_end_channel - temp_start_channel + 1); n2++)
        for (int n3 = 0; n3 < n_sweeps; n3++)
          for (int n4 = 0; n4 < 2; n4++)
	    data_array.datum(n1, n2, n3, n4) = 0; */

    record_nr = lowest_rec_nr;
    while (record_nr <= highest_rec_nr)
    { if (!(record_nr - lowest_rec_nr) % 10) then
	yield();

unbusy();

//      static GMDR gmdr;
      Edr_f->read(Gmdr, _ltp(record_nr));

// check for science
      if (!Gmdr.is_science()) then
      { record_nr++;
        continue;
      }
    
      if (Gmdr.sc_mode() < Equivalent_sc_modes[Current_sc_mode]) then
      { int upper_sweep = (Tplotmode < Binned_modes ? 1 :
                           Gmdr.n_sweeps()),
            sweep_incr = 1;
        if (Gmdr.vim()) then
        { upper_sweep /= 1;
          sweep_incr = /* 2 */ 1;
        }
        if ((File_type == pds_browse) || (File_type == pds_bbrowse)) then
          sweep_incr = 2;

        int apparent_sweep_duration = Gmdr.sweep_duration();
        if ((File_type == pds_browse) || (File_type == pds_bbrowse)) then
          apparent_sweep_duration *= 2;

          for (int sweep = 1; sweep <= upper_sweep; sweep += sweep_incr)
        { timeclass actual_time = timeclass(Gmdr.year(),
                                            Gmdr.day() - 1, Gmdr.hour(),
                                Gmdr.minute(), Gmdr.second() + 
                                ((sweep - 1) * apparent_sweep_duration));

// check that neither the mode nor configuration is null
			  if (!(Gmdr.mode_command())) then
				 continue;
			  if (!(Gmdr.configuration_command())) then
				 continue;

			 if (Gmdr.pra_mode(sweep) == Current_pra_mode) then
				for (int channel = Start_channel; channel <= End_channel;
							channel++)
				{ int tsense = Gmdr.polarisation(sweep, channel);
				  return_value++;
				  uint16 datum = Gmdr.datum(sweep, channel);
				  if (/* Gmdr.vim() || */ (File_type == pds_browse) || (File_type == pds_bbrowse)) then       			// LHC + RHC
					 datum = MAX(datum, Gmdr.datum(sweep + 1, channel));

				  if ((Tplotmode == BINNED_SPECTRUM))
					 then
			{ datum = Binned_record[record_nr].bdatum(channel);
					 tsense = 0;
				  }

				  if (_valid(datum)) then
		data_array.element(record_nr - lowest_rec_nr, sweep - 1,
		  channel - Start_channel, tsense) = datum;
				}
		  }
		}
	 record_nr++;
	 }

	 for (int ch = 0; ch < 201; ch++)
		for (int p = 0; p < 2; p++)
		{ Background[ch][p] = 0;
		  Midground[ch][p] = 0;
		  Step[ch][p] = 0;
		}

// calibrate each channel and polarisation independently
	 data_array.freeze();
	 uint16* sort_array;
	 heap_check(sort_array = new uint16 [n_recs * n_sweeps]);
	 for (int pol = 0;
			pol <= (((Tplotmode == BINNED_SPECTRUM)) ? 0 : 1); pol++)
		for (int channel = Start_channel; channel <= End_channel; channel++)
		{ yield();
	for (int n1 = 0; n1 < n_recs; n1++)
			 for (int n2 = 0; n2 < n_sweeps; n2++)
				sort_array[n1 * n_sweeps + n2] = data_array.element(n1, n2,
						  channel - Start_channel,
						  pol);

// speed things by getting rid of all the invalid data
		  int out_index = 0;
		  for (int in_index = 0; in_index < n_recs * n_sweeps; in_index++)
	  if (sort_array[in_index]) then
				sort_array[out_index++] = sort_array[in_index];
		  const int last_index = out_index - 1;

		  qsort((void*)sort_array, last_index + 1, sizeof(uint16),
				  shortintcompare);
		  if (last_index > -1 /* first_non_zero_element < (n_recs * 8)*/ )
		  { const int n_greys = Pad->max_character_number(Sq_width, Sq_height)
									- 1;

// start with the general case; specific exceptions come later
		  float background = sort_array[last_index / 10],
				  foreground = sort_array[(9 * last_index) / 10],
              plotted_dynamic_range = 0;

        if (foreground) then
          plotted_dynamic_range = (foreground - background) / foreground;

			 if (foreground != background) then
				Step[channel][pol] = n_greys / (foreground - background);
	  Background[channel][pol] = (uint16)background;

// special case : small dynamic range => keep the plot fairly dark
//			 for (int n = 2; n < 16; n *= 2)
//				if (plotted_dynamic_range < n_greys / n) then
//			Step[channel][pol] /= n;
      if (plotted_dynamic_range < 0.1) then
        Step[channel][pol] /= 2;

// now apply brightness modifier
	  Step[channel][pol] *= Brightness / 50.0;
// apply saturation modifier
		 Background[channel][pol] +=
				  (int)(plotted_dynamic_range * (1 - Saturation / 50.0));
	}
	 }
  destroy_array(sort_array);
  unbusy();
  return return_value;
}

int operating_mode::_tomplot(const timeint& start_timeint,
                             const timeint& end_timeint)
{ timeclass& start_time = *(start_timeint.t);
  timeclass& end_time = *(end_timeint.t);

// make sure that we plot a reasonable range of data, regardless of what
// was actually requested
  if (start_time < Minimum_time)
  { start_time = Minimum_time;
    end_time = start_time + timeclass(0, 0, 0, Duration);
  }

  if (start_time > Maximum_time)
  { end_time = Maximum_time;
    start_time = end_time - timeclass(0, 0, 0, Duration);
  }

  boolean started_plot = false;
  uint16 datum;
  int sweep, tsense, x_position, y_position, temp_x, temp_y,
		return_value = 0;

  display_status(DREstring10((int)(100.0 * ((start_time - Minimum_time) /
						(Maximum_time - Minimum_time)) + 0.5)) + (DREstring)"%");

  int max_height = (7 * Pad->height()) / 8,
		max_width = (5 * Pad->width()) / 6;
  if (Pad->hardcopy()) then
	 max_width = (6 * Pad->width()) / 7;

  Sq_height = (max_height - 2 - (N_tomplot_channels - 1)) / N_tomplot_channels;

// Here we have a problem. Because of the ability to change the duration
// of "sweeps" in VIM, we cannot automatically build the screen without
// taking a look at an edr to determine the current duration of a sweep.
  boolean image_set = false;
  const int lowest_rec_nr = _first_after(start_time),
				highest_rec_nr = _last_before(end_time);
  int record_nr = lowest_rec_nr, h, w;

// bin if necessary
  if (Tplotmode < Binned_modes) then
  { busy("Binning records");
    for (int rec_nr = lowest_rec_nr; rec_nr <= highest_rec_nr; rec_nr++)
      _bin(rec_nr);
    unbusy();
    clear_status();
  }

  while ((record_nr <= highest_rec_nr) && (!image_set))
  { Edr_f->read(Gmdr, _ltp(record_nr++));
	 if (!Gmdr.is_science()) then
		continue;

// we have a record
	 h = Sq_height * N_tomplot_channels;
	 Pad->set_image((Pad->width() - max_width) / 2,
						 ((Pad->height() - h) / 2) + h - 1,
						 ((Pad->width() - max_width) / 2) + max_width - 1,
						 (Pad->height() - h) / 2);
	 image_set = true;
  }

// if autoscaling is switched on, we need an initial pass through the
// data to be plotted
  if (Autoscale_tomplot) then
  { busy("Autoscaling");
    uint16 * min_array, * max_array;

    heap_check(min_array = new uint16 [N_tomplot_channels]);
    heap_check(max_array = new uint16 [N_tomplot_channels]);

    for (int n = 0; n < N_tomplot_channels; n++)
    { min_array[n] = 64000;
      max_array[n] = 0;
    }

    record_nr = lowest_rec_nr;
    while ((record_nr <= highest_rec_nr) && (!Cancelled))
    { if (Tplotmode < Binned_modes) then
		  _bin(record_nr);
      Edr_f->read(Gmdr, _ltp(record_nr));
	   if (!Gmdr.is_science()) then
	   { record_nr++;
        continue;
	   }
	   const int upper_sweep = (Tplotmode == BINNED_SPECTRUM ? 1 :
                                (((File_type == pds_browse) || (File_type == pds_browse)) ? 2 :
								        Gmdr.n_sweeps()));
	   for (sweep = 1; sweep <= upper_sweep; sweep++)
	   { if (Gmdr.pra_mode(sweep) == Current_pra_mode) then
		  { for (int channel = Start_tomplot_channel;
                   channel < Start_tomplot_channel + N_tomplot_channels;
                   channel++)
          { const int index = channel - Start_tomplot_channel;
            if (Tplotmode < Binned_modes) then
              datum = Binned_record[record_nr].bdatum(channel);
            else
              datum = Gmdr.datum(sweep, channel);
            if (datum) then
            { min_array[index] = MIN(min_array[index], datum);
              max_array[index] = MAX(max_array[index], datum);
            }
          }
        }
      }
      record_nr++;
    }
    if (Cancelled) then
    { Cancelled = false;
      return 1;
    }

// min array and max array hold the minimum and maximum per-channel values.
// Now set the min and max values of Tomplot_dn_range for each displayed
// channel. If the values are octets, then set to the nearest 10, rounded
// in the correct direction. If the values are uint16s, then set to the
// nearest 1000, rounded in the correct direction.
    uint16 max_of_max = 0;
    for (n = 0; n < N_tomplot_channels; n++)
      max_of_max = MAX(max_of_max, max_array[n]);
    const uint16 round_value = (max_of_max > 255) ? 1000 : 10;
    for (n = Start_tomplot_channel;
         n < Start_tomplot_channel + N_tomplot_channels;
         n++)
    { Tomplot_dn_range[n][0] = min_array[n - Start_tomplot_channel];
      Tomplot_dn_range[n][0] = (Tomplot_dn_range[n][0] / round_value) *
                                 round_value;
      Tomplot_dn_range[n][1] = max_array[n - Start_tomplot_channel];
      Tomplot_dn_range[n][1] = ((Tomplot_dn_range[n][1] / round_value) + 1) *
                                 round_value;
    }
    destroy_array(min_array);
    destroy_array(max_array);
    unbusy();
  }

// We've finished with the autoscaling
  record_nr = lowest_rec_nr;
  while ((record_nr <= highest_rec_nr) && (!Cancelled))
  { // if (Tplotmode < Binned_modes) then
	 //	_bin(record_nr);
	 Edr_f->read(Gmdr, _ltp(record_nr));

// check for science
	 if (!Gmdr.is_science()) then
	 { record_nr++;
		continue;
	 }
	 int upper_sweep = (Tplotmode == BINNED_TOMPLOT ? 1 :
							 (((File_type == pds_browse) || (File_type == pds_bbrowse)) ? 2 :
								Gmdr.n_sweeps()));

	 if (!started_plot) then
	 { Pad->batch_on();
		started_plot = true;
		return_value++;
		Pad->clear();
		_x_axis();
		for (int n_frame = 0; n_frame < N_tomplot_channels; n_frame++)
		{ const int channel = Start_tomplot_channel + n_frame;
		  *Pad << box(Pad->image_left() - 1, Pad->image_top() + n_frame *
						  Sq_height, Pad->image_right() + 1,
						  Pad->image_top() + (n_frame + 1) * Sq_height);
		  const int h = Pad->image_height() / 50;
		  *Pad << text(Pad->image_left() / 4, (int)(Pad->image_top() +
						  (n_frame + 0.6) * Sq_height), h,
						  DREstring10(Start_tomplot_channel + n_frame));

// mark the min and max values on the right of the plot
    	  temp_x = Pad->image_right() + 10;
		  temp_y = (int)(Pad->image_top() + (n_frame + 0.9) *
							  Sq_height);
		  *Pad << text(temp_x, temp_y, h,
						  DREstring10(Tomplot_dn_range[channel][0]));
		  temp_y = (int)(Pad->image_top() + (n_frame + 0.3) *
							  Sq_height);
		  *Pad << text(temp_x, temp_y, h,
						  DREstring10(Tomplot_dn_range[channel][1]));
		}
		DREstring space(" "), colon(":");
		_title((DREstring)"VOYAGER " + DREstring10(Spacecraft) + space +
				DREstring10(start_time.year()) + colon +
				DREstring10(start_time.day() + 1) +
				colon + DREstring10(start_time.hour()) + colon +
				DREstring10(start_time.minute()) + (DREstring)"    " +
				DREstring(_sc_mode_name[Current_sc_mode]));
		_comment(Comment);
		Pad->batch_off();
	 }
	 Pad->batch_on();
	 for (sweep = 1; sweep <= upper_sweep; sweep++)
	 { if (Gmdr.pra_mode(sweep) == Current_pra_mode) then
		{ for (int channel = Start_tomplot_channel; channel < Start_tomplot_channel +
			  N_tomplot_channels; channel++)
		  { int n_frame = channel - Start_tomplot_channel;

// find the sense of this channel
			 tsense = Gmdr.polarisation(sweep, channel);
			 datum = Gmdr.datum(sweep, channel);

			 if (Tplotmode == BINNED_TOMPLOT) then
          { datum = Binned_record[record_nr].bdatum(channel);
				tsense = 0;
			 }
			 if ((datum >= Tomplot_dn_range[channel][0]) &&
              (datum <= Tomplot_dn_range[channel][1])) then
			 { timeclass actual_time = timeclass(Gmdr.year(),
												  Gmdr.day() - 1,
												  Gmdr.hour(), Gmdr.minute(),
												  Gmdr.second() + ((sweep - 1) *
												  Gmdr.sweep_duration()));

				float second = (float)(dseconds(actual_time - start_time));
				x_position = Pad->image_left() +
                           (int)(second / (Duration * 60.0) * Pad->image_width());
				y_position = Pad->image_top() + (n_frame  + 1) *
								  Sq_height -
								 ((datum - Tomplot_dn_range[channel][0]) *
								  (uint32)Sq_height /
								 (Tomplot_dn_range[channel][1] -
								  Tomplot_dn_range[channel][0]));
				int dot_size = Pad->small_dot_size();
				if (dot_size == 4) then
				  dot_size = 6;
				if (!tsense) then
				  dot_size *= 2;
				if (x_position < Pad->image_right()) then
				  *Pad << dot(x_position, y_position, dot_size);
			 }
		  }
		}
	 }
	 Pad->batch_off();
	 record_nr++;
           if (Cancelled) then
           { Cancelled = false;
             return 1;
           }
  }
  clear_status();
  return return_value;
}

boolean operating_mode::_valid(const uint16 test)
{ if (test > 255) then
    return true;
  return Valid_data[test];
}

void operating_mode::_x_axis(void)
{ int x_spacing = (Duration <= 35 ? 1 : (Duration <= 180 ? 10 : (Duration >= 2880 ? 1440 : 60))),
		h = (Pad->image_top() - Pad->usable_top()) / 5,
		y = (2 * Pad->usable_bottom() + Pad->image_bottom()) / 3;

  DREstring x_axis_string;
  if (x_spacing < 60) then
	 x_axis_string = DREstring10(x_spacing) + (DREstring)" MINUTE TICK MARKS";
  else
	 x_axis_string = DREstring10(x_spacing / 60) + (DREstring)" HOUR TICK MARKS";

  Pad->set_font((DREstring)"ROMAN");
  *Pad << text(-(Pad->usable_left() + 60), y, h, x_axis_string);

  for (int n_tick = 0; n_tick <= Duration; n_tick += x_spacing)
  { const uint tick_position = Pad->image_left() +
                (uint)((1.0 * n_tick / Duration) * Pad->image_width());

	 *Pad << LINE(tick_position, Pad->image_bottom() + 1, tick_position,
					  Pad->image_bottom() +
						MIN((Pad->usable_bottom() - Pad->image_bottom()) / 4,
							 Pad->height() / 50));
  }
}

void operating_mode::_y_axis(void)
{ int n_channels = (End_channel - Start_channel + 1),
		y_spacing = (n_channels <= 50 ? 10 : (n_channels <= 150 ? 20 : 25)),
		h = (Pad->image_top() - Pad->usable_top()) / 5;
  Pad->set_font((DREstring)"ROMAN");
  int x_posn = (Pad->image_left() - Pad->usable_left()) * 7 / 8 - 6 * h;
  x_posn = MAX(0, x_posn);
  *Pad << text(x_posn, 
					(Pad->image_top() + Pad->image_bottom()) / 2, h, (DREstring)"#");
  for (int n_tick = Start_channel; n_tick <= End_channel; n_tick++)
	 if (!(n_tick % y_spacing)) then
	 { int tick_position = Pad->image_top() +
		 (int)((n_tick - Start_channel + 0.5) * Pad->image_height() / n_channels);
		*Pad << LINE(Pad->image_left() * 7 / 8,
						 tick_position, Pad->image_left() - 1, tick_position);
		DREstring channel_number(n_tick, 10);
		const int x = Pad->image_left() * 5 / 8;
		*Pad << text(x, tick_position, h,
			left_fill(channel_number, 3));
	 }
}

int operating_mode::display_spectral(const timeint& start_timeint,
                                     const timeint& end_timeint)
{ if (!((Current_pra_mode == prapollo) ||
       ((Current_pra_mode == prafixlo) &&
         ((Current_sc_mode == cr5a) ||
           (Current_sc_mode == uv5a))))) then
  { warning((DREstring)"Cannot display spectrum in mode " +
                 _sc_mode_name[Current_sc_mode] + "/" +
                 _pra_mode_name[Current_pra_mode]);
    return 1;
  }

  timeclass& start_time = *(start_timeint.t);
  timeclass& end_time = *(end_timeint.t);

// make sure that we plot a reasonable range of data, regardless of what
// was actually requested
  if (start_time < Minimum_time)
  { start_time = Minimum_time;
	 end_time = start_time + timeclass(0, 0, 0, Duration);
  }

  if (start_time > Maximum_time)
  { end_time = Maximum_time;
    start_time = end_time - timeclass(0, 0, 0, Duration);
  }

// make sure that Start_channel and End_channel are the correct way
// around
  if (Start_channel > End_channel) then
  { const int temp = Start_channel;
    Start_channel = End_channel;
	 End_channel = temp;
  }

  int sweep, channel, tsense, square_value, return_value = 0;
  boolean started_plot = false;

  display_status(DREstring10((int)(100.0 * ((start_time - Minimum_time) /
                  (Maximum_time - Minimum_time)) + 0.5)) + (DREstring)"%");

  int m_factor = 1;
  if ((Tplotmode < Binned_modes)) then
  { switch (Current_sc_mode) 
    { case cr5 : m_factor = 4; break;
      default : m_factor = 8;
    }
  }

  int max_height = (7 * Pad->usable_height()) / 8,
      max_width = (7 * Pad->usable_width()) / 8;
  if (Pad->hardcopy()) then
    max_width = (6 * Pad->usable_width()) / 7;

// we need to know the duration of a sweep
  const int lowest_rec_nr = _last_before(MAX(start_time, Minimum_time)),
	    highest_rec_nr = _last_before(end_time);
  int record_nr = lowest_rec_nr, apparent_sweep_duration;
  boolean found_science_record = false;

  while ((record_nr <= highest_rec_nr) && !(found_science_record))
  { Edr_f->read(Gmdr, _ltp(record_nr++));

// check for science
    if (!Gmdr.is_science()) then
      continue;

// automatically adjust vertical scale
    if (Start_channel < Gmdr.min_channel()) then
    { Start_channel = MAX(Start_channel, Gmdr.min_channel());
      verbose((DREstring)"Start channel adjusted to: " +
                DREstring10(Start_channel));
    }

    if (End_channel > Gmdr.max_channel()) then
    { End_channel = MIN(End_channel, Gmdr.max_channel());
		verbose((DREstring)"End channel adjusted to: " +
                DREstring10(End_channel));
    }
    
    if (Gmdr.sc_mode() < Equivalent_sc_modes[Current_sc_mode]) then
    { apparent_sweep_duration = Gmdr.sweep_duration();
      if ((File_type == pds_browse) || (File_type == pds_bbrowse)) then
        apparent_sweep_duration *= 2;

      found_science_record = true;
      Start_channel = MAX(Start_channel, Gmdr.min_channel());
      DREstring colon(":");
      verbose(DREstring10(Gmdr.year()) + colon + DREstring10(Gmdr.day()) +
              colon + DREstring10(Gmdr.hour()) + colon +
              DREstring10(Gmdr.minute()) + colon + DREstring10(Gmdr.second()));
    }
  }

// if there was no science then don't display anything
  if (!found_science_record) then
	 return return_value;

  const int n_channels = End_channel - Start_channel + 1;
  Sq_height = max_height / n_channels;
  Sq_width = ((uint32)max_width * apparent_sweep_duration * m_factor) /
					(Duration * 60L);

  const uint16 n_pixels = Sq_width * Sq_height;

  if (n_pixels >= MAX_SQUARE_SIZE) then
  { Duration *= 2;
    verbose((DREstring)"Too many pixels per grey-scale square; changing duration to " + DREstring10(Duration));
    End_time = Start_time + timeclass(0, 0, 0, Duration);
    return display_spectral(Start_time, End_time);
  }

  if (!Sq_width) then
  { Duration /= 2;
    verbose((DREstring)"Too long a duration for this width canvas; changing duration to " + DREstring10(Duration));
    End_time = Start_time + timeclass(0, 0, 0, Duration);
    return display_spectral(Start_time, End_time);
  }

  if (!Sq_height) then
  { error((DREstring)"Canvas too short");
    return 0;
  }

// bin if necessary
  if (Tplotmode < Binned_modes) then
  { busy("Binning records");
    for (int rec_nr = lowest_rec_nr; rec_nr <= highest_rec_nr; rec_nr++)
      _bin(rec_nr);
    unbusy();
    clear_status();
  }

  if (_scale(start_time, end_time)) then
  { int record_nr = lowest_rec_nr;
    while (record_nr <= highest_rec_nr)
    { Edr_f->read(Gmdr, _ltp(record_nr));

// check for science
      if (!Gmdr.is_science()) then
      { record_nr++;
        continue;
      }

      if (started_plot == false) then
      { started_plot = true;
        Previous_edr_record_nr = record_nr;
        Pad->clear();

	Upper_square_value = Pad->max_character_number
                               (Sq_width, Sq_height);
	busy("resizing rectangles");
        Pad->resize_font(Sq_width, Sq_height);
	unbusy();

		  const uint16 h = Sq_height * n_channels,
							w = ((uint32)Sq_width * Duration * 60) /
									(apparent_sweep_duration * m_factor);
		  Pad->set_image((Pad->usable_width() - w) / 2 + Pad->usable_left(),
							  ((Pad->usable_height() - h) / 2) + h - 1 + Pad->usable_top(),
							  ((Pad->usable_width() - w) / 2) + w - 1 + Pad->usable_left(),
							  (Pad->usable_height() - h) / 2 + Pad->usable_top());

		  DREstring space(" "), colon(":");
		  _title((DREstring)"VOYAGER " + DREstring10(Spacecraft) + space +
					DREstring10(start_time.year()) + colon +
					DREstring10(start_time.day() + 1) +
					colon + DREstring10(start_time.hour()) + colon +
					DREstring10(start_time.minute()) + (DREstring)"    " +
					(DREstring)_sc_mode_name[Current_sc_mode]);
		  _x_axis();
		  _y_axis();
		  _comment(Comment);
		  Pad->surround();
		  Pad->set_font_height(Sq_height);
		}

		int upper_sweep = (Tplotmode < Binned_modes ? 1
																  : Gmdr.n_sweeps()),
			 sweep_incr = 1;

		if (Gmdr.vim()) then
		{ upper_sweep /= 1;
		  sweep_incr = /* 2 */ 1;
		}
                if ((File_type == pds_browse) || (File_type == pds_bbrowse)) then
                { // upper_sweep = 1;
                  sweep_incr = 2;
                }
		Pad->update();
		Pad->batch_on();
		int display_sweep = 0;

		for (sweep = 1; sweep <= upper_sweep; sweep += sweep_incr)
		{ display_sweep++;
// check that neither the mode nor configuration is null
		  if (!Gmdr.mode_command()) then
			 continue;
	if (!Gmdr.configuration_command()) then
	  continue;

// check that this time actually appears on the plot
		  timeclass actual_time = timeclass(Gmdr.year(),
														Gmdr.day() - 1,
														Gmdr.hour(), Gmdr.minute(),
														Gmdr.second() + ((sweep - 1) *
														apparent_sweep_duration));

	if ((actual_time < start_time) || (actual_time >= end_time))
	  continue;  // may need this in _scale too

	if (Gmdr.pra_mode(sweep) == Current_pra_mode) then
			 for (channel = Start_channel; channel <= End_channel; channel++)
			 { tsense = Gmdr.polarisation(sweep, channel);
				uint16 datum = Gmdr.datum(sweep, channel);
		 if (/* Gmdr.vim() || */ (File_type == pds_browse) || (File_type == pds_bbrowse)) then				// LHC + RHCs
				  datum = MAX(datum, Gmdr.datum(sweep + 1, channel));

				if ((Tplotmode == BINNED_SPECTRUM))
				  then
		 { datum = Binned_record[record_nr].bdatum(channel);
				  tsense = 0;
				}

				if (_valid(datum)) then
				{ 
				  { square_value =  (int)((datum - Background[channel][tsense]) *
													Step[channel][tsense]) + 1;
					 if (datum <= Background[channel][tsense]) then
						square_value = 1;
					 square_value = MIN(square_value, Upper_square_value);
				  }
				}
				else
				  square_value = 0;

				*Pad += square_value;
				return_value++;
			 }

// Laserwriter adjustments
/*		  if (Pad->hardcopy()) then
		  { const int x = Pad->image_left() + 12 +
								(Sq_width * (seconds(actual_time - start_time) /
								(apparent_sweep_duration * m_factor))),
						  y = Pad->height() - Pad->image_top() + 75 - Sq_height;

			 Pad->moveto(x, y);
			 Pad->flush();
			 yield();
		  }
		  else  */                       // monitor
		  {
// the order of the following calculation is important; truncate down to
// the next lowest valid scan time
			 const int x = Pad->image_left() +
								  (Sq_width * (seconds(actual_time - start_time)
									 / (apparent_sweep_duration * m_factor)));

// don't plot if we are going to go over the edge
			 if (x + Sq_width - 1 <= Pad->image_right()) then
			 { display_progress((int)((x - Pad->image_left() * 1.0) * 100 /
			                           Pad->image_width() + 0.5));
				Pad->moveto(x, Pad->image_top());
				Pad->flush();
				yield();
			 }
		  }
		}
		Pad->batch_off();
		record_nr++;
	 }
  }
  clear_status();
  return return_value;
}

// how far through a file are we?
int operating_mode::file_percentage(void) const
{ const int rv = ((Edr_f == 0) ?  0 :
	(int)(0.5 + ((ftell((FILE*)(*Edr_f)) * 100.0) / Filesize_in_bytes)));
  if (rv < 0 || rv > 100) then
	 fatal_error("invalid file position in opertaing_mode::file_percentage()");
  return rv;
}

void operating_mode::initialise(void)
{ Previous_edr_record_nr = 0;
  End_time = -1;
  Brightness = 50;
  Saturation = 50;
  destroy_array(Is_binned);
}

int operating_mode::do_plot(const timeint& start_timeint,
                            const timeint& end_timeint)
{ 
  { int rv = (this->*_plot_function)(start_timeint, end_timeint);
    if ((Pad->hardcopy()) && (rv)) then
      print();
    return rv;
  }
}

void operating_mode::first(void)
{ RECURSE(first)
  if (_file_not_open()) then
	 return;

  switch (Tplotmode)
  { case GS2_LOW_RES :
		_plot_full_frame();
		break;
    case GS2_HIGH_RES:
      _plot_gs2_hi_res(Current_phiex_position = phiex_position(0, 0),
                       Gs2_n_data, Gs2_n_strips);
      break;

	 default :
		if (!_modes_in_file()) then
		  return;
		End_time = -1;
		Previous_edr_record_nr = -1;
		Current_phiex_position = phiex_position(0, 0);
		next();
  }
}

void operating_mode::last(void)
{ RECURSE(last)
  if (_file_not_open()) then
	 return;

  switch (Tplotmode)
  { case GS2_LOW_RES :
		_plot_full_frame();
		break;
    case GS2_HIGH_RES:
      Current_phiex_position = phiex_position(800, 0) - Gs2_n_data;
      _plot_gs2_hi_res(Current_phiex_position, Gs2_n_data, Gs2_n_strips);
      break;
	 default:
		if (!_modes_in_file()) then
		  return;
  Start_time = Maximum_time + timeclass(0, 0, 0, 1);
  Previous_edr_record_nr = Filesize + 1;
  prior();
	 }
//  do_plot(Start_time, End_time);
}

int operating_mode::next(void)
{ RECURSENONVOID(next)
  if (_file_not_open()) then
	 return 0;

  int plot_status;

  switch (Tplotmode)
  { case RAWPLOT :
	 { if (!_modes_in_file()) then
		  return 0;
      do
		{ plot_status = do_plot(Previous_edr_record_nr + 1, 0);
		} while ((plot_status == 0) && (Previous_edr_record_nr <= Filesize));
// set the start time to the time of the plot (if there was one)
                 if (plot_status) then
                 { Start_time = _time_of_record(Previous_edr_record_nr);
                   
                 }
		return plot_status;
	 }
	 case UNBINNED_SPECTRUM :
	 case BINNED_SPECTRUM :
	 case BINNED_TOMPLOT :
	 case UNBINNED_TOMPLOT :
	 { 		if (!_modes_in_file()) then
		  return 0;
do
		{ timeclass temp_start_time(Start_time);
		  if (End_time < 0) then
			 Start_time = Minimum_time;
		  else
			 Start_time = End_time;

// look for the next sorted edr which works
		  const int index = _first_after(Start_time);
		  if (index < 0) then
	{ Start_time = temp_start_time;

// don't popup a message if we are operating in a mode that produces
// more than one plot
     if (!Next_called_implicitly) then
       _popup("WARNING [NEXT]", "At end of file -- no succeeding plot");
     Next_called_implicitly = false;
 	  return 0;
	}

		  Start_time = Sorted_list[index].t;
	Start_time -= timeclass(0, 0, 0, 0, Start_time.second());
	End_time = Start_time + timeclass(0, 0, 0, Duration);
		  plot_status = do_plot(Start_time, End_time);
		}
		while ((plot_status == 0) && (End_time <= (Maximum_time +
																 timeclass(0, 0, 0, 1))));
		return plot_status;
	 }
	 case GS2_HIGH_RES :
	 { // Ensure that we do not go past the end of the file
      Current_phiex_position = MIN(Current_phiex_position,
                                   phiex_position(799, 399) - (Gs2_n_data - 1));
      _plot_gs2_hi_res(Current_phiex_position, Gs2_n_data, Gs2_n_strips);
		return 1;
	 }
	 case GS2_LOW_RES :
	 { _plot_full_frame();
		return 1;
	 }
  }
// unreachable
  return 0;
}

void operating_mode::plotall(void)
{ RECURSE(plotall)
  if (_file_not_open()) then
	 return;

		if (!_modes_in_file()) then
		  return;
  End_time = -1;
  Previous_edr_record_nr = 0;
  Current_phiex_position = phiex_position(0, 0);
  plotrmdr();
}

void operating_mode::plotrmdr(void)
{ RECURSE(plotrmdr);
  if (_file_not_open()) then
	 return;

  if (!_modes_in_file()) then
    return;

  int plot_status;
  Next_called_implicitly = true;

  do
  { plot_status = next();
	 if ((plot_status > 0)  && (!(Pad->hardcopy()))) then
      print();
  }
  while (plot_status > 0);

  Pad->clear();
}

void operating_mode::print(void)
{ RECURSE(print)
  if (To_file) then
    Pad->detach();
  else
    Pad->print(Printer_name);
}

int operating_mode::prior(void)
{ RECURSENONVOID(prior)
  if (_file_not_open()) then
	 return 0;

  int plot_status;

  switch (Tplotmode)
  { case GS2_HIGH_RES :
	 { // Ensure that we do not start before the beginning
      Current_phiex_position =
        MAX(Last_phiex_position - Gs2_n_data, phiex_position(0, 0));
      _plot_gs2_hi_res(Current_phiex_position, Gs2_n_data, Gs2_n_strips);
		return 1;
	 }


  case RAWPLOT :
	 { if (!_modes_in_file()) then
		  return 0;

// rawplot always scans forward in time, so we must manually force calls
// to go backward
      do
		{ plot_status = do_plot(Previous_edr_record_nr - 1, 0);
        if (!plot_status) then
          Previous_edr_record_nr -= 2;
		} while ((plot_status == 0) && (Previous_edr_record_nr >= 0));
		return plot_status;
	 }
	 case UNBINNED_SPECTRUM :
	 case BINNED_SPECTRUM :
	 case BINNED_TOMPLOT :
	 case UNBINNED_TOMPLOT :
	 { if (!_modes_in_file()) then
		  return 0;

      if (Start_time <= Minimum_time) then
      { _popup("WARNING [PRIOR]", "At start of file -- no preceding plot");
        return 0;
      }

        do
		{ timeclass temp_end_time(End_time);
		  if (Start_time > Maximum_time) then
	  End_time = Maximum_time;
		  else
			 End_time = Start_time;

// look for the most recent sorted edr which works
	const int index = _last_before(End_time);
        if (index < 0) then        // we are at the beginning of the file
	{ End_time = temp_end_time;
	  return next();
	}

	End_time = Sorted_list[index].t;
	End_time += timeclass(0, 0, 0, 0, 60 - End_time.second());
	Start_time = End_time - timeclass(0, 0, 0, Duration);
	Start_time = MAX(Start_time, Minimum_time);
	End_time = Start_time + timeclass(0, 0, 0, Duration);

	plot_status = do_plot(Start_time, End_time);
		}
		while ((plot_status == 0) && (Start_time > Minimum_time) );
		return plot_status;
	 }
  }
// unreachable
  return 0;
}

void operating_mode::replot(void)
{ RECURSE(replot)
  if (_file_not_open()) then
	 return;

  switch (Tplotmode)
  { case GS2_LOW_RES :
		_plot_full_frame();
		break;
	 case GS2_HIGH_RES :
      _plot_gs2_hi_res(Last_phiex_position, Gs2_n_data, Gs2_n_strips);
      break;
	 case RAWPLOT :
	 { do_plot(Previous_edr_record_nr, 0);
		break;
	 }
	 case UNBINNED_SPECTRUM :
	 case BINNED_SPECTRUM :
	 case BINNED_TOMPLOT :
	 case UNBINNED_TOMPLOT :
	 { if (Start_time > timeclass(1)) then
		{ End_time = Start_time + timeclass(0, 0, 0, Duration);
	do_plot(Start_time, End_time);
		}
		break;
	 }
  }
}

void operating_mode::busy(DREstring& msg)
{ if ((int)msg) then
	 display_status((DREstring)"Midas is busy... (" + msg + ")");
  else
	 display_status((DREstring)"Midas is busy...");
}

