]> git.mxchange.org Git - flightgear.git/blob - src/Instrumentation/KLN89/kln89.cxx
Merge branch 'next' of gitorious.org:fg/flightgear into atcdcl
[flightgear.git] / src / Instrumentation / KLN89 / kln89.cxx
1 // kln89_page.cxx - a class to manage the simulation of a KLN89
2 //                  GPS unit.  Note that this is primarily the 
3 //                  simulation of the user interface and display
4 //                  - the core GPS calculations such as position
5 //                  and waypoint sequencing are done (or should 
6 //                  be done) by FG code. 
7 //
8 // Written by David Luff, started 2005.
9 //
10 // Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
11 //
12 // This program is free software; you can redistribute it and/or
13 // modify it under the terms of the GNU General Public License as
14 // published by the Free Software Foundation; either version 2 of the
15 // License, or (at your option) any later version.
16 //
17 // This program is distributed in the hope that it will be useful, but
18 // WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20 // General Public License for more details.
21 //
22 // You should have received a copy of the GNU General Public License
23 // along with this program; if not, write to the Free Software
24 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
25 //
26 // $Id$
27
28 #include "kln89.hxx"
29 #include "kln89_page.hxx"
30 #include "kln89_page_apt.hxx"
31 #include "kln89_page_vor.hxx"
32 #include "kln89_page_ndb.hxx"
33 #include "kln89_page_int.hxx"
34 #include "kln89_page_usr.hxx"
35 #include "kln89_page_act.hxx"
36 #include "kln89_page_nav.hxx"
37 #include "kln89_page_fpl.hxx"
38 #include "kln89_page_cal.hxx"
39 #include "kln89_page_set.hxx"
40 #include "kln89_page_oth.hxx"
41 #include "kln89_page_alt.hxx"
42 #include "kln89_page_dir.hxx"
43 #include "kln89_page_nrst.hxx"
44 #include "kln89_symbols.hxx"
45 #include <iostream>
46
47 #include <ATCDCL/ATCProjection.hxx>
48
49 #include <Main/fg_props.hxx>
50 #include <simgear/math/SGMath.hxx>
51 #include <simgear/structure/commands.hxx>
52 #include <Airports/simple.hxx>
53
54 using std::cout;
55
56 // Command callbacks for FlightGear
57
58 static bool do_kln89_msg_pressed(const SGPropertyNode* arg) {
59         //cout << "do_kln89_msg_pressed called!\n";
60         KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
61         gps->MsgPressed();
62         return(true);
63 }
64
65 static bool do_kln89_obs_pressed(const SGPropertyNode* arg) {
66         //cout << "do_kln89_obs_pressed called!\n";
67         KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
68         gps->OBSPressed();
69         return(true);
70 }
71
72 static bool do_kln89_alt_pressed(const SGPropertyNode* arg) {
73         //cout << "do_kln89_alt_pressed called!\n";
74         KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
75         gps->AltPressed();
76         return(true);
77 }
78
79 static bool do_kln89_nrst_pressed(const SGPropertyNode* arg) {
80         KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
81         gps->NrstPressed();
82         return(true);
83 }
84
85 static bool do_kln89_dto_pressed(const SGPropertyNode* arg) {
86         KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
87         gps->DtoPressed();
88         return(true);
89 }
90
91 static bool do_kln89_clr_pressed(const SGPropertyNode* arg) {
92         KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
93         gps->ClrPressed();
94         return(true);
95 }
96
97 static bool do_kln89_ent_pressed(const SGPropertyNode* arg) {
98         KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
99         gps->EntPressed();
100         return(true);
101 }
102
103 static bool do_kln89_crsr_pressed(const SGPropertyNode* arg) {
104         KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
105         gps->CrsrPressed();
106         return(true);
107 }
108
109 static bool do_kln89_knob1left1(const SGPropertyNode* arg) {
110         KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
111         gps->Knob1Left1();
112         return(true);
113 }
114
115 static bool do_kln89_knob1right1(const SGPropertyNode* arg) {
116         KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
117         gps->Knob1Right1();
118         return(true);
119 }
120
121 static bool do_kln89_knob2left1(const SGPropertyNode* arg) {
122         KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
123         gps->Knob2Left1();
124         return(true);
125 }
126
127 static bool do_kln89_knob2right1(const SGPropertyNode* arg) {
128         KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
129         gps->Knob2Right1();
130         return(true);
131 }
132
133 // End command callbacks
134
135 KLN89::KLN89(RenderArea2D* instrument) 
136 : DCLGPS(instrument) {
137         _mode = KLN89_MODE_DISP;
138         _blink = false;
139         _cum_dt = 0.0;
140         _nFields = 2;
141         _maxFields = 2;
142         _xBorder = 0;
143         _yBorder = 4;
144         // ..Field..[0] => no fields in action
145         _xFieldBorder[0] = 0;
146         _xFieldBorder[1] = 0;
147         _yFieldBorder[0] = 0;
148         _yFieldBorder[1] = 0;
149         _xFieldBorder[2] = 2;
150         _yFieldBorder[2] = 0;
151         _xFieldStart[0] = 0;
152         _xFieldStart[1] = 0;
153         _xFieldStart[2] = 45;
154         _yFieldStart[0] = 0;
155         _yFieldStart[1] = 0;
156         _yFieldStart[2] = 0;
157         
158         //_pixelated = true;
159         _pixelated = false;
160
161         // Cyclic pages
162         _pages.clear();
163         KLN89Page* apt_page = new KLN89AptPage(this);
164         _pages.push_back(apt_page);
165         KLN89Page* vor_page = new KLN89VorPage(this);
166         _pages.push_back(vor_page);
167         KLN89Page* ndb_page = new KLN89NDBPage(this);
168         _pages.push_back(ndb_page);
169         KLN89Page* int_page = new KLN89IntPage(this);
170         _pages.push_back(int_page);
171         KLN89Page* usr_page = new KLN89UsrPage(this);
172         _pages.push_back(usr_page);
173         KLN89Page* act_page = new KLN89ActPage(this);
174         _pages.push_back(act_page);
175         KLN89Page* nav_page = new KLN89NavPage(this);
176         _pages.push_back(nav_page);
177         KLN89Page* fpl_page = new KLN89FplPage(this);
178         _pages.push_back(fpl_page);
179         KLN89Page* cal_page = new KLN89CalPage(this);
180         _pages.push_back(cal_page);
181         KLN89Page* set_page = new KLN89SetPage(this);
182         _pages.push_back(set_page);
183         KLN89Page* oth_page = new KLN89OthPage(this);
184         _pages.push_back(oth_page);
185         _nPages = _pages.size();
186         _curPage = 0;
187         
188         // Other pages
189         _alt_page = new KLN89AltPage(this);
190         _dir_page = new KLN89DirPage(this);
191         _nrst_page = new KLN89NrstPage(this);
192         
193         _activePage = apt_page;
194         _obsMode = false;
195         _dto = false;
196         _fullLegMode = true;
197         _obsHeading = 215;
198
199         // User-settable configuration.  Eventually this should be user-achivable in order that settings can be persistent between sessions.
200         _altUnits = GPS_ALT_UNITS_FT;
201         _baroUnits = GPS_PRES_UNITS_IN;
202         _velUnits = GPS_VEL_UNITS_KT;
203         _distUnits = GPS_DIST_UNITS_NM;
204         _suaAlertEnabled = false;
205         _altAlertEnabled = false;
206         _minDisplayBrightness = 4;
207         _defaultFirstChar = 'A';        
208         
209         if(_baroUnits == GPS_PRES_UNITS_IN) {
210                 _userBaroSetting = 2992;
211         } else {
212                 _userBaroSetting = 1013;
213         }
214         
215         _maxFlightPlans = 26;
216         for(unsigned int i=0; i<_maxFlightPlans; ++i) {
217                 GPSFlightPlan* fp = new GPSFlightPlan;
218                 fp->waypoints.clear();
219                 _flightPlans.push_back(fp);
220         }
221         _activeFP = _flightPlans[0];
222         
223         _entJump = _clrJump = -1;
224         _jumpRestoreCrsr = false;
225         
226         _dispMsg = false;
227         
228         _dtoReview = false;
229
230         // Moving map stuff
231         _mapOrientation = 0;
232         _mapHeading = 0.0;
233         _mapHeadingUpdateTimer = 0.0;
234         _drawSUA = false;
235         _drawVOR = false;
236         _drawApt = true;
237         //_mapScaleIndex = 20;
238         _mapScaleIndex = 7;     // I think that the above is more accurate for no-flightplan default, but this is more sane for initial testing!
239         _mapScaleAuto = true;
240         
241         // 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
242         // TODO - do this better one day!
243         _airportTowns["KSFO"] = "San Francisco";
244         _airportTowns["KSQL"] = "San Carlos";
245         _airportTowns["KPAO"] = "Palo Alto";
246         _airportTowns["KNUQ"] = "Mountain View";
247         _airportTowns["KSJC"] = "San Jose";
248         _airportTowns["KRHV"] = "San Jose";
249         _airportTowns["E16"] = "San Martin";
250         _airportTowns["KWVI"] = "Watsonville";
251         _airportTowns["KOAK"] = "Oakland";
252         _airportTowns["KHWD"] = "Hayward";
253         _airportTowns["KLVK"] = "Livermore";
254         _airportTowns["KCCR"] = "Concord";
255         _airportTowns["KTCY"] = "Tracy";
256         _airportTowns["KSCK"] = "Stockton";
257         _airportTowns["KHAF"] = "Half Moon Bay";
258         
259         _airportStates["KSFO"] = "CA";
260         _airportStates["KSQL"] = "CA";
261         _airportStates["KPAO"] = "CA";
262         _airportStates["KNUQ"] = "CA";
263         _airportStates["KSJC"] = "CA";
264         _airportStates["KRHV"] = "CA";
265         _airportStates["E16"] = "CA";
266         _airportStates["KWVI"] = "CA";
267         _airportStates["KOAK"] = "CA";
268         _airportStates["KHWD"] = "CA";
269         _airportStates["KLVK"] = "CA";
270         _airportStates["KCCR"] = "CA";
271         _airportStates["KTCY"] = "CA";
272         _airportStates["KSCK"] = "CA";
273         _airportStates["KHAF"] = "CA";
274 }
275
276 KLN89::~KLN89() {
277         for(unsigned int i=0; i<_pages.size(); ++i) {
278                 delete _pages[i];
279         }
280         
281         delete _alt_page;
282         delete _dir_page;
283         delete _nrst_page;
284         
285         for(unsigned int i=0; i<_maxFlightPlans; ++i) {
286                 ClearFlightPlan(i);
287                 delete _flightPlans[i];
288         }
289 }
290
291 void KLN89::bind() {
292         fgTie("/instrumentation/gps/message-alert", this, &KLN89::GetMsgAlert);
293         DCLGPS::bind();
294 }
295
296 void KLN89::unbind() {
297         fgUntie("/instrumentation/gps/message-alert");
298         DCLGPS::unbind();
299 }
300
301 void KLN89::init() {
302         globals->get_commands()->addCommand("kln89_msg_pressed", do_kln89_msg_pressed);
303         globals->get_commands()->addCommand("kln89_obs_pressed", do_kln89_obs_pressed);
304         globals->get_commands()->addCommand("kln89_alt_pressed", do_kln89_alt_pressed);
305         globals->get_commands()->addCommand("kln89_nrst_pressed", do_kln89_nrst_pressed);
306         globals->get_commands()->addCommand("kln89_dto_pressed", do_kln89_dto_pressed);
307         globals->get_commands()->addCommand("kln89_clr_pressed", do_kln89_clr_pressed);
308         globals->get_commands()->addCommand("kln89_ent_pressed", do_kln89_ent_pressed);
309         globals->get_commands()->addCommand("kln89_crsr_pressed", do_kln89_crsr_pressed);
310         globals->get_commands()->addCommand("kln89_knob1left1", do_kln89_knob1left1);
311         globals->get_commands()->addCommand("kln89_knob1right1", do_kln89_knob1right1);
312         globals->get_commands()->addCommand("kln89_knob2left1", do_kln89_knob2left1);
313         globals->get_commands()->addCommand("kln89_knob2right1", do_kln89_knob2right1);
314         
315         DCLGPS::init();
316 }
317
318 void KLN89::update(double dt) {
319         // Run any positional calc's required first
320         DCLGPS::update(dt);
321         
322         // Set the display brightness.  This should be reduced in response to falling light
323         // (i.e. nighttime), or the user covering the photocell that detects the light level.
324         // At the moment I don't know how to detect nighttime or actual light level, so only
325         // respond to the photocell being obscured.
326         // TODO - reduce the brightness in response to nighttime / lowlight.
327         float rgba[4] = {1.0, 0.0, 0.0, 1.0};
328         if(fgGetBool("/instrumentation/kln89/photocell-obscured")) {
329                 rgba[0] -= (9 - _minDisplayBrightness) * 0.05;
330         }
331         _instrument->SetPixelColor(rgba);
332         
333         _cum_dt += dt;
334         if(_blink) {
335                 if(_cum_dt > 0.2) {
336                         _cum_dt = 0.0;
337                         _blink = false;
338                 }
339         } else {
340                 if(_cum_dt > 0.8) {
341                         _cum_dt = 0.0;
342                         _blink = true;
343                 }
344         }
345         
346         _mapHeadingUpdateTimer += dt;
347         if(_mapHeadingUpdateTimer > 1.0) {
348                 UpdateMapHeading();
349                 _mapHeadingUpdateTimer = 0.0;
350         }
351         
352         _instrument->Flush();
353         _instrument->DrawBackground();
354         
355         if(_dispMsg) {
356                 if(_messageStack.empty()) {
357                         DrawText("No Message", 0, 5, 2);
358                 } else {
359                         // TODO - parse the message string for special strings that indicate degrees signs etc!
360                         DrawText(*_messageStack.begin(), 0, 0, 3);
361                 }
362                 return;
363         } else {
364                 if(!_messageStack.empty()) {
365                         DrawMessageAlert();
366                 }
367         }
368         
369         // Draw the indicator that shows which page we are on.
370         if(_curPage == 6 && _activePage->GetSubPage() == 3) {
371                 // Don't draw the bar on the nav-4 page
372         } else if((_activePage != _nrst_page) && (_activePage != _dir_page) && (_activePage != _alt_page) && (!_dispMsg)) {
373                 // Don't draw the bar on the NRST, DTO or MSG pages
374                 DrawBar(_curPage);
375         }
376         
377         _activePage->Update(dt);
378 }
379
380 void KLN89::CreateDefaultFlightPlans() {
381         // TODO - read these in from preferences.xml or similar instead!!!!
382         // Create some hardwired default flightplans for testing.
383         vector<string> ids;
384         vector<GPSWpType> wps;
385         
386         ids.clear();
387         wps.clear();
388         ids.push_back("KLSN");
389         wps.push_back(GPS_WP_APT);
390         ids.push_back("VOLTA");
391         wps.push_back(GPS_WP_INT);
392         ids.push_back("C83");
393         wps.push_back(GPS_WP_APT);
394         CreateFlightPlan(_flightPlans[5], ids, wps);
395         
396         ids.clear();
397         wps.clear();
398         ids.push_back("KCCR");
399         wps.push_back(GPS_WP_APT);
400         ids.push_back("KHAF");
401         wps.push_back(GPS_WP_APT);
402         CreateFlightPlan(_flightPlans[4], ids, wps);
403         
404         ids.clear();
405         wps.clear();
406         ids.push_back("KLVK");
407         wps.push_back(GPS_WP_APT);
408         ids.push_back("OAK");
409         wps.push_back(GPS_WP_VOR);
410         ids.push_back("PORTE");
411         wps.push_back(GPS_WP_INT);
412         ids.push_back("KHAF");
413         wps.push_back(GPS_WP_APT);
414         CreateFlightPlan(_flightPlans[3], ids, wps);
415         
416         ids.clear();
417         wps.clear();
418         ids.push_back("KDPA");
419         wps.push_back(GPS_WP_APT);
420         ids.push_back("OBK");
421         wps.push_back(GPS_WP_VOR);
422         ids.push_back("ENW");
423         wps.push_back(GPS_WP_VOR);
424         ids.push_back("KRAC");
425         wps.push_back(GPS_WP_APT);
426         CreateFlightPlan(_flightPlans[2], ids, wps);
427         //cout << "Size of FP2 WP list is " << _flightPlans[2]->waypoints.size() << '\n';
428         
429         ids.clear();
430         wps.clear();
431         ids.push_back("KSFO");
432         ids.push_back("KOAK");
433         wps.push_back(GPS_WP_APT);
434         wps.push_back(GPS_WP_APT);
435         CreateFlightPlan(_flightPlans[1], ids, wps);
436         
437         ids.clear();
438         wps.clear();
439         //ids.push_back("KOSH");
440         ids.push_back("KSFO");
441         ids.push_back("KHAF");
442         ids.push_back("OSI");
443         ids.push_back("KSQL");
444         //ids.push_back("KPAO");
445         //ids.push_back("KHWD");
446         wps.push_back(GPS_WP_APT);
447         wps.push_back(GPS_WP_APT);
448         wps.push_back(GPS_WP_VOR);
449         wps.push_back(GPS_WP_APT);
450         //wps.push_back(GPS_WP_APT);
451         //wps.push_back(GPS_WP_APT);
452         CreateFlightPlan(_flightPlans[0], ids, wps);
453         
454         /*
455         ids.clear();
456         wps.clear();
457         ids.push_back("KLVK");
458         ids.push_back("KHWD");
459         wps.push_back(GPS_WP_APT);
460         wps.push_back(GPS_WP_APT);
461         CreateFlightPlan(_flightPlans[0], ids, wps);
462         */
463 }
464
465 void KLN89::SetBaroUnits(int n, bool wrap) {
466         if(n < 1) {
467                 _baroUnits = (KLN89PressureUnits)(wrap ? 3 : 1);
468         } else if(n > 3) {
469                 _baroUnits = (KLN89PressureUnits)(wrap ? 1 : 3);
470         } else {
471                 _baroUnits = (KLN89PressureUnits)n;
472         }
473 }
474
475 void KLN89::Knob1Right1() {
476         if(_mode == KLN89_MODE_DISP) {
477                 _activePage->LooseFocus();
478                 if(_cleanUpPage >= 0) {
479                         _pages[(unsigned int)_cleanUpPage]->CleanUp();
480                         _cleanUpPage = -1;
481                 }
482                 _curPage++;
483                 if(_curPage >= _pages.size()) _curPage = 0;
484                 _activePage = _pages[_curPage];
485         } else {
486                 _activePage->Knob1Right1();
487         }
488         update(0.0);
489 }
490
491 void KLN89::Knob1Left1() {
492         if(_mode == KLN89_MODE_DISP) {
493                 _activePage->LooseFocus();
494                 if(_cleanUpPage >= 0) {
495                         _pages[(unsigned int)_cleanUpPage]->CleanUp();
496                         _cleanUpPage = -1;
497                 }
498                 if(_curPage == 0) {
499                         _curPage = _pages.size() - 1;
500                 } else {
501                         _curPage--;
502                 }
503                 _activePage = _pages[_curPage];
504         } else {
505                 _activePage->Knob1Left1();
506         }
507         update(0.0);
508 }
509
510 void KLN89::Knob2Left1() {
511         _activePage->Knob2Left1();
512 }
513
514 void KLN89::Knob2Right1() {
515         _activePage->Knob2Right1();
516 }
517
518 void KLN89::CrsrPressed() {
519         _dispMsg = false;
520         // CRSR cannot be switched off on nrst page.
521         if(_activePage == _nrst_page) { return; }
522         // CRSR is always off when inner-knob is out on nav4 page.
523         if(_curPage == 6 && _activePage->GetSubPage() == 3 && fgGetBool("/instrumentation/kln89/scan-pull")) { return; }
524         if(_cleanUpPage >= 0) {
525                 _pages[(unsigned int)_cleanUpPage]->CleanUp();
526                 _cleanUpPage = -1;
527         }
528         _jumpRestoreCrsr = false;
529         _entJump = _clrJump = -1;
530         ((KLN89Page*)_activePage)->SetEntInvert(false);
531         if(_mode == KLN89_MODE_DISP) {
532                 _mode = KLN89_MODE_CRSR;
533                 _activePage->CrsrPressed();
534         } else {
535                 _mode = KLN89_MODE_DISP;
536                 _activePage->CrsrPressed();
537         }
538         update(0.0);
539 }
540
541 void KLN89::EntPressed() {
542         if(_entJump >= 0) {
543                 if(_curPage < 5) {
544                         // one of the data pages.  Signal ent pressed to it here, and ent pressed to the call back page a few lines further down.
545                         // Ie. 2 ent pressed signals in this case is deliberate.
546                         _activePage->EntPressed();
547                 }
548                 _curPage = _entJump;
549                 _activePage = _pages[(unsigned int)_entJump];
550                 if(_jumpRestoreCrsr) _mode = KLN89_MODE_CRSR;
551                 _entJump = _clrJump = -1;
552         }
553         if(_activePage == _dir_page) {
554                 _dir_page->EntPressed();
555                 _mode = KLN89_MODE_DISP;
556                 _activePage = _pages[_curPage];
557         } else {
558                 _activePage->EntPressed();
559         }
560 }
561
562 void KLN89::ClrPressed() {
563         if(_clrJump >= 0) {
564                 _curPage = _clrJump;
565                 _activePage = _pages[(unsigned int)_clrJump];
566                 if(_jumpRestoreCrsr) _mode = KLN89_MODE_CRSR;
567                 _entJump = _clrJump = -1;
568         }
569         _activePage->ClrPressed();
570 }
571
572 void KLN89::DtoPressed() {
573         if(_activePage != _dir_page) {
574                 // Figure out which waypoint the dir page should display, according to the following rules:
575                 // 1. If the FPL 0 page is displayed AND the cursor is over one of the waypoints, display that waypoint.
576                 // 2. If the NAV 4 page is displayed with the inner knob pulled out, display the waypoint highlighted in the lower RH corner of the nav page.
577                 // 3. If any of APT, VOR, NDB, INT, USR or ACT pages is displayed then display the waypoint being viewed.
578                 // 4. If none of the above, display the active waypoint, unless the active waypoint is the MAP of an approach and it has been flown past 
579                 // (no waypoint sequence past the MAP), in which case display the first waypoint of the missed approach procedure.
580                 // 5. If none of the above (i.e. no active waypoint) then display blanks.
581                 if(_curPage <= 5) {
582                         // APT, VOR, NDB, INT, USR or ACT
583                         if(!_activePage->GetId().empty()) {     // Guard against no user waypoints defined
584                                 _dir_page->SetId(_activePage->GetId());
585                         } else {
586                                 _dir_page->SetId(_activeWaypoint.id);
587                         }
588                 } else if(_curPage == 6 && _activePage->GetSubPage() == 3 && fgGetBool("/instrumentation/kln89/scan-pull") && _activeFP->waypoints.size()) {
589                         // NAV 4
590                         _dir_page->SetId(((KLN89NavPage*)_activePage)->GetNav4WpId());
591                 } else if(_curPage == 7 && _activePage->GetSubPage() == 0 && _mode == KLN89_MODE_CRSR) {
592                         // FPL 0
593                         if(!_activePage->GetId().empty()) {
594                                 //cout << "Not empty!!!\n";
595                                 _dir_page->SetId(_activePage->GetId());
596                         } else {
597                                 //cout << "empty :-(\n";
598                                 _dir_page->SetId(_activeWaypoint.id);
599                         }
600                 } else {
601                         _dir_page->SetId(_activeWaypoint.id);
602                 }
603                 // This need to come after the bit before otherwise the FPL or NAV4 page clears their current ID when it looses focus.
604                 _activePage->LooseFocus();
605                 _activePage = _dir_page;
606                 _mode = KLN89_MODE_CRSR;
607         }
608 }
609
610 void KLN89::NrstPressed() {
611         if(_activePage != _nrst_page) {
612                 _activePage->LooseFocus();      // TODO - check whether we should call loose focus here
613                 _lastActivePage = _activePage;
614                 _activePage = _nrst_page;
615                 _lastMode = _mode;
616                 _mode = KLN89_MODE_CRSR;
617         } else {
618                 _activePage = _lastActivePage;
619                 _mode = _lastMode;
620         }
621 }
622         
623 void KLN89::AltPressed() {
624         if(_activePage != _alt_page) {
625                 _activePage->LooseFocus();      // TODO - check whether we should call loose focus here
626                 _lastActivePage = _activePage;
627                 _alt_page->SetSubPage(0);
628                 _activePage = _alt_page;
629                 _lastMode = _mode;
630                 _mode = KLN89_MODE_CRSR;
631         } else {
632                 _alt_page->LooseFocus();
633                 if(_alt_page->GetSubPage() == 0) {
634                         _alt_page->SetSubPage(1);
635                         _mode = KLN89_MODE_CRSR;
636                 } else {
637                         _activePage = _lastActivePage;
638                         _mode = _lastMode;
639                 }
640         }
641 }
642
643 void KLN89::OBSPressed() {
644         ToggleOBSMode();
645         if(_obsMode) {
646                 if(!fgGetBool("/instrumentation/nav/slaved-to-gps")) {
647                         // NOTE: this only applies to ORS 02 firmware, in ORS 01
648                         // CRSR mode is not automatically set when OBS is started.
649                         _mode = KLN89_MODE_CRSR;
650                 }
651                 _activePage->OBSPressed();
652         }
653 }
654
655 void KLN89::MsgPressed() {
656         // TODO - handle persistent messages such as SUA alerting.
657         // (The message annunciation flashes before first view, but afterwards remains continuously lit with the message available
658         // until the potential conflict no longer pertains).
659         if(_dispMsg && _messageStack.size()) {
660                 _messageStack.pop_front();
661         }
662         _dispMsg = !_dispMsg;
663 }
664
665 void KLN89::ToggleOBSMode() {
666         DCLGPS::ToggleOBSMode();
667 }
668
669 void KLN89::DtoInitiate(const string& id) {
670         _dtoReview = false;
671         // Set the current page to NAV1
672         _curPage = 6;
673         _activePage = _pages[_curPage];
674         _activePage->SetSubPage(0);
675         // TODO - need to output a scratchpad message with the new course, but we don't know it yet!
676         // Call the base class to actually initiate the DTO.
677         DCLGPS::DtoInitiate(id);
678 }
679
680 void KLN89::SetMinDisplayBrightness(int n) {
681         _minDisplayBrightness = n;
682         if(_minDisplayBrightness < 1) _minDisplayBrightness = 1;
683         if(_minDisplayBrightness > 9) _minDisplayBrightness = 9;
684 }
685
686 void KLN89::DecrementMinDisplayBrightness() {
687         _minDisplayBrightness--;
688         if(_minDisplayBrightness < 1) _minDisplayBrightness = 1;
689 }
690
691 void KLN89::IncrementMinDisplayBrightness() {
692         _minDisplayBrightness++;
693         if(_minDisplayBrightness > 9) _minDisplayBrightness = 9;
694 }
695
696 void KLN89::DrawBar(int page) {
697         int px = 1 + (page * 15);
698         int py = 1;
699         for(int i=0; i<7; ++i) {
700                 // Ugh - this is crude and inefficient!
701                 _instrument->DrawPixel(px+i, py);
702                 _instrument->DrawPixel(px+i, py+1);
703         }
704 }
705
706 // Convert moving map to instrument co-ordinates
707 void KLN89::MapToInstrument(int &x, int &y) {
708         x += _xBorder + _xFieldBorder[2] + _xFieldStart[2];
709 }
710
711 // Draw a pixel specified in instrument co-ords, but clipped to the map region
712 //void KLN89::DrawInstrMapPixel(int x, int y) {
713
714 /*
715 // Clip, translate and draw a map pixel
716 // If we didn't need per-pixel clipping, it would be cheaper to translate object rather than pixel positions.
717 void KLN89::DrawMapPixel(int x, int y, bool invert) {
718         if(x < 0 || x > 111 || y < 0 || y > 39)  return;
719         x += _xBorder + _xFieldBorder[2] + _xFieldStart[2];
720         _instrument->DrawPixel(x, y, invert);
721 }
722 */
723
724 // HACK - use something FG provides
725 static double gps_min(const double &a, const double &b) {
726         return(a <= b ? a : b);
727 }
728
729 static double gps_max(const double &a, const double &b) {
730         return(a >= b ? a : b);
731 }
732
733 void KLN89::UpdateMapHeading() {
734         switch(_mapOrientation) {
735         case 0:         // North up
736                 _mapHeading = 0.0;
737                 break;
738         case 1:         // DTK up
739                 _mapHeading = _dtkTrue;
740                 break;
741         case 2:         // Track up
742                 _mapHeading = _track;
743                 break;
744         }
745 }               
746
747 // The screen area allocated to the moving map is 111 x 40 pixels.
748 // In North up mode, the user position marker is at 57, 20. (Map co-ords).
749 void KLN89::DrawMap(bool draw_avs) {
750         // Set the clipping region to the moving map part of the display
751         int xstart = _xBorder + _xFieldBorder[2] + _xFieldStart[2];
752         _instrument->SetClipRegion(xstart, 0, xstart + 110, 39);
753         
754         _mapScaleUnits = (int)_distUnits;
755         _mapScale = (double)(KLN89MapScales[_mapScaleUnits][_mapScaleIndex]);
756         
757         //cout << "Map scale = " << _mapScale << '\n';
758         
759         double mapScaleMeters = _mapScale * (_mapScaleUnits == 0 ? SG_NM_TO_METER : 1000);
760         
761         // TODO - use an aligned projection when either DTK or TK up!
762         FGATCAlignedProjection mapProj(SGGeod::fromRad(_gpsLon, _gpsLat), _mapHeading);
763         double meter_per_pix = (_mapOrientation == 0 ? mapScaleMeters / 20.0f : mapScaleMeters / 29.0f);
764         SGGeod bottomLeft = mapProj.ConvertFromLocal(SGVec3d(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));
765         SGGeod topRight = mapProj.ConvertFromLocal(SGVec3d(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));
766
767
768
769         
770         // Draw Airport labels first (but not one's that are waypoints)
771         // Draw Airports first (but not one's that are waypoints)
772         // Ditto for VORs (not sure if SUA/VOR/Airport ordering is important or not).
773         // Ditto for SUA
774         // Then flighttrack
775         // Then waypoints
776         // Then waypoint labels (not sure if this should be before or after waypoints)
777         // Then user pos.
778         // Annotation then gets drawn by Nav page, NOT this function.
779
780         if(_drawApt && draw_avs) {
781                 /*
782                 bool have_apt = _overlays->FindArpByRegion(&apt, bottomLeft.lat(), bottomLeft.lon(), topRight.lat(), topRight.lon());
783                 //cout << "Vors enclosed are: ";
784                 // Draw all the labels first...
785                 for(unsigned int i=0; i<apt.size(); ++i) {
786                         //cout << nav[i]->id << ' ';
787                         Point3D p = mapProj.ConvertToLocal(Point3D(apt[i]->lon * SG_RADIANS_TO_DEGREES, apt[i]->lat * SG_RADIANS_TO_DEGREES, 0.0));
788                         //cout << p << " .... ";
789                         int mx = int(p.x() / meter_per_pix) + 56;
790                         int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
791                         //cout << "mx = " << mx << ", my = " << my << '\n';
792                         bool right_align = (p.x() < 0.0);
793                         DrawLabel(apt[i]->id, mx + (right_align ? -2 : 3), my + (p.y() < 0.0 ? -7 : 3), right_align);
794                         // I think that we probably should have -1 in the right_align case above to match the real life instrument.
795                 }
796                 // ...and then all the Apts.
797                 for(unsigned int i=0; i<apt.size(); ++i) {
798                         //cout << nav[i]->id << ' ';
799                         Point3D p = mapProj.ConvertToLocal(Point3D(apt[i]->lon * SG_RADIANS_TO_DEGREES, apt[i]->lat * SG_RADIANS_TO_DEGREES, 0.0));
800                         //cout << p << " .... ";
801                         int mx = int(p.x() / meter_per_pix) + 56;
802                         int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
803                         //cout << "mx = " << mx << ", my = " << my << '\n';
804                         DrawApt(mx, my);
805                 }
806                 //cout << '\n';
807                 */
808         }
809         /*
810         if(_drawVOR && draw_avs) {
811                 Overlays::nav_array_type nav;
812                 bool have_vor = _overlays->FindVorByRegion(&nav, bottomLeft.lat(), bottomLeft.lon(), topRight.lat(), topRight.lon());
813                 //cout << "Vors enclosed are: ";
814                 // Draw all the labels first...
815                 for(unsigned int i=0; i<nav.size(); ++i) {
816                         //cout << nav[i]->id << ' ';
817                         Point3D p = mapProj.ConvertToLocal(Point3D(nav[i]->lon * SG_RADIANS_TO_DEGREES, nav[i]->lat * SG_RADIANS_TO_DEGREES, 0.0));
818                         //cout << p << " .... ";
819                         int mx = int(p.x() / meter_per_pix) + 56;
820                         int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
821                         //cout << "mx = " << mx << ", my = " << my << '\n';
822                         bool right_align = (p.x() < 0.0);
823                         DrawLabel(nav[i]->id, mx + (right_align ? -2 : 3), my + (p.y() < 0.0 ? -7 : 3), right_align);
824                         // I think that we probably should have -1 in the right_align case above to match the real life instrument.
825                 }
826                 // ...and then all the VORs.
827                 for(unsigned int i=0; i<nav.size(); ++i) {
828                         //cout << nav[i]->id << ' ';
829                         Point3D p = mapProj.ConvertToLocal(Point3D(nav[i]->lon * SG_RADIANS_TO_DEGREES, nav[i]->lat * SG_RADIANS_TO_DEGREES, 0.0));
830                         //cout << p << " .... ";
831                         int mx = int(p.x() / meter_per_pix) + 56;
832                         int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
833                         //cout << "mx = " << mx << ", my = " << my << '\n';
834                         DrawVOR(mx, my);
835                 }
836                 //cout << '\n';
837         }
838         */
839         
840         // FlightTrack
841         if(_activeFP->waypoints.size() > 1) {
842                 vector<int> xvec, yvec, qvec;   // qvec stores the quadrant that each waypoint label should
843                                                                                 // be drawn in (relative to the waypoint). 
844                                                                                 // 1 = NE, 2 = SE, 3 = SW, 4 = NW.
845                 double save_h = 0.0; // Each pass, save a heading from the previous one for label quadrant determination.
846                 bool drawTrack = true;
847                 for(unsigned int i=1; i<_activeFP->waypoints.size(); ++i) {
848                         GPSWaypoint* wp0 = _activeFP->waypoints[i-1];
849                         GPSWaypoint* wp1 = _activeFP->waypoints[i];
850                         SGVec3d p0 = mapProj.ConvertToLocal(SGGeod::fromRad(wp0->lon, wp0->lat));
851                         SGVec3d p1 = mapProj.ConvertToLocal(SGGeod::fromRad(wp1->lon, wp1->lat));
852                         int mx0 = int(p0.x() / meter_per_pix + 0.5) + 56;
853                         int my0 = int(p0.y() / meter_per_pix + 0.5) + (_mapOrientation == 0 ? 19 : 10);
854                         int mx1 = int(p1.x() / meter_per_pix + 0.5) + 56;
855                         int my1 = int(p1.y() / meter_per_pix + 0.5) + (_mapOrientation == 0 ? 19 : 10);
856                         if(i == 1) {
857                                 xvec.push_back(mx0);
858                                 yvec.push_back(my0);
859                                 double h = GetGreatCircleCourse(wp0->lat, wp0->lon, wp1->lat, wp1->lon) * SG_RADIANS_TO_DEGREES;
860                                 // Adjust for map orientation
861                                 h -= _mapHeading;
862                                 qvec.push_back(GetLabelQuadrant(h));
863                                 //cout << "i = " << i << ", h = " << h << ", qvec[0] = " << qvec[0] << '\n';
864                         }
865                         xvec.push_back(mx1);
866                         yvec.push_back(my1);
867                         if(drawTrack) { DrawLine(mx0, my0, mx1, my1); }
868                         if(i != 1) {
869                                 double h = GetGreatCircleCourse(wp0->lat, wp0->lon, wp1->lat, wp1->lon) * SG_RADIANS_TO_DEGREES;
870                                 // Adjust for map orientation
871                                 h -= _mapHeading;
872                                 qvec.push_back(GetLabelQuadrant(save_h, h));
873                         }
874                         save_h = GetGreatCircleCourse(wp1->lat, wp1->lon, wp0->lat, wp0->lon) * SG_RADIANS_TO_DEGREES;
875                         // Adjust for map orientation
876                         save_h -= _mapHeading;
877                         if(i == _activeFP->waypoints.size() - 1) {
878                                 qvec.push_back(GetLabelQuadrant(save_h));
879                         }
880                         // Don't draw flight track beyond the missed approach point of an approach
881                         if(_approachLoaded) {
882                                 //cout << "Waypoints are " << wp0->id << " and " << wp1->id << '\n';
883                                 //cout << "Types are " << wp0->appType << " and " << wp1->appType << '\n';
884                                 if(wp1->appType == GPS_MAP) {
885                                         drawTrack = false;
886                                 }
887                         }
888                 }
889                 // ASSERT(xvec.size() == yvec.size() == qvec.size() == _activeFP->waypoints.size());
890                 for(unsigned int i=0; i<xvec.size(); ++i) {
891                         DrawWaypoint(xvec[i], yvec[i]);
892                         bool right_align = (qvec[i] > 2);
893                         bool top = (qvec[i] == 1 || qvec[i] == 4);
894                         // TODO - not sure if labels should be drawn in sequence with waypoints and flightpaths,
895                         // or all before or all afterwards.  Doesn't matter a huge deal though.
896                         DrawLabel(_activeFP->waypoints[i]->id, xvec[i] + (right_align ? -2 : 3), yvec[i] + (top ? 3 : -7), right_align);
897                 }
898         }
899         
900         // User pos
901         if(_mapOrientation == 0) {
902                 // North up
903                 DrawUser1(56, 19);
904         } else if(_mapOrientation == 1) {
905                 // DTK up
906                 DrawUser1(56, 10);
907         } else if(_mapOrientation == 2) {
908                 // TK up
909                 DrawUser2(56, 10);
910         } else {
911                 // Heading up
912                 // TODO - don't know what to do here!
913         }
914         
915         // And finally, reset the clip region to stop the rest of the code going pear-shaped!
916         _instrument->ResetClipRegion();
917 }
918
919 // Get the quadrant to draw the label of the start or end waypoint (i.e. one with only one track from it).
920 // Heading specified FROM the waypoint.
921 // 4 | 1
922 // -----
923 // 3 | 2
924 int KLN89::GetLabelQuadrant(double h) {
925         while(h < 0.0) h += 360.0;
926         while(h > 360.0) h -= 360.0;
927         if(h < 90.0) return(3);
928         if(h < 180.0) return(4);
929         if(h < 270.0) return(1);
930         return(2);
931 }
932
933 // Get the quadrant to draw the label of an en-route waypoint,
934 // with BOTH tracks specified as headings FROM the waypoint.
935 // 4 | 1
936 // -----
937 // 3 | 2
938 int KLN89::GetLabelQuadrant(double h1, double h2) {
939         while(h1 < 0.0) h1 += 360.0;
940         while(h1 > 360.0) h1 -= 360.0;
941         while(h2 < 0.0) h2 += 360.0;
942         while(h2 > 360.0) h2 -= 360.0;
943         double max_min_diff = 0.0;
944         int quad = 1;
945         for(int i=0; i<4; ++i) {
946                 double h = 45 + (90 * i);
947                 double diff1 = fabs(h - h1);
948                 if(diff1 > 180) diff1 = 360 - diff1;
949                 double diff2 = fabs(h - h2);
950                 if(diff2 > 180) diff2 = 360 - diff2;
951                 double min_diff = gps_min(diff1, diff2);
952                 if(min_diff > max_min_diff) {
953                         max_min_diff = min_diff;
954                         quad = i + 1;
955                 }
956         }
957         //cout << "GetLabelQuadrant, h1 = " << h1 << ", h2 = " << h2 << ", quad = " << quad << '\n';
958         return(quad);
959 }
960
961 // Draw the diamond style of user pos
962 // 
963 //    o
964 //   oxo
965 //  oxxxo
966 // oxxxxxo
967 //  oxxxo
968 //   oxo
969 //    o
970 // 
971 void KLN89::DrawUser1(int x, int y) {
972         MapToInstrument(x, y);
973         int min_j = 0, max_j = 0;
974         for(int i=-3; i<=3; ++i) {
975                 for(int j=min_j; j<=max_j; ++j) {
976                         _instrument->DrawPixel(x+j, y+i, (j == min_j || j == max_j ? true : false));
977                 }
978                 if(i < 0) {
979                         min_j--;
980                         max_j++;
981                 } else {
982                         min_j++;
983                         max_j--;
984                 }
985         }
986 }
987
988 // Draw the airplane style of user pos
989 // Define the origin to be the midpoint of the *fuselage*
990 void KLN89::DrawUser2(int x, int y) {
991         MapToInstrument(x, y);
992         
993         // Draw the background as three black quads first
994         _instrument->DrawQuad(x-2, y-3, x+2, y-1, true);
995         _instrument->DrawQuad(x-3, y, x+3, y+2, true);
996         _instrument->DrawQuad(x-1, y+3, x+1, y+3, true);
997         
998         if(_pixelated) {
999                 for(int j=y-2; j<=y+2; ++j) {
1000                         _instrument->DrawPixel(x, j);
1001                 }
1002                 for(int i=x-1; i<=x+1; ++i) {
1003                         _instrument->DrawPixel(i, y-2);
1004                 }
1005                 for(int i=x-2; i<=x+2; ++i) {
1006                         _instrument->DrawPixel(i, y+1);
1007                 }
1008         } else {
1009                 _instrument->DrawQuad(x, y-2, x, y+2);
1010                 _instrument->DrawQuad(x-1, y-2, x+1, y-2);
1011                 _instrument->DrawQuad(x-2, y+1, x+2, y+1);
1012         }
1013 }
1014
1015 // Draw an airport symbol on the moving map
1016 //
1017 //  ooo
1018 // ooxoo
1019 // oxxxo
1020 // ooxoo
1021 //  ooo
1022 //
1023 void KLN89::DrawApt(int x, int y) {
1024         MapToInstrument(x, y);
1025         
1026         int j = y-2;
1027         int i;
1028         for(i=x-1; i<=x+1; ++i) _instrument->DrawPixel(i, j, true);
1029         ++j;
1030         for(i=x-2; i<=x+2; ++i) _instrument->DrawPixel(i, j, (i != x ? true : false));
1031         ++j;
1032         for(i=x-2; i<=x+2; ++i) _instrument->DrawPixel(i, j, (abs(i - x) > 1 ? true : false));
1033         ++j;
1034         for(i=x-2; i<=x+2; ++i) _instrument->DrawPixel(i, j, (i != x ? true : false));
1035         ++j;
1036         for(i=x-1; i<=x+1; ++i) _instrument->DrawPixel(i, j, true);
1037 }
1038
1039 // Draw a waypoint on the moving map
1040 //
1041 // ooooo
1042 // oxxxo
1043 // oxxxo
1044 // oxxxo
1045 // ooooo
1046 //
1047 void KLN89::DrawWaypoint(int x, int y) {
1048         MapToInstrument(x, y);
1049         _instrument->SetDebugging(true);
1050         
1051         // Draw black background
1052         _instrument->DrawQuad(x-2, y-2, x+2, y+2, true);
1053         
1054         // Draw the coloured square
1055         if(_pixelated) {
1056                 for(int i=x-1; i<=x+1; ++i) {
1057                         for(int j=y-1; j<=y+1; ++j) {
1058                                 _instrument->DrawPixel(i, j);
1059                         }
1060                 }
1061         } else {
1062                 _instrument->DrawQuad(x-1, y-1, x+1, y+1);
1063         }
1064         _instrument->SetDebugging(false);
1065 }
1066
1067 // Draw a VOR on the moving map
1068 //
1069 // ooooo
1070 // oxxxo
1071 // oxoxo
1072 // oxxxo
1073 // ooooo
1074 //
1075 void KLN89::DrawVOR(int x, int y) {
1076         // Cheat - draw a waypoint and then a black pixel in the middle.
1077         // Need to call Waypoint draw *before* translating co-ords.
1078         DrawWaypoint(x, y);
1079         MapToInstrument(x, y);
1080         _instrument->DrawPixel(x, y, true);
1081 }
1082
1083 // Draw a line on the moving map
1084 void KLN89::DrawLine(int x1, int y1, int x2, int y2) {
1085         MapToInstrument(x1, y1);
1086         MapToInstrument(x2, y2);
1087         _instrument->DrawLine(x1, y1, x2, y2);
1088 }
1089
1090 void KLN89::DrawMapUpArrow(int x, int y) {
1091         MapToInstrument(x, y);
1092         if(_pixelated) {
1093                 for(int j=0; j<7; ++j) {
1094                         _instrument->DrawPixel(x + 2, y + j);
1095                 }
1096         } else {
1097                 _instrument->DrawQuad(x+2, y, x+2, y+6);
1098         }
1099         _instrument->DrawPixel(x, y+4);
1100         _instrument->DrawPixel(x+1, y+5);
1101         _instrument->DrawPixel(x+3, y+5);
1102         _instrument->DrawPixel(x+4, y+4);
1103 }
1104
1105 // Draw a quad on the moving map
1106 void KLN89::DrawMapQuad(int x1, int y1, int x2, int y2, bool invert) {
1107         MapToInstrument(x1, y1);
1108         MapToInstrument(x2, y2);
1109         _instrument->DrawQuad(x1, y1, x2, y2, invert);
1110 }
1111
1112 // Draw an airport or waypoint label on the moving map
1113 // Specify position by the map pixel co-ordinate of the left or right, bottom, of the *visible* portion of the label.
1114 // The black background quad will automatically overlap this by 1 pixel.
1115 void KLN89::DrawLabel(const string& s, int x1, int y1, bool right_align) {
1116         MapToInstrument(x1, y1);
1117         if(!right_align) {
1118                 for(unsigned int i=0; i<s.size(); ++i) {
1119                         char c = s[i];
1120                         x1 += DrawSmallChar(c, x1, y1);
1121                         x1 ++;
1122                 }
1123         } else {
1124                 for(int i=(int)(s.size()-1); i>=0; --i) {
1125                         char c = s[i];
1126                         x1 -= DrawSmallChar(c, x1, y1, right_align);
1127                         x1--;
1128                 }
1129         }
1130 }
1131
1132 void KLN89::DrawCDI() {
1133         // Scale
1134         for(int i=0; i<5; ++i) {
1135                 DrawSpecialChar(2, 2, 3+i, 2);
1136                 DrawSpecialChar(1, 2, 9+i, 2);
1137         }
1138         
1139         int field = 2;
1140         int px = 8 * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field] + 2;
1141         int py = 2 * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1142         
1143         // Deflection bar
1144         // Every 7 pixels deflection left or right is one dot on the scale, and hence 1/5 FSD.
1145         // Maximum deflection is 37 pixels left, or 38 pixels right !?!
1146         double xtd = CalcCrossTrackDeviation();
1147         int deflect;
1148         if(_cdiScaleTransition) {
1149                 double dots = (xtd / _currentCdiScale) * 5.0;
1150                 deflect = (int)(dots * 7.0 * -1.0);
1151                 // TODO - for all these I think I should add 0.5 before casting to int, and *then* multiply by -1.  Possibly!
1152         } else {
1153                 if(0 == _currentCdiScaleIndex) {        // 5.0nm FSD => 1 nm per dot => 7 pixels per nm.
1154                         deflect = (int)(xtd * 7.0 * -1.0);      // The -1.0 is because we move the 'needle' indicating the course, not the plane.
1155                 } else if(1 == _currentCdiScaleIndex) {
1156                         deflect = (int)(xtd * 35.0 * -1.0);
1157                 } else {        // 0.3 == _cdiScale
1158                         deflect = (int)(xtd * 116.6666666667 * -1.0);
1159                 }
1160         }
1161         if(deflect > 38) deflect = 38;
1162         if(deflect < -37) deflect = -37;
1163         if(_pixelated) {
1164                 for(int j=0; j<9; ++j) {
1165                         _instrument->DrawPixel(px + deflect, py+j);
1166                         _instrument->DrawPixel(px + deflect + 1, py+j);
1167                 }
1168         } else {
1169                 _instrument->DrawQuad(px + deflect, py, px + deflect + 1, py + 8);
1170         }
1171         
1172         // To/From indicator
1173         px-=4;
1174         py+=2;
1175         for(int j=4; j>=0; --j) {
1176                 int k = 10 - (2*j);
1177                 for(int i=0; i<k; ++i) {                
1178                         _instrument->DrawPixel(px+j+i, (_headingBugTo ? py+j : py+4-j));
1179                         // At the extremities, draw the outlining dark pixel
1180                         if(i == 0 || i == k-1) {
1181                                 _instrument->DrawPixel(px+j+i, (_headingBugTo ? py+j+1 : py+3-j), true);
1182                         }
1183                 }
1184         }
1185 }
1186
1187 void KLN89::DrawLegTail(int py) {
1188         int px = 0 * 7 + _xBorder + _xFieldBorder[2] + _xFieldStart[2];
1189         py = py * 9 + _yBorder + _yFieldBorder[2] + _yFieldStart[2];
1190         
1191         px++;
1192         py+=3;
1193         py++;   // Hack - not sure if this represents a border issue.
1194         
1195         for(int i=0; i<9; ++i) _instrument->DrawPixel(px, py+i);
1196         for(int i2=0; i2<5; ++i2) _instrument->DrawPixel(px+i2, py+9);
1197 }
1198
1199 void KLN89::DrawLongLegTail(int py) {
1200         int px = 0 * 7 + _xBorder + _xFieldBorder[2] + _xFieldStart[2];
1201         py = py * 9 + _yBorder + _yFieldBorder[2] + _yFieldStart[2];
1202         
1203         px++;
1204         py+=3;
1205         py++;   // Hack - not sure if this represents a border issue.
1206         
1207         for(int i=0; i<18; ++i) _instrument->DrawPixel(px, py+i);
1208         for(int i2=0; i2<5; ++i2) _instrument->DrawPixel(px+i2, py+18);
1209 }
1210
1211 void KLN89::DrawHalfLegTail(int py) {
1212 }
1213
1214 void KLN89::DrawDivider() {
1215         int px = _xFieldStart[2] - 1;
1216         int py = _yBorder;
1217         for(int i=0; i<36; ++i) {
1218                 _instrument->DrawPixel(px, py+i);
1219         }
1220 }
1221
1222 void KLN89::DrawEnt(int field, int px, int py) {
1223         px = px * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1224         py = py * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field] + 1;
1225         
1226         px++;   // Not sure why we need px++, but it seems to work!
1227         py++;
1228         
1229         // E
1230         for(int i=0; i<5; ++i) _instrument->DrawPixel(px, py+i);
1231         _instrument->DrawPixel(px+1, py);
1232         _instrument->DrawPixel(px+2, py);
1233         _instrument->DrawPixel(px+1, py+2);
1234         _instrument->DrawPixel(px+1, py+4);
1235         _instrument->DrawPixel(px+2, py+4);
1236         
1237         px += 4;
1238         // N
1239         for(int i=0; i<4; ++i) _instrument->DrawPixel(px, py+i);
1240         _instrument->DrawPixel(px+1, py+2);
1241         _instrument->DrawPixel(px+2, py+1);
1242         for(int i=0; i<4; ++i) _instrument->DrawPixel(px+3, py+i);
1243         
1244         px += 5;
1245         // T
1246         _instrument->DrawPixel(px, py+3);
1247         for(int i=0; i<4; ++i) _instrument->DrawPixel(px+1, py+i);
1248         _instrument->DrawPixel(px+2, py+3);
1249 }
1250
1251 void KLN89::DrawMessageAlert() {
1252         // TODO - draw the proper message indicator
1253         if(!_blink) {
1254                 int px = _xBorder + _xFieldBorder[1] + _xFieldStart[1];
1255                 int py = 1 * 9 + _yBorder + _yFieldBorder[1] + _yFieldStart[1] + 1;
1256                 
1257                 px++;   // Not sure why we need px++, but it seems to work!
1258                 py++;
1259
1260                 DrawText("  ", 1, 0, 1, false, 99);
1261                 _instrument->DrawQuad(px+1, py-1, px+2, py+5, true);
1262                 _instrument->DrawQuad(px+3, py+3, px+3, py+5, true);
1263                 _instrument->DrawQuad(px+4, py+2, px+4, py+4, true);
1264                 _instrument->DrawQuad(px+5, py+1, px+6, py+3, true);
1265                 _instrument->DrawQuad(px+7, py+2, px+7, py+4, true);
1266                 _instrument->DrawQuad(px+8, py+3, px+8, py+5, true);
1267                 _instrument->DrawQuad(px+9, py-1, px+10, py+5, true);
1268         }
1269 }
1270
1271 void KLN89::Underline(int field, int px, int py, int len) {
1272         px = px * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1273         py = py * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1274         for(int i=0; i<(len*7); ++i) {
1275                 _instrument->DrawPixel(px, py);
1276                 ++px;
1277         }
1278 }
1279
1280 void KLN89::DrawKPH(int field, int cx, int cy) {
1281         // Add some border
1282         int px = cx * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1283         int py = cy * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1284         
1285         px++;
1286         py++;
1287         
1288         for(int j=0; j<=4; ++j) {
1289                 _instrument->DrawPixel(px, py + 2 +j);
1290                 _instrument->DrawPixel(px + 8, py + j);
1291                 if(j <= 1) {
1292                         _instrument->DrawPixel(px + 11, py + j);
1293                         _instrument->DrawPixel(px + 9 + j, py + 2);
1294                 }
1295         }
1296         
1297         for(int i=0; i<=6; ++i) {
1298                 if(i <= 2) {
1299                         _instrument->DrawPixel(px + 1 + i, py + 4 + i);
1300                         _instrument->DrawPixel(px + 1 + i, py + (4 - i));
1301                 }
1302                 _instrument->DrawPixel(px + 2 + i, py + i);
1303         }
1304 }
1305
1306 void KLN89::DrawDTO(int field, int cx, int cy) {
1307         DrawSpecialChar(6, field, cx, cy);
1308         if(!(_waypointAlert && _blink)) {
1309                 DrawSpecialChar(3, field, cx+1, cy);
1310         }
1311         
1312         int px = cx * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1313         int py = cy * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1314         
1315         px++;
1316         py++;
1317         
1318         // Fill in the gap between the 'D' and the arrow.
1319         _instrument->DrawPixel(px+5, py+3);
1320 }
1321
1322 // Takes character position
1323 void KLN89::DrawChar(char c, int field, int px, int py, bool bold, bool invert) {
1324         // Ignore field for now
1325         // Add some border
1326         px = px * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1327         py = py * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1328         
1329         // Draw an orange background for inverted characters
1330         if(invert) {
1331                 for(int i=0; i<7; ++i) {
1332                         for(int j=0; j<9; ++j) {
1333                                 _instrument->DrawPixel(px + i, py + j);
1334                         }
1335                 }
1336         }
1337                                 
1338         if(c < 33) return;  // space
1339         
1340         // Render normal decimal points in bold floats
1341         if(c == '.') bold = false;
1342         
1343         ++py;   // Shift the char up by one pixel
1344         for(int j=7; j>=0; --j) {
1345                 char c1 = (bold ? NumbersBold[c-48][j] : UpperAlpha[c-33][j]);
1346                 // Don't do the last column for now (ie. j = 1, not 0)
1347                 for(int i=5; i>=0; --i) {
1348                         if(c1 & (01 << i)) {
1349                                 _instrument->DrawPixel(px, py, invert);
1350                         }
1351                         ++px;
1352                 }
1353                 px -= 6;
1354                 ++py;
1355         }
1356 }
1357
1358 // Takes pixel position
1359 void KLN89::DrawFreeChar(char c, int x, int y, bool draw_background) {
1360         
1361         if(draw_background) {
1362                 _instrument->DrawQuad(x, y, x+6, y+8, true);
1363         }               
1364                                 
1365         if(c < 33) return;  // space
1366         
1367         ++y;    // Shift the char up by one pixel
1368         for(int j=7; j>=0; --j) {
1369                 char c1 = UpperAlpha[c-33][j];
1370                 // Don't do the last column for now (ie. j = 1, not 0)
1371                 for(int i=5; i>=0; --i) {
1372                         if(c1 & (01 << i)) {
1373                                 _instrument->DrawPixel(x, y);
1374                         }
1375                         ++x;
1376                 }
1377                 x -= 6;
1378                 ++y;
1379         }
1380 }
1381
1382 // Takes instrument pixel co-ordinates.
1383 // Position is specified by the bottom of the *visible* portion, by default the left position unless align_right is true.
1384 // The return value is the pixel width of the visible portion
1385 int KLN89::DrawSmallChar(char c, int x, int y, bool align_right) {
1386         // calculate the index into the SmallChar array
1387         int idx;
1388         if(c > 47 && c < 58) {
1389                 // number
1390                 idx = c - 48;
1391         } else if(c > 64 && c < 91) {
1392                 // Uppercase letter
1393                 idx = c - 55;
1394         } else {
1395                 return(0);
1396         }
1397         
1398         char n = SmallChar[idx][0];             // Width of visible portion
1399         if(align_right) x -= n;
1400         
1401         // background
1402         _instrument->DrawQuad(x - 1, y - 1, x + n, y + 5, true);
1403         
1404         for(int j=7; j>=3; --j) {
1405                 char c1 = SmallChar[idx][j];
1406                 for(int i=n-1; i>=0; --i) {
1407                         if(c1 & (01 << i)) {
1408                                 _instrument->DrawPixel(x, y);
1409                         }
1410                         ++x;
1411                 }
1412                 x -= n;
1413                 ++y;
1414         }
1415         
1416         return(n);
1417 }
1418
1419 // Takes character position
1420 void KLN89::DrawSpecialChar(char c, int field, int cx, int cy, bool bold) {
1421         if(c > 7) {
1422                 cout << "ERROR - requested special char outside array bounds!\n";
1423                 return;  // Increment this as SpecialChar grows
1424         }
1425         
1426         // Convert character to pixel position.
1427         // Ignore field for now
1428         // Add some border
1429         int px = cx * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1430         int py = cy * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1431         ++py;   // Total hack - the special chars were coming out 1 pixel too low!
1432         for(int i=7; i>=0; --i) {
1433                 char c1 = SpecialChar[(int)c][i];
1434                 // Don't do the last column for now (ie. j = 1, not 0)
1435                 for(int j=5; j>=0; --j) {
1436                         if(c1 & (01 << j)) {
1437                                 _instrument->DrawPixel(px, py);
1438                         }
1439                         ++px;
1440                 }
1441                 px -= 6;
1442                 ++py;
1443         }
1444 }
1445
1446 void KLN89::DrawText(const string& s, int field, int px, int py, bool bold, int invert) {
1447         for(int i = 0; i < (int)s.size(); ++i) {
1448                 DrawChar(s[(unsigned int)i], field, px+i, py, bold, (invert == i || invert == 99));
1449         }
1450 }
1451
1452 void KLN89::DrawMapText(const string& s, int x, int y, bool draw_background) {
1453         MapToInstrument(x, y);
1454         if(draw_background) {
1455                 //_instrument->DrawQuad(x, y, x + (7 * s.size()) - 1, y + 8, true);
1456                 _instrument->DrawQuad(x - 1, y, x + (7 * s.size()) - 2, y + 8, true);
1457                 // 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!
1458         }
1459         
1460         for(int i = 0; i < (int)s.size(); ++i) {
1461                 DrawFreeChar(s[(unsigned int)i], x+(i * 7)-1, y);
1462         }
1463 }
1464
1465 void KLN89::DrawLatitude(double d, int field, int px, int py) {
1466         DrawChar((d >= 0 ? 'N' : 'S'), field, px, py);
1467         d = fabs(d);
1468         px += 1;
1469         // TODO - sanity check input to ensure major lat field can only ever by 2 chars wide
1470         char buf[8];
1471         // Don't know whether to zero pad the below for single digits or not?
1472         //cout << d << ", " << (int)d << '\n';
1473         // 3 not 2 in size before for trailing \0
1474         int n = snprintf(buf, 3, "%i", (int)d);
1475         string s = buf;
1476         //cout << s << "... " << n << '\n';
1477         DrawText(s, field, px+(3-n), py);
1478         n = snprintf(buf, 7, "%05.2f'", ((double)(d - (int)d)) * 60.0f);
1479         s = buf;
1480         px += 3;
1481         DrawSpecialChar(0, field, px, py);      // Degrees symbol
1482         px++;
1483         DrawText(s, field, px, py);
1484 }
1485
1486 void KLN89::DrawLongitude(double d, int field, int px, int py) {
1487         DrawChar((d >= 0 ? 'E' : 'W'), field, px, py);
1488         d = fabs(d);
1489         px += 1;
1490         // TODO - sanity check input to ensure major lat field can only ever be 2 chars wide
1491         char buf[8];
1492         // Don't know whether to zero pad the below for single digits or not?
1493         //cout << d << ", " << (int)d << '\n';
1494         // 4 not 3 in size before for trailing \0
1495         int n = snprintf(buf, 4, "%i", (int)d);
1496         string s = buf;
1497         //cout << s << "... " << n << '\n';
1498         DrawText(s, field, px+(3-n), py);
1499         n = snprintf(buf, 7, "%05.2f'", ((double)(d - (int)d)) * 60.0f);
1500         s = buf;
1501         px += 3;
1502         DrawSpecialChar(0, field, px, py);      // Degrees symbol
1503         px++;
1504         DrawText(s, field, px, py);
1505 }
1506
1507 void KLN89::DrawFreq(double d, int field, int px, int py) {
1508         if(d >= 1000) d /= 100.0f;
1509         char buf[8];
1510         snprintf(buf, 7, "%6.2f", d);
1511         string s = buf;
1512         DrawText(s, field, px, py);
1513 }
1514
1515 void KLN89::DrawTime(double time, int field, int px, int py) {
1516         int hrs = (int)(time / 3600);
1517         int mins = (int)(ceil((time - (hrs * 3600)) / 60.0));
1518         char buf[10];
1519         int n;
1520         if(time >= 60.0) {
1521                 // Draw hr:min
1522                 n = snprintf(buf, 9, "%i:%02i", hrs, mins);
1523         } else {
1524                 // Draw :secs
1525                 n = snprintf(buf, 4, ":%02i", (int)time);
1526         }
1527         string s = buf;
1528         DrawText(s, field, px - n + 1, py);
1529 }
1530
1531 void KLN89::DrawHeading(int h, int field, int px, int py) {
1532         char buf[4];
1533         snprintf(buf, 4, "%i", h);
1534         string s = buf;
1535         DrawText(s, field, px - s.size(), py);
1536         DrawSpecialChar(0, field, px, py);      // Degrees symbol
1537 }
1538
1539 void KLN89::DrawDist(double d, int field, int px, int py) {
1540         d *= (_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001);
1541         char buf[10];
1542         snprintf(buf, 9, "%i", (int)(d + 0.5));
1543         string s = buf;
1544         s += (_distUnits == GPS_DIST_UNITS_NM ? "nm" : "Km");
1545         DrawText(s, field, px - s.size() + 1, py);
1546 }
1547
1548 void KLN89::DrawSpeed(double v, int field, int px, int py, int decimal) {
1549         // TODO - implement variable decimal places
1550         v *= (_velUnits == GPS_VEL_UNITS_KT ? 1.0 : 0.51444444444 * 0.001 * 3600.0);
1551         char buf[10];
1552         snprintf(buf, 9, "%i", (int)(v + 0.5));
1553         string s = buf;
1554         if(_velUnits == GPS_VEL_UNITS_KT) {
1555                 s += "kt";
1556                 DrawText(s, field, px - s.size() + 1, py);
1557         } else {
1558                 DrawText(s, field, px - s.size() - 1, py);
1559                 DrawKPH(field, px - 1, py);
1560         }
1561 }
1562
1563 void KLN89::DrawDirDistField(double lat, double lon, int field, int px, int py, bool to_flag, bool cursel) {
1564         DrawChar('>', field, px, py);
1565         char buf[8];
1566         double h;
1567         if(to_flag) {
1568                 h = GetMagHeadingFromTo(_gpsLat, _gpsLon, lat, lon);
1569         } else {
1570                 h = GetMagHeadingFromTo(lat, lon, _gpsLat, _gpsLon);
1571         }
1572         while(h < 0.0) h += 360.0;
1573         while(h > 360.0) h -= 360.0;
1574         snprintf(buf, 4, "%3i", (int)(h + 0.5));
1575         string s = buf;
1576         if(!(cursel && _blink)) { 
1577                 DrawText(s, field, px + 4 - s.size(), py);
1578                 DrawSpecialChar(0, field, px+4, py);
1579                 DrawText((to_flag ? "To" : "Fr"), field, px+5, py);
1580                 if(cursel) Underline(field, px + 1, py, 6);
1581         }
1582         //double d = GetHorizontalSeparation(_gpsLat, _gpsLon, lat, lon);
1583         //d *= (_distUnits == GPS_DIST_UNITS_NM ? SG_METER_TO_NM : 0.001);
1584         double d = GetGreatCircleDistance(_gpsLat, _gpsLon, lat, lon);
1585         d *= (_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001);
1586         if(d >= 100.0) {
1587                 snprintf(buf, 7, "%5i", (int)(d + 0.5));
1588         } else {
1589                 snprintf(buf, 7, "%4.1f", d);
1590         }
1591         s = buf;
1592         DrawText(s, field, px + 12 - s.size(), py);
1593         DrawText((_distUnits == GPS_DIST_UNITS_NM ? "nm" : "Km"), field, px + 12, py);
1594 }
1595
1596 char KLN89::IncChar(char c, bool gap, bool wrap) {
1597         if(c == '9') return(wrap ? (gap ? ' ' : 'A') : '9');
1598         if(c == 'Z') return('0');
1599         if(c == ' ') return('A');
1600         return(c + 1);
1601 }
1602
1603 char KLN89::DecChar(char c, bool gap, bool wrap) {
1604         if(c == 'A') return(wrap ? (gap ? ' ' : '9') : 'A');
1605         if(c == '0') return('Z');
1606         if(c == ' ') return('9');
1607         return(c - 1);
1608 }