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