/*
 * Program: Global Navigator II
 * Programmer: Robert John Morton UK-YE572246C
 * Programming Language: 'C'
 * Date: Thu 30 Dec 2019 - Belo Horizonte-MG, Brazil

This program is exclusively the inetllectual property of the programmer.

Project: Date Started: Thu 30 Dec 2019
         Date Finished: Wed 28 Feb 2020 - 2 months
         8-tab conversion 15 Aug 2020 to 28 Aug 2020 - 2 weeks
         Latest modification 08 July 2021
         Tabs 4 & 5 implemented 05 to 14 April 2022

This is a monolithic program, which operates through an Xwindows GUI. Code for
the 'estoninho' attack avoidance algorithm is not included with this version. 
From the directory in which 'nav.c' resides:

                       location of the X-windows X11 graphics library
                       |                name of the executable output file
                       |                |
to compile: gcc nav.c -L/usr/X11R6/lib -o nav -lX11 -lm
                                               |     |
  link to X-windows X11 graphics objects library     |
  link to mathematics objects library for sin, cos etc.

to run: ./nav       [runs program as a self-contained unit]
        ./nav -o    [runs program sending flight commands to stdout]
        ./nav -io   [also accepting input of real-world lat/lng from stdin]

The file routes.txt and its corresponding waypoints data files routes00.txt
must be present in the same directory as the 'nav' executable.

NOTE: For real-world navigation, this program must be run embedded within a
dedicated computer under a minimal operating system. On a normal operating
system, processing can be interrupted in a way that can cause any iteration of
the program to remain unfinished. This can cause output commands to be missing
from the output stream and the map or data display to blank out momentarily.

Discourse on this program at: https://robmorton.website/navigation/nav.html */

#include <X11/Xlib.h>   // contains the X11 grapical drawing functions
#include <stdio.h>      // input output to/from the console and keyboard
#include <stdlib.h>     // standard functions for the 'C' language
#include <string.h>     // for initialising strings as character arrays
#include <unistd.h>     // contains the function usleep()
#include <math.h>       // sin() cos() asin() atan() atan2() etc
#include <time.h>       // to measure elapsed time since last pass

#define WHITE 0xFFFFFF  // colour white for 'selected item' lettering
#define GREY 0x999999   // colour grey for 'unselected item' lettering
#define BLEY 0xBBCCDD   // colour blue-grey for 'instructions' lettering
#define DARK 0x333333   // colour for menu item background
#define BLACK 0x000000  // colour black for clearing graph areas
#define YELLOW 0xFFFF00 // colour yellow for menu titles and GPS guide bars
#define YELL 0xAAAA00   // duller yellow for scroll bars
#define GREEN 0x00FF00  // colour green for graphical traces and messages
#define RED 0xFF0000    // colour red for graphical traces and messages

#define LM 17   // left margin of application
#define DM 110  // LM + 105 left margin of waypoint and aircraft data display
#define BW 59   // button width + inter-button space [pixels]
#define BY 362  // vertical datum of the tops of the row of control buttons
#define BH 21   // button height [pixels]
#define TL 63   // base height for the top line of the text within a Tab
#define IL 18   // inter-line drop for text

#define pi 3.14159265358979323846264338327950288      // circular constant π
#define TwoPi 6.28318530717958647692528676655900576   // 2π [360 degrees]
#define HalfPi 1.57079632679489661923132169163975144  // π/2 [90 degs]

// The 'PolarLimit' is 1 second of arc less than a right-angle.
#define PolarLimit 1.57050543858623089763516774317834

#define MPR 6366197.723857773  // metres per mean Great Circle radian [Erad]
#define DPR 57.2957795141996   // number of degrees per radian
#define RPD 0.01745329252      // number of radians per degree
#define SPR 206264.806247      // seconds of arc per radian 648000/π

#define WPR 100000    // max range of waypoint influence [metres]
#define StlHgt 100    // stealth height above terrain [in metres].
#define MaxSpd 1000   /* metres/second [metric knots] mk

Note: real flights along the example routes provided in this demo, especi-
ally intercontinental flights, take a long time. To reduce flight time, I
have used a fictitious aircraft with a top speed of 1000 m/s {3600 km/h,
2237 mph, 1944 knots]. Mach 1 = 343 m/s = 1235 km/h = 767 mph = 667 knots.
The aircraft does however slow for the turn at each waypoint and its take
off and landing speed is a mere 75 mk = 270 kph = 168 mph = 146 knots. For
comparison: the cruising speed of the Boeing 787-9 Dreamliner is 254 m/s
= 913 km/h =  567 mph = 493 knots. */

// MAP IS 300 BY 300 PIXELS
#define TopLim 53     // top map limit for displaying radials at turns
#define BotLim 348    // bottom map limit for displaying radials at turns
#define RgtLim 480    // max right where permitted to write on map
#define LftLim 184    // max left where permitted to write on map
#define LndDst 20000  // allow a 20 km approach and landing run
#define MWP 50        // maximum possible number of waypoints per route
#define TrnRad 10000  /* radius of the aircraft's ideal turning circle
                         of 10 kilometres [expressed in metres] */

#define MinSpd 75  /* Aircraft's take-off and landing speed: 75 metres/second
                      = 270 kilometres per hour = 168 miles per hour = 146
                      knots. [1 m/s = 3.6 kph = 2.23694 mph = 1.94384 kts. */

/* One second of arc represents a Great Circle distance of 30.864197531
   metres. A 32-bit integer can express a bipolar longitude to a precision
   of 0.009313226 metre [about 1cm]. The old 24-bit integer could express
   a bipolar longitude to a precision of 2.384185791 metres. */

Display *XD;     // pointer to an X11 display device
Window XW;       // reference for the window created for this program
GC XG;           // X11 graphics context within which to write and draw
FILE *FI, *FO;   // handles for input.txt & output.txt

int
  ICS[6] = {     // SELECTABLE ITERATION COUNTS FOR MAP UPDATES 
    49,          // for update every 5 seconds        50
    19,          // for update every 2 seconds        20
     9,          // for update every second           10
     4,          // for update every 500 milliseconds  5
     1,          // for update every 200 milliseconds  2
     0           // for update every 100 milliseconds  1
  }, IC = 0,     // Iteration Counter
     IP = 2,     // iteration period index number [default: 1 second]
     JC = 0,     // Fixed 1-second iteration counter

  // MANAGEMENT OF GEOGRAPHIC FEATURES AND HEIGHT PLOTS
  Hidx[100],     // index numbers of geographic features for Hoare Sort
  Hdst[100],     // distances of geographic features for Hoare Sort
  gf[16],        // refs to current waypoint's associated geographic features
  T4P[436],      // to hold 436 plots [extent of x-axis in pixels]

  // TAB AND CONTROL BUTTON MANAGEMENT
  TB = 0,        // number of the currently displayed Tab [0 to 7]
  T3BS,          // Tab 0 control button states
  T0BS,          // Tab 1 control button states
  T6BS = 8,      // Tab 4 control button states [default = SIMUlated input]
  T7BS,          // Tab 5 control button states
  T4BS = 2,      // Tab 6 control button state [default AUTO is bright]
  T4PC = 0,      // Tab 6's plot counter

  // LOGIC FLAGS [This lot could be done as single-bit switches]
  InFlg = 0,     // 1:input of Lat Lng from 'stdin' enabled; 0:disabled
  StdOut = 0,    // 1; output of flight commands to 'stdout' enabled
  FileOut = 0,   // 1; output of flight commands to 'output.txt' enabled
  Flag = 0,      // 0:suppress display of a map item; 1:permit display
  ToFlag = 0,    // TO/FROM flag for the waypoint encounter
  Landing = 0,   // 1:when aircraft is in final landing phase
  RIGHT = 0,     // 1: right turn; 0: left turn at current waypoint
  ORD = 0,       // 1: first in route; 2: last in route; 0: mid route
  RunState = 0,  // 0: STOP, 1: GO
  Ready = 0,     // 0: LOADING, 1: LOADED [READY]
  Gauto = 1,     // 1:geoid selection on automatic; 0:manual
  Wind = 0,      // 0:wind off; 1:wind on
  GFL = 0,       // 'all geographic features loaded' flag: 1:all loaded

  // ROUTES AND WAYPOINTS
  Route = 1,     // List Number of selected route
  TNR = 0,       // current total number of routes
  TGF = 0,       // total number of features in current route
  cgf = 0,       // index number of current geographic feature
  rgf = 0,       // number of associated geographic features for current waypnt
  TWP = 0,       // total number of waypoints loaded
  LWP = 0,       // index number of the last waypoint in the route
  pwp = 0,       // index number of previous waypoint
  cwp = 0,       // index number of current waypoint
  nwp = 0,       // index number of next waypoint

  // MOUSE AND GRAPHICS
  mX = 0,        // x-coord of mouse click 
  mY = 0,        // y-coord of mouse click
  MW = 0,        // mouse wheel variable: 1:scroll up, 2:scroll down
  MB = 0,        // vertical bias for routes list in Tab 1
  LB = 0,        // vertical bias for waypoints list in Tab 2
  NL = 0,        // line number of input commands
  LN = 0,        // line number of output commands

  // MISCELLANEOUS
  i = 0, j = 0,  // universal loop variables
  VinErr = 0,    // Vincenty functions: error code.
  IC1 = 0,       // Maximim iterations seen for Vincenty Direct method 
  IC2 = 0,       // Maximim iterations seen for Vincenty Inverse method 
  MapSize = 3,   // 0:500 1:400 2:300 3:200 4:100 5:50 [km square]
  Geoid = 0,     // List Number of the selected geoidic ellipsoid
  CM = 0,        // index number of current message
  MC = 0;        // colour of current message

double
  MS[6] = {      // FOR TIMING & ELAPSED TIME COMPUTATIONS
    0.0006,      // 500 x 500 Kilometres per metre (3/5)/1000
    0.00075,     // 400 x 400 Kilometres per metre (3/4)/1000
    0.001,       // 300 x 300 Kilometres per metre 1/1000
    0.0015,      // 200 x 200 Kilometres per metre (3/2)/1000
    0.003,       // 100 x 100 Kilometres per metre 3/1000
    0.006        //  50 x  50 Kilometres per metre 6/1000
  }, ms = 0,     // variable to hold the selected one of the above

  ETS[6] = {     // SELECTABLE UPDATE PERIODS [IN SECONDS]. Map updated every:
    5.00,        // 5 seconds
    2.00,        // 2 seconds
    1.00,        // 1 second
    0.5,         // half second
    0.2,         // 200 milliseconds
    0.1          // 100 milliseconds
  }, ET = 0,     // elapsed time [seconds] since last pass of updtTrack()

  RH[100][2],    // for up to 100 origin & destination runway headings

  // VINCENTY FORWARD AND INVERSE DISTANCE AND BEARING METHODS
  FwdAzi = 0.0,  // forward azimuth: bearing of waypnt as viewed from aircraft
  BakAzi = 0.0,  // back-azimuth: bearing of aircraft as viewed from waypoint
  VinDst = 0.0,  // ellipsoidal distance observer to observed [metres]
  VinLat = 0.0,  // returned latitude of the observed [radians]
  VinLng = 0.0,  // returned longitude of the observed [radians]
  EquRad = 0.0,  // equitorial radius of the Earth in metres
  FlatF = 0.0,   // flattening factor [depends on which geoid is selected]
  EquPol = 0.0,  // equitorial radius x EquPol = polar radius
  PolDst = 0.0,  // maximum permitted distance short of antipodal
  FwdBrg = 0.0,  // bearing of current waypoint from aircraft [radians]
  BakBrg = 0.0,  // bearing of aircraft from current waypoint [radians]
  WptDst = 0.0,  // distance between aircraft and current waypoint [in metres]

  // AIRCRAFT'S REQUIRED POSITION AND ATTITUDE
  InRad = 0.0,   // current waypoint's inbound radial [radians]
  OutRad = 0.0,  // current waypoint's outbound radial [radians]
  PreDst = 0.0,  // previous distance from current waypoint
  ReqHdg = 0.0,  // aircraft's required heading [in radians]
  ReqSpd = 0.0,  // aircraft's required air speed [metres per second]

  // AIRCRAFT'S COMMANDED POSITION AND ATTITUDE
  CmdHdg = 0.0,  // heading command to be sent to autopilot
  CmdHgt = 0.0,  // height at which the aircraft is commanded to fly
  CmdSpd = 0.0,  // commanded air speed in metres per second
  CmdROT = 0.0,  // aircraft's required change in heading this iteration
  CmdROC = 0.0,  // aircraft's commanded rate of climb
  CmdAtt = 0.0,  // Pitch Command
  CmdBnk = 0.0,  // Roll Command

  // AIRCRAFT'S ACTUAL POSITION AND ATTITUDE
  AirLat = 0.0,  // aircraft's latitude
  AirLng = 0.0,  // aircraft's longitude
  AirHdg = 0.0,  // aircraft's actual heading
  AirHgt = 0.0,  // aircraft height [metres]
  AirAtt = 0.0,  // Actual Pitch
  AirBnk = 0.0,  // Actual Roll

  // RELATIONSHIP BETWEEN AIRCRAFT AND PREVIOUS, CURRENT & NEXT WAYPOINTS
  MapFwd = 0.0,  // bearing of next or prev waypoint from aircraft
  cwpLat = 0.0,  // latitude of current waypoint
  cwpLng = 0.0,  // longitude of current waypoint
  pwpHgt = 0.0,  // height of previous waypoint
  cwpHgt = 0.0,  // height of current waypoint
  nwpHgt = 0.0,  // height of next waypoint
  PFD = 0.0,     // full distance between current & previous waypoint
  PHD = 0.0,     // half-distance between current & previous waypoint
  NFD = 0.0,     // full distance between current & previous waypoint
  NHD = 0.0,     // half-distance between current and next wayppoint
  HHDP = 0.0,    // half-height-diff between current & previous waypoints
  HHDN = 0.0,    // half-height-difference between current & next waypoints
  DesDst = 0.0,  // dist from touchdown at which to begin glide slope descent
  CtrDst = 0.0,  // distance of centre of turning circle from waypoint
  CtrBrg = 0.0,  // bearing of centre of turning circle from waypoint
  TanDst = 0.0,  // dist from waypnt at which InRad/OutRad touch turning circle
  CtrLat = 0.0,  // latitude of centre of turning circle
  CtrLng = 0.0,  // longitude of centre of turning circle
  HgtH1 = 0.0,   // height [metres above sea level] of receding waypoint
  HgtH2 = 0.0,   // height [metres above sea level] of approaching waypoint
  Hgt_D = 0.0,   // distance [km] between receding & approaching waypoints
  HgtBD = 0.0,   // distance from touchdown at which to begin descent

  // WIND
  WndBrg = 0.872664626,  // default wind blowing TO not FROM brg 50 degrees
  WndSpd = 5.0,  // default wind speed 5 metres/second [9.72 knots]
  ReqOff = 0.0;  // heading offset required to compensate for wind


/* NAMES AND OTHER DATA FOR THE CURRENTLY AVAILABLE ROUTES
   Runway headings are needed only for the first and last waypoints in a
   route; namely, the origin and destination airfields. These are therefore
   stored [in radians clockwise from true North] in a 2-dimentional array
   RH[][], here together with the route names. */

char
  RN[100][80],  // up to 100 route names of up to 80 chars each
  UO[16][80],   // to hold screen full of intput commands
  OU[16][80];   // to hold screen full of output commands

/* The following structure contains the basic specification of a Geographic
   Feature [GF]. A geographic feature is simply some recognisable feature in
   the navigational landscape. It could be a city, mountain, estuary and so
   on. The data file for an air route contains details of many geographic
   features that are situated on or close to the route. */

#define MGF 100   // maximum possible number of geographic features per route
struct feature {  // to contain information for a Geographic Feature:
  char Name[21];  // name [up to 20 characters only allowed]
  int wpi,        // index number of corresponding waypoint data
      Hgt;        // waypoint's elevation above sea level in metres
  double
    Lat,          // waypoint's latitude in radians
    Lng;          // waypoint's longitude in radians
} GF[MGF];        // array of up to MGF geographic feature data strutures

/* Some of the geographic features contained in a route's data file are de-
   signated as waypoints. These are the points on the ground [usually loc-
   ations of navigational aids] through which the route passes, and hence,
   over which the aircraft flies. The waypoints are, conceptually, joined
   together by the legs of the route. The following structure contains the
   extra information that needs to be known about a geographic feature used
   as a waypoint. */

struct waypoint {
  int
    pgf,     // GF index number of previous waypoint
    cgf,     // GF index number of current waypoint
    ngf,     // GF index number of next waypoint
    ORD,     // 1: first in route; 2: last in route; 0: mid route
    RIGHT,   // 1: right turn at current waypoint; 0: left turn
    rgf,     // number of associated geographic features
    gf[16];  // references to up to 7 associated geographic features
  double     // BEARINGS IN RADIANS, DISTANCES IN METRES:
    InRad,   // bearing from this waypoint to previous waypoint
    OutRad,  // bearing from this waypoint to next waypoint
    PFD,     // full distance between this and previous waypoint
    PHD,     // half-distance between this and previous waypoint
    NFD,     // full distance between this and next waypoint
    NHD,     // half-distance between this and next waypoint
    CtrDst,  // Distance of centre of turning circle from waypoint
    CtrBrg,  // Bearing of centre of turning circle from waypoint
    TanDst,  // Dist from wpt at which inRad/OutRad touch turning circle
    CtrLat,  // latitude of centre of turning circle
    CtrLng;  // longitude of centre of turning circle
} WP[MWP];   // array of up to MWP waypoint data strutures




void helpPage(char *S) {  // DISPLAY THE HELP PAGE IN THE DEFAULT WEB BROWSER
  int i, s = strlen(S);   // length of 'nav.html' file path
  char C[64] = "xdg-open https://robmorton.website/"; // 35 characters
  for(i = 0; i < s; i++)  // add the file path for 'nav.html'
    C[i+35] = S[i];       // onto the end of the base URL
  C[i+35] = ' ';          // space after the end of the command
  C[i+36] = '&';          // place command in 'background' mode
  C[i+37] = 0;            // string-terminating NULL character
  printf("%s\n",C);       // verification print in terminal window
  system(C);              // issue the command to the system
}




void showMsg(int m, int colour) {  // DISPLAY ADVICE AND ERROR MESSAGES
  CM = m; MC = colour;             // preserve current message number
  if(TB != 0) return;              // messages only displayed in Tab 0
  char *MSG[] = {
    "",
    "01 GDP observer's lat too close to north pole.",
    "02 GDP observer's lat too close to south pole.",
    "03 GDP observer's longitude out of range.",
    "04 GDP observed is at observer's antipode.",
    "05 GDP Didn't converge within 100 iterations.",
    "06 GIP aircraft's latitude > 89°59'00N.",
    "07 GIP aircraft's latitude > 89°59'00S.",
    "08 GIP observed's latitude > 89°59'00N.",
    "09 GIP observed is at observer's antipode.",
    "10 GIP Reference longitude > 180° E or W.",
    "11 GIP Observed's longitude > 180° E or W.",
    "12 GIP Didn't converge within 100 iterations.",
    "13 GIP Observer and observed are antipodal.",
    "",
    "15 The aircraft has reached its destination.",
    "16 Loading route names...",
    "17 Loading waypoints...",
    "18 Flight ready to depart.",
    "19 Flight in progress.",
    "20 Flight paused.",
    "21 Can't open route names file 'routes.txt'.",
    "22 Can't open waypoints data file for this route."
  };
  XSetForeground(XD,XG,DARK);  // clear the message area
  XFillRectangle(XD,XW,XG,LM+48,331,418,BH);
  XSetForeground(XD,XG,colour); 
  XDrawString(XD,XW,XG,LM+54,345,MSG[m],strlen(MSG[m]));
}





// ---------------------GENERAL ANCILLARY FUNCTIONS---------------------------

void clearTabArea() {  // CLEAR TAB AREA
  XSetForeground(XD,XG,BLACK);
  XFillRectangle(XD,XW,XG,LM,38,484,383);
}


// CLEAR MAP AREA. Called from 1 place each in T3show(), T3updt() & T4updt()
void clearMapArea() {
  XSetForeground(XD,XG,0x182448);  // map background colour dark blue
  XFillRectangle(XD,XW,XG,182,50,301,301);
}


// MAKE ANGLE LIE BETWEEN 0 AND 2π [Called generally throughout the program]
double RatAng(double a) {
  while(a >= TwoPi) a -= TwoPi; 
  while(a < 0) a += TwoPi; 
  return a;
}


// MAKE ANGLE LIE BETWEEN -π AND +π  [Called generally throughout the program]
double BipAng(double a) {
  while(a > pi) a -= TwoPi; while(a <= -pi) a += TwoPi; return a;
}



/* Formats the latitude or longitude of the current Geographic Feature,
   supplied as an integral number of seconds of arc, within a 10-character
   array ready for display by the T3updt() function. */

char *RadToDMS(double d, int flag) {  // convert radians to DEG:MIN:SEC format
  int x = rint(d * SPR);              // convert radians to integral seconds
  static char C[11] = "000:00:00N";   // template for display string

  if(flag == 0)    // If formatting a latitude
    if(x < 0) {
      C[9] = 'S';
      x = -x;
    } else
      C[9] = 'N';
  else             // else formatting a longitude
    if(x < 0) {
      C[9] = 'W';
      x = -x;
    } else
      C[9] = 'E';

  int y = x / 60,              // integral minutes
      z = x % 60;              // remaining seconds
  C[7] = (char)(z / 10 + 48);  // integral 10s of seconds
  C[8] = (char)(z % 10 + 48);  // integral remaining seconds
  x = y / 60;                  // integral degrees
  z = y % 60;                  // remaining minutes
  C[4] = (char)(z / 10 + 48);  // integral 10s of minutes
  C[5] = (char)(z % 10 + 48);  // integral remaining minutes
  y = x / 10;                  // 10s of degrees
  C[2] = (char)(x % 10 + 48);  // integral remaining degrees
  C[1] = (char)(y % 10 + 48);  // integral remaining 10s of degrees

  if(flag == 0)                  // if a latitude,
    C[0] = ' ';                  // prefix it with a space
  else                           // else it's a longitude, so show the
    C[0] = (char)(y / 10 + 48);  // integral remaining 100s of degrees
  return &C[0];                  // address of the output string
}



/* CONVERT AN 'int x' TO AN l-DIGIT NUMERIC STRING WITH LEADING
   ZEROS IN ARRAYS[]: Called from 1 place each in editSI() and
   openDoc() and from 2 places each in editDT() and ixtotxt(). */

char *showInt(int x, int l) {
  static char S[33];          // to hold the string version of the number
  if(l > 11)                  // limit length to 11 characters
    l = 11;                   // as a safety precaution
  for(int i = 0; i < l; i++)
    S[i] = '0';               // fill the whole field with zeros
  S[l] = (char)0;             // terminating NULL character
  if(x > 0) {                 // if the number is non-zero:
    int i = l;                // total of y possible digits in the number
    while(x > 0 &&            // while there are more digita to process
      i > 0) {                // work backwards from [8] to [0]
      S[--i] = (char)         // Store remainder after dividing the number
      (x % 10 + 48);          // by 10 as a numeric character in the array. 
      x /= 10;                // divide the number by 10 [unrounded]
    }                         // and loop back
  }                           // containing the formatted number.
  return S;                   // Address of start of char array number
}



/* Places the HEIGHT of the current Geographic Feature, supplied as an
   integer, right-justified with leading spaces, within a 10-character array
   ready for display by the T3updt() function. */

char *showHeight(int x) {
  static char            // use the same character array for each call
  C[11] = "        0+";  // template for display format

  if(x < 0) {    // if the number is negative
    C[9] = '-';  // install the minus-sign character
    x = -x;      // and make the number positive
  } else         // otherwise
    C[9] = '+';  // install the plus-sign character

  for(i = 0; i < 9; i++)  // set all 9 characters
    C[i] = ' ';           // as spaces initially

  if(x == 0)     // if the number is simply zero
    C[8] = '0';  // just set final numeric charcter as a zero
  else {         // else, if the number is non-zero:
    i = 9;       // total of 9 possible digits in the number

    while(x > 0 &&     // while there are more digita to process
      i > 0) {         // work backwards from [8] to [0]
      C[--i] = (char)  // Store what remains after dividing the number by 
      (x % 10 + 48);   // 10 as a numeric character in the array. 
       x /= 10;        // divide the number by 10 [unrounded]
    }                  // and loop back

  }
   return &C[0];  // return the address of the output string
}



/* Places the BEARING or RADIAL of the current Geographic Feature, supplied
   as a double in radians, right-justified with leading spaces, within a 3-
   character array ready for display by the T3updt() function. The supplied
   angle is usually bipolar, so it is first rationalised into the range 0-360
   degrees. */

char *showRat(double d) {
  int x = rint(RatAng(d) * DPR);  // convert to rounded integral degrees
  static char C[4] = "000";       // 3-digit display string template

  for(i = 0; i < 3; i++)  // make all 3 digits
    C[i] = '0';           // into '0' characters

  i = 3;              // possibe maximum of 3 characters to deal with
  int y = 0;          // assume 3 characters to start with
  if(x < 100) y = 1;  // if number less than 100, 1 less char to deal with
  if(x < 10) y = 2;   // if number less than 10, 2 less chars to deal with
  while(i > y) {      // while there's yet [another] character to deal with
    C[--i] = (char)   // Write to appropriate position in the 3-char array
    (x % 10 + 48);    // the remainder augmented into an ASCII character.
    x /= 10;          // divide the number by 10 [truncated]
  }
  return &C[0];  // return the address of the output string [char array]
}



/* Convert a number, supplied in radians, into a string that shows a signed
   angle of from 0 to 99 degrees to 3 decimal places. This function is used
   only for converting the aircraft's commanded rate of turn [CmdROT]. */

char *showBip(double d) {        // Convert the supplied radians to rounded
  int x = rint(1000 * d * DPR);  // integral thousandths of a degree.
  static char C[8] = "00.000+";  // decimal degrees data template

  for(i = 0; i < 6; i++)  // Put a '0' character in all but the final 
    C[i] = '0';           // position of the character array.

  C[2] = '.';    // put the decimal point in position '2'
  if(x < 0) {    // if the number is negative
    C[6] = '-';  // put a minus sign in the final position
    x = -x;      // and make the number positive
  } else         // otherwise
    C[6] = '+';  // just put a plus sign in the final position

  i = 6;         // maximum of 6 possible characters to deal with
  int y = 0;     // at first assume that all 6 must be dealt with
  if(x < 10000)  // if less than 10 degs,
    y = 1;       // 1 less digit to deal with
  if(x < 1000)   // if less than 1 deg,
    y = 2;       // 2 less digits to deal with
  if(x < 100)    // if less than .1 deg,
    y = 3;       // 3 less digits to deal with
  if(x < 10)     // if less than .01 deg,
    y = 4;       // 4 less digits to deal with

  while(i > y) {    // while there's yet another character to deal with
    if(--i == 2)    // skip over the decimal
      --i;          // point's position
    C[i] = (char)   // Write to appropriate position in 7-char array
    (x % 10 + 48);  // the remainder augmented into an ASCII character.
    x /= 10;        // divide the number by 10 [truncated]
  }
  return &C[0];  // return the address of the output string [char array]
}



/* Places the name of a Geographic Feature right-justified within a
   20-character array ready for display by the T3updt() function. */

char *showName(char *s) {
  static char 
    C[21] = "                    ";  // re-usable char. array

  for(i = 0; i < 20; i++)  // clear character array
    C[i] = ' ';            // to spaces
  int x = 20 - strlen(s);  // starting character for waypoint name
  for(i = 0; i < 20; i++)  // for each character of the name
    C[i + x] = *(s + i);   // put it in the C[] array right justified
  return &C[0];  // return the address of the output string [char array]
}





//------------------VINCENTY METHODS FOR DISTANCE & BEARING-------------------

/* Solution to the Geodetic DIRECT Problem by T. Vincenty: a modified Rains-
   ford's Method with Helmert's elliptical terms. Effective in any azimuth
   and at any distance short of antipodal. Latitudes and longitudes in radians
   positive north and east. Azimuths in radians clockwise from north. Distance
   in metres.

   Transcoded by the author from a FORTRAN version
   by Lcdr L Pfeifer NGS Rockville MD 20FEB75.

   Error Code values:
   0  no error
   1  observer's latitude too close to north pole
   2  observer's latitude too close to south pole
   3  observer's longitude out of range
   4  suspect that observed is at observer's antipode
   5  Vincenty iterations excessive [equations didn't converge] */

int VSGDP(        // VINCENTY SOLUTION TO THE GEODETIC DIRECT PROBLEM
  double RefLat,  // latitude of observer
  double RefLng,  // longitude of observer
  double Brg,     // forward azimuth from observer to observed
  double Dst      // distance between observer and observed
  ) {
  if(RefLat > PolarLimit) return 1;          // RefLat > 89°59'00"N
  if(RefLat < -PolarLimit) return 2;         // RefLat > 89°59'00"S
  if(RefLng > pi || RefLng < -pi) return 3;  // RefLng > 180° E or W
  if(Dst > 19996296.296867833) return 4;     // too close to antipodal
  double 
    T = EquPol * tan(RefLat),  // tangent of observer's reduced latitude
    S = sin(Brg),              // sine of forward azimuth
    C = cos(Brg);              // cosine of forward azimuth

  /* Zero the back-azimuth. Then, provided the
  forward azimuth is not exactly east or west: */

  BakAzi = 0;
  if(C != 0)
    BakAzi = 2 * atan2(T,C);

  double 
    A = 1 / sqrt(T * T + 1),  // cosine of observer's reduced lat
    B = T * A,                // sine of observer's reduced lat
    D = A * S,                // sine of 'a'
    F = 1 - D * D,            // cos-squared-a
    x = sqrt((1 / EquPol / EquPol - 1) * F + 1) + 1;
  x = (x - 2) / x;
 
  double
    X = x * x,
    d = (0.375 * X - 1) * x;

  T = Dst / (EquPol * EquRad * (X / 4 + 1) / (1 - x));

  double
    y = T,  // initialise y to tangent to observer's reduced latitude
    H,      // sin(y)
    I,      // cos(y)
    J,      // cos(z)
    e,
    Y;

  /* LOOP UNTIL NO FURTHER SIGNIFICANT CHANGE IN y. Note that 'ic' is
     an iteration counter, which cuts off the loop after 100 iterations
     in case y does not converge. 0.5e-13 is the convergence limit for
     a distance accuracy to within 1 mm on the Earth's surface. */

  int ic = 0;  // zero the iteration counter

  do {         // loop for ic values 0 to 99
    H = sin(y);
    I = cos(y);
    J = cos(BakAzi + y);
    e = J * J * 2 - 1;
    Y = y;     // preserve last time's value of 'y'
    y = T      // compute this time's value of 'y'
      + (((H * H * 4 - 3) * (e + e - 1) * J * d / 6 + e * I) * d / 4 - J)
      * H * d;
  } while(fabs(y - Y) > 0.5e-13 && ++ic < 100);

  /* NOTE: the above loop was implemented with a for()-loop containing an
     if()-break; and also as a while()-loop. But the above do-while() ver-
     sion was found to be a bit faster and speed is of the essence here. */

  if(ic > 99) return 5;   /* Exit if 'y' didn't converge sufficiently
                             within the 100 iterations allowed. */

  if(ic > IC1) IC1 = ic;  /* capture maximum iterations that have occurred

  NOTE: even for routes with long transoceanic stretches, the maximum num-
  ber of iterations of the above do-while() loop has been logged as only 3.
  */
  BakAzi = A * I * C - B * H;
  VinLat = atan2( B * I + A * H * C, 
                  EquPol * sqrt(D * D + BakAzi * BakAzi) );

  x = atan2(H * S, A * I - B * H * C);
  double c = ((4 - 3 * F) * FlatF + 4) * F * FlatF / 16;
  VinLng = RefLng + x - (1 - c) * ((e * I * c + J) * H * c + y) * D * FlatF;
  BakAzi = atan2(D, BakAzi) + pi;
  return 0;  // success
}



/* Solution to the Geodetic INVERSE Problem by T. Vincenty: a modified Rains-
   ford's Method with Helmert's elliptical terms. Effective in any azimuth
   and at any distance short of antipodal. Latitudes and longitudes in radians
   positive north and east. Azimuths in radians clockwise from north. Distance
   in metres.

   Transcoded by the author from a FORTRAN version
   by Lcdr L Pfeifer NGS Rockville MD 20FEB75.

   Error Code values:
    0  no error
    6  aircraft's latitude > 89°59'00"N
    7  aircraft's latitude > 89°59'00"S
    8  observed's latitude > 89°59'00"N
    9  suspect that observed is at observer's antipode
   10  Reference longitude > 180° E or W
   11  Observed's longitude > 180° E or W
   12  More than 100 iterations without converging
   13  Observer and observed are antipodal */

int VSGIP(        // VINCENTY SOLUTION TO THE GEODETIC INVERSE PROBLEM
  double RefLat,  // latitude of the observer
  double RefLng,  // longitude of the observer
  double ObsLat,  // latitude of the observed
  double ObsLng   // longitude of the observed
  ) {
  if(RefLat > PolarLimit) return 6;        //aircraft's latitude > 89°59'00"N
  else if(RefLat < -PolarLimit) return 7;  //aircraft's latitude > 89°59'00"S
  else if(ObsLat > PolarLimit) return 8;   //observed's latitude > 89°59'00"N
  else if(ObsLat < -PolarLimit) return 9;  //observed's latitude > 89°59'00"S

  // Reference longitude > 180° E or W
  else if(RefLng > pi || RefLng < -pi) return 10;

  // Observed's longitude > 180° E or W
  else if(ObsLng > pi || ObsLng < -pi) return 11;

  double
    A = ObsLng - RefLng,       // longitude difference
    B = EquPol * tan(RefLat),  // tan(reduced latitude of observer)
    C = EquPol * tan(ObsLat),  // tan(reduced latitude of observed)

    /* Compute the sine and cosine of the reduced latitude of the ob-
    server and the cosine of the reduced latitude of the observed. */
    D = 1 / sqrt(1 + B * B),
    E = D * B,
    F = 1 / sqrt(1 + C * C),

    T = A,  // prime T before entering the loop
    O,      // holds previous value of T for each iteration
    G, H,
    I, J,   // sine(T) and cos(T)
    K, L,   // sin(S) and cos(S)
    M, e, y, c; 

  VinDst = D * F;       // prime ellipsoidal dist (observer to observed)
  BakAzi = VinDst * C;  // prime the back-azimuth
  FwdAzi = BakAzi * B;  // prime the forward azimuth

  /* LOOP UNTIL NO FURTHER SIGNIFICANT CHANGE IN T. Note that 'ic' is
     an iteration counter, which cuts off the loop after 100 iterations
     in case T does not converge. 0.5e-13 is the convergence limit for
     a distance accuracy to within 1 mm on the Earth's surface. */

  int ic = 0;  // zero the iteration counter

  do {         // loop for ic values 0 to 99
    I = sin(T);
    J = cos(T);
    B = F * I;
    C = BakAzi - E * F * J;
    G = sqrt(B * B + C * C);
    H = VinDst * J + FwdAzi;
    y = atan2(G, H);
    K = VinDst * I / G;
    L = 1 - K * K;
    M = FwdAzi + FwdAzi;
    if(L > 0) M = H - M / L;
    e = M * M * 2 - 1;
    c = ((4 - 3 * L) * FlatF + 4) * L * FlatF /  16;
    O = T;  // save old T before computing its new value
    T = (1 - c) * ((e * H * c + M) * G * c + y) * K * FlatF + A;
  } while(fabs(O - T) > 0.5e-13 && ++ic < 100);
  /*      ---------------------
                    |
  Falls through [terminates the loop] on whichever iteration [up to 100]
  at which the error in the longitudinal difference between observer and
  observed is less than 0.5x10^-13 of an Earth-radian, which is 1 mm! */

  if(ic > 99) return 12;  /* T didn't converge sufficiently
                             within 100 iterations, so exit. */
  if(ic > IC2) IC2 = ic;  /* capture maximum iterations that have occurred

  NOTE: Even for routes with long transoceanic stretches, the maximum num-
  ber of iterations of the above do-while() loop has been logged as only 5.

  T converged OK, so now compute the distance 'VinDst'. */
  T = sqrt((1 / EquPol / EquPol - 1) * L + 1) + 1;
  T = (T - 2) / T;
  c = (T * T / 4 + 1) / (1 - T);
  T = (0.375 * T * T - 1) * T;
  VinDst = ((((G * G * 4 - 3) * (1 - e - e) * M * T / 6 - e * H) 
         * T / 4 + M ) * G * T + y ) * c * EquRad * EquPol;

  /* If distance calculation be indeterminate then the locations
  of the observer and observed are either antipodal or coincident;
  else, compute the forward and back azimuths. */

  if(isnan(VinDst) > 0) {  // if the distance be indeterminate, then
    if(fabs(A) > 1)        // if the observer and observed are antipodal
      return 13;           // return errpr code 13 [invalid]
    VinDst = 0;            // the points must therefore be coincident
  } else {                 // otherwise the distance was determined
    FwdAzi = atan2(B, C);  // so compute forward and back azimuths
    BakAzi = atan2(D * I, BakAzi * J - E * F) + pi;
  }
  if(VinDst > MPR)         // Rob's addition: not interested in
    return 0;              // in features beyond one Earth-radian.
  if(VinDst < 0)           // Ensure that distances are always positive,
    VinDst = -VinDst;      // just to be on the safe side.
  FwdAzi = RatAng(FwdAzi); // Express the forward and back azimuths
  BakAzi = RatAng(BakAzi); // numerically between 0 and 2π radians.
  return 0;                // success 
}



// Wrapper for Vincenty functions to automatically display any error messages

int VinChk(int VinErr){
  if(VinErr > 0 && VinErr < 15)
    showMsg(VinErr,RED);
}





// --------GENERAL FLIGHT FUNCTIONS CALLED FROM MORE THAN ONE TAB-------------

/* SET UP HEIGHT COMPUTATION PARAMETERS WHEN THE ToFlag CHANGES STATE
   This only need computing when the landing flag or the to-flag changes
   state. Called only from 3 places in updtTrack() */ 

void ToFrom() {
  if(Landing) {        // if in landing phase [beyond final waypoint]
    HgtH1 = cwpHgt + StlHgt; // height above sea level of final waypoint cwp
    HgtH2 = cwpHgt;    // height above sea level of touchdown point

    /* Standard glide slope angle is 3 degrees. Need to know the distance
    'Hgt_D' from touchdown at which to start the sigmoidal descent profile.
    Hgt_D = (12 / (5 * tan(3deg))) * h = 45.7947280505h [Hgt_D & h are in
    metres] */

    Hgt_D = 45.7947280505 * (HgtH1 - HgtH2);
    HgtBD = LndDst - Hgt_D;  

  } else if(ToFlag) {  // if going 'to' the current waypoint [cwp], 
    Hgt_D = PFD;       // dist [in km] between previous & current waypoints
    HgtH1 = pwpHgt;    // height above sea level of the receding waypoint pwp
    HgtH2 = cwpHgt;    // height above sea level of approaching waypoint cwp

  } else {             // if receding 'from' current waypoint [cwp], 
    Hgt_D = NFD;       // dist [in km] between current & next waypoints
    HgtH1 = cwpHgt;    // height above sea level of receding waypoint cwp
    HgtH2 = nwpHgt;    // height above sea level of approaching waypoint nwp
  }
}




/* INITIALISE THE AIRCRAFT'S ENCOUNTER WITH A NEW WAYPOINT. Sets up the rela-
   tionship between the aircraft and the waypoint with reference to which it
   is about to navigate. This is done just before the start of a flight and
   each time it passes the half-way point between the waypoint from which it
   is receding and the next waypoint in the route. Called from one place each
   in the functions setAircraftAt() and updtTrack(). */

void initEncounter() {
  if(cwp == 0)      // if this is the first waypoint in the route
    pwp = 0;        // the previous waypoint does not exist
  else              // otherwise the previous waypoint's index number
    pwp = cwp - 1;  // is 1 less than this waypoint's index number.

  if(cwp == LWP)    // if this is the last waypoint in the route
    nwp = LWP;      // the next waypoint is itself
  else              // otherwise, the index number of the next waypoint 
    nwp = cwp + 1;  // is 1 + this waypoint's index number

  int
   i = WP[pwp].cgf,  // geographic feature number of current waypoint
   j = WP[cwp].cgf,  // geographic feature number of current waypoint
   k = WP[nwp].cgf;  // geographic feature number of current waypoint

  pwpHgt = GF[i].Hgt;       // set height of previous waypoint
  cwpLat = GF[j].Lat;       // set latitude of current waypoint
  cwpLng = GF[j].Lng;       // set longitude of current waypoint
  cwpHgt = GF[j].Hgt;       // set height of current waypoint
  nwpHgt = GF[k].Hgt;       // set height of next waypoint
  InRad = WP[cwp].InRad;    // get its inbound radial from previous waypoint
  OutRad = WP[cwp].OutRad;  // get its outbound radial to next waypoint
  PFD = WP[cwp].PFD;        // full distance to previous waypoint
  PHD = WP[cwp].PHD;        // half-distance to previous waypoint
  NFD = WP[cwp].NFD;        // full distance to next waypoint
  NHD = WP[cwp].NHD;        // half-distance to next waypoint
  ORD = WP[cwp].ORD;        // waypoints: 1:first; 2:last; 0:all others
  PreDst = 0;               // prime previous distance
  CtrDst = WP[cwp].CtrDst;  // dist to ctr of this waypoint's turning circle
  CtrBrg = WP[cwp].CtrBrg;  // brg of ctr of this waypoint's turning circle
  TanDst = WP[cwp].TanDst;  // dist to where radials touch turning circle
  CtrLat = WP[cwp].CtrLat;  // lat of centre of this wpt's turning circle
  CtrLng = WP[cwp].CtrLng;  // lat of centre of this wpt's turning circle
  RIGHT = WP[j].RIGHT;      // route makes right/left turn at this wpt.
  Landing = 0;              // we don't start in the landing phase!
  ToFlag = 0;               // aircraft initially recedes FROM 1st waypoint
  ToFrom();

  // This block was added 08 July 2021. 
  rgf = WP[cwp].rgf;              // waypoint's number of associated features
  for(int i = 0; i < rgf; i++) {  // Copy the references to the 7 geographic
    gf[i] = WP[cwp].gf[i];        // features associated with this waypoint to
  }                               // current waypoint's features data in gf[].

  /* INITIALIZE THE STEALTH HEIGHT COMMAND CHANNEL FOR THIS ENCOUNTER
  For HEIGHT update, need to establish the half-distances between previous &
  current waypoints 'PHD' and current & next waypoint 'NHD'. Also the half-
  height-difference between the current & previous waypoints 'HHDP' and the
  current & next waypoints 'HHDN'. I also need the BASE height for the calcu-
  lation, which is the absolute height of the lower of the two waypoints in-
  volved in this encounter. Must do by comparing the heights of the current
  waypoint with that of the next or previous waypoint as appropriate. */

  CmdHgt = cwpHgt;               // Initialise the command height.
  HHDP = (double)                // Half height-difference between 
    ((cwpHgt - pwpHgt) / 2);     // current & previous waypoints.
  if(ORD != 2)                   // Provided this isn't last way-
    HHDN = (double)              // point, compute half height diff- 
      ((cwpHgt - nwpHgt) / 2);   // erence between current & next wpts.
  switch(ORD) {                  // If aircraft has just been reset to:
    case 0:                      // -any en-route waypoint, set both 
      CmdHgt = cwpHgt + StlHgt;  // aircraft height & command height to 
      AirHgt = cwpHgt + StlHgt;  // waypoint height + stealth height
      break;
    case 1:                      // -the first waypoint in the route, 
      CmdHgt = cwpHgt + StlHgt;  // waypoint height & command height to 
      AirHgt = cwpHgt;           // set aircraft's initial height to 
      break;                     // waypoint height + stealth height.
    case 2:                      // -the last waypoint in the route, 
      CmdHgt = cwpHgt;           // set both aircraft height & command 
      AirHgt = cwpHgt;           // set both aircraft height & command 
  }
}




/* SET THE AIRCRAFT TO A SPECIFIED WAYPOINT.
   Called from one place initFlightParams(), T0CL()
   and T1CL() and from 3 places in T3click(). */

void setAircraftAt() {
  initEncounter();  // init. an encounter between aircraft and this waypoint
  AirLat = cwpLat;  // set aircraft's initial latitude
  AirLng = cwpLng;  // set aircraft's initial longitude
  AirHgt = cwpHgt;  // set aircraft's initial height
  CmdHgt = AirHgt;  // command height: dynamic attractor for AirHgt
  CmdROT = 0;       // zero the commanded rate of turn
  CmdROC = 0;       // zero the commanded rate of climb
  AirBnk = 0;       // zero the actual roll
  AirAtt = 0;       // zero the actual pitch
  for(int i = 0; i < 436; i++)
    T4P[i] = 0;     // clear Tab6's heights plotter
  T4PC = 0;         // reset to first plot on screen
  if(cwp > 0 && cwp < LWP) {  // if not first or last waypoint in route
    AirHgt += StlHgt;         // add 100 metres to aircraft altitude
    CmdHgt = AirHgt;          // command height: dynamic attractor for AirHgt
  }
  WptDst = 0;               // zero aircraft's dist from current waypoint
  ReqHdg = OutRad;          // set aircraft heading along outbound radial
  CmdHdg = OutRad;          // set required heading along outbound radial
  AirHdg = OutRad;          // set the actual heading of the aircraft
  if(cwp == 0)              // if this is the first waypoint
    CmdHdg = RH[Route][0];  // runway heading of origin airport
  else if(cwp == LWP)       // else if it's the destination waypoint
    CmdHdg = RH[Route][1];  // runway heading of destination airport
  CmdSpd = MinSpd;          // set aircraft stationary
}





// --------------FUNCTIONS PERTAINING TO TAB 0 [ROUTES SELECTOR]--------------


/* Sorts distance array Hdst[] & corresponding index array Hidx[] into ascend-
   ing order of distance. Called only from one place in T0associatedFeatures()
   This function was added 08 July 2021. */

void T0HoareSort(
  int LOW,        // lowest occupied element of the array to be sorted
  int HIGH) {     // highest occupied element of the array to be sorted
  int
    low = LOW,    // set moving low to LOW end of partition
    high = HIGH;  // set moving high to HIGH end of partition

  if(HIGH > LOW) {                      // if the partition contains anything
    int Mid = Hdst[(LOW + HIGH) >> 1];  // get the content of its mid element

    while(low <= high) {  // loop through the array until the indices cross

      /* While distance held in lowest element of Hdst[] < distance held in
      midway element of Hdst[], push lower sort boundary up by one element. */

      while(low < HIGH && Hdst[low] < Mid) low++;

      /* While distance held in highest element of Hdst[] > distance held in
      midway element of Hdst[], pull upper sort boundary down by 1 element. */

      while(high > LOW && Hdst[high] > Mid) high--;

      if(low <= high) {  // if low index <= high index, swap the contents of
        int              // the high & low elements of each of the 2 arrays
        x = Hdst[low]; Hdst[low] = Hdst[high]; Hdst[high] = x;
        x = Hidx[low]; Hidx[low] = Hidx[high]; Hidx[high] = x;

        low++;   // push lower sort boundary up by one element
        high--;  // pull upper sort boundary down by one element
      }
    }
    if(LOW < high) T0HoareSort(LOW,high);  // sort lower partition
    if(low < HIGH) T0HoareSort(low,HIGH);  // sort upper partition
  }
}




/* For all the waypoints in the current route, find its 7 nearest geographic
   features and place their reference numbers in the array gf[] of the way-
   point's data structure [see line 140 in struct waypoint]. One of the 7
   features will] be the waypoint itself [distance zero]. This is done once
   when a new route is loaded. It avoids the map display function T3updt()
   having to re-compute the distances of ALL the waypoints in the route every
   time the aircraft is advanced. Instead, it only has to compute the dist-
   ances of the up to 7 geographic features associated with the current way-
   point. This function was added 08 July 2021. Called from only one place in
   'T0getWayPts()' every time a new route is loaded. */

void T0associatedFeatures() {
  int i, k;
  for(i = 0; i < TWP; i++) {  // for each waypoint in the route:
  for(k = 0; k < TGF; k++) {  // For each geographic feature in this route:
 
    /* Compute the distance of this [the 'k'th] geographic feature
    from the 'i'th waypoint. VSGIP() places the answer [in metres]
    in the global variable 'VinDst'. */

    VinChk(VinErr = VSGIP(
      GF[WP[i].cgf].Lat,  // latitude of the 'i'th waypoint
      GF[WP[i].cgf].Lng,  // longitude of the 'i'th waypoint
      GF[k].Lat,          // latitude of the 'k'th geographic feature
      GF[k].Lng           // longitude of the 'k'th geographic feature
    ));
    Hidx[k] = k;            // index num within struct GF[] of 'k'th feature
    Hdst[k] = (int)VinDst;  // distance of 'k'th feature from 'i'th waypoint
  }                         // end of the for(k) loop

  T0HoareSort(0,TGF-1);  // sort above arrays into ascending order of distance

  for(k = 0; k < 7; k++)
    if(Hdst[k] < 200000)      // if this feature is within 200 km of waypoint
      WP[i].gf[k] = Hidx[k];  // put waypoint's index number 'k' in shortlist
    else break;
  WP[i].rgf = k;  // total number of associated features acquired
  }               // end of the for(i) loop
}




int T0getLatLngHgt(int N, FILE *fp) {  // Called only by T0getWayPts() below
  int x = 0, n = 0;
  for(i = 0; i < N; i++) {  // For the N characters of the lat, lng or hgt
    char c = fgetc(fp);     // input the [next] character from the file
    if(c == '-') n = 1;     // set negative flag if '-' sign encountered
    else
    if(c > 47 && c < 58)  // if it's within the ASCII range of a numeric
      x = x * 10          // accumulate it character by character to
        + (c - '0');      // form an integer
  }
  if(n == 1) x = -x;  // reverse sign if '-' sign was encountered
  return x;           // return lat, long or height as an integer
}




/* Loads the data for all the Geographic Features, pertaining to a newly sel-
   ected route, from its respective route00.txt file into the Geographic Feat-
   ures structure array. Line format of a routes00.txt file [Each line is
   terminated by a 'new line' character.]: "* 186780     780  106 Stansted".
 
   "*" indicates that this geographic feature is a waypoint
   " " absence of a * indicates that this geographic feature is not a waypoint
   " 186780" latitude as an integral number of seconds of arc
   "     780" longitude as an integral number of seconds of arc
   "  106" elevation in metres
   " Stansted" name of geographic feature.

   NOTE: do not put leading zeros in latitudes, longitudes or heights. Put the
   sign [if present] immediately in front of the first digit. Right-justify
   each figure in its field using leading spaces, e.g. "  -1234". 
   Useful ref: https://worldaerodata.com/nav/ for VORs DMEs etc.. */

void T0getWayPts() {                 // Called by T0CL() & initFlightParams()
  showMsg(17,WHITE);                 // show "Loading waypoint data" message
  char S[24] = "route00.txt";        // file name for default route: route00 
  S[5] = (char)((Route / 10) + 48);  // 1st char of route number
  S[6] = (char)((Route % 10) + 48);  // 2nd char of route number
  FILE *fp = fopen(S,"r");           // open selected route file for reading
  if(fp == NULL) {                   // if can't open waypoints data file
     showMsg(22,RED); return;        // display error message and exit
  }
  LWP = -1;                         // prime the 'last waypoint' number
  for(TGF = 0; TGF < MGF; TGF++) {  // For all features in the route...
    char c;                         // for each character input from file
    int x = 0;                      // clear the 'is waypoint' flag
    if((c = fgetc(fp)) == '*')      // if this feature is a waypoint
      x = 1;                        // set the 'is waypoint' flag
    if(feof(fp)) break;             // terminate loop at end of file
    if(x == 1) {                    // if this feature is a waypoint:
      WP[++LWP].cgf = TGF;          // add feature to waypoints list
      GF[TGF].wpi = LWP;            // and the waypoint index number of
    }                               // this feature's waypoint data
    else GF[TGF].wpi = -1;          // else give it  a 'null' index number

    /* Input the latitude, longitude [in integral seconds of arc] and height
    [in metres] as strings from the rout00.txt file, convert them all to
    integers then convert the latitude and longitude to (double)radians, and
    store all 3 in the Geographic Features structure. */

    GF[TGF].Lat = T0getLatLngHgt(7, fp) / SPR; 
    GF[TGF].Lng = T0getLatLngHgt(8, fp) / SPR; 
    GF[TGF].Hgt = T0getLatLngHgt(6, fp); 

    for(i = 0; i < 20; i++) {  // For each of the upto 20 possible characters
      if((c = fgetc(fp)) == '\n') {  // if it's a new-line character, add a 
        GF[TGF].Name[i] = (char)0;   // string-terminator null char instead
        break;                       // break out of loop if it's a 'CR'
      }
      GF[TGF].Name[i] = c;     // store char in name of Geographic Feature
    }
  }
  fclose(fp);     // Close the route file.
  TWP = LWP + 1;  // total number of waypoints in this route
  if(LWP < 2)     // If less than 2 waypoints,
    return;       // can't make a route, so exit.

  /* SET UP THE FIRST WAYPOINT: In the main for() loop below,
  the following items do not get set for the first waypoint. */

  WP[0].InRad = RH[Route][0];  // Inbound radial is runway heading
  WP[0].PFD = 0;  // this waypoint's previous waypoint is itself
  WP[0].PHD = 0;  // this waypoint's previous waypoint is itself
  WP[0].pgf = 0;  // this waypoint's previous waypoint is itself
  WP[0].ORD = 1;  // this waypoint is the first in the route

  /* LINK THE WAYPOINTS TOGETHER TO FORM A ROUTE
  Set up data from the second to the last in the route inclusive. The way-
  point being set up during each pass of the loop is the current one. The
  previous waypoint is the reference from which measurements are taken. */

  for(i = 1 ; i < TWP; i++) {  // For each waypoint except the first ...
    j = i - 1;                 // waypoint number to previous waypoint
    int 
    I = WP[i].cgf,  // index number of current waypoint's feature data
    J = WP[j].cgf;  // index number of previous waypoint's feature data

    /* Compute the distance VinDst between the current and previous way-
    points and the forward FwdAzi and back BakAzi azimuths between them
    Note: FwdAzi & BackAzi are returned by VSGIP() as angles 0 to 2π. */

    VinChk(VSGIP(GF[J].Lat, GF[J].Lng, GF[I].Lat, GF[I].Lng));

    WP[i].InRad = BakAzi;   // this waypoint's inbound radial
    WP[j].OutRad = FwdAzi;  // previous waypoint's outbound radial
    double x = VinDst / 2;  // half-distance between waypoints [in metres]
    WP[i].PFD = VinDst;     // this waypoint's distance from prev waypoint
    WP[i].PHD = x;          // half this waypoint's dist from prev waypoint
    WP[j].NFD = VinDst;     // prev waypoint's distance to current waypoint
    WP[j].NHD = x;          // half prev waypoint's dist to current waypoint
    WP[i].pgf = j;          // previous wp is current wp's previous wp
    WP[j].ngf = i;          // current 'wp' is previous 'wp's' next 'wp'
    WP[i].ORD = 0;          // this is neither first nor last waypoint
  }

  /* SET UP THE LAST WAYPOINT: the following items do
     NOT get set up by the loop for the last waypoint. */

  WP[LWP].OutRad = RH[Route][1];  // Outbound radial is dest. runway heading
  WP[LWP].NHD = 20000;            // landing run [not actually used]
  WP[LWP].ngf = i;                // this waypoint's next waypoint is itself
  WP[LWP].ORD = 2;                // set it as the destination waypoint
  cwp = 0;                        // select the first waypoint in the route

  // [REF:TURNCTR] METHODOLOGY FOR WAYPOINT ENCOUNTER: see diagram turn0.png

  for(i = 0; i < TWP; i++) {         // For each waypoint except the first:
    int I = WP[i].cgf;               // index number of Geographic Feature
    double                           // Note T is θ in diagram turn0.png
    OutRad = WP[i].OutRad,           // get this waypoit's inbound radial
    InRad  = WP[i].InRad,            // get this waypoit's outbound radial
    A = BipAng(InRad - OutRad),      // -ve if left turn 
    T = 0.5 * A,                     // +ve if right turn
    CtrDst = fabs(TrnRad / sin(T)),  // must be always positive
    CtrBrg = RatAng(InRad - T),      // puts CtrBrg on correct side
    Lat = GF[I].Lat,                 // latitude of waypoint
    Lng = GF[I].Lng;                 // longitude of waypoint

    /* Compute and store the state of the RIGHT flag, which indicates
    whether this waypoint is a right turn or a left turn within the route. */

    if(A > 0)
      RIGHT = 1;
    else
      RIGHT = 0;
    WP[i].RIGHT = RIGHT;

    WP[i].CtrDst = CtrDst;  // Distance and bearing of the centre of way-
    WP[i].CtrBrg = CtrBrg;  // point's turning circle from the waypoint.

    /* Compute & store the distance from the waypoint, along the inbound
    & outbound radials respectively, of the beginning & end of the turn. */

    WP[i].TanDst = sqrt(CtrDst * CtrDst - 100000000);  // square of TrnRad

    /* Compute & store the latitude & longitude of the centre of the way-
    point's turning circle arc using the Vicenty Geodetic Direct function. */

    VinChk(VSGDP(Lat,Lng,CtrBrg,CtrDst));

    WP[i].CtrLat = VinLat;  // latitude of centre of turning circle
    WP[i].CtrLng = VinLng;  // longitude of centre of turning circle
  }
  WP[0].TanDst = 1000;  // The origin waypoint does not have a normal TanDst.
  GFL = 1;              // Set the 'Geographic Features loaded' flag.
  T0associatedFeatures(); // load each waypoint's associated features
  Ready = 1;            // indicates that the route is loaded and ready
  showMsg(18,GREEN);    // "Flight ready to depart."
}




/* DRAW HORIZONTAL ROW OF CONTROL BUTTONS ACROSS BOTTOM OF ROUTE TAB
   Called from only 1 place in T0show(). */

void T0buts() {
  if(TB != 0) return;  // bail out if ROUTE tab not currently visible
  int h = LM;
  XSetForeground(XD,XG,DARK);   // shade for buttons background
  for(int i = 0; i < 8; i++) {  // shade the background area for each button
    XFillRectangle(XD,XW,XG,h,BY,53,BH);
    h += BW;  // move on to the next button
  }
  const char  // annotate horizontal row of control buttons
    *A[] = {"WIND","    ","    ","    ","    ","    ","    ","HELP"};
  const int 
    a[] = {15,0,0,0,0,0,0,15},  // horiz offsets for word starts
    b[] = { 4,0,0,0,0,0,0, 4};  // number of letters in each word
  h = LM; int v = BY + 15;
  XSetForeground(XD,XG,GREY);   // colour for the lettering
  for(int i = 0; i < 8; i++) {  // for each of the 8 buttons:
    if(T0BS & 1 << i)
      XSetForeground(XD,XG,WHITE); // colour for the STOP button
    else
      XSetForeground(XD,XG,GREY);  // colour for the lettering
    XDrawString(XD,XW,XG,h + a[i],v,A[i],b[i]);
    h += BW;
  }
}




/* DISPLAY THE ROUTE TAB [TAB0]. Shows Refresh Rate, Map Size, WIND data
   and Routes List. Highlights the currently selected value for each menu.
   Called only from 1 place each in T0CL(), showTab(), T0click(),
   MT4() & MT5(). */

void T0show() {
  if(TB != 0) return;  // bail out if the Route tab is not currently visible
  clearTabArea();
  XSetForeground(XD,XG,YELLOW);     // Display the Refresh Rate Selector
  XDrawString(XD,XW,XG,LM,TL,"REFRESH RATE",12);
  char *S[] = {
    "5 seconds","2 seconds","1 second",
    "500 millisecs","200 millisecs","100 millisecs"
  };
  int L[] = {9,9,8,13,13,13},       // lengths of the above lines of text
      v = TL + 18;                  // y-coord of first line
  for(int i = 0; i < 6; i++) {      // for each of the iteration periods
    if(i == IP)                     // if this is the currently selected one
      XSetForeground(XD,XG,WHITE);  // highlight it
    else                            // otherwise
      XSetForeground(XD,XG,GREY);   // grey it
    XDrawString(XD,XW,XG,LM,v,*(S + i),*(L + i));
    v += 16;                        // inter-line drop
  }
  XSetForeground(XD,XG,YELLOW);     // Display the Map Size Selector
  XDrawString(XD,XW,XG,LM,191,"MAP SIZE [km]",13);
  char *S2[] = {
    "500 x 500","400 x 400","300 x 300","200 x 200","100 x 100"," 50 x  50"
  };
  v = 209;                          // y-coord of first map size
  for(int i = 0; i < 6; i++) {      // for each map size
    if(i == MapSize)                // if it is the currently selected one
      XSetForeground(XD,XG,WHITE);  // display it in white
    else                            // otherwise
      XSetForeground(XD,XG,GREY);   // display it in grey
    XDrawString(XD,XW,XG,LM,v,*(S2 + i),9);
    v += 16;                        // drop down to next item
  }
  if(T0BS & 1)                      // display wind data if wind is active
    XSetForeground(XD,XG,WHITE);    // if active, display wind data in white
  else                              // otherwise, display it in black
    XSetForeground(XD,XG,BLACK);    // i.e. erase it
  XDrawString(XD,XW,XG,LM,318,"WIND: 230 @ 5m/s",16);

  XSetForeground(XD,XG,YELLOW);     // Display MESSAGE annotation in yellow
  XDrawString(XD,XW,XG,LM,345,"MESSAGE",7);
  showMsg(CM,MC);                   // display the current message

  // DISPLAY THE ROUTES LIST
  int q = LM + 120,              // left margin for the routes list
      x = TL + 40, y = TL + 15, z = TL - 5, w = TL + 48,
      u = q + 32,  t = q  + 56, s = q + 72, r = q  + 74;
  XSetForeground(XD,XG,YELLOW);  // show titles and headings in yellow
  XDrawLine(XD,XW,XG,u,z,u,w);   // vertical
  XDrawLine(XD,XW,XG,t,y,t,w);   // vertical
  XDrawLine(XD,XW,XG,u,z,s,z);   // horizontal
  XDrawLine(XD,XW,XG,t,y,s,y);   // horizontal
  XDrawString(XD,XW,XG,r,TL,"Origin Runway Heading [ORH] degrees 0-360",41);
  XDrawString(XD,XW,XG,r,TL+20,
    "Destination Runway Heading [DRH] deg 0-360",42);
  XDrawString(XD,XW,XG,q-6,TL+27,"Route",5);
  XDrawString(XD,XW,XG,q,x,"No.",3);
  XDrawString(XD,XW,XG,r,x,"ROUTE NAME",10);

  // UPDATE MOUSE SCROLL POSITION IF MOUSE WHEEL HAS BEEN MOVED
  if(MW == 1 && MB < TNR - 12)  // if Mouse Wheel 1,
    MB++;                       // scroll down the list
  if(MW == 2 && MB > 0)         // if Mouse Wheel 2,
    MB--;                       // scroll back up list
  MW = 0;                       // Mouse wheel event now dealt with

  v = TL + 60;  // y-coord of top of routes list 
  int M = TNR;  // display all waypoints in the route, but
  if(TNR > 12)  // limit to a maximum of 12 at a time
    M = 12;

  // DISPLAY THE APPROPRIATE SECTION OF UP TO 12 ROUTES
  for(int i = 0; i < M; i++) {  // display runway headings and route name
    int x = MB + i;             // route number is bias + display line number

    if(Route == x)                  // if displaying selected route
      XSetForeground(XD,XG,WHITE);  // show selected route in white
    else                            // otherwise
      XSetForeground(XD,XG,GREY);   // show all the other routes in grey

    XDrawString(XD,XW,XG,q,v,showInt(x,3),3);          // route number
    XDrawString(XD,XW,XG,q+24,v,showRat(RH[x][0]),3);  // origin rw heading
    XDrawString(XD,XW,XG,q+48,v,showRat(RH[x][1]),3);  // dest. rw heading
    XDrawString(XD,XW,XG,s,v,RN[x],strlen(RN[x]));     // route name
    v += 17;  // drop down to the next line
  }

  // DISPLAY THE SCROLL BAR
  if(TNR > 12) {                  // if not more than 12 routes on file
    XSetForeground(XD,XG,DARK);   // display whole scroll bar in dark grey
    XFillRectangle(XD,XW,XG,475,w,8,204);
    int h = 2248 / TNR;           // extent of highlighted scroll bar
    v = MB * (204-h) / (TNR-12);  // dull extent above highlighted part
    XSetForeground(XD,XG,YELL);   // display highlighted part of scroll bar
    XFillRectangle(XD,XW,XG,475,w+v,8,h);
  }
  T0buts();  // display the 8 control buttons along the bottom
}




int T0CLsub(int h, int N) {  // called only from T0CL() below
  int n = -1;                // number of clicked name
  int L = 17;
  if(N == 6)                 // adjust the line drop
    L = 16;
  for(int i = 0; i < N; i++) {   // for each Route name
    if(mY > h && mY < h + 15) {  // if click was on this name
      n = i; break;              // exit with i = route index number
    } h += L;                    // drop down to next line
  } return n;                    // return the selection number or -1
}




/* MOUSE CLICKED ON A NON-BUTTON PART OF THE ROUTE TAB.
   Called only from 1 place in MEH(). */

void T0CL() {
  if(TB != 0) return;  // bail out if ROUTE tab isn't currently visible
  int x = -1, y = TNR;
  if(mX > 134 && mX < 483) {  // if within x-limits of Route names
    if(y > 12)                // limit the display to 12 items
      y = 12;
    x = T0CLsub(111,y);     // list number of clicked item

    if(x != -1) {         // if list number is valid
      Route = x + MB;     // set selected route
      T0getWayPts();      // load the waypoints for the default route
      RunState = 0;       // set the flight tp 'stopped' if running
      cwp = 0;            // set current waypoint as first in the route
      cgf = WP[cwp].cgf;  // set reference to this waypoint's data
      setAircraftAt();    // set aircraft at start of current route
    }
  }

  // click was within the left menus column
  else if(mX > 15 && mX < 99) {

    // if a Refresh Rate was clicked
    if(mY < 170 && (x = T0CLsub(69,6)) != -1) {
      IP = x;            // set selected Refresh Rate
      ET = ETS[IP];      // set elapsed time [in μs]
    } 

    // if a Map Size clicked
    else if((x = T0CLsub(197,6)) != -1) { 
      MapSize = x;       // set the selected Map Size
      ms = MS[MapSize];  // set the corresponding  map scale
    }
  }
  T0show();  // re-display the modified Routes Tab
}




/* THE ROUTE TAB'S 'WIND' OR 'HELP' BUTTON WAS PRESSED.
   Button numbers: 0:WIND, 7:HELP. Called from only 1 place in MEH(). */

void T0click(int x) {
  int y = 1 << x;     // bit mask for the clicked button
  switch(x) {         // switch on button number
    case 0:           // WIND BUTTON PRESSED
      if(Wind) {      // if wind is currently active
        Wind = 0;     // switch wind off
        T0BS &= ~ 1;  // dim the WIND button
      } else {        // otherwise
        Wind = 1;     // switch wind on
        T0BS |= 1;    // brighten the WIND button
      } break;
    case 7:           // ROUTE Tab HELP
      helpPage("navigation/nav.html#route");
  }
  T0show();  // show the button
}





// -------------FUNCTIONS PERTAINING TO TAB 1 [WAYPOINTS SELECTOR]-------------

/* DRAW HORIZONTAL ROW OF CONTROL BUTTONS ACROSS BOTTOM OF WAYPOINTS TAB
   Called from only one place in T1show(). */

void showT1buts() {
  if(TB != 1) return;  // bail out if WAYPTS tab not currently visible
  int h = LM;          // set h to left side of first button
  XSetForeground(XD,XG,DARK);  // shade for buttons background

  // shade the background area for each button
  for(int i = 0; i < 8; i++) {
    XFillRectangle(XD,XW,XG,h,BY,53,BH);
    h += BW;  // move across to the next button
  }

  char  // annotate horizontal row of control buttons
    *A[] = {"","","","","","","","HELP"};
  int 
    a[] = {0,0,0,0,0,0,0,15},   // horiz offsets for word starts
    b[] = {0,0,0,0,0,0,0, 4};   // number of letters in each word
  h = LM; int v = BY + 15;      // x & y coords of button block
  XSetForeground(XD,XG,GREY);   // colour for the lettering

  for(int i = 0; i < 8; i++) {  // for each of the 8 buttons:
    XDrawString(XD,XW,XG,h + a[i],v,A[i],b[i]);
    h += BW;  // move across [rightwards] to next button
  }
}




/* DISPLAY THE STATIC CONTENT OF THE WAYPOINTS TAB. Called from only 1 place
   each in T1CL(), showTab(), updtTrack(), MT4() & MT5(). */

void T1show() {
  if(TB != 1) return;  // bail out is WAYPTS tab not currently visible
  clearTabArea();
  char *S[] = {
    "Waypoints of","the currently","active route.","The aircraft",
    "is currently","locked on to","highlighted","waypoint.","",
    "List may be","scrolled by","mouse wheel."
  };

  int L[] = {12,13,13,12,12,12,11,9,0,11,11,12};  // line lengths

  XSetForeground(XD,XG,BLEY);  // colour in which to display instructions

  int v = TL + 20,  // y-coord of first line of instructions
      h = LM;       // x-coord of instruction text block

  // display the instruction lines
  for(int i = 0; i < 12; i++) {
    XDrawString(XD,XW,XG,h,v,*(S + i),*(L + i));
    v += 17;  // drop to next lower line
  }

  // DISPLAY THE VINCENTY MAXIMUM ITERATION COUNTERS

  v += 17; XDrawString(XD,XW,XG,h,v,"VINCENTY MAX",12);
  v += 17; XDrawString(XD,XW,XG,h,v,"DIR =",5);
  XDrawString(XD,XW,XG,h+10,v,showHeight(IC1),10);
  v += 17; XDrawString(XD,XW,XG,h,v,"INV =",5);
  XDrawString(XD,XW,XG,h+10,v,showHeight(IC2),10);

  // DISPLAY THE WAYPOINTS LIST HEADINGS

  int Q = LM + 95;  // y-coord of column headings

  XSetForeground(XD,XG,YELLOW);  // show column headings in yellow
  XDrawString(XD,XW,XG,Q,TL,"No.",3);
  XDrawString(XD,XW,XG,Q+28,TL,"WAYPOINT NAME",13);
  XDrawString(XD,XW,XG,Q+162,TL,"LATITUDE",8);
  XDrawString(XD,XW,XG,Q+228,TL,"LONGITUDE",9);
  XDrawString(XD,XW,XG,Q+318,TL,"HEIGHT",6);

  if(MW == 1 && LB < TWP-16)  // if Mouse Wheel 1
    LB++;                     // scroll down the list
  if(MW == 2 && LB > 0)       // if Mouse Wheel 2
    LB--;                     // scroll back up list
  MW = 0;                     // Mouse wheel event now dealt with

  v = TL + 20;  // y-coord for first waypoint shown
  int M = TWP;  // number of lines to be displayed
  if(TWP > 16)  // only got space for 16 lines max
    M = 16;
  for(int i = 0; i < M; i++) {      // for each line to be displayed
    int x = LB + i;                 // waypoint number for current line
    if(cwp == x)                    // if this is the current waypoint
      XSetForeground(XD,XG,WHITE);  // highlight it in bright white
    else                            // otherwise
      XSetForeground(XD,XG,GREY);   // display it in grey

    // display waypoint's list number, name, latitude, longitude and height
    XDrawString(XD,XW,XG,Q,v,showInt(x,3),3);
    XDrawString(XD,XW,XG,Q+28,v,GF[WP[x].cgf].Name,strlen(GF[WP[x].cgf].Name));
    XDrawString(XD,XW,XG,Q+150,v,RadToDMS(GF[WP[x].cgf].Lat,0),10);
    XDrawString(XD,XW,XG,Q+222,v,RadToDMS(GF[WP[x].cgf].Lng,1),10); 
    XDrawString(XD,XW,XG,Q+294,v,showHeight(GF[WP[x].cgf].Hgt),10);
    v += 17;  // drop to next lower line
  }

  // If more than 16 waypoints in the route we need to display a scroll bar.
  if(TWP > 16) {                      // if more than 16 waypoints in route
    h = 4352 / TWP;                   // height of highlighted scroll bar
    v = LB * (272 - h) / (TWP - 16);  // dull above the highlighted part
    XSetForeground(XD,XG,DARK);       // show full length scroll bar in grey
    XFillRectangle(XD,XW,XG,475,TL+8,8,272);
    XSetForeground(XD,XG,YELL);       // show highlighted part of scroll bar
    XFillRectangle(XD,XW,XG,475,TL+8+v,8,h);
  }
  showT1buts();  // show the 8 control buttons along the bottom
}




/* THE WAYPOINT TAB'S HELP BUTTON WAS PRESSED.
   Called from only one place in MEH(). */

void T1click(int x) {
  switch(x) {  // switch on button number
    case 7: helpPage("navigation/nav.html#waypts");
  }
}




/* Loads the names of all the available routes into the Routes Menu array
   ready for display by the showMenu() function. For each of the route names
   in the route names file, gets each of its characters in turn and puts it
   in this route name's character array. However, when it hits a 'new line'
   character, it break out of the inner loop. When, in the outer loop, it hits
   the 'end of file', it notes the number of named routes and the breaks the
   outer loop. Line format of routes.txt "224 062 Stansted to Edinburgh".

   "224 " runway heading at origin [degrees 0 to 360]
   "062 " runway heading at destination [degrees 0 to 360]
   "Stansted to Edinburgh" name of route 

   Each line is terminated by a 'new line' character. */

void T1routes() {     // Called only by initFlightParams()
  showMsg(16,WHITE);  // display the "Loading Route Names" message

  FILE *fp = fopen("routes.txt", "r"); // open route names file for reading
  if(fp == NULL) {                     // if could not find or open the file
    showMsg(21,RED); return;           // show eror message in red and exit
  }
  char c;  // for characters got from the route names file
  int x;   // for runway headings

  for(i = 0; i < 100; i++) {  // for each of upto 100 possible routes:
    if(feof(fp)) {            // break when 'end of file' encountered
      TNR = --i;              // number of routes
      break;
    }
    x = 0;                    // clear the integer accumulator
    for(j = 0; j < 3; j++) {  // for each of up to 3 numeric characters
      c = fgetc(fp);          // input the next character
      if(c > 47 && c < 58)    // if it is within numeric character range
        x = x * 10            // multiply previous value by 10 
          + (c - '0');        // and add this characters integer value
    }                         // integer value = ASCII value - '0' [48]

    /* convert origin runway heading from degrees
    to radians; store in runway headings array */

    RH[i][0] = (double)x * RPD;

    c = fgetc(fp);  // skip the space character
    x = 0;          // clear the integer accumulator

    for(j = 0; j < 3; j++) {  // for each of up to 3 numeric characters
      c = fgetc(fp);          // input the next character

      /* if it is within numeric character range multiply prev-
      ious value by 10 and add this character's integer value.
      Integer value = ASCII value - '0' [48] */ 

      if(c > 47 && c < 58)
        x = x * 10 + (c - '0');
    }

    RH[i][1] = (double)  // convert destination runway heading from °
      x * RPD;           // to radians; store in runway headings array
    c = fgetc(fp);       // skip the space character

    for(int j = 0; j < 80; j++) {    // For each of up to 80 characters
      if((c = fgetc(fp)) == '\n') {  // if character is a 'new line'
        RN[i][j] = (char)0;          // terminate the route name string
        break;                       // terminate the loop
      }
      RN[i][j] = c;  // store character in route name array
    }
  }            // end of 'for each of upto 100 possible routes:'
  fclose(fp);  // Close the route names file.
}




/* RELOCATE THE AIRCRAFT TO THE WAYPOINT CLICKED WITHIN THE WAYPTS
   TAB'S WAYPOINTS LIST. Called from only 1 place in MEH(). */

void T1CL() {
  if(mX > 111 || mX < 465) {   // click within horiz limits of waypoints list
    int v = 71, i;             // vertical coord of top of list
    for(i = 0; i < 16; i++) {  // for each of the 16 visible waypoints
      if(mY > v && mY < v+15)  // if the click was on this waypoint
        break;                 // break out: i is its list number
      v += 17;                 // drop down to next waypoint name
    }
    if(i < 16) {          // click was on a waypoint entry
      cwp = LB + i;       // set current waypoint = clicked waypoint
      RunState = 0;       // stop the flight if it is running
      cgf = WP[cwp].cgf;  // reference to waypoint data
      setAircraftAt();    // set aircraft to current waypoint
      T1show();           // re-display the waypoints list
    }
  }
}





// ---------------FUNCTIONS PERTAINING TO TAB 2 [GEOID SELECTOR]---------------


/* INSTALL THE METRICS FOR THE SELECTED GEIOD. Called from only 1 place
   each in initFlightParams(), T2auto() and T2click(). */

void T2setGeoid() {
  const double
  A[] = {         //  SEMI-MAJOR AXES OF AVAILABLE GEOIDS
    6378137.000,  //  0 GRS80 / WGS84 (NAD83) *
    6378206.400,  //  1 Clark 1866 (NAD27)
    6377563.396,  //  2 Airy 1858
    6377340.189,  //  3 Airy Modified
    6378160.000,  //  4 Australian National
    6377397.155,  //  5 Bessel 1841
    6378249.145,  //  6 Clark 1880
    6377276.345,  //  7 Everest 1830
    6377304.063,  //  8 Everest Modified
    6378166.000,  //  9 Fisher 1960
    6378150.000,  // 10 Fisher 1968
    6378270.000,  // 11 Hough 1956
    6378388.000,  // 12 International (Hayford)
    6378245.000,  // 13 Krassovsky 1938
    6378145.000,  // 14 NWL-9D  (WGS 66)
    6378160.000,  // 15 South American 1969
    6378136.000,  // 16 Soviet Geod. System 1985
    6378135.000,  // 17 WGS 72
  },
  F[] = {                                // FLATTENING FACTORS OF GEOIDS
    0.00335281068118366789616980869833,  //  0 GRS80 / WGS84 (NAD83) *
    0.00339007530392991937343144432063,  //  1 Clark 1866 (NAD27)
    0.00334085064149707745426055919427,  //  2 Airy 1858
    0.00334085064149707745426055919427,  //  3 Airy Modified
    0.00335289186923721709974853310981,  //  4 Australian National
    0.00334277318217480587901060845382,  //  5 Bessel 1841
    0.00340756137869933382175046428024,  //  6 Clark 1880
    0.00332444929666288455151682985834,  //  7 Everest 1830
    0.00332444929666288455151682985834,  //  8 Everest Modified
    0.00335232986925913509889373114314,  //  9 Fisher 1960
    0.00335232986925913509889373114314,  // 10 Fisher 1968
    0.00336700336700336700336700336700,  // 11 Hough 1956
    0.00336700336700336700336700336700,  // 12 International (Hayford)
    0.00335232986925913509889373114314,  // 13 Krassovsky 1938
    0.00335289186923721709974853310981,  // 14 NWL-9D  (WGS 66)
    0.00335289186923721709974853310981,  // 15 South American 1969
    0.00335281317789691440603238146967,  // 16 Soviet Geod. System 1985
    0.00335277945416750486153020854288,  // 17 WGS 72
  };
  EquRad = A[Geoid];   // semi-major axis of selected geoid [metres]
  FlatF = F[Geoid];    // flattening factor of selected geoid
  EquPol = 1 - FlatF;  // ratio between polar and equitorial radii of Earth
}




/* DISPLAYS THE CONTENT OF THE GEOID TAB. Highlights the currently
   selected selection mode and geoid. Called from only 1 place each in
   T2click(), showTab() and T2auto(). */

void T2show() {
  if(TB != 2) return;

  const char
    *S1[] = {
      "Geoid selection can be MANUAL","or AUTOMATIC. The selection",
      "mode currently effective is","the highlighted [bright] one.",
      "You can change selection mode","by clicking on MANUAL or AUTO-",
      "MATIC. The GEOID currently in","use by the Vincenty distance",
      "& bearing computations is the","one highlighted on the right.",
      "When in MANUAL selection mode,","a different geoid can be sel-",
      "ected by clickling on its name." 
    },
    *S2[] = {  // Geoid Select Menu
      "GRS80/WGS84 (NAD83)","Clark 1866 (NAD27)","Airy 1858","Airy Modified",
      "Australian National", "Bessel 1841", "Clark 1880", "Everest 1830",
      "Everest Modified","Fisher 1960","Fisher 1968","Hough 1956",
      "International (Hayford)","Krassovsky 1938","NWL-9D  (WGS 66)",
      "South American 1969","Soviet Geod System 1985","WGS 72"
    };

  const int
    L1[] = {29,27,27,29,29,30,29,28,29,29,30,29,31},
    L2[] = {19,18,9,13,19,11,10,12,16,11,11,10,23,15,16,19,23,6};
  int
    c = GREY,
    d = GREY;

  if(Gauto == 1)  // if in AUTOMATIC mode
    d = WHITE;    // highlight the word AUTOMATIC
  else            // else, must be in MANUAL mode
    c = WHITE;    // so highlight the word MANUAL

  XSetForeground(XD,XG,c);
  XDrawString(XD,XW,XG,LM,TL + 20,"MANUAL",6);
  XSetForeground(XD,XG,d);
  XDrawString(XD,XW,XG,LM,TL + 38,"AUTOMATIC",9);

  XSetForeground(XD,XG,DARK); 
  XFillRectangle(XD,XW,XG,145,TL+23,53,BH);   // background for HELP button
  XSetForeground(XD,XG,GREY); 
  XDrawString(XD,XW,XG,160,TL + 38,"HELP",4); // annotation for HELP button

  // DISPLAY THE INSTRUCTION TEXT IN GREY
  XSetForeground(XD,XG,BLEY);
  int v = TL + 90;  // vertical coord of first line of instructions text

  for(int i = 0; i < 13; i++) {
    XDrawString(XD,XW,XG,LM,v,*(S1 + i),*(L1 + i));
    v += 18;
  }

  // DISPLAY THE LIST OF GEOIDS, HIGHLIGHTING THE SELECTED ONE
  v = TL;  // vertical coord of first line of geoids list

  for(int i = 0; i < 18; i++) {
    if(i == Geoid) XSetForeground(XD,XG,WHITE);
    else XSetForeground(XD,XG,GREY);
    XDrawString(XD,XW,XG,255,v,*(S2 + i),*(L2 + i));
    v += 18;
  }

  // SHOW THE TWO LIST [MENU] TITLES
  XSetForeground(XD,XG,YELLOW);
  XDrawString(XD,XW,XG,145,TL,"AVAILABLE GEOIDS:",17);
  XDrawString(XD,XW,XG,LM,TL,"GEOID SELECTION",15);
}




/* MOUSE WAS CLICKED WITHIN THE GEOID TAB.
   Called from 2 places in MEH(). */

void T2click() {
  int
    h = 51,  // top of first click area
    n = -1;  // number of clicked name

  if(mX > 144 && mX < 198 && mY > 85 && mY < 107)  // HELP buton clicked
    helpPage("navigation/nav_full.html#geoid");

  else if(mX > 253 && mX < 483) {  // if within x-limits of Geoid names

    for(int i = 0; i < 18; i++) {  // for each geoid name
      if(mY > h && mY < h + 15) {  // if click was on this name
        n = i;
        break;  // exit with i = geoid index number
      }
      h += 18;  // drop down to next line
    }

    if(n != -1) {  // click was on a geoid name
      Geoid = n;   // set number of newly selected geoid
      T2setGeoid();  // set up the geoid parameters
    }
  }
  else if(mX > 14 && mX < 75) {  // if within x-limits of mode selector

    if(mY > 71 && mY < 86)  // if click was on MANUAL
      Gauto = 0;            // set to manual
    else
    if(mY > 89 && mY < 104)  // if click was on AUTOMATIC
      Gauto = 1;             // set to automatic
  }
  T2show();  // re-display the Geoid Tab content
}




/* Proposed function to automatically select the best geoid to use according
   to the aircraft's current position on the planet. This current implement-
   ation is very rudimentary. The applicable zones for each geoid need to be 
   properly researched and constructed. */

void T2auto() {           // called only from updtTrack() below
  if(Gauto == 0) return;  // return if geoid selection is set to manual
  int x = 0;              // for the geiod index number
  if(AirLat < 0) {        // If aircraft is in the southern hemisphere:

    /* if the aircraft's longitude is between 30°W and 150°W use
    the South American geoid else use the Australian geoid */

    if(AirLng < -0.523598776 && AirLng < -2.617993878)
      x = 4;
    else
      x = 15;

  } else {  // Else aircraft is in the northern hemisphere:

    /* if the aircraft's longitude is between 30°W and 150°W, use the
    North American geoid else if its longitude is between 45°E and 170°E
    use the Soviet geoid. */

    if(AirLng < -0.523598776 && AirLng < -2.967059728)
      x = 0;
    else if(AirLng > 0.785398163 && AirLng < 0.296705973)
      x = 16;
  }
  if(Geoid != x) {  // if the selected geoided has been automatically changed
    Geoid = x;      // set the global geoid index number
    T2setGeoid();   // set up the parameters of the new geoid
    T2show();       // re-display the geiod selector menu
  }
}





// ----------------FUNCTIONS PERTAINING TO TAB 3 [MOVING MAP]----------------


// DRAW THE CURRENT WAYPOINT'S GUIDE ARC AND ARC RADII

void T3guideArc(int mapx, int mapy) {
  double
    a = CtrDst * ms,       // Distance from waypoint to the radial
                           // centre of its turning circle arc.
    b = CtrBrg - CmdHdg;   // Bearing, relative to screen vertical,
                           // from the waypoint to the radial
                           // centre of its turning circle arc.
    if(RIGHT == 0)         // if it is a left turn, rotate the
      b += pi;             // bearing to the opposite direction
    b = RatAng(b);         // re-rationalise it 0-360
  int
    S = rint(a * sin(b)),  // x-component of dist to centre of arc
    C = rint(a * cos(b));  // y-component of dist to centre of arc

  if(RIGHT == 0) {         // If this waypoint is a left turn,
    S = -S;                // transpose the arc to the other side.
    C = -C;
  }
  double  // Half the angle subtended by the turning arc, which is
    A = HalfPi - CtrBrg + OutRad;  // π/2 - θ in diagram turn0.png
  int
    Cx = mapx + S,  // x-coord of centre of turning circle
    Cy = mapy - C;  // y-coord of centre of turning circle

  /* if the y-coordinate of the arc centre is within the map area,
     then set the arc centre flag [acf]. */
  int acf = 0; if(Cy < BotLim && Cy > TopLim) acf = 1;

  // COMPUTE THE TURNING CIRCLE RADIUS IN MAP PIXELS
  a = TrnRad * ms;  // Need to make the arc's radials the
  if(CtrDst < 0)    // same sign as the distance between 
    a = -a;         // the waypoint and the centre of the arc.


  // DRAW THE TWO BOUNDING RADII OF THE TURNING ARC IN GREY.
  XSetForeground(XD,XG,GREY);
  double
    K = RatAng(b - A + pi);  // half-angle to start point of turning arc
  int
    x = rint(a * sin(K)),  // x-offset of start-point of arc from arc centre
    y = rint(a * cos(K)),  // y-offset of start-point of arc from arc centre
    z = Cy - y;            // screen y-coord of arc start-point

  /* if the y-coordinate of the arc's start point is within the map area,
     then set the arc start flag [asf] */
  int asf = 0; if(z < BotLim && z > TopLim) asf = 1;

  // display the start-radius if it is within the map area
  if(acf && asf) XDrawLine(XD,XW,XG,Cx,Cy,Cx + x,z);

  double
    L = RatAng(b + A + pi);  // half-angle to end point of turning arc 
  x = rint(a * sin(L));      // x-offset of end-point of arc from arc centre
  y = rint(a * cos(L));      // y-offset of end-point of arc from arc centre
  z = Cy - y;                // screen y-coord of arc end-point

  /* if the y-coordinate of the arc's end point is within the map area,
     then set the arc's end flag [aef] */
  int aef = 0; if(z < BotLim && z > TopLim) aef = 1;

  // display the start-radius if it is within the map area
  if(acf && aef) XDrawLine(XD,XW,XG,Cx,Cy,Cx + x,z);


  // DRAW THE ARC ITSELF: A RIGHT TURN IN GREEN AND A LEFT TURN IN RED.

  if(asf & aef) {   // if both ends of the arc are within the map area
    double c, q;
    if(RIGHT == 1)  // for right turn: go clockwise from K to L
      XSetForeground(XD, XG, GREEN);
    else {          // for left turn: go clockwise from L to K
      XSetForeground(XD, XG, RED);
      c = K; K = L; L = c;  // transpose L and K for left turn
    }
    q = L - K;        // angle swept by the arc.
    if(q < 0)         // if the sweep angle is negative
      q = RatAng(q);  // rationalise it to range from 0 to 2π
    L = K + q;        // L is greater than K [rationalised]

    for(c = K; c < L; c += RPD)  // for each degree from K to L
      XDrawPoint(XD,XW,XG,       // plot a point in RED or GREEN
        Cx + rint(a * sin(c)),   // x-coord of plot
        Cy - rint(a * cos(c)));  // y-coord of plot
  }
}




// DRAW THE CURRENT WAYPOINT'S INBOUND AND OUTBOUND RADIALS

void T3drawRads(int mapx, int mapy) {
  int                  // SET UP DATA FOR DRAWING THE WAYPOINT'S RADIALS
  X1 = RgtLim - mapx,  // positive limit of x (rightwards is +ve)
  X2 = LftLim - mapx,  // negative limit of x (leftwards is -ve)
  Y2 = TopLim - mapy,  // negative limit of y (upwards is -ve)
  Y1 = BotLim - mapy;  // positive limit of y (downwards is +ve)

  // DRAW THE CURRENT WAYPOINT'S OUTBOUND RADIAL

  if(RIGHT == 1)  // radial is GREEN for a right turn
    XSetForeground(XD,XG,GREEN);
  else            // and RED for a left turn
    XSetForeground(XD,XG,RED);

  double
    a = OutRad - CmdHdg,  // screen direction for outbound radial
    b = TanDst * ms,      // distance from waypoint of start of radial
    c = NHD;              // distance from waypoint of end of radial

  if(c > WPR)  // If radial would extend beyond radio range, 
    c = WPR;   // limit it to extend only up to radio range.
  c *= ms;     // scale the real-world range to map pixels

  // [REF:CHOP1] See diagram radialChop.png 'a' is θ in the diagram
  double         // NOTE: screen Y coords are -ve upwards and +ve downwards
    S = sin(a),  // sine of the screen bearing of the outbound radial 
    C = cos(a),  // cosine of the screen bearing of the outbound radial 
    X = c * S,   // X is +ve or -ve according to a's quadrant
    Y = -c * C,  // Y is -ve or +ve according to a's quadrant
    R1, R2;      // lengths of X and Y generated chopped radials

  Flag = 0;            // 1:overshoot occurred; 0:didn't occur
  if(X > X1) {         // if radial has overshot to the right of map area
    X = X1;            // set x limit to the right extremity
    Flag = 1;          // indicate that an overshoot has occurred
  } else if(X < X2) {  // if radial has overshot to the left of map area
    X = X2;            // set x limit to the left extremity
    Flag = 1;          // indicate that an overshoot has occurred
  } if(Y > Y1) {       // if radial has overshot the bottom of map area 
    Y = Y1;            // set y limit to the bottom extremity †
    Flag = 1;          // indicate that an overshoot has occurred
  } else if(Y < Y2) {  // if radial has overshot the top of map area  
    Y = Y2;            // set y limit to the top extremity
    Flag = 1;          // indicate that an overshoot has occurred
  }

  if(Flag == 1) {      // if an overshoot has occurred:
    if(a == 0)
      c = -Y2;         // Avoid division by zero anomalies when a radial
    else               // is aligned exactly with one of the axes.
    if(a == pi)
      c = Y1;
    else
    if(a == HalfPi)
      c = X1; 
    else 
    if(a == HalfPi + pi)
      c = -X2;
    else {               // otherwise:
      R1 = fabs(X / S);  // compute x-component of chopped radial
      R2 = fabs(Y / C);  // compute y-component of chopped radial
      if(R1 > R2)        // If radial based on x limit is greater than
        c = R2;          // the radial based on y-limit then use
      else c = R1;       // the smaller of the two; and vice versa.
    }
  }

  if(b < c)              // if there is at least some of the radial to show
    XDrawLine(XD,XW,XG,
      mapx + rint(b * S),
      mapy - rint(b * C),
      mapx + rint(c * S),
      mapy - rint(c * C));


  // DRAW THE CURRENT WAYPOINT'S INBOUND RADIAL

  a = InRad - CmdHdg;  // screen direction for inbound radial
  c = PHD;             // half the distance to the previous waypoint
  if(PHD > WPR)        // if this is greater than the waypoint's radio
    c = WPR;           // range, limit it to the waypoint's radio range.
  c *= ms;             // scale real-world distance to map pixels

  /* In the case of the originating waypoint, the inbound radial is the
  runway heading; so make it yellow, starting at the waypoint itself and
  extending for 8 kilometres beyond the waypoint. */

  if(cwp == 0) {
    XSetForeground(XD,XG,YELLOW);
    b = 0;
    c = 8000 * ms;  // scale 8km to map pixels
  }

  // [REF:CHOP2] See diagram radialChop.png 'a' is θ in the diagram

  S = sin(a);  // sine of the screen bearing of the inbound radial 
  C = cos(a);  // cosine of the screen bearing of the inbound radial 
  X = c * S;   // +ve or -ve according to a's quadrant
  Y = -c * C;  // -ve or +ve according to a's quadrant

  Flag = 0;            // indicates whether or not the radial overshoots
  if(X > X1) {         // if radial has overshot to the right of map area
    X = X1;            // set x limit to the right extremity
    Flag = 1;          // indicate that an overshoot has occurred
  } else if(X < X2) {  // if radial has overshot to the left of map area
    X = X2;            // set x limit to the left extremity
    Flag = 1;          // indicate that an overshoot has occurred
  }
  if(Y > Y1) {         // if radial has overshot the bottom of map area 
    Y = Y1;            // set y limit to the bottom extremity †
    Flag = 1;          // indicate that an overshoot has occurred
  } else if(Y < Y2) {  // if radial has overshot the top of map area  
    Y = Y2;            // set y limit to the top extremity
    Flag = 1;          // indicate that an overshoot has occurred
  }
  if(Flag == 1) {      // if an overshoot has occurred:
    if(a == 0)
      c = -Y2;         // Avoid division by zero anomalies when a radial
    else if(a == pi)   // is aligned exactly with one of the axes.
      c = Y1;
    else if(a == HalfPi)
      c = X1; 
    else if(a == HalfPi + pi)
      c = -X2;
    else {               // otherwise:
      R1 = fabs(X / S);  // compute x-component of chopped radial
      R2 = fabs(Y / C);  // compute y-component of chopped radial
      if(R1 > R2)        // if radial based on the x limit is greater
        c = R2;          // than the radial based the on y-limit then
      else c = R1;       // use the smaller of the two; and vice versa.
    }
  }
  if(b < c)                 // if there is at least some of the
    XDrawLine(XD,XW,XG,     // radial to show, then display it.
      mapx + rint(b * S),   // The rint() function is to ensure that the
      mapy - rint(b * C),   // distances in pixels from the map centre are
      mapx + rint(c * S),   // rounded down and NOT up in order to avoid
      mapy - rint(c * C));  // a radial over-shooting the map boundary.
}




/* To display the map. This function is executed from the 'run' loop.
   It updates local variables containing all the data needed to draw the
   map. Called from 1 place in T3show(), 2 places in updtTrack(),
   and 5 places in T3click(). */

void T3updt() {
  if(TB != 3) return;  // the map only appears on Tab 3

  /* The map can be painted only after the route names and the waypoints of
  the default route have all been loaded. Painting before this time would
  paint data from variables that had not yet been created. The GFL flag is
  set true once waypoint data is available. If it goes false later during
  the loading of another route's waypoints, repainting of the map can still
  take place without any problems. */

  if(GFL == 0) return;  // exit if routes & default waypoints not yet loaded

  /* Display current waypoint's Name, Latitude, Longitude, Height, Distance,
  Bearing + Selected Radial; and the aircraft's Latitude, Longitude, Alti-
  tude, Speed, required and commanded Headings + Rate of Turn. */

  XSetForeground(XD,XG,BLACK);  // clear all the writing areas to black
  XFillRectangle(XD,XW,XG,83,50,90,301);  // position correct 16/08/2020
  XFillRectangle(XD,XW,XG,42,68,48,17);   // extra space for waypoint name

  XSetForeground(XD,XG,WHITE);  // write all data items in white
  XDrawString(XD,XW,XG,DM-60,TL+18,showName(GF[WP[cwp].cgf].Name),20);
  XDrawString(XD,XW,XG,DM,TL+36,RadToDMS(cwpLat,0),10);
  XDrawString(XD,XW,XG,DM,TL+54,RadToDMS(cwpLng,1),10);
  XDrawString(XD,XW,XG,DM,TL+72,showHeight(cwpHgt),10);

  int
    x = WptDst,    // distance of the waypoint from the aircraft
    y;             

  if(ToFlag == 1)  // Distance is positive when travelling FROM a
    x = -x;        // waypoint and negative when travelling TO it.

  XDrawString(XD,XW,XG,DM,TL+90,showHeight(x),10);
  XDrawString(XD,XW,XG,DM+36,TL+108,showRat(FwdBrg),3);
  XDrawString(XD,XW,XG,DM+36,TL+126,showRat(OutRad),3);

  XDrawString(XD,XW,XG,DM,TL+172,RadToDMS(AirLat, 0),10);
  XDrawString(XD,XW,XG,DM,TL+190,RadToDMS(AirLng, 1),10);
  XDrawString(XD,XW,XG,DM,TL+208,showHeight(AirHgt),10);
  XDrawString(XD,XW,XG,DM,TL+226,showHeight(CmdSpd),10);
  XDrawString(XD,XW,XG,DM+36,TL+244,showRat(ReqHdg),3);
  XDrawString(XD,XW,XG,DM+36,TL+262,showRat(CmdHdg),3);
  XDrawString(XD,XW,XG,DM+18,TL+280,showBip(CmdROT),7);

  #define MapCtrX 332  // window x-coordinate of the centre of the map
  #define MapCtrY 200  // window y-coordinate of the centre of the map
  #define WinLimX 482  // left window limit for waypoint names
  #define MNVW 100     // max number of visible waypoints allowed on map
  #define MaxX 150     // maximum x from centre of map
  #define MaxY 150     // maximum y from centre of map
  #define MapLft 182   // = MapCtrX - MaxX;
  #define MapRgt 482   // = MapCtrX + MaxX;
  #define MapTop 50    // = MapCtrY - MaxY;
  #define MapBot 350   // = MapCtrY + MaxY;

  int
    VWC = -1,    // for index number of current waypoint
    vw = 0,      // set total number of visible waypoints to zero
    mapx = 0,    // pixel x-coordinate of current geographic feature
    mapy = 0,    // pixel y-coordinate of current geographic feature
    MAPX[MNVW],  // array for 'x' of all visible features
    MAPY[MNVW];  // array for 'y' of all visible features

  char *WN[MNVW];  // holds all the feature names for the current route

  Flag = 0;  // 0:suppress display of an item; 1:permit display


  /* FOR EACH GEOGRAPHIC FEATURE ASSOCIATED WITH THE CURRENT WAYPOINT, IN-
  CLUDING ITSELF, COMPUTE ITS SCREEN COORDINATES FROM ITS RANGE & BEARING.
  NOTE: all geographic features associated with a given waypoint have been
  filtered by the function T0associatedFeatures() to be within 200km of that
  waypoint. This loop was last modified 08 July 2021. */

  for(int k = 0; k < rgf; k++) {  // new
    int g = gf[k];  // index number, within struct GF[], of current feature

    // get distance & bearing from aircraft of current geographic feature.
    VinChk(VinErr = VSGIP(AirLat,AirLng,GF[g].Lat,GF[g].Lng)); 

    int inMapRange = 0;  // initially assume it is not within Map Range
    if(VinDst == 0) {    // if the aircraft is at this feature [waypoint]
      mapx = MapCtrX;    // set 'int' x co-ordinate to map centre
      mapy = MapCtrY;    // set 'int' y co-ordinate to map centre
      inMapRange = 1;    // and return that it is in range
    } 
    else {                      // else the 'g'th geographic feature is some 
      double                    // distance away from the current waypoint
        d = VinDst * ms,        // waypoint distance in map pixels 
        b = FwdAzi - CmdHdg;    // waypoint brg rel to map orientation
      mapx = rint(d * sin(b));  // rounded interger screen x-pixel
      mapy = rint(d * cos(b));  // rounded interger screen y-pixel
      if(fabs(mapx) <= 145 &&   // if this waypoint is within range
        fabs(mapy) <= 142) {    // of the map display area:
        mapx += MapCtrX;        // bias its co-ordinates to
        mapy = MapCtrY - mapy;  // the centre of the map
        inMapRange = 1;         // return that it is within map range
      }
    }      
    if(inMapRange > 0) {    // if feature within map window range
      WN[vw] = GF[g].Name;  // get its name
      MAPX[vw] = mapx;      // save its x & y screen coordinates
      MAPY[vw] = mapy; 
      if(GF[g].wpi == cwp)  // if geographic feature is current waypoint
        VWC = vw;           // set index number of this waypoint
      vw++;                 // increment the number of visible waypoints
    }
  }  // end of 'FOR EACH GEOGRAPHIC FEATURE ASSOCIATED WITH CURRENT WAYPOINT'


  int inRng = 1;    // indicates that the waypoint is within radio range
  if(WptDst > WPR)  // if it is beyond radio range
    inRng = 0;      // indicate that it is not within radio range
  clearMapArea();   // clear map area


  // RE-DRAW THE MAP IN THE EVENT OF IT BEING UPDATED OR ECLIPSED

  for(int k = 0; k < vw; k++) {  // FOR EACH VISIBLE GEOGRAPHIC FEATURE: 
    mapx = MAPX[k];              // get its x screen coordinate and
    mapy = MAPY[k];              // its y screen coordinate

    /* Draw the white circle to represent the visible waypoint
    and annotate it with its geographic name. */

    XSetForeground(XD, XG, WHITE);  // white circle
    XDrawArc(XD,XW,XG,mapx - 5,mapy - 5,10,10,0,23040);

    char *S = WN[k];       // waypoint name
    int
      LS = strlen(S),      // number of characters in waypoint name
      ls = LS * 6,         // length [in pixels] of waypoint name
      PS = ls + mapx + 9;  // window x position of end of name

    /* if this waypoint's name overshoots the right margin
    or if the route makes a left turn at this waypoint: */

    if(PS > WinLimX || (RIGHT == 1 && k == VWC))
      x = mapx - ls - 7;  // shift it the name length + 7 pixels left
    else                  // else, if it wouldn't overshoot
      x = mapx + 9;       // space it 9 pixels to right of the waypoint

    XDrawString(XD,XW,XG,x,mapy + 5,S,LS);

    if(k == VWC) {      // if this is the current feature:
      if(inRng == 1) {  // if this waypoint is within radio range:
        if(cwp > 0)     // If this is not the first waypoint in the route
          T3guideArc(mapx,mapy);
        T3drawRads(mapx,mapy);
      }
    }
  }   // end of 'FOR EACH VISIBLE GEOGRAPHIC FEATURE:'


  /* DRAW THE GPS/INERTIAL GUIDE BARS. Displays a yellow bar pointing from the
  aircraft to the waypoint it is currently approaching and a yellow bar point-
  ing from the aircraft back towards the waypoint it has just left. */

  if(inRng == 0) {           // if beyond radio range of current waypoint:
    double bf = 0, bb = 0;
    if(ToFlag == 1) {        // if aircraft approaching current waypoint
      bb = MapFwd - CmdHdg;  // brg to prev waypoint (behind aircraft)
      bf = FwdBrg - CmdHdg;  // brg to this waypoint (ahead of aircraft)
    } else {                 // else if receding from current waypoint
      bb = MapFwd - CmdHdg;  // brg to next waypoint (ahead of aircraft)
      bf = FwdBrg - CmdHdg;  // brg to this waypoint (behind aircraft)
    }
    XSetForeground(XD, XG, YELLOW);
    XDrawLine(XD,XW,XG,MapCtrX,MapCtrY,  // waypoint approach pointer
      MapCtrX + rint(100 * sin(bf)),
      MapCtrY - rint(100 * cos(bf)));
    XDrawLine(XD,XW,XG,MapCtrX,MapCtrY,  // pointer to receding waypoint
      MapCtrX + rint(100 * sin(bb)),
      MapCtrY - rint(100 * cos(bb)));
  } 


  // DISPLAY MAP'S CENTRAL CROSS-HAIRS [POSITION OF THE AIRCRAFT]
  XSetForeground(XD,XG,GREY); 
  XDrawLine(XD,XW,XG,MapCtrX,MapCtrY-10,MapCtrX,MapCtrY+10);
  XDrawLine(XD,XW,XG,MapCtrX-10,MapCtrY,MapCtrX+10,MapCtrY);

  // SHOW THE LITTLE COMPASS CROSS AT THE BOTTOM RIGHT OF THE MAP
  double a = sin(CmdHdg),  // sine and cosine of aircraft's heading
         b = cos(CmdHdg);
  x = MapCtrX + 120;       // x coordinates of centre of compass
  y = MapCtrY + 120;       // y coordinates of centre of compass

  // Draw the letter 'N' to indicate 'North'.
  XDrawString(XD,XW,XG,x - rint(23 * a) - 3,y - rint(23 * b) + 3,"N",1);

  // Draw the North-South and East-West lines of the compass.
  int
    cc = rint(15 * a),  // vertical & horizontal off-sets of the 
    dd = rint(15 * b);  // points of the compass cross
  XDrawLine(XD,XW,XG,x-cc,y-dd,x+cc,y+dd);
  XDrawLine(XD,XW,XG,x+dd,y-cc,x-dd,y+cc);
}




/* DRAW THE HORIZONTAL ROW OF CONTROL BUTTONS ACROSS THE BOTTOM OF THE
   MAP TAB. Called from 1 place each in T3show() and T3click(). */

void T3buts() {
  if(TB != 3) return;
  int h = LM;
  XSetForeground(XD,XG,DARK);   // shade for buttons background
  for(int i = 0; i < 8; i++) {  // shade the background area for each button
    XFillRectangle(XD,XW,XG,h,BY,53,BH);
    h += BW;  
  }
  const char  // annotate horizontal row of control buttons
    *A[] = {"GO","STOP","NEXT","PREV","RESET","SCALE+","SCALE-","HELP"};
  const int 
    a[] = {21,15,15,15,12, 9, 9,15},  // horiz offsets for word starts
    b[] = { 2, 4, 4, 4, 5, 6, 6, 4};  // number of letters in each word
  h = LM; int v = BY + 15;
  XSetForeground(XD,XG,GREY);   // colour for the lettering
  for(int i = 0; i < 8; i++) {  // for each of the 8 buttons:
    if(T3BS & 1 << i) 
      XSetForeground(XD,XG,WHITE);  // colour for the STOP button
    else
      XSetForeground(XD,XG,GREY);  // colour for the lettering
    XDrawString(XD,XW,XG,h + a[i],v,A[i],b[i]);
    h += BW;
  }
}




/* DISPLAY THE STATIC CONTENT OF THE MAP TAB. 
   Called only from 1 place in showTab(). */

void T3show() {
  if(TB != 3) return;
  // Draw the map graticule: the corner and mid-edge marker lines.
  XSetForeground(XD, XG, GREY);
  XDrawLine(XD,XW,XG,181, 49,190, 49);  // left top corner horizontal
  XDrawLine(XD,XW,XG,181, 50,181, 58);  // left top corner vertical
  XDrawLine(XD,XW,XG,181,351,190,351);  // left bottom corner horizontal
  XDrawLine(XD,XW,XG,181,342,181,350);  // left bottom corner vertical
  XDrawLine(XD,XW,XG,474, 49,483, 49);  // right top corner horizontal
  XDrawLine(XD,XW,XG,483, 50,483, 58);  // right top corner vertical
  XDrawLine(XD,XW,XG,474,351,483,351);  // right bottom corner horizontal
  XDrawLine(XD,XW,XG,483,342,483,351);  // right bottom corner vertical
  XDrawLine(XD,XW,XG,173,200,180,200);  // centre left horizontal
  XDrawLine(XD,XW,XG,181,191,181,209);  // centre left vertical
  XDrawLine(XD,XW,XG,484,200,491,200);  // centre right horizontal
  XDrawLine(XD,XW,XG,483,191,483,209);  // centre right vertical
  XDrawLine(XD,XW,XG,323, 49,341, 49);  // centre top horizontal
  XDrawLine(XD,XW,XG,332, 41,332, 48);  // centre top vertical
  XDrawLine(XD,XW,XG,323,351,341,351);  // centre bottom horizontal
  XDrawLine(XD,XW,XG,332,350,332,359);  // centre bottom vertical
  clearMapArea();

  // Draw the flight data annotations
  char *S[] = {
    "WAYPOINT","Name","Latitude","Longitude","Altitude",
    "Distance","Bearing","Radial","AIRCRAFT","Latitude",
    "Longitude","Altitude","Speed m/s","Req Hdg","Cmd Hdg","Cmd ROT"
  };
  int v = TL;  // base height of the top line of the text within a tab

  for(int i = 0; i < 16; i++) {  // for the 16 waypoint & aircraft data lines
    if(i == 8) v += 10;          // extra gap above 'AIRCRAFT' title
    if(i == 0 || i == 8)         // if it's a title line
         XSetForeground(XD,XG,YELLOW);  // menu titles in yellow
    else XSetForeground(XD,XG,GREY);    // annotations in white
    XDrawString(XD,XW,XG,LM,v,S[i],strlen(S[i]));
    v += 18;  // drop to next line
  }

  // ANNOTATE THE STOP BUTTON ACCORDING TO CURRENT FLIGHT STATE

  if(RunState) {  // if the aircraft is currently in flight
    T3BS |= 1;    // highlight the MAP tab's GO  button
    T3BS &= ~2;   // dim the MAP tab's STOP button
    T6BS |= 1;    // highlight the INPUT tab's GO  button
    T6BS &= ~2;   // dim the INPUT tab's STOP button
    T7BS |= 1;    // highlight the OUTPUT tab's GO  button
    T7BS &= ~2;   // dim the OUTPUT tab's STOP button
  } else {        // else, the aircraft is NOT currently in flight, so
    T3BS |= 2;    // highlight the MAP tab's STOP button
    T3BS &= ~1;   // dim the MAP tab's GO  button
    T6BS |= 2;    // highlight the INPUT tab's STOP button
    T6BS &= ~1;   // dim the INPUT tab's GO  button
    T7BS |= 2;    // highlight the OUTPUT tab's STOP button
    T7BS &= ~1;   // dim the OUTPUT tab's GO  button
  }
  T3buts();       // show control buttons for the MAP Tab
  if(Ready)       // provided route & waypoint data is loaded and initialised
    T3updt();     // display the map
}




/* ONE OF THE MAP TAB'S CONTROL BUTTONS WAS PRESSED. Button numbers:
   0:start, 1:stop, 2:next, 3:prev, 4:reset. Called from 2 places in
   T7CL() and from one place each in updtTrack() and MEH(). */

void T3click(int x) {
  switch(x) {             // switch on button number
    case 0:               // GO THE CURRENT FLIGHT
      if(RunState == 0    // provided a flight is not currently in progress
      && Ready) {         // if the aircraft is not in flight
        T3BS |= 1;        // highlight the  GO  button
        T3BS &= ~2;       // dim the STOP button
        T6BS |= 1;        // highlight the input tab's GO  button
        T6BS &= ~2;       // dim the input tab's STOP button
        T7BS |= 1;        // highlight the output tab's GO  button
        T7BS &= ~2;       // dim the output tab's STOP button
        CM = 19;          // message No. 19 "Flight in progress."
        MC = GREEN;       // colour in which message is displayed
        CmdSpd = MinSpd;  // set initial take-off speed
        AirBnk = 0;       // zero the aircraft actual roll [bank] angle
        CmdBnk = 0;       // zero the roll [bank] command
        AirAtt = 0;       // zero the actual pitch
        CmdAtt = 0;       // zero the pitch command
        RunState = 1;     // set flight active [running]
        LN = 0;           // reset output tab's line number
        T3updt();         // update the map to show this
      } break;
    case 1:               // STOP THE CURRENT FLIGHT
      if(RunState) {      // if the aircraft is currently in flight:
        T3BS |= 2;        // highlight the STOP button
        T3BS &= ~1;       // dim the  GO  button
        T6BS |= 2;        // highlight the input tab's STOP button
        T6BS &= ~1;       // dim the input tab's GO  button
        T7BS |= 2;        // highlight the output tab's STOP button
        T7BS &= ~1;       // dim the output tab's GO  button
        CM = 20;          // message No. 20 "Flight paused."
        MC = YELLOW;      // colour in which message is displayed
        RunState = 0;     // stop the flight
        T3updt();         // update the map to show this
      } break;
    case 2:               // MOVE AIRCRAFT TO THE NEXT WAYPOINT
      if(cwp < LWP)       // provided aircraft is not already at
        cwp++;            // the last waypoint, increment waypoint number
      RunState = 0;       // stop the flight if it is running
      cgf = WP[cwp].cgf;  // reference to waypoint data
      setAircraftAt();    // set aircraft to start of current route
      T3updt();           // update the map to show this
      break;
    case 3:               //MOVE AIRCRAFT TO THE PREVIOUS WAYPOINT
      if(cwp > 0)         // So provided the aircraft is not at the first
        cwp--;            // waypoint, decrement the waypoint number
      RunState = 0;       // stop the flight if it is running
      cgf = WP[cwp].cgf;  // reference to waypoint data
      setAircraftAt();    // set aircraft to start of current route
      T3updt();           // update the map to show this
      break;
    case 4:               // RESET AIRCRAFT TO BEGINNING OF CURRENT ROUTE
      cwp = 0;            // set current waypoint as first in the route
      RunState = 0;       // stop the flight if it is running
      cgf = WP[cwp].cgf;  // reference to waypoint data
      setAircraftAt();    // set aircraft to start of current route
      T3updt();           // update the map to show this
      break;
    case 5:
      if(MapSize < 5) {      // if map not currently at largest scale
        ms = MS[++MapSize];  // set map scale to next higher value
        T3updt();            // update the map to show this
      } break;
    case 6:
      if(MapSize > 0) {      // if map not currently at smallest scale
        ms = MS[--MapSize];  // set map scale to next lower value
        T3updt();            // update the map to show this
      } break;
    case 7: helpPage("navigation/nav.html#map");
  } T3buts();             // re-display the buttons
}





// ------FUNCTIONS PERTAINING TO TAB 4 [ROLL & PITCH COMMAND INDICATOR]------


/* UPDATE THE COMMAND HEIGHT PROFILE GRAPH. Called only from 1 place each in
   T4plot() and T4show(). */

void T4graph() {
  if(TB == 4 && T4BS & 64) {  // if in Tab 6 and the PLOT button is lit
    XSetForeground(XD,XG,BLACK);
    XFillRectangle(XD,XW,XG,LM+30,70,436,280);  // clear the graph panel
    XSetForeground(XD,XG,0x553300);
    int j = 100;
    for(int i = 0; i < 6; i++) {  // display brown horizontal graticule
      XDrawLine(XD,XW,XG,LM+30,j,LM+465,j);
      j += 50;
    }
    XSetForeground(XD,XG,GREEN);
    for(int i = 0; i < 436; i++) {  // display all plots
      int y = T4P[i];
      if(y > 280) y = 280;  // still show a plot out of range heights
      if(y > 0)
        XDrawPoint(XD,XW,XG,LM + 30 + i,350 - T4P[i]);
    }
  }
}




/* DISPLAY THE NEXT DOT OF THE GRAPH. The graph starts from the left of the
   screen. When it reached the right side of the graph, the trace is pushed
   backwards each time to make way for the new plot. Called from the 1-second
   clock loop in main() irrespectively of whether or not the PLOT graph is on
   screen.*/

void T4plot() {
  int y = CmdHgt;         // height of reference waypoint
  if(T4PC < 436)          // if current plot is not at end of x-axis
    T4P[T4PC++] = y;      // put the current plot in this position
  else {                  // otherwise
    for(int i = 0; i < 435; i++)
      T4P[i] = T4P[i+1];  // shift all plots one back in the array and
    T4P[435] = y;         // put the new plot in the vacated last position
  }
  T4graph();
}




/* DISPLAY THE ROLL AND PITCH INDICATOR BARS
   Attitude Command Indicator is a horizontal green bar that moves up and down
   from -150 pixels to + 150 pixels representing a real range -30 to +30 deg-
   rees. Each vertical pixel thus represents a fifth of a degree. The integer
   'CmdAtt' runs from -150 to +150 in fifths of a degree. For presented values
   of 'CmdAtt' < -30 and > +30 degrees, the pitch bar stays at full scale but
   turns red. The Bank Command Indicator is a yellow bar that rotates around
   the centre of the blue indicator panel. The ends of the two arcs indicate
   a 45 degree bank command. Beyond a bank command angle of 1 radian, the bar
   turns red and there is an enforced upper bank command limit of 1.5 radians.
   Called from T4show() and updtTrack(). */

void T4updt() {
  AirBnk += 0.2 * (CmdBnk - AirBnk) * (CmdSpd / MaxSpd);
  AirAtt += 0.2 * (CmdAtt - AirAtt) * (CmdSpd / MaxSpd);
  if(TB != 4 || T4BS & 64) return;
  clearMapArea();
  XSetForeground(XD,XG,BLACK);
  XFillRectangle(XD,XW,XG,110,50,60,300);  // clear the figures panel
  XSetForeground(XD,XG,GREY);              // graticule colour
  XDrawLine(XD,XW,XG,333,50,333,351);      // vertical centre line
  XDrawLine(XD,XW,XG,185,201,480,201);     // horizontal centre line
  XDrawArc(XD,XW,XG,182,50,301,301,-2880,5760);  // right arc
  XDrawArc(XD,XW,XG,182,50,301,301, 8640,5760);  // left arc

  // COMPUTE PITCH & ROLL COMMANDS AND APPLY IN-FLIGHT LIMITS TO THEM
  if(RunState == 1) {        // if in flight [test buttons are ineffective]
    CmdBnk = CmdROT * -8.0;  /* compute the Roll Command
                        |
    These constants need to be adjusted to match the flight 
    characteristics of the particular air vehicle concerned.
                       |                                 */
    CmdAtt = CmdROC * 0.045;  // compute the Pitch Command
    if(CmdAtt > +0.5)         // In-flight command pitch limits 
      CmdAtt = +0.5;          // are plus or minus half a radian
    else if(CmdAtt < -0.5)
      CmdAtt = -0.5;
  }
  // INDICATOR'S ABSOLUTE MAXIMUM ROLL COMMAND LIMIT = 1.5 RADIANS
  if(CmdBnk > +1.5) CmdBnk = +1.5;       // limit the rightwards roll
  else if(CmdBnk < -1.5) CmdBnk = -1.5;  // limit the leftwards roll

  // INDICATOR'S MAXIMUM SAFE ROLL COMMAND LIMIT = 1 RADIAN
  int c = YELLOW;
  if(CmdBnk > 1 || CmdBnk < -1) c = RED;
  XSetForeground(XD,XG,c);  // colour of indicator bars

  // DISPLAY THE CURRENT POSITION OF THE ROLL COMMAND BAR
  int b = 145 * sin(CmdBnk),  // x-dimension of containing rectangle
      a = 145 * cos(CmdBnk);  /* y-dimension of containing rectangle
           |
           half-diagonal of containing rectangle [a constant] 
                      x-coord of the centre of the graph
                      |       y-coord of the centre of the graph
                      |       |                               */
  XDrawLine(XD,XW,XG,333 + a,201 + b,333 - a,201 - b);

  // DISPLAY THE AIRCRAFT'S CURRENT ACTUAL ROLL [ROLL] ANGLE



  if(T4BS & 2) {  // provided the AUTO button is bright
    XSetForeground(XD,XG,0x00FFFF);
    b = 145 * sin(AirBnk),  // x-dimension of containing rectangle
    a = 145 * cos(AirBnk);  /* y-dimension of containing rectangle
         |
         half-diagonal of containing rectangle [a constant] 
                        x-coord of the centre of the graph
                        |       y-coord of the centre of the graph
                        |       |                               */
    XDrawLine(XD,XW,XG,333 + a,201 + b,333 - a,201 - b);
  }
  // DISPLAY THE CURRENT POSITION OF THE PITCH COMMAND BAR
  a = CmdAtt * 4 * DPR;  // pitch angle from degrees to pixels 
  c = YELLOW;            // default colour for pitch bar
  if(a > +149) {         // positive excusion limit
    a = +149; c = RED;
  } else if(a < -149) {  // negative excursion limit
    a = -149; c = RED;
  }
  a += 201;              // add y-coordinate of graphical origin
  XSetForeground(XD,XG,c);          // colour of pitch indicator bar
  XDrawLine(XD,XW,XG,222,a,444,a);  // pitch indicator

  // DISPLAY THE POSITION OF THE ARICRAFT'S ACTUAL PITCH
  if(T4BS & 2) {  // provided the AUTO button is bright
    a = 201 + AirAtt * 5 * DPR;       // pitch angle from degrees to pixels 
    XSetForeground(XD,XG,0x00FFFF);
    XDrawLine(XD,XW,XG,222,a,444,a);  // pitch indicator
  }
  // DISPLAY THIS INFORMATION IN FIGURES ON THE LEFT OF THE INDICATOR
  int v = TL;
  XSetForeground(XD,XG,WHITE);  // write all data items in white
  XDrawString(XD,XW,XG,LM+33,v,showName(GF[WP[cwp].cgf].Name),20); // Wpt Name
  v += IL;
  XDrawString(XD,XW,XG,LM+92,v+=IL,showHeight(WptDst),10);  // Waypoint Dist.
  XDrawString(XD,XW,XG,LM+92,v+=IL,showHeight(CmdSpd),10);  // Aircraft Speed
  v += IL;
  XSetForeground(XD,XG,YELLOW);
  XDrawString(XD,XW,XG,LM+110,v+=IL,showBip(CmdBnk),7);  // Roll Command
  XDrawString(XD,XW,XG,LM+110,v+=IL,showBip(CmdAtt),7);  // Pitch Command
  v += IL;
  if(T4BS & 2) {
    XSetForeground(XD,XG,0x00FFFF);
    XDrawString(XD,XW,XG,LM+110,v+=IL,showBip(AirBnk),7);  // Actual Roll
    XDrawString(XD,XW,XG,LM+110,v+=IL,showBip(AirAtt),7);  // Actual Pitch
  } else { v += IL; v += IL; }
  v += IL;
  XSetForeground(XD,XG,WHITE);
  XDrawString(XD,XW,XG,LM+92,v+=IL,showHeight(CmdHgt),10);  // Command Height
  XDrawString(XD,XW,XG,LM+92,v+=IL,showHeight(AirHgt),10);  // Actual Height
  v += IL;
  XDrawString(XD,XW,XG,LM+110,v+=IL,showBip(CmdROT),7);  // Rate of Turn
  XDrawString(XD,XW,XG,LM+110,v+=IL,showBip(CmdROC),7);  // Rate of Climb
}




/* DRAW HORIZONTAL ROW OF CONTROL BUTTONS ACROSS BOTTOM OF ATAV TAB
   Called from only one place in T1show(). */

void T4buts() {
  if(TB != 4) return;  // bail out if ROUTE tab not currently visible
  int h = LM;          // set h to left side of first button

  XSetForeground(XD,XG,DARK);  // shade for buttons background

  for(int i = 0; i < 8; i++) {  // shade the background area for each button
    XFillRectangle(XD,XW,XG,h,BY,53,BH);
    h += BW;  // move across to next button
  }
  char  // annotate horizontal row of control buttons
    *A[] = {"MAN","AUTO","LEFT","RIGHT","UP","DOWN","PLOT","HELP"};
  int 
    a[] = {18,15,15,12,21,15,15,15},  // horiz offsets for word starts
    b[] = { 3, 4, 4, 5, 2, 4, 4, 4};  // number of letters in each word
  h = LM; int v = BY + 15;      // x & y coords of button block
  for(int i = 0; i < 8; i++) {  // for each of the 8 buttons:
    if(T4BS & 1 << i)
      XSetForeground(XD,XG,WHITE);  // colour for the lettering
    else
      XSetForeground(XD,XG,GREY);   // colour for the lettering
    XDrawString(XD,XW,XG,h + a[i],v,A[i],b[i]);
    h += BW;  // move across [rightwards] to next button
  }
}




/* DISPLAY AND UPDATE THE WAYPOINT NAME ON THE HEIGHT GRAPH
   Called from 1 place near the beginning of updtTrack() and also
   from 1 place in T4show(). */

void T4name() {  // show name of current waypoint on PLOT height graph
  if(TB == 4 && T4BS & 64) {      // if in Tab 6 and the PLOT button is lit
    XSetForeground(XD,XG,BLACK);  // clear the waypoint name field
    XFillRectangle(XD,XW,XG,LM+346,45,120,12);
    XSetForeground(XD,XG,WHITE);  // display the name of the next waypoint
    XDrawString(XD,XW,XG,LM+346,55,showName(GF[WP[cwp].cgf].Name),20);
  }
}




/* DISPLAY THE STATIC CONTENT OF THE PITCH & ROLL COMMANDS INDICATOR TAB OR
   THE COMMAND HEIGHT GRAPH TAB. Called from only 1 place in showTab(). */

void T4show() {
  char *S[7] = {"300","250","200","150","100","050","000"};
  clearTabArea();

  if(T4BS & 64) {  // if the PLOT button is bright

    // DISPLAY THE STATIC CONTENT OF THE COMMAND HEIGHT GRAPH
    XSetForeground(XD,XG,GREY);
    XDrawLine(XD,XW,XG,LM+27,50,LM+27,350);  // draw the height scale
    int j = 50;
    for(int i = 0; i < 31; i++) {
      XDrawLine(XD,XW,XG,LM+22,j,LM+27,j);
      j += 10;
    }
    j = 55;
    for(int i = 0; i < 7; i++) {
      XDrawString(XD,XW,XG,LM,j,S[i],3);
      j += 50;
    }
    XDrawString(XD,XW,XG,LM+33,55,
      "CMD HGT [metres above sea level]   Waypoint:",44);
    T4name();
    T4graph();

  } else {  // else the PLOT button is dim, so:

    // DISPLAY THE STATIC CONTENT OF THE ATTITUDE [PITCH & ROLL] INDICATOR 
    int v = TL;
    XSetForeground(XD,XG,GREY);
    XDrawString(XD,XW,XG,LM,v,"WAYPOINT",8);
    v += IL;
    XDrawString(XD,XW,XG,LM,v+=IL,"DISTANCE:",9);
    XDrawString(XD,XW,XG,LM,v+=IL,"SPEED:",6);
    v += IL;
    XSetForeground(XD,XG,YELLOW);
    XDrawString(XD,XW,XG,LM,v+=IL,"ROLL COMMAND:",13);
    XDrawString(XD,XW,XG,LM,v+=IL,"PITCH COMMAND:",14);
    v += IL;
    XSetForeground(XD,XG,0x00FFFF);
    XDrawString(XD,XW,XG,LM,v+=IL,"ROLL ACTUAL:",12);
    XDrawString(XD,XW,XG,LM,v+=IL,"PITCH ACTUAL:",13);
    v += IL;
    XSetForeground(XD,XG,GREY);
    XDrawString(XD,XW,XG,LM,v+=IL,"COMMAND HEIGHT:",15);
    XDrawString(XD,XW,XG,LM,v+=IL,"ACTUAL HEIGHT:",14);
    v += IL;
    XDrawString(XD,XW,XG,LM,v+=IL,"RATE OF TURN:",13);
    XDrawString(XD,XW,XG,LM,v+=IL,"RATE OF CLIMB:",14);
  }
  T4buts();  // re-display the control buttons at the bottom
  T4updt();  // initialise the Tab 4's variable content
}




/* HANDLES MOUSE CLICKS ON TAB 4'S BOTTOM BUTTONS.
   Called from only one place in MEH [the Mouse Event Handler]. */

void T4click(int x) {    // 
  switch(x) {            // switch on button number
    case 0:              // the MAN button was clicked
      if(!(T4BS & 1)) {  // if the MAN button is off
        T4BS |= 1;       // switch it on
        T4BS &= ~2;      // switch off the AUTO button
      } break;
    case 1:              // the AUTO button was clicked
      if(!(T4BS & 2)) {  // if the AUTO button is off
        T4BS |= 2;       // switch it on
        T4BS &= ~1;      // switch off the MAN button
      } break;
  }
  if(RunState == 0) {    // provided the aitcraft is not currently in flight
    switch(x) {          // switch on button number
      case 2: CmdBnk += RPD; break;  // bank left
      case 3: CmdBnk -= RPD; break;  // bank right
      case 4: CmdAtt += RPD; break;  // nose up
      case 5: CmdAtt -= RPD; break;  // nose down
    }
  }
  switch(x) {
    case 6:             // the PLOT button was pressed
      if(!(T4BS & 64))  // if the PLOT button is off
        T4BS |= 64;     // switch it on
      else
        T4BS &= ~64;    // switch it off
      break;
    case 7:
      helpPage("navigation/nav.html#att");
      return;
  }
  T4show();             // re-display the tab content
}





// -------------------FUNCTIONS PERTAINING TO TAB 5--------------------------


// UPDATE THE AZIMUTH COMMAND DISPLAY. Called only from 1 place in main().

void T5updt() {
  AirHdg += 0.02 * (CmdHdg - AirHdg) * (CmdSpd / MaxSpd);
  if(TB != 5) return;  // the map only appears on Tab 0
  clearMapArea();
  XSetForeground(XD,XG,BLACK);
  XFillRectangle(XD,XW,XG,110,50,60,180);     // clear the figures panel
  XSetForeground(XD,XG,GREY);                 // graticule colour
  XDrawArc(XD,XW,XG,182,50,301,301,0,23040);  // compass circle
  XDrawLine(XD,XW,XG,334,50,334,351);         // vertical centre line
  XDrawLine(XD,XW,XG,185,202,480,202);        // horizontal centre line

  // DISPLAY THE CURRENT POSITION OF THE HEADING COMMAND BAR
  XSetForeground(XD,XG,YELLOW);
  int a,b;
  double c = CmdHdg - AirHdg;
  a = 145 * sin(c);  // x-dimension of containing rectangle
  b = 145 * cos(c);  /* y-dimension of containing rectangle
           |
           half-diagonal of containing rectangle [a constant] 
                      x-coord of the centre of the graph
                      |       y-coord of the centre of the graph
                      |       |                               */
  XDrawLine(XD,XW,XG,334 - a/3,202 + b/3,334 + a,202 - b);

  // DISPLAY THE CURRENT POSITION OF THE REQUIRED HEADING BAR
  XSetForeground(XD,XG,0x00FFFF);
  c = ReqHdg - AirHdg;
  a = 145 * sin(c);  // x-dimension of containing rectangle
  b = 145 * cos(c);  /* y-dimension of containing rectangle
           |
           half-diagonal of containing rectangle [a constant] 
                      x-coord of the centre of the graph
                      |         y-coord of the centre of the graph
                      |         |                               */
  XDrawLine(XD,XW,XG,334 - a/3,202 + b/3,334 + a,202 - b);

  // DISPLAY THE NORTH POINTER
  XSetForeground(XD,XG,GREEN);
  a = 100 * sin(-AirHdg);  // x-dimension of containing rectangle
  b = 100 * cos(-AirHdg);  /* y-dimension of containing rectangle
           |
           half-diagonal of containing rectangle [a constant] 
                      x-coord of the centre of the graph
                      |       y-coord of the centre of the graph
                      |       |                               */
  XDrawLine(XD,XW,XG,334 - a,202 + b,334 + a,202 - b);
  a *= 1.1; b *= 1.1;
  XDrawString(XD,XW,XG,332 + a,206 - b,"N",1);  // N for North
  XDrawString(XD,XW,XG,332 - a,206 + b,"S",1);  // S for South

  a = 100 * sin(HalfPi-AirHdg);  // x-dimension of containing rectangle
  b = 100 * cos(HalfPi-AirHdg);  /* y-dimension of containing rectangle
           |
           half-diagonal of containing rectangle [a constant] 
                      x-coord of the centre of the graph
                      |       y-coord of the centre of the graph
                      |       |                               */
  XDrawLine(XD,XW,XG,334 - a,202 + b,334 + a,202 - b);
  a *= 1.1; b *= 1.1;
  XDrawString(XD,XW,XG,332 + a,206 - b,"E",1);  // N for North
  XDrawString(XD,XW,XG,332 - a,206 + b,"W",1);  // N for North

  int v = TL;
  XSetForeground(XD,XG,WHITE);  // write all data items in white
  XDrawString(XD,XW,XG,LM+33,v,showName(GF[WP[cwp].cgf].Name),20); // Wpt Name
  v += IL;
  XDrawString(XD,XW,XG,LM+92,v+=IL,showHeight(WptDst),10);  // Waypoint Dist.
  XDrawString(XD,XW,XG,LM+92,v+=IL,showHeight(CmdSpd),10);  // Aircraft Speed
  v += IL;
  XDrawString(XD,XW,XG,LM+110,v+=IL,showBip(CmdROT),7);     // Rate of Turn
  v += IL;
  XSetForeground(XD,XG,0x00FFFF);
  XDrawString(XD,XW,XG,LM+128,v+=IL,showRat(ReqHdg),3);  // Required Heading
  XSetForeground(XD,XG,YELLOW);
  XDrawString(XD,XW,XG,LM+128,v+=IL,showRat(CmdHdg),3);  // Heading Command
  XSetForeground(XD,XG,WHITE);
  XDrawString(XD,XW,XG,LM+128,v+=IL,showRat(AirHdg),3);  // Aircraft Speed
}




/* DRAW HORIZONTAL ROW OF CONTROL BUTTONS ACROSS BOTTOM OF AZImuth TAB
   Called from only one place in T5show(). */

void T5buts() {
  if(TB != 5) return;  // bail out if ROUTE tab not currently visible
  int h = LM;          // set h to left side of first button

  XSetForeground(XD,XG,DARK);   // shade for buttons background

  // shade the background area for each button
  for(int i = 0; i < 8; i++) {
    XFillRectangle(XD,XW,XG,h,BY,53,BH);
    h += BW;  // move across to next button
  }
  char  // annotate horizontal row of control buttons
    *A[] = {"","","","","","","ATAV","HELP"};
  int 
    a[] = {0,0,0,0,0,0,15,15},  // horiz offsets for word starts
    b[] = {0,0,0,0,0,0, 4, 4};  // number of letters in each word
  h = LM; int v = BY + 15;      // x & y coords of button block
  XSetForeground(XD,XG,GREY);   // colour for the lettering

  for(int i = 0; i < 8; i++) {  // for each of the 8 buttons:
    XDrawString(XD,XW,XG,h + a[i],v,A[i],b[i]);
    h += BW;  // move across [rightwards] to next button
  }
}




/* DISPLAY THE STATIC CONTENT OF THE AZIMUTH COMMANDS INDICATOR TAB.
   Not implemented in this demonstration version of the Global Navigator II.
   Called from only 1 place in showTab(). */

void T5show() {
  int v = TL;
  XSetForeground(XD,XG,GREY);
  XDrawString(XD,XW,XG,LM,v,"WAYPOINT",8);
  v += IL;
  XDrawString(XD,XW,XG,LM,v+=IL,"DISTANCE:",9);
  XDrawString(XD,XW,XG,LM,v+=IL,"SPEED:",6);
  v += IL;
  XDrawString(XD,XW,XG,LM,v+=IL,"RATE-OF-TURN:",13);
  v += IL;
  XSetForeground(XD,XG,0x00FFFF);
  XDrawString(XD,XW,XG,LM,v+=IL,"REQD HDG:",9);
  XSetForeground(XD,XG,YELLOW);
  XDrawString(XD,XW,XG,LM,v+=IL,"CMND HDG:",9);
  XSetForeground(XD,XG,GREY);
  XDrawString(XD,XW,XG,LM,v+=IL,"REAL HDG:",9);
  T5buts();
  T5updt();
}




/* THE AZImuth TAB'S HELP BUTTON WAS PRESSED.
Called from only one place in MEH(). */

void T5click(int x) {
  switch(x) {  // switch on button number
    case 7:
      helpPage("navigation/nav.html#azi");
  }
}





// --------------FUNCTIONS PERTAINING TO TAB 6 [COMMAND INPUTS]--------------


/* DISPLAYS THE INPUTS FOR LATITUDE, LONGITUDE & HEIGHT. Starts filling the
   panel from the top. Once the panel is full, shunts all lines upwards one
   place to make room for each new command line as it arrives. Called only
   from 1 place each in updtTrack() and T6show(). */

void T6updt() {   // INPUT TAB
  int
    v = TL + 20,  // set y-coord of first line of output
    M = 16,       // default panel-line number of the most recent input
    Q = NL - 16,  // line number bias for shunted lines
    P = 1;        // shunt flag is set by default

  if(TB == 6) {   // clear panel if this tab is currently visible
    XSetForeground(XD,XG,BLACK);
    XFillRectangle(XD,XW,XG,LM,TL+6,466,288);
  }

  if(NL < 17) {  // if the panel's display area is not yet full
    M = NL;      // loop only as far as the most recent input line
    Q = 0;       // line number is simply the panel's line index
    P = 0;       // set the lines shunt-up flag to 'don't shunt'
  }

  for(int i = 0; i < M; i++) {  // for each visible input line
    if(P)                          // if the shunt flag is set
      for(int j = 0; j < 71; j++)  // for each character of input line
        UO[i][j] = UO[i + 1][j];   // shift it up to previous line position

    XSetForeground(XD,XG,GREY);    // colour for past inputs

    if(i == M - 1) {                // if this is the latest input
      XSetForeground(XD,XG,WHITE);  // set colour for latest input
      char *S = showInt(i+Q,5);     // line number as 5-char string
      sprintf(UO[i],"%21.18lf  %22.15lf  %26.19lf ",AirLat,AirLng,AirHgt);
    }

    if(TB == 6) {  // if this tab is currently visible
      XDrawString(XD,XW,XG,LM,v,showInt(i+Q,5),5);  // show line number
      XDrawString(XD,XW,XG,LM+36,v,UO[i],71);       // show commands
    }

    v += 17;  // drop down to next line
  }

  if(RunState)  // provided the flight is in progress
    NL++;       // advance command line number ready for next pass
}




/* DRAW THE HORIZONTAL ROW OF CONTROL BUTTONS ACROSS THE BOTTOM
OF THE INPUT TAB. Called only from 1 place in T6show(). */

void T6buts() {
  int
    h = LM;  // x-coordinate of the left side of a button

  XSetForeground(XD,XG,DARK);   // shade for buttons background

  // shade the background area for each button
  for(int i = 0; i < 8; i++) {
    XFillRectangle(XD,XW,XG,h,BY,53,BH);
    h += BW;  
  }

  const char  // annotate horizontal row of control buttons
    *A[] = {"GO","STOP","LIVE","SIMU","","    ","    ","HELP"};

  const int 
    a[] = {21,15,15,15,0,0,0,15},  // horiz offsets for word starts
    b[] = { 2, 4, 4, 4,0,0,0, 4};  // number of letters in each word

  h = LM;
  int
    v = BY + 15;
  XSetForeground(XD,XG,GREY);   // colour for the lettering

  for(int i = 0; i < 8; i++) {  // for each of the 8 buttons:
    if(T6BS & 1 << i) {
      if(i == 2) 
        XSetForeground(XD,XG,RED);  // LIVE button not implemented
      else
        XSetForeground(XD,XG,WHITE);  // colour for the button
    } else
      XSetForeground(XD,XG,GREY);  // colour for the lettering
    XDrawString(XD,XW,XG,h + a[i],v,A[i],b[i]);
    h += BW;
  }
}




/* SETS UP THE HEADINGS AND BUTTONS FOR THE INPUT TAB.
Re-displays current command lines for a flight that is
still active. Called only from 1 place in showTab(). */

void T6show() {
  if(TB != 6) return;
  XSetForeground(XD,XG,YELLOW);
  XDrawString(XD,XW,XG,LM+3,TL,"LINE",4);
  XDrawString(XD,XW,XG,LM+42,TL,"LATITUDE (IN RADIANS)",21);
  XDrawString(XD,XW,XG,LM+192,TL,"LONGITUDE (IN RADIANS)",22);
  XDrawString(XD,XW,XG,LM+348,TL,"HEIGHT (IN METRES)",18);
  T6buts();
  T6updt();
}




/* INPUT TAB'S BUTTONS CONTROL LOGIC [Tab4CL]: The first 2 buttons are
   mutually exclusive. The next 2 buttons second 2 are also mutually ex-
   clusive. The states of the INPUT TAB'S control buttons are kept in the
   variable T6BS [Tab 4 Button States] as the states of the lower 8 bits,
   the least significant bit representing the state of the first button [GO].
   Called from 4 places in T7click(). 

T6BS: 000001 [0X01] = GO on       [bits going from right to left repres- 
      000010 [0X02] = STOP on      ent buttons going from left to right]
      000100 [0X04] = LIVE on
      001000 [0X08] = SIMU on  */

void Tab4CL(int x) {  // x = button number from left to right
  int y = 1 << x;     // set the bit for button 'x'
  if(!(T6BS & y)) {   // if button 'x' is dim
    switch(x) {       // switch on button number
      case 0:         // if the GO button is dim
        T3click(0);   // start the current flight
        break;
      case 1:         // if the STOP button is dim
        T3click(1);   // stop the current flight
        break;
      case 2:         // if the LIVE button is dim
        InFlg = 0;    // should set to 1 [LIVE] but this isn't implemented
        T6BS &= ~8;   // dim the SIMU button
        break;
      case 3:         // if the SIMU button is bright
        InFlg = 0;    // set for simulated input
        T6BS &= ~4;   // dim the LIVE button
        break;
    } T6BS |= y;      // brighten button 'x'
  } T6buts();         // re-display the buttons
}




/* ONE OF THE ROW OF 5 BUTTONS AT THE BOTTOM OF THE INPUT TAB WAS CLICKED
The values in the if() statements are the left and right edges of the
buttons in pixels. Called from only one place in MEH() case 2: case 4: */

void T6click(int x) {
  if(mX > 16 && mX < 70)         // if clicked on 'GO' button
    Tab4CL(0);                   // start the currently selected flight
  else if(mX > 75 && mX < 129)   // if clicked on 'STOP' button
    Tab4CL(1);                   // stop the currently selected flight
  else if(mX > 134 && mX < 188)  // if LIVE button was clicked
    Tab4CL(2);                   // enable/disable live INPUT
  else if(mX > 193 && mX < 247)  // if the SIMU button was clicked
    Tab4CL(3);                   // enable/disable SIMUlated input
  else if(mX > 429 && mX < 483)  // if the HELP button was clicked
    helpPage("navigation/nav.html#input");
}





// --------------FUNCTIONS PERTAINING TO TAB 7 [COMMAND OUTPUTS]--------------


/* DISPLAYS THE ACTUAL COMMAND OUTPUTS FOR HEADING, HEIGHT AND SPEED.
   Starts filling the panel from the top. Once the panel is full, shunts
   all lines upwards 1 place to make room for each new command line as it
   arrives. Called only from 1 place each in updtTrack() & T7show(). */

void T7updt() {   // OUTPUT TAB
  int
    v = TL + 20,  // set y-coord of first line of output
    M = 16,       // default panel-line number of the most recent command
    Q = LN - 16,  // line number bias for shunted lines
    P = 1;        // shunt flag is set by default

  if(TB == 7) {  // clear panel if this tab is currently visible
    XSetForeground(XD,XG,BLACK);
    XFillRectangle(XD,XW,XG,LM,TL+6,466,288);
  }

  if(LN < 17) {  // if the panel's display area is not yet full
    M = LN;      // loop only as far as the most recent command line
    Q = 0;       // line number is simply the panel's line index
    P = 0;       // set the lines shunt-up flag to 'don't shunt'
  }

  for(int i = 0; i < M; i++) {       // for each visible command line
    if(P)                            // if the shunt flag is set
      for(int j = 0; j < 71; j++)    // for each character of command line
        OU[i][j] = OU[i + 1][j];     // shift it up to previous line position

    XSetForeground(XD,XG,GREY);      // colour for past commands

    if(i == M - 1) {                 // if this is the latest command
      XSetForeground(XD,XG,WHITE);   // set colour for latest command
      char *S = showInt(i+Q,5);      // line number as 5-char string

      sprintf(OU[i],"%21.18lf  %22.15lf  %26.19lf ",CmdHdg,CmdHgt,CmdSpd);

      if(FileOut) {                  // if file output is active
        for(int j = 0; j < 5; j++)   // for each character of line number
          fputc(S[j],FO);            // put the 5 characters to the file
        fputc(' ',FO);               // space character [separator]
        for(int j = 0; j < 71; j++)  // for each character of command line
          fputc(OU[i][j],FO);        // output it to the file 'output.txt'
        fputc('\n',FO);              // terminate the line with a 'new line'
      }

      if(StdOut) {                   // if output to terminal is active
        for(int j = 0; j < 5; j++)   // for each character of line number
          fputc(S[j],stdout);        // put the 5 characters to the file
        fputc(' ',stdout);           // space character [separator]
        for(int j = 0; j < 71; j++)  // for each character of command line
          fputc(OU[i][j],stdout);    // output it to the file 'output.txt'
        fputc('\n',stdout);          // terminate the line with a 'new line'
      }
    }
    if(TB == 7) {  // if this tab is currently visible
      XDrawString(XD,XW,XG,LM,v,showInt(i+Q,5),5);  // show line number
      XDrawString(XD,XW,XG,LM+36,v,OU[i],71);       // show commands
    }
    v += 17;  // drop down to next line
  }
  if(RunState)  // if flight is in progress
    LN++;       // advance command line number ready for next pass
}




/* DRAW THE HORIZONTAL ROW OF CONTROL BUTTONS ACROSS THE BOTTOM OF THE OUTPUT
   TAB. Called from only 1 place each in T7show() and T7CL(). */

void T7buts() {
  int
    h = LM;  // x-coordinate of the left side of a button

  XSetForeground(XD,XG,DARK);   // shade for buttons background

  // shade the background area of each button
  for(int i = 0; i < 8; i++) {
    XFillRectangle(XD,XW,XG,h,BY,53,BH);
    h += BW;  
  }

  const char  // annotate horizontal row of control buttons
    *A[] = {"GO","STOP","STDOUT","FILE","    ","    ","    ","HELP"};

  const int 
    a[] = {21,15, 9,15,15,15,15,15},  // horiz offsets for word starts
    b[] = { 2, 4, 6, 4, 4, 4, 4, 4};  // number of letters in each word

  h = LM;
  int
    v = BY + 15;
  XSetForeground(XD,XG,GREY);   // colour for the lettering
  for(int i = 0; i < 8; i++) {  // for each of the 8 buttons:
    if(T7BS & 1 << i) 
      XSetForeground(XD,XG,WHITE);  // colour for the STOP button
    else
      XSetForeground(XD,XG,GREY);  // colour for the lettering
    XDrawString(XD,XW,XG,h + a[i],v,A[i],b[i]);
    h += BW;
  }
}




/* SETS UP THE HEADINGS AND BUTTONS FOR THE OUTPUT TAB. 
   Re-displays current command lines for a flight that is
   still active. Called only from 1 place in showTab(). */

void T7show() {
  if(TB != 7) return;
  XSetForeground(XD,XG,YELLOW);
  XDrawString(XD,XW,XG,LM+3,TL,"LINE",4);
  XDrawString(XD,XW,XG,LM+36,TL,"COMMAND HEADING(RADS)",21);
  XDrawString(XD,XW,XG,LM+174,TL,"COMMAND HEIGHT(METRES)",22);
  XDrawString(XD,XW,XG,LM+318,TL,"COMMAND SPEED(METRES/SEC)",25);
  T7buts();
  T7updt();
}




/* OUTPUT TAB'S BUTTONS CONTROL LOGIC [T7CL]: The first 2 buttons are
   mutually exclusive. The next 2 buttons operate independently; each is
   respectively set to its 'on' state when clicked. The states of the OUTPUT
   TAB'S control buttons are kept in the variable T7BS [Tab 5 Button States]
   as the states of the lower 8 bits, the least significant bit representing
   the state of the first button [GO]. Called from 4 places in T7click(). 

T7BS: 000001 [0X01] = GO on       [bits going from right to left repres- 
      000010 [0X02] = STOP on      ent buttons going from left to right]
      000100 [0X04] = STDOUT on
      001000 [0X08] = FILE on  */

void T7CL(int x) {     // x = button number from left to right
  int y = 1 << x;      // set the bit for button 'x'
  if(T7BS & y) {       // if button 'x' is bright
    switch(x) {        // switch on button number
      case 0: return;  // if the GO button is already bright, do nothing
      case 1: return;  // if the STOP button is already bright, do nothing
      case 2:          // if the STDOUT button is bright
        StdOut = 0;    // disable output to 'stdout'
        break;
      case 3:          // if FILE button is bright
        FileOut = 0;   // disable output to 'output.txt'
        fclose(FO);    // close the file 'output.txt'
        break;
    } T7BS &= ~y;      // dim button 'x'
  } else {             // else, if button 'x' is dim
    switch(x) {        // switch on button number
      case 0:          // if the GO button is dim
        T3click(0);    // start the current flight
        break;
      case 1:          // if the STOP button is dim
        T3click(1);    // stop the current flight
        break;
      case 2:          // if the STDOUT button is dim
        StdOut = 1;    // enable output to 'stdout'
        break;
      case 3:          // if the FILE button is bright
        FO = fopen("output.txt","wb");
        FileOut = 1;   // enable output to 'output.txt'
        break;
    } T7BS |= y;       // brighten button 'x'
  } T7buts();          // re-display the buttons
}




/* ONE OF THE ROW OF 5 BUTTONS AT THE BOTTOM OF THE OUTPUT TAB WAS CLICKED
The values in the if() statements are the left and right edges of the
buttons in pixels. Called from only one place in MEH() case 2: case 5: */

void T7click(int x) {
  if(mX > 16 && mX < 70)         // if clicked on 'GO' button
    T7CL(0);                     // start the currently selected flight
  else if(mX > 75 && mX < 129)   // if clicked on 'STOP' button
    T7CL(1);                     // stop the currently selected flight
  else if(mX > 134 && mX < 188)  // if STDOUT button was clicked
    T7CL(2);                     // enable/disable OUTPUT to 'stdout'
  else if(mX > 193 && mX < 247)  // if the FILE button was clicked
    T7CL(3);                     // enable/disable OUTPUT to 'output.txt'
  else if(mX > 429 && mX < 483)  // if the HELP button was clicked
    helpPage("navigation/nav.html#output");
}





//-------------------FUNCTIONS CALLED DIRECTLY BY MAIN()---------------------


/* DISPLAY ALL THE TITLES, BUTTONS AND DEFAULT IN-FILL FOR CURRENT TAB
   which is specified by the numeric value of the global variable 'TB':
   1=ADDRESS Tab, 2=PROFILE Tab, 3=DIARY Tab etc.. Called from only 1
   place each in main() and MEH(). */

void showTab() {
  XSetForeground(XD,XG,BLACK);
  XFillRectangle(XD,XW,XG,24,55,453,380);  // clear Tab area

  const char
    *BA[8] = {
      "ROUTE","WAYPTS","GEOID","MAP","ATT","AZI","INPUT","OUTPUT"
    };

  const int 
    NC[8] = { 5,6, 5, 3, 3, 3, 5,6},  // number of chars in each annotation
    NB[8] = {12,9,12,18,18,18,12,9};  // x-bias for each annotation

  XSetForeground(XD,XG, DARK);  // shade button background

  int h = LM;
  for(int i = 0; i < 8; i++) {  // draw each button
    XFillRectangle(XD,XW,XG,h,17,53,21);
    h += BW;
  }

  h = LM;
  for(int i = 0; i < 8; i++) {  // annotate each button
    if(TB == i)
      XSetForeground(XD,XG,WHITE);
    else
      XSetForeground(XD,XG,GREY); 
    XDrawString(XD,XW,XG,h + *(NB +i),32,*(BA + i),*(NC + i));
    h += BW;
  }

  clearTabArea();
  switch(TB) {                // switch on tab number
    case 0: T0show(); break;  // paint the ROUTES tab
    case 1: T1show(); break;  // paint the WAYPTS tab
    case 2: T2show(); break;  // paint the GEOID tab
    case 3: T3show(); break;  // paint the MAP tab
    case 4: T4show(); break;  // paint the ATTITUDE tab
    case 5: T5show(); break;  // paint the AZIMUTH tab
    case 6: T6show(); break;  // paint the INTPUT tab
    case 7: T7show(); break;  // paint the OUTPUT tab
  }
}




/* ADVANCE THE AIRCRAFT ALONG THE CURRENTLY SELECTED ROUTE, UPDATING ALL
ENCOUNTER VARIABLES. This function is called only from main() run loop. */ 

void updtTrack() {
  double dW = 0;  // distance the aircraft is blown by the wind

            /* If receding from current waypoint
               |  AND      more than half way to the next one
               |  |        | */
  if(ToFlag == 0 && WptDst > NHD) {
 
    if(cwp < LWP) {       // if not last waypoint in route [destination] 
      cwp++;              // increment to the next waypoint in the route
      initEncounter();    // initialise variables for waypoint changeover
      T1show();           // to update the highlighting of waypoint name
      T4name();           // update the name of the current waypoint on graph
      ToFlag = 1;         // show that a waypoint changeover has occurred.
      ToFrom();           // update the to/from status for command height
    } else {              // Else aircraft has reached its destination:
      T3click(1);         // so hit the STOP button
      showMsg(15,GREEN);  // display the "Reached Destination" message
      CmdSpd = 0;         // stop the aircraft
      AirHgt = 0;         // put it on the ground
      T3updt();           // show the final speed etc.
      return;             // end-of-flight exit
    }
  }                       // Preserve last time's waypoint distance for
  PreDst = WptDst;        // comparison after distance update [see below].

  /* If this program is running in a pipe, that is, it was started with the 
  command 'nav -io', the 'InFlg' is set to '1'. This causes the program here
  to read in the actual latitudes and longitudes of the aircraft as supplied
  by such sources as GPS, an inertial platform or ground-based radio aids.
  Latitude & longitude are expected as a single line of input comprising a
  pair of E-format strings of 25 characters each, separated by a space char-
  acter: 9.0553499357681432880e-01 3.7815467126561474152e-03 representing the
  latitude & longitude of the aircraft in radians. */

  if(InFlg) {                     // if Lat/Lng from stdin is active
    char *S = NULL;               // pointer to getline()'s input string
    size_t L = 0;                 // length of getline()'s input string
    getline(&S, &L, stdin);       // get lat/lng input line from stdin
    double 
      x = atof(strtok(S, " ")),     // latitude from stdin
      y = atof(strtok(NULL, " "));  // longitude from stdin
    free(S);                        // de-alloc memory used for input string

      /* if latitude between -90° and + 90°
         |              |       AND   longitude between -180° and +180°
         |              |        |    |          | */
    if(x > -HalfPi && x < HalfPi && y > -pi && y < pi) { 

      /* Use the Vinceny Inverse function to get the distance and bearing of
      the real position of the aircraft from the point of view of where it was
      predicted to be at the end of the last pass through this function. */

      VinChk(VSGIP(AirLat,AirLng,x,y));
      double 
        X = dW * sin(WndBrg) + VinDst * sin(FwdAzi),  // latitudinal error
        Y = dW * cos(WndBrg) + VinDst * cos(FwdAzi);  // longitudinal error

      /* It is assumed that the error is due to displacement of the aircraft 
      by the wind. The corrected distance blown by the wind since last time
      through is therefore: D = sqrt(X * X + Y * Y) - see wind.png  */

      WndSpd = sqrt(X * X + Y * Y) * MPR / ET; // corrected wind speed m/s
      WndBrg = RatAng(HalfPi - atan2(Y,X));    // corrected wind bearing

      /* Compute the wind offset: see diagram ReqOff.png  NOTE: the atan2()
      function takes care of all the +/- signs for the 4 quadrants. */

      ReqOff = atan2(WndSpd * cos(WndBrg) + CmdSpd * cos(CmdHdg),  // 'Y'
                     WndSpd * sin(WndBrg) + CmdSpd * sin(CmdHdg))  // 'X'
             - CmdHdg;  // last time's command heading

      AirLat = x;  // use aircraft's real-world latitude & 
      AirLng = y;  // longitude for the rest of this pass.
    }
  }

  // COMPUTE THE DISTANCE & BEARINGS FOR THE CURRENT WAYPOINT
  i = WP[cwp].cgf;  // Geographic Feature index number of current waypoint

  VinChk(VSGIP(AirLat,AirLng,GF[i].Lat,GF[i].Lng));

  WptDst = VinDst;  // distance from aircraft to current waypoint
  FwdBrg = FwdAzi;  // bearing from aircraft to current waypoint
  BakBrg = BakAzi;  // bearing of aircraft from current waypoint

  double             // ESTABLISH THE CURRENT TO/FROM SITUATION
    dDst = WptDst - PreDst;  // distance travelled since last pass

  if(ToFlag == 0) {  // if travelling FROM the current waypoint
    if(dDst < 0) {   // if the distance travelled is negative
      ToFlag = 1;    // aircraft has just started to travel TO
      ToFrom();      // the next waypoint
    }
  } else             // else
  if(dDst > 0) {     // if the distance travelled is positive
    ToFlag = 0;      // aircraft has just started to travel FROM
    ToFrom();        // the current waypoint.
  }

  if(cwp == 0 && WptDst < 1000)  // Don't begin turn onto outbound radial
    goto LABEL;                  // until 1000 metres after take-off.

  // [REF:OCEANIC] COMPUTE AIRCRAFT'S NEW REQUIRED HEADING
  if(WptDst >= WPR) {   // if aircraft beyond waypoint's range it is
    if(ToFlag == 1) {   // transoceanic. Please see diagram transTo.png
      i = WP[pwp].cgf;  // index number of previous Geographic Feature

      VinChk(VSGIP(AirLat,AirLng,GF[i].Lat,GF[i].Lng));

   /* The required heading is 
      |        twice the bearing of current waypoint from the aircraft
      |        |        |                      */
      ReqHdg = FwdBrg + FwdBrg - FwdAzi - pi;  /*
                                 |        |
            minus the bearing from        the backwards-extended line of the
                                          previous waypoint from aircraft */

      MapFwd = FwdAzi;  // Bearing of previous waypoint from aircraft
    }                   // when current waypoint is ahead of aircraft.
    else {              // Please see diagram transFrom.png
      i = WP[nwp].cgf;  // index number of next Geographic Feature

      VinChk(VSGIP(AirLat,AirLng,GF[i].Lat,GF[i].Lng));

      ReqHdg = FwdAzi   // The required heading is twice the bearing
             + FwdAzi   // of next waypoint from aircraft, minus the
             - FwdBrg   // bearing from the backwards-extended line to
             - pi;      // the current waypoint from the aircraft.
      MapFwd = FwdAzi;  // Bearing of next waypoint from aircraft
    }                   // when current waypoint is behind aircraft.
  }

  /* [REF:TO/FROM] NOTE: the functions required to recover from severe dis-
  placements from the established route, in the following TO and FROM situa-
  tions, are not included with this version. The aircraft must always be with-
  in the limits of radial capture, which are, nonetheless, quite generous.

                 if this waypoint is the route origin
                 | OR        the aircraft has not yet reached
                 |           |  the waypoint's turning arc
                 |           |  */
  else if(cwp == 0 || WptDst > TanDst) {

    if(ToFlag == 1) {  // TO: please see diagram to.png & nav.html
      double
        RadDev = BipAng(BakBrg - InRad),  // compute the radial deviation
        a = RadDev;
      if (a < 0)  // create a positive-only version
        a = -a;   // of the radial deviation

      /* This formula is safe only where the aircraft is on a back bearing
      of less than 90° from the inbound radial. If back bearing is beyond
      90°, simply leave ReqHdg with the value it had last time through. */

      if(a < HalfPi)
        ReqHdg = FwdBrg + 0.49 * RadDev;

   /* else {
        Additional code could be placed here for handling extremely abnormal
        situations where the angular deviation of the aircraft from the in-
        bound radial is greater than 0.49(π/2) [88°]. In this situation, the
        best strategy is to try immediately to get a lock onto the next way-
        point along the route and so on until the deviation of the aircraft
        from the inbound radial is less than 0.49(π/2) radians [88°].
      } */

    } else {  // FROM: please see diagram from.png and the text in nav.html
      double                               // RadDev is θ in the diagram
        RadDev = BipAng(BakBrg - OutRad),  // compute the radial deviation
        a = RadDev;
      if (a < 0)  // make a positive-only version
        a = -a;   // of the radial deviation

      /* This formula is safe only where the aircraft is on a forward bearing
      of up to 3/2 radians [86°] from the outbound radial. Notice the non-
      linear Radial Deviation compensation multiplier. If beyond 3/2 radians 
      [86°] simply leave ReqHdg with the value it had last time through. */

      if(a < 3)
        ReqHdg = OutRad - (1 - a / 6) * RadDev;

   /* else {
        Additional code could be placed here for handling extremely abnormal
        situations where the angular deviation of the aircraft from the out-
        bound radial can actually reach +/- π radians [+/- 180°]. In this sit-
        uation, the best strategy is to try immediately to get a lock onto the
        next waypoint along the route and so on until the aircraft's deviation
        from the INbound radial thereof is < 0.49(π/2) radians [88°].
      } */
    }
  }

  /* [REF:TURN] Else the aircraft is on the turning circle arc: so first
  get the distance & bearing of the centre of the turning circle arc. */

  else {
    VinChk(VSGIP(AirLat,AirLng,CtrLat,CtrLng));
    double dHdg;
    if(VinDst > TrnRad)            // if aircraft outside turning circle:
      dHdg = asin(TrnRad/VinDst);  // TrnRad & VinDst are always +ve so
                                   // dHdg always lies between 0 & π/2.
                                   // please see diagrams turn1.png
    else if(VinDst < TrnRad)       // else if aircraft is inside the turning
      dHdg = HalfPi;               // circle: please see diagram turn2.png
    if(RIGHT == 1)                 // dHdg runs anticlockwise for a right turn
      dHdg = -dHdg;
    ReqHdg = FwdAzi + dHdg;   // FwdAzi is bearing of the centre of the
  }                           // turning circle arc from the aircraft.
  ReqHdg = RatAng(ReqHdg);    // Express ReqHdg in range 0 to 2π.

  // [REF:DAMP] DAMP THE CHANGE IN THE REQUIRED HEADING
  CmdROT = 0.2 * BipAng(ReqHdg - CmdHdg);  // heading error
  #define MaxErr 0.052359878  // 3° as a fraction of a radian
  if(CmdROT > +MaxErr)    // limit the heading error
    CmdROT = +MaxErr;     // to a prescribed maximum of +3°
  if(CmdROT < -MaxErr)    // limit the heading error
    CmdROT = -MaxErr;     // to a prescribed minimum of -3°
  CmdHdg += CmdROT * ET;  // limited new command heading
  if(InFlg == 1)          // if receiving live lat/lng input
    CmdHdg -= ReqOff;     // compensate for heading error due to wind

LABEL:  // to skip turn onto outbound radial until 1000 metres after take-off

  // COMPUTE THE AIRCRAFT'S REQUIRED AIR SPEED
  #define PasSpd 150    // 150 m/s  292 knots  540 kph  336 mph
  if(ORD == 2 &&        // if this is the destination waypoint
    ToFlag == 0) {      // AND aircraft is beyond final waypoint
    Landing = 1;        // used by the height channel
    ToFrom();
    double 
      D = LndDst - WptDst;  // compute distance to touch-down
    if(D > 0)               // if there is still some distance to fly
      ReqSpd = MinSpd       // required speed is the touch-down speed
             + PasSpd / D;  // + waypoint over-fly speed / distance to fly
    else {                  // Else, if touch-down has occurred
      ReqSpd = 0;           // set required speed to zero
      CmdSpd = 0;           // set command speed to zero
    }
  } else {                 // else, aircraft is not landing, so
    if(WptDst < 2000)      // if aircraft is within 2 km of waypoint, set
      ReqSpd = PasSpd;     // required speed to waypoint over-fly speed
    else if(WptDst > WPR)  // if aircraft beyond waypoint's radio range
      ReqSpd = MaxSpd;     // set required speed to maximum speed
    else                   // if between 2km and maximum radio range

    /* Make the aircraft accelerate or decelerate between maximum speed and
    waypoint over-fly speed according to how far it is along the stretch bet-
    ween the waypoint's maximum radio range and 2 km from the waypoint. Dimen-
    sional analysis of following formula: LT^-1 = L * LT^-1 * L^-1 + LT^-1

             distance the aircraft is from the waypoint
             |                range over which its speed is allowed to vary
             |                |                            */
    ReqSpd = WptDst * (MaxSpd - PasSpd) / (WPR - 2000) + PasSpd;
                                            /* |         |
      distance range over which variation occurs         |
                                base speed [minimum speed] */
  }

  // GADUALLY PULL THE COMMAND SPEED TOWARDS THE REQUIRED SPEED
  CmdSpd += (ReqSpd - CmdSpd) * ET * 0.125;  // speed increment this pass
  if(CmdSpd > MaxSpd) CmdSpd = MaxSpd;       // cap the command speed

  // ANTICIPATE DISTANCE [IN METRES] TRAVELLED DURING THIS PASS

  #define RPM 1.57079633e-7  /* Earth-radians per metre

              commanded speed in metres per second
              |        elapsed time in seconds since last pass
              |        |    convert metres to Earth-radians 
              |        |    |     */
  double dD = CmdSpd * ET * RPM;  //  // distance travelled since last pass 

  /* ANTICIPATE DISTANCE AIRCRAFT WAS BLOWN BY THE WIND DURING THIS PASS

             if the wind is switched on
             |  AND      the aircraft is off the ground
             |  |        |        */
  if(Wind == 1 && CmdSpd > MinSpd) {  // if the aircraft is in flight

      /* wind speed in metres per second
         |        elapsed time in seconds since last pass
         |        |     convert metres to Earth radians 
         |        |     |    */
    dW = WndSpd * ET * RPM;  // distance aircraft has been blown by the wind
  } 

  // ANTICIPATE LATITUDINAL & LONGITUDINAL INCREMENTAL SHIFTS FOR THIS PASS
  double dLat, dLng,          // lat and lng increments this pass
  K = cos(AirLat);            // convert Erads to local longitude radians

         /* engine thrust component
            |                  wind component
            |                  | */
  dLat = dD * cos(CmdHdg) + dW * cos(WndBrg);  // increment in latitude

  AirLat += dLat;  // increment the aircraft's latitude
  if((K) != 0) {   /* avoid a possible division by zero

    engine thrust component     wind component
             |                  |                     */
    dLng = (dD * sin(CmdHdg) + dW * sin(WndBrg)) / K; /*
                                                   |
    scale longitudinal degrees according to latitude  */

    AirLng += dLng;  // increment the aircraft's longitude
  }
}




/* [REF:HEIGHT] UPDATE THE 'STEALTH' HEIGHT COMMAND
The aircraft is commanded to cruise between consecutive waypoints at a predet-
ermined height above the ground called the 'stealth height'. Its preset value
[in metres above sea level] is represented by the label 'StlHgt'. 

The ground is deemed to 'undulate' according to a sigmoidal profile between
the ground levels of each consecutive pair of waypoints. The ground height at
any point between two waypoints is computed by the standard monopolar sigmoid
function y = 1/(1+exp(-x)), where 'x' ranges from -6 to +6 and y ranges from
0 to +1. The value of 'x' is {12 times the ratio between the current distance
'WptDst' of the aircraft from the receding waypoint and the distance 'D' be-
tween the two waypoints} minus 6: i.e. 12 * WptDst / D - 6.

The Height Command 'CmdHgt' is obtained by re-scaling 'y' to metres. This is
done by multiplying 'y' by the height difference between the two waypoints
and then adding the height of the lower of the two waypoints. A rate of climb
command 'CmdROC' is then calculated as a time-sensitive function of the diff-
erence between the current value of the 'CmdHgt' and the current value of the
actual aircraft height 'AirHgt'. The 'AirHgt' is then updated by adding to it
the newly calculated rate of climb 'CmdROC'.

See description at https://robmorton.website/navigation/tech.html#CmdHgt
Called from only 1 place in main()'s clock loop. */

void updtHgt() {
  double d;
  if(Landing)             // if in landing phase [beyond final waypoint]
    d = LndDst - WptDst;  // distance of aircraft from final waypoint
  else if(ToFlag)         // if going 'to' the current waypoint [cwp], 
    d = Hgt_D - WptDst;   // distance of aircraft from receding waypoint
  else                    // if receding 'from' current waypoint [cwp], 
    d = WptDst;           // distance of aircraft from receding waypoint

  if(Hgt_D > 0 &&  // avoid possible division by zero and
     d > 0) {      // force an upper limit of +6 for 'x' */ 
    if(d > Hgt_D)  // force a lower limit of -6 for 'x'
      d = Hgt_D;
    double x = 6 - 12 * d / Hgt_D;
    if(Landing) x = -x;  // glide slope run distance is measured backwards
    double z = 1 + exp(x);                 // divisor for Sigmoid Formula
    if(z < 1) z = 1;                       // avoid 'y' being > 1
    CmdHgt = HgtH1 + (HgtH2 - HgtH1) / z;  // base hgt + scaled hgt difference

    if(Landing == 0)     // If aircraft not yet in final leg of route, add
      CmdHgt += StlHgt;  // Stealth Height to calculated Height Command to
                         // get Command Height above sea level.

                             // If aircraft not yet reached 'HgtBD' where
    else if(WptDst < HgtBD)  // it must begin sigmoidal glide slope descent,
      CmdHgt = HgtH1;        // maintain the aircraft at its Stealth Height
                             // above the 'current' waypoint.
  }
  // compute the commanded rate of climb [note: CmdROC is used in Tab6]
  CmdROC = 0.1 * (CmdSpd / MaxSpd) * (CmdHgt - AirHgt);
  AirHgt += CmdROC;  // set new actual aircraft height 
}




/* INITIALISE FLIGHT PARAMETERS. This must be done only once when the program
is started. It must NOT be re-done upon re-exposure of the application window
after the program returns from a minimized state or from another work space.
This function is called only from one place within main(). */

void initFlightParams() {
  T2setGeoid();       // vital for the Vincenty functions
  T1routes();         // load in the details of all the available routes
  T0getWayPts();      // load the waypoints for the default route
  cgf = WP[cwp].cgf;  // set reference to this waypoint's data
  setAircraftAt();    // set aircraft at start of current route
  ET = ETS[IP];       // set elapsed time [in μs] corresponding to the
  IC = ICS[IP];       // prime the Iteration Counter
  ms = MS[MapSize];   // set the corresponding  map scale
}




/* MOUSE EVENT HANDLER. Handles clicks of the left mouse button [Button 1].
The x and y coordinates of the click point within the application window
are already in global variables mX and mY. Called only from one place in
main()'s event loop. */

void MEH() {
  int x = 1, i;           // default click area is the Tab buttons
  if(mY > 16 && mY < 38)  // if click on one of the Tab buttons
    x = 0;
  else 
  if(mY > 361 && mY < 383)  // if click on one of the Bottom buttons
    x = 2;
  for(i = 0; i < 8; i++) {          // for each button
    int y = i * BW;                 // button offset in pixels
    if(mX > 16 + y && mX < 70 + y)  // if click within button
      break;                        // break out of loop
  }
  switch(x) {
    case 0:            // ONE OF THE TAB BUTTONS WAS CLICKED
      TB = i;          // set Tab Number
      clearTabArea();  // clear the Tab's area
      showTab();       // display the Tab's content
      break;
    case 1:            // CLICK OCCURRED IN SOME OTHER PART OF THE TAB AREA
      switch(TB) {
        case 0: T0CL(); break;  // clicked another part of ROUTE Tab
        case 1: T1CL(); break;  // clicked another part of WAYPTS Tab
        case 2: T2click();      // clicked another part of GEOID Tab
      }
      break;
    case 2:            // CONTROL BUTTONS ALONG THE BOTTOM OF THE TAB
      switch(TB) {
        case 0: T0click(i); break;  // ROUTE Tab's button click handler
        case 1: T1click(i); break;  // WAYPOINT Tab's button click handler
        case 2: T2click(i); break;  // GEOID Tab's button click handler
        case 3: T3click(i); break;  // click was in MAP Tab
        case 4: T4click(i); break;  // ATT Tab's button click handler
        case 5: T5click(i); break;  // AZI Tab's button click handler
        case 6: T6click(i); break;  // INPUT Tab's button click handler
        case 7: T7click(i);         // OUTPUT Tab's button click handler
      }
  }
}




void MT4() {  // MOUSE WHEEL DOWN
  switch(TB) {
    case 1: MW = 2; T3show(); break;
    case 2: MW = 2; T0show();
  }
}




void MT5() {  // MOUSE WHEEL UP
  switch(TB) {
    case 1: MW = 1; T3show(); break;
    case 2: MW = 1; T0show();
  }
}




// MAIN FUNCTION FROM WHICH ALL THE ABOVE APPLICATION FUNCTIONS ARE CALLED

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

  // ACCEPT POSSIBLE COMMAND-LINE ARGUMENTS
  if(argc > 2) {                    // command line argument usage:
    printf("%s\n","Use: nav");      // run 'nav.c' without input or output
    printf("%s\n","or:  nav -o");   // send numeric commands to 'stdout'
    printf("%s\n","or:  nav -io");  // also accept real lat/lng from stdin
    return 0;                       // exit because too many args entered
  } else if(argc > 1) {
    if(strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "-help") == 0) {
      printf("%s\n","Use: nav");  // normal -help output
      printf("%s\n","or:  nav -o");
      printf("%s\n","or:  nav -io");
      return 0;
    }  // enable only output '-o' or both input and output '-io'
    else if(strcmp(argv[1], "-o") == 0)
      StdOut = 1;
    else if(strcmp(argv[1], "-io") == 0) {
      InFlg = 1;
      StdOut = 1;
    }
  }
  XEvent e;    // reference to any event from the created window
  int s,       // number of the current default screen
      mouseB;  // number of the mouse button that was pressed 0, 1, 2, 3, 4

  /* Check that you can actually open the X-display by getting a non-NULL
  reference to a display. Else send an error message to the terminal. */

  XD = XOpenDisplay(NULL);
  if (XD == NULL) { fprintf(stderr, "Cannot open display\n"); exit(1); }

  s = DefaultScreen(XD);     // get the number of the default screen
  XG = DefaultGC(XD, s);     // set the graphics context to use
  XW = XCreateSimpleWindow(  // create an application window 
         XD,                 // within the default display 
         RootWindow(XD,s),   // within the root window [desktop] 
         10, 10,             // position of top right corner 
         500, 400,           // width and height of the window 
         1,                  // border width in pixels 
         BlackPixel(XD,s),   // colour of window's border
         BlackPixel(XD,s)    // colour of window's background
       );
  XStoreName(XD,XW,  // set window title
    "GLOBAL NAVIGATOR II by Robert John Morton YE572246C");

  /* Make input possible from the keyboard, window controls and mouse
  then display the new window on the screen. Not all these bit masks
  are needed so comment out those that are not needed. */

  XSelectInput(
     XD,XW,
     ExposureMask
   | ButtonPressMask  // this program uses only the mouse buttons
// | KeyPressMask
// | KeyReleaseMask
// | PointerMotionMask
// | ButtonReleaseMask
// | StructureNotifyMask 
  );
  XMapWindow(XD,XW);
  XFlush(XD);

  /* Define a unique indentifier WM_DELETE_WINDOW (referred to as an atom)
  that will invoke the X11 protocol that closes windows upon the display
  'XD' we have just created. [At least I think this is what it means] */

  Atom WM_DELETE_WINDOW = XInternAtom(XD,"WM_DELETE_WINDOW",False); 

  /* Make 'delete window' events from this application's
  window visible as a ClientMessage event.*/

  XSetWMProtocols(XD,XW,&WM_DELETE_WINDOW,1); 

  initFlightParams();  // initialise all flight parameters

  /* The event-handling [or 'run'] loop [a permanent loop] broken only
  by the 'break' function in response to an external event. */

  while (1) { 
    time_t st = clock();    // start time for execution of this pass
    if(XPending(XD) > 0) {  // Provided there are events on the event queue,
      XNextEvent(XD,&e);    // go fetch the first one.

      /* If it is the 'window ready' event, set the foreground [drawing]
      colour to white [hex value FFFFFF] and draw the static words and
      selector lists on the screen. */

      if(e.type == Expose)  // if an 'expose' event has occurred
        showTab();          // draw all the titles and menus in the window

      /* Whenever the mouse is clicked, get its window coordiates. If it was
      clicked within the area of one of the selector lists, re-draw that list
      with the clicked item highlishted. */

      else if(e.type == ButtonPress) 
        if(e.xbutton.button == Button1) {  // if left mouse button clicked
          mX = e.xbutton.x;                // set x-coord of click
          mY = e.xbutton.y;                // set y-coord of click
          MEH();                           // call Mouse Event Handler
        }
        if(e.xbutton.button == Button4) MT4();  // mouse wheel scroll up
        if(e.xbutton.button == Button5) MT5();  // mouse wheel scroll down
    } 

    /* Else if any key has been pressed, or a box control clicked, break out
    of the permanent while() loop, close the window and exit. Should also
    check here for other client message types; however, as the only protocol
    registered above is WM_DELETE_WINDOW, it is safe for this program. */

    else if (e.type == KeyPress || e.type == ClientMessage)
      break;

    /* No external event captured, so if the aircraft is
    in flight & it's time to do another flight update: */

    else if(RunState) {  // if a flight is in progress
      if(JC == 0) {      // if the 1-second timer has expired
        T4plot();        // update the height plotter
        JC = 9;          // and re-initialise the 1-second timer
      } else JC--;       // else decrement the counter
      if(IC == 0) {      // if the settable iteration timer has expired
        updtTrack();     // advance & display aircraft's position
        updtHgt();       // advance & display aircraft's height
        T3updt();     // Update and redisplay flight data and map
        T2auto();     // automatically check and select appropriate geiod
        T6updt();     // show inputs of AirLat, AirLng & AirHgt in input tab
        T7updt();     // update the output display
        T4updt();     // update the pitch and roll command display
        T5updt();        // update the azimuth command display
        IC = ICS[IP];    // reset the Iteration Counter
      } else IC--;       // decrement the counter
    }
    usleep(100000 - clock() + st);  // Pause thread for remainder of 100ms
  }                      // end of outer permanent while() loop
  XCloseDisplay(XD);     // clear this window from the X11 display 'XD'
  return 0;              // return that everything went OK
}                        // end of main()

