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.
31 #include "kln89_page_nav.hxx"
32 #include <Main/fg_props.hxx>
36 KLN89NavPage::KLN89NavPage(KLN89* parent)
41 _posFormat = 0; // Check - should this default to ref from waypoint?
52 KLN89NavPage::~KLN89NavPage() {
55 void KLN89NavPage::Update(double dt) {
56 GPSFlightPlan* fp = _kln89->_activeFP;
57 GPSWaypoint* awp = _kln89->GetActiveWaypoint();
58 // Scan-pull out on nav4 page switches off the cursor
59 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) { _kln89->_mode = KLN89_MODE_DISP; }
60 bool crsr = (_kln89->_mode == KLN89_MODE_CRSR);
61 bool blink = _kln89->_blink;
62 double lat = _kln89->_gpsLat * SG_RADIANS_TO_DEGREES;
63 double lon = _kln89->_gpsLon * SG_RADIANS_TO_DEGREES;
65 if(_subPage != 3) { _scanWpSet = false; }
68 if(_kln89->_navFlagged) {
69 _kln89->DrawText("> F L A G", 2, 0, 2);
70 _kln89->DrawText("DTK --- TK ---", 2, 0, 1);
71 _kln89->DrawText(">--- To --:--", 2, 0, 0);
72 _kln89->DrawSpecialChar(0, 2, 7, 1);
73 _kln89->DrawSpecialChar(0, 2, 15, 1);
74 _kln89->DrawSpecialChar(0, 2, 4, 0);
75 _kln89->DrawSpecialChar(1, 2, 3, 2);
76 _kln89->DrawSpecialChar(1, 2, 4, 2);
77 _kln89->DrawSpecialChar(1, 2, 6, 2);
78 _kln89->DrawSpecialChar(1, 2, 10, 2);
79 _kln89->DrawSpecialChar(1, 2, 12, 2);
80 _kln89->DrawSpecialChar(1, 2, 13, 2);
83 _kln89->DrawDTO(2, 7, 3);
85 if(!(_kln89->_waypointAlert && _kln89->_blink)) {
86 _kln89->DrawSpecialChar(3, 2, 8, 3);
89 _kln89->DrawText(awp->id, 2, 10, 3);
90 if(!_kln89->_dto && !_kln89->_obsMode && !_kln89->_fromWaypoint.id.empty()) {
91 if(_kln89->_fromWaypoint.type != GPS_WP_VIRT) { // Don't draw the virtual waypoint names
92 _kln89->DrawText(_kln89->_fromWaypoint.id, 2, 1, 3);
95 if(!(crsr && blink && _uLinePos == 1)) {
98 } else if(_cdiFormat == 1) {
99 _kln89->DrawText("Fly", 2, 2, 2);
100 double x = _kln89->CalcCrossTrackDeviation();
101 // TODO - check the R/L from sign of x below - I *think* it holds but not sure!
102 // Note also that we're setting Fly R or L based on the aircraft
103 // position only, not the heading. Not sure if this is correct or not.
104 _kln89->DrawText(x < 0.0 ? "R" : "L", 2, 6, 2);
107 x = fabs(x * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001));
109 n = snprintf(buf, 6, "%0.2f", x);
110 } else if(x < 100.0) {
111 n = snprintf(buf, 6, "%0.1f", x);
113 n = snprintf(buf, 6, "%i", (int)(x+0.5));
115 _kln89->DrawText((string)buf, 2, 13-n, 2);
116 _kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "km", 2, 13, 2);
118 _kln89->DrawText("CDI Scale:", 2, 1, 2);
119 double d = _kln89->_cdiScales[_kln89->_currentCdiScaleIndex] * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001);
123 snprintf(buf, 4, "%2.1f", d);
126 snprintf(buf, 5, "%2.2f", d);
127 // trim the leading zero
129 s = s.substr(1, s.size() - 1);
131 _kln89->DrawText(s, 2, 11, 2);
132 _kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "km", 2, 14, 2);
135 _kln89->DrawChar('>', 2, 0, 2);
136 _kln89->DrawChar('>', 2, 0, 0);
138 if(_uLinePos == 1) _kln89->Underline(2, 1, 2, 15);
139 else if(_uLinePos == 2) _kln89->Underline(2, 1, 0, 9);
141 // Desired and actual magnetic track
142 if(!_kln89->_obsMode) {
143 _kln89->DrawText("DTK", 2, 0, 1);
144 _kln89->DrawHeading((int)_kln89->_dtkMag, 2, 7, 1);
146 _kln89->DrawText("TK", 2, 9, 1);
147 if(_kln89->_groundSpeed_ms > 3) { // about 6 knots, don't know exactly what value to disable track
148 // The trouble with relying on FG gps's track value is we don't know when it's valid.
149 _kln89->DrawHeading((int)_kln89->_magTrackDeg, 2, 15, 1);
151 _kln89->DrawText("---", 2, 12, 1);
152 _kln89->DrawSpecialChar(0, 2, 15, 1);
155 // Radial to/from active waypoint.
156 // TODO - Not sure if this either is or should be true or mag!!!!!!!
157 if(!(crsr && blink && _uLinePos == 2)) {
159 _kln89->DrawHeading((int)_kln89->GetHeadingToActiveWaypoint(), 2, 4, 0);
160 _kln89->DrawText("To", 2, 5, 0);
161 } else if(1 == _vnv) {
162 _kln89->DrawHeading((int)_kln89->GetHeadingFromActiveWaypoint(), 2, 4, 0);
163 _kln89->DrawText("Fr", 2, 5, 0);
165 _kln89->DrawText("Vnv Off", 2, 1, 0);
168 // It seems that the floating point groundspeed must be at least 30kt
169 // for an ETA to be calculated. Note that this means that (integer) 30kt
170 // can appear in the frame 1 display both with and without an ETA displayed.
171 // TODO - need to switch off track (and heading bug change) based on instantaneous speed as well
172 // since the long gps lag filter means we can still be displaying when stopped on ground.
173 if(_kln89->_groundSpeed_kts > 30.0) {
174 // Assuming eta display is always hh:mm
175 // Does it ever switch to seconds when close?
176 if(_kln89->_eta / 3600.0 > 100.0) {
177 // More that 100 hours ! - Doesn't fit.
178 _kln89->DrawText("--:--", 2, 11, 0);
180 _kln89->DrawTime(_kln89->_eta, 2, 15, 0);
183 _kln89->DrawText("--:--", 2, 11, 0);
186 } else if(1 == _subPage) {
188 _kln89->DrawChar('>', 2, 1, 3);
189 if(!(crsr && blink && _uLinePos == 1)) _kln89->DrawText("PRESENT POSN", 2, 2, 3);
190 if(crsr && _uLinePos == 1) _kln89->Underline(2, 2, 3, 12);
191 if(0 == _posFormat) {
193 _kln89->DrawLatitude(lat, 2, 3, 1);
194 _kln89->DrawLongitude(lon, 2, 3, 0);
196 // Ref from wp - defaults to nearest vor (and resets to default when page left and re-entered).
198 } else if(2 == _subPage) {
199 _kln89->DrawText("Time", 2, 0, 3);
200 // TODO - hardwired to UTC at the moment
201 _kln89->DrawText("UTC", 2, 6, 3);
202 string th = fgGetString("/instrumentation/clock/indicated-hour");
203 string tm = fgGetString("/instrumentation/clock/indicated-min");
204 if(th.size() == 1) th = "0" + th;
205 if(tm.size() == 1) tm = "0" + tm;
206 _kln89->DrawText(th + tm, 2, 11, 3);
207 _kln89->DrawText("Depart", 2, 0, 2);
208 _kln89->DrawText(_kln89->_departureTimeString, 2, 11, 2);
209 _kln89->DrawText("ETA", 2, 0, 1);
210 if(_kln89->_departed) {
211 /* Rules of ETA waypoint are:
212 If the active waypoint is part of the active flightplan, then display
213 the ETA to the final (destination) waypoint of the active flightplan.
214 If the active waypoint is not part of the active flightplan, then
215 display the ETA to the active waypoint. */
216 // TODO - implement the above properly - we haven't below!
218 if(fp->waypoints.size()) {
219 wid = fp->waypoints[fp->waypoints.size() - 1]->id;
224 _kln89->DrawText(wid, 2, 4, 1);
225 double tsec = _kln89->GetTimeToWaypoint(wid);
227 _kln89->DrawText("----", 2, 11, 1);
229 int etah = (int)tsec / 3600;
230 int etam = ((int)tsec - etah * 3600) / 60;
231 etah += atoi(fgGetString("/instrumentation/clock/indicated-hour"));
232 etam += atoi(fgGetString("/instrumentation/clock/indicated-min"));
241 int n = snprintf(buf, 6, "%02i%02i", etah, etam);
242 _kln89->DrawText((string)buf, 2, 15-n, 1);
245 _kln89->DrawText("----", 2, 11, 1);
248 _kln89->DrawText("----", 2, 11, 1);
250 _kln89->DrawText("Flight", 2, 0, 0);
251 if(_kln89->_departed) {
252 int eh = (int)_kln89->_elapsedTime / 3600;
253 int em = ((int)_kln89->_elapsedTime - eh * 3600) / 60;
255 int n = snprintf(buf, 6, "%i:%02i", eh, em);
256 _kln89->DrawText((string)buf, 2, 15-n, 0);
258 _kln89->DrawText("-:--", 2, 11, 0);
260 } else { // if(3 == _subPage)
262 // Switch the cursor off if scan-pull is out on this page.
264 if(fgGetBool("/instrumentation/kln89/scan-pull")) { _kln89->_mode = KLN89_MODE_DISP; }
267 // Draw the moving map if valid.
268 // We call the core KLN89 class to do this.
270 if(_kln89->_mapOrientation == 2 && _kln89->_groundSpeed_kts < 2) {
271 // Don't draw it if in track up mode and groundspeed < 2kts, as per real-life unit.
273 _kln89->DrawMap(!_suspendAVS);
277 // Now that the map has been drawn, add the annotation (scale, etc).
279 int scale = KLN89MapScales[_kln89->_mapScaleUnits][_kln89->_mapScaleIndex];
280 string scle_str = GPSitoa(scale);
283 // Draw a background quad to encompass on/off for the first three at 'off' length
284 _kln89->DrawMapQuad(28, 9, 48, 36, true);
285 _kln89->DrawMapText("SUA:", 1, 27, true);
286 if(!(_menuPos == 0 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawSUA ? "on" : "off"), 29, 27, true);
287 if(_menuPos == 0) _kln89->DrawLine(28, 27, 48, 27);
288 _kln89->DrawMapText("VOR:", 1, 18, true);
289 if(!(_menuPos == 1 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawVOR ? "on" : "off"), 29, 18, true);
290 if(_menuPos == 1) _kln89->DrawLine(28, 18, 48, 18);
291 _kln89->DrawMapText("APT:", 1, 9, true);
292 if(!(_menuPos == 2 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawApt ? "on" : "off"), 29, 9, true);
293 if(_menuPos == 2) _kln89->DrawLine(28, 9, 48, 9);
294 _kln89->DrawMapQuad(0, 0, 27, 8, true);
295 if(!(_menuPos == 3 && _kln89->_blink)) {
296 if(_kln89->_mapOrientation == 0) {
297 _kln89->DrawMapText("N", 1, 0, true);
298 _kln89->DrawMapUpArrow(7, 1);
299 } else if(_kln89->_mapOrientation == 1) {
300 _kln89->DrawMapText("DTK", 1, 0, true);
301 _kln89->DrawMapUpArrow(21, 1);
303 // Don't bother with heading up for now!
304 _kln89->DrawMapText("TK", 1, 0, true);
305 _kln89->DrawMapUpArrow(14, 1);
308 if(_menuPos == 3) _kln89->DrawLine(0, 0, 27, 0);
311 if(!_kln89->_blink) {
312 _kln89->DrawMapText("Menu?", 1, 9, true);
314 _kln89->DrawLine(0, 9, 34, 9);
316 _kln89->DrawMapQuad(0, 9, 34, 17, true);
319 _kln89->DrawMapText("Menu?", 1, 9, true);
321 // right-justify the scale when _uLinePos == 3
322 if(!(_uLinePos == 3 && _kln89->_blink)) _kln89->DrawMapText(scle_str, (_uLinePos == 3 ? 29 - (scle_str.size() * 7) : 1), 0, true);
323 if(_uLinePos == 3) _kln89->DrawLine(0, 0, 27, 0);
326 // Just draw the scale
327 _kln89->DrawMapText(scle_str, 1, 0, true);
329 // If the scan-pull knob is out, draw one of the waypoints (if applicable).
330 if(fgGetBool("/instrumentation/kln89/scan-pull")) {
331 if(_kln89->_activeFP->waypoints.size()) {
332 //cout << "Need to draw a waypoint!\n";
333 _kln89->DrawLine(70, 0, 111, 0);
334 if(!_kln89->_blink) {
335 //_kln89->DrawMapQuad(45, 0, 97, 8, true);
337 _scanWpIndex = _kln89->GetActiveWaypointIndex();
340 _kln89->DrawMapText(_kln89->_activeFP->waypoints[_scanWpIndex]->id, 71, 0, true);
346 // Do part of the field 1 update, since NAV 4 is a special case for the last line.
348 _kln89->DrawChar('>', 1, 0, 0);
349 if(crsr && _uLinePos == 1) _kln89->Underline(1, 1, 0, 5);
350 if(!(crsr && _uLinePos == 1 && _kln89->_blink)) {
351 if(_kln89->_obsMode && _nav4DataSnippet == 0) _nav4DataSnippet = 1;
353 switch(_nav4DataSnippet) {
356 _kln89->DrawLabel("DTK", -39, 6);
357 // TODO - check we have an active FP / dtk and draw dashes if not.
359 snprintf(buf0, 4, "%03i", (int)(_kln89->_dtkMag));
360 _kln89->DrawText((string)buf0, 1, 3, 0);
364 _kln89->DrawSpeed(_kln89->_groundSpeed_kts, 1, 5, 0);
368 tsec = _kln89->GetETE();
370 _kln89->DrawText("--:--", 1, 1, 0);
372 int eteh = (int)tsec / 3600;
373 int etem = ((int)tsec - eteh * 3600) / 60;
375 int n = snprintf(buf, 6, "%02i:%02i", eteh, etem);
376 _kln89->DrawText((string)buf, 1, 6-n, 0);
380 // Cross-track correction
381 double x = _kln89->CalcCrossTrackDeviation();
383 _kln89->DrawSpecialChar(3, 1, 5, 0);
385 _kln89->DrawSpecialChar(7, 1, 5, 0);
389 x = fabs(x * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001));
391 n = snprintf(buf3, 6, "%0.2f", x);
392 } else if(x < 100.0) {
393 n = snprintf(buf3, 6, "%0.1f", x);
395 n = snprintf(buf3, 6, "%i", (int)(x+0.5));
397 _kln89->DrawText((string)buf3, 1, 5-n, 0);
403 KLN89Page::Update(dt);
406 // Returns the id string of the selected waypoint on NAV4 if valid, else returns an empty string.
407 string KLN89NavPage::GetNav4WpId() {
409 if(fgGetBool("/instrumentation/kln89/scan-pull")) {
410 if(_kln89->_activeFP->waypoints.size()) {
412 return(_kln89->_activeWaypoint.id);
414 return(_kln89->_activeFP->waypoints[_scanWpIndex]->id);
422 void KLN89NavPage::LooseFocus() {
427 void KLN89NavPage::CrsrPressed() {
428 if(_kln89->_mode == KLN89_MODE_DISP) {
429 // Crsr just switched off
432 // Crsr just switched on
441 void KLN89NavPage::EntPressed() {
442 if(_kln89->_mode == KLN89_MODE_CRSR) {
443 if(_subPage == 3 && _uLinePos == 2 && !_menuActive) {
451 void KLN89NavPage::ClrPressed() {
452 if(_kln89->_mode == KLN89_MODE_CRSR) {
456 if(_cdiFormat > 2) _cdiFormat = 0;
457 } else if(_uLinePos == 2) {
459 if(_vnv > 2) _vnv = 0;
464 _suspendAVS = !_suspendAVS;
466 } else if(_uLinePos == 1) {
468 if(_nav4DataSnippet > 3) _nav4DataSnippet = 0;
473 _suspendAVS = !_suspendAVS;
478 void KLN89NavPage::Knob1Left1() {
479 if(_kln89->_mode == KLN89_MODE_CRSR) {
480 if(!(_subPage == 3 && _menuActive)) {
481 if(_uLinePos > 0) _uLinePos--;
483 if(_menuPos > 0) _menuPos--;
488 void KLN89NavPage::Knob1Right1() {
489 if(_kln89->_mode == KLN89_MODE_CRSR) {
491 if(_uLinePos < 2) _uLinePos++;
492 } else if(_subPage == 2) {
495 // NAV 4 - this is complicated by whether the menu is displayed or not.
497 if(_menuPos < 3) _menuPos++;
499 if(_uLinePos < 3) _uLinePos++;
505 void KLN89NavPage::Knob2Left1() {
506 // If the inner-knob is out on the nav4 page, the only effect is to cycle the displayed waypoint.
507 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) {
508 if(_kln89->_activeFP->waypoints.size()) { // TODO - find out what happens when scan-pull is on on nav4 without an active FP.
509 // It's unlikely that we could get here without _scanWpSet, but theoretically possible, so we need to cover it.
511 _scanWpIndex = _kln89->GetActiveWaypointIndex();
514 if(0 == _scanWpIndex) {
515 _scanWpIndex = _kln89->_activeFP->waypoints.size() - 1;
523 if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
524 KLN89Page::Knob2Left1();
528 if(_uLinePos == 1 && _cdiFormat == 2) {
529 _kln89->CDIFSDIncrease();
531 } else if(_subPage == 3) {
534 _kln89->_drawSUA = !_kln89->_drawSUA;
535 } else if(_menuPos == 1) {
536 _kln89->_drawVOR = !_kln89->_drawVOR;
537 } else if(_menuPos == 2) {
538 _kln89->_drawApt = !_kln89->_drawApt;
540 if(_kln89->_mapOrientation == 0) {
541 // Don't allow heading up for now
542 _kln89->_mapOrientation = 2;
544 _kln89->_mapOrientation--;
546 _kln89->UpdateMapHeading();
548 } else if(_uLinePos == 3) {
550 if(_kln89->_mapScaleIndex == 0) {
551 _kln89->_mapScaleIndex = 20;
553 _kln89->_mapScaleIndex--;
559 void KLN89NavPage::Knob2Right1() {
560 // If the inner-knob is out on the nav4 page, the only effect is to cycle the displayed waypoint.
561 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) {
562 if(_kln89->_activeFP->waypoints.size()) { // TODO - find out what happens when scan-pull is on on nav4 without an active FP.
563 // It's unlikely that we could get here without _scanWpSet, but theoretically possible, so we need to cover it.
565 _scanWpIndex = _kln89->GetActiveWaypointIndex();
569 if(_scanWpIndex > static_cast<int>(_kln89->_activeFP->waypoints.size()) - 1) {
576 if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
577 KLN89Page::Knob2Right1();
581 if(_uLinePos == 1 && _cdiFormat == 2) {
582 _kln89->CDIFSDDecrease();
584 } else if(_subPage == 3) {
587 _kln89->_drawSUA = !_kln89->_drawSUA;
588 } else if(_menuPos == 1) {
589 _kln89->_drawVOR = !_kln89->_drawVOR;
590 } else if(_menuPos == 2) {
591 _kln89->_drawApt = !_kln89->_drawApt;
593 if(_kln89->_mapOrientation >= 2) {
594 // Don't allow heading up for now
595 _kln89->_mapOrientation = 0;
597 _kln89->_mapOrientation++;
599 _kln89->UpdateMapHeading();
601 } else if(_uLinePos == 3) {
603 if(_kln89->_mapScaleIndex == 20) {
604 _kln89->_mapScaleIndex = 0;
606 _kln89->_mapScaleIndex++;