/*
 * Title:  SkyView
 * Author: N P Hawkes
 *         (c) 1992
 * Purpose: View the sky from any point on the
 *          Earth's surface, at any time in 20th
 *          or 21st centuries.
 */
#define VERSION "1.02"

#include "event.h"
#include "baricon.h"
#include "res.h"
#include "resspr.h"
#include "win.h"
#include "wimpt.h"
#include "menu.h"
#include "template.h"
#include "dbox.h"
#include "bbc.h"
#include "os.h"
#include "coords.h"
#include "colourtran.h"
#include "print.h"
#include "werr.h"
#include "visdelay.h"
#include "sprite.h"
#include "signal.h"
#include "font.h"
#include "kernel.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>

#include "sv_header.h"
#include "datime.h"
#include "tvsats.h"
#include "markers.h"
#include "stars.h"
#include "sun.h"
#include "planets.h"
#include "moon.h"
#include "userobj.h"

/********************************************************/
/*                       CONSTANTS                      */
/********************************************************/

/*------------------------------------------------------*/
/*                         Names                        */
/*------------------------------------------------------*/

#define APP_NAME      "SkyView"    /* Task name       */
#define ICON_NAME     "!skyview"   /* Icon Bar icon   */
#define HORIZ_NAME    "Horiz"      /* Horiz window id */
#define VERT_NAME     "Vert"       /* Vert window id  */
#define OB_NAME       "Observer"   /* Observer dbox   */
#define INFO_NAME     "Info"       /* Info dbox       */
#define DEF_NAME      "ObData"     /* File of defaults*/
#define SEL_INFO_NAME "SelInfo"    /* Sel Info dbox   */

/*------------------------------------------------------*/
/*                        Numbers                       */
/*------------------------------------------------------*/

#define DIR_BASE 1      /* Icon No. of North button in  */
                        /* Observer Details dbox        */
#define NO_ENTRY -1     /* Dummy entry number for a menu*/
                        /* entry which does not exist.  */

/*------------------------------------------------------*/
/*                   Main Menu Items                    */
/*------------------------------------------------------*/

#define MAIN_NAME APP_NAME

#define MAIN_MENU_ITEMS "\
 >Program info,     \
  Observer...,      \
 ~Display,          \
 ~Horizontal view,  \
 ~Vertical view,    \
  Print|            \
 ~Select object,    \
 ~See selection,    \
 ~Selection info,   \
 ~Cancel selection| \
  Quit"

enum {item_info = 1,
      item_observer,
      item_display,
      item_horiz,
      item_vert,
      item_print,
      item_select,
      item_view,
      item_details,
      item_cancel,
      item_quit      };


/*------------------------------------------------------*/
/*                "View" Sub-menu Items                 */
/*------------------------------------------------------*/

#define VIEW_SUB_NAME "See selectn:"

#define VIEW_SUB_MENU_ITEMS "\
  Now,          \
  On rising,    \
  On setting,   \
  On culminating"

enum {view_item_now = 1,
      view_item_rising,
      view_item_setting,
      view_item_culminating};

/*------------------------------------------------------*/
/*               "Print" Sub-menu Items                 */
/*------------------------------------------------------*/

#define PRINT_SUB_NAME "Print:"

#define PRINT_SUB_MENU_ITEMS "\
  Horiz. quadrant,\
  Vert. complete"

enum {print_item_horizq = 1,
      print_item_vertc};

/*------------------------------------------------------*/
/*                  Other Sub-Menu Items                */
/*------------------------------------------------------*/

#define  SEL_SUB_NAME  "Select:"
#define DISP_SUB_NAME  "Display:"

/*------------------------------------------------------*/
/*                  Miscellaneous Items                 */
/*------------------------------------------------------*/

#define sv_info_field     4  /* Field for version No.   */


/********************************************************/
/*                  New types of variable               */
/********************************************************/

/*------------------------------------------------------*/
/*   Structures and Function Types for the windows      */
/*------------------------------------------------------*/

/* Set up a structure similar to a wimp_box, but        */
/* designed to suit the arguments of bbc_rectangle.     */
typedef struct {
  int x;         /* x      */
  int y;         /* y      */
  int w;         /* width  */
  int h;         /* height */
} rect_box;

/* Define a type for a compass-drawing function, which  */
/* takes no arguments and returns an os_error pointer:  */
typedef os_error *comp_drawfntype(void);

/* Define a type for a title-building function, which   */
/* takes a char pointer and a BOOL as argument and      */
/* returns void:                                        */
typedef void title_buildfntype(char *title, BOOL font_ok);

/* Define a type for a title-writing function, which    */
/* takes a char pointer and a flag and returns an       */
/* os_error pointer:                                    */
typedef os_error *title_writefntype(char *title, BOOL font_ok);

/* Structure containing information to particularise    */
/* the generic window-handling functions.               */
typedef struct {
  BOOL open;                      /* Open/Closed flag.  */
  comp_drawfntype *compass_drawfn;/*Compass-drawing fn. */
  plotobj* listptr;          /* Pointer to plotting list*/
  int number;                /* No. of objects in list. */
  int selnum;                /* ID of selected object.  */
  rect_box sel_frame;        /* Frame round selected obj*/
  wimp_box sel_bounds;       /* Bounding box of frame   */
                             /* around selected object. */
  wimp_box wind_area;        /* Area to be printed.     */
  print_transmatstr matrix;  /* Transfm matrix for print*/
  print_positionstr posn;    /* Position for print o/p. */
  title_buildfntype *title_buildfn; /*Title-building fn.*/
  title_writefntype *title_writefn; /*Title-writing  fn.*/
} sv_windata;

/*------------------------------------------------------*/
/*             Structure for Compass Label              */
/*------------------------------------------------------*/

typedef struct {
  int  len;          /* Length of compass label - 1     */
  char name[3];      /* Character string for label      */
} c_label;

/*------------------------------------------------------*/
/*       Structure for recording Module functions       */
/*------------------------------------------------------*/

typedef struct {
  buildfntype   *buildfn;  /* Ptr to List Building fn.  */
  selectfntype  *selectfn; /* Ptr to Obj Selecting fn.  */
  displayfntype *dispfn;   /* Ptr to Disp Option fn.    */
  infofntype    *infofn;   /* Ptr to Info function.     */
} fnliststr;

/*------------------------------------------------------*/
/* Structure for converting argument of Wimp_SetColour  */
/* into separate colour, gcol action and fore/back flag.*/
/*------------------------------------------------------*/

typedef union {
  int argument;
  struct {unsigned int w_colour :4;
          unsigned int gcol_act :3;
          unsigned int foreback :1;
          } fields;
  } ctrans_type;

/********************************************************/
/*                FUNCTION PROTOTYPES                   */
/********************************************************/

static void sv_info_about_program(void);
static void main_menuproc(void *handle, char *hit);
static menu main_premenuproc(void *handle);
static void view_sub_menuproc(char *hit);
static BOOL sv_initialise(void);
static BOOL create_window(char *name, wimp_w *handle, wimp_box *size);
static void sv_win_event_handler(wimp_eventstr *e, void *handle);
static void    redraw_window(wimp_w handle, sv_windata *wd);
static os_error *draw_window(sv_windata *wd, wimp_box to_do);
static void toggle_window(wimp_w handle, BOOL *open);
static os_error *draw_horiz_compass(void);
static os_error *draw_vert_compass(void);
static int  x_h(REAL azimuth);
static int  y_h(REAL altitude);
static void xy_v(REAL altitude, REAL azimuth, int *xptr, int *yptr);
static void observer(void);
static BOOL ob_raw_handler(dbox d, void *event, void *handle);
static void data_to_box(observerstr data);
static void data_from_box(observerstr *ptr);
static void ob_ok_click(void);
static BOOL save_ob_data(void);
static BOOL load_ob_data(void);
static void ob_arrows(int response, int *targptr);
static void load_sys_date(void);
static void sv_Draw_circle(int radius);
static void sv_Draw_move(int x, int y);
static void sv_Draw_line(int x, int y);
static void sv_Draw_bezi(int x1, int y1, int x2, int y2, int x3, int y3);
static void sv_Draw_endp(void);
static BOOL register_module(initfntype *initfn);
static BOOL install_select_entry(modulestr new_module);
static BOOL install_display_entry(modulestr new_module);
static void invoke_displayfns(char *hit);
static void display_rebuild(void);
static void recalc_horiz_xy(void);
static void recalc_vert_xy(void);
static void window_click(int x, int y, wimp_w wh, sv_windata *wd);
static void cutup(char *linestart[]);
static void infobox_load(int objno);
static void infobox_buttons(int nobjects);
static void build_lists(void);
static void invoke_selectfns(char *hit);
static void fillin_obfields(observerstr *ptr);
static void invalidate_framearea(wimp_w whandle, sv_windata wdat);
static void establish_new_frame(wimp_w whandle, sv_windata *wd);
static void calculate_frame(sv_windata *wd);
static void establish_new_selection(int new_owner);
static void invalidate_all(void);
static void cancel_selection(void);
static void sel_info(void);
static void build_timestring(char *stringptr, BOOL valid, int hour, int min);
static int  nearest_dir(REAL azim);
static void arrange_windows(REAL alt, REAL az);
static void arrange_horiz(REAL alt, REAL az);
static void arrange_vert(REAL alt, REAL az);
static void print_sub_menuproc(char *hit);
static BOOL pdriver_installed(void);
static void print_hquadrant(void);
static void print_vertical(void);
static BOOL overlap(wimp_box *b1, wimp_box *b2, wimp_box *olap);
static void build_horiz_title(char *title, BOOL font_ok);
static void build_vert_title(char *title, BOOL font_ok);
static os_error *write_horiz_title(char *title, BOOL font_ok);
static os_error *write_vert_title(char *title,  BOOL font_ok);
static os_error *write_title(int x, int y, char *title, BOOL font_ok);
static void ob_info_string(char *title, BOOL split, BOOL font_ok);
static os_error *write_string(char *title, int *countptr);
static void print_window(sv_windata *wd, int title_ht);
static os_error *sprite_tables(void);
static os_error *save_error(os_error *ptr1, os_error *ptr2);


/********************************************************/
/*                  GLOBAL VARIABLES                    */
/********************************************************/

/*------------------------------------------------------*/
/*                  Program environment                 */
/*------------------------------------------------------*/
       char app_dir[256];        /* For SkyView$Dir.    */
static char start_file[256]="";  /* For cmd line file.  */
static char data_file[256];      /* For name of file of */
                                 /* observer defaults.  */

/*------------------------------------------------------*/
/*            Information on the Modules                */
/*------------------------------------------------------*/
static int module_count = 0; /* No. of Modules          */
                             /* registered.             */ 
static int select_count = 0; /* No. of non-null object- */
                             /* selecting fns registered*/
static int display_count = 0;/* No. of non-null display */
                             /* functions (fns to set   */
                             /* display options)        */
                             /* registered.             */
#define MAXMODULES 32

/* Set up an array to record which modules are enabled  */
/* and which disabled.                                  */
static BOOL enabled[MAXMODULES];

/* Set up an array of fnliststrs, to record a list of   */
/* functions for each module.                           */
static fnliststr module_fns[MAXMODULES];

/* Set up an index array which defines which module     */
/* owns the nth entry in the Select menu.               */
static int sel_index[MAXMODULES];

/* Set up an index array which defines which module     */
/* owns the nth entry in the Display menu.              */
static int disp_index[MAXMODULES];

/* Set up an index array which defines which entry in   */
/* the Select menu is owned by module n.                */
static int module_index[MAXMODULES];

/*------------------------------------------------------*/
/* Character arrays etc for receiving info from Modules */
/*------------------------------------------------------*/
#define MAXRQ 5 /*Max no. of simultaneous info requests.*/
static char infobuf[MAXRQ*MAXINFO];
char *infoptr = infobuf;

/*------------------------------------------------------*/
/*                   Menu Variables                     */
/*------------------------------------------------------*/
static menu main_menu;       /* Top of menu tree        */
static menu display_sub_menu;/* Sub-menu for 'Display'  */
static menu sel_sub_menu;    /* Sub-menu for 'Sel obj.' */
static menu view_sub_menu;   /* Sub-menu for 'View sel.'*/
static menu print_sub_menu;  /* Sub-menu for 'Print'    */

char *module_submenu_hits = NULL; /* For hits on        */
                                  /* module sub-menus.  */

/*------------------------------------------------------*/
/* Flag causing Select or Disp fns to be called repeat- */
/* edly.  Used when module has a dbox which is per-     */
/* sisting (user has clicked OK with Adjust).           */
/*------------------------------------------------------*/
  BOOL dbox_persisting = FALSE;

/*------------------------------------------------------*/
/* Flag causing plotting lists to be rebuilt following  */
/* a call to a Display function.  Set by the module in  */
/* question if the Display options have been so drastic-*/
/* ally modified (eg change of position) that a simple  */
/* re-drawing of the windows will not do.               */
/*------------------------------------------------------*/
  BOOL rebuild_request = FALSE;

/*------------------------------------------------------*/
/*                   Window Variables                   */
/*------------------------------------------------------*/
static wimp_w horiz_handle;  /* Handle for Horiz window */
static sv_windata horiz_data;/* Data for Horiz window   */
static wimp_box horiz_size;  /* Dimens. of Horiz window */

static wimp_w vert_handle;   /* Handle for Vert window  */
static sv_windata vert_data; /* Data for Vert window    */
static wimp_box vert_size;   /* Dimens. of Vert window  */

/*------------------------------------------------------*/
/*                     Screen Data                      */
/*------------------------------------------------------*/
static int  width;           /* Width of horiz window,  */
                             /* OS units.               */
static int  height;          /* Height of horiz window. */
static REAL factor;          /* Scaling factor in plott-*/
                             /* ing functions.          */
static REAL compass_alt \
 =(REAL)59.5 * CONV;         /* 'Altitude' of compass   */
                             /* circle in Vert window.  */
static REAL alt_max;         /* Maximum altitude        */
                             /* in Horiz window.        */
static REAL alt_min;         /* Min altitude            */
                             /* in Vert window.         */
static wimp_box targ = \
         {-8, -8, 8, 8};     /* Target for mouse clicks */
                             /* on objects in windows.  */

/*------------------------------------------------------*/
/*              Palette and Printing Data               */
/*------------------------------------------------------*/
static BOOL printing = FALSE; /* TRUE if currently      */
                              /* printing (via driver). */

/* Set up a palette describing the standard Wimp colours*/
static wimp_paletteword screen_palette[16] =
  {
    {0x00, 0xFF, 0xFF, 0xFF},
    {0x00, 0xDD, 0xDD, 0xDD},
    {0x00, 0xBB, 0xBB, 0xBB},
    {0x00, 0x99, 0x99, 0x99},
    {0x00, 0x77, 0x77, 0x77},
    {0x00, 0x55, 0x55, 0x55},
    {0x00, 0x33, 0x33, 0x33},
    {0x00, 0x00, 0x00, 0x00},
    {0x00, 0x00, 0x44, 0x99},
    {0x00, 0xEE, 0xEE, 0x00},
    {0x00, 0x00, 0xCC, 0x00},
    {0x00, 0xDD, 0x00, 0x00},
    {0x00, 0xEE, 0xEE, 0xBB},
    {0x00, 0x55, 0x88, 0x00},
    {0x00, 0xFF, 0xBB, 0x00},
    {0x00, 0x00, 0xBB, 0xFF}
  };

/* Set up a palette which is the same as the standard   */
/* Wimp palette, but which has an inverted grey scale.  */
static wimp_paletteword printer_palette[16] =
  {
    {0x00, 0x00, 0x00, 0x00},
    {0x00, 0x33, 0x33, 0x33},
    {0x00, 0x55, 0x55, 0x55},
    {0x00, 0x77, 0x77, 0x77},
    {0x00, 0x99, 0x99, 0x99},
    {0x00, 0xBB, 0xBB, 0xBB},
    {0x00, 0xDD, 0xDD, 0xDD},
    {0x00, 0xFF, 0xFF, 0xFF},
    {0x00, 0x00, 0x44, 0x99},
    {0x00, 0xEE, 0xEE, 0x00},
    {0x00, 0x00, 0xCC, 0x00},
    {0x00, 0xDD, 0x00, 0x00},
    {0x00, 0xEE, 0xEE, 0xBB},
    {0x00, 0x55, 0x88, 0x00},
    {0x00, 0xFF, 0xBB, 0x00},
    {0x00, 0x00, 0xBB, 0xFF}
  };

/* Declare pixel translation tables for sprite plotting.*/
static sprite_pixtrans sc_pixtrans[256]; /* For screen. */
static sprite_pixtrans pr_pixtrans[256]; /* For printer.*/

/* Declare 'sprite factor' tables for sprite plotting.  */
static sprite_factors s_factors20; /*For mode 20 sprites*/
static sprite_factors s_factors12; /*For mode 12 sprites*/

/* Define some constants for functions which build title*/
#define TITLE_LEN 256
#define THREE_LINES TRUE
#define ONE_LINE    FALSE

/* Define overall scaling factor for printing.          */
#define SCALFACT (5<<14)

/* Define some constants for control of Font used to    */
/* print title strings.                                 */
#define TITLE_FONT "Homerton.Medium"
#define PAL_BLACK 0x00000000
#define PAL_WHITE 0xFFFFFF00
#define FONT_SIZE ((1<<10)/5)
#define VSHIFT 28

/*------------------------------------------------------*/
/*           Plotting Lists for Windows                 */
/*------------------------------------------------------*/
#define MAXOBJS 600
static plotobj horiz_list[MAXOBJS];
static plotobj vert_list[MAXOBJS];

/*------------------------------------------------------*/
/*               Compass-Drawing Data                   */
/*------------------------------------------------------*/
#define NDIR 8     /* No. of directions of view offered */
                   /* to user, and number of compass    */
                   /* labels in Horiz and Vert windows. */

/* Array giving the NDIR azimuthal offsets corresponding*/
/* to the "directions of view" offered to the user, and */
/* (equivalently) the positions of the compass labels.  */

static REAL direction[] = {
 (REAL)(  0.0)*CONV,
 (REAL)( 45.0)*CONV,
 (REAL)( 90.0)*CONV,
 (REAL)(135.0)*CONV,
 (REAL)(180.0)*CONV,
 (REAL)(225.0)*CONV,
 (REAL)(270.0)*CONV,
 (REAL)(315.0)*CONV};


/* Array giving the NDIR compass labels to be used by   */
/* the compass-drawing functions.                       */

static c_label compass[] = {
  {0, "N" },
  {1, "NE"},
  {0, "E" },
  {1, "SE"},
  {0, "S" },
  {1, "SW"},
  {0, "W" },
  {1, "NW"}
};


/*------------------------------------------------------*/
/* Variables & constants for invoking Draw Module in    */
/* Vert window.                                         */
/*------------------------------------------------------*/

#define sv_XDraw_ProcessPath 0x60700
#define sv_XDraw_StrokePath  0x60706
#define sv_XDraw_Stroke      0x60704
#define DrawU    100
#define PathMax  254
#define OutPMax 2048

/* Set up Draw's transformation matrix so that 1 'user  */
/* unit' is 1/DrawU OS unit.                            */
/* For now, assume DrawU = 100                          */
static int matrix[6] = {0x28F5C, 0, 0, 0x28F5C, 0, 0};

/* Set butt caps and round joins.                       */
static int cj_style  = 1;

/* Reserve space for Draw Paths.  Set up path pointer.  */
static int path[PathMax+2];
static int pathptr = 0;
static int semi[OutPMax]= {0,OutPMax*4 - 8};
                          /* Fill in No. of free bytes. */

/* 'Register Set' structures for use with Draw SWIs.    */
static os_regset draw_regsS;
static os_regset draw_regsP;


/*------------------------------------------------------*/
/*         Variables relating to Selected Object        */
/*------------------------------------------------------*/

       selectstr selection;
static BOOL selection_exists = FALSE;
static int sel_owner; /* Module number of current owner */
                      /* of selection.                  */

/*------------------------------------------------------*/
/*       Variables relating to Observer Details         */
/*------------------------------------------------------*/

static dbox d_ob;         /* dbox for Observer Details. */
static wimp_w ob_handle;  /* handle for Observer Box.   */

static BOOL observer_verified = FALSE;  /* Flag denoting*/
     /* whether user has verified the observer details. */

/* Set 'factory defaults' for Observer Details          */
       observerstr ob_data = {
 517,                /* Latitude *10 */
 TRUE,               /* North        */
 (REAL)0.902335,     /* Latitude     */
 13,                 /* Longitude*10 */
 TRUE,               /* West         */
 (REAL)0.0226893,    /* Longitude    */
 1,                  /* Day          */
 6,                  /* Month        */
 1992,               /* Year         */
 0,                  /* Hour         */
 0,                  /* Minutes      */
 0.0f,               /* Offset       */
 33754.5,            /* Julian       */
 (REAL)4.33582,      /* Loc Siderial */
 0,                  /* View North   */
 (REAL)0.0,          /* Radians      */
};

/* Set up named constants for the field numbers in the  */
/* Observer Details dbox.                               */
enum {
  ob_close_rq    =-1,    /* Result of cancelling dbox.  */
  ob_ok_butt     = 0,    /* Icon No. of OK button.      */
  ob_cancel_butt = 9,    /* Icon No. of Cancel button.  */
  set_butt       =10,    /* Icon No. of Set  button.    */
  sys_date_butt  =12,    /* Icon No. of Sys Time button.*/
  show_butt      =70,    /* Icon No. of Show button.    */
  cent_butt = 30,        /* Icon No. of century button. */
  e_or_w    = 55,        /* Icon No. of E or W display. */
  ew_butt   = 56,        /* Icon No. of E/W button.     */
  n_or_s    = 66,        /* Icon No. of N or S display. */
  ns_butt   = 67,        /* Icon No. of N/S button.     */
  dayf = 13, day_tu=14, day_td, day_uu, day_ud, /* Day    */
  monf = 19, mon_tu=21, mon_td, mon_uu, mon_ud, /* Month  */
  yrf  = 25,  yr_tu=26,  yr_td,  yr_uu,  yr_ud, /* Year   */
  hrf  = 31,  hr_tu=32,  hr_td,  hr_uu,  hr_ud, /* Hour   */
  minf = 37, min_tu=38, min_td, min_uu, min_ud, /* Minute */
  /* Longitude, Integer Part: */
  loif = 44, loi_hu=47, loi_hd, loi_tu, loi_td, loi_uu, loi_ud,
  /* Longitude, Decimal Part: */
  lodf = 46, lod_uu=53, lod_ud,
  ewf  = 55,       /* E or W */
  /* Latitude, Integer Part: */
  laif = 57, lai_tu=60, lai_td, lai_uu, lai_ud,
  /* Latitude, Decimal Part: */
  ladf = 59, lad_uu=64, lad_ud,
  nsf  = 66,       /* N or S */
  /* Time offset:            */
  offf = 74, off_uu=75, off_ud, off_xu, off_xd};

/* Increments corresponding to the arrow buttons:       */
  static int addon[6] = {-1, 1, -10, 10, -100, 100};

/* Special increments for Time Offset field:            */
  static float offset_incr[4] = {-0.5f, 0.5f, -1.0f, 1.0f};

/* Days in each month, for non-leap and leap years:     */
  static int daysin[2][13] = {
    {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
    {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
  };

/*------------------------------------------------------*/
/*      Variables & Constants relating to Info Box      */
/*------------------------------------------------------*/
static dbox d_info;
enum {
  info_cancel  =-1,   /* Response cancelling the dbox.  */
  info_ok      = 0,   /* OK button.                     */
  info_select,        /* Select button.                 */
  alt_field    = 4,   /* Altitude of object.            */
  azim_field,         /* Azimuth of object.             */
  info_uparrow,       /* Butt. to display next object.  */
  info_objno,         /* Display of object's number.    */
  info_dnarrow,       /* Butt. to display prev object.  */
  info_textline       /* First line of text.            */
};

/* For info on objects which have been clicked on.      */
static int  info_id[MAXRQ];
static int  info_modno[MAXRQ];
static selectfntype *sel_fn_ptr[MAXRQ];
static char *info_lineptr[MAXRQ][INFOBOX_LINES];
static REAL info_alt[MAXRQ];
static REAL info_azim[MAXRQ];

/*------------------------------------------------------*/
/*       Constants realting to Selection Info Box       */
/*------------------------------------------------------*/
enum {
  selinfo_cancel = -1, /* Response cancelling the dbox. */
  selinfo_ok,          /* OK button.                    */
  selinfo_rise,        /* Field for time of rising.     */
  selinfo_set,         /* Field for time of setting.    */
  selinfo_culmin,      /* Field for time of culminating.*/
  selinfo_textline     /* First line of text.           */
};

 
/********************************************************/
/*           Generic Window-Handling Functions          */
/********************************************************/

/*------------------------------------------------------*/
/* Create a window, yielding its handle.                */
/* Return TRUE if OK                                    */
/*------------------------------------------------------*/

static BOOL create_window(char *name, wimp_w *handle, wimp_box *size)
{
  wimp_wind *window;    /* pointer to window definition */
  os_error *creation_error;

  window = template_syshandle(name);  /* find template  */
  if (window == 0)
    return FALSE;

  /* create window, handling any errors                 */
  creation_error=wimpt_complain(wimp_create_wind(window, handle));
  if (creation_error != 0)
    return FALSE;

  /* Make a note of the dimensions of the window.       */
  *size = window->ex;

  return TRUE;
}

/*------------------------------------------------------*/
/*             Event Handler for a window               */
/*------------------------------------------------------*/

static void sv_win_event_handler(wimp_eventstr *e, void *handle)
/* handle points to a data block for the window */
{
  sv_windata *windata = handle;
 
  switch (e->e)
  {
    case wimp_EREDRAW:
      /* Register current screen mode.  Re-calculate    */
      /* sprite-plotting tables if mode has changed.    */
      if (wimpt_checkmode()) wimpt_noerr(sprite_tables());
      /* Draw requested part of requested window.       */
      redraw_window(e->data.o.w, windata);
      break;

    case wimp_EOPEN:
      wimpt_noerr(wimp_open_wind(&e->data.o));
      break;

    case wimp_ECLOSE:
      wimpt_noerr(wimp_close_wind(e->data.o.w));
      windata->open = FALSE;
      break;

    case wimp_EBUT:
    {
      int x = e->data.but.m.x;    /* Coordinates of     */
      int y = e->data.but.m.y;    /* mouse click.       */
      wimp_w wh = e->data.but.m.w;/* Window handle.     */
      window_click(x, y, wh, windata);
      break;
    }

    default:
      break;
  }
  return;
}

/*------------------------------------------------------*/
/*        Handler for mouse click inside window         */
/*------------------------------------------------------*/
static void window_click(int x, int y, wimp_w wh, sv_windata *wd)
{
  wimp_wstate result;           /*Data on window.       */
  coords_cvtstr conv;           /*For doing conversions.*/
  coords_pointstr pt;           /*For mouse coordinates.*/
  wimp_box work_targ;           /*Target for clicks, in */
                                /*work area coords.     */
  infofntype *infofn;           /*Ptr to info function. */

  plotobj *pstart=wd->listptr;  /*Point to start of list*/
  plotobj *pend  =pstart + wd->number; /* Point to end. */
  plotobj *p;

  int hits = 0;

/* Obtain data to convert between screen coords and     */
/* window workspace coords.                             */
  wimpt_noerr(wimp_get_wind_state(wh, &result));

/* Fill in the coords_cvtstr required by conversion     */
/* functions.                                           */
  conv.box = result.o.box;
  conv.scx = result.o.x;
  conv.scy = result.o.y;

/* Fill in the coords_pointstr with the screen coords of*/
/* the mouse click.                                     */
  pt.x = x;
  pt.y = y;

/* Convert to work area coords.                         */
  coords_point_toworkarea(&pt, &conv);

/* Search for hits with mouse on plotting objects.      */
/* Ignore objects whose parent module is currently      */
/* disabled, or whose parent module has no info fn.     */
/* Max No. of 'hits' is MAXRQ.                          */

  for(p = pstart; p<pend && hits<MAXRQ; p++)
  {
    /* Set up target for this object.                   */
    coords_offsetbox(&targ, p->wind_x, p->wind_y, &work_targ);
    /* Test for a hit.                                  */
    if (coords_withinbox(&pt, &work_targ) &&\
        enabled[p->module]                &&\
        (infofn = module_fns[p->module].infofn, infofn != NULL))
    {

      /* Point to place to write info string.           */
      infoptr = infobuf + hits*MAXINFO;

      /* Get info from module.                          */
      infofn(p->id);

      /* Cut info string up into INFOBOX_LINES lines.   */
      cutup(info_lineptr[hits]);

      /* Store data on object.  Increment hits at end.  */
      sel_fn_ptr[hits] = \
       module_fns[p->module].selectfn; /* Ptr to sel fn.*/
      info_modno[hits]= p->module;/* Module number.     */
      info_id[hits]   = p->id;    /* Object's id.       */
      info_alt[hits]  = p->alt;   /* Altitude.          */
      info_azim[hits] = p->azim;  /* Azimuth.           */
      hits++;
    }
  }

  if (hits==0)  return;

/* Load info dbox with details of last object.          */
  infobox_load(hits-1);

/* Fade out up and down arrows if there is only 1 object*/
  if (hits==1)
  {
    dbox_fadefield(d_info, info_uparrow);
    dbox_fadefield(d_info, info_dnarrow);
  }
  else
  {
    dbox_unfadefield(d_info, info_uparrow);
    dbox_unfadefield(d_info, info_dnarrow);
  }


/* Display dbox.                                        */
  dbox_show(d_info);

/* Deal with clicks on its buttons, changing between    */
/* 'hits' different objects as requested by user.       */
  infobox_buttons(hits);

/* Remove dbox from screen.                             */
  dbox_hide(d_info);

  return;
}


static void cutup(char *linestart[])
/* Divide the contents of the info buffer into          */
/* INFOBOX_LINES separate lines.                        */
/* Write pointers to the start of each line into the    */
/* pointer array linestart.                             */
{
  int i;
  char *startptr;

  /* Make sure there is an end-of-string marker in the  */
  /* buffer.                                            */
  *(infoptr+MAXINFO-1) = '\0';

  /* First line always starts at start of buffer.       */
  linestart[0] = infoptr;

  /* Look for the remaining INFOBOX_LINES - 1 lines.    */
  for (i=1; i<INFOBOX_LINES; i++)
  {
    /* Look for end-of-line marker.                     */
    startptr = strchr(linestart[i-1],'\n');
    if (startptr != NULL)
      /* If found, change it to end-of-string, and point*/
      /* to start of next line.                         */
      *startptr++ = '\0';
    else
      /* If not found, point to end-of-string marker.   */
      startptr = linestart[i-1] + strlen(linestart[i-1]);
    linestart[i] = startptr;
  }

  return;
}


static void infobox_load(int objno)
{
/* Load details of object objno into info dbox.         */

  int i;
  REAL degrees;
  char numbuf[32];

  /* Fade Select button if object cannot be selected.   */
  if (sel_fn_ptr[objno]==NULL)
    dbox_fadefield(d_info, info_select);
  else
    dbox_unfadefield(d_info, info_select);

  /* Altitude field.                                    */
  degrees = info_alt[objno]/CONV;
  sprintf(numbuf, "%+5.1f", (double)degrees);
  dbox_setfield(d_info, alt_field, numbuf);

  /* Azimuth field.                                     */
  degrees = info_azim[objno]/CONV;
  sprintf(numbuf, "%5.1f", (double)degrees);
  dbox_setfield(d_info, azim_field, numbuf);

  /* Object number.                                     */
  sprintf(numbuf, "Object %i", objno+1);
  dbox_setfield(d_info, info_objno, numbuf);

  /* Write INFOBOX_LINES lines of text.                 */
  for (i=0; i < INFOBOX_LINES; i++)
    dbox_setfield(d_info, info_textline+i, info_lineptr[objno][i]);

  return;
}


static void infobox_buttons(int nobjects)
{
/* Deal with clicks on buttons in info box.  Change     */
/* back and forth amongst the nobjects objects as       */
/* requested by the user.                               */
/* The object initially showing is number nobjects-1.   */

int obj = nobjects-1; /* No. of object showing initially*/
int  response;   /* Latest field clicked by user.       */
BOOL keep_open;  /* TRUE if dbox to remain open after   */
                 /* this button is processed.           */
                                                          
/* Loop until dbox is to close.                         */
  do
  {

  /* Get field number of latest click.                  */
    response = dbox_fillin(d_info);

  /* Take appropriate action.                           */
    switch (response) {

      case info_cancel:
      /* Dbox cancelled. Just close it.                 */
        keep_open = FALSE;
        break;

      case info_ok:
      /* Finished with dbox.                            */
        keep_open = dbox_persist();
        break;

      case info_select:
      /* Select the object whose details are currently  */
      /* on display.                                    */
        /* Set id field of 'selection' to specify which */
        /* object is meant.                             */
        selection.id = info_id[obj];
        /* Call appropriate Select function to fill in  */
        /* the remaining fields.                        */
        module_fns[info_modno[obj]].selectfn(window_selection);
        /* Establish the new selection.                 */
        establish_new_selection(info_modno[obj]);
        keep_open = dbox_persist();
        break;

      case info_uparrow:
      /* Display info on next object.                   */
        obj = (obj+1)%nobjects;
        infobox_load(obj);
        keep_open = TRUE;
        break;

      case info_dnarrow:
      /* Display info on previous object.               */
        obj = (obj+nobjects-1)%nobjects;
        infobox_load(obj);
        keep_open = TRUE;
        break;

      default:
      /* Unknown response.  Do nothing.                 */
        keep_open = TRUE;
        break;

    }

  } while (keep_open);

  return;
}


/*------------------------------------------------------*/
/*                 Re-draw a window                     */
/*------------------------------------------------------*/

static void redraw_window(wimp_w handle, sv_windata *wd)
{
  int more, x_orig, y_orig;
  wimp_redrawstr r;
  wimp_box to_redraw;

  r.w = handle;
  wimpt_noerr(wimp_redraw_wind(&r, &more));
  /* Work out screen coords of work area origin.        */
  x_orig = r.box.x0 - r.scx;
  y_orig = r.box.y1 - r.scy;

  while (more)
  {
    /* Move graphics origin to coincide with work       */
    /* area origin.                                     */
    bbc_origin(x_orig,y_orig);

    /* Get work area coords of rectangle to re-draw     */
    coords_offsetbox(&r.g, -x_orig, -y_orig, &to_redraw);

    /* Draw requested part of window.                   */
    wimpt_noerr(draw_window(wd, to_redraw));

    /* Move graphics origin back again                  */
    bbc_origin(0,0);

    /* Get next rectangle to do.                        */
    wimpt_noerr(wimp_get_rectangle(&r, &more));
  }

  return;
}


/*------------------------------------------------------*/
/*  Function to plot (or print) the specified part of a */
/*                        window.                       */
/*------------------------------------------------------*/
static os_error *draw_window(sv_windata *wd, wimp_box to_do)
{
  os_error *errptr;
  plotobj *p;
  plotobj *pstart = wd->listptr;  /*Start of plot list. */
  plotobj *pend   = pstart + wd->number; /*End of list. */

/*......................................................*/
/*       Plot the objects in the plotting list.         */
/*......................................................*/

/* Only plot those whose bounding box overlaps the      */
/* rectangle to be drawn.                               */
/* Do not plot if parent module is currently disabled.  */

  for(p = pstart; p < pend; p++)
    if (coords_boxesoverlap(&to_do, &p->bounds) &&\
      enabled[p->module])
    {
      errptr = p->plotfn(p->wind_x, p->wind_y, p->id);
      if (errptr != NULL) return errptr;
    }
/*......................................................*/

/* Also do the following, without bothering to check    */
/* whether the requested rectangle includes them:       */

/* Draw frame around selected object, if it exists      */
/* and is currently in window.                          */
  if (selection_exists && wd->selnum != NOWHERE)
  {
/* Get x and y coords of selected object.               */
    int sel_x = (pstart + wd->selnum)->wind_x;
    int sel_y = (pstart + wd->selnum)->wind_y;
/* Set foreground graphics colour to Wimp colour 0      */
    errptr = sv_setcolour(0);
    if (errptr != NULL) return errptr;
/* Draw frame, after shifting it to coords of           */
/* selected object.                                     */
    errptr = bbc_rectangle(\
      wd->sel_frame.x + sel_x, wd->sel_frame.y + sel_y,\
      wd->sel_frame.w, wd->sel_frame.h);
    if (errptr != NULL) return errptr;
  }

/* Call fn to re-draw compass points, and return.       */
  return (wd->compass_drawfn)();

}


/*------------------------------------------------------*/
/*   Toggle state of window between open and closed.    */
/*   Update Open/Closed flag appropriately.             */
/*------------------------------------------------------*/

static void toggle_window(wimp_w handle, BOOL *open)
{
  if (*open)
    /* Close the window */
  {
    wimpt_noerr(wimp_close_wind(handle));
    *open = FALSE;
  }
  else
  {
    /* Get window state */
    wimp_wstate state;
    if (wimpt_complain(wimp_get_wind_state(handle, &state)) == 0)
    {
      /* Open the window, in front of all others */
      state.o.behind = -1;
      wimpt_noerr(wimp_open_wind(&state.o));
      *open = TRUE;
    }
  }
}



/********************************************************/
/*         Specific Window-handling Functions           */
/********************************************************/

/*------------------------------------------------------*/
/*         Draw compass points for Horiz window         */
/*------------------------------------------------------*/

static os_error *draw_horiz_compass(void)
{
  static int bar_y0 = -4; /* Min y coord of horiz bar.  */
  static int bar_wd =  3; /* "Width" of horiz bar.      */

  static int tik_y0 =-12; /* Min y coord of tick mark.  */
  static int tik_ht =  7; /* "Height" of tick mark.     */
  static int tik_os = -1; /* x offset of LHS of tick mk */
  static int tik_wd =  2; /* "Width" of tick mark.      */

  static int lab_dn =-14; /* Down shift to print label  */
  static int lab_lt = -8; /* Fixed left shift before    */
                          /* printing label             */
  static int chr_sh =  8; /* Left shift for each        */
                          /* additional label character */

  int i;     /* Counter for drawing compass point labels*/
  int x_pos; /* Horizontal position of compass ticks.   */
  char *ptr; /* Pointer to text of compass label.       */
  int left;  /* Total left shift before printing label. */

  os_error *errptr;
  

/* Set foreground graphics colour to Wimp colour 0      */
  errptr = sv_setcolour(0);
  if (errptr != NULL) return errptr;

/* Draw a horizontal bar across whole window to denote  */
/* horizon.                                             */
  errptr = bbc_rectanglefill(horiz_size.x0,bar_y0,width,bar_wd);
  if (errptr != NULL) return errptr;

/* Draw in the NDIR compass point labels                */
  for (i=0; i<NDIR; i+=1)
  {
    /* Position of tick mark.                           */
    x_pos = x_h(direction[i]);

    /* Calculate left shift.                            */
    left = lab_lt - compass[i].len * chr_sh;

    /* Draw tick mark.                                  */
    errptr = bbc_rectanglefill(x_pos + tik_os, tik_y0, tik_wd, tik_ht);
    if (errptr != NULL) return errptr;

    /* Move to print position.                          */
    errptr = bbc_moveby(left, lab_dn);
    if (errptr != NULL) return errptr;

    /* Point to text of label, and print it one char    */
    /* at a time.                                       */
    for (ptr = compass[i].name; *ptr; ptr += 1)
      {errptr = bbc_vdu((int)*ptr);
       if (errptr != NULL) return errptr;}

  }

  return NULL;
}


/*------------------------------------------------------*/
/*         Draw compass points for Vert window.         */
/*------------------------------------------------------*/

static os_error *draw_vert_compass(void)
{
  int i;
  int x,y;
  int lab_dn =-12;
  int lab_lt =  8;
  int chr_sh =  8;
  int left;
  char *ptr;

  os_error *errptr;

/* Set foreground graphics colour to Wimp colour 0      */
  errptr = sv_setcolour(0);
  if (errptr != NULL) return errptr;

/* Plot the Draw Path set up earlier.                   */
/* If plotting to screen, use the semi-digested form    */
/* for speed.                                           */
  if (!printing)
  {
    draw_regsS.r[0] = (int)semi;
    errptr = os_swix(sv_XDraw_ProcessPath, &draw_regsS);
  }
  else
/* If printing, process the 'raw' path to take full     */
/* advantage of the resolution of the printer.          */
  {
    draw_regsP.r[0] = (int)path;
    errptr = os_swix(sv_XDraw_Stroke, &draw_regsP);
  }
  if (errptr != NULL) return errptr;

/* Plot the NDIR compass labels.                        */
  for(i=0; i<NDIR; i++)
  {
    xy_v(compass_alt*(REAL)0.94, direction[i], &x, &y);
    y -= lab_dn;
    left = lab_lt + compass[i].len * chr_sh;
    x -= left;

    /* Move to print position.                          */
    errptr = bbc_move(x,y);
    if (errptr != NULL) return errptr;

    /* Point to text of label, and print it one char    */
    /* at a time.                                       */
    for (ptr = compass[i].name; *ptr; ptr += 1)
      {errptr = bbc_vdu((int)*ptr);
       if (errptr != NULL) return errptr;}
  }

  return NULL;
}

/********************************************************/
/*                    Plotting Functions                */
/********************************************************/

static int x_h(REAL azimuth)
{
  /*               - Horizontal window -                */
  /* Calculate x-coordinate corresponding to given      */
  /* azimuth, taking into account the current direction */
  /* of view.                                           */

  int x;
  REAL x_floating = factor * (azimuth - ob_data.ang);

  x = (int)floor((double)x_floating);
  if      (x <  horiz_size.x0)
    x += width;
  else if (x >= horiz_size.x1)
    x -= width;

  return x;
}


static int y_h(REAL altitude)
{
  /*               - Horizontal window -                */
  /* Calculate y-coordinate corresponding to given      */
  /* altitude.                                          */

  return (int)(factor*altitude);
}


static void xy_v(REAL altitude, REAL azimuth, int *xptr, int *yptr)
{
/*                   - Vert window -                    */
/* Calculate x- and y-coordinate from given altitude    */
/* and azimuth, taking into account current direction   */
/* of view.                                             */

  REAL theta = azimuth - ob_data.ang;
  REAL r     = factor*(PIby2 - altitude);

  if (xptr != NULL)
    *xptr = (int)floor( (double)r * \
                    sin((double)theta));
  if (yptr != NULL)
    *yptr = (int)floor(-(double)r * \
                    cos((double)theta));
  return;
}


/*------------------------------------------------------*/
/*         Functions for constructing Draw Path         */
/*------------------------------------------------------*/

static void sv_Draw_circle(int r)
{
  int ar = (int)((REAL)0.55228475*(REAL)r);

/* Move to (r,0).                                       */
  sv_Draw_move(r,0);

/* Set up four Bezier curves describing one quadrant each*/
  sv_Draw_bezi(  r, ar,   ar,  r,   0, r);
  sv_Draw_bezi(-ar,  r,   -r, ar,  -r, 0);
  sv_Draw_bezi( -r,-ar,  -ar, -r,   0,-r);
  sv_Draw_bezi( ar, -r,    r,-ar,   r, 0);

  return;
}

static void sv_Draw_move(int x, int y)
{
  if (pathptr+3 > PathMax) return;
  path[pathptr++] = 2;
  path[pathptr++] = x;
  path[pathptr++] = y;

  return;
}

static void sv_Draw_line(int x, int y)
{
  if (pathptr+3 > PathMax) return;
  path[pathptr++] = 8;
  path[pathptr++] = x;
  path[pathptr++] = y;

  return;
}

static void sv_Draw_bezi(int x1, int y1, int x2, int y2, int x3, int y3)
{
  if (pathptr+7 > PathMax) return;
  path[pathptr++] = 6;
  path[pathptr++] = x1;
  path[pathptr++] = y1;
  path[pathptr++] = x2;
  path[pathptr++] = y2;
  path[pathptr++] = x3;
  path[pathptr++] = y3;

  return;
}

static void sv_Draw_endp(void)
{
  path[pathptr]   = 0;
  path[pathptr+1] = 0;

  return;
}


/*------------------------------------------------------*/
/*    Function for setting graphics fore/back colour.   */
/*------------------------------------------------------*/
os_error *sv_setcolour(int colour)
{

/* If not currently printing, pass colour unaltered to  */
/* Wimp_SetColour.                                      */
  if (!printing) return wimp_setcolour(colour);

/* If printing, use printer palette instead of screen   */
/* palette.                                             */
  {
  /* Using a variable of type ctrans_type, split the    */
  /* given argument into a Wimp colour number (0 - 15), */
  /* a GCOL action, and a Foreground / Background flag. */
    ctrans_type ctrans;
    wimp_paletteword entry;
    int w_colour;
    int fore_back;
    int gcol_act;
    int gcol_out;

    ctrans.argument = colour;
    w_colour  = (int)ctrans.fields.w_colour;
    fore_back = (int)ctrans.fields.foreback << 7;
    gcol_act  = (int)ctrans.fields.gcol_act;

  /* Get the printer palette entry corresponding to     */
  /* w_colour.                                          */
    entry     = printer_palette[w_colour];

  /* Set the GCOL using the appropriate ColourTrans call*/
    return colourtran_setGCOL(entry, fore_back, gcol_act, &gcol_out);

  }
}

/*------------------------------------------------------*/
/* Function to plot a Mode 12 or Mode 20 sprite, scaled */
/*            to suit the current screen mode.          */
/*------------------------------------------------------*/
os_error *sv_plotsprite(sprite_id *id,
                       BOOL mode20,
                       int x, int y,
                       int col, int row)
{
  int gcol_act = 8; /*Plot with overwriting, using mask.*/

/* Calculate plotting position in OS units which will   */
/* place the Key Pixel at the desired coords (x,y).     */
  x -= col * 2;
  y -= row * (mode20? 2 : 4);

/* The pixel translation table to use when plotting     */
/* depends on whether the output is going to the screen */
/* or the printer.                                      */
/* Also, use scaling factors appropriate to sprite mode.*/

  return sprite_put_scaled(resspr_area(),
                           id,
                           gcol_act,
                           x, y,
                           (mode20?   &s_factors20: &s_factors12),
                           (printing? pr_pixtrans : sc_pixtrans));
}

/*------------------------------------------------------*/
/*  Function to re-calculate Sprite scaling and trans-  */
/*  lation tables, following a change of screen mode.   */
/*------------------------------------------------------*/
static os_error *sprite_tables(void)
{
/* Set scaling factors for a mode 12 sprite.            */
  s_factors12.xmag = 2;
  s_factors12.ymag = 4;
  s_factors12.xdiv = wimpt_dx();
  s_factors12.ydiv = wimpt_dy();

/* Set scaling factors for a mode 20 sprite.            */
  s_factors20.xmag = 2;
  s_factors20.ymag = 2;
  s_factors20.xdiv = wimpt_dx();
  s_factors20.ydiv = wimpt_dy();

/* Set pixel translation table for plotting to screen.  */
/* Use same table for mode 12 and mode 20 sprites.      */
  return colourtran_select_table(20,
                                 screen_palette,
                                 -1,
                                 (wimp_paletteword *)(-1),
                                 sc_pixtrans);
}

/********************************************************/
/*                    Main Menu Handling                */
/********************************************************/

/*------------------------------------------------------*/
/*                    pre menu proc                     */
/*------------------------------------------------------*/

static menu main_premenuproc(void *handle)
{
  BOOL fade_select_item;   /* TRUE if Select item needs */
                           /* to be faded.              */

  /* Fade Horiz and Vert items if observer details have */
  /* not been checked yet.                              */
  /* Tick them if the corresponding window is open now. */
  menu_setflags(main_menu, item_horiz, \
    horiz_data.open, !observer_verified);
  menu_setflags(main_menu, item_vert, \
    vert_data.open, !observer_verified);

  /* Fade Print item if observer details have not      */
  /* been verified yet.                                */
  menu_setflags(main_menu, item_print, 0, !observer_verified);

  /* Fade Select item if observer details have not      */
  /* been verified yet, or if there are no items in the */
  /* Select sub-menu.                                   */
  fade_select_item = !observer_verified || (select_count == 0);
  menu_setflags(main_menu, item_select,  0, fade_select_item);

  /* Fade View item if ob. details not verified yet.    */
  menu_setflags(main_menu, item_view,    0, !observer_verified);

  /* Fade Details and Cancel if there is no selection.  */
  menu_setflags(main_menu, item_details, 0, !selection_exists);
  menu_setflags(main_menu, item_cancel,  0, !selection_exists);

  /* Fade 'View sel.' sub-menu items according to      */
  /* whether or not there is a selection, and (if      */
  /* there is) when it can be seen.                    */
  menu_setflags(view_sub_menu, view_item_now,\
    0, !(selection_exists && selection.now));
  menu_setflags(view_sub_menu, view_item_rising,\
    0, !(selection_exists && selection.rising));
  menu_setflags(view_sub_menu, view_item_setting,\
    0, !(selection_exists && selection.setting));
  menu_setflags(view_sub_menu, view_item_culminating,\
    0, !(selection_exists && selection.culminating));

  return main_menu;
}

/*------------------------------------------------------*/
/*               handle hits on main menu               */
/*------------------------------------------------------*/

static void main_menuproc(void *handle, char *hit)
{
  switch (hit[0])
  {
    case item_info:
      sv_info_about_program();
      break;

    case item_observer:
      /* Make sure menu has gone. It wouldn't     */
      /* reflect changes made by the Observer dbox*/
      event_clear_current_menu();
      observer();
      break;

    case item_display:
      invoke_displayfns(hit);
      break;

    case item_horiz:
      /* Open window if it's closed, & vice versa */
      toggle_window(horiz_handle, &horiz_data.open);
      break;

    case item_vert:
      /* Open window if it's closed, & vice versa */
      toggle_window(vert_handle, &vert_data.open);
      break;

    case item_print:
      print_sub_menuproc(hit);
      break;

    case item_select:
      invoke_selectfns(hit);
      break;

    case item_view:
      view_sub_menuproc(hit);
      break;

    case item_details:
      sel_info();
      break;

    case item_cancel:
      cancel_selection();
      break;

    case item_quit:
      exit(0);
      break;

    default:
      break;
  }

  return;
}


/**** Functions dealing with printing of window data ****/

/*------------------------------------------------------*/
/*      Function to handle hits on Print sub-menu       */
/*------------------------------------------------------*/
static void print_sub_menuproc(char *hit)
{
  switch (hit[1])
  {
    case print_item_horizq:
    /* Print a quadrant of the Horiz window.            */
    if (!pdriver_installed()) return;
    printing = TRUE;
    print_hquadrant();
    printing = FALSE;
    break;

    case print_item_vertc:
    /* Print the Vert window.                           */
    if (!pdriver_installed()) return;
    printing = TRUE;
    print_vertical();
    printing = FALSE;
    break;

    default:
    /* Unknown option.  Do nothing.                     */
    break;

  }

  return;
}

/* ********* Functions dealing with printing ********** */

/*------------------------------------------------------*/
/*    Function to print a quadrant of the Horizontal    */
/*  window (centered on the current direction of view). */
/*------------------------------------------------------*/
static void print_hquadrant(void)
{
  int left_dir;   /* Direction of view corresponding to */
                  /* left side of window area.          */
  int right_dir;  /* Direction of view corresponding to */
                  /* right side of window area.         */
  int wider = 64; /* Amount to widen window area to make*/
                  /* sure it includes a compass label at*/
                  /* each side as well as in the middle.*/

  int title_ht = 64; /* Height of box containing title. */

/* Specify the area of the window to be plotted:        */

/* Find the directions of view corresponding to         */
/* the left and right sides of the print region.        */
  left_dir  = (ob_data.dir + NDIR - 1) % NDIR;
  right_dir = (ob_data.dir        + 1) % NDIR;
/* Then specify the window area required.               */
  horiz_data.wind_area.x0 = x_h(direction[left_dir])  - wider;
  horiz_data.wind_area.y0 = horiz_size.y0;
  horiz_data.wind_area.x1 = x_h(direction[right_dir]) + wider;
  horiz_data.wind_area.y1 = horiz_size.y1;

/* Print the specified part of the window, with title   */
/* and frame.                                           */
  print_window(&horiz_data, title_ht);

  return;
}

/*------------------------------------------------------*/
/* Function to build a title string for the Horiz window*/
/*------------------------------------------------------*/
static void build_horiz_title(char *title, BOOL font_ok)
{
/* Write current observer details into a string.  Put   */
/* everything onto a single line of text.               */
  ob_info_string(title, ONE_LINE, font_ok);
  return;
}

/*------------------------------------------------------*/
/* Function to write out the title for the Horiz window.*/
/* Return any os_error which occurs.                    */
/*------------------------------------------------------*/
static os_error *write_horiz_title(char *title, BOOL font_ok)
{
  int x, y;

  x = horiz_data.wind_area.x0 + 8;
  y = horiz_data.wind_area.y1 + 48;
  return write_title(x, y, title, font_ok);
}

/*------------------------------------------------------*/
/*    Function to print the entire Vertical window.     */
/*------------------------------------------------------*/
static void print_vertical(void)
{
  int title_ht = 160; /* Height of box containing title.*/

/* Specify the window area required (= whole area).     */
  vert_data.wind_area = vert_size;

/* Print the specified part of the window, with title   */
/* and frame.                                           */
  print_window(&vert_data, title_ht);

  return;
}

/*------------------------------------------------------*/
/* Function to build a title string for the Vert window.*/
/*------------------------------------------------------*/
static void build_vert_title(char *title, BOOL font_ok)
{
/* Write current observer details into a string.  Split */
/* the info into three separate lines.                  */
  ob_info_string(title, THREE_LINES, font_ok);
  return;
}

/*------------------------------------------------------*/
/* Function to write out the title for the Vert window. */
/* Return any os_error which occurs.                    */
/*------------------------------------------------------*/
static os_error *write_vert_title(char *title, BOOL font_ok)
{
  os_error *errptr;
  int x, y;

  x = vert_data.wind_area.x0 +  62;
  y = vert_data.wind_area.y1 + 144;
  errptr = write_title(x, y, title, font_ok);
  if (errptr != NULL) return errptr;
  title += strlen(title)+1;

  x = vert_data.wind_area.x0 +  62;
  y = vert_data.wind_area.y1 +  96;
  errptr = write_title(x, y, title, font_ok);
  if (errptr != NULL) return errptr;
  title += strlen(title)+1;

  x = vert_data.wind_area.x0 +  62;
  y = vert_data.wind_area.y1 +  48;
  return write_title(x, y, title, font_ok);
}

/*------------------------------------------------------*/
/*  Function to write out a title string, starting at   */
/*    the specified coords (in OS units).  Use anti-    */
/*    aliassed font if BOOL flag is TRUE, system font   */
/*                      otherwise.                      */
/*------------------------------------------------------*/
static os_error *write_title(int x, int y, char *title, BOOL font_ok)
{
  int counter;
  os_error *errptr;

  if (font_ok) return font_paint(title, font_OSCOORDS, x, y-VSHIFT);

  errptr = bbc_move(x,y);
  if (errptr != NULL) return errptr;
  return write_string(title, &counter);
}

/*------------------------------------------------------*/
/*  Function to print (part of) a window using RISC OS  */
/*                  printer drivers.                    */
/*------------------------------------------------------*/
static void print_window(sv_windata *wd, int title_ht)
{
  os_regset OSFind_regs;     /* For OS_Find call which  */
  char file[] = "printer:";  /* opens "printer:" file.  */
  int jhandle;               /* Job handle.             */
  int old;                   /* Old job handle.         */
  int jdummy;                /* For unwanted job handle.*/

  int frame_lt;      /* Left of frame around window.    */
  int frame_bt;      /* Bottom of frame around window.  */
  int frame_wd;      /* Width of frame around window.   */
  int frame_ht;      /* Height of frame around window   */
                     /* (includes title box).           */
  int margin = 4;    /* All-round safety margin, to make*/
                     /* sure complete image gets printed*/

  int bgcol = PAL_WHITE;  /* Background colour (white). */

  print_box to_print;/* Region to be printed.  Includes */
                     /* window area, frame, title box & */
                     /* all-round safety margin.        */

  wimp_box to_draw;           /* Region that PDriver    */
                              /* wants drawn.           */
  int more;                   /* Pdriver's flag.        */
  int id;                     /* ID of rectangle.       */
  wimp_box clip;              /* Pdriver's clip rectang.*/

  char title[TITLE_LEN];      /* Title string for windw.*/
  font font_handle;           /* Font handle.           */
  BOOL font_open = FALSE;     /* Is a font file open?   */
  wimp_paletteword foregr;    /* Foreground pal. entry. */
  wimp_paletteword backgr;    /* Background pal. entry. */
  int off = 0;                /* Max intermediate colors*/

/* Error pointer returned by RISC_OSLib routines:       */
  os_error *errptr;
/* Temp storage for errors during exit handling:        */
  os_error *ptrsav_e = NULL;   /* Pointer.              */
  os_error err_blk_e;          /* Error block.          */
/* Flag indicating if print job has started:            */
  BOOL job_started = FALSE;
/* Temp storage for errors which occur while printing:  */
  os_error *ptrsav_p = NULL;   /* Pointer.              */
  os_error err_blk_p = {       /* Error block (contain- */
   255,                        /* ing a default message)*/
   "Error (print cancelled)"};
/* Error block for unreported Escapes.                  */
  os_error unrep_esc = {
   255,
   "Escape. (Print cancelled.)"};


/* Open "printer:" file and record the returned handle. */
  OSFind_regs.r[0] = 0x8F;
  OSFind_regs.r[1] = (int)file;
  wimpt_noerr(os_find(&OSFind_regs));
  jhandle = OSFind_regs.r[0];

/* Make sure returned handle is valid.                  */
  if (jhandle == 0)
    werr(FATAL, "Invalid 'printer:' handle");

/* Turn on hourglass.                                   */
  visdelay_begin();

/* Start the print job.                                 */
  errptr = print_selectjob(jhandle, "SkyViewPr", &old);
  if (errptr != NULL) goto error_exit;
  job_started = TRUE;

/* Set pixel translation table for plotting sprites to  */
/* printer.  Use same table for mode 12 sprites as for  */
/* mode 20 sprites.                                     */
  errptr = colourtran_select_table(20,
                                   printer_palette,
                                   -1,
                                   (wimp_paletteword *)(-1),
                                   pr_pixtrans);
  if (errptr != NULL) goto error_exit;

/* Open font for title string.  If errors, use system   */
/* font instead.                                        */
  errptr = font_find(TITLE_FONT, FONT_SIZE, FONT_SIZE, 0, 0, &font_handle);
  font_open = (errptr == NULL);

/* Set anti-alias palette.                              */
  if (font_open)
  {
    foregr.word = PAL_BLACK;
    backgr.word = PAL_WHITE;
    errptr = colourtran_setfontcolours(&font_handle, &backgr, &foregr, &off);
    if (errptr != NULL) goto error_exit;
  }

/* Call function to build title string.                 */
  wd->title_buildfn(title, font_open);

/* Specify the rectangle to be plotted:                 */

/* Using the given wind_area, work out the coords of    */
/* the frame surrounding the window and the title.      */
  frame_lt = wd->wind_area.x0 - 1;
  frame_bt = wd->wind_area.y0 - 1;
  frame_wd = wd->wind_area.x1 - wd->wind_area.x0 + 1;
  frame_ht = wd->wind_area.y1 - wd->wind_area.y0 + 1 + title_ht;
/* Can now fill in the printer_box.                     */
  to_print.x0 = frame_lt - margin;
  to_print.y0 = frame_bt - margin;
  to_print.x1 = frame_lt + frame_wd + margin;
  to_print.y1 = frame_bt + frame_ht + margin;
/* Finally call PDriver_GiveRectangle.                  */
/* Abort printing if there is an error.                 */
  errptr = print_giverectangle\
                 (0, &to_print, &wd->matrix, &wd->posn, bgcol);
  if (errptr != NULL) goto error_exit;

/* Get series of rectangles to be drawn.                */
  errptr = print_drawpage(1, 0, NULL, (print_box *)&to_draw, &more, &id);
  if (errptr != NULL) goto error_exit;

  while (more != 0)
  {

  /* First do the following, without bothering to check */
  /* whether they are inside the requested rectangle:   */
  /* Set foreground colour.                             */
    errptr = sv_setcolour(0);
    if (errptr != NULL) goto error_exit;
  /* Draw the frame around the window.                  */
    errptr = bbc_rectangle(frame_lt, frame_bt, frame_wd, frame_ht);
    if (errptr != NULL) goto error_exit;
  /* Draw a line to mark the top of the window.         */
    errptr = bbc_move(frame_lt,          wd->wind_area.y1);
    if (errptr != NULL) goto error_exit;
    errptr = bbc_draw(frame_lt+frame_wd, wd->wind_area.y1);
    if (errptr != NULL) goto error_exit;
  /* Clear 'escape seen' flag.                          */
    _kernel_escape_seen();
  /* Call function to write title.                      */
    errptr = wd->title_writefn(title, font_open);
    if (errptr != NULL) goto error_exit;
  /* Check for unreported escape.                       */
    if (_kernel_escape_seen()) { errptr = &unrep_esc;
                               goto error_exit;}

  /* Then draw the window (if the requested rectangle   */
  /* overlaps it at all), setting the pdriver's clip    */
  /* rectangle so that nothing can spill over the frame.*/
    if (overlap(&to_draw, &wd->wind_area, &clip))
    {
      errptr = bbc_vdu(bbc_DefGraphWindow);
      if (errptr != NULL) goto error_exit;
      errptr = bbc_vduw(clip.x0);
      if (errptr != NULL) goto error_exit;
      errptr = bbc_vduw(clip.y0);
      if (errptr != NULL) goto error_exit;
     /* VDU clipping rectangle uses inclusive coords:   */
      errptr = bbc_vduw(clip.x1-1);
      if (errptr != NULL) goto error_exit;
      errptr = bbc_vduw(clip.y1-1);
      if (errptr != NULL) goto error_exit;
      errptr = draw_window(wd, clip);
      if (errptr != NULL) goto error_exit;
    }

  /* Reset clipping rectangle.                          */
    errptr = bbc_vdu(bbc_DefaultWindow);
    if (errptr != NULL) goto error_exit;

  /* Get next rectangle.                                */
    errptr = print_getrectangle((print_box *)&to_draw, &more, &id);
    if (errptr != NULL) goto error_exit;
  }

/* End the print job.                                   */
  errptr = print_endjob(jhandle);
  if (errptr != NULL) goto error_exit;

/* Normal exit:                                         */

/* Turn off hourglass.                                  */
  visdelay_end();
/* Re-select job which was active when this one started.*/
  errptr = print_selectjob(old, NULL, &jdummy);
  if (errptr != NULL) ptrsav_e = save_error(errptr, &err_blk_e);
/* Close "printer:" file.                               */
  OSFind_regs.r[0] = 0;
  OSFind_regs.r[1] = jhandle;
  errptr = os_find(&OSFind_regs);
  if (errptr != NULL) ptrsav_e = save_error(errptr, &err_blk_e);
/* Close font file.                                     */
  if (font_open)
  {
    errptr = font_lose(font_handle);
    if (errptr != NULL) ptrsav_e = save_error(errptr, &err_blk_e);
  }
/* If errors, report most recent, treating it as fatal. */
  wimpt_noerr(ptrsav_e);

  return;


  error_exit:

/* Store the error for reporting later.  If pointer is  */
/* obviously invalid, use the default message.          */
  if ((int)errptr <= 0x1C)
    ptrsav_p = &err_blk_p;
  else
    ptrsav_p = save_error(errptr, &err_blk_p);
/* Turn off hourglass.                                  */
  visdelay_end();
/* Cope with case where print job has started.          */
  if (job_started)
  {
  /* Abort the print job.                               */
    errptr = print_abortjob(jhandle);
    if (errptr != NULL) ptrsav_e = save_error(errptr, &err_blk_e);
  /*Re-select job which was active when this one started*/
    errptr = print_selectjob(old, NULL, &jdummy);
    if (errptr != NULL) ptrsav_e = save_error(errptr, &err_blk_e);
  }
/* Close "printer:" file.                               */
  OSFind_regs.r[0] = 0;
  OSFind_regs.r[1] = jhandle;
  errptr = os_find(&OSFind_regs);
  if (errptr != NULL) ptrsav_e = save_error(errptr, &err_blk_e);
/* Close font file.                                     */
  if (font_open)
  {
    errptr = font_lose(font_handle);
    if (errptr != NULL) ptrsav_e = save_error(errptr, &err_blk_e);
  }
/* If errors occurred during error handling, report the */
/* most recent, treating it as fatal.                   */
  wimpt_noerr(ptrsav_e);
/* Report the original error.                           */
  wimpt_reporterror(ptrsav_p, wimp_EOK);

  return;
}

/*-------------------------------------------------------*/
/*  Function to save the error block pointed to by ptr1  */
/*   into the block pointed to by ptr2.  Returns ptr2.   */
/*-------------------------------------------------------*/
static os_error *save_error(os_error *ptr1, os_error *ptr2)
{
  *ptr2 = *ptr1;
  return ptr2;
}

/*-------------------------------------------------------*/
/* Function to build a string containing current observer*/
/* details.  Returns one string containing everything if */
/* 'split' is FALSE, otherwise splits the info over 3    */
/* strings which follow one after the other in memory.   */
/* Adds extra spaces if split is false and a fancy font  */
/* is being used.                                        */
/*-------------------------------------------------------*/
static void ob_info_string(char *chrptr, BOOL split, BOOL font_ok)
{
  chrptr += sprintf(chrptr, "SkyView");
  if (split)
    chrptr += sprintf(chrptr, "%c", '\0');
  else if (font_ok)
    chrptr += sprintf(chrptr, "          ");
  else
    chrptr += sprintf(chrptr, "     ");
  chrptr += sprintf(chrptr, "Lat. %.1f ",  (double)ob_data.lat10/10.0);
  chrptr += sprintf(chrptr, "%s,  ", (ob_data.isn? "N" : "S"));
  chrptr += sprintf(chrptr, "Long. %.1f ", (double)ob_data.long10/10.0);
  chrptr += sprintf(chrptr, "%s",    (ob_data.isw? "W" : "E"));
  if (split)
    chrptr += sprintf(chrptr, "%c", '\0');
  else if (font_ok)
    chrptr += sprintf(chrptr, "          ");
  else
    chrptr += sprintf(chrptr, "     ");
  chrptr += sprintf(chrptr, "%02i/%02i/%i", ob_data.day, ob_data.month,
                                                         ob_data.year);
  chrptr += sprintf(chrptr, "  %02i:%02i", ob_data.hour, ob_data.min);
  if (ob_data.offset >= 0.0f)
    chrptr += sprintf(chrptr, "  (GMT + ");
  else
    chrptr += sprintf(chrptr, "  (GMT - ");
  chrptr += sprintf(chrptr, "%.1f h)", fabs((double)ob_data.offset));
  return;
}

/*-------------------------------------------------------*/
/* Function to write out a string as a sequence of VDU   */
/* commands.  Fills in *countptr with the number of      */
/* characters written, and returns any os_error which    */
/* occurred.                                             */
/*-------------------------------------------------------*/
static os_error *write_string(char *chrptr, int *countptr)
{
  os_error *errptr;

  errptr = sv_setcolour(0);
  if (errptr != NULL) return errptr;

  for (*countptr = 0; *chrptr; chrptr++, (*countptr)++)
    {errptr = bbc_vdu(*chrptr);
     if (errptr != NULL) return errptr;}

  return NULL;
}

/*------------------------------------------------------*/
/*  Function which sets the wimp_box *olap to the area  */
/* common to the wimp_boxes *b1 and *b2.  Returns FALSE */
/*   and leaves *olap undefined if *b1 and *b2 do not   */
/*           overlap.  Returns TRUE if all OK.          */
/*------------------------------------------------------*/
static BOOL overlap(wimp_box *b1, wimp_box *b2, wimp_box *olap)
{
  os_error olap_error = {            /* Define a new    */
    255,                             /* type of error.  */
    "Invalid printer rectangle"};

/* Check validity of *b1 and *b2.                       */
  if (b1->x0 > b1->x1 ||
      b1->y0 > b1->y1 ||
      b2->x0 > b2->x1 ||
      b2->y0 > b2->y1)
  {
    wimpt_reporterror(&olap_error, wimp_EOK);
    return FALSE;
  }

/* Calculate overlap.                                   */
  olap->x0 = (b1->x0 > b2->x0 ? b1->x0 : b2->x0);
  olap->x1 = (b1->x1 < b2->x1 ? b1->x1 : b2->x1);
  if (olap->x0 >= olap->x1) return FALSE;

  olap->y0 = (b1->y0 > b2->y0 ? b1->y0 : b2->y0);
  olap->y1 = (b1->y1 < b2->y1 ? b1->y1 : b2->y1);
  if (olap->y0 >= olap->y1) return FALSE;

  return TRUE;
}

/*------------------------------------------------------*/
/*  Function to check if a printer driver is installed  */
/*------------------------------------------------------*/
static BOOL pdriver_installed(void)
{
/* Returns TRUE if a printer driver is installed.       */
/* Displays an error message and returns FALSE if no    */
/* printer driver is installed (or other error occurs). */

  os_error pdriver_error = {         /* Define a new    */
    255,                             /* type of error.  */
    "No printer driver installed"};
  os_error *errptr;
  print_infostr printerinfo;

/* Attempt to get printer information from driver.      */
  errptr = print_info(&printerinfo);

/* All OK if no error.                                  */
  if (errptr == NULL) return TRUE;

/* If error is "No such SWI", PDriver is not installed. */
  if (errptr->errnum == 486)
    wimpt_reporterror(&pdriver_error, wimp_EOK);
  else
    wimpt_reporterror(errptr, wimp_EOK);

  return FALSE;
}


/**** Functions dealing with Selection of an Object *****/

/*------------------------------------------------------*/
/*              Function to cancel selection.           */
/*------------------------------------------------------*/
static void cancel_selection(void)
{
  /* Remove frame around old selection (if one is       */
  /* currently in window).                              */
  if (selection_exists)
  {
    if (horiz_data.selnum != NOWHERE)
      invalidate_framearea(horiz_handle, horiz_data);
    if (vert_data.selnum != NOWHERE)
      invalidate_framearea(vert_handle, vert_data);
  }
  /* Reset selection flag.                              */
  selection_exists = FALSE;
  /* Set owner number to invalid value.                 */
  sel_owner = NO_ONE;

  return;
}

/*------------------------------------------------------*/
/*     Function to display info on selected object.     */
/*------------------------------------------------------*/
static void sel_info(void)
{
  dbox d;                        /* Dialogue box handle.*/
  int i;                         /* Line counter.       */
  char timestring[8];            /* For times of phenom.*/
  char *linestart[INFOBOX_LINES];/* Array of pointers   */
                                 /* to start of each    */
                                 /* line of info text.  */

  /* Create dbox.                                       */
  d = dbox_new(SEL_INFO_NAME);
  if (d == NULL) return;

/* Build strings containing times of phenomena, and     */
/* display them in the dbox.                            */
  build_timestring(timestring,
             selection.rising, selection.rise_hour, selection.rise_min);
  dbox_setfield(d, selinfo_rise, timestring);
  build_timestring(timestring,
             selection.setting, selection.set_hour, selection.set_min);
  dbox_setfield(d, selinfo_set, timestring);
  build_timestring(timestring,
             selection.culminating, selection.cul_hour, selection.cul_min);
  dbox_setfield(d, selinfo_culmin, timestring);

/* Set up pointer to text buffer, and obtain info.      */
  infoptr = infobuf;
  module_fns[sel_owner].selectfn(sel_info_request);

/* Cut up returned text string into separate lines,     */
/* obtaining pointers to start of each line.            */
  cutup(linestart);

/* Fill in the text lines in the dbox.                  */
  for (i=0; i < INFOBOX_LINES; i++)
    dbox_setfield(d, selinfo_textline+i, linestart[i]);

/* Display dbox until user cancels or okays it.         */
  dbox_show(d);
  dbox_fillin(d);

  /* Delete dbox.                                       */
  dbox_dispose(&d);

  return;
}

/*------------------------------------------------------*/
/* Function to build a string containing the time of a  */
/*                      phenomenon.                     */
/*String pointed to by stringptr is assumed long enough.*/
/*------------------------------------------------------*/
static void build_timestring(char *stringptr, BOOL valid, int hour, int min)
{
/* If hour and min are not valid, return a suitable     */
/* string.                                              */
  if (!valid)
  {
    sprintf(stringptr, "--:--");
    return;
  }

/* Build time string in style of a 24-hour clock.       */
  sprintf(stringptr, "%02.2i:%02.2i", hour, min);

  return;
}


/*------------------------------------------------------*/
/*          Handle hits on 'View sel.' Sub-Menu         */
/*------------------------------------------------------*/

static void view_sub_menuproc(char *hit)
{
  REAL view_alt;  /* Altitude of object at time of      */
                  /* phenomenon.                        */
  REAL view_azim; /* Azimuth of object at time of       */
                  /* phenomenon.                        */
  int  best_dir;  /* Which of the standard directions   */
                  /* is best for viewing this azimuth.  */
  int selnum;     /* Window ID number of object.        */

  switch (hit[1])
  {
    case view_item_now:
      /* If the object is not in fact visible as billed,*/
      /* quit at once.  This should not happen.         */
      if (horiz_data.selnum == NOWHERE &&
           vert_data.selnum == NOWHERE) return;
      /* Find the altitude and azimuth of the object now*/
      if (horiz_data.selnum != NOWHERE)
      {
        selnum    = horiz_data.selnum;
        view_alt  = horiz_list[selnum].alt;
        view_azim = horiz_list[selnum].azim;
      }
      else
      {
        selnum    = vert_data.selnum;
        view_alt  = vert_list[selnum].alt;
        view_azim = vert_list[selnum].azim;
      }
      /* Set the direction of view to the best one for  */
      /* viewing the object (if it is currently         */
      /* some other direction).                         */
      best_dir = nearest_dir(view_azim);
      if (ob_data.dir != best_dir)
      {
        ob_data.dir = best_dir;
        ob_data.ang = direction[ob_data.dir];
        /*Recalc screen coords to reflect new direction.*/
        recalc_horiz_xy();
        recalc_vert_xy();
        /* Invalidate windows.                          */
        invalidate_all();
      }
      /* Arrange windows appropriately to display object*/
      arrange_windows(view_alt, view_azim);

      return;

    case view_item_rising:
      /* Change observer data so that the rising of the */
      /* selected object can be viewed.                 */
      ob_data.hour = selection.rise_hour;
      ob_data.min  = selection.rise_min;
      view_alt     = (REAL)0.0;
      view_azim    = selection.rise_azim;
      ob_data.dir  = nearest_dir(view_azim);
      break;

    case view_item_setting:
      /* Change observer data so that the setting of    */
      /* the selected object can be viewed.             */
      ob_data.hour = selection.set_hour;
      ob_data.min  = selection.set_min;
      view_alt     = (REAL)0.0;
      view_azim    = selection.set_azim;
      ob_data.dir  = nearest_dir(view_azim);
      break;

    case view_item_culminating:
      /* Change observer data so that the culminating   */
      /* of the selected object can be viewed.          */
      ob_data.hour = selection.cul_hour;
      ob_data.min  = selection.cul_min;
      view_alt     = selection.cul_alt;
      view_azim    = selection.cul_azim;
      ob_data.dir  = nearest_dir(view_azim);
      break;

    default:
      return;
  }

  /* Fill in derived fields of ob_data (ang, jul, etc)  */
  fillin_obfields(&ob_data);

  /* Rebuild the plotting lists for the new time of day.*/
  build_lists();

  /* Update Selection data to correspond to new lists.  */
  module_fns[sel_owner].selectfn(timeonly_recalculate);

  /* Record new id numbers of selected object.          */
  horiz_data.selnum = selection.horiz_id;
  vert_data.selnum  = selection.vert_id;

  /* Ensure size of Selection frame is valid.           */
  if (horiz_data.selnum != NOWHERE)
    calculate_frame(&horiz_data);
  if (vert_data.selnum != NOWHERE)
    calculate_frame(&vert_data);

  /* Invalidate windows.                                */
  invalidate_all();

  /* Arrange windows appropriately to display object.   */
  arrange_windows(view_alt, view_azim);

  return;
}

/*------------------------------------------------------*/
/* Function to find which of the standard directions of */
/*      view is the nearest to the given azimuth.       */
/*------------------------------------------------------*/
static int nearest_dir(REAL azim)
{
  REAL min_error = (REAL)2.0 * PI;
  REAL error;
  int i;
  int nearest = 0;

  for (i=0; i<NDIR; i++)
  {
    error = direction[i] - azim;
    error = (REAL)fabs((double)error);
    if (error < min_error)
    {
      min_error = error;
      nearest = i;
    }
  }
  return nearest;
}

/*------------------------------------------------------*/
/*  Function to arrange the windows to bring the object */
/*  at the given altitude and azimuth onto the screen.  */
/*------------------------------------------------------*/
static void arrange_windows(REAL alt, REAL az)
{
/* If the given altitude is greater than the maximum    */
/* altitude which appears in the Horiz window, use the  */
/* Vert window.                                         */
  if (alt >= alt_max)
  {
    arrange_vert(alt, az);
    return;
  }

/* Otherwise use the Horiz window.                      */
  arrange_horiz(alt, az);

  return;
}

/*                                                      */
/* Function to arrange Horiz window appropriately to    */
/* display the object at the given alt and az.          */
/*                                                      */
static void arrange_horiz(REAL alt, REAL az)
{
/* Assume Horiz window is at full height (or was when   */
/* it was last open).                                   */
/* Ensure window is open and on top, and scroll it to   */
/* bring the given azimuth to the centre of the visible */
/* portion.  Ignore altitude.                           */

  wimp_wstate winstate;
  int x_coord;          /* x coord corresponding to az. */
  int viswidth;         /* Width of visible part of win.*/
  int x_scroll;         /* Scroll offset required.      */

/* Get current state of Horiz window.                   */
  wimpt_noerr(wimp_get_wind_state(horiz_handle, &winstate));

/* Calculate current width of visible portion.          */
  viswidth = winstate.o.box.x1 - winstate.o.box.x0;

/* Calculate x coord corresponding to given azimuth.    */
  x_coord = x_h(az);

/* Calculate scroll offset needed to put x_coord into   */
/* centre of visible portion.                           */
  x_scroll = x_coord - (viswidth/2);

/* Make sure x_scroll is valid.                         */
  if (x_scroll < horiz_size.x0)
    x_scroll = horiz_size.x0;
  if (x_scroll + viswidth > horiz_size.x1)
    x_scroll = horiz_size.x1 - viswidth;

/* Call Wimp_OpenWindow with new value of x_scroll.     */
/* Also, ensure Horiz window is on top of stack.        */
  winstate.o.x      =  x_scroll;
  winstate.o.behind = -1;
  wimpt_noerr(wimp_open_wind(&winstate.o));

  return;
}

/*                                                      */
/* Function to arrange Vert window appropriately to     */
/* display object at given alt and az.                  */
/*                                                      */
static void arrange_vert(REAL alt, REAL az)
{
/* Assume Vert window is at full size (or was when it   */
/* was last open).                                      */
/* Ensure window is open and on top of stack.           */

  wimp_wstate winstate;

/* Get current state of Vert window.                    */
  wimpt_noerr(wimp_get_wind_state(vert_handle, &winstate));

/* Call Wimp_OpenWindow, ensuring Vert window is on top */
/* of stack.                                            */
  winstate.o.behind = -1;
  wimpt_noerr(wimp_open_wind(&winstate.o));

  return;
}

/*------------------------------------------------------*/
/*  Function to invoke Select function appropriate to   */
/*               hit on Select sub-menu                 */
/*------------------------------------------------------*/
static void invoke_selectfns(char *hit)
{
  selectfntype *appropriate_fn;
  int module_no;    /* For module no. of owner of this  */
                    /* menu entry.                      */

/* For unknown option, do nothing                       */
  if (hit[1] < 1 ) return;
  if (hit[1] > select_count) return;

/* Find which module owns this menu entry.              */
  module_no = sel_index[hit[1]];

/* Give modules access to list of submenu hits.         */
  module_submenu_hits = hit+2;

/* Find appropriate Select function.                    */
  appropriate_fn = module_fns[module_no].selectfn;

/* Set looping flag to FALSE. Might be changed by module*/
  dbox_persisting = FALSE;

/* Enclose module call in loop. End when flag is FALSE. */
  do
  {

  /* Invoke appropriate Select function.                */
    appropriate_fn(new_selection);

  /* Establish new selection, if dialogue with user was */
  /* successful.                                        */
    if (selection.selected_OK)
      establish_new_selection(module_no);

/* End of loop for persisting dbox.                     */
  }
  while (dbox_persisting);

/* List of submenu hits no longer valid.                */
  module_submenu_hits = NULL;

  return;
}

/*------------------------------------------------------*/
/*     Function to establish a new selected object.     */
/*------------------------------------------------------*/
static void establish_new_selection(int new_owner)
{

/* Remove frame around old selection (if one is         */
/* currently in window).                                */
  if (selection_exists)
  {
    if (horiz_data.selnum != NOWHERE)
      invalidate_framearea(horiz_handle, horiz_data);
    if (vert_data.selnum != NOWHERE)
      invalidate_framearea(vert_handle, vert_data);
  }

/* Flag the existence of new selection.                 */
  selection_exists = TRUE;

/* Record the new owner of the selection.               */
  sel_owner = new_owner;

/* Record window number of new selection. Number =      */
/* NOWHERE if selection is not in window.               */
  horiz_data.selnum = selection.horiz_id;
  vert_data.selnum  = selection.vert_id;

/* Establish new frame (if selected object is in window)*/
  if (horiz_data.selnum != NOWHERE)
    establish_new_frame(horiz_handle, &horiz_data);
  if (vert_data.selnum != NOWHERE)
    establish_new_frame(vert_handle, &vert_data);

  return;
}

/*------------------------------------------------------*/
/*    Function to invalidate the part of the window     */
/*   containing the frame around the selected object.   */
/*------------------------------------------------------*/
static void invalidate_framearea(wimp_w whandle, sv_windata wdat)
{
  wimp_redrawstr r;

/* Point to selected object:                            */
  plotobj *selptr = (wdat.listptr + wdat.selnum);

  /* Get x and y coords of selected object.             */
  int x = selptr->wind_x;
  int y = selptr->wind_y;

  /* Shift frame's bounding box to coords of object.    */
  /* Put result into redrawstr.                         */
  coords_offsetbox(&wdat.sel_bounds, x, y, &r.box);

  /* Fill in window handle.                             */
  r.w = whandle;

  /* Invalidate the part of the window containing the   */
  /* frame.                                             */
  wimpt_noerr(wimp_force_redraw(&r));

  return;
}


/*------------------------------------------------------*/
/*  Function to establish frame around new selection.   */
/*------------------------------------------------------*/
static void establish_new_frame(wimp_w whandle, sv_windata *wd)
{

  /* Calculate coords of frame and bounding box for     */
  /* frame, and write them into window's data block.    */
  calculate_frame(wd);

  /* Invalidate the part of the window which is to      */
  /* contain the new frame.                             */
  invalidate_framearea(whandle, *wd);

  return;
}

/*------------------------------------------------------*/
/*     Function to calculate coords of frame around     */
/*    selection, and coords of bounding box of frame,   */
/*     and write them into the window's data block.     */
/*------------------------------------------------------*/
static void calculate_frame(sv_windata *wd)
{

  wimp_box size;

  /* Read size of new selected object.                  */
  size = (wd->listptr + wd->selnum)->size;

  /* Construct a frame larger than size and centered    */
  /* on 0,0.                                            */
  wd->sel_frame.w = 2*(size.x1 - size.x0);
  wd->sel_frame.h = 2*(size.y1 - size.y0);
  wd->sel_frame.x = -(wd->sel_frame.w)/2;
  wd->sel_frame.y = -(wd->sel_frame.h)/2;

  /* Set up bounding box which encloses frame.          */
  wd->sel_bounds.x0 = wd->sel_frame.x - 4;
  wd->sel_bounds.y0 = wd->sel_frame.y - 4;
  wd->sel_bounds.x1 = wd->sel_frame.x +\
                      wd->sel_frame.w + 6;
  wd->sel_bounds.y1 = wd->sel_frame.y +\
                      wd->sel_frame.h + 6;

  return;
}

/*********** 'Display' Options for Modules **************/
/*------------------------------------------------------*/
/*    Function to invoke display function appropriate   */
/*             to hit on Display sub-menu.              */
/*------------------------------------------------------*/
static void invoke_displayfns(char *hit)
{
  BOOL w_invalid;/* TRUE if windows become invalid.     */
  int module_no; /* For module no. of owner of this     */
                 /* menu entry.                         */
  displayfntype *appropriate_fn;

/* For unknown option, do nothing                       */
  if (hit[1] < 1 ) return;
  if (hit[1] > display_count) return;

/* Give modules access to list of submenu hits.         */
  module_submenu_hits = hit+2;

/* Find owner of this menu entry.                       */
  module_no = disp_index[hit[1]];

/* Find appropriate Display function.                   */
  appropriate_fn = module_fns[module_no].dispfn;

/* Set looping flag to FALSE. Might be changed by module*/
  dbox_persisting = FALSE;

/* Enclose module call in loop. End when flag is FALSE. */
  do
  {

  /* Set rebuilding flag to FALSE. Module can change it.*/
    rebuild_request = FALSE;
  /* Invoke appropriate display function.               */
    w_invalid = appropriate_fn(&enabled[module_no]);

  /* Set tick on menu entry accordingly.                */
    menu_setflags(display_sub_menu, hit[1], enabled[module_no], 0);

  /* Make sure a Selection cannot be made on a disabled */
  /* module. If module has a Select entry, set the fade */
  /* status of that entry according to the value of the */
  /* tick flag just returned.                           */
    if (module_index[module_no] > 0)
      menu_setflags(sel_sub_menu, module_index[module_no],\
                    0, !enabled[module_no]);

  /* If module is now disabled, and it was the owner of */
  /* the selection, cancel the selection.               */
    if (module_no==sel_owner && !enabled[module_no])
      cancel_selection();

  /* Declare windows invalid, if necessary.             */
    if (w_invalid || rebuild_request) invalidate_all();

  /* Rebuild plotting lists, if necessary.              */
    if (rebuild_request) display_rebuild();


/* End of loop for persisting dbox.                     */
  }
  while (dbox_persisting);

/* List of submenu hits no longer valid.                */
  module_submenu_hits = NULL;

  return;
}

/*------------------------------------------------------*/
/*  Function to rebuild plotting lists, at the request  */
/*               of a Display function.                 */
/*------------------------------------------------------*/
static void display_rebuild(void)
{

  build_lists();

/* Nothing more to do if there is no selected object.   */
  if (!selection_exists) return;

/* Call selection function for owner of object.  Reason */
/* 'timeonly_recalculate' will do.                      */
  module_fns[sel_owner].selectfn(timeonly_recalculate);

/* Record new id numbers of selected object.            */
  horiz_data.selnum = selection.horiz_id;
  vert_data.selnum  = selection.vert_id;

/* If object is on view, make sure coords of frame and  */
/* bounding box are valid.                              */
  if (horiz_data.selnum != NOWHERE)
    calculate_frame(&horiz_data);
  if (vert_data.selnum  != NOWHERE)
    calculate_frame(&vert_data);

  return;
}


/********************************************************/
/*                   Info Box Handler                   */
/********************************************************/

static void sv_info_about_program(void)
{
  dbox d;      /* Dialogue box handle */
  char versionstring[256];

  if (d = dbox_new("progInfo"), d != NULL)
  {
    sprintf(versionstring, "%s (%s) ",VERSION,__DATE__);
    dbox_setfield(d, sv_info_field, versionstring);

    dbox_show(d);      /* display it  */
    dbox_fillin(d);    /* maintain it */
    dbox_dispose(&d);  /* remove it   */
  }
}


/********************************************************/
/*           Functions for Observer Details             */
/********************************************************/

/*------------------------------------------------------*/
/*       Function to obtain new Observer Details        */
/*         and update windows accordingly.              */
/*------------------------------------------------------*/
static void observer(void)
{
  int response;        /* Which action button has just  */
                       /* been been pressed.            */
  int target;          /* Value that the Day field      */
                       /* "wants" to have.              */
  BOOL keep_open;      /* TRUE if the dbox is to remain */
                       /* open after the present button */
                       /* has been processed.           */
  int year;            /* For changing century.         */
  char buf[10];        /* For changing N/S and E/W.     */

/* Load dbox with the settings currently in force.      */
  data_to_box(ob_data);

/* Put Observer Box on screen.                          */
  dbox_show(d_ob);

/* Set value of target.                                 */
  target = dbox_getnumeric(d_ob, dayf);


/* Loop until Select on 'OK', or 'close' request.       */

  do
  {
/* Get number of action button pressed by the user.     */
    response = dbox_fillin(d_ob);

/* Take appropriate action.                             */
    switch (response) {

      case ob_close_rq:
      /* Do nothing.  Data on display may be invalid,   */
      /* but box is being closed anyway.                */
        keep_open = FALSE;
        break;

      case ob_cancel_butt:
      /* Put the current data back on display.          */
        data_to_box(ob_data);
      /* Reset the value of target.                     */
        target = dbox_getnumeric(d_ob, dayf);
      /* Keep dbox open if user has so requested.       */
        keep_open = dbox_persist();
        break;

      case ob_ok_butt:
      /* Call handler function for clicks on OK button. */
        ob_ok_click();
      /* Reset the value of target.                     */
        target = dbox_getnumeric(d_ob, dayf);
      /* Keep dbox open if user has so requested.       */
        keep_open = dbox_persist();
        break;

      case set_butt:
      /* Set Default to the data now on display in      */
      /* the dbox.  (This is not necessarily the same   */
      /* as the settings currently in force.)           */
      /* If error, report it & continue.                */
        if (!save_ob_data())
        {
          werr(NON_FATAL, "Can't set defaults");
          keep_open = TRUE;
        }
        else
      /* Reset the value of target, and                 */
      /* Keep dbox open if user has so requested.       */
        {
          target = dbox_getnumeric(d_ob, dayf);
          keep_open = dbox_persist();
        }
        break;

      case show_butt:
      /* Load the default settings into the dbox, for   */
      /* user to inspect, alter, or whatever.           */
      /* If error, report it & continue.                */
        if (!load_ob_data())
          werr(NON_FATAL, "Can't load defaults");
        else
      /* Reset the value of target.                     */
          target = dbox_getnumeric(d_ob, dayf);
      /* Keep dbox open.                                */
        keep_open = TRUE;
        break;

      case sys_date_butt:
      /* Write the date from the system clock into      */
      /* the dbox.                                      */
        load_sys_date();
      /* Reset the value of target.                     */
        target = dbox_getnumeric(d_ob, dayf);
      /* Keep dbox open.                                */
        keep_open = TRUE;
        break;

      case cent_butt:
      /* Toggle between 20th and 21st centuries.        */
        year = dbox_getnumeric(d_ob, yrf);
        year = (year >= 2000 ? year-100 : year+100);
        dbox_setnumeric(d_ob, yrf, year);
      /* Keep dbox open.                                */
        keep_open = TRUE;
        break;

      case ns_butt: case n_or_s:
      /* Toggle between N and S latitude.               */
        dbox_getfield(d_ob, nsf, buf, 10);
        buf[0] = (buf[0]=='N'? 'S' : 'N');
        dbox_setfield(d_ob, nsf, buf);
      /* Keep dbox open.                                */
        keep_open = TRUE;
        break;

      case ew_butt: case e_or_w:
      /* Toggle between E and W longitude.              */
        dbox_getfield(d_ob, ewf, buf, 10);
        buf[0] = (buf[0]=='W'? 'E' : 'W');
        dbox_setfield(d_ob, ewf, buf);
      /* Keep dbox open.                                */
        keep_open = TRUE;
        break;

      default:
      /* Must be one of the date / time changing butts. */
      /* Call function to handle clicks on arrows.      */
        ob_arrows(response, &target);
      /* Keep dbox open.                                */
        keep_open = TRUE;
        break;
    }

  } while (keep_open);

/* Remove dbox from screen    */
  dbox_hide(d_ob);

  return;
}


/*------------------------------------------------------*/
/*       Handle clicks on data-changing buttons         */
/*                    in Observer Box                   */
/*------------------------------------------------------*/
static void ob_arrows(int response, int *targptr)
{
  char numstring[10];/* For number / string conversions */

  int lati;
  int latd;
  int lat10;
  int longi;
  int longd;
  int long10;
  int day;
  int month;         
  int year;          
  int lastday;       /* Last day of the month.          */
  int hour;
  int min;
  float offset;

  switch (response) {

    /* Latitude (integer part) */
    case lai_tu: case lai_td: case lai_uu: case lai_ud:
      lati = dbox_getnumeric(d_ob, laif);
      latd = dbox_getnumeric(d_ob, ladf);
      lati += addon[lai_ud-response];
      lat10 = 10*lati + latd;
      if (lat10 < 900 && lat10 >= 0)
      {
      /* Set integer field of latitude display.         */
        sprintf(numstring, "%02.2i", lat10/10);
        dbox_setfield(d_ob, laif, numstring);
      }
      break;

    /* Latitude (decimal part) */
    case lad_uu: case lad_ud:
      lati = dbox_getnumeric(d_ob, laif);
      latd = dbox_getnumeric(d_ob, ladf);
      latd += addon[lad_ud-response];
      lat10 = 10*lati + latd;
      if (lat10 < 900 && lat10 >= 0)
      {
      /* Set both fields of latitude display.           */
        sprintf(numstring, "%02.2i", lat10/10);
        dbox_setfield(d_ob, laif, numstring);
        dbox_setnumeric(d_ob, ladf, lat10%10);
      }
      break;

    /* Longitude (integer part) */
    case loi_hu: case loi_hd: case loi_tu: 
    case loi_td: case loi_uu: case loi_ud:
      longi  = dbox_getnumeric(d_ob, loif);
      longd  = dbox_getnumeric(d_ob, lodf);
      longi += addon[loi_ud-response];
      long10 = 10*longi + longd;
      if (long10 <= 1800 && long10 >= 0)
      {
      /* Set integer field of longitude display.        */
        sprintf(numstring, "%03.3i", long10/10);
        dbox_setfield(d_ob, loif, numstring);
      }
      break;

    /* Longitude (decimal part) */
    case lod_uu: case lod_ud:
      longi  = dbox_getnumeric(d_ob, loif);
      longd  = dbox_getnumeric(d_ob, lodf);
      longd += addon[lod_ud-response];
      long10 = 10*longi + longd;
      if (long10 <= 1800 && long10 >= 0)
      {
      /* Set both fields of longitude display.          */
        sprintf(numstring, "%03.3i", long10/10);
        dbox_setfield(d_ob, loif, numstring);
        dbox_setnumeric(d_ob, lodf, long10%10);
      }
      break;

    /* Years   */
    case yr_tu:  case yr_td:  case yr_uu:  case yr_ud:
      year = dbox_getnumeric(d_ob, yrf);
      year += addon[yr_ud-response];
      if (year <= 2099 && year >= 1900)
      {
      /* Set year field.  Make day field as close as    */
      /* possible to its target, while keeping it valid.*/
        dbox_setnumeric(d_ob, yrf, year);
        month = dbox_getnumeric(d_ob, monf);
        lastday = daysin[leap(year)][month];
        day = (*targptr > lastday ? lastday : *targptr);
        sprintf(numstring, "%02.2i", day);
        dbox_setfield(d_ob, dayf, numstring);
      }
      break;

    /* Months  */
    case mon_tu: case mon_td: case mon_uu: case mon_ud:
      year   = dbox_getnumeric(d_ob, yrf );
      month  = dbox_getnumeric(d_ob, monf);
      month += addon[mon_ud-response];
      if (month <= 12 && month > 0)
      {
      /* Set month field.  Make day field as close as   */
      /* possible to its target, while keeping it valid.*/
        sprintf(numstring, "%02.2i", month);
        dbox_setfield(d_ob, monf, numstring);
        year = dbox_getnumeric(d_ob, yrf );
        lastday = daysin[leap(year)][month];
        day = (*targptr > lastday ? lastday : *targptr);
        sprintf(numstring, "%02.2i", day);
        dbox_setfield(d_ob, dayf, numstring);
      }
      break;

    /* Days    */
    case day_tu: case day_td: case day_uu: case day_ud:
      year = dbox_getnumeric(d_ob, yrf );
      month= dbox_getnumeric(d_ob, monf);
      day  = dbox_getnumeric(d_ob, dayf);
      day += addon[day_ud-response];
      lastday = daysin[leap(year)][month];
      if (day <= lastday && day > 0)
      {
      /* Set day field and change value of target.      */
        sprintf(numstring, "%02.2i", day);
        dbox_setfield(d_ob, dayf, numstring);
        *targptr = day;
      }
      break;

    /* Hours   */
    case hr_tu:  case hr_td:  case hr_uu:  case hr_ud:
      hour  = dbox_getnumeric(d_ob, hrf);
      hour += addon[hr_ud-response];
      if (hour <= 23 && hour >= 0)
      {
        sprintf(numstring, "%02.2i", hour);
        dbox_setfield(d_ob, hrf, numstring);
      }
      break;

    /* Minutes */
    case min_tu: case min_td: case min_uu: case min_ud:
      min = dbox_getnumeric(d_ob, minf);
      min += addon[min_ud-response];
      if (min <= 59 && min >= 0)
      {
        sprintf(numstring, "%02.2i", min);
        dbox_setfield(d_ob, minf, numstring);
      }
      break;

    /* Time offset */
    case off_uu: case off_ud: case off_xu: case off_xd:
      dbox_getfield(d_ob, offf, numstring, 10);
      sscanf(numstring, "%f", &offset);
      offset += offset_incr[off_xd - response];
      if (offset >= -15.0f && offset <= 15.0f)
      {
        sprintf(numstring, "%+05.1f", (double)offset);
        dbox_setfield(d_ob, offf, numstring);
      }
      break;

    default:
      break;

  }
  return;
}


/*------------------------------------------------------*/
/*          Handle clicks on OK in Observer Box         */
/*------------------------------------------------------*/
static void ob_ok_click(void)
{
  observerstr new_data; /* New data read from dbox.     */

  BOOL dir_changed = FALSE;/* Becomes TRUE if user      */
                           /* changes direction of view.*/
  BOOL dat_changed = FALSE;/* Becomes TRUE if user      */
                           /* changes date field.       */
  BOOL tim_changed = FALSE;/* Becomes TRUE if user      */
                           /* changes time field.       */
  BOOL off_changed = FALSE;/* Becomes TRUE if user      */
                           /* changes time offset.      */
  BOOL loc_changed = FALSE;/* Becomes TRUE if user      */
                           /* changes location.         */
  BOOL dir_only    = FALSE;/* Becomes TRUE if user      */
                           /* changes just the direction*/
  BOOL tim_dir_only= TRUE; /* Becomes FALSE if user     */
                           /* changes dat, loc or offset*/
  BOOL any_changed = FALSE;/* Becomes TRUE if user      */
                           /* changes any of the data.  */

/* Read new data from dbox                              */
  data_from_box(&new_data);

/* Give special treatment if this is the first time.    */
if (!observer_verified)
  {
    /* Update observer data to match data read from box.*/
    ob_data = new_data;

    /* Flag observer data as verified.                  */
    observer_verified = TRUE;

    /* Build plotting lists.                            */
    build_lists();

    return;
  }

/* Normal Processing.                                   */

/* Examine new data to find out what's changed.         */
/* Note 'date' and 'time' here refer to the values app- */
/* earing in the dbox.  Need to look at 'offset' field  */
/* as well in order to detect changes.                  */
  off_changed = (fabs((double)new_data.offset - (double)ob_data.offset)
                 >= 0.1);
  dir_changed = (new_data.dir != ob_data.dir);
  dat_changed = (new_data.day   != ob_data.day   ||
                 new_data.month != ob_data.month ||
                 new_data.year  != ob_data.year);
  tim_changed = (new_data.hour  != ob_data.hour  ||
                 new_data.min   != ob_data.min);
  loc_changed = (new_data.lat10  != ob_data.lat10  ||
                 new_data.isn    != ob_data.isn    ||
                 new_data.long10 != ob_data.long10 ||
                 new_data.isw    != ob_data.isw);
  dir_only    =  dir_changed && !dat_changed && \
                !tim_changed && !off_changed && !loc_changed;
  tim_dir_only= !dat_changed && !off_changed && !loc_changed;
  any_changed =  dir_changed || dat_changed || \
                 tim_changed || loc_changed || off_changed;


/* Do nothing if no changes have been made.             */
  if (!any_changed)
  return;

/* Change Observer Details to match new data.           */
  ob_data = new_data;

/* Invalidate the windows.                              */
  invalidate_all();

/* If the direction alone has changed, just re-calculate*/
/* the screen coords and then return.                   */
  if (dir_only)
  {
    recalc_horiz_xy();
    recalc_vert_xy();
    return;
  }
/*
 * The date or time have changed, so rebuild the
 * plotting lists, and re-calculate when the
 * selected object (if any) is visible.
 */
  build_lists();

  if (selection_exists)
  {
    /* Set special reason code if nothing but the time  */
    /* (and possibly the direction as well - this is of */
    /* no importance) has changed.                      */
    int reason = (tim_dir_only? \
        timeonly_recalculate : recalculate_data);

    /* Call Selection function for owner of object.     */
    module_fns[sel_owner].selectfn(reason);
    /* Record new id numbers of selected object.        */
    horiz_data.selnum = selection.horiz_id;
    vert_data.selnum  = selection.vert_id;
    /* If object is on view, ensure coords of frame and */
    /* bounding box of frame are valid.                 */
    if (horiz_data.selnum != NOWHERE)
      calculate_frame(&horiz_data);
    if (vert_data.selnum != NOWHERE)
      calculate_frame(&vert_data);
  }

  return;
}

/*------------------------------------------------------*/
/*             Fn to build plotting lists.              */
/*------------------------------------------------------*/
static void build_lists(void)
{
  int i;
  buildfntype *bfn;

/* Turn hour glass on.                                  */
  visdelay_begin();

  /* Reset number of objects to zero.                   */
  horiz_data.number = 0;
  vert_data.number  = 0;

  for (i=0; i < module_count; i++)
  {
     bfn = module_fns[i].buildfn;
     if (bfn != NULL) bfn();
  }

/* Turn hour glass off.                                 */
  visdelay_end();

  return;
}

/*------------------------------------------------------*/
/*   Fn to declare all parts of both windows invalid    */
/*------------------------------------------------------*/
static void invalidate_all(void)
{
  wimp_redrawstr r;

  r.w   = horiz_handle;
  r.box = horiz_size;
  wimpt_noerr(wimp_force_redraw(&r));
  r.w   = vert_handle;
  r.box = vert_size;
  wimpt_noerr(wimp_force_redraw(&r));

  return;
}

/*------------------------------------------------------*/
/*      Fn to write data into Observer Details box      */
/*------------------------------------------------------*/
static void data_to_box(observerstr data)
{
  int i;
  char numstring[10];

/* Set Latitude field (integer part, then decimal).     */
  sprintf(numstring, "%02.2i", data.lat10/10);
  dbox_setfield(d_ob, laif, numstring);
  dbox_setnumeric(d_ob, ladf, data.lat10%10);

/* Set N or S for Latitude.  */
  dbox_setfield(d_ob, nsf, (data.isn? "N" : "S"));

/* Set Longitude field (integer part, then decimal).    */
  sprintf(numstring, "%03.3i", data.long10/10);
  dbox_setfield(d_ob, loif, numstring);
  dbox_setnumeric(d_ob, lodf, data.long10%10);

/* Set E or W for Longitude. */
  dbox_setfield(d_ob, ewf, (data.isw? "W" : "E"));

/* Set Day field.                                       */
  sprintf(numstring, "%02.2i", data.day);
  dbox_setfield(d_ob, dayf, numstring);

/* Set Month field.                                     */
  sprintf(numstring, "%02.2i", data.month);
  dbox_setfield(d_ob, monf, numstring);

/* Set Year field.                                      */
  dbox_setnumeric(d_ob, yrf, data.year);

/* Set Hour field.                                      */
  sprintf(numstring, "%02.2i", data.hour);
  dbox_setfield(d_ob, hrf, numstring);

/* Set Minute field.                                    */
  sprintf(numstring, "%02.2i", data.min);
  dbox_setfield(d_ob, minf, numstring);

/* Set Offset field.                                    */
  sprintf(numstring, "%+05.1f", (double)data.offset);
  dbox_setfield(d_ob, offf, numstring);

/* Set 'direction' radio buttons.                       */
  for (i=0; i<NDIR; i++)
    dbox_setnumeric(d_ob, DIR_BASE+i, (i==data.dir? 1 : 0));

  return;
}


/*------------------------------------------------------*/
/*      Fn to read data from Observer Details box       */
/*------------------------------------------------------*/
static void data_from_box(observerstr *ptr)
{
  int i;
  int d;
  char buf[10];

/* Read latitude*10.  Combine integer and decimal parts.*/
  i = dbox_getnumeric(d_ob, laif);
  d = dbox_getnumeric(d_ob, ladf);
  ptr->lat10 = 10*i + d;

/* Read N or S for latitude.                            */
  dbox_getfield(d_ob, nsf, buf, 10);
  ptr->isn = (buf[0]=='N');

/* Construct latitude in radians.                       */
  ptr->latit = CONV*(REAL)ptr->lat10/(REAL)10;
  if (!ptr->isn)
    ptr->latit = -ptr->latit;

/* Read longitude*10. Combine integer and decimal parts.*/
  i = dbox_getnumeric(d_ob, loif);
  d = dbox_getnumeric(d_ob, lodf);
  ptr->long10 = 10*i + d;

/* Read E or W for longitude.                           */
  dbox_getfield(d_ob, ewf, buf, 10);
  ptr->isw = (buf[0]=='W');

/* Construct longitude in radians.                      */
  ptr->longit = CONV*(REAL)ptr->long10/(REAL)10;
  if (!ptr->isw)
    ptr->longit = -ptr->longit;

/* Read day.                                            */
  ptr->day   = dbox_getnumeric(d_ob, dayf);

/* Read month.                                          */
  ptr->month = dbox_getnumeric(d_ob, monf);

/* Read year.                                           */
  ptr->year  = dbox_getnumeric(d_ob, yrf);

/* Read hour.                                           */
  ptr->hour  = dbox_getnumeric(d_ob, hrf);

/* Read minute.                                         */
  ptr->min   = dbox_getnumeric(d_ob, minf);

/* Read time offset.                                    */
  dbox_getfield(d_ob, offf, buf, 10);
  sscanf(buf, "%f", &ptr->offset);

/* Read direction.                                      */
/* Search the 'direction' radio buttons for the one     */
/* that is ON.                                          */
  for (i=NDIR-1;\
       i>0 && dbox_getnumeric(d_ob, DIR_BASE+i)==0;\
       i--)
    ;
/* Set dir field.                                       */
  ptr->dir = i;

/* Fill in derived fields of observer data.             */
  fillin_obfields(ptr);

  return;
}

/*------------------------------------------------------*/
/*     Function to fill in the derived fields of the    */
/*             observerstr pointed to by ptr.           */
/*------------------------------------------------------*/
static void fillin_obfields(observerstr *ptr)
{

/* Set ang field to angle corresponding to dir field.   */
  ptr->ang = direction[ptr->dir];

/* Calculate days since 12:00 on 00/01/1900             */
  ptr->jul = datime_julian(ptr, ptr->hour, ptr->min);

/* Set local siderial time.                             */
  ptr->sid = datime_siderial(ptr, ptr->jul, ptr->hour, ptr->min);

  return;
}

/*------------------------------------------------------*/
/*        Function to save Observer Data to disc        */
/*------------------------------------------------------*/
static BOOL save_ob_data(void)
{
  observerstr new_data[1];  /* For data read from dbox. */
  FILE *defaults;           /* File handle.             */
  size_t numwritten;

/* Open disc file for writing.  Quit if error.          */
  defaults = fopen(data_file, "wb");
  if (defaults == NULL) return FALSE;

/* Read new data from dbox.                             */
  data_from_box(new_data);

/* Write Observer Details.      */
  numwritten = fwrite((void *)new_data, sizeof(observerstr), 1, defaults);

/* Close file.  Fatal error if it won't close.          */
  if (fclose(defaults) != 0)
    werr(FATAL, "SkyView Error: Can't close Default file");

/* Return success or failure.                           */
  return (numwritten == 1);
}

/*------------------------------------------------------*/
/*       Function to load Observer Data from disc       */
/*------------------------------------------------------*/
static BOOL load_ob_data(void)
{
  observerstr new_data[1]; /* For data read from disc.  */
  FILE *defaults;          /* File handle.              */
  size_t numread;

/* Open disc file for reading.  Quit if error.          */
  defaults = fopen(data_file, "rb");
  if (defaults == NULL) return FALSE;

/* Attempt to read Observer Details.                    */
  numread = fread((void *)new_data, sizeof(observerstr), 1, defaults);

/* Close disc file.  Fatal error if it won't close.     */
  if (fclose(defaults) != 0)
    werr(FATAL, "SkyView Error: Can't close Default file");

/* If read failed, do not load dbox.                    */
  if (numread != 1) return FALSE;

/* All OK. Load data into dbox & return TRUE.           */
  data_to_box(new_data[0]);
  return TRUE;
}


/*------------------------------------------------------*/
/*        Function to load System Date into dbox.       */
/*------------------------------------------------------*/
static void load_sys_date(void)
{
  time_t encoded_time;
  struct tm *sys_time;
  char numstring[10];  /* For No.-to-string conversions */

/* Read system time.                                    */
  encoded_time = time(NULL);
  if (encoded_time == (time_t)-1)
    return;
  sys_time = localtime(&encoded_time);

/* Set Day field.                                       */
  sprintf(numstring, "%02.2i", sys_time->tm_mday);
  dbox_setfield(d_ob, dayf, numstring);

/* Set Month field.                                     */
  sprintf(numstring, "%02.2i", 1 + sys_time->tm_mon);
  dbox_setfield(d_ob, monf, numstring);

/* Set Year field.                                      */
  dbox_setnumeric(d_ob, yrf, 1900 + sys_time->tm_year);

  return;
}


/*------------------------------------------------------*/
/*          Raw Event Handler for Observer Box          */
/*------------------------------------------------------*/
static BOOL ob_raw_handler(dbox d, void *event, void *handle)

/* Prevent a click with Adjust from clearing ALL the    */
/* 'direction' radio buttons at the same time.          */

{
  wimp_eventstr *e=(wimp_eventstr *)event;
  wimp_i icon;

  if (e->e == wimp_EBUT &&\
      e->data.but.m.bbits == wimp_BRIGHT)
  {
    icon = e->data.but.m.i;
    if (icon>=1 &&\
        icon<=8 &&\
        dbox_getnumeric(d,icon)==0)
    {
      dbox_setnumeric(d,icon,TRUE);
      return TRUE;
    }
  }
  return FALSE;
}


/********************************************************/
/*         Handling of Select on Icon Bar Icon          */
/********************************************************/

static void sv_iconclick(wimp_i icon)
{
  return;
}


/********************************************************/
/*     Function to add new object to plotting list.     */
/********************************************************/

void addobj(plotobj new_object, int *horiz_id, int *vert_id)
{


/* Add new object to Horiz window if it is above the    */
/* horizon but below alt_max, and if there is room in   */
/* the plotting list.                                   */

  if (horiz_data.number <  MAXOBJS   && \
         new_object.alt >  (REAL)0.0 && \
         new_object.alt <= alt_max)
  {
    /* Fill in the remaining info                       */
    /*(ie screen coords, bounding box, pointer to next).*/
    new_object.wind_x = x_h(new_object.azim);
    new_object.wind_y = y_h(new_object.alt);
    coords_offsetbox(&new_object.size, \
                      new_object.wind_x, \
                      new_object.wind_y, \
                     &new_object.bounds);
    new_object.next = 0;

    /* Add new object to list, and return an id number  */
    /* to the calling module.                           */
    horiz_list[horiz_data.number] = new_object;
    *horiz_id = horiz_data.number++;
  }
  else
    /* Inform calling module that obj is not in window. */
    *horiz_id = NOWHERE;


/* Add new object to Vert window if it is above         */
/* alt_min, and if there is room in the plotting list.  */

  if (vert_data.number <  MAXOBJS   && \
         new_object.alt >= alt_min)
  {
    /* Fill in the remaining info                       */
    /*(ie screen coords, bounding box, pointer to next).*/
    xy_v( new_object.alt,     new_object.azim, \
         &new_object.wind_x, &new_object.wind_y);
    coords_offsetbox(&new_object.size, \
                      new_object.wind_x, \
                      new_object.wind_y, \
                     &new_object.bounds);
    new_object.next = 0;

    /* Add new object to list, and return an id number  */
    /* to the calling module.                           */
    vert_list[vert_data.number] = new_object;
    *vert_id = vert_data.number++;
  }
  else
    /* Inform calling module that obj is not in window. */
    *vert_id = NOWHERE;


  return;
}


/********************************************************/
/*    Functions to re-calculate screen x and y values   */
/*         following change of direction of view        */
/********************************************************/
/*------------------------------------------------------*/
/*                   Horiz Window xy                    */
/*------------------------------------------------------*/
static void recalc_horiz_xy(void)
{
  plotobj *p;
  /* Point to end of Horiz list:                        */
  plotobj *pend = horiz_list + horiz_data.number;

  for(p = horiz_list; p < pend; p++)
  {
  /* Fill in x, y, bounding box and pointer to next.    */
    p->wind_x = x_h(p->azim);
    p->wind_y = y_h(p->alt);
    coords_offsetbox(&p->size, p->wind_x, p->wind_y, &p->bounds);
    p->next = 0;
  }

  return;
}

/*------------------------------------------------------*/
/*                    Vert Window xy                    */
/*------------------------------------------------------*/
static void recalc_vert_xy(void)
{
  plotobj *p;
  /* Point to end of Vert list:                         */
  plotobj *pend = vert_list + vert_data.number;

  for (p = vert_list; p < pend; p++)
  {
  /* Fill in x, y, bounding box and pointer to next.    */
    xy_v(p->alt, p->azim, &p->wind_x, &p->wind_y);
    coords_offsetbox(&p->size, p->wind_x, p->wind_y, &p->bounds);
    p->next = 0;
  }

  return;
}

/********************************************************/
/*          Function to register a module.              */
/********************************************************/
BOOL register_module(initfntype *initfn)
{
  BOOL text_null;        /* TRUE for null menu entries. */
  modulestr new_module;  /* For info on new module.     */

/*------------------------------------------------------*/
/*      Check there is space to register module.        */
/*------------------------------------------------------*/
  if (module_count >= MAXMODULES) return FALSE;

/*------------------------------------------------------*/
/*                  Obtain Module Info                  */
/*------------------------------------------------------*/
  /* Call initialisation function and obtain info on    */
  /* new module.  Abandon if initialisation fails.      */
  if (!initfn(module_count, &new_module)) return FALSE;

/*------------------------------------------------------*/
/*    Record whether module is initially enabled or     */
/*                  initially disabled.                 */
/*------------------------------------------------------*/
  enabled[module_count]=new_module.initial;

/*------------------------------------------------------*/
/*           Record new List-Building function          */
/*------------------------------------------------------*/
  module_fns[module_count].buildfn = new_module.buildfn;

/*------------------------------------------------------*/
/*               Record new Info Function               */
/*------------------------------------------------------*/
  module_fns[module_count].infofn = new_module.infofn;

/*------------------------------------------------------*/
/*             Deal with Select function.               */
/*------------------------------------------------------*/
  /* Check for illegal combinations of NULL items.      */

  text_null = (*new_module.select_entry == '\0');
  /* Quit if select fn is NULL but text entry isn't.    */
  if (new_module.selectfn  == NULL  && !text_null) 
    return FALSE;
  /* Quit if text entry is NULL but select menu isn't.  */
  if (text_null && new_module.select_menu  != NULL)
    return FALSE;

  /* Checks passed OK, so record Select function for    */
  /* this module.                                       */
  module_fns[module_count].selectfn=new_module.selectfn;

  /* Install menu entry (if any) and sub-menu (if any). */
  if (!text_null)
  {
    if (!install_select_entry(new_module)) return FALSE;
    /* Set module_index to record that this module owns */
    /* the newly-installed entry in the Select menu.    */
    module_index[module_count] = select_count;
    /* Ie, module m owns menu entry module_index[m]     */
  }
  else
    /* Set module_index to record that this module does */
    /* not own an entry in the Select menu.             */
    module_index[module_count] = NO_ENTRY;

/*------------------------------------------------------*/
/*             Deal with Display function.              */
/*------------------------------------------------------*/
  /* Check for illegal combinations of NULL items.      */

  text_null = (*new_module.display_entry == '\0');
  /* Quit if display fn is NULL but text entry isn't.   */
  if (new_module.dispfn == NULL  && !text_null)
    return FALSE;
  /* Quit if text entry is NULL but display menu isn't. */
  if (text_null && new_module.display_menu  != NULL)
    return FALSE;

  /* Checks passed OK, so record Display function for   */
  /* this module.                                       */
  module_fns[module_count].dispfn = new_module.dispfn;

  /* Install menu entry (if any) and sub-menu (if any). */
  if (!text_null)
    if (!install_display_entry(new_module)) return FALSE;

/*------------------------------------------------------*/
/*                 Module installed OK                  */
/*------------------------------------------------------*/
  /* Increment module count to show the no. of modules. */
  module_count++;

  return TRUE;
}


/*------------------------------------------------------*/
/*       Install Select menu entry for a Module.        */
/*------------------------------------------------------*/
static BOOL install_select_entry(modulestr new_module)
{

  /* Add text entry to menu.  Use menu_new the first    */
  /* time, and menu_extend thereafter.                  */
  if (select_count == 0)
  {
    sel_sub_menu = menu_new(SEL_SUB_NAME, new_module.select_entry);
    if (sel_sub_menu == NULL) return FALSE;
  }
  else
    menu_extend(sel_sub_menu, new_module.select_entry);

  /* Update sel_index array to associate new module     */
  /* with this entry in the Select menu.                */
  /* ie Entry n is owned by module sel_index[n]         */
  sel_index[++select_count] = module_count;

  /* Set tick/fade status of menu entry, according to   */
  /* whether module is enabled or disabled.             */
  menu_setflags(sel_sub_menu, select_count, 0, !enabled[module_count]);

  /* Attach sub-menu, if one was provided.              */
  if (new_module.select_menu != NULL)
    menu_submenu(sel_sub_menu, select_count, new_module.select_menu);

  return TRUE;
}

/*------------------------------------------------------*/
/*        Install Display menu entry for a Module.      */
/*------------------------------------------------------*/
static BOOL install_display_entry(modulestr new_module)
{

  /* Add text entry to menu.  Use menu_new the first    */
  /* time, and menu_extend thereafter.                  */
  if (display_count == 0)
  {
    display_sub_menu = menu_new(DISP_SUB_NAME, new_module.display_entry);
    if (display_sub_menu == NULL) return FALSE;
  }
  else
    menu_extend(display_sub_menu, new_module.display_entry);

  /* Update index array to associate new module with    */
  /* this entry in the Display menu.                    */
  disp_index[++display_count] = module_count;

  /* Set ticked/faded status of menu entry, according   */
  /* to whether module is enabled or disabled.          */
  menu_setflags(display_sub_menu, display_count,\
                enabled[module_count], 0);

  /* Attach sub-menu, if one was provided.              */
  if (new_module.display_menu != NULL)
    menu_submenu(display_sub_menu, display_count, new_module.display_menu);

  return TRUE;
}

  

/********************************************************/
/*                    INITIALISATION                    */
/********************************************************/

static BOOL sv_initialise(void)
{
  REAL big_r;   /* Max dist from origin of Vert window  */
                /* to any point in that window.         */
  int compass_rad;   /* Radius of Vert compass circle.  */
  int i;        /* General purpose counter.             */
  REAL window_factor;/* Plotting scale calculated from  */
                     /* size of Horiz window.           */

/*------------------------------------------------------*/
/*        Read SkyView$Dir and form file name           */
/*------------------------------------------------------*/
  os_read_var_val("SkyView$Dir", app_dir, 245);
  if (*app_dir == '\0') return FALSE;

/* Copy SkyView$Dir into array for file name.           */
  strcpy(data_file, app_dir);

/* Append file name */
  strcat(data_file, "." DEF_NAME);

/*------------------------------------------------------*/
/*                  Creation of menus                   */
/*------------------------------------------------------*/
/* Main menu structure.                                 */
  if (main_menu=menu_new(MAIN_NAME, MAIN_MENU_ITEMS), \
      main_menu==NULL)
    return FALSE;

/* Sub menu structure for 'View sel.'.                  */
  if (view_sub_menu=menu_new(VIEW_SUB_NAME, VIEW_SUB_MENU_ITEMS), \
      view_sub_menu==NULL)
    return FALSE;

/* Attach 'View sel.' sub-menu.                         */
  menu_submenu(main_menu, item_view, view_sub_menu);

/* Sub menu structure for 'Print'.                      */
  print_sub_menu = menu_new(PRINT_SUB_NAME, PRINT_SUB_MENU_ITEMS);
  if (print_sub_menu == NULL) return FALSE;

/* Attach 'Print' sub-menu.                             */
  menu_submenu(main_menu, item_print, print_sub_menu);

/*------------------------------------------------------*/
/*                  Horizontal Window                   */
/*------------------------------------------------------*/
/* Create Horiz window         */
  if (!create_window(HORIZ_NAME, &horiz_handle, &horiz_size))
    return FALSE;

/*    Initialise data structure for Horiz window       */
  horiz_data.open = FALSE;                         /* Open/Closed flag     */
  horiz_data.compass_drawfn = draw_horiz_compass;  /* Compass-drawing fn   */
  horiz_data.listptr = horiz_list;                 /* Point to plot list   */
  horiz_data.number = 0;                           /* No objs. in list.    */
  horiz_data.selnum = NOWHERE;                     /* No selected obj.     */
  horiz_data.matrix.xx = 0;                        /* Transformation matrix*/
  horiz_data.matrix.xy =  SCALFACT;                /* for printing.  Scale */
  horiz_data.matrix.yx = -SCALFACT;                /* by SCALFACT and      */
  horiz_data.matrix.yy = 0;                        /* rotate by 90.       */
  horiz_data.posn.dx = 540000;                     /* Position of print    */
  horiz_data.posn.dy = 108000;                     /* rect. on page.       */
  horiz_data.title_buildfn = build_horiz_title;    /* Title-building fn.   */
  horiz_data.title_writefn = write_horiz_title;    /* Title-writing  fn.   */

/* Register event handler for Horiz window */
  win_register_event_handler(horiz_handle, sv_win_event_handler, &horiz_data);

/* Establish Menu Handler and Pre-menu Proc for Horiz. */
  if(!event_attachmenumaker(horiz_handle,main_premenuproc,main_menuproc,0))
    return FALSE;

/* Establish screen scaling factor from                 */
/* width of Horiz window.                               */
  width = (horiz_size.x1 - horiz_size.x0);
  window_factor  = (REAL)width/((REAL)2.0*PI);

/* Establish max altitude in Horiz window               */
  height = horiz_size.y1;
  alt_max= (REAL)height/window_factor;

/*------------------------------------------------------*/
/*                     Vert Window                      */
/*------------------------------------------------------*/
/* Create Vert window         */
  if (!create_window(VERT_NAME, &vert_handle, &vert_size))
    return FALSE;

/*     Initialise data structure for Vert window.       */
  vert_data.open = FALSE;                         /* Open/Closed flag     */
  vert_data.compass_drawfn = draw_vert_compass;   /* Compass-drawing fn.  */
  vert_data.listptr = vert_list;                  /* Point to plot list.  */
  vert_data.number = 0;                           /* No objs. in list.    */
  vert_data.selnum = NOWHERE;                     /* No selected obj.     */
  vert_data.matrix.xx = SCALFACT;                 /* Transformation matrix*/
  vert_data.matrix.xy = 0;                        /* for printing.  Scale */
  vert_data.matrix.yx = 0;                        /* by SCALFACT and      */
  vert_data.matrix.yy = SCALFACT;                 /* rotate by 0.        */
  vert_data.posn.dx =  80000;                     /* Position of print    */
  vert_data.posn.dy = 144000;                     /* rect. on page.       */
  vert_data.title_buildfn = build_vert_title;     /* Title-building fn.   */
  vert_data.title_writefn = write_vert_title;     /* Title-writing  fn.   */

/* Register event handler for Vert window */
  win_register_event_handler(vert_handle, sv_win_event_handler, &vert_data);

/* Establish Menu Handler and Pre-menu Proc for Vert.   */
  if(!event_attachmenumaker(vert_handle,main_premenuproc,main_menuproc,0))
    return FALSE;

/* Establish min altitude in Vert window.              */
/* First get maximum distance from origin to any point */
/* in Vert window.                                     */
  {
    int corner1, corner2, corner3, corner4;
    int r1, r2;
    int r_max;
    corner1 = vert_size.x0 * vert_size.x0 +
              vert_size.y0 * vert_size.y0;
    corner2 = vert_size.x0 * vert_size.x0 +
              vert_size.y1 * vert_size.y1;
    corner3 = vert_size.x1 * vert_size.x1 +
              vert_size.y0 * vert_size.y0;
    corner4 = vert_size.x1 * vert_size.x1 +
              vert_size.y1 * vert_size.y1;
    r1 = (corner1 > corner2 ? corner1 : corner2);
    r2 = (corner3 > corner4 ? corner3 : corner4);
    r_max = (r1 > r2 ? r1 : r2);
    big_r = (REAL)sqrt((double)r_max);
  }
/* Can now calculate min altitude.                      */
  alt_min = PIby2 - big_r/window_factor;
  if (alt_min < (REAL)0.0)
    alt_min = (REAL)0.0;

/*     Construct a Draw Path consisting of a circle     */
/*                 and NDIR tick marks.                 */

/* Set plotting factor to suit units used by Draw       */
/* functions.                                           */
  factor = (REAL)DrawU * window_factor;

/* Calculate radius of compass circle.                  */
  compass_rad = (int)(factor*(PIby2-compass_alt));

/* Set up Path describing circle.                       */
  sv_Draw_circle(compass_rad);

/* Set up Path describing NDIR tick marks.              */
  for (i=0; i<NDIR; i++)
  {
    int x, y;
    xy_v(compass_alt, direction[i], &x, &y);
    sv_Draw_move(x,y);
    xy_v(compass_alt*(REAL)0.98, direction[i], &x, &y);
    sv_Draw_line(x,y);
  }

/* Write end-of-path marker.                            */
  sv_Draw_endp();

/* Process the Draw Path into a semi-digested form which*/
/* will be quicker to plot  (at the penalty of being    */
/* unable to take advantage of any increase in screen   */
/* resolution due to a later mode change).              */
  draw_regsP.r[0] = (int)path;     /* Input path.       */
  draw_regsP.r[1] = (int)semi;     /* Output path.      */
  draw_regsP.r[2] = (int)&matrix;  /* Transformation.   */
  draw_regsP.r[3] = 0;             /* Default flatness. */
  draw_regsP.r[4] = 4*DrawU;       /* 4 OS units thick. */
  draw_regsP.r[5] = (int)&cj_style;/* Caps & joins.     */
  draw_regsP.r[6] = 0;             /* No dashes.        */
  wimpt_noerr(os_swix(sv_XDraw_StrokePath, &draw_regsP));

/* Set up register set for later call to plot the semi- */
/* digested Path.                                       */
  draw_regsS.r[0] = (int)semi;    /* Input path.        */
  draw_regsS.r[1] = 0x30;         /* Fill only. Style.  */
  draw_regsS.r[2] = 0;            /* Transform 1:1      */
  draw_regsS.r[3] = 0;            /* Flatness not used. */
  draw_regsS.r[4] = 0;            /* Thickness not used.*/
  draw_regsS.r[5] = 0;            /* Caps joins not used*/
  draw_regsS.r[6] = 0;            /* No dashes.         */
  draw_regsS.r[7] = 2;            /* Fill by subpaths.  */

/* Set up register set for later call to print 'raw'    */
/* Draw Path (to take full advantage of printer resoln.)*/
  draw_regsP.r[1] = 0x30;         /* Fill only. Style.  */

/* Set plotting factor to suit Horiz and Vert windows.  */
  factor = window_factor;

/*------------------------------------------------------*/
/*    Register current screen mode, and set sprite -    */
/*                   plotting tables.                   */
/*------------------------------------------------------*/
  wimpt_checkmode();
  wimpt_noerr(sprite_tables());

/*------------------------------------------------------*/
/*               dbox for Observer Details              */
/*------------------------------------------------------*/

/* Create dbox                 */
  if (d_ob = dbox_new(OB_NAME), d_ob == NULL)
    return FALSE;

/* Establish raw event handler */
  dbox_raw_eventhandler(d_ob, ob_raw_handler, 0);

/* Find out window handle      */
  ob_handle = (wimp_w)dbox_syshandle(d_ob);

/* Attach menu & pre-menu proc */
  if(!event_attachmenumaker(ob_handle,main_premenuproc,main_menuproc,0))
    return FALSE;

/* Set dbox fields to Factory Default Settings.         */
  data_to_box(ob_data);

/* Attempt to load user's default settings into dbox    */
/* from disc.                                           */
  load_ob_data();

/* Read observer details back from dbox.  These will be */
/* user's defaults if attempt to read was sucessful, and*/
/* factory defaults otherwise.                          */
  data_from_box(&ob_data);

/*------------------------------------------------------*/
/*                       Info Box                       */
/*------------------------------------------------------*/

/* Create dbox.                                         */
  if (d_info = dbox_new(INFO_NAME), d_info == NULL)
    return FALSE;

/*------------------------------------------------------*/
/*                    Icon Bar Icon                     */
/*------------------------------------------------------*/
/* Put icon on icon bar        */
/* and register icon click handler */
  baricon(ICON_NAME, 1, sv_iconclick);

/* Establish icon menu handler */
/* and menu pre-proc function  */
  if(!event_attachmenumaker(win_ICONBAR,main_premenuproc,main_menuproc,0))
    return FALSE;

/*------------------------------------------------------*/
/*               Registration of Modules                */
/*------------------------------------------------------*/

  if(!register_module(sun_initfn)) return FALSE;
  if(!register_module(moon_initfn)) return FALSE;
  if(!register_module(plans_initfn)) return FALSE;
  if(!register_module(star_initfn)) return FALSE;
  if(!register_module(markers_initfn)) return FALSE;
  if(!register_module(tvsats_initfn)) return FALSE;
  if(!register_module(userobj_initfn)) return FALSE;

/* If there are any items in the Display sub-menu,      */
/* attach the sub-menu and unfade the Display item.     */
  if (display_count > 0)
  {
    menu_submenu(main_menu, item_display, display_sub_menu);
    menu_setflags(main_menu, item_display, 0, 0);
  }

/* If there are any items in the Select sub-menu,       */
/* attach the sub-menu.  Unfading is handled by the     */
/* menumaker, as it depends on whether the user has     */
/* verified the observer details.                       */
  if (select_count > 0)
    menu_submenu(main_menu, item_select, sel_sub_menu);

/*------------------------------------------------------*/
/*                 End of Initialisation                */
/*------------------------------------------------------*/
  return TRUE;
}

/********************************************************/
/*                 The MAIN function                    */
/********************************************************/

int main(int argc, char *argv[])
{
  BOOL init_ok;

  if (argc>1) strcpy(start_file, argv[1]);

/*             RISC_OSlib initialisation                */
  wimpt_init(APP_NAME);  /* initialise task */
  res_init(APP_NAME);    /*    resources    */
  resspr_init();         /*     sprites     */
  template_init();       /*    templates    */
  dbox_init();           /* dialogue boxes  */
  visdelay_init();       /*    hourglass    */

/*              Other initialisation                    */
  visdelay_begin();
  init_ok = sv_initialise();
  visdelay_end();

  if (init_ok)
  {
    /* The main event loop */
    while (TRUE)
      event_process();
  }

  return 0;
}
