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>
33 KLN89NavPage::KLN89NavPage(KLN89* parent)
38 _posFormat = 0; // Check - should this default to ref from waypoint?
49 KLN89NavPage::~KLN89NavPage() {
52 void KLN89NavPage::Update(double dt) {
53 GPSFlightPlan* fp = _kln89->_activeFP;
54 GPSWaypoint* awp = _kln89->GetActiveWaypoint();
55 // Scan-pull out on nav4 page switches off the cursor
56 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) { _kln89->_mode = KLN89_MODE_DISP; }
57 bool crsr = (_kln89->_mode == KLN89_MODE_CRSR);
58 bool blink = _kln89->_blink;
59 double lat = _kln89->_gpsLat * SG_RADIANS_TO_DEGREES;
60 double lon = _kln89->_gpsLon * SG_RADIANS_TO_DEGREES;
62 if(_subPage != 3) { _scanWpSet = false; }
65 if(_kln89->_navFlagged) {
66 _kln89->DrawText("> F L A G", 2, 0, 2);
67 _kln89->DrawText("DTK --- TK ---", 2, 0, 1);
68 _kln89->DrawText(">--- To --:--", 2, 0, 0);
69 _kln89->DrawSpecialChar(0, 2, 7, 1);
70 _kln89->DrawSpecialChar(0, 2, 15, 1);
71 _kln89->DrawSpecialChar(0, 2, 4, 0);
72 _kln89->DrawSpecialChar(1, 2, 3, 2);
73 _kln89->DrawSpecialChar(1, 2, 4, 2);
74 _kln89->DrawSpecialChar(1, 2, 6, 2);
75 _kln89->DrawSpecialChar(1, 2, 10, 2);
76 _kln89->DrawSpecialChar(1, 2, 12, 2);
77 _kln89->DrawSpecialChar(1, 2, 13, 2);
80 _kln89->DrawDTO(2, 7, 3);
82 if(!(_kln89->_waypointAlert && _kln89->_blink)) {
83 _kln89->DrawSpecialChar(3, 2, 8, 3);
86 _kln89->DrawText(awp->id, 2, 10, 3);
87 if(!_kln89->_dto && !_kln89->_obsMode && !_kln89->_fromWaypoint.id.empty()) {
88 if(_kln89->_fromWaypoint.type != GPS_WP_VIRT) { // Don't draw the virtual waypoint names
89 _kln89->DrawText(_kln89->_fromWaypoint.id, 2, 1, 3);
92 if(!(crsr && blink && _uLinePos == 1)) {
95 } else if(_cdiFormat == 1) {
96 _kln89->DrawText("Fly", 2, 2, 2);
97 double x = _kln89->CalcCrossTrackDeviation();
98 // TODO - check the R/L from sign of x below - I *think* it holds but not sure!
99 // Note also that we're setting Fly R or L based on the aircraft
100 // position only, not the heading. Not sure if this is correct or not.
101 _kln89->DrawText(x < 0.0 ? "R" : "L", 2, 6, 2);
104 x = fabs(x * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001));
106 n = snprintf(buf, 6, "%0.2f", x);
107 } else if(x < 100.0) {
108 n = snprintf(buf, 6, "%0.1f", x);
110 n = snprintf(buf, 6, "%i", (int)(x+0.5));
112 _kln89->DrawText((string)buf, 2, 13-n, 2);
113 _kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "km", 2, 13, 2);
115 _kln89->DrawText("CDI Scale:", 2, 1, 2);
116 double d = _kln89->_cdiScales[_kln89->_currentCdiScaleIndex] * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001);
120 snprintf(buf, 4, "%2.1f", d);
123 snprintf(buf, 5, "%2.2f", d);
124 // trim the leading zero
126 s = s.substr(1, s.size() - 1);
128 _kln89->DrawText(s, 2, 11, 2);
129 _kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "km", 2, 14, 2);
132 _kln89->DrawChar('>', 2, 0, 2);
133 _kln89->DrawChar('>', 2, 0, 0);
135 if(_uLinePos == 1) _kln89->Underline(2, 1, 2, 15);
136 else if(_uLinePos == 2) _kln89->Underline(2, 1, 0, 9);
138 // Desired and actual magnetic track
139 if(!_kln89->_obsMode) {
140 _kln89->DrawText("DTK", 2, 0, 1);
141 _kln89->DrawHeading((int)_kln89->_dtkMag, 2, 7, 1);
143 _kln89->DrawText("TK", 2, 9, 1);
144 if(_kln89->_groundSpeed_ms > 3) { // about 6 knots, don't know exactly what value to disable track
145 // The trouble with relying on FG gps's track value is we don't know when it's valid.
146 _kln89->DrawHeading((int)_kln89->_magTrackDeg, 2, 15, 1);
148 _kln89->DrawText("---", 2, 12, 1);
149 _kln89->DrawSpecialChar(0, 2, 15, 1);
152 // Radial to/from active waypoint.
153 // TODO - Not sure if this either is or should be true or mag!!!!!!!
154 if(!(crsr && blink && _uLinePos == 2)) {
156 _kln89->DrawHeading((int)_kln89->GetHeadingToActiveWaypoint(), 2, 4, 0);
157 _kln89->DrawText("To", 2, 5, 0);
158 } else if(1 == _vnv) {
159 _kln89->DrawHeading((int)_kln89->GetHeadingFromActiveWaypoint(), 2, 4, 0);
160 _kln89->DrawText("Fr", 2, 5, 0);
162 _kln89->DrawText("Vnv Off", 2, 1, 0);
165 // It seems that the floating point groundspeed must be at least 30kt
166 // for an ETA to be calculated. Note that this means that (integer) 30kt
167 // can appear in the frame 1 display both with and without an ETA displayed.
168 // TODO - need to switch off track (and heading bug change) based on instantaneous speed as well
169 // since the long gps lag filter means we can still be displaying when stopped on ground.
170 if(_kln89->_groundSpeed_kts > 30.0) {
171 // Assuming eta display is always hh:mm
172 // Does it ever switch to seconds when close?
173 if(_kln89->_eta / 3600.0 > 100.0) {
174 // More that 100 hours ! - Doesn't fit.
175 _kln89->DrawText("--:--", 2, 11, 0);
177 _kln89->DrawTime(_kln89->_eta, 2, 15, 0);
180 _kln89->DrawText("--:--", 2, 11, 0);
183 } else if(1 == _subPage) {
185 _kln89->DrawChar('>', 2, 1, 3);
186 if(!(crsr && blink && _uLinePos == 1)) _kln89->DrawText("PRESENT POSN", 2, 2, 3);
187 if(crsr && _uLinePos == 1) _kln89->Underline(2, 2, 3, 12);
188 if(0 == _posFormat) {
190 _kln89->DrawLatitude(lat, 2, 3, 1);
191 _kln89->DrawLongitude(lon, 2, 3, 0);
193 // Ref from wp - defaults to nearest vor (and resets to default when page left and re-entered).
195 } else if(2 == _subPage) {
196 _kln89->DrawText("Time", 2, 0, 3);
197 // TODO - hardwired to UTC at the moment
198 _kln89->DrawText("UTC", 2, 6, 3);
199 string th = fgGetString("/instrumentation/clock/indicated-hour");
200 string tm = fgGetString("/instrumentation/clock/indicated-min");
201 if(th.size() == 1) th = "0" + th;
202 if(tm.size() == 1) tm = "0" + tm;
203 _kln89->DrawText(th + tm, 2, 11, 3);
204 _kln89->DrawText("Depart", 2, 0, 2);
205 _kln89->DrawText(_kln89->_departureTimeString, 2, 11, 2);
206 _kln89->DrawText("ETA", 2, 0, 1);
207 if(_kln89->_departed) {
208 /* Rules of ETA waypoint are:
209 If the active waypoint is part of the active flightplan, then display
210 the ETA to the final (destination) waypoint of the active flightplan.
211 If the active waypoint is not part of the active flightplan, then
212 display the ETA to the active waypoint. */
213 // TODO - implement the above properly - we haven't below!
215 if(fp->waypoints.size()) {
216 wid = fp->waypoints[fp->waypoints.size() - 1]->id;
221 _kln89->DrawText(wid, 2, 4, 1);
222 double tsec = _kln89->GetTimeToWaypoint(wid);
224 _kln89->DrawText("----", 2, 11, 1);
226 int etah = (int)tsec / 3600;
227 int etam = ((int)tsec - etah * 3600) / 60;
228 etah += atoi(fgGetString("/instrumentation/clock/indicated-hour"));
229 etam += atoi(fgGetString("/instrumentation/clock/indicated-min"));
238 int n = snprintf(buf, 6, "%02i%02i", etah, etam);
239 _kln89->DrawText((string)buf, 2, 15-n, 1);
242 _kln89->DrawText("----", 2, 11, 1);
245 _kln89->DrawText("----", 2, 11, 1);
247 _kln89->DrawText("Flight", 2, 0, 0);
248 if(_kln89->_departed) {
249 int eh = (int)_kln89->_elapsedTime / 3600;
250 int em = ((int)_kln89->_elapsedTime - eh * 3600) / 60;
252 int n = snprintf(buf, 6, "%i:%02i", eh, em);
253 _kln89->DrawText((string)buf, 2, 15-n, 0);
255 _kln89->DrawText("-:--", 2, 11, 0);
257 } else { // if(3 == _subPage)
259 // Switch the cursor off if scan-pull is out on this page.
261 if(fgGetBool("/instrumentation/kln89/scan-pull")) { _kln89->_mode = KLN89_MODE_DISP; }
264 // Draw the moving map if valid.
265 // We call the core KLN89 class to do this.
267 if(_kln89->_mapOrientation == 2 && _kln89->_groundSpeed_kts < 2) {
268 // Don't draw it if in track up mode and groundspeed < 2kts, as per real-life unit.
270 _kln89->DrawMap(!_suspendAVS);
274 // Now that the map has been drawn, add the annotation (scale, etc).
276 int scale = KLN89MapScales[_kln89->_mapScaleUnits][_kln89->_mapScaleIndex];
277 string scle_str = GPSitoa(scale);
280 // Draw a background quad to encompass on/off for the first three at 'off' length
281 _kln89->DrawMapQuad(28, 9, 48, 36, true);
282 _kln89->DrawMapText("SUA:", 1, 27, true);
283 if(!(_menuPos == 0 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawSUA ? "on" : "off"), 29, 27, true);
284 if(_menuPos == 0) _kln89->DrawLine(28, 27, 48, 27);
285 _kln89->DrawMapText("VOR:", 1, 18, true);
286 if(!(_menuPos == 1 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawVOR ? "on" : "off"), 29, 18, true);
287 if(_menuPos == 1) _kln89->DrawLine(28, 18, 48, 18);
288 _kln89->DrawMapText("APT:", 1, 9, true);
289 if(!(_menuPos == 2 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawApt ? "on" : "off"), 29, 9, true);
290 if(_menuPos == 2) _kln89->DrawLine(28, 9, 48, 9);
291 _kln89->DrawMapQuad(0, 0, 27, 8, true);
292 if(!(_menuPos == 3 && _kln89->_blink)) {
293 if(_kln89->_mapOrientation == 0) {
294 _kln89->DrawMapText("N", 1, 0, true);
295 _kln89->DrawMapUpArrow(7, 1);
296 } else if(_kln89->_mapOrientation == 1) {
297 _kln89->DrawMapText("DTK", 1, 0, true);
298 _kln89->DrawMapUpArrow(21, 1);
300 // Don't bother with heading up for now!
301 _kln89->DrawMapText("TK", 1, 0, true);
302 _kln89->DrawMapUpArrow(14, 1);
305 if(_menuPos == 3) _kln89->DrawLine(0, 0, 27, 0);
308 if(!_kln89->_blink) {
309 _kln89->DrawMapText("Menu?", 1, 9, true);
311 _kln89->DrawLine(0, 9, 34, 9);
313 _kln89->DrawMapQuad(0, 9, 34, 17, true);
316 _kln89->DrawMapText("Menu?", 1, 9, true);
318 // right-justify the scale when _uLinePos == 3
319 if(!(_uLinePos == 3 && _kln89->_blink)) _kln89->DrawMapText(scle_str, (_uLinePos == 3 ? 29 - (scle_str.size() * 7) : 1), 0, true);
320 if(_uLinePos == 3) _kln89->DrawLine(0, 0, 27, 0);
323 // Just draw the scale
324 _kln89->DrawMapText(scle_str, 1, 0, true);
326 // If the scan-pull knob is out, draw one of the waypoints (if applicable).
327 if(fgGetBool("/instrumentation/kln89/scan-pull")) {
328 if(_kln89->_activeFP->waypoints.size()) {
329 //cout << "Need to draw a waypoint!\n";
330 _kln89->DrawLine(70, 0, 111, 0);
331 if(!_kln89->_blink) {
332 //_kln89->DrawMapQuad(45, 0, 97, 8, true);
334 _scanWpIndex = _kln89->GetActiveWaypointIndex();
337 _kln89->DrawMapText(_kln89->_activeFP->waypoints[_scanWpIndex]->id, 71, 0, true);
343 // Do part of the field 1 update, since NAV 4 is a special case for the last line.
345 _kln89->DrawChar('>', 1, 0, 0);
346 if(crsr && _uLinePos == 1) _kln89->Underline(1, 1, 0, 5);
347 if(!(crsr && _uLinePos == 1 && _kln89->_blink)) {
348 if(_kln89->_obsMode && _nav4DataSnippet == 0) _nav4DataSnippet = 1;
350 switch(_nav4DataSnippet) {
353 _kln89->DrawLabel("DTK", -39, 6);
354 // TODO - check we have an active FP / dtk and draw dashes if not.
356 snprintf(buf0, 4, "%03i", (int)(_kln89->_dtkMag));
357 _kln89->DrawText((string)buf0, 1, 3, 0);
361 _kln89->DrawSpeed(_kln89->_groundSpeed_kts, 1, 5, 0);
365 tsec = _kln89->GetETE();
367 _kln89->DrawText("--:--", 1, 1, 0);
369 int eteh = (int)tsec / 3600;
370 int etem = ((int)tsec - eteh * 3600) / 60;
372 int n = snprintf(buf, 6, "%02i:%02i", eteh, etem);
373 _kln89->DrawText((string)buf, 1, 6-n, 0);
377 // Cross-track correction
378 double x = _kln89->CalcCrossTrackDeviation();
380 _kln89->DrawSpecialChar(3, 1, 5, 0);
382 _kln89->DrawSpecialChar(7, 1, 5, 0);
386 x = fabs(x * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001));
388 n = snprintf(buf3, 6, "%0.2f", x);
389 } else if(x < 100.0) {
390 n = snprintf(buf3, 6, "%0.1f", x);
392 n = snprintf(buf3, 6, "%i", (int)(x+0.5));
394 _kln89->DrawText((string)buf3, 1, 5-n, 0);
400 KLN89Page::Update(dt);
403 // Returns the id string of the selected waypoint on NAV4 if valid, else returns an empty string.
404 string KLN89NavPage::GetNav4WpId() {
406 if(fgGetBool("/instrumentation/kln89/scan-pull")) {
407 if(_kln89->_activeFP->waypoints.size()) {
409 return(_kln89->_activeWaypoint.id);
411 return(_kln89->_activeFP->waypoints[_scanWpIndex]->id);
419 void KLN89NavPage::LooseFocus() {
424 void KLN89NavPage::CrsrPressed() {
425 if(_kln89->_mode == KLN89_MODE_DISP) {
426 // Crsr just switched off
429 // Crsr just switched on
438 void KLN89NavPage::EntPressed() {
439 if(_kln89->_mode == KLN89_MODE_CRSR) {
440 if(_subPage == 3 && _uLinePos == 2 && !_menuActive) {
448 void KLN89NavPage::ClrPressed() {
449 if(_kln89->_mode == KLN89_MODE_CRSR) {
453 if(_cdiFormat > 2) _cdiFormat = 0;
454 } else if(_uLinePos == 2) {
456 if(_vnv > 2) _vnv = 0;
461 _suspendAVS = !_suspendAVS;
463 } else if(_uLinePos == 1) {
465 if(_nav4DataSnippet > 3) _nav4DataSnippet = 0;
470 _suspendAVS = !_suspendAVS;
475 void KLN89NavPage::Knob1Left1() {
476 if(_kln89->_mode == KLN89_MODE_CRSR) {
477 if(!(_subPage == 3 && _menuActive)) {
478 if(_uLinePos > 0) _uLinePos--;
480 if(_menuPos > 0) _menuPos--;
485 void KLN89NavPage::Knob1Right1() {
486 if(_kln89->_mode == KLN89_MODE_CRSR) {
488 if(_uLinePos < 2) _uLinePos++;
489 } else if(_subPage == 2) {
492 // NAV 4 - this is complicated by whether the menu is displayed or not.
494 if(_menuPos < 3) _menuPos++;
496 if(_uLinePos < 3) _uLinePos++;
502 void KLN89NavPage::Knob2Left1() {
503 // If the inner-knob is out on the nav4 page, the only effect is to cycle the displayed waypoint.
504 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) {
505 if(_kln89->_activeFP->waypoints.size()) { // TODO - find out what happens when scan-pull is on on nav4 without an active FP.
506 // It's unlikely that we could get here without _scanWpSet, but theoretically possible, so we need to cover it.
508 _scanWpIndex = _kln89->GetActiveWaypointIndex();
511 if(0 == _scanWpIndex) {
512 _scanWpIndex = _kln89->_activeFP->waypoints.size() - 1;
520 if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
521 KLN89Page::Knob2Left1();
525 if(_uLinePos == 1 && _cdiFormat == 2) {
526 _kln89->CDIFSDIncrease();
528 } else if(_subPage == 3) {
531 _kln89->_drawSUA = !_kln89->_drawSUA;
532 } else if(_menuPos == 1) {
533 _kln89->_drawVOR = !_kln89->_drawVOR;
534 } else if(_menuPos == 2) {
535 _kln89->_drawApt = !_kln89->_drawApt;
537 if(_kln89->_mapOrientation == 0) {
538 // Don't allow heading up for now
539 _kln89->_mapOrientation = 2;
541 _kln89->_mapOrientation--;
543 _kln89->UpdateMapHeading();
545 } else if(_uLinePos == 3) {
547 if(_kln89->_mapScaleIndex == 0) {
548 _kln89->_mapScaleIndex = 20;
550 _kln89->_mapScaleIndex--;
556 void KLN89NavPage::Knob2Right1() {
557 // If the inner-knob is out on the nav4 page, the only effect is to cycle the displayed waypoint.
558 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) {
559 if(_kln89->_activeFP->waypoints.size()) { // TODO - find out what happens when scan-pull is on on nav4 without an active FP.
560 // It's unlikely that we could get here without _scanWpSet, but theoretically possible, so we need to cover it.
562 _scanWpIndex = _kln89->GetActiveWaypointIndex();
566 if(_scanWpIndex > static_cast<int>(_kln89->_activeFP->waypoints.size()) - 1) {
573 if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
574 KLN89Page::Knob2Right1();
578 if(_uLinePos == 1 && _cdiFormat == 2) {
579 _kln89->CDIFSDDecrease();
581 } else if(_subPage == 3) {
584 _kln89->_drawSUA = !_kln89->_drawSUA;
585 } else if(_menuPos == 1) {
586 _kln89->_drawVOR = !_kln89->_drawVOR;
587 } else if(_menuPos == 2) {
588 _kln89->_drawApt = !_kln89->_drawApt;
590 if(_kln89->_mapOrientation >= 2) {
591 // Don't allow heading up for now
592 _kln89->_mapOrientation = 0;
594 _kln89->_mapOrientation++;
596 _kln89->UpdateMapHeading();
598 } else if(_uLinePos == 3) {
600 if(_kln89->_mapScaleIndex == 20) {
601 _kln89->_mapScaleIndex = 0;
603 _kln89->_mapScaleIndex++;