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