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