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.
30 #include "kln89_page_nav.hxx"
31 #include <Main/fg_props.hxx>
35 KLN89NavPage::KLN89NavPage(KLN89* parent)
40 _posFormat = 0; // Check - should this default to ref from waypoint?
51 KLN89NavPage::~KLN89NavPage() {
54 void KLN89NavPage::Update(double dt) {
55 GPSFlightPlan* fp = _kln89->_activeFP;
56 GPSWaypoint* awp = _kln89->GetActiveWaypoint();
57 // Scan-pull out on nav4 page switches off the cursor
58 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) { _kln89->_mode = KLN89_MODE_DISP; }
59 bool crsr = (_kln89->_mode == KLN89_MODE_CRSR);
60 bool blink = _kln89->_blink;
61 double lat = _kln89->_gpsLat * SG_RADIANS_TO_DEGREES;
62 double lon = _kln89->_gpsLon * SG_RADIANS_TO_DEGREES;
64 if(_subPage != 3) { _scanWpSet = false; }
67 if(_kln89->_navFlagged) {
68 _kln89->DrawText("> F L A G", 2, 0, 2);
69 _kln89->DrawText("DTK --- TK ---", 2, 0, 1);
70 _kln89->DrawText(">--- To --:--", 2, 0, 0);
71 _kln89->DrawSpecialChar(0, 2, 7, 1);
72 _kln89->DrawSpecialChar(0, 2, 15, 1);
73 _kln89->DrawSpecialChar(0, 2, 4, 0);
74 _kln89->DrawSpecialChar(1, 2, 3, 2);
75 _kln89->DrawSpecialChar(1, 2, 4, 2);
76 _kln89->DrawSpecialChar(1, 2, 6, 2);
77 _kln89->DrawSpecialChar(1, 2, 10, 2);
78 _kln89->DrawSpecialChar(1, 2, 12, 2);
79 _kln89->DrawSpecialChar(1, 2, 13, 2);
82 _kln89->DrawDTO(2, 7, 3);
84 if(!(_kln89->_waypointAlert && _kln89->_blink)) {
85 _kln89->DrawSpecialChar(3, 2, 8, 3);
88 _kln89->DrawText(awp->id, 2, 10, 3);
89 if(!_kln89->_dto && !_kln89->_obsMode && !_kln89->_fromWaypoint.id.empty()) {
90 if(_kln89->_fromWaypoint.type != GPS_WP_VIRT) { // Don't draw the virtual waypoint names
91 _kln89->DrawText(_kln89->_fromWaypoint.id, 2, 1, 3);
94 if(!(crsr && blink && _uLinePos == 1)) {
97 } else if(_cdiFormat == 1) {
98 _kln89->DrawText("Fly", 2, 2, 2);
99 double x = _kln89->CalcCrossTrackDeviation();
100 // TODO - check the R/L from sign of x below - I *think* it holds but not sure!
101 // Note also that we're setting Fly R or L based on the aircraft
102 // position only, not the heading. Not sure if this is correct or not.
103 _kln89->DrawText(x < 0.0 ? "R" : "L", 2, 6, 2);
106 x = fabs(x * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001));
108 n = snprintf(buf, 6, "%0.2f", x);
109 } else if(x < 100.0) {
110 n = snprintf(buf, 6, "%0.1f", x);
112 n = snprintf(buf, 6, "%i", (int)(x+0.5));
114 _kln89->DrawText((string)buf, 2, 13-n, 2);
115 _kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "km", 2, 13, 2);
117 _kln89->DrawText("CDI Scale:", 2, 1, 2);
118 double d = _kln89->_cdiScales[_kln89->_currentCdiScaleIndex] * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001);
122 snprintf(buf, 4, "%2.1f", d);
125 snprintf(buf, 5, "%2.2f", d);
126 // trim the leading zero
128 s = s.substr(1, s.size() - 1);
130 _kln89->DrawText(s, 2, 11, 2);
131 _kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "km", 2, 14, 2);
134 _kln89->DrawChar('>', 2, 0, 2);
135 _kln89->DrawChar('>', 2, 0, 0);
137 if(_uLinePos == 1) _kln89->Underline(2, 1, 2, 15);
138 else if(_uLinePos == 2) _kln89->Underline(2, 1, 0, 9);
140 // Desired and actual magnetic track
141 if(!_kln89->_obsMode) {
142 _kln89->DrawText("DTK", 2, 0, 1);
143 _kln89->DrawHeading((int)_kln89->_dtkMag, 2, 7, 1);
145 _kln89->DrawText("TK", 2, 9, 1);
146 if(_kln89->_groundSpeed_ms > 3) { // about 6 knots, don't know exactly what value to disable track
147 // The trouble with relying on FG gps's track value is we don't know when it's valid.
148 _kln89->DrawHeading((int)_kln89->_magTrackDeg, 2, 15, 1);
150 _kln89->DrawText("---", 2, 12, 1);
151 _kln89->DrawSpecialChar(0, 2, 15, 1);
154 // Radial to/from active waypoint.
155 // TODO - Not sure if this either is or should be true or mag!!!!!!!
156 if(!(crsr && blink && _uLinePos == 2)) {
158 _kln89->DrawHeading((int)_kln89->GetHeadingToActiveWaypoint(), 2, 4, 0);
159 _kln89->DrawText("To", 2, 5, 0);
160 } else if(1 == _vnv) {
161 _kln89->DrawHeading((int)_kln89->GetHeadingFromActiveWaypoint(), 2, 4, 0);
162 _kln89->DrawText("Fr", 2, 5, 0);
164 _kln89->DrawText("Vnv Off", 2, 1, 0);
167 // It seems that the floating point groundspeed must be at least 30kt
168 // for an ETA to be calculated. Note that this means that (integer) 30kt
169 // can appear in the frame 1 display both with and without an ETA displayed.
170 // TODO - need to switch off track (and heading bug change) based on instantaneous speed as well
171 // since the long gps lag filter means we can still be displaying when stopped on ground.
172 if(_kln89->_groundSpeed_kts > 30.0) {
173 // Assuming eta display is always hh:mm
174 // Does it ever switch to seconds when close?
175 if(_kln89->_eta / 3600.0 > 100.0) {
176 // More that 100 hours ! - Doesn't fit.
177 _kln89->DrawText("--:--", 2, 11, 0);
179 _kln89->DrawTime(_kln89->_eta, 2, 15, 0);
182 _kln89->DrawText("--:--", 2, 11, 0);
185 } else if(1 == _subPage) {
187 _kln89->DrawChar('>', 2, 1, 3);
188 if(!(crsr && blink && _uLinePos == 1)) _kln89->DrawText("PRESENT POSN", 2, 2, 3);
189 if(crsr && _uLinePos == 1) _kln89->Underline(2, 2, 3, 12);
190 if(0 == _posFormat) {
192 _kln89->DrawLatitude(lat, 2, 3, 1);
193 _kln89->DrawLongitude(lon, 2, 3, 0);
195 // Ref from wp - defaults to nearest vor (and resets to default when page left and re-entered).
197 } else if(2 == _subPage) {
198 _kln89->DrawText("Time", 2, 0, 3);
199 // TODO - hardwired to UTC at the moment
200 _kln89->DrawText("UTC", 2, 6, 3);
201 string th = fgGetString("/instrumentation/clock/indicated-hour");
202 string tm = fgGetString("/instrumentation/clock/indicated-min");
203 if(th.size() == 1) th = "0" + th;
204 if(tm.size() == 1) tm = "0" + tm;
205 _kln89->DrawText(th + tm, 2, 11, 3);
206 _kln89->DrawText("Depart", 2, 0, 2);
207 _kln89->DrawText(_kln89->_departureTimeString, 2, 11, 2);
208 _kln89->DrawText("ETA", 2, 0, 1);
209 if(_kln89->_departed) {
210 /* Rules of ETA waypoint are:
211 If the active waypoint is part of the active flightplan, then display
212 the ETA to the final (destination) waypoint of the active flightplan.
213 If the active waypoint is not part of the active flightplan, then
214 display the ETA to the active waypoint. */
215 // TODO - implement the above properly - we haven't below!
217 if(fp->waypoints.size()) {
218 wid = fp->waypoints[fp->waypoints.size() - 1]->id;
223 _kln89->DrawText(wid, 2, 4, 1);
224 double tsec = _kln89->GetTimeToWaypoint(wid);
226 _kln89->DrawText("----", 2, 11, 1);
228 int etah = (int)tsec / 3600;
229 int etam = ((int)tsec - etah * 3600) / 60;
230 etah += atoi(fgGetString("/instrumentation/clock/indicated-hour"));
231 etam += atoi(fgGetString("/instrumentation/clock/indicated-min"));
240 int n = snprintf(buf, 6, "%02i%02i", etah, etam);
241 _kln89->DrawText((string)buf, 2, 15-n, 1);
244 _kln89->DrawText("----", 2, 11, 1);
247 _kln89->DrawText("----", 2, 11, 1);
249 _kln89->DrawText("Flight", 2, 0, 0);
250 if(_kln89->_departed) {
251 int eh = (int)_kln89->_elapsedTime / 3600;
252 int em = ((int)_kln89->_elapsedTime - eh * 3600) / 60;
254 int n = snprintf(buf, 6, "%i:%02i", eh, em);
255 _kln89->DrawText((string)buf, 2, 15-n, 0);
257 _kln89->DrawText("-:--", 2, 11, 0);
259 } else { // if(3 == _subPage)
261 // Switch the cursor off if scan-pull is out on this page.
263 if(fgGetBool("/instrumentation/kln89/scan-pull")) { _kln89->_mode = KLN89_MODE_DISP; }
266 // Draw the moving map if valid.
267 // We call the core KLN89 class to do this.
269 if(_kln89->_mapOrientation == 2 && _kln89->_groundSpeed_kts < 2) {
270 // Don't draw it if in track up mode and groundspeed < 2kts, as per real-life unit.
272 _kln89->DrawMap(!_suspendAVS);
276 // Now that the map has been drawn, add the annotation (scale, etc).
278 int scale = KLN89MapScales[_kln89->_mapScaleUnits][_kln89->_mapScaleIndex];
279 string scle_str = GPSitoa(scale);
282 // Draw a background quad to encompass on/off for the first three at 'off' length
283 _kln89->DrawMapQuad(28, 9, 48, 36, true);
284 _kln89->DrawMapText("SUA:", 1, 27, true);
285 if(!(_menuPos == 0 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawSUA ? "on" : "off"), 29, 27, true);
286 if(_menuPos == 0) _kln89->DrawLine(28, 27, 48, 27);
287 _kln89->DrawMapText("VOR:", 1, 18, true);
288 if(!(_menuPos == 1 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawVOR ? "on" : "off"), 29, 18, true);
289 if(_menuPos == 1) _kln89->DrawLine(28, 18, 48, 18);
290 _kln89->DrawMapText("APT:", 1, 9, true);
291 if(!(_menuPos == 2 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawApt ? "on" : "off"), 29, 9, true);
292 if(_menuPos == 2) _kln89->DrawLine(28, 9, 48, 9);
293 _kln89->DrawMapQuad(0, 0, 27, 8, true);
294 if(!(_menuPos == 3 && _kln89->_blink)) {
295 if(_kln89->_mapOrientation == 0) {
296 _kln89->DrawMapText("N", 1, 0, true);
297 _kln89->DrawMapUpArrow(7, 1);
298 } else if(_kln89->_mapOrientation == 1) {
299 _kln89->DrawMapText("DTK", 1, 0, true);
300 _kln89->DrawMapUpArrow(21, 1);
302 // Don't bother with heading up for now!
303 _kln89->DrawMapText("TK", 1, 0, true);
304 _kln89->DrawMapUpArrow(14, 1);
307 if(_menuPos == 3) _kln89->DrawLine(0, 0, 27, 0);
310 if(!_kln89->_blink) {
311 _kln89->DrawMapText("Menu?", 1, 9, true);
313 _kln89->DrawLine(0, 9, 34, 9);
315 _kln89->DrawMapQuad(0, 9, 34, 17, true);
318 _kln89->DrawMapText("Menu?", 1, 9, true);
320 // right-justify the scale when _uLinePos == 3
321 if(!(_uLinePos == 3 && _kln89->_blink)) _kln89->DrawMapText(scle_str, (_uLinePos == 3 ? 29 - (scle_str.size() * 7) : 1), 0, true);
322 if(_uLinePos == 3) _kln89->DrawLine(0, 0, 27, 0);
325 // Just draw the scale
326 _kln89->DrawMapText(scle_str, 1, 0, true);
328 // If the scan-pull knob is out, draw one of the waypoints (if applicable).
329 if(fgGetBool("/instrumentation/kln89/scan-pull")) {
330 if(_kln89->_activeFP->waypoints.size()) {
331 //cout << "Need to draw a waypoint!\n";
332 _kln89->DrawLine(70, 0, 111, 0);
333 if(!_kln89->_blink) {
334 //_kln89->DrawMapQuad(45, 0, 97, 8, true);
336 _scanWpIndex = _kln89->GetActiveWaypointIndex();
339 _kln89->DrawMapText(_kln89->_activeFP->waypoints[_scanWpIndex]->id, 71, 0, true);
345 // Do part of the field 1 update, since NAV 4 is a special case for the last line.
347 _kln89->DrawChar('>', 1, 0, 0);
348 if(crsr && _uLinePos == 1) _kln89->Underline(1, 1, 0, 5);
349 if(!(crsr && _uLinePos == 1 && _kln89->_blink)) {
350 if(_kln89->_obsMode && _nav4DataSnippet == 0) _nav4DataSnippet = 1;
352 switch(_nav4DataSnippet) {
355 _kln89->DrawLabel("DTK", -39, 6);
356 // TODO - check we have an active FP / dtk and draw dashes if not.
358 snprintf(buf0, 4, "%03i", (int)(_kln89->_dtkMag));
359 _kln89->DrawText((string)buf0, 1, 3, 0);
363 _kln89->DrawSpeed(_kln89->_groundSpeed_kts, 1, 5, 0);
367 tsec = _kln89->GetETE();
369 _kln89->DrawText("--:--", 1, 1, 0);
371 int eteh = (int)tsec / 3600;
372 int etem = ((int)tsec - eteh * 3600) / 60;
374 int n = snprintf(buf, 6, "%02i:%02i", eteh, etem);
375 _kln89->DrawText((string)buf, 1, 6-n, 0);
379 // Cross-track correction
380 double x = _kln89->CalcCrossTrackDeviation();
382 _kln89->DrawSpecialChar(3, 1, 5, 0);
384 _kln89->DrawSpecialChar(7, 1, 5, 0);
388 x = fabs(x * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001));
390 n = snprintf(buf3, 6, "%0.2f", x);
391 } else if(x < 100.0) {
392 n = snprintf(buf3, 6, "%0.1f", x);
394 n = snprintf(buf3, 6, "%i", (int)(x+0.5));
396 _kln89->DrawText((string)buf3, 1, 5-n, 0);
402 KLN89Page::Update(dt);
405 // Returns the id string of the selected waypoint on NAV4 if valid, else returns an empty string.
406 string KLN89NavPage::GetNav4WpId() {
408 if(fgGetBool("/instrumentation/kln89/scan-pull")) {
409 if(_kln89->_activeFP->waypoints.size()) {
411 return(_kln89->_activeWaypoint.id);
413 return(_kln89->_activeFP->waypoints[_scanWpIndex]->id);
421 void KLN89NavPage::LooseFocus() {
426 void KLN89NavPage::CrsrPressed() {
427 if(_kln89->_mode == KLN89_MODE_DISP) {
428 // Crsr just switched off
431 // Crsr just switched on
440 void KLN89NavPage::EntPressed() {
441 if(_kln89->_mode == KLN89_MODE_CRSR) {
442 if(_subPage == 3 && _uLinePos == 2 && !_menuActive) {
450 void KLN89NavPage::ClrPressed() {
451 if(_kln89->_mode == KLN89_MODE_CRSR) {
455 if(_cdiFormat > 2) _cdiFormat = 0;
456 } else if(_uLinePos == 2) {
458 if(_vnv > 2) _vnv = 0;
463 _suspendAVS = !_suspendAVS;
465 } else if(_uLinePos == 1) {
467 if(_nav4DataSnippet > 3) _nav4DataSnippet = 0;
472 _suspendAVS = !_suspendAVS;
477 void KLN89NavPage::Knob1Left1() {
478 if(_kln89->_mode == KLN89_MODE_CRSR) {
479 if(!(_subPage == 3 && _menuActive)) {
480 if(_uLinePos > 0) _uLinePos--;
482 if(_menuPos > 0) _menuPos--;
487 void KLN89NavPage::Knob1Right1() {
488 if(_kln89->_mode == KLN89_MODE_CRSR) {
490 if(_uLinePos < 2) _uLinePos++;
491 } else if(_subPage == 2) {
494 // NAV 4 - this is complicated by whether the menu is displayed or not.
496 if(_menuPos < 3) _menuPos++;
498 if(_uLinePos < 3) _uLinePos++;
504 void KLN89NavPage::Knob2Left1() {
505 // If the inner-knob is out on the nav4 page, the only effect is to cycle the displayed waypoint.
506 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) {
507 if(_kln89->_activeFP->waypoints.size()) { // TODO - find out what happens when scan-pull is on on nav4 without an active FP.
508 // It's unlikely that we could get here without _scanWpSet, but theoretically possible, so we need to cover it.
510 _scanWpIndex = _kln89->GetActiveWaypointIndex();
513 if(0 == _scanWpIndex) {
514 _scanWpIndex = _kln89->_activeFP->waypoints.size() - 1;
522 if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
523 KLN89Page::Knob2Left1();
527 if(_uLinePos == 1 && _cdiFormat == 2) {
528 _kln89->CDIFSDIncrease();
530 } else if(_subPage == 3) {
533 _kln89->_drawSUA = !_kln89->_drawSUA;
534 } else if(_menuPos == 1) {
535 _kln89->_drawVOR = !_kln89->_drawVOR;
536 } else if(_menuPos == 2) {
537 _kln89->_drawApt = !_kln89->_drawApt;
539 if(_kln89->_mapOrientation == 0) {
540 // Don't allow heading up for now
541 _kln89->_mapOrientation = 2;
543 _kln89->_mapOrientation--;
545 _kln89->UpdateMapHeading();
547 } else if(_uLinePos == 3) {
549 if(_kln89->_mapScaleIndex == 0) {
550 _kln89->_mapScaleIndex = 20;
552 _kln89->_mapScaleIndex--;
558 void KLN89NavPage::Knob2Right1() {
559 // If the inner-knob is out on the nav4 page, the only effect is to cycle the displayed waypoint.
560 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) {
561 if(_kln89->_activeFP->waypoints.size()) { // TODO - find out what happens when scan-pull is on on nav4 without an active FP.
562 // It's unlikely that we could get here without _scanWpSet, but theoretically possible, so we need to cover it.
564 _scanWpIndex = _kln89->GetActiveWaypointIndex();
568 if(_scanWpIndex > static_cast<int>(_kln89->_activeFP->waypoints.size()) - 1) {
575 if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
576 KLN89Page::Knob2Right1();
580 if(_uLinePos == 1 && _cdiFormat == 2) {
581 _kln89->CDIFSDDecrease();
583 } else if(_subPage == 3) {
586 _kln89->_drawSUA = !_kln89->_drawSUA;
587 } else if(_menuPos == 1) {
588 _kln89->_drawVOR = !_kln89->_drawVOR;
589 } else if(_menuPos == 2) {
590 _kln89->_drawApt = !_kln89->_drawApt;
592 if(_kln89->_mapOrientation >= 2) {
593 // Don't allow heading up for now
594 _kln89->_mapOrientation = 0;
596 _kln89->_mapOrientation++;
598 _kln89->UpdateMapHeading();
600 } else if(_uLinePos == 3) {
602 if(_kln89->_mapScaleIndex == 20) {
603 _kln89->_mapScaleIndex = 0;
605 _kln89->_mapScaleIndex++;