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