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.
8 // Written by David Luff, started 2005.
10 // Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
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.
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.
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.
29 #include "kln89_page.hxx"
30 #include "kln89_page_apt.hxx"
31 #include "kln89_page_vor.hxx"
32 #include "kln89_page_ndb.hxx"
33 #include "kln89_page_int.hxx"
34 #include "kln89_page_usr.hxx"
35 #include "kln89_page_act.hxx"
36 #include "kln89_page_nav.hxx"
37 #include "kln89_page_fpl.hxx"
38 #include "kln89_page_cal.hxx"
39 #include "kln89_page_set.hxx"
40 #include "kln89_page_oth.hxx"
41 #include "kln89_page_alt.hxx"
42 #include "kln89_page_dir.hxx"
43 #include "kln89_page_nrst.hxx"
44 #include "kln89_symbols.hxx"
48 #include <ATCDCL/ATCProjection.hxx>
50 #include <ATC/atcutils.hxx>
53 #include <Main/fg_props.hxx>
54 #include <simgear/math/SGMath.hxx>
55 #include <simgear/structure/commands.hxx>
56 #include <Airports/simple.hxx>
60 // Command callbacks for FlightGear
62 static bool do_kln89_msg_pressed(const SGPropertyNode* arg) {
63 //cout << "do_kln89_msg_pressed called!\n";
64 KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
69 static bool do_kln89_obs_pressed(const SGPropertyNode* arg) {
70 //cout << "do_kln89_obs_pressed called!\n";
71 KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
76 static bool do_kln89_alt_pressed(const SGPropertyNode* arg) {
77 //cout << "do_kln89_alt_pressed called!\n";
78 KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
83 static bool do_kln89_nrst_pressed(const SGPropertyNode* arg) {
84 KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
89 static bool do_kln89_dto_pressed(const SGPropertyNode* arg) {
90 KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
95 static bool do_kln89_clr_pressed(const SGPropertyNode* arg) {
96 KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
101 static bool do_kln89_ent_pressed(const SGPropertyNode* arg) {
102 KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
107 static bool do_kln89_crsr_pressed(const SGPropertyNode* arg) {
108 KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
113 static bool do_kln89_knob1left1(const SGPropertyNode* arg) {
114 KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
119 static bool do_kln89_knob1right1(const SGPropertyNode* arg) {
120 KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
125 static bool do_kln89_knob2left1(const SGPropertyNode* arg) {
126 KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
131 static bool do_kln89_knob2right1(const SGPropertyNode* arg) {
132 KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
137 // End command callbacks
139 KLN89::KLN89(RenderArea2D* instrument)
140 : DCLGPS(instrument) {
141 _mode = KLN89_MODE_DISP;
148 // ..Field..[0] => no fields in action
149 _xFieldBorder[0] = 0;
150 _xFieldBorder[1] = 0;
151 _yFieldBorder[0] = 0;
152 _yFieldBorder[1] = 0;
153 _xFieldBorder[2] = 2;
154 _yFieldBorder[2] = 0;
157 _xFieldStart[2] = 45;
167 KLN89Page* apt_page = new KLN89AptPage(this);
168 _pages.push_back(apt_page);
169 KLN89Page* vor_page = new KLN89VorPage(this);
170 _pages.push_back(vor_page);
171 KLN89Page* ndb_page = new KLN89NDBPage(this);
172 _pages.push_back(ndb_page);
173 KLN89Page* int_page = new KLN89IntPage(this);
174 _pages.push_back(int_page);
175 KLN89Page* usr_page = new KLN89UsrPage(this);
176 _pages.push_back(usr_page);
177 KLN89Page* act_page = new KLN89ActPage(this);
178 _pages.push_back(act_page);
179 KLN89Page* nav_page = new KLN89NavPage(this);
180 _pages.push_back(nav_page);
181 KLN89Page* fpl_page = new KLN89FplPage(this);
182 _pages.push_back(fpl_page);
183 KLN89Page* cal_page = new KLN89CalPage(this);
184 _pages.push_back(cal_page);
185 KLN89Page* set_page = new KLN89SetPage(this);
186 _pages.push_back(set_page);
187 KLN89Page* oth_page = new KLN89OthPage(this);
188 _pages.push_back(oth_page);
189 _nPages = _pages.size();
193 _alt_page = new KLN89AltPage(this);
194 _dir_page = new KLN89DirPage(this);
195 _nrst_page = new KLN89NrstPage(this);
197 _activePage = apt_page;
203 // User-settable configuration. Eventually this should be user-achivable in order that settings can be persistent between sessions.
204 _altUnits = GPS_ALT_UNITS_FT;
205 _baroUnits = GPS_PRES_UNITS_IN;
206 _velUnits = GPS_VEL_UNITS_KT;
207 _distUnits = GPS_DIST_UNITS_NM;
208 _suaAlertEnabled = false;
209 _altAlertEnabled = false;
210 _minDisplayBrightness = 4;
211 _defaultFirstChar = 'A';
213 if(_baroUnits == GPS_PRES_UNITS_IN) {
214 _userBaroSetting = 2992;
216 _userBaroSetting = 1013;
219 _maxFlightPlans = 26;
220 for(unsigned int i=0; i<_maxFlightPlans; ++i) {
221 GPSFlightPlan* fp = new GPSFlightPlan;
222 fp->waypoints.clear();
223 _flightPlans.push_back(fp);
225 _activeFP = _flightPlans[0];
227 _entJump = _clrJump = -1;
228 _jumpRestoreCrsr = false;
237 _mapHeadingUpdateTimer = 0.0;
241 //_mapScaleIndex = 20;
242 _mapScaleIndex = 7; // I think that the above is more accurate for no-flightplan default, but this is more sane for initial testing!
243 _mapScaleAuto = true;
245 // 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
246 // TODO - do this better one day!
247 _airportTowns["KSFO"] = "San Francisco";
248 _airportTowns["KSQL"] = "San Carlos";
249 _airportTowns["KPAO"] = "Palo Alto";
250 _airportTowns["KNUQ"] = "Mountain View";
251 _airportTowns["KSJC"] = "San Jose";
252 _airportTowns["KRHV"] = "San Jose";
253 _airportTowns["E16"] = "San Martin";
254 _airportTowns["KWVI"] = "Watsonville";
255 _airportTowns["KOAK"] = "Oakland";
256 _airportTowns["KHWD"] = "Hayward";
257 _airportTowns["KLVK"] = "Livermore";
258 _airportTowns["KCCR"] = "Concord";
259 _airportTowns["KTCY"] = "Tracy";
260 _airportTowns["KSCK"] = "Stockton";
261 _airportTowns["KHAF"] = "Half Moon Bay";
263 _airportStates["KSFO"] = "CA";
264 _airportStates["KSQL"] = "CA";
265 _airportStates["KPAO"] = "CA";
266 _airportStates["KNUQ"] = "CA";
267 _airportStates["KSJC"] = "CA";
268 _airportStates["KRHV"] = "CA";
269 _airportStates["E16"] = "CA";
270 _airportStates["KWVI"] = "CA";
271 _airportStates["KOAK"] = "CA";
272 _airportStates["KHWD"] = "CA";
273 _airportStates["KLVK"] = "CA";
274 _airportStates["KCCR"] = "CA";
275 _airportStates["KTCY"] = "CA";
276 _airportStates["KSCK"] = "CA";
277 _airportStates["KHAF"] = "CA";
281 for(unsigned int i=0; i<_pages.size(); ++i) {
289 for(unsigned int i=0; i<_maxFlightPlans; ++i) {
291 delete _flightPlans[i];
296 fgTie("/instrumentation/gps/message-alert", this, &KLN89::GetMsgAlert);
300 void KLN89::unbind() {
301 fgUntie("/instrumentation/gps/message-alert");
306 globals->get_commands()->addCommand("kln89_msg_pressed", do_kln89_msg_pressed);
307 globals->get_commands()->addCommand("kln89_obs_pressed", do_kln89_obs_pressed);
308 globals->get_commands()->addCommand("kln89_alt_pressed", do_kln89_alt_pressed);
309 globals->get_commands()->addCommand("kln89_nrst_pressed", do_kln89_nrst_pressed);
310 globals->get_commands()->addCommand("kln89_dto_pressed", do_kln89_dto_pressed);
311 globals->get_commands()->addCommand("kln89_clr_pressed", do_kln89_clr_pressed);
312 globals->get_commands()->addCommand("kln89_ent_pressed", do_kln89_ent_pressed);
313 globals->get_commands()->addCommand("kln89_crsr_pressed", do_kln89_crsr_pressed);
314 globals->get_commands()->addCommand("kln89_knob1left1", do_kln89_knob1left1);
315 globals->get_commands()->addCommand("kln89_knob1right1", do_kln89_knob1right1);
316 globals->get_commands()->addCommand("kln89_knob2left1", do_kln89_knob2left1);
317 globals->get_commands()->addCommand("kln89_knob2right1", do_kln89_knob2right1);
322 void KLN89::update(double dt) {
323 // Run any positional calc's required first
326 // Set the display brightness. This should be reduced in response to falling light
327 // (i.e. nighttime), or the user covering the photocell that detects the light level.
328 // At the moment I don't know how to detect nighttime or actual light level, so only
329 // respond to the photocell being obscured.
330 // TODO - reduce the brightness in response to nighttime / lowlight.
331 float rgba[4] = {1.0, 0.0, 0.0, 1.0};
332 if(fgGetBool("/instrumentation/kln89/photocell-obscured")) {
333 rgba[0] -= (9 - _minDisplayBrightness) * 0.05;
335 _instrument->SetPixelColor(rgba);
350 _mapHeadingUpdateTimer += dt;
351 if(_mapHeadingUpdateTimer > 1.0) {
353 _mapHeadingUpdateTimer = 0.0;
356 _instrument->Flush();
357 _instrument->DrawBackground();
360 if(_messageStack.empty()) {
361 DrawText("No Message", 0, 5, 2);
363 // TODO - parse the message string for special strings that indicate degrees signs etc!
364 DrawText(*_messageStack.begin(), 0, 0, 3);
368 if(!_messageStack.empty()) {
373 // Draw the indicator that shows which page we are on.
374 if(_curPage == 6 && _activePage->GetSubPage() == 3) {
375 // Don't draw the bar on the nav-4 page
376 } else if((_activePage != _nrst_page) && (_activePage != _dir_page) && (_activePage != _alt_page) && (!_dispMsg)) {
377 // Don't draw the bar on the NRST, DTO or MSG pages
381 _activePage->Update(dt);
384 void KLN89::CreateDefaultFlightPlans() {
385 // TODO - read these in from preferences.xml or similar instead!!!!
386 // Create some hardwired default flightplans for testing.
388 vector<GPSWpType> wps;
392 ids.push_back("KLSN");
393 wps.push_back(GPS_WP_APT);
394 ids.push_back("VOLTA");
395 wps.push_back(GPS_WP_INT);
396 ids.push_back("C83");
397 wps.push_back(GPS_WP_APT);
398 CreateFlightPlan(_flightPlans[5], ids, wps);
402 ids.push_back("KCCR");
403 wps.push_back(GPS_WP_APT);
404 ids.push_back("KHAF");
405 wps.push_back(GPS_WP_APT);
406 CreateFlightPlan(_flightPlans[4], ids, wps);
410 ids.push_back("KLVK");
411 wps.push_back(GPS_WP_APT);
412 ids.push_back("OAK");
413 wps.push_back(GPS_WP_VOR);
414 ids.push_back("PORTE");
415 wps.push_back(GPS_WP_INT);
416 ids.push_back("KHAF");
417 wps.push_back(GPS_WP_APT);
418 CreateFlightPlan(_flightPlans[3], ids, wps);
422 ids.push_back("KDPA");
423 wps.push_back(GPS_WP_APT);
424 ids.push_back("OBK");
425 wps.push_back(GPS_WP_VOR);
426 ids.push_back("ENW");
427 wps.push_back(GPS_WP_VOR);
428 ids.push_back("KRAC");
429 wps.push_back(GPS_WP_APT);
430 CreateFlightPlan(_flightPlans[2], ids, wps);
431 //cout << "Size of FP2 WP list is " << _flightPlans[2]->waypoints.size() << '\n';
435 ids.push_back("KSFO");
436 ids.push_back("KOAK");
437 wps.push_back(GPS_WP_APT);
438 wps.push_back(GPS_WP_APT);
439 CreateFlightPlan(_flightPlans[1], ids, wps);
443 //ids.push_back("KOSH");
444 ids.push_back("KSFO");
445 ids.push_back("KHAF");
446 ids.push_back("OSI");
447 ids.push_back("KSQL");
448 //ids.push_back("KPAO");
449 //ids.push_back("KHWD");
450 wps.push_back(GPS_WP_APT);
451 wps.push_back(GPS_WP_APT);
452 wps.push_back(GPS_WP_VOR);
453 wps.push_back(GPS_WP_APT);
454 //wps.push_back(GPS_WP_APT);
455 //wps.push_back(GPS_WP_APT);
456 CreateFlightPlan(_flightPlans[0], ids, wps);
461 ids.push_back("KLVK");
462 ids.push_back("KHWD");
463 wps.push_back(GPS_WP_APT);
464 wps.push_back(GPS_WP_APT);
465 CreateFlightPlan(_flightPlans[0], ids, wps);
469 void KLN89::SetBaroUnits(int n, bool wrap) {
471 _baroUnits = (KLN89PressureUnits)(wrap ? 3 : 1);
473 _baroUnits = (KLN89PressureUnits)(wrap ? 1 : 3);
475 _baroUnits = (KLN89PressureUnits)n;
479 void KLN89::Knob1Right1() {
480 if(_mode == KLN89_MODE_DISP) {
481 _activePage->LooseFocus();
482 if(_cleanUpPage >= 0) {
483 _pages[(unsigned int)_cleanUpPage]->CleanUp();
487 if(_curPage >= _pages.size()) _curPage = 0;
488 _activePage = _pages[_curPage];
490 _activePage->Knob1Right1();
495 void KLN89::Knob1Left1() {
496 if(_mode == KLN89_MODE_DISP) {
497 _activePage->LooseFocus();
498 if(_cleanUpPage >= 0) {
499 _pages[(unsigned int)_cleanUpPage]->CleanUp();
503 _curPage = _pages.size() - 1;
507 _activePage = _pages[_curPage];
509 _activePage->Knob1Left1();
514 void KLN89::Knob2Left1() {
515 _activePage->Knob2Left1();
518 void KLN89::Knob2Right1() {
519 _activePage->Knob2Right1();
522 void KLN89::CrsrPressed() {
524 // CRSR cannot be switched off on nrst page.
525 if(_activePage == _nrst_page) { return; }
526 // CRSR is always off when inner-knob is out on nav4 page.
527 if(_curPage == 6 && _activePage->GetSubPage() == 3 && fgGetBool("/instrumentation/kln89/scan-pull")) { return; }
528 if(_cleanUpPage >= 0) {
529 _pages[(unsigned int)_cleanUpPage]->CleanUp();
532 _jumpRestoreCrsr = false;
533 _entJump = _clrJump = -1;
534 ((KLN89Page*)_activePage)->SetEntInvert(false);
535 if(_mode == KLN89_MODE_DISP) {
536 _mode = KLN89_MODE_CRSR;
537 _activePage->CrsrPressed();
539 _mode = KLN89_MODE_DISP;
540 _activePage->CrsrPressed();
545 void KLN89::EntPressed() {
548 // one of the data pages. Signal ent pressed to it here, and ent pressed to the call back page a few lines further down.
549 // Ie. 2 ent pressed signals in this case is deliberate.
550 _activePage->EntPressed();
553 _activePage = _pages[(unsigned int)_entJump];
554 if(_jumpRestoreCrsr) _mode = KLN89_MODE_CRSR;
555 _entJump = _clrJump = -1;
557 if(_activePage == _dir_page) {
558 _dir_page->EntPressed();
559 _mode = KLN89_MODE_DISP;
560 _activePage = _pages[_curPage];
562 _activePage->EntPressed();
566 void KLN89::ClrPressed() {
569 _activePage = _pages[(unsigned int)_clrJump];
570 if(_jumpRestoreCrsr) _mode = KLN89_MODE_CRSR;
571 _entJump = _clrJump = -1;
573 _activePage->ClrPressed();
576 void KLN89::DtoPressed() {
577 if(_activePage != _dir_page) {
578 // Figure out which waypoint the dir page should display, according to the following rules:
579 // 1. If the FPL 0 page is displayed AND the cursor is over one of the waypoints, display that waypoint.
580 // 2. If the NAV 4 page is displayed with the inner knob pulled out, display the waypoint highlighted in the lower RH corner of the nav page.
581 // 3. If any of APT, VOR, NDB, INT, USR or ACT pages is displayed then display the waypoint being viewed.
582 // 4. If none of the above, display the active waypoint, unless the active waypoint is the MAP of an approach and it has been flown past
583 // (no waypoint sequence past the MAP), in which case display the first waypoint of the missed approach procedure.
584 // 5. If none of the above (i.e. no active waypoint) then display blanks.
586 // APT, VOR, NDB, INT, USR or ACT
587 if(!_activePage->GetId().empty()) { // Guard against no user waypoints defined
588 _dir_page->SetId(_activePage->GetId());
590 _dir_page->SetId(_activeWaypoint.id);
592 } else if(_curPage == 6 && _activePage->GetSubPage() == 3 && fgGetBool("/instrumentation/kln89/scan-pull") && _activeFP->waypoints.size()) {
594 _dir_page->SetId(((KLN89NavPage*)_activePage)->GetNav4WpId());
595 } else if(_curPage == 7 && _activePage->GetSubPage() == 0 && _mode == KLN89_MODE_CRSR) {
597 if(!_activePage->GetId().empty()) {
598 //cout << "Not empty!!!\n";
599 _dir_page->SetId(_activePage->GetId());
601 //cout << "empty :-(\n";
602 _dir_page->SetId(_activeWaypoint.id);
605 _dir_page->SetId(_activeWaypoint.id);
607 // This need to come after the bit before otherwise the FPL or NAV4 page clears their current ID when it looses focus.
608 _activePage->LooseFocus();
609 _activePage = _dir_page;
610 _mode = KLN89_MODE_CRSR;
614 void KLN89::NrstPressed() {
615 if(_activePage != _nrst_page) {
616 _activePage->LooseFocus(); // TODO - check whether we should call loose focus here
617 _lastActivePage = _activePage;
618 _activePage = _nrst_page;
620 _mode = KLN89_MODE_CRSR;
622 _activePage = _lastActivePage;
627 void KLN89::AltPressed() {
628 if(_activePage != _alt_page) {
629 _activePage->LooseFocus(); // TODO - check whether we should call loose focus here
630 _lastActivePage = _activePage;
631 _alt_page->SetSubPage(0);
632 _activePage = _alt_page;
634 _mode = KLN89_MODE_CRSR;
636 _alt_page->LooseFocus();
637 if(_alt_page->GetSubPage() == 0) {
638 _alt_page->SetSubPage(1);
639 _mode = KLN89_MODE_CRSR;
641 _activePage = _lastActivePage;
647 void KLN89::OBSPressed() {
650 if(!fgGetBool("/instrumentation/nav/slaved-to-gps")) {
651 // NOTE: this only applies to ORS 02 firmware, in ORS 01
652 // CRSR mode is not automatically set when OBS is started.
653 _mode = KLN89_MODE_CRSR;
655 _activePage->OBSPressed();
659 void KLN89::MsgPressed() {
660 // TODO - handle persistent messages such as SUA alerting.
661 // (The message annunciation flashes before first view, but afterwards remains continuously lit with the message available
662 // until the potential conflict no longer pertains).
663 if(_dispMsg && _messageStack.size()) {
664 _messageStack.pop_front();
666 _dispMsg = !_dispMsg;
669 void KLN89::ToggleOBSMode() {
670 DCLGPS::ToggleOBSMode();
673 void KLN89::DtoInitiate(const string& id) {
675 // Set the current page to NAV1
677 _activePage = _pages[_curPage];
678 _activePage->SetSubPage(0);
679 // TODO - need to output a scratchpad message with the new course, but we don't know it yet!
680 // Call the base class to actually initiate the DTO.
681 DCLGPS::DtoInitiate(id);
684 void KLN89::SetMinDisplayBrightness(int n) {
685 _minDisplayBrightness = n;
686 if(_minDisplayBrightness < 1) _minDisplayBrightness = 1;
687 if(_minDisplayBrightness > 9) _minDisplayBrightness = 9;
690 void KLN89::DecrementMinDisplayBrightness() {
691 _minDisplayBrightness--;
692 if(_minDisplayBrightness < 1) _minDisplayBrightness = 1;
695 void KLN89::IncrementMinDisplayBrightness() {
696 _minDisplayBrightness++;
697 if(_minDisplayBrightness > 9) _minDisplayBrightness = 9;
700 void KLN89::DrawBar(int page) {
701 int px = 1 + (page * 15);
703 for(int i=0; i<7; ++i) {
704 // Ugh - this is crude and inefficient!
705 _instrument->DrawPixel(px+i, py);
706 _instrument->DrawPixel(px+i, py+1);
710 // Convert moving map to instrument co-ordinates
711 void KLN89::MapToInstrument(int &x, int &y) {
712 x += _xBorder + _xFieldBorder[2] + _xFieldStart[2];
715 // Draw a pixel specified in instrument co-ords, but clipped to the map region
716 //void KLN89::DrawInstrMapPixel(int x, int y) {
719 // Clip, translate and draw a map pixel
720 // If we didn't need per-pixel clipping, it would be cheaper to translate object rather than pixel positions.
721 void KLN89::DrawMapPixel(int x, int y, bool invert) {
722 if(x < 0 || x > 111 || y < 0 || y > 39) return;
723 x += _xBorder + _xFieldBorder[2] + _xFieldStart[2];
724 _instrument->DrawPixel(x, y, invert);
728 // HACK - use something FG provides
729 static double gps_min(const double &a, const double &b) {
730 return(a <= b ? a : b);
733 static double gps_max(const double &a, const double &b) {
734 return(a >= b ? a : b);
737 void KLN89::UpdateMapHeading() {
738 switch(_mapOrientation) {
743 _mapHeading = _dtkTrue;
746 _mapHeading = _track;
751 // The screen area allocated to the moving map is 111 x 40 pixels.
752 // In North up mode, the user position marker is at 57, 20. (Map co-ords).
753 void KLN89::DrawMap(bool draw_avs) {
754 // Set the clipping region to the moving map part of the display
755 int xstart = _xBorder + _xFieldBorder[2] + _xFieldStart[2];
756 _instrument->SetClipRegion(xstart, 0, xstart + 110, 39);
758 _mapScaleUnits = (int)_distUnits;
759 _mapScale = (double)(KLN89MapScales[_mapScaleUnits][_mapScaleIndex]);
761 //cout << "Map scale = " << _mapScale << '\n';
763 double mapScaleMeters = _mapScale * (_mapScaleUnits == 0 ? SG_NM_TO_METER : 1000);
765 // TODO - use an aligned projection when either DTK or TK up!
767 FGATCAlignedProjection mapProj(SGGeod::fromRad(_gpsLon, _gpsLat), _mapHeading);
769 FGKln89AlignedProjection mapProj(SGGeod::fromRad(_gpsLon, _gpsLat), _mapHeading);
771 double meter_per_pix = (_mapOrientation == 0 ? mapScaleMeters / 20.0f : mapScaleMeters / 29.0f);
772 SGGeod bottomLeft = mapProj.ConvertFromLocal(SGVec3d(gps_max(-57.0 * meter_per_pix, -50000), gps_max((_mapOrientation == 0 ? -20.0 * meter_per_pix : -11.0 * meter_per_pix), -25000), 0.0));
773 SGGeod topRight = mapProj.ConvertFromLocal(SGVec3d(gps_min(54.0 * meter_per_pix, 50000), gps_min((_mapOrientation == 0 ? 20.0 * meter_per_pix : 29.0 * meter_per_pix), 25000), 0.0));
778 // Draw Airport labels first (but not one's that are waypoints)
779 // Draw Airports first (but not one's that are waypoints)
780 // Ditto for VORs (not sure if SUA/VOR/Airport ordering is important or not).
784 // Then waypoint labels (not sure if this should be before or after waypoints)
786 // Annotation then gets drawn by Nav page, NOT this function.
788 if(_drawApt && draw_avs) {
790 bool have_apt = _overlays->FindArpByRegion(&apt, bottomLeft.lat(), bottomLeft.lon(), topRight.lat(), topRight.lon());
791 //cout << "Vors enclosed are: ";
792 // Draw all the labels first...
793 for(unsigned int i=0; i<apt.size(); ++i) {
794 //cout << nav[i]->id << ' ';
795 Point3D p = mapProj.ConvertToLocal(Point3D(apt[i]->lon * SG_RADIANS_TO_DEGREES, apt[i]->lat * SG_RADIANS_TO_DEGREES, 0.0));
796 //cout << p << " .... ";
797 int mx = int(p.x() / meter_per_pix) + 56;
798 int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
799 //cout << "mx = " << mx << ", my = " << my << '\n';
800 bool right_align = (p.x() < 0.0);
801 DrawLabel(apt[i]->id, mx + (right_align ? -2 : 3), my + (p.y() < 0.0 ? -7 : 3), right_align);
802 // I think that we probably should have -1 in the right_align case above to match the real life instrument.
804 // ...and then all the Apts.
805 for(unsigned int i=0; i<apt.size(); ++i) {
806 //cout << nav[i]->id << ' ';
807 Point3D p = mapProj.ConvertToLocal(Point3D(apt[i]->lon * SG_RADIANS_TO_DEGREES, apt[i]->lat * SG_RADIANS_TO_DEGREES, 0.0));
808 //cout << p << " .... ";
809 int mx = int(p.x() / meter_per_pix) + 56;
810 int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
811 //cout << "mx = " << mx << ", my = " << my << '\n';
818 if(_drawVOR && draw_avs) {
819 Overlays::nav_array_type nav;
820 bool have_vor = _overlays->FindVorByRegion(&nav, bottomLeft.lat(), bottomLeft.lon(), topRight.lat(), topRight.lon());
821 //cout << "Vors enclosed are: ";
822 // Draw all the labels first...
823 for(unsigned int i=0; i<nav.size(); ++i) {
824 //cout << nav[i]->id << ' ';
825 Point3D p = mapProj.ConvertToLocal(Point3D(nav[i]->lon * SG_RADIANS_TO_DEGREES, nav[i]->lat * SG_RADIANS_TO_DEGREES, 0.0));
826 //cout << p << " .... ";
827 int mx = int(p.x() / meter_per_pix) + 56;
828 int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
829 //cout << "mx = " << mx << ", my = " << my << '\n';
830 bool right_align = (p.x() < 0.0);
831 DrawLabel(nav[i]->id, mx + (right_align ? -2 : 3), my + (p.y() < 0.0 ? -7 : 3), right_align);
832 // I think that we probably should have -1 in the right_align case above to match the real life instrument.
834 // ...and then all the VORs.
835 for(unsigned int i=0; i<nav.size(); ++i) {
836 //cout << nav[i]->id << ' ';
837 Point3D p = mapProj.ConvertToLocal(Point3D(nav[i]->lon * SG_RADIANS_TO_DEGREES, nav[i]->lat * SG_RADIANS_TO_DEGREES, 0.0));
838 //cout << p << " .... ";
839 int mx = int(p.x() / meter_per_pix) + 56;
840 int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
841 //cout << "mx = " << mx << ", my = " << my << '\n';
849 if(_activeFP->waypoints.size() > 1) {
850 vector<int> xvec, yvec, qvec; // qvec stores the quadrant that each waypoint label should
851 // be drawn in (relative to the waypoint).
852 // 1 = NE, 2 = SE, 3 = SW, 4 = NW.
853 double save_h = 0.0; // Each pass, save a heading from the previous one for label quadrant determination.
854 bool drawTrack = true;
855 for(unsigned int i=1; i<_activeFP->waypoints.size(); ++i) {
856 GPSWaypoint* wp0 = _activeFP->waypoints[i-1];
857 GPSWaypoint* wp1 = _activeFP->waypoints[i];
858 SGVec3d p0 = mapProj.ConvertToLocal(SGGeod::fromRad(wp0->lon, wp0->lat));
859 SGVec3d p1 = mapProj.ConvertToLocal(SGGeod::fromRad(wp1->lon, wp1->lat));
860 int mx0 = int(p0.x() / meter_per_pix + 0.5) + 56;
861 int my0 = int(p0.y() / meter_per_pix + 0.5) + (_mapOrientation == 0 ? 19 : 10);
862 int mx1 = int(p1.x() / meter_per_pix + 0.5) + 56;
863 int my1 = int(p1.y() / meter_per_pix + 0.5) + (_mapOrientation == 0 ? 19 : 10);
867 double h = GetGreatCircleCourse(wp0->lat, wp0->lon, wp1->lat, wp1->lon) * SG_RADIANS_TO_DEGREES;
868 // Adjust for map orientation
870 qvec.push_back(GetLabelQuadrant(h));
871 //cout << "i = " << i << ", h = " << h << ", qvec[0] = " << qvec[0] << '\n';
875 if(drawTrack) { DrawLine(mx0, my0, mx1, my1); }
877 double h = GetGreatCircleCourse(wp0->lat, wp0->lon, wp1->lat, wp1->lon) * SG_RADIANS_TO_DEGREES;
878 // Adjust for map orientation
880 qvec.push_back(GetLabelQuadrant(save_h, h));
882 save_h = GetGreatCircleCourse(wp1->lat, wp1->lon, wp0->lat, wp0->lon) * SG_RADIANS_TO_DEGREES;
883 // Adjust for map orientation
884 save_h -= _mapHeading;
885 if(i == _activeFP->waypoints.size() - 1) {
886 qvec.push_back(GetLabelQuadrant(save_h));
888 // Don't draw flight track beyond the missed approach point of an approach
889 if(_approachLoaded) {
890 //cout << "Waypoints are " << wp0->id << " and " << wp1->id << '\n';
891 //cout << "Types are " << wp0->appType << " and " << wp1->appType << '\n';
892 if(wp1->appType == GPS_MAP) {
897 // ASSERT(xvec.size() == yvec.size() == qvec.size() == _activeFP->waypoints.size());
898 for(unsigned int i=0; i<xvec.size(); ++i) {
899 DrawWaypoint(xvec[i], yvec[i]);
900 bool right_align = (qvec[i] > 2);
901 bool top = (qvec[i] == 1 || qvec[i] == 4);
902 // TODO - not sure if labels should be drawn in sequence with waypoints and flightpaths,
903 // or all before or all afterwards. Doesn't matter a huge deal though.
904 DrawLabel(_activeFP->waypoints[i]->id, xvec[i] + (right_align ? -2 : 3), yvec[i] + (top ? 3 : -7), right_align);
909 if(_mapOrientation == 0) {
912 } else if(_mapOrientation == 1) {
915 } else if(_mapOrientation == 2) {
920 // TODO - don't know what to do here!
923 // And finally, reset the clip region to stop the rest of the code going pear-shaped!
924 _instrument->ResetClipRegion();
927 // Get the quadrant to draw the label of the start or end waypoint (i.e. one with only one track from it).
928 // Heading specified FROM the waypoint.
932 int KLN89::GetLabelQuadrant(double h) {
933 while(h < 0.0) h += 360.0;
934 while(h > 360.0) h -= 360.0;
935 if(h < 90.0) return(3);
936 if(h < 180.0) return(4);
937 if(h < 270.0) return(1);
941 // Get the quadrant to draw the label of an en-route waypoint,
942 // with BOTH tracks specified as headings FROM the waypoint.
946 int KLN89::GetLabelQuadrant(double h1, double h2) {
947 while(h1 < 0.0) h1 += 360.0;
948 while(h1 > 360.0) h1 -= 360.0;
949 while(h2 < 0.0) h2 += 360.0;
950 while(h2 > 360.0) h2 -= 360.0;
951 double max_min_diff = 0.0;
953 for(int i=0; i<4; ++i) {
954 double h = 45 + (90 * i);
955 double diff1 = fabs(h - h1);
956 if(diff1 > 180) diff1 = 360 - diff1;
957 double diff2 = fabs(h - h2);
958 if(diff2 > 180) diff2 = 360 - diff2;
959 double min_diff = gps_min(diff1, diff2);
960 if(min_diff > max_min_diff) {
961 max_min_diff = min_diff;
965 //cout << "GetLabelQuadrant, h1 = " << h1 << ", h2 = " << h2 << ", quad = " << quad << '\n';
969 // Draw the diamond style of user pos
979 void KLN89::DrawUser1(int x, int y) {
980 MapToInstrument(x, y);
981 int min_j = 0, max_j = 0;
982 for(int i=-3; i<=3; ++i) {
983 for(int j=min_j; j<=max_j; ++j) {
984 _instrument->DrawPixel(x+j, y+i, (j == min_j || j == max_j ? true : false));
996 // Draw the airplane style of user pos
997 // Define the origin to be the midpoint of the *fuselage*
998 void KLN89::DrawUser2(int x, int y) {
999 MapToInstrument(x, y);
1001 // Draw the background as three black quads first
1002 _instrument->DrawQuad(x-2, y-3, x+2, y-1, true);
1003 _instrument->DrawQuad(x-3, y, x+3, y+2, true);
1004 _instrument->DrawQuad(x-1, y+3, x+1, y+3, true);
1007 for(int j=y-2; j<=y+2; ++j) {
1008 _instrument->DrawPixel(x, j);
1010 for(int i=x-1; i<=x+1; ++i) {
1011 _instrument->DrawPixel(i, y-2);
1013 for(int i=x-2; i<=x+2; ++i) {
1014 _instrument->DrawPixel(i, y+1);
1017 _instrument->DrawQuad(x, y-2, x, y+2);
1018 _instrument->DrawQuad(x-1, y-2, x+1, y-2);
1019 _instrument->DrawQuad(x-2, y+1, x+2, y+1);
1023 // Draw an airport symbol on the moving map
1031 void KLN89::DrawApt(int x, int y) {
1032 MapToInstrument(x, y);
1036 for(i=x-1; i<=x+1; ++i) _instrument->DrawPixel(i, j, true);
1038 for(i=x-2; i<=x+2; ++i) _instrument->DrawPixel(i, j, (i != x ? true : false));
1040 for(i=x-2; i<=x+2; ++i) _instrument->DrawPixel(i, j, (abs(i - x) > 1 ? true : false));
1042 for(i=x-2; i<=x+2; ++i) _instrument->DrawPixel(i, j, (i != x ? true : false));
1044 for(i=x-1; i<=x+1; ++i) _instrument->DrawPixel(i, j, true);
1047 // Draw a waypoint on the moving map
1055 void KLN89::DrawWaypoint(int x, int y) {
1056 MapToInstrument(x, y);
1057 _instrument->SetDebugging(true);
1059 // Draw black background
1060 _instrument->DrawQuad(x-2, y-2, x+2, y+2, true);
1062 // Draw the coloured square
1064 for(int i=x-1; i<=x+1; ++i) {
1065 for(int j=y-1; j<=y+1; ++j) {
1066 _instrument->DrawPixel(i, j);
1070 _instrument->DrawQuad(x-1, y-1, x+1, y+1);
1072 _instrument->SetDebugging(false);
1075 // Draw a VOR on the moving map
1083 void KLN89::DrawVOR(int x, int y) {
1084 // Cheat - draw a waypoint and then a black pixel in the middle.
1085 // Need to call Waypoint draw *before* translating co-ords.
1087 MapToInstrument(x, y);
1088 _instrument->DrawPixel(x, y, true);
1091 // Draw a line on the moving map
1092 void KLN89::DrawLine(int x1, int y1, int x2, int y2) {
1093 MapToInstrument(x1, y1);
1094 MapToInstrument(x2, y2);
1095 _instrument->DrawLine(x1, y1, x2, y2);
1098 void KLN89::DrawMapUpArrow(int x, int y) {
1099 MapToInstrument(x, y);
1101 for(int j=0; j<7; ++j) {
1102 _instrument->DrawPixel(x + 2, y + j);
1105 _instrument->DrawQuad(x+2, y, x+2, y+6);
1107 _instrument->DrawPixel(x, y+4);
1108 _instrument->DrawPixel(x+1, y+5);
1109 _instrument->DrawPixel(x+3, y+5);
1110 _instrument->DrawPixel(x+4, y+4);
1113 // Draw a quad on the moving map
1114 void KLN89::DrawMapQuad(int x1, int y1, int x2, int y2, bool invert) {
1115 MapToInstrument(x1, y1);
1116 MapToInstrument(x2, y2);
1117 _instrument->DrawQuad(x1, y1, x2, y2, invert);
1120 // Draw an airport or waypoint label on the moving map
1121 // Specify position by the map pixel co-ordinate of the left or right, bottom, of the *visible* portion of the label.
1122 // The black background quad will automatically overlap this by 1 pixel.
1123 void KLN89::DrawLabel(const string& s, int x1, int y1, bool right_align) {
1124 MapToInstrument(x1, y1);
1126 for(unsigned int i=0; i<s.size(); ++i) {
1128 x1 += DrawSmallChar(c, x1, y1);
1132 for(int i=(int)(s.size()-1); i>=0; --i) {
1134 x1 -= DrawSmallChar(c, x1, y1, right_align);
1140 void KLN89::DrawCDI() {
1142 for(int i=0; i<5; ++i) {
1143 DrawSpecialChar(2, 2, 3+i, 2);
1144 DrawSpecialChar(1, 2, 9+i, 2);
1148 int px = 8 * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field] + 2;
1149 int py = 2 * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1152 // Every 7 pixels deflection left or right is one dot on the scale, and hence 1/5 FSD.
1153 // Maximum deflection is 37 pixels left, or 38 pixels right !?!
1154 double xtd = CalcCrossTrackDeviation();
1156 if(_cdiScaleTransition) {
1157 double dots = (xtd / _currentCdiScale) * 5.0;
1158 deflect = (int)(dots * 7.0 * -1.0);
1159 // TODO - for all these I think I should add 0.5 before casting to int, and *then* multiply by -1. Possibly!
1161 if(0 == _currentCdiScaleIndex) { // 5.0nm FSD => 1 nm per dot => 7 pixels per nm.
1162 deflect = (int)(xtd * 7.0 * -1.0); // The -1.0 is because we move the 'needle' indicating the course, not the plane.
1163 } else if(1 == _currentCdiScaleIndex) {
1164 deflect = (int)(xtd * 35.0 * -1.0);
1165 } else { // 0.3 == _cdiScale
1166 deflect = (int)(xtd * 116.6666666667 * -1.0);
1169 if(deflect > 38) deflect = 38;
1170 if(deflect < -37) deflect = -37;
1172 for(int j=0; j<9; ++j) {
1173 _instrument->DrawPixel(px + deflect, py+j);
1174 _instrument->DrawPixel(px + deflect + 1, py+j);
1177 _instrument->DrawQuad(px + deflect, py, px + deflect + 1, py + 8);
1180 // To/From indicator
1183 for(int j=4; j>=0; --j) {
1185 for(int i=0; i<k; ++i) {
1186 _instrument->DrawPixel(px+j+i, (_headingBugTo ? py+j : py+4-j));
1187 // At the extremities, draw the outlining dark pixel
1188 if(i == 0 || i == k-1) {
1189 _instrument->DrawPixel(px+j+i, (_headingBugTo ? py+j+1 : py+3-j), true);
1195 void KLN89::DrawLegTail(int py) {
1196 int px = 0 * 7 + _xBorder + _xFieldBorder[2] + _xFieldStart[2];
1197 py = py * 9 + _yBorder + _yFieldBorder[2] + _yFieldStart[2];
1201 py++; // Hack - not sure if this represents a border issue.
1203 for(int i=0; i<9; ++i) _instrument->DrawPixel(px, py+i);
1204 for(int i2=0; i2<5; ++i2) _instrument->DrawPixel(px+i2, py+9);
1207 void KLN89::DrawLongLegTail(int py) {
1208 int px = 0 * 7 + _xBorder + _xFieldBorder[2] + _xFieldStart[2];
1209 py = py * 9 + _yBorder + _yFieldBorder[2] + _yFieldStart[2];
1213 py++; // Hack - not sure if this represents a border issue.
1215 for(int i=0; i<18; ++i) _instrument->DrawPixel(px, py+i);
1216 for(int i2=0; i2<5; ++i2) _instrument->DrawPixel(px+i2, py+18);
1219 void KLN89::DrawHalfLegTail(int py) {
1222 void KLN89::DrawDivider() {
1223 int px = _xFieldStart[2] - 1;
1225 for(int i=0; i<36; ++i) {
1226 _instrument->DrawPixel(px, py+i);
1230 void KLN89::DrawEnt(int field, int px, int py) {
1231 px = px * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1232 py = py * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field] + 1;
1234 px++; // Not sure why we need px++, but it seems to work!
1238 for(int i=0; i<5; ++i) _instrument->DrawPixel(px, py+i);
1239 _instrument->DrawPixel(px+1, py);
1240 _instrument->DrawPixel(px+2, py);
1241 _instrument->DrawPixel(px+1, py+2);
1242 _instrument->DrawPixel(px+1, py+4);
1243 _instrument->DrawPixel(px+2, py+4);
1247 for(int i=0; i<4; ++i) _instrument->DrawPixel(px, py+i);
1248 _instrument->DrawPixel(px+1, py+2);
1249 _instrument->DrawPixel(px+2, py+1);
1250 for(int i=0; i<4; ++i) _instrument->DrawPixel(px+3, py+i);
1254 _instrument->DrawPixel(px, py+3);
1255 for(int i=0; i<4; ++i) _instrument->DrawPixel(px+1, py+i);
1256 _instrument->DrawPixel(px+2, py+3);
1259 void KLN89::DrawMessageAlert() {
1260 // TODO - draw the proper message indicator
1262 int px = _xBorder + _xFieldBorder[1] + _xFieldStart[1];
1263 int py = 1 * 9 + _yBorder + _yFieldBorder[1] + _yFieldStart[1] + 1;
1265 px++; // Not sure why we need px++, but it seems to work!
1268 DrawText(" ", 1, 0, 1, false, 99);
1269 _instrument->DrawQuad(px+1, py-1, px+2, py+5, true);
1270 _instrument->DrawQuad(px+3, py+3, px+3, py+5, true);
1271 _instrument->DrawQuad(px+4, py+2, px+4, py+4, true);
1272 _instrument->DrawQuad(px+5, py+1, px+6, py+3, true);
1273 _instrument->DrawQuad(px+7, py+2, px+7, py+4, true);
1274 _instrument->DrawQuad(px+8, py+3, px+8, py+5, true);
1275 _instrument->DrawQuad(px+9, py-1, px+10, py+5, true);
1279 void KLN89::Underline(int field, int px, int py, int len) {
1280 px = px * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1281 py = py * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1282 for(int i=0; i<(len*7); ++i) {
1283 _instrument->DrawPixel(px, py);
1288 void KLN89::DrawKPH(int field, int cx, int cy) {
1290 int px = cx * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1291 int py = cy * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1296 for(int j=0; j<=4; ++j) {
1297 _instrument->DrawPixel(px, py + 2 +j);
1298 _instrument->DrawPixel(px + 8, py + j);
1300 _instrument->DrawPixel(px + 11, py + j);
1301 _instrument->DrawPixel(px + 9 + j, py + 2);
1305 for(int i=0; i<=6; ++i) {
1307 _instrument->DrawPixel(px + 1 + i, py + 4 + i);
1308 _instrument->DrawPixel(px + 1 + i, py + (4 - i));
1310 _instrument->DrawPixel(px + 2 + i, py + i);
1314 void KLN89::DrawDTO(int field, int cx, int cy) {
1315 DrawSpecialChar(6, field, cx, cy);
1316 if(!(_waypointAlert && _blink)) {
1317 DrawSpecialChar(3, field, cx+1, cy);
1320 int px = cx * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1321 int py = cy * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1326 // Fill in the gap between the 'D' and the arrow.
1327 _instrument->DrawPixel(px+5, py+3);
1330 // Takes character position
1331 void KLN89::DrawChar(char c, int field, int px, int py, bool bold, bool invert) {
1332 // Ignore field for now
1334 px = px * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1335 py = py * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1337 // Draw an orange background for inverted characters
1339 for(int i=0; i<7; ++i) {
1340 for(int j=0; j<9; ++j) {
1341 _instrument->DrawPixel(px + i, py + j);
1346 if(c < 33) return; // space
1348 // Render normal decimal points in bold floats
1349 if(c == '.') bold = false;
1351 ++py; // Shift the char up by one pixel
1352 for(int j=7; j>=0; --j) {
1353 char c1 = (bold ? NumbersBold[c-48][j] : UpperAlpha[c-33][j]);
1354 // Don't do the last column for now (ie. j = 1, not 0)
1355 for(int i=5; i>=0; --i) {
1356 if(c1 & (01 << i)) {
1357 _instrument->DrawPixel(px, py, invert);
1366 // Takes pixel position
1367 void KLN89::DrawFreeChar(char c, int x, int y, bool draw_background) {
1369 if(draw_background) {
1370 _instrument->DrawQuad(x, y, x+6, y+8, true);
1373 if(c < 33) return; // space
1375 ++y; // Shift the char up by one pixel
1376 for(int j=7; j>=0; --j) {
1377 char c1 = UpperAlpha[c-33][j];
1378 // Don't do the last column for now (ie. j = 1, not 0)
1379 for(int i=5; i>=0; --i) {
1380 if(c1 & (01 << i)) {
1381 _instrument->DrawPixel(x, y);
1390 // Takes instrument pixel co-ordinates.
1391 // Position is specified by the bottom of the *visible* portion, by default the left position unless align_right is true.
1392 // The return value is the pixel width of the visible portion
1393 int KLN89::DrawSmallChar(char c, int x, int y, bool align_right) {
1394 // calculate the index into the SmallChar array
1396 if(c > 47 && c < 58) {
1399 } else if(c > 64 && c < 91) {
1406 char n = SmallChar[idx][0]; // Width of visible portion
1407 if(align_right) x -= n;
1410 _instrument->DrawQuad(x - 1, y - 1, x + n, y + 5, true);
1412 for(int j=7; j>=3; --j) {
1413 char c1 = SmallChar[idx][j];
1414 for(int i=n-1; i>=0; --i) {
1415 if(c1 & (01 << i)) {
1416 _instrument->DrawPixel(x, y);
1427 // Takes character position
1428 void KLN89::DrawSpecialChar(char c, int field, int cx, int cy, bool bold) {
1430 cout << "ERROR - requested special char outside array bounds!\n";
1431 return; // Increment this as SpecialChar grows
1434 // Convert character to pixel position.
1435 // Ignore field for now
1437 int px = cx * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1438 int py = cy * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1439 ++py; // Total hack - the special chars were coming out 1 pixel too low!
1440 for(int i=7; i>=0; --i) {
1441 char c1 = SpecialChar[(int)c][i];
1442 // Don't do the last column for now (ie. j = 1, not 0)
1443 for(int j=5; j>=0; --j) {
1444 if(c1 & (01 << j)) {
1445 _instrument->DrawPixel(px, py);
1454 void KLN89::DrawText(const string& s, int field, int px, int py, bool bold, int invert) {
1455 for(int i = 0; i < (int)s.size(); ++i) {
1456 DrawChar(s[(unsigned int)i], field, px+i, py, bold, (invert == i || invert == 99));
1460 void KLN89::DrawMapText(const string& s, int x, int y, bool draw_background) {
1461 MapToInstrument(x, y);
1462 if(draw_background) {
1463 //_instrument->DrawQuad(x, y, x + (7 * s.size()) - 1, y + 8, true);
1464 _instrument->DrawQuad(x - 1, y, x + (7 * s.size()) - 2, y + 8, true);
1465 // 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!
1468 for(int i = 0; i < (int)s.size(); ++i) {
1469 DrawFreeChar(s[(unsigned int)i], x+(i * 7)-1, y);
1473 void KLN89::DrawLatitude(double d, int field, int px, int py) {
1474 DrawChar((d >= 0 ? 'N' : 'S'), field, px, py);
1477 // TODO - sanity check input to ensure major lat field can only ever by 2 chars wide
1479 // Don't know whether to zero pad the below for single digits or not?
1480 //cout << d << ", " << (int)d << '\n';
1481 // 3 not 2 in size before for trailing \0
1482 int n = snprintf(buf, 3, "%i", (int)d);
1484 //cout << s << "... " << n << '\n';
1485 DrawText(s, field, px+(3-n), py);
1486 n = snprintf(buf, 7, "%05.2f'", ((double)(d - (int)d)) * 60.0f);
1489 DrawSpecialChar(0, field, px, py); // Degrees symbol
1491 DrawText(s, field, px, py);
1494 void KLN89::DrawLongitude(double d, int field, int px, int py) {
1495 DrawChar((d >= 0 ? 'E' : 'W'), field, px, py);
1498 // TODO - sanity check input to ensure major lat field can only ever be 2 chars wide
1500 // Don't know whether to zero pad the below for single digits or not?
1501 //cout << d << ", " << (int)d << '\n';
1502 // 4 not 3 in size before for trailing \0
1503 int n = snprintf(buf, 4, "%i", (int)d);
1505 //cout << s << "... " << n << '\n';
1506 DrawText(s, field, px+(3-n), py);
1507 n = snprintf(buf, 7, "%05.2f'", ((double)(d - (int)d)) * 60.0f);
1510 DrawSpecialChar(0, field, px, py); // Degrees symbol
1512 DrawText(s, field, px, py);
1515 void KLN89::DrawFreq(double d, int field, int px, int py) {
1516 if(d >= 1000) d /= 100.0f;
1518 snprintf(buf, 7, "%6.2f", d);
1520 DrawText(s, field, px, py);
1523 void KLN89::DrawTime(double time, int field, int px, int py) {
1524 int hrs = (int)(time / 3600);
1525 int mins = (int)(ceil((time - (hrs * 3600)) / 60.0));
1530 n = snprintf(buf, 9, "%i:%02i", hrs, mins);
1533 n = snprintf(buf, 4, ":%02i", (int)time);
1536 DrawText(s, field, px - n + 1, py);
1539 void KLN89::DrawHeading(int h, int field, int px, int py) {
1541 snprintf(buf, 4, "%i", h);
1543 DrawText(s, field, px - s.size(), py);
1544 DrawSpecialChar(0, field, px, py); // Degrees symbol
1547 void KLN89::DrawDist(double d, int field, int px, int py) {
1548 d *= (_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001);
1550 snprintf(buf, 9, "%i", (int)(d + 0.5));
1552 s += (_distUnits == GPS_DIST_UNITS_NM ? "nm" : "Km");
1553 DrawText(s, field, px - s.size() + 1, py);
1556 void KLN89::DrawSpeed(double v, int field, int px, int py, int decimal) {
1557 // TODO - implement variable decimal places
1558 v *= (_velUnits == GPS_VEL_UNITS_KT ? 1.0 : 0.51444444444 * 0.001 * 3600.0);
1560 snprintf(buf, 9, "%i", (int)(v + 0.5));
1562 if(_velUnits == GPS_VEL_UNITS_KT) {
1564 DrawText(s, field, px - s.size() + 1, py);
1566 DrawText(s, field, px - s.size() - 1, py);
1567 DrawKPH(field, px - 1, py);
1571 void KLN89::DrawDirDistField(double lat, double lon, int field, int px, int py, bool to_flag, bool cursel) {
1572 DrawChar('>', field, px, py);
1576 h = GetMagHeadingFromTo(_gpsLat, _gpsLon, lat, lon);
1578 h = GetMagHeadingFromTo(lat, lon, _gpsLat, _gpsLon);
1580 while(h < 0.0) h += 360.0;
1581 while(h > 360.0) h -= 360.0;
1582 snprintf(buf, 4, "%3i", (int)(h + 0.5));
1584 if(!(cursel && _blink)) {
1585 DrawText(s, field, px + 4 - s.size(), py);
1586 DrawSpecialChar(0, field, px+4, py);
1587 DrawText((to_flag ? "To" : "Fr"), field, px+5, py);
1588 if(cursel) Underline(field, px + 1, py, 6);
1590 //double d = GetHorizontalSeparation(_gpsLat, _gpsLon, lat, lon);
1591 //d *= (_distUnits == GPS_DIST_UNITS_NM ? SG_METER_TO_NM : 0.001);
1592 double d = GetGreatCircleDistance(_gpsLat, _gpsLon, lat, lon);
1593 d *= (_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001);
1595 snprintf(buf, 7, "%5i", (int)(d + 0.5));
1597 snprintf(buf, 7, "%4.1f", d);
1600 DrawText(s, field, px + 12 - s.size(), py);
1601 DrawText((_distUnits == GPS_DIST_UNITS_NM ? "nm" : "Km"), field, px + 12, py);
1604 char KLN89::IncChar(char c, bool gap, bool wrap) {
1605 if(c == '9') return(wrap ? (gap ? ' ' : 'A') : '9');
1606 if(c == 'Z') return('0');
1607 if(c == ' ') return('A');
1611 char KLN89::DecChar(char c, bool gap, bool wrap) {
1612 if(c == 'A') return(wrap ? (gap ? ' ' : '9') : 'A');
1613 if(c == '0') return('Z');
1614 if(c == ' ') return('9');