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