1 // kln89_page_*.[ch]xx - this file is one of the "pages" that
2 // are used in the KLN89 GPS unit simulation.
4 // Written by David Luff, started 2005.
6 // Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 // General Public License for more details.
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
28 #include "kln89_page_nav.hxx"
29 #include <Main/fg_props.hxx>
31 KLN89NavPage::KLN89NavPage(KLN89* parent)
36 _posFormat = 0; // Check - should this default to ref from waypoint?
47 KLN89NavPage::~KLN89NavPage() {
50 void KLN89NavPage::Update(double dt) {
51 GPSFlightPlan* fp = _kln89->_activeFP;
52 GPSWaypoint* awp = _kln89->GetActiveWaypoint();
53 // Scan-pull out on nav4 page switches off the cursor
54 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) { _kln89->_mode = KLN89_MODE_DISP; }
55 bool crsr = (_kln89->_mode == KLN89_MODE_CRSR);
56 bool blink = _kln89->_blink;
57 double lat = _kln89->_gpsLat * SG_RADIANS_TO_DEGREES;
58 double lon = _kln89->_gpsLon * SG_RADIANS_TO_DEGREES;
60 if(_subPage != 3) { _scanWpSet = false; }
63 if(_kln89->_navFlagged) {
64 _kln89->DrawText("> F L A G", 2, 0, 2);
65 _kln89->DrawText("DTK --- TK ---", 2, 0, 1);
66 _kln89->DrawText(">--- To --:--", 2, 0, 0);
67 _kln89->DrawSpecialChar(0, 2, 7, 1);
68 _kln89->DrawSpecialChar(0, 2, 15, 1);
69 _kln89->DrawSpecialChar(0, 2, 4, 0);
70 _kln89->DrawSpecialChar(1, 2, 3, 2);
71 _kln89->DrawSpecialChar(1, 2, 4, 2);
72 _kln89->DrawSpecialChar(1, 2, 6, 2);
73 _kln89->DrawSpecialChar(1, 2, 10, 2);
74 _kln89->DrawSpecialChar(1, 2, 12, 2);
75 _kln89->DrawSpecialChar(1, 2, 13, 2);
78 _kln89->DrawDTO(2, 7, 3);
80 if(!(_kln89->_waypointAlert && _kln89->_blink)) {
81 _kln89->DrawSpecialChar(3, 2, 8, 3);
84 _kln89->DrawText(awp->id, 2, 10, 3);
85 if(!_kln89->_dto && !_kln89->_obsMode && !_kln89->_fromWaypoint.id.empty()) {
86 if(_kln89->_fromWaypoint.type != GPS_WP_VIRT) { // Don't draw the virtual waypoint names
87 _kln89->DrawText(_kln89->_fromWaypoint.id, 2, 1, 3);
90 if(!(crsr && blink && _uLinePos == 1)) {
93 } else if(_cdiFormat == 1) {
94 _kln89->DrawText("Fly", 2, 2, 2);
95 double x = _kln89->CalcCrossTrackDeviation();
96 // TODO - check the R/L from sign of x below - I *think* it holds but not sure!
97 // Note also that we're setting Fly R or L based on the aircraft
98 // position only, not the heading. Not sure if this is correct or not.
99 _kln89->DrawText(x < 0.0 ? "R" : "L", 2, 6, 2);
102 x = fabs(x * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001));
104 n = snprintf(buf, 6, "%0.2f", x);
105 } else if(x < 100.0) {
106 n = snprintf(buf, 6, "%0.1f", x);
108 n = snprintf(buf, 6, "%i", (int)(x+0.5));
110 _kln89->DrawText((string)buf, 2, 13-n, 2);
111 _kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "km", 2, 13, 2);
113 _kln89->DrawText("CDI Scale:", 2, 1, 2);
114 double d = _kln89->_cdiScales[_kln89->_currentCdiScaleIndex] * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001);
118 snprintf(buf, 4, "%2.1f", d);
121 snprintf(buf, 5, "%2.2f", d);
122 // trim the leading zero
124 s = s.substr(1, s.size() - 1);
126 _kln89->DrawText(s, 2, 11, 2);
127 _kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "km", 2, 14, 2);
130 _kln89->DrawChar('>', 2, 0, 2);
131 _kln89->DrawChar('>', 2, 0, 0);
133 if(_uLinePos == 1) _kln89->Underline(2, 1, 2, 15);
134 else if(_uLinePos == 2) _kln89->Underline(2, 1, 0, 9);
136 // Desired and actual magnetic track
137 if(!_kln89->_obsMode) {
138 _kln89->DrawText("DTK", 2, 0, 1);
139 _kln89->DrawHeading((int)_kln89->_dtkMag, 2, 7, 1);
141 _kln89->DrawText("TK", 2, 9, 1);
142 if(_kln89->_groundSpeed_ms > 3) { // about 6 knots, don't know exactly what value to disable track
143 // The trouble with relying on FG gps's track value is we don't know when it's valid.
144 _kln89->DrawHeading((int)_kln89->_magTrackDeg, 2, 15, 1);
146 _kln89->DrawText("---", 2, 12, 1);
147 _kln89->DrawSpecialChar(0, 2, 15, 1);
150 // Radial to/from active waypoint.
151 // TODO - Not sure if this either is or should be true or mag!!!!!!!
152 if(!(crsr && blink && _uLinePos == 2)) {
154 _kln89->DrawHeading((int)_kln89->GetHeadingToActiveWaypoint(), 2, 4, 0);
155 _kln89->DrawText("To", 2, 5, 0);
156 } else if(1 == _vnv) {
157 _kln89->DrawHeading((int)_kln89->GetHeadingFromActiveWaypoint(), 2, 4, 0);
158 _kln89->DrawText("Fr", 2, 5, 0);
160 _kln89->DrawText("Vnv Off", 2, 1, 0);
163 // It seems that the floating point groundspeed must be at least 30kt
164 // for an ETA to be calculated. Note that this means that (integer) 30kt
165 // can appear in the frame 1 display both with and without an ETA displayed.
166 // TODO - need to switch off track (and heading bug change) based on instantaneous speed as well
167 // since the long gps lag filter means we can still be displaying when stopped on ground.
168 if(_kln89->_groundSpeed_kts > 30.0) {
169 // Assuming eta display is always hh:mm
170 // Does it ever switch to seconds when close?
171 if(_kln89->_eta / 3600.0 > 100.0) {
172 // More that 100 hours ! - Doesn't fit.
173 _kln89->DrawText("--:--", 2, 11, 0);
175 _kln89->DrawTime(_kln89->_eta, 2, 15, 0);
178 _kln89->DrawText("--:--", 2, 11, 0);
181 } else if(1 == _subPage) {
183 _kln89->DrawChar('>', 2, 1, 3);
184 if(!(crsr && blink && _uLinePos == 1)) _kln89->DrawText("PRESENT POSN", 2, 2, 3);
185 if(crsr && _uLinePos == 1) _kln89->Underline(2, 2, 3, 12);
186 if(0 == _posFormat) {
188 _kln89->DrawLatitude(lat, 2, 3, 1);
189 _kln89->DrawLongitude(lon, 2, 3, 0);
191 // Ref from wp - defaults to nearest vor (and resets to default when page left and re-entered).
193 } else if(2 == _subPage) {
194 _kln89->DrawText("Time", 2, 0, 3);
195 // TODO - hardwired to UTC at the moment
196 _kln89->DrawText("UTC", 2, 6, 3);
197 string th = fgGetString("/instrumentation/clock/indicated-hour");
198 string tm = fgGetString("/instrumentation/clock/indicated-min");
199 if(th.size() == 1) th = "0" + th;
200 if(tm.size() == 1) tm = "0" + tm;
201 _kln89->DrawText(th + tm, 2, 11, 3);
202 _kln89->DrawText("Depart", 2, 0, 2);
203 _kln89->DrawText(_kln89->_departureTimeString, 2, 11, 2);
204 _kln89->DrawText("ETA", 2, 0, 1);
205 if(_kln89->_departed) {
206 /* Rules of ETA waypoint are:
207 If the active waypoint is part of the active flightplan, then display
208 the ETA to the final (destination) waypoint of the active flightplan.
209 If the active waypoint is not part of the active flightplan, then
210 display the ETA to the active waypoint. */
211 // TODO - implement the above properly - we haven't below!
213 if(fp->waypoints.size()) {
214 wid = fp->waypoints[fp->waypoints.size() - 1]->id;
219 _kln89->DrawText(wid, 2, 4, 1);
220 double tsec = _kln89->GetTimeToWaypoint(wid);
222 _kln89->DrawText("----", 2, 11, 1);
224 int etah = (int)tsec / 3600;
225 int etam = ((int)tsec - etah * 3600) / 60;
226 etah += atoi(fgGetString("/instrumentation/clock/indicated-hour"));
227 etam += atoi(fgGetString("/instrumentation/clock/indicated-min"));
236 int n = snprintf(buf, 6, "%02i%02i", etah, etam);
237 _kln89->DrawText((string)buf, 2, 15-n, 1);
240 _kln89->DrawText("----", 2, 11, 1);
243 _kln89->DrawText("----", 2, 11, 1);
245 _kln89->DrawText("Flight", 2, 0, 0);
246 if(_kln89->_departed) {
247 int eh = (int)_kln89->_elapsedTime / 3600;
248 int em = ((int)_kln89->_elapsedTime - eh * 3600) / 60;
250 int n = snprintf(buf, 6, "%i:%02i", eh, em);
251 _kln89->DrawText((string)buf, 2, 15-n, 0);
253 _kln89->DrawText("-:--", 2, 11, 0);
255 } else { // if(3 == _subPage)
257 // Switch the cursor off if scan-pull is out on this page.
259 if(fgGetBool("/instrumentation/kln89/scan-pull")) { _kln89->_mode = KLN89_MODE_DISP; }
262 // Draw the moving map if valid.
263 // We call the core KLN89 class to do this.
265 if(_kln89->_mapOrientation == 2 && _kln89->_groundSpeed_kts < 2) {
266 // Don't draw it if in track up mode and groundspeed < 2kts, as per real-life unit.
268 _kln89->DrawMap(!_suspendAVS);
272 // Now that the map has been drawn, add the annotation (scale, etc).
274 int scale = KLN89MapScales[_kln89->_mapScaleUnits][_kln89->_mapScaleIndex];
275 string scle_str = GPSitoa(scale);
278 // Draw a background quad to encompass on/off for the first three at 'off' length
279 _kln89->DrawMapQuad(28, 9, 48, 36, true);
280 _kln89->DrawMapText("SUA:", 1, 27, true);
281 if(!(_menuPos == 0 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawSUA ? "on" : "off"), 29, 27, true);
282 if(_menuPos == 0) _kln89->DrawLine(28, 27, 48, 27);
283 _kln89->DrawMapText("VOR:", 1, 18, true);
284 if(!(_menuPos == 1 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawVOR ? "on" : "off"), 29, 18, true);
285 if(_menuPos == 1) _kln89->DrawLine(28, 18, 48, 18);
286 _kln89->DrawMapText("APT:", 1, 9, true);
287 if(!(_menuPos == 2 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawApt ? "on" : "off"), 29, 9, true);
288 if(_menuPos == 2) _kln89->DrawLine(28, 9, 48, 9);
289 _kln89->DrawMapQuad(0, 0, 27, 8, true);
290 if(!(_menuPos == 3 && _kln89->_blink)) {
291 if(_kln89->_mapOrientation == 0) {
292 _kln89->DrawMapText("N", 1, 0, true);
293 _kln89->DrawMapUpArrow(7, 1);
294 } else if(_kln89->_mapOrientation == 1) {
295 _kln89->DrawMapText("DTK", 1, 0, true);
296 _kln89->DrawMapUpArrow(21, 1);
298 // Don't bother with heading up for now!
299 _kln89->DrawMapText("TK", 1, 0, true);
300 _kln89->DrawMapUpArrow(14, 1);
303 if(_menuPos == 3) _kln89->DrawLine(0, 0, 27, 0);
306 if(!_kln89->_blink) {
307 _kln89->DrawMapText("Menu?", 1, 9, true);
309 _kln89->DrawLine(0, 9, 34, 9);
311 _kln89->DrawMapQuad(0, 9, 34, 17, true);
314 _kln89->DrawMapText("Menu?", 1, 9, true);
316 // right-justify the scale when _uLinePos == 3
317 if(!(_uLinePos == 3 && _kln89->_blink)) _kln89->DrawMapText(scle_str, (_uLinePos == 3 ? 29 - (scle_str.size() * 7) : 1), 0, true);
318 if(_uLinePos == 3) _kln89->DrawLine(0, 0, 27, 0);
321 // Just draw the scale
322 _kln89->DrawMapText(scle_str, 1, 0, true);
324 // If the scan-pull knob is out, draw one of the waypoints (if applicable).
325 if(fgGetBool("/instrumentation/kln89/scan-pull")) {
326 if(_kln89->_activeFP->waypoints.size()) {
327 //cout << "Need to draw a waypoint!\n";
328 _kln89->DrawLine(70, 0, 111, 0);
329 if(!_kln89->_blink) {
330 //_kln89->DrawMapQuad(45, 0, 97, 8, true);
332 _scanWpIndex = _kln89->GetActiveWaypointIndex();
335 _kln89->DrawMapText(_kln89->_activeFP->waypoints[_scanWpIndex]->id, 71, 0, true);
341 // Do part of the field 1 update, since NAV 4 is a special case for the last line.
343 _kln89->DrawChar('>', 1, 0, 0);
344 if(crsr && _uLinePos == 1) _kln89->Underline(1, 1, 0, 5);
345 if(!(crsr && _uLinePos == 1 && _kln89->_blink)) {
346 if(_kln89->_obsMode && _nav4DataSnippet == 0) _nav4DataSnippet = 1;
348 switch(_nav4DataSnippet) {
351 _kln89->DrawLabel("DTK", -39, 6);
352 // TODO - check we have an active FP / dtk and draw dashes if not.
354 snprintf(buf0, 4, "%03i", (int)(_kln89->_dtkMag));
355 _kln89->DrawText((string)buf0, 1, 3, 0);
359 _kln89->DrawSpeed(_kln89->_groundSpeed_kts, 1, 5, 0);
363 tsec = _kln89->GetETE();
365 _kln89->DrawText("--:--", 1, 1, 0);
367 int eteh = (int)tsec / 3600;
368 int etem = ((int)tsec - eteh * 3600) / 60;
370 int n = snprintf(buf, 6, "%02i:%02i", eteh, etem);
371 _kln89->DrawText((string)buf, 1, 6-n, 0);
375 // Cross-track correction
376 double x = _kln89->CalcCrossTrackDeviation();
378 _kln89->DrawSpecialChar(3, 1, 5, 0);
380 _kln89->DrawSpecialChar(7, 1, 5, 0);
384 x = fabs(x * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001));
386 n = snprintf(buf3, 6, "%0.2f", x);
387 } else if(x < 100.0) {
388 n = snprintf(buf3, 6, "%0.1f", x);
390 n = snprintf(buf3, 6, "%i", (int)(x+0.5));
392 _kln89->DrawText((string)buf3, 1, 5-n, 0);
398 KLN89Page::Update(dt);
401 // Returns the id string of the selected waypoint on NAV4 if valid, else returns an empty string.
402 string KLN89NavPage::GetNav4WpId() {
404 if(fgGetBool("/instrumentation/kln89/scan-pull")) {
405 if(_kln89->_activeFP->waypoints.size()) {
407 return(_kln89->_activeWaypoint.id);
409 return(_kln89->_activeFP->waypoints[_scanWpIndex]->id);
417 void KLN89NavPage::LooseFocus() {
422 void KLN89NavPage::CrsrPressed() {
423 if(_kln89->_mode == KLN89_MODE_DISP) {
424 // Crsr just switched off
427 // Crsr just switched on
436 void KLN89NavPage::EntPressed() {
437 if(_kln89->_mode == KLN89_MODE_CRSR) {
438 if(_subPage == 3 && _uLinePos == 2 && !_menuActive) {
446 void KLN89NavPage::ClrPressed() {
447 if(_kln89->_mode == KLN89_MODE_CRSR) {
451 if(_cdiFormat > 2) _cdiFormat = 0;
452 } else if(_uLinePos == 2) {
454 if(_vnv > 2) _vnv = 0;
459 _suspendAVS = !_suspendAVS;
461 } else if(_uLinePos == 1) {
463 if(_nav4DataSnippet > 3) _nav4DataSnippet = 0;
468 _suspendAVS = !_suspendAVS;
473 void KLN89NavPage::Knob1Left1() {
474 if(_kln89->_mode == KLN89_MODE_CRSR) {
475 if(!(_subPage == 3 && _menuActive)) {
476 if(_uLinePos > 0) _uLinePos--;
478 if(_menuPos > 0) _menuPos--;
483 void KLN89NavPage::Knob1Right1() {
484 if(_kln89->_mode == KLN89_MODE_CRSR) {
486 if(_uLinePos < 2) _uLinePos++;
487 } else if(_subPage == 2) {
490 // NAV 4 - this is complicated by whether the menu is displayed or not.
492 if(_menuPos < 3) _menuPos++;
494 if(_uLinePos < 3) _uLinePos++;
500 void KLN89NavPage::Knob2Left1() {
501 // If the inner-knob is out on the nav4 page, the only effect is to cycle the displayed waypoint.
502 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) {
503 if(_kln89->_activeFP->waypoints.size()) { // TODO - find out what happens when scan-pull is on on nav4 without an active FP.
504 // It's unlikely that we could get here without _scanWpSet, but theoretically possible, so we need to cover it.
506 _scanWpIndex = _kln89->GetActiveWaypointIndex();
509 if(0 == _scanWpIndex) {
510 _scanWpIndex = _kln89->_activeFP->waypoints.size() - 1;
518 if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
519 KLN89Page::Knob2Left1();
523 if(_uLinePos == 1 && _cdiFormat == 2) {
524 _kln89->CDIFSDIncrease();
526 } else if(_subPage == 3) {
529 _kln89->_drawSUA = !_kln89->_drawSUA;
530 } else if(_menuPos == 1) {
531 _kln89->_drawVOR = !_kln89->_drawVOR;
532 } else if(_menuPos == 2) {
533 _kln89->_drawApt = !_kln89->_drawApt;
535 if(_kln89->_mapOrientation == 0) {
536 // Don't allow heading up for now
537 _kln89->_mapOrientation = 2;
539 _kln89->_mapOrientation--;
541 _kln89->UpdateMapHeading();
543 } else if(_uLinePos == 3) {
545 if(_kln89->_mapScaleIndex == 0) {
546 _kln89->_mapScaleIndex = 20;
548 _kln89->_mapScaleIndex--;
554 void KLN89NavPage::Knob2Right1() {
555 // If the inner-knob is out on the nav4 page, the only effect is to cycle the displayed waypoint.
556 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) {
557 if(_kln89->_activeFP->waypoints.size()) { // TODO - find out what happens when scan-pull is on on nav4 without an active FP.
558 // It's unlikely that we could get here without _scanWpSet, but theoretically possible, so we need to cover it.
560 _scanWpIndex = _kln89->GetActiveWaypointIndex();
564 if(_scanWpIndex > static_cast<int>(_kln89->_activeFP->waypoints.size()) - 1) {
571 if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
572 KLN89Page::Knob2Right1();
576 if(_uLinePos == 1 && _cdiFormat == 2) {
577 _kln89->CDIFSDDecrease();
579 } else if(_subPage == 3) {
582 _kln89->_drawSUA = !_kln89->_drawSUA;
583 } else if(_menuPos == 1) {
584 _kln89->_drawVOR = !_kln89->_drawVOR;
585 } else if(_menuPos == 2) {
586 _kln89->_drawApt = !_kln89->_drawApt;
588 if(_kln89->_mapOrientation >= 2) {
589 // Don't allow heading up for now
590 _kln89->_mapOrientation = 0;
592 _kln89->_mapOrientation++;
594 _kln89->UpdateMapHeading();
596 } else if(_uLinePos == 3) {
598 if(_kln89->_mapScaleIndex == 20) {
599 _kln89->_mapScaleIndex = 0;
601 _kln89->_mapScaleIndex++;