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