]> git.mxchange.org Git - flightgear.git/commitdiff
kln89 GPS unit simulation
authordaveluff <daveluff>
Wed, 30 Nov 2005 00:21:25 +0000 (00:21 +0000)
committerdaveluff <daveluff>
Wed, 30 Nov 2005 00:21:25 +0000 (00:21 +0000)
src/Instrumentation/KLN89/Makefile.am [new file with mode: 0644]
src/Instrumentation/KLN89/kln89.cxx [new file with mode: 0644]
src/Instrumentation/KLN89/kln89.hxx [new file with mode: 0644]
src/Instrumentation/KLN89/kln89_page.cxx [new file with mode: 0644]
src/Instrumentation/KLN89/kln89_page.hxx [new file with mode: 0644]

diff --git a/src/Instrumentation/KLN89/Makefile.am b/src/Instrumentation/KLN89/Makefile.am
new file mode 100644 (file)
index 0000000..bbd5e21
--- /dev/null
@@ -0,0 +1,21 @@
+noinst_LIBRARIES = libKLN89.a
+
+libKLN89_a_SOURCES = \
+       kln89.cxx kln89.hxx \
+       kln89_page.cxx kln89_page.hxx \
+       kln89_page_act.cxx kln89_page_act.hxx \
+       kln89_page_apt.cxx kln89_page_apt.hxx \
+       kln89_page_cal.cxx kln89_page_cal.hxx \
+       kln89_page_dir.cxx kln89_page_dir.hxx \
+       kln89_page_fpl.cxx kln89_page_fpl.hxx \
+       kln89_page_int.cxx kln89_page_int.hxx \
+       kln89_page_nav.cxx kln89_page_nav.hxx \
+       kln89_page_ndb.cxx kln89_page_ndb.hxx \
+       kln89_page_nrst.cxx kln89_page_nrst.hxx \
+       kln89_page_oth.cxx kln89_page_oth.hxx \
+       kln89_page_set.cxx kln89_page_set.hxx \
+       kln89_page_usr.cxx kln89_page_usr.hxx \
+       kln89_page_vor.cxx kln89_page_vor.hxx \
+       kln89_symbols.hxx
+
+INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/src
\ No newline at end of file
diff --git a/src/Instrumentation/KLN89/kln89.cxx b/src/Instrumentation/KLN89/kln89.cxx
new file mode 100644 (file)
index 0000000..e66115b
--- /dev/null
@@ -0,0 +1,1404 @@
+// kln89_page.cxx - a class to manage the simulation of a KLN89
+//                  GPS unit.  Note that this is primarily the 
+//                  simulation of the user interface and display
+//                  - the core GPS calculations such as position
+//                  and waypoint sequencing are done (or should 
+//                  be done) by FG code. 
+//
+// Written by David Luff, started 2005.
+//
+// Copyright (C) 2005 - David C Luff - david.luff@nottingham.ac.uk
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+//
+// $Id$
+
+#include "kln89.hxx"
+#include "kln89_page.hxx"
+#include "kln89_page_apt.hxx"
+#include "kln89_page_vor.hxx"
+#include "kln89_page_ndb.hxx"
+#include "kln89_page_int.hxx"
+#include "kln89_page_usr.hxx"
+#include "kln89_page_act.hxx"
+#include "kln89_page_nav.hxx"
+#include "kln89_page_fpl.hxx"
+#include "kln89_page_cal.hxx"
+#include "kln89_page_set.hxx"
+#include "kln89_page_oth.hxx"
+#include "kln89_page_dir.hxx"
+#include "kln89_page_nrst.hxx"
+#include "kln89_symbols.hxx"
+#include <iostream>
+
+#include <ATC/ATCProjection.hxx>
+#include <simgear/math/point3d.hxx>
+
+SG_USING_STD(cout);
+
+KLN89::KLN89(RenderArea2D* instrument) 
+: DCLGPS(instrument) {
+       _mode = KLN89_MODE_DISP;
+       _blink = false;
+       _cum_dt = 0.0;
+       _nFields = 2;
+       _maxFields = 2;
+       _xBorder = 0;
+       _yBorder = 4;
+       // ..Field..[0] => no fields in action
+       _xFieldBorder[0] = 0;
+       _xFieldBorder[1] = 0;
+       _yFieldBorder[0] = 0;
+       _yFieldBorder[1] = 0;
+       _xFieldBorder[2] = 2;
+       _yFieldBorder[2] = 0;
+       _xFieldStart[0] = 0;
+       _xFieldStart[1] = 0;
+       _xFieldStart[2] = 45;
+       _yFieldStart[0] = 0;
+       _yFieldStart[1] = 0;
+       _yFieldStart[2] = 0;
+       
+       //_pixelated = true;
+       _pixelated = false;
+
+       // Cyclic pages
+       GPSPage* apt_page = new KLN89AptPage(this);
+       _pages.push_back(apt_page);
+       GPSPage* vor_page = new KLN89VorPage(this);
+       _pages.push_back(vor_page);
+       GPSPage* ndb_page = new KLN89NDBPage(this);
+       _pages.push_back(ndb_page);
+       GPSPage* int_page = new KLN89IntPage(this);
+       _pages.push_back(int_page);
+       GPSPage* usr_page = new KLN89UsrPage(this);
+       _pages.push_back(usr_page);
+       GPSPage* act_page = new KLN89ActPage(this);
+       _pages.push_back(act_page);
+       GPSPage* nav_page = new KLN89NavPage(this);
+       _pages.push_back(nav_page);
+       GPSPage* fpl_page = new KLN89FplPage(this);
+       _pages.push_back(fpl_page);
+       GPSPage* cal_page = new KLN89CalPage(this);
+       _pages.push_back(cal_page);
+       GPSPage* set_page = new KLN89SetPage(this);
+       _pages.push_back(set_page);
+       GPSPage* oth_page = new KLN89OthPage(this);
+       _pages.push_back(oth_page);
+       _nPages = _pages.size();
+       _curPage = 0;
+       
+       // Other pages
+       _dir_page = new KLN89DirPage(this);
+       _nrst_page = new KLN89NrstPage(this);
+       
+       _activePage = apt_page;
+       _obsMode = false;
+       _dto = false;
+       _fullLegMode = true;
+       _obsHeading = 215;
+       
+       _maxFlightPlans = 26;
+       for(unsigned int i=0; i<_maxFlightPlans; ++i) {
+               GPSFlightPlan* fp = new GPSFlightPlan;
+               fp->waypoints.clear();
+               _flightPlans.push_back(fp);
+       }
+       _activeFP = _flightPlans[0];
+       
+       // Hackish
+       _entJump = -1;
+       _entRestoreCrsr = false;
+       
+       _dispMsg = false;
+
+       // Moving map stuff
+       _mapOrientation = 0;
+       _mapHeading = 0.0;
+       _mapHeadingUpdateTimer = 0.0;
+       _drawSUA = false;
+       _drawVOR = false;
+       _drawApt = true;
+       //_mapScaleIndex = 20;
+       _mapScaleIndex = 7;     // I think that the above is more accurate for no-flightplan default, but this is more sane for initial testing!
+       _mapScaleAuto = true;
+       
+       // Mega-hack - hardwire airport town and state names for the FG base area since we don't have any data for these at the moment
+       // TODO - do this better one day!
+       _airportTowns["KSFO"] = "San Francisco";
+       _airportTowns["KSQL"] = "San Carlos";
+       _airportTowns["KPAO"] = "Palo Alto";
+       _airportTowns["KNUQ"] = "Mountain View";
+       _airportTowns["KSJC"] = "San Jose";
+       _airportTowns["KRHV"] = "San Jose";
+       _airportTowns["E16"] = "San Martin";
+       _airportTowns["KWVI"] = "Watsonville";
+       _airportTowns["KOAK"] = "Oakland";
+       _airportTowns["KHWD"] = "Hayward";
+       _airportTowns["KLVK"] = "Livermore";
+       _airportTowns["KCCR"] = "Concord";
+       _airportTowns["KTCY"] = "Tracy";
+       _airportTowns["KSCK"] = "Stockton";
+       _airportTowns["KHAF"] = "Half Moon Bay";
+       
+       _airportStates["KSFO"] = "CA";
+       _airportStates["KSQL"] = "CA";
+       _airportStates["KPAO"] = "CA";
+       _airportStates["KNUQ"] = "CA";
+       _airportStates["KSJC"] = "CA";
+       _airportStates["KRHV"] = "CA";
+       _airportStates["E16"] = "CA";
+       _airportStates["KWVI"] = "CA";
+       _airportStates["KOAK"] = "CA";
+       _airportStates["KHWD"] = "CA";
+       _airportStates["KLVK"] = "CA";
+       _airportStates["KCCR"] = "CA";
+       _airportStates["KTCY"] = "CA";
+       _airportStates["KSCK"] = "CA";
+       _airportStates["KHAF"] = "CA";
+}
+
+KLN89::~KLN89() {
+       for(unsigned int i=0; i<_pages.size(); ++i) {
+               delete _pages[i];
+       }
+       
+       delete _dir_page;
+       
+       for(unsigned int i=0; i<_maxFlightPlans; ++i) {
+               ClearFlightPlan(i);
+               delete _flightPlans[i];
+       }
+}
+
+void KLN89::bind() {
+       fgTie("/instrumentation/gps/message-alert", this, &KLN89::GetMsgAlert);
+       DCLGPS::bind();
+}
+
+void KLN89::unbind() {
+       fgUntie("/instrumentation/gps/message-alert");
+       DCLGPS::unbind();
+}
+
+void KLN89::update(double dt) {
+       // Run any positional calc's required first
+       DCLGPS::update(dt);
+       
+       _cum_dt += dt;
+       if(_blink) {
+               if(_cum_dt > 0.2) {
+                       _cum_dt = 0.0;
+                       _blink = false;
+               }
+       } else {
+               if(_cum_dt > 0.8) {
+                       _cum_dt = 0.0;
+                       _blink = true;
+               }
+       }
+       
+       _mapHeadingUpdateTimer += dt;
+       if(_mapHeadingUpdateTimer > 1.0) {
+               UpdateMapHeading();
+               _mapHeadingUpdateTimer = 0.0;
+       }
+       
+       _instrument->Flush();
+       _instrument->DrawBackground();
+       
+       if(_dispMsg) {
+               if(_messageStack.empty()) {
+                       DrawText("No Message", 0, 5, 2);
+               } else {
+                       // TODO - parse the message string for special strings that indicate degrees signs etc!
+                       DrawText(*_messageStack.begin(), 0, 0, 3);
+               }
+               return;
+       } else {
+               if(!_messageStack.empty()) {
+                       DrawMessageAlert();
+               }
+       }
+       
+       if(_curPage == 6 && _activePage->GetSubPage() == 3) {
+               // Don't draw the bar on the nav-4 page
+       } else if(_activePage == _nrst_page) {
+               // Don't draw the bar on the nearest page
+       } else {
+               DrawBar(_curPage);
+       }
+       
+       _activePage->Update(dt);
+}
+
+void KLN89::CreateDefaultFlightPlans() {
+       // TODO - read these in from preferences.xml or similar instead!!!!
+       // Create some hardwired default flightplans for testing.
+       vector<string> ids;
+       vector<GPSWpType> wps;
+       
+       ids.clear();
+       wps.clear();
+       ids.push_back("KLSN");
+       wps.push_back(GPS_WP_APT);
+       ids.push_back("VOLTA");
+       wps.push_back(GPS_WP_INT);
+       ids.push_back("C83");
+       wps.push_back(GPS_WP_APT);
+       CreateFlightPlan(_flightPlans[5], ids, wps);
+       
+       ids.clear();
+       wps.clear();
+       ids.push_back("KCCR");
+       wps.push_back(GPS_WP_APT);
+       ids.push_back("SUZYE");
+       wps.push_back(GPS_WP_INT);
+       ids.push_back("ALTAM");
+       wps.push_back(GPS_WP_INT);
+       ids.push_back("C83");
+       wps.push_back(GPS_WP_APT);
+       CreateFlightPlan(_flightPlans[4], ids, wps);
+       
+       ids.clear();
+       wps.clear();
+       ids.push_back("KLVK");
+       wps.push_back(GPS_WP_APT);
+       ids.push_back("OAK");
+       wps.push_back(GPS_WP_VOR);
+       ids.push_back("PORTE");
+       wps.push_back(GPS_WP_INT);
+       ids.push_back("KHAF");
+       wps.push_back(GPS_WP_APT);
+       CreateFlightPlan(_flightPlans[3], ids, wps);
+       
+       ids.clear();
+       wps.clear();
+       ids.push_back("KDPA");
+       wps.push_back(GPS_WP_APT);
+       ids.push_back("OBK");
+       wps.push_back(GPS_WP_VOR);
+       ids.push_back("ENW");
+       wps.push_back(GPS_WP_VOR);
+       ids.push_back("KRAC");
+       wps.push_back(GPS_WP_APT);
+       CreateFlightPlan(_flightPlans[2], ids, wps);
+       //cout << "Size of FP2 WP list is " << _flightPlans[2]->waypoints.size() << '\n';
+       
+       ids.clear();
+       wps.clear();
+       ids.push_back("KSFO");
+       ids.push_back("KOAK");
+       wps.push_back(GPS_WP_APT);
+       wps.push_back(GPS_WP_APT);
+       CreateFlightPlan(_flightPlans[1], ids, wps);
+       
+       ids.clear();
+       wps.clear();
+       //ids.push_back("KOSH");
+       ids.push_back("KSFO");
+       ids.push_back("KHAF");
+       ids.push_back("OSI");
+       ids.push_back("KSQL");
+       //ids.push_back("KPAO");
+       //ids.push_back("KHWD");
+       wps.push_back(GPS_WP_APT);
+       wps.push_back(GPS_WP_APT);
+       wps.push_back(GPS_WP_VOR);
+       wps.push_back(GPS_WP_APT);
+       //wps.push_back(GPS_WP_APT);
+       //wps.push_back(GPS_WP_APT);
+       CreateFlightPlan(_flightPlans[0], ids, wps);
+       
+       /*
+       ids.clear();
+       wps.clear();
+       ids.push_back("KLVK");
+       ids.push_back("KHWD");
+       wps.push_back(GPS_WP_APT);
+       wps.push_back(GPS_WP_APT);
+       CreateFlightPlan(_flightPlans[0], ids, wps);
+       */
+}
+
+void KLN89::Knob1Right1() {
+       if(_mode == KLN89_MODE_DISP) {
+               _activePage->LooseFocus();
+               if(_cleanUpPage >= 0) {
+                       _pages[(unsigned int)_cleanUpPage]->CleanUp();
+                       _cleanUpPage = -1;
+               }
+               _curPage++;
+               if(_curPage >= _pages.size()) _curPage = 0;
+               _activePage = _pages[_curPage];
+       } else {
+               _activePage->Knob1Right1();
+       }
+       update(0.0);
+}
+
+void KLN89::Knob1Left1() {
+       if(_mode == KLN89_MODE_DISP) {
+               _activePage->LooseFocus();
+               if(_cleanUpPage >= 0) {
+                       _pages[(unsigned int)_cleanUpPage]->CleanUp();
+                       _cleanUpPage = -1;
+               }
+               if(_curPage == 0) {
+                       _curPage = _pages.size() - 1;
+               } else {
+                       _curPage--;
+               }
+               _activePage = _pages[_curPage];
+       } else {
+               _activePage->Knob1Left1();
+       }
+       update(0.0);
+}
+
+void KLN89::Knob2Left1() {
+       _activePage->Knob2Left1();
+}
+
+void KLN89::Knob2Right1() {
+       _activePage->Knob2Right1();
+}
+
+void KLN89::CrsrPressed() {
+       _dispMsg = false;
+       if(_activePage == _nrst_page) return;   // CRSR cannot be switched off on nrst page.
+       if(_cleanUpPage >= 0) {
+               _pages[(unsigned int)_cleanUpPage]->CleanUp();
+               _cleanUpPage = -1;
+       }
+       _entRestoreCrsr = false;
+       _entJump = -1;
+       ((KLN89Page*)_activePage)->SetEntInvert(false);
+       if(_mode == KLN89_MODE_DISP) {
+               _mode = KLN89_MODE_CRSR;
+               _activePage->CrsrPressed();
+       } else {
+               _mode = KLN89_MODE_DISP;
+               _activePage->CrsrPressed();
+       }
+       update(0.0);
+}
+
+void KLN89::EntPressed() {
+       if(_entJump >= 0) {
+               if(_curPage < 5) {
+                       // one of the data pages.  Signal ent pressed to it here, and ent pressed to the call back page a few lines further down.
+                       // Ie. 2 ent pressed signals in this case is deliberate.
+                       _activePage->EntPressed();
+               }
+               _curPage = _entJump;
+               _activePage = _pages[(unsigned int)_entJump];
+               if(_entRestoreCrsr) _mode = KLN89_MODE_CRSR;
+               _entJump = -1;
+       }
+       if(_activePage == _dir_page) {
+               _dir_page->EntPressed();
+               _mode = KLN89_MODE_DISP;
+               _activePage = _pages[_curPage];
+       } else {
+               _activePage->EntPressed();
+       }
+}
+
+void KLN89::ClrPressed() {
+       _activePage->ClrPressed();
+}
+
+void KLN89::DtoPressed() {
+       if(_activePage != _dir_page) {
+               // Figure out which waypoint the dir page should display
+               if(_curPage <= 5) {
+                       // Apt, Vor, Ndb, Int, Usr or Act
+                       if(!_activePage->GetId().empty()) {     // Guard against no user waypoints defined
+                               _dir_page->SetId(_activePage->GetId());
+                       } else {
+                               _dir_page->SetId(_activeWaypoint.id);
+                       }
+               // } else if(_curPage == 6 && _nav_page->GetSubPage() == 3 && 0) {
+                       // NAV 4
+                       // TODO
+                       // The && 0 should be && outer knob is out.
+               } else if(_curPage == 7 && _activePage->GetSubPage() == 0 && _mode == KLN89_MODE_CRSR) {
+                       //cout << "Checking the fpl page!\n";
+                       // FPL 0
+                       if(!_activePage->GetId().empty()) {
+                               //cout << "Not empty!!!\n";
+                               _dir_page->SetId(_activePage->GetId());
+                       } else {
+                               //cout << "empty :-(\n";
+                               _dir_page->SetId(_activeWaypoint.id);
+                       }
+               } else {
+                       _dir_page->SetId(_activeWaypoint.id);
+               }
+               // This need to come after the bit before otherwise the FPL page clears it's current ID when it looses focus.
+               _activePage->LooseFocus();
+               _activePage = _dir_page;
+               _mode = KLN89_MODE_CRSR;
+       }
+}
+
+void KLN89::NrstPressed() {
+       if(_activePage != _nrst_page) {
+               _activePage->LooseFocus();      // TODO - check whether we should call loose focus here
+               _lastActivePage = _activePage;
+               _activePage = _nrst_page;
+               _lastMode = _mode;
+               _mode = KLN89_MODE_CRSR;
+       } else {
+               _activePage = _lastActivePage;
+               _mode = _lastMode;
+       }
+}
+       
+void KLN89::AltPressed() {}
+
+void KLN89::OBSPressed() {
+       DCLGPS::OBSPressed();
+       if(_obsMode) {
+               // if(ORS 02)
+               _mode = KLN89_MODE_CRSR;
+               _activePage->OBSPressed();
+       }
+}
+
+void KLN89::MsgPressed() {
+       // TODO - handle persistent messages such as SUA alerting.
+       // (The message annunciation flashes before first view, but afterwards remains continuously lit with the message available
+       // until the potential conflict no longer pertains).
+       if(_dispMsg && _messageStack.size()) {
+               _messageStack.pop_front();
+       }
+       _dispMsg = !_dispMsg;
+}
+
+void KLN89::DrawBar(int page) {
+       int px = 1 + (page * 15);
+       int py = 1;
+       for(int i=0; i<7; ++i) {
+               // Ugh - this is crude and inefficient!
+               _instrument->DrawPixel(px+i, py);
+               _instrument->DrawPixel(px+i, py+1);
+       }
+}
+
+// Convert moving map to instrument co-ordinates
+void KLN89::MapToInstrument(int &x, int &y) {
+       x += _xBorder + _xFieldBorder[2] + _xFieldStart[2];
+}
+
+// Draw a pixel specified in instrument co-ords, but clipped to the map region
+//void KLN89::DrawInstrMapPixel(int x, int y) {
+
+/*
+// Clip, translate and draw a map pixel
+// If we didn't need per-pixel clipping, it would be cheaper to translate object rather than pixel positions.
+void KLN89::DrawMapPixel(int x, int y, bool invert) {
+       if(x < 0 || x > 111 || y < 0 || y > 39)  return;
+       x += _xBorder + _xFieldBorder[2] + _xFieldStart[2];
+       _instrument->DrawPixel(x, y, invert);
+}
+*/
+
+// HACK - use something FG provides
+static double gps_min(const double &a, const double &b) {
+       return(a <= b ? a : b);
+}
+
+static double gps_max(const double &a, const double &b) {
+       return(a >= b ? a : b);
+}
+
+void KLN89::UpdateMapHeading() {
+       switch(_mapOrientation) {
+       case 0:         // North up
+               _mapHeading = 0.0;
+               break;
+       case 1:         // DTK up
+               _mapHeading = _dtkTrue;
+               break;
+       case 2:         // Track up
+               _mapHeading = _track;
+               break;
+       }
+}              
+
+// The screen area allocated to the moving map is 111 x 40 pixels.
+// In North up mode, the user position marker is at 57, 20. (Map co-ords).
+void KLN89::DrawMap(bool draw_avs) {
+       // Set the clipping region to the moving map part of the display
+       int xstart = _xBorder + _xFieldBorder[2] + _xFieldStart[2];
+       _instrument->SetClipRegion(xstart, 0, xstart + 110, 39);
+       
+       _mapScaleUnits = (int)_distUnits;
+       _mapScale = (double)(KLN89MapScales[_mapScaleUnits][_mapScaleIndex]);
+       
+       //cout << "Map scale = " << _mapScale << '\n';
+       
+       double mapScaleMeters = _mapScale * (_mapScaleUnits == 0 ? SG_NM_TO_METER : 1000);
+       
+       // TODO - use an aligned projection when either DTK or TK up!
+       FGATCAlignedProjection mapProj(Point3D(_gpsLon * SG_RADIANS_TO_DEGREES, _gpsLat * SG_RADIANS_TO_DEGREES, 0.0), _mapHeading);
+       
+       double meter_per_pix = (_mapOrientation == 0 ? mapScaleMeters / 20.0f : mapScaleMeters / 29.0f);
+       
+       Point3D bottomLeft = mapProj.ConvertFromLocal(Point3D(gps_max(-57.0 * meter_per_pix, -50000), gps_max((_mapOrientation == 0 ? -20.0 * meter_per_pix : -11.0 * meter_per_pix), -25000), 0.0));
+       Point3D topRight = mapProj.ConvertFromLocal(Point3D(gps_min(54.0 * meter_per_pix, 50000), gps_min((_mapOrientation == 0 ? 20.0 * meter_per_pix : 29.0 * meter_per_pix), 25000), 0.0));
+       
+       // Draw Airport labels first (but not one's that are waypoints)
+       // Draw Airports first (but not one's that are waypoints)
+       // Ditto for VORs (not sure if SUA/VOR/Airport ordering is important or not).
+       // Ditto for SUA
+       // Then flighttrack
+       // Then waypoints
+       // Then waypoint labels (not sure if this should be before or after waypoints)
+       // Then user pos.
+       // Annotation then gets drawn by Nav page, NOT this function.
+
+       if(_drawApt && draw_avs) {
+               airport_list apt;
+               /*
+               bool have_apt = _overlays->FindArpByRegion(&apt, bottomLeft.lat(), bottomLeft.lon(), topRight.lat(), topRight.lon());
+               //cout << "Vors enclosed are: ";
+               // Draw all the labels first...
+               for(unsigned int i=0; i<apt.size(); ++i) {
+                       //cout << nav[i]->id << ' ';
+                       Point3D p = mapProj.ConvertToLocal(Point3D(apt[i]->lon * SG_RADIANS_TO_DEGREES, apt[i]->lat * SG_RADIANS_TO_DEGREES, 0.0));
+                       //cout << p << " .... ";
+                       int mx = int(p.x() / meter_per_pix) + 56;
+                       int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
+                       //cout << "mx = " << mx << ", my = " << my << '\n';
+                       bool right_align = (p.x() < 0.0);
+                       DrawLabel(apt[i]->id, mx + (right_align ? -2 : 3), my + (p.y() < 0.0 ? -7 : 3), right_align);
+                       // I think that we probably should have -1 in the right_align case above to match the real life instrument.
+               }
+               // ...and then all the Apts.
+               for(unsigned int i=0; i<apt.size(); ++i) {
+                       //cout << nav[i]->id << ' ';
+                       Point3D p = mapProj.ConvertToLocal(Point3D(apt[i]->lon * SG_RADIANS_TO_DEGREES, apt[i]->lat * SG_RADIANS_TO_DEGREES, 0.0));
+                       //cout << p << " .... ";
+                       int mx = int(p.x() / meter_per_pix) + 56;
+                       int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
+                       //cout << "mx = " << mx << ", my = " << my << '\n';
+                       DrawApt(mx, my);
+               }
+               //cout << '\n';
+               */
+       }
+       /*
+       if(_drawVOR && draw_avs) {
+               Overlays::nav_array_type nav;
+               bool have_vor = _overlays->FindVorByRegion(&nav, bottomLeft.lat(), bottomLeft.lon(), topRight.lat(), topRight.lon());
+               //cout << "Vors enclosed are: ";
+               // Draw all the labels first...
+               for(unsigned int i=0; i<nav.size(); ++i) {
+                       //cout << nav[i]->id << ' ';
+                       Point3D p = mapProj.ConvertToLocal(Point3D(nav[i]->lon * SG_RADIANS_TO_DEGREES, nav[i]->lat * SG_RADIANS_TO_DEGREES, 0.0));
+                       //cout << p << " .... ";
+                       int mx = int(p.x() / meter_per_pix) + 56;
+                       int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
+                       //cout << "mx = " << mx << ", my = " << my << '\n';
+                       bool right_align = (p.x() < 0.0);
+                       DrawLabel(nav[i]->id, mx + (right_align ? -2 : 3), my + (p.y() < 0.0 ? -7 : 3), right_align);
+                       // I think that we probably should have -1 in the right_align case above to match the real life instrument.
+               }
+               // ...and then all the VORs.
+               for(unsigned int i=0; i<nav.size(); ++i) {
+                       //cout << nav[i]->id << ' ';
+                       Point3D p = mapProj.ConvertToLocal(Point3D(nav[i]->lon * SG_RADIANS_TO_DEGREES, nav[i]->lat * SG_RADIANS_TO_DEGREES, 0.0));
+                       //cout << p << " .... ";
+                       int mx = int(p.x() / meter_per_pix) + 56;
+                       int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
+                       //cout << "mx = " << mx << ", my = " << my << '\n';
+                       DrawVOR(mx, my);
+               }
+               //cout << '\n';
+       }
+       */
+       
+       // FlightTrack
+       if(_activeFP->waypoints.size() > 1) {
+               vector<int> xvec, yvec, qvec;   // qvec stores the quadrant that each waypoint label should
+                                                                               // be drawn in (relative to the waypoint). 
+                                                                               // 1 = NE, 2 = SE, 3 = SW, 4 = NW.
+               double save_h;  // Each pass, save a heading from the previous one for label quadrant determination.
+               bool drawTrack = true;
+               for(unsigned int i=1; i<_activeFP->waypoints.size(); ++i) {
+                       GPSWaypoint* wp0 = _activeFP->waypoints[i-1];
+                       GPSWaypoint* wp1 = _activeFP->waypoints[i];
+                       Point3D p0 = mapProj.ConvertToLocal(Point3D(wp0->lon * SG_RADIANS_TO_DEGREES, wp0->lat * SG_RADIANS_TO_DEGREES, 0.0));
+                       Point3D p1 = mapProj.ConvertToLocal(Point3D(wp1->lon * SG_RADIANS_TO_DEGREES, wp1->lat * SG_RADIANS_TO_DEGREES, 0.0));
+                       int mx0 = int(p0.x() / meter_per_pix) + 56;
+                       int my0 = int(p0.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
+                       int mx1 = int(p1.x() / meter_per_pix) + 56;
+                       int my1 = int(p1.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
+                       if(i == 1) {
+                               xvec.push_back(mx0);
+                               yvec.push_back(my0);
+                               double h = GetGreatCircleCourse(wp0->lat, wp0->lon, wp1->lat, wp1->lon) * SG_RADIANS_TO_DEGREES;
+                               // Adjust for map orientation
+                               h -= _mapHeading;
+                               qvec.push_back(GetLabelQuadrant(h));
+                               //cout << "i = " << i << ", h = " << h << ", qvec[0] = " << qvec[0] << '\n';
+                       }
+                       xvec.push_back(mx1);
+                       yvec.push_back(my1);
+                       if(drawTrack) { DrawLine(mx0, my0, mx1, my1); }
+                       if(i != 1) {
+                               double h = GetGreatCircleCourse(wp0->lat, wp0->lon, wp1->lat, wp1->lon) * SG_RADIANS_TO_DEGREES;
+                               // Adjust for map orientation
+                               h -= _mapHeading;
+                               qvec.push_back(GetLabelQuadrant(save_h, h));
+                       }
+                       save_h = GetGreatCircleCourse(wp1->lat, wp1->lon, wp0->lat, wp0->lon) * SG_RADIANS_TO_DEGREES;
+                       // Adjust for map orientation
+                       save_h -= _mapHeading;
+                       if(i == _activeFP->waypoints.size() - 1) {
+                               qvec.push_back(GetLabelQuadrant(save_h));
+                       }
+                       // Don't draw flight track beyond the missed approach point of an approach
+                       if(_approachLoaded) {
+                               //cout << "Waypoints are " << wp0->id << " and " << wp1->id << '\n';
+                               //cout << "Types are " << wp0->appType << " and " << wp1->appType << '\n';
+                               if(wp1->appType == GPS_MAP) {
+                                       drawTrack = false;
+                               }
+                       }
+               }
+               // ASSERT(xvec.size() == yvec.size() == qvec.size() == _activeFP->waypoints.size());
+               for(unsigned int i=0; i<xvec.size(); ++i) {
+                       DrawWaypoint(xvec[i], yvec[i]);
+                       bool right_align = (qvec[i] > 2);
+                       bool top = (qvec[i] == 1 || qvec[i] == 4);
+                       // TODO - not sure if labels should be drawn in sequence with waypoints and flightpaths,
+                       // or all before or all afterwards.  Doesn't matter a huge deal though.
+                       DrawLabel(_activeFP->waypoints[i]->id, xvec[i] + (right_align ? -2 : 3), yvec[i] + (top ? 3 : -7), right_align);
+               }
+       }
+       
+       // User pos
+       if(_mapOrientation == 0) {
+               // North up
+               DrawUser1(56, 19);
+       } else if(_mapOrientation == 1) {
+               // DTK up
+               DrawUser1(56, 10);
+       } else if(_mapOrientation == 2) {
+               // TK up
+               DrawUser2(56, 10);
+       } else {
+               // Heading up
+               // TODO - don't know what to do here!
+       }
+       
+       // And finally, reset the clip region to stop the rest of the code going pear-shaped!
+       _instrument->ResetClipRegion();
+}
+
+// Get the quadrant to draw the label of the start or end waypoint (i.e. one with only one track from it).
+// Heading specified FROM the waypoint.
+// 4 | 1
+// -----
+// 3 | 2
+int KLN89::GetLabelQuadrant(double h) {
+       while(h < 0.0) h += 360.0;
+       while(h > 360.0) h -= 360.0;
+       if(h < 90.0) return(3);
+       if(h < 180.0) return(4);
+       if(h < 270.0) return(1);
+       return(2);
+}
+
+// Get the quadrant to draw the label of an en-route waypoint,
+// with BOTH tracks specified as headings FROM the waypoint.
+// 4 | 1
+// -----
+// 3 | 2
+int KLN89::GetLabelQuadrant(double h1, double h2) {
+       while(h1 < 0.0) h1 += 360.0;
+       while(h1 > 360.0) h1 -= 360.0;
+       while(h2 < 0.0) h2 += 360.0;
+       while(h2 > 360.0) h2 -= 360.0;
+       double max_min_diff = 0.0;
+       int quad;
+       for(int i=0; i<4; ++i) {
+               double h = 45 + (90 * i);
+               double diff1 = fabs(h - h1);
+               if(diff1 > 180) diff1 = 360 - diff1;
+               double diff2 = fabs(h - h2);
+               if(diff2 > 180) diff2 = 360 - diff2;
+               double min_diff = gps_min(diff1, diff2);
+               if(min_diff > max_min_diff) {
+                       max_min_diff = min_diff;
+                       quad = i + 1;
+               }
+       }
+       //cout << "GetLabelQuadrant, h1 = " << h1 << ", h2 = " << h2 << ", quad = " << quad << '\n';
+       return(quad);
+}
+
+// Draw the diamond style of user pos
+// 
+//    o
+//   oxo
+//  oxxxo
+// oxxxxxo
+//  oxxxo
+//   oxo
+//    o
+// 
+void KLN89::DrawUser1(int x, int y) {
+       MapToInstrument(x, y);
+       int min_j = 0, max_j = 0;
+       for(int i=-3; i<=3; ++i) {
+               for(int j=min_j; j<=max_j; ++j) {
+                       _instrument->DrawPixel(x+j, y+i, (j == min_j || j == max_j ? true : false));
+               }
+               if(i < 0) {
+                       min_j--;
+                       max_j++;
+               } else {
+                       min_j++;
+                       max_j--;
+               }
+       }
+}
+
+// Draw the airplane style of user pos
+// Define the origin to be the midpoint of the *fuselage*
+void KLN89::DrawUser2(int x, int y) {
+       MapToInstrument(x, y);
+       
+       // Draw the background as three black quads first
+       _instrument->DrawQuad(x-2, y-3, x+2, y-1, true);
+       _instrument->DrawQuad(x-3, y, x+3, y+2, true);
+       _instrument->DrawQuad(x-1, y+3, x+1, y+3, true);
+       
+       if(_pixelated) {
+               for(int j=y-2; j<=y+2; ++j) {
+                       _instrument->DrawPixel(x, j);
+               }
+               for(int i=x-1; i<=x+1; ++i) {
+                       _instrument->DrawPixel(i, y-2);
+               }
+               for(int i=x-2; i<=x+2; ++i) {
+                       _instrument->DrawPixel(i, y+1);
+               }
+       } else {
+               _instrument->DrawQuad(x, y-2, x, y+2);
+               _instrument->DrawQuad(x-1, y-2, x+1, y-2);
+               _instrument->DrawQuad(x-2, y+1, x+2, y+1);
+       }
+}
+
+// Draw an airport symbol on the moving map
+//
+//  ooo
+// ooxoo
+// oxxxo
+// ooxoo
+//  ooo
+//
+void KLN89::DrawApt(int x, int y) {
+       MapToInstrument(x, y);
+       
+       int j = y-2;
+       int i;
+       for(i=x-1; i<=x+1; ++i) _instrument->DrawPixel(i, j, true);
+       ++j;
+       for(i=x-2; i<=x+2; ++i) _instrument->DrawPixel(i, j, (i != x ? true : false));
+       ++j;
+       for(i=x-2; i<=x+2; ++i) _instrument->DrawPixel(i, j, (abs(i - x) > 1 ? true : false));
+       ++j;
+       for(i=x-2; i<=x+2; ++i) _instrument->DrawPixel(i, j, (i != x ? true : false));
+       ++j;
+       for(i=x-1; i<=x+1; ++i) _instrument->DrawPixel(i, j, true);
+}
+
+// Draw a waypoint on the moving map
+//
+// ooooo
+// oxxxo
+// oxxxo
+// oxxxo
+// ooooo
+//
+void KLN89::DrawWaypoint(int x, int y) {
+       MapToInstrument(x, y);
+       _instrument->SetDebugging(true);
+       
+       // Draw black background
+       _instrument->DrawQuad(x-2, y-2, x+2, y+2, true);
+       
+       // Draw the coloured square
+       if(_pixelated) {
+               for(int i=x-1; i<=x+1; ++i) {
+                       for(int j=y-1; j<=y+1; ++j) {
+                               _instrument->DrawPixel(i, j);
+                       }
+               }
+       } else {
+               _instrument->DrawQuad(x-1, y-1, x+1, y+1);
+       }
+       _instrument->SetDebugging(false);
+}
+
+// Draw a VOR on the moving map
+//
+// ooooo
+// oxxxo
+// oxoxo
+// oxxxo
+// ooooo
+//
+void KLN89::DrawVOR(int x, int y) {
+       // Cheat - draw a waypoint and then a black pixel in the middle.
+       // Need to call Waypoint draw *before* translating co-ords.
+       DrawWaypoint(x, y);
+       MapToInstrument(x, y);
+       _instrument->DrawPixel(x, y, true);
+}
+
+// Draw a line on the moving map
+void KLN89::DrawLine(int x1, int y1, int x2, int y2) {
+       MapToInstrument(x1, y1);
+       MapToInstrument(x2, y2);
+       _instrument->DrawLine(x1, y1, x2, y2);
+}
+
+void KLN89::DrawMapUpArrow(int x, int y) {
+       MapToInstrument(x, y);
+       if(_pixelated) {
+               for(int j=0; j<7; ++j) {
+                       _instrument->DrawPixel(x + 2, y + j);
+               }
+       } else {
+               _instrument->DrawQuad(x+2, y, x+2, y+6);
+       }
+       _instrument->DrawPixel(x, y+4);
+       _instrument->DrawPixel(x+1, y+5);
+       _instrument->DrawPixel(x+3, y+5);
+       _instrument->DrawPixel(x+4, y+4);
+}
+
+// Draw a quad on the moving map
+void KLN89::DrawMapQuad(int x1, int y1, int x2, int y2, bool invert) {
+       MapToInstrument(x1, y1);
+       MapToInstrument(x2, y2);
+       _instrument->DrawQuad(x1, y1, x2, y2, invert);
+}
+
+// Draw an airport or waypoint label on the moving map
+// Specify position by the map pixel co-ordinate of the left or right, bottom, of the *visible* portion of the label.
+// The black background quad will automatically overlap this by 1 pixel.
+void KLN89::DrawLabel(string s, int x1, int y1, bool right_align) {
+       MapToInstrument(x1, y1);
+       if(!right_align) {
+               for(unsigned int i=0; i<s.size(); ++i) {
+                       char c = s[i];
+                       x1 += DrawSmallChar(c, x1, y1);
+                       x1 ++;
+               }
+       } else {
+               for(int i=(int)(s.size()-1); i>=0; --i) {
+                       char c = s[i];
+                       x1 -= DrawSmallChar(c, x1, y1, right_align);
+                       x1--;
+               }
+       }
+}
+
+void KLN89::DrawCDI() {
+       // Scale
+       for(int i=0; i<5; ++i) {
+               DrawSpecialChar(2, 2, 3+i, 2);
+               DrawSpecialChar(1, 2, 9+i, 2);
+       }
+       
+       int field = 2;
+       int px = 8 * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field] + 2;
+       int py = 2 * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
+       
+       // Deflection bar
+       // Every 7 pixels deflection left or right is one dot on the scale, and hence 1/5 FSD.
+       // Maximum deflection is 37 pixels left, or 38 pixels right !?!
+       double xtd = CalcCrossTrackDeviation();
+       int deflect;
+       if(_cdiScaleTransition) {
+               double dots = (xtd / _currentCdiScale) * 5.0;
+               deflect = (int)(dots * 7.0 * -1.0);
+               // TODO - for all these I think I should add 0.5 before casting to int, and *then* multiply by -1.  Possibly!
+       } else {
+               if(0 == _currentCdiScaleIndex) {        // 5.0nm FSD => 1 nm per dot => 7 pixels per nm.
+                       deflect = (int)(xtd * 7.0 * -1.0);      // The -1.0 is because we move the 'needle' indicating the course, not the plane.
+               } else if(1 == _currentCdiScaleIndex) {
+                       deflect = (int)(xtd * 35.0 * -1.0);
+               } else {        // 0.3 == _cdiScale
+                       deflect = (int)(xtd * 116.6666666667 * -1.0);
+               }
+       }
+       if(deflect > 38) deflect = 38;
+       if(deflect < -37) deflect = -37;
+       if(_pixelated) {
+               for(int j=0; j<9; ++j) {
+                       _instrument->DrawPixel(px + deflect, py+j);
+                       _instrument->DrawPixel(px + deflect + 1, py+j);
+               }
+       } else {
+               _instrument->DrawQuad(px + deflect, py, px + deflect + 1, py + 8);
+       }
+       
+       // To/From indicator
+       px-=4;
+       py+=2;
+       for(int j=4; j>=0; --j) {
+               int k = 10 - (2*j);
+               for(int i=0; i<k; ++i) {                
+                       _instrument->DrawPixel(px+j+i, (_headingBugTo ? py+j : py+4-j));
+                       // At the extremities, draw the outlining dark pixel
+                       if(i == 0 || i == k-1) {
+                               _instrument->DrawPixel(px+j+i, (_headingBugTo ? py+j+1 : py+3-j), true);
+                       }
+               }
+       }
+}
+
+void KLN89::DrawLegTail(int py) {
+       int px = 0 * 7 + _xBorder + _xFieldBorder[2] + _xFieldStart[2];
+       py = py * 9 + _yBorder + _yFieldBorder[2] + _yFieldStart[2];
+       
+       px++;
+       py+=3;
+       py++;   // Hack - not sure if this represents a border issue.
+       
+       for(int i=0; i<9; ++i) _instrument->DrawPixel(px, py+i);
+       for(int i2=0; i2<5; ++i2) _instrument->DrawPixel(px+i2, py+9);
+}
+
+void KLN89::DrawLongLegTail(int py) {
+       int px = 0 * 7 + _xBorder + _xFieldBorder[2] + _xFieldStart[2];
+       py = py * 9 + _yBorder + _yFieldBorder[2] + _yFieldStart[2];
+       
+       px++;
+       py+=3;
+       py++;   // Hack - not sure if this represents a border issue.
+       
+       for(int i=0; i<18; ++i) _instrument->DrawPixel(px, py+i);
+       for(int i2=0; i2<5; ++i2) _instrument->DrawPixel(px+i2, py+18);
+}
+
+void KLN89::DrawHalfLegTail(int py) {
+}
+
+void KLN89::DrawDivider() {
+       int px = _xFieldStart[2] - 1;
+       int py = _yBorder;
+       for(int i=0; i<36; ++i) {
+               _instrument->DrawPixel(px, py+i);
+       }
+}
+
+void KLN89::DrawEnt(int field, int px, int py) {
+       px = px * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
+       py = py * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field] + 1;
+       
+       px++;   // Not sure why we need px++, but it seems to work!
+       py++;
+       
+       // E
+       for(int i=0; i<5; ++i) _instrument->DrawPixel(px, py+i);
+       _instrument->DrawPixel(px+1, py);
+       _instrument->DrawPixel(px+2, py);
+       _instrument->DrawPixel(px+1, py+2);
+       _instrument->DrawPixel(px+1, py+4);
+       _instrument->DrawPixel(px+2, py+4);
+       
+       px += 4;
+       // N
+       for(int i=0; i<4; ++i) _instrument->DrawPixel(px, py+i);
+       _instrument->DrawPixel(px+1, py+2);
+       _instrument->DrawPixel(px+2, py+1);
+       for(int i=0; i<4; ++i) _instrument->DrawPixel(px+3, py+i);
+       
+       px += 5;
+       // T
+       _instrument->DrawPixel(px, py+3);
+       for(int i=0; i<4; ++i) _instrument->DrawPixel(px+1, py+i);
+       _instrument->DrawPixel(px+2, py+3);
+}
+
+void KLN89::DrawMessageAlert() {
+       // TODO - draw the proper message indicator
+       if(!_blink) {
+               int px = _xBorder + _xFieldBorder[1] + _xFieldStart[1];
+               int py = 1 * 9 + _yBorder + _yFieldBorder[1] + _yFieldStart[1] + 1;
+               
+               px++;   // Not sure why we need px++, but it seems to work!
+               py++;
+
+               DrawText("  ", 1, 0, 1, false, 99);
+               _instrument->DrawQuad(px+1, py-1, px+2, py+5, true);
+               _instrument->DrawQuad(px+3, py+3, px+3, py+5, true);
+               _instrument->DrawQuad(px+4, py+2, px+4, py+4, true);
+               _instrument->DrawQuad(px+5, py+1, px+6, py+3, true);
+               _instrument->DrawQuad(px+7, py+2, px+7, py+4, true);
+               _instrument->DrawQuad(px+8, py+3, px+8, py+5, true);
+               _instrument->DrawQuad(px+9, py-1, px+10, py+5, true);
+       }
+}
+
+void KLN89::Underline(int field, int px, int py, int len) {
+       px = px * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
+       py = py * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
+       for(int i=0; i<(len*7); ++i) {
+               _instrument->DrawPixel(px, py);
+               ++px;
+       }
+}
+
+void KLN89::DrawKPH(int field, int cx, int cy) {
+       // Add some border
+       int px = cx * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
+       int py = cy * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
+       
+       px++;
+       py++;
+       
+       for(int j=0; j<=4; ++j) {
+               _instrument->DrawPixel(px, py + 2 +j);
+               _instrument->DrawPixel(px + 8, py + j);
+               if(j <= 1) {
+                       _instrument->DrawPixel(px + 11, py + j);
+                       _instrument->DrawPixel(px + 9 + j, py + 2);
+               }
+       }
+       
+       for(int i=0; i<=6; ++i) {
+               if(i <= 2) {
+                       _instrument->DrawPixel(px + 1 + i, py + 4 + i);
+                       _instrument->DrawPixel(px + 1 + i, py + (4 - i));
+               }
+               _instrument->DrawPixel(px + 2 + i, py + i);
+       }
+}
+
+void KLN89::DrawDTO(int field, int cx, int cy) {
+       DrawSpecialChar(6, field, cx, cy);
+       if(!(_waypointAlert && _blink)) {
+               DrawSpecialChar(3, field, cx+1, cy);
+       }
+       
+       int px = cx * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
+       int py = cy * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
+       
+       px++;
+       py++;
+       
+       // Fill in the gap between the 'D' and the arrow.
+       _instrument->DrawPixel(px+5, py+3);
+}
+
+// Takes character position
+void KLN89::DrawChar(char c, int field, int px, int py, bool bold, bool invert) {
+       // Ignore field for now
+       // Add some border
+       px = px * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
+       py = py * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
+       
+       // Draw an orange background for inverted characters
+       if(invert) {
+               for(int i=0; i<7; ++i) {
+                       for(int j=0; j<9; ++j) {
+                               _instrument->DrawPixel(px + i, py + j);
+                       }
+               }
+       }
+                               
+       if(c < 33) return;  // space
+       
+       // Render normal decimal points in bold floats
+       if(c == '.') bold = false;
+       
+       ++py;   // Shift the char up by one pixel
+       for(int j=7; j>=0; --j) {
+               char c1 = (bold ? NumbersBold[c-48][j] : UpperAlpha[c-33][j]);
+               // Don't do the last column for now (ie. j = 1, not 0)
+               for(int i=5; i>=0; --i) {
+                       if(c1 & (01 << i)) {
+                               _instrument->DrawPixel(px, py, invert);
+                       }
+                       ++px;
+               }
+               px -= 6;
+               ++py;
+       }
+}
+
+// Takes pixel position
+void KLN89::DrawFreeChar(char c, int x, int y, bool draw_background) {
+       
+       if(draw_background) {
+               _instrument->DrawQuad(x, y, x+6, y+8, true);
+       }               
+                               
+       if(c < 33) return;  // space
+       
+       ++y;    // Shift the char up by one pixel
+       for(int j=7; j>=0; --j) {
+               char c1 = UpperAlpha[c-33][j];
+               // Don't do the last column for now (ie. j = 1, not 0)
+               for(int i=5; i>=0; --i) {
+                       if(c1 & (01 << i)) {
+                               _instrument->DrawPixel(x, y);
+                       }
+                       ++x;
+               }
+               x -= 6;
+               ++y;
+       }
+}
+
+// Takes instrument pixel co-ordinates.
+// Position is specified by the bottom of the *visible* portion, by default the left position unless align_right is true.
+// The return value is the pixel width of the visible portion
+int KLN89::DrawSmallChar(char c, int x, int y, bool align_right) {
+       // calculate the index into the SmallChar array
+       int idx;
+       if(c > 47 && c < 58) {
+               // number
+               idx = c - 48;
+       } else if(c > 64 && c < 91) {
+               // Uppercase letter
+               idx = c - 55;
+       } else {
+               return(0);
+       }
+       
+       char n = SmallChar[idx][0];             // Width of visible portion
+       if(align_right) x -= n;
+       
+       // background
+       _instrument->DrawQuad(x - 1, y - 1, x + n, y + 5, true);
+       
+       for(int j=7; j>=3; --j) {
+               char c1 = SmallChar[idx][j];
+               for(int i=n-1; i>=0; --i) {
+                       if(c1 & (01 << i)) {
+                               _instrument->DrawPixel(x, y);
+                       }
+                       ++x;
+               }
+               x -= n;
+               ++y;
+       }
+       
+       return(n);
+}
+
+// Takes character position
+void KLN89::DrawSpecialChar(char c, int field, int cx, int cy, bool bold) {
+       if(c > 7) {
+               cout << "ERROR - requested special char outside array bounds!\n";
+               return;  // Increment this as SpecialChar grows
+       }
+       
+       // Convert character to pixel position.
+       // Ignore field for now
+       // Add some border
+       int px = cx * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
+       int py = cy * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
+       ++py;   // Total hack - the special chars were coming out 1 pixel too low!
+       for(int i=7; i>=0; --i) {
+               char c1 = SpecialChar[(int)c][i];
+               // Don't do the last column for now (ie. j = 1, not 0)
+               for(int j=5; j>=0; --j) {
+                       if(c1 & (01 << j)) {
+                               _instrument->DrawPixel(px, py);
+                       }
+                       ++px;
+               }
+               px -= 6;
+               ++py;
+       }
+}
+
+void KLN89::DrawText(const string& s, int field, int px, int py, bool bold, int invert) {
+       for(int i = 0; i < (int)s.size(); ++i) {
+               DrawChar(s[(unsigned int)i], field, px+i, py, bold, (invert == i || invert == 99));
+       }
+}
+
+void KLN89::DrawMapText(const string& s, int x, int y, bool draw_background) {
+       MapToInstrument(x, y);
+       if(draw_background) {
+               //_instrument->DrawQuad(x, y, x + (7 * s.size()) - 1, y + 8, true);
+               _instrument->DrawQuad(x - 1, y, x + (7 * s.size()) - 2, y + 8, true);
+               // The minus 1 and minus 2 are an ugly hack to disguise the fact that I've lost track of exactly what's going on!
+       }
+       
+       for(int i = 0; i < (int)s.size(); ++i) {
+               DrawFreeChar(s[(unsigned int)i], x+(i * 7)-1, y);
+       }
+}
+
+void KLN89::DrawLatitude(double d, int field, int px, int py) {
+       DrawChar((d >= 0 ? 'N' : 'S'), field, px, py);
+       d = fabs(d);
+       px += 1;
+       // TODO - sanity check input to ensure major lat field can only ever by 2 chars wide
+       char buf[8];
+       // Don't know whether to zero pad the below for single digits or not?
+       //cout << d << ", " << (int)d << '\n';
+       // 3 not 2 in size before for trailing \0
+       int n = snprintf(buf, 3, "%i", (int)d);
+       string s = buf;
+       //cout << s << "... " << n << '\n';
+       DrawText(s, field, px+(3-n), py);
+       n = snprintf(buf, 7, "%05.2f'", ((double)(d - (int)d)) * 60.0f);
+       s = buf;
+       px += 3;
+       DrawSpecialChar(0, field, px, py);      // Degrees symbol
+       px++;
+       DrawText(s, field, px, py);
+}
+
+void KLN89::DrawLongitude(double d, int field, int px, int py) {
+       DrawChar((d >= 0 ? 'E' : 'W'), field, px, py);
+       d = fabs(d);
+       px += 1;
+       // TODO - sanity check input to ensure major lat field can only ever be 2 chars wide
+       char buf[8];
+       // Don't know whether to zero pad the below for single digits or not?
+       //cout << d << ", " << (int)d << '\n';
+       // 4 not 3 in size before for trailing \0
+       int n = snprintf(buf, 4, "%i", (int)d);
+       string s = buf;
+       //cout << s << "... " << n << '\n';
+       DrawText(s, field, px+(3-n), py);
+       n = snprintf(buf, 7, "%05.2f'", ((double)(d - (int)d)) * 60.0f);
+       s = buf;
+       px += 3;
+       DrawSpecialChar(0, field, px, py);      // Degrees symbol
+       px++;
+       DrawText(s, field, px, py);
+}
+
+void KLN89::DrawFreq(double d, int field, int px, int py) {
+       if(d >= 1000) d /= 100.0f;
+       char buf[8];
+       snprintf(buf, 7, "%6.2f", d);
+       string s = buf;
+       DrawText(s, field, px, py);
+}
+
+void KLN89::DrawTime(double time, int field, int px, int py) {
+       int hrs = (int)(time / 3600);
+       int mins = (int)(ceil((time - (hrs * 3600)) / 60.0));
+       char buf[10];
+       int n;
+       if(time >= 60.0) {
+               // Draw hr:min
+               n = snprintf(buf, 9, "%i:%02i", hrs, mins);
+       } else {
+               // Draw :secs
+               n = snprintf(buf, 4, ":%02i", (int)time);
+       }
+       string s = buf;
+       DrawText(s, field, px - n + 1, py);
+}
+
+void KLN89::DrawHeading(int h, int field, int px, int py) {
+       char buf[4];
+       snprintf(buf, 4, "%i", h);
+       string s = buf;
+       DrawText(s, field, px - s.size(), py);
+       DrawSpecialChar(0, field, px, py);      // Degrees symbol
+}
+
+void KLN89::DrawDist(double d, int field, int px, int py) {
+       d *= (_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001);
+       char buf[10];
+       snprintf(buf, 9, "%i", (int)(d + 0.5));
+       string s = buf;
+       s += (_distUnits == GPS_DIST_UNITS_NM ? "nm" : "Km");
+       DrawText(s, field, px - s.size() + 1, py);
+}
+
+void KLN89::DrawSpeed(double v, int field, int px, int py, int decimal) {
+       // TODO - implement variable decimal places
+       v *= (_velUnits == GPS_VEL_UNITS_KT ? 1.0 : 0.51444444444 * 0.001 * 3600.0);
+       char buf[10];
+       snprintf(buf, 9, "%i", (int)(v + 0.5));
+       string s = buf;
+       if(_velUnits == GPS_VEL_UNITS_KT) {
+               s += "kt";
+               DrawText(s, field, px - s.size() + 1, py);
+       } else {
+               DrawText(s, field, px - s.size() - 1, py);
+               DrawKPH(field, px - 1, py);
+       }
+}
+
+void KLN89::DrawDirDistField(double lat, double lon, int field, int px, int py, bool to_flag, bool cursel) {
+       DrawChar('>', field, px, py);
+       char buf[8];
+       double h;
+       if(to_flag) {
+               h = GetMagHeadingFromTo(_gpsLat, _gpsLon, lat, lon);
+       } else {
+               h = GetMagHeadingFromTo(lat, lon, _gpsLat, _gpsLon);
+       }
+       while(h < 0.0) h += 360.0;
+       while(h > 360.0) h -= 360.0;
+       snprintf(buf, 4, "%3i", (int)(h + 0.5));
+       string s = buf;
+       if(!(cursel && _blink)) { 
+               DrawText(s, field, px + 4 - s.size(), py);
+               DrawSpecialChar(0, field, px+4, py);
+               DrawText((to_flag ? "To" : "Fr"), field, px+5, py);
+               if(cursel) Underline(field, px + 1, py, 6);
+       }
+       //double d = GetHorizontalSeparation(_gpsLat, _gpsLon, lat, lon);
+       //d *= (_distUnits == GPS_DIST_UNITS_NM ? SG_METER_TO_NM : 0.001);
+       double d = GetGreatCircleDistance(_gpsLat, _gpsLon, lat, lon);
+       d *= (_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001);
+       if(d >= 100.0) {
+               snprintf(buf, 7, "%5i", (int)(d + 0.5));
+       } else {
+               snprintf(buf, 7, "%4.1f", d);
+       }
+       s = buf;
+       DrawText(s, field, px + 12 - s.size(), py);
+       DrawText((_distUnits == GPS_DIST_UNITS_NM ? "nm" : "Km"), field, px + 12, py);
+}
+
+char KLN89::IncChar(char c, bool gap, bool wrap) {
+       if(c == '9') return(wrap ? (gap ? ' ' : 'A') : '9');
+       if(c == 'Z') return('0');
+       if(c == ' ') return('A');
+       return(c + 1);
+}
+
+char KLN89::DecChar(char c, bool gap, bool wrap) {
+       if(c == 'A') return(wrap ? (gap ? ' ' : '9') : 'A');
+       if(c == '0') return('Z');
+       if(c == ' ') return('9');
+       return(c - 1);
+}
diff --git a/src/Instrumentation/KLN89/kln89.hxx b/src/Instrumentation/KLN89/kln89.hxx
new file mode 100644 (file)
index 0000000..992063e
--- /dev/null
@@ -0,0 +1,269 @@
+// kln89_page.hxx - a class to manage the simulation of a KLN89
+//                  GPS unit.  Note that this is primarily the 
+//                  simulation of the user interface and display
+//                  - the core GPS calculations such as position
+//                  and waypoint sequencing are done (or should 
+//                  be done) by FG code. 
+//
+// Written by David Luff, started 2005.
+//
+// Copyright (C) 2005 - David C Luff - david.luff@nottingham.ac.uk
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+//
+// $Id$
+
+#ifndef _KLN89_HXX
+#define _KLN89_HXX
+
+#include <Instrumentation/dclgps.hxx>
+#include "kln89_page.hxx"
+
+const int KLN89MapScales[2][21] = {{1, 2, 3, 5, 7, 10, 12, 15, 17, 20, 25, 30, 40, 60, 80, 100, 120, 160, 240, 320, 500},
+                                      {2, 4, 6, 9, 13, 18, 22, 28, 32, 37, 46, 55, 75, 110, 150, 185, 220, 300, 440, 600, 925}};
+
+enum KLN89Mode {
+       KLN89_MODE_DISP,
+       KLN89_MODE_CRSR
+};
+
+/*
+const char* KLN89TimeCodes[20] = { "UTC", "GST", "GDT", "ATS", "ATD", "EST", "EDT", "CST", "CDT", "MST", 
+                                   "MDT", "PST", "PDT", "AKS", "AKD", "HAS", "HAD", "SST", "SDT", "LCL" };
+*/
+
+// Used for storing airport town and county mapped by ID, since currently FG does not store this
+typedef map<string, string> airport_id_str_map_type;
+typedef airport_id_str_map_type::iterator airport_id_str_map_iterator;
+
+class KLN89 : public DCLGPS {
+       
+       friend class KLN89Page;
+       friend class KLN89AptPage;
+       friend class KLN89VorPage;
+       friend class KLN89NDBPage;
+       friend class KLN89IntPage;
+       friend class KLN89UsrPage;
+       friend class KLN89ActPage;
+       friend class KLN89NavPage;
+       friend class KLN89FplPage;
+       friend class KLN89CalPage;
+       friend class KLN89SetPage;
+       friend class KLN89OthPage;
+       friend class KLN89DirPage;
+       friend class KLN89NrstPage;
+       
+public:
+       KLN89(RenderArea2D* instrument);
+       ~KLN89();
+       
+       void bind();
+       void unbind();
+       void update(double dt);
+       
+       inline void SetTurnAnticipation(bool b) { _turnAnticipationEnabled = b; }
+       inline bool GetTurnAnticipation() { return(_turnAnticipationEnabled); }
+
+       inline void SetSuaAlertEnabled(bool b) { _suaAlertEnabled = b; }
+       inline bool GetSuaAlertEnabled() { return(_suaAlertEnabled); }
+       
+       inline void SetAltAlertEnabled(bool b) { _altAlertEnabled = b; }
+       inline bool GetAltAlertEnabled() { return(_altAlertEnabled); }
+       
+       inline bool GetMsgAlert() const { return(!_messageStack.empty()); }
+       
+       void Knob1Right1();
+       void Knob1Left1();
+       void Knob2Right1();
+       void Knob2Left1();
+       void CrsrPressed();
+       void EntPressed();
+       void ClrPressed();
+       void DtoPressed();
+       void NrstPressed();
+       void AltPressed();
+       void OBSPressed();
+       void MsgPressed();
+       
+       void CreateDefaultFlightPlans();
+
+private:
+       //----------------------- Drawing functions which take CHARACTER units -------------------------
+       // Render string s in display field field at position x, y
+       // WHERE POSITION IS IN CHARACTER UNITS!
+       // zero y at bottom?
+       // invert: -1 => no inversion, 0 -> n => 1 char - s[invert] gets inverted, 99 => entire string gets inverted 
+       void DrawText(const string& s, int field, int px, int py, bool bold = false, int invert = -1);
+       
+       void DrawLatitude(double d, int field, int px, int py);
+       void DrawLongitude(double d, int field, int px, int py);
+
+       // Draw a frequency as xxx.xx
+       void DrawFreq(double d, int field, int px, int py);
+       
+       // Draw a time in seconds as hh:mm
+       // NOTE: px is RIGHT JUSTIFIED!
+       void DrawTime(double time, int field, int px, int py);
+
+       // Draw an integer heading, where px specifies the position of the degrees sign at the RIGHT of the value.
+       void DrawHeading(int h, int field, int px, int py);
+       
+       // Draw a distance spec'd as nm as an integer (TODO - may need 1 decimal place if < 100) where px specifies RHS of units.
+       // Some uses definately don't want decimal place though (as at present), so would have to be arg.
+       void DrawDist(double d, int field, int px, int py);
+       
+       // Draw a speed specifed in knots.  px is RHS of the units.  Can draw up to 2 decimal places.
+       void DrawSpeed(double v, int field, int px, int py, int decimals = 0);
+       
+       void Underline(int field, int px, int py, int len);
+       
+       // Render a char at a given position as above (position in CHARACTER units)
+       void DrawChar(char c, int field, int px, int py, bool bold = false, bool invert = false);
+       void DrawSpecialChar(char c, int field, int cx, int cy, bool bold = false);
+       
+       // Draws the dir/dist field at the bottom of the main field
+       void DrawDirDistField(double lat, double lon, int field, int px, int py, bool to_flag = true, bool cursel = false);
+       //
+       //--------------------------------- end char units -----------------------------------------------
+       
+       //----------------------- Drawing functions which take PIXEL units ------------------------------
+       //
+       // Takes instrument *pixel* co-ordinates NOT character units
+       // Position is specified by the bottom of the *visible* portion, by default the left position unless align_right is true.
+       // The return value is the pixel width of the visible portion
+       int DrawSmallChar(char c, int x, int y, bool align_right = false);
+       
+       void DrawFreeChar(char c, int x, int y, bool draw_background = false);
+       //
+       //----------------------------------- end pixel unit functions -----------------------------------
+       
+       void DrawDivider();
+       
+       void DrawEnt(int field = 1, int px = 0, int py = 1);
+       
+       void DrawMessageAlert();
+       
+       void DrawKPH(int field, int cx, int cy);
+       
+       void DrawDTO(int field, int cx, int cy);
+       
+       // Draw the bar that indicates which page we're on (zero-based)
+       void DrawBar(int page);
+       
+       void DrawCDI();
+       
+       void DrawLegTail(int py);
+       void DrawLongLegTail(int py);
+       void DrawHalfLegTail(int py);
+       
+       void UpdateMapHeading();
+       
+       // Draw the moving map
+       // Apt, VOR and SUA drawing can be suspended by setting draw_avs to false, without affecting the stored drawing preference state.
+       void DrawMap(bool draw_avs = true);
+       
+       // Set whether the display should be draw pixelated (more primatives, but might be closer to real-life)
+       // or not (in which case it is assumed that pixels are square and can be merged into quads).
+       bool _pixelated;
+       
+       // Flashing output should be hidden when blink is true 
+       bool _blink;
+       
+       double _cum_dt;
+       
+       // In Crsr mode, CRSR pressed events are passed to the active page, in disp mode they change which page is active
+       KLN89Mode _mode;
+       // And the facility to save a mode
+       KLN89Mode _lastMode;
+       
+       // Increment/Decrement a character in the KLN89 A-Z,0-9 scheme.  
+       // Set gap to true to get a space between A and 9 when wrapping, set wrap to false to disable wrap.
+       char IncChar(char c, bool gap = false, bool wrap = true);
+       char DecChar(char c, bool gap = false, bool wrap = true);
+       
+       // Hackish
+       int _entJump;   // The page to jump back to if ent is pressed.  -1 indicates no jump
+       bool _entRestoreCrsr;   // Indicates that pressing ENT at this point should restore cursor mode
+       
+       // Misc pages
+       // Direct To
+       GPSPage* _dir_page;
+       // Nearest
+       GPSPage* _nrst_page;
+       
+       // Moving-map display stuff
+       int _mapOrientation;    // 0 => North (true) up, 1 => DTK up, 2 => TK up, 3 => heading up (only when connected to external heading source).
+       double _mapHeading;             // Degrees.  The actual map heading gets updated at a lower frequency than DrawMap() is called at, hence we need to store it.
+       double _mapHeadingUpdateTimer;  // Timer to determine when to update the above.
+       bool _mapScaleAuto;             // Indicates that map should autoscale when true.
+       int _mapScaleIndex;             // Index into array of available map scales.
+       int _mapScaleUnits;             // 0 => nm, 1 => km.
+       double _mapScale;       // nm or km from aircraft position to top of map.
+                                               // Note that aircraft position differs depending on orientation, but 'scale' retains the same meaning,
+                                               // so the scale per pixel alters to suit the defined scale when the rendered aircraft position changes.
+       bool _drawSUA;  // special user airspace
+       bool _drawVOR;
+       bool _drawApt;
+       
+       // Convert map to instrument coordinates
+       void MapToInstrument(int &x, int &y);
+       
+       // The following map drawing functions all take MAP co-ordinates, NOT instrument co-ordinates!
+       
+       // Draw the diamond style of user pos
+       void DrawUser1(int x, int y);
+
+       // Draw the airplane style of user pos
+       void DrawUser2(int x, int y);
+       
+       // Draw an airport symbol on the moving map
+       void DrawApt(int x, int y);
+       
+       // Draw a waypoint on the moving map
+       void DrawWaypoint(int x, int y);
+       
+       // Draw a VOR on the moving map
+       void DrawVOR(int x, int y);
+       
+       // Draw an airport or waypoint label on the moving map
+       // Specify position by the map pixel co-ordinate of the left or right, bottom, of the *visible* portion of the label.
+       // The black background quad will automatically overlap this by 1 pixel.
+       void DrawLabel(string s, int x1, int y1, bool right_align = false);
+       
+       int GetLabelQuadrant(double h);
+       int GetLabelQuadrant(double h1, double h2);
+       
+       // Draw a line on the moving map
+       void DrawLine(int x1, int y1, int x2, int y2);
+       
+       // Draw normal sized text on the moving map
+       void DrawMapText(const string& s, int x, int y, bool draw_background = false);
+       
+       void DrawMapUpArrow(int x, int y);
+       
+       // Draw a Quad on the moving map
+       void DrawMapQuad(int x1, int y1, int x2, int y2, bool invert = false);
+       
+       // Airport town and state mapped by ID, since currently FG does not store this
+       airport_id_str_map_type _airportTowns;
+       airport_id_str_map_type _airportStates;
+       
+       // NOTE - It is a deliberate decision not to have a proper message page class,
+       // since button events get directed to the page that was active before the
+       // message was displayed, not the message page itself.
+       bool _dispMsg;  // Set true while the message page is being displayed
+};
+
+#endif  // _KLN89_HXX
diff --git a/src/Instrumentation/KLN89/kln89_page.cxx b/src/Instrumentation/KLN89/kln89_page.cxx
new file mode 100644 (file)
index 0000000..1531648
--- /dev/null
@@ -0,0 +1,204 @@
+// kln89_page.cxx - base class for the "pages" that\r
+//                  are used in the KLN89 GPS unit simulation. \r
+//\r
+// Written by David Luff, started 2005.\r
+//\r
+// Copyright (C) 2005 - David C Luff - david.luff@nottingham.ac.uk\r
+//\r
+// This program is free software; you can redistribute it and/or\r
+// modify it under the terms of the GNU General Public License as\r
+// published by the Free Software Foundation; either version 2 of the\r
+// License, or (at your option) any later version.\r
+//\r
+// This program is distributed in the hope that it will be useful, but\r
+// WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
+// General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, write to the Free Software\r
+// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\r
+//\r
+// $Id$\r
+\r
+#include "kln89_page.hxx"\r
+\r
+KLN89Page::KLN89Page(KLN89* parent) \r
+: GPSPage(parent) {\r
+       _kln89 = (KLN89*)parent;\r
+       _entInvert = false;\r
+       _to_flag = true;\r
+}\r
+\r
+KLN89Page::~KLN89Page() {\r
+}\r
+\r
+void KLN89Page::Update(double dt) {\r
+       bool crsr = (_kln89->_mode == KLN89_MODE_CRSR ? true : false);\r
+       bool nav1 = (_name == "NAV" && _subPage == 0);\r
+       bool nav4 = (_name == "NAV" && _subPage == 3);\r
+       // The extra level of check for the ACT page is necessary since\r
+       // ACT is implemented by using the other waypoint pages as\r
+       // appropriate.\r
+       bool act = (_kln89->_activePage->GetName() == "ACT");\r
+       _kln89->DrawDivider();\r
+       if(crsr) {\r
+               if(!nav4) _kln89->DrawText("*CRSR*", 1, 0, 0);\r
+               if(_uLinePos == 0) _kln89->Underline(1, 3, 1, 3);\r
+       } else {\r
+               if(!nav4) {\r
+                       if(act) {\r
+                               _kln89->DrawText("ACT", 1, 0, 0);\r
+                       } else {\r
+                               _kln89->DrawText(_name, 1, 0, 0);\r
+                       }\r
+                       if(_name == "DIR") {\r
+                               // Don't draw a subpage number\r
+                       } else if(_name == "USR" || _name == "FPL") {\r
+                               // Zero-based\r
+                               _kln89->DrawText(GPSitoa(_subPage), 1, 4, 0);\r
+                       } else {\r
+                               // One-based\r
+                               _kln89->DrawText(GPSitoa(_subPage+1), 1, 4, 0);\r
+                       }\r
+               }\r
+       }\r
+       if(crsr && _uLinePos == 0 && _kln89->_blink) {\r
+               // Don't draw\r
+       } else {\r
+               if(_kln89->_obsMode) {\r
+                       _kln89->DrawText(GPSitoa(_kln89->_obsHeading), 1, 3, 1);\r
+               } else {\r
+                       _kln89->DrawText("Leg", 1, 3, 1);\r
+               }\r
+       }\r
+       _kln89->DrawText((_kln89->GetDistVelUnitsSI() ? "km" : "nm"), 1, 4, 3);\r
+       GPSWaypoint* awp = _parent->GetActiveWaypoint();\r
+       if(_kln89->_navFlagged) {\r
+               _kln89->DrawText("--.-", 1, 0 ,3);\r
+               // Only nav1 still gets speed drawn if nav is flagged - not ACT\r
+               if(!nav1) _kln89->DrawText("------", 1, 0, 2);\r
+       } else {\r
+               char buf[8];\r
+               float f = _parent->GetDistToActiveWaypoint() * (_kln89->GetDistVelUnitsSI() ? 0.001 : SG_METER_TO_NM);\r
+               snprintf(buf, 5, (f >= 100.0 ? "%4.0f" : "%4.1f"), f);\r
+               string s = buf;\r
+               _kln89->DrawText(s, 1, 4 - s.size(), 3, true);\r
+               // Draw active waypoint ID, except for\r
+               // nav1, act, and any waypoint pages matching \r
+               // active waypoint that need speed drawn instead.\r
+               if(act || nav1 || (awp && awp->id == _id)) {\r
+                       _kln89->DrawSpeed(_kln89->_groundSpeed_kts, 1, 5, 2);\r
+               } else {\r
+                       if(!(_kln89->_waypointAlert && _kln89->_blink)) _kln89->DrawText(awp->id, 1, 0, 2);\r
+               }\r
+       }\r
+       /*\r
+       if(_noNrst) {\r
+               _kln89->DrawText("  No  ", 1, 0, 1, false, 99);\r
+               _kln89->DrawText(" Nrst ", 1, 0, 0, false, 99);\r
+       }\r
+       */\r
+       if(_scratchpadMsg) {\r
+               _kln89->DrawText(_scratchpadLine1, 1, 0, 1, false, 99);\r
+               _kln89->DrawText(_scratchpadLine2, 1, 0, 0, false, 99);\r
+               _scratchpadTimer += dt;\r
+               if(_scratchpadTimer > 4.0) {\r
+                       _scratchpadMsg = false;\r
+                       _scratchpadTimer = 0.0;\r
+               }\r
+       }\r
+}\r
+\r
+void KLN89Page::ShowScratchpadMessage(string line1, string line2) {\r
+       _scratchpadLine1 = line1;\r
+       _scratchpadLine2 = line2;\r
+       _scratchpadTimer = 0.0;\r
+       _scratchpadMsg = true;\r
+}      \r
+\r
+void KLN89Page::Knob1Left1() {\r
+       if(_kln89->_mode == KLN89_MODE_CRSR) {\r
+               if(_uLinePos > 0) _uLinePos--;\r
+       }\r
+}\r
+\r
+void KLN89Page::Knob1Right1() {\r
+       if(_kln89->_mode == KLN89_MODE_CRSR) {\r
+               if(_uLinePos < _maxULinePos) _uLinePos++;\r
+       }\r
+}\r
+\r
+void KLN89Page::Knob2Left1() {\r
+       if(_kln89->_mode != KLN89_MODE_CRSR) {\r
+               GPSPage::Knob2Left1(); \r
+       } else {\r
+               if(_uLinePos == 0 && _kln89->_obsMode) {\r
+                       _kln89->_obsHeading--;\r
+                       if(_kln89->_obsHeading < 0) {\r
+                               _kln89->_obsHeading += 360;\r
+                       }\r
+                       _kln89->SetOBSFromWaypoint();\r
+               }\r
+       }\r
+}\r
+\r
+void KLN89Page::Knob2Right1() {\r
+       if(_kln89->_mode != KLN89_MODE_CRSR) {\r
+               GPSPage::Knob2Right1(); \r
+       } else {\r
+               if(_uLinePos == 0 && _kln89->_obsMode) {\r
+                       _kln89->_obsHeading++;\r
+                       if(_kln89->_obsHeading > 359) {\r
+                               _kln89->_obsHeading -= 360;\r
+                       }\r
+                       _kln89->SetOBSFromWaypoint();\r
+               }\r
+       }\r
+}\r
+\r
+void KLN89Page::CrsrPressed() {\r
+       // Stick some sensible defaults in\r
+       if(_kln89->_obsMode) {\r
+               _uLinePos = 0;\r
+       } else {\r
+               _uLinePos = 1;\r
+       }\r
+       _maxULinePos = 1;\r
+}\r
+\r
+void KLN89Page::EntPressed() {}\r
+void KLN89Page::ClrPressed() {}\r
+void KLN89Page::DtoPressed() {}\r
+void KLN89Page::NrstPressed() {}\r
+void KLN89Page::AltPressed() {}\r
+\r
+void KLN89Page::OBSPressed() {\r
+       if(_kln89->_obsMode) {\r
+               // If ORS2 and not slaved to gps\r
+               _uLinePos = 0;\r
+       } else {\r
+               // Don't leave the cursor on in the leg position.\r
+               if(_uLinePos == 0) {\r
+                       _kln89->_mode = KLN89_MODE_DISP;\r
+               }\r
+       }\r
+}\r
+\r
+void KLN89Page::MsgPressed() {}\r
+\r
+void KLN89Page::CleanUp() {\r
+       _kln89->_cleanUpPage = -1;\r
+}\r
+\r
+void KLN89Page::LooseFocus() {\r
+       _entInvert = false;\r
+}\r
+\r
+void KLN89Page::SetId(string s) {\r
+       _id = s;\r
+}\r
+\r
+string KLN89Page::GetId() {\r
+       return(_id);\r
+}\r
diff --git a/src/Instrumentation/KLN89/kln89_page.hxx b/src/Instrumentation/KLN89/kln89_page.hxx
new file mode 100644 (file)
index 0000000..251436b
--- /dev/null
@@ -0,0 +1,93 @@
+// kln89_page.hxx - base class for the "pages" that\r
+//                  are used in the KLN89 GPS unit simulation. \r
+//\r
+// Written by David Luff, started 2005.\r
+//\r
+// Copyright (C) 2005 - David C Luff - david.luff@nottingham.ac.uk\r
+//\r
+// This program is free software; you can redistribute it and/or\r
+// modify it under the terms of the GNU General Public License as\r
+// published by the Free Software Foundation; either version 2 of the\r
+// License, or (at your option) any later version.\r
+//\r
+// This program is distributed in the hope that it will be useful, but\r
+// WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
+// General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program; if not, write to the Free Software\r
+// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\r
+//\r
+// $Id$\r
+\r
+#ifndef _KLN89_PAGE_HXX\r
+#define _KLN89_PAGE_HXX\r
+\r
+#include <Instrumentation/dclgps.hxx>\r
+#include "kln89.hxx"\r
+\r
+class KLN89;\r
+\r
+class KLN89Page : public GPSPage {\r
+\r
+public:\r
+       KLN89Page(KLN89* parent);\r
+       virtual ~KLN89Page();\r
+       virtual void Update(double dt);\r
+       virtual void Knob1Left1();\r
+       virtual void Knob1Right1();\r
+       virtual void Knob2Left1();\r
+       virtual void Knob2Right1();\r
+       virtual void CrsrPressed();\r
+       virtual void EntPressed();\r
+       virtual void ClrPressed();\r
+       // Even though some/all of the buttons below aren't processed directly by the current page,\r
+       // the current page often needs to save or change some state when they are pressed, and \r
+       // hence should provide a function to handle them.\r
+       virtual void DtoPressed();\r
+       virtual void NrstPressed();\r
+       virtual void AltPressed();\r
+       virtual void OBSPressed();\r
+       virtual void MsgPressed();\r
+       \r
+       // See base class comments for this.\r
+       virtual void CleanUp();\r
+       \r
+       // ditto\r
+       virtual void LooseFocus();\r
+       \r
+       inline void SetEntInvert(bool b) { _entInvert = b; }\r
+       \r
+       // Get / Set a waypoint id, NOT the page name!\r
+       virtual void SetId(string s);\r
+       virtual string GetId();\r
+       \r
+protected:\r
+       KLN89* _kln89;\r
+       \r
+       // Underline position in cursor mode is not persistant when subpage is changed - hence we only need one variable per page for it.\r
+       // Note that pos 0 is special - this is the leg pos in field 1, so pos will normally be set to 1 when crsr is pressed.\r
+       // Also note that in general it doesn't seem to wrap.\r
+       unsigned int _uLinePos;\r
+       unsigned int _maxULinePos;\r
+       \r
+       // This is NOT the main gps to/from flag - derived page classes can use this flag\r
+       // for any purpose, typically whether a radial bearing should be displayed to or from.\r
+       bool _to_flag;  // true for TO, false for FROM\r
+       \r
+       // Invert ID and display ENT in field 1\r
+       bool _entInvert;\r
+       \r
+       string _id;             // The ID of the waypoint that the page is displaying.\r
+                                       // Doesn't make sense for all pages, but does for all the data pages.\r
+                                       \r
+       void ShowScratchpadMessage(string line1, string line2);\r
+                                       \r
+       bool _scratchpadMsg;            // Set true when there is a scratchpad message to display\r
+       double _scratchpadTimer;        // Used for displaying the scratchpad messages for the right amount of time.\r
+       string _scratchpadLine1;\r
+       string _scratchpadLine2;\r
+};\r
+\r
+#endif // _KLN89_PAGE_HXX\r