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