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 - david.luff@nottingham.ac.uk
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?
46 KLN89NavPage::~KLN89NavPage() {
49 void KLN89NavPage::Update(double dt) {
50 GPSFlightPlan* fp = ((KLN89*)_parent)->_activeFP;
51 GPSWaypoint* awp = _parent->GetActiveWaypoint();
52 // Scan-pull out on nav4 page switches off the cursor
53 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) { _kln89->_mode = KLN89_MODE_DISP; }
54 bool crsr = (_kln89->_mode == KLN89_MODE_CRSR);
55 bool blink = _kln89->_blink;
56 double lat = _kln89->_gpsLat * SG_RADIANS_TO_DEGREES;
57 double lon = _kln89->_gpsLon * SG_RADIANS_TO_DEGREES;
59 if(_subPage != 3) { _scanWpSet = false; }
62 if(_kln89->_navFlagged) {
63 _kln89->DrawText("> F L A G", 2, 0, 2);
64 _kln89->DrawText("DTK --- TK ---", 2, 0, 1);
65 _kln89->DrawText(">--- To --:--", 2, 0, 0);
66 _kln89->DrawSpecialChar(0, 2, 7, 1);
67 _kln89->DrawSpecialChar(0, 2, 15, 1);
68 _kln89->DrawSpecialChar(0, 2, 4, 0);
69 _kln89->DrawSpecialChar(1, 2, 3, 2);
70 _kln89->DrawSpecialChar(1, 2, 4, 2);
71 _kln89->DrawSpecialChar(1, 2, 6, 2);
72 _kln89->DrawSpecialChar(1, 2, 10, 2);
73 _kln89->DrawSpecialChar(1, 2, 12, 2);
74 _kln89->DrawSpecialChar(1, 2, 13, 2);
77 _kln89->DrawDTO(2, 7, 3);
79 if(!(_kln89->_waypointAlert && _kln89->_blink)) {
80 _kln89->DrawSpecialChar(3, 2, 8, 3);
83 _kln89->DrawText(awp->id, 2, 10, 3);
84 if(!_kln89->_dto && !_kln89->_obsMode && !_kln89->_fromWaypoint.id.empty()) {
85 _kln89->DrawText(_kln89->_fromWaypoint.id, 2, 1, 3);
87 if(!(crsr && blink && _uLinePos == 1)) {
90 } else if(_cdiFormat == 1) {
91 _kln89->DrawText("Fly", 2, 2, 2);
92 double x = _kln89->CalcCrossTrackDeviation();
93 // TODO - check the R/L from sign of x below - I *think* it holds but not sure!
94 // Note also that we're setting Fly R or L based on the aircraft
95 // position only, not the heading. Not sure if this is correct or not.
96 _kln89->DrawText(x < 0.0 ? "R" : "L", 2, 6, 2);
99 x = fabs(x * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001));
101 n = snprintf(buf, 6, "%0.2f", x);
102 } else if(x < 100.0) {
103 n = snprintf(buf, 6, "%0.1f", x);
105 n = snprintf(buf, 6, "%i", (int)(x+0.5));
107 _kln89->DrawText((string)buf, 2, 13-n, 2);
108 _kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "km", 2, 13, 2);
110 _kln89->DrawText("CDI Scale:", 2, 1, 2);
111 double d = _kln89->_cdiScales[_kln89->_currentCdiScaleIndex] * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001);
115 snprintf(buf, 4, "%2.1f", d);
118 snprintf(buf, 5, "%2.2f", d);
119 // trim the leading zero
121 s = s.substr(1, s.size() - 1);
123 _kln89->DrawText(s, 2, 11, 2);
124 _kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "km", 2, 14, 2);
127 _kln89->DrawChar('>', 2, 0, 2);
128 _kln89->DrawChar('>', 2, 0, 0);
130 if(_uLinePos == 1) _kln89->Underline(2, 1, 2, 15);
131 else if(_uLinePos == 2) _kln89->Underline(2, 1, 0, 9);
133 // Desired and actual magnetic track
134 if(!_kln89->_obsMode) {
135 _kln89->DrawText("DTK", 2, 0, 1);
136 _kln89->DrawHeading((int)_kln89->_dtkMag, 2, 7, 1);
138 _kln89->DrawText("TK", 2, 9, 1);
139 if(_kln89->_groundSpeed_ms > 3) { // about 6 knots, don't know exactly what value to disable track
140 // The trouble with relying on FG gps's track value is we don't know when it's valid.
141 _kln89->DrawHeading((int)_kln89->_magTrackDeg, 2, 15, 1);
143 _kln89->DrawText("---", 2, 12, 1);
144 _kln89->DrawSpecialChar(0, 2, 15, 1);
147 // Radial to/from active waypoint.
148 // TODO - Not sure if this either is or should be true or mag!!!!!!!
149 if(!(crsr && blink && _uLinePos == 2)) {
151 _kln89->DrawHeading((int)_kln89->GetHeadingToActiveWaypoint(), 2, 4, 0);
152 _kln89->DrawText("To", 2, 5, 0);
153 } else if(1 == _vnv) {
154 _kln89->DrawHeading((int)_kln89->GetHeadingFromActiveWaypoint(), 2, 4, 0);
155 _kln89->DrawText("Fr", 2, 5, 0);
157 _kln89->DrawText("Vnv Off", 2, 1, 0);
160 // It seems that the floating point groundspeed must be at least 30kt
161 // for an ETA to be calculated. Note that this means that (integer) 30kt
162 // can appear in the frame 1 display both with and without an ETA displayed.
163 // TODO - need to switch off track (and heading bug change) based on instantaneous speed as well
164 // since the long gps lag filter means we can still be displaying when stopped on ground.
165 if(_kln89->_groundSpeed_kts > 30.0) {
166 // Assuming eta display is always hh:mm
167 // Does it ever switch to seconds when close?
168 if(_kln89->_eta / 3600.0 > 100.0) {
169 // More that 100 hours ! - Doesn't fit.
170 _kln89->DrawText("--:--", 2, 11, 0);
172 _kln89->DrawTime(_kln89->_eta, 2, 15, 0);
175 _kln89->DrawText("--:--", 2, 11, 0);
178 } else if(1 == _subPage) {
180 _kln89->DrawChar('>', 2, 1, 3);
181 if(!(crsr && blink && _uLinePos == 1)) _kln89->DrawText("PRESENT POSN", 2, 2, 3);
182 if(crsr && _uLinePos == 1) _kln89->Underline(2, 2, 3, 12);
183 if(0 == _posFormat) {
185 _kln89->DrawLatitude(lat, 2, 3, 1);
186 _kln89->DrawLongitude(lon, 2, 3, 0);
188 // Ref from wp - defaults to nearest vor (and resets to default when page left and re-entered).
190 } else if(2 == _subPage) {
191 _kln89->DrawText("Time", 2, 0, 3);
192 // TODO - hardwired to UTC at the moment
193 _kln89->DrawText("UTC", 2, 6, 3);
194 string th = fgGetString("/instrumentation/clock/indicated-hour");
195 string tm = fgGetString("/instrumentation/clock/indicated-min");
196 if(th.size() == 1) th = "0" + th;
197 if(tm.size() == 1) tm = "0" + tm;
198 _kln89->DrawText(th + tm, 2, 11, 3);
199 _kln89->DrawText("Depart", 2, 0, 2);
200 _kln89->DrawText(_kln89->_departureTimeString, 2, 11, 2);
201 _kln89->DrawText("ETA", 2, 0, 1);
202 if(_kln89->_departed) {
203 /* Rules of ETA waypoint are:
204 If the active waypoint is part of the active flightplan, then display
205 the ETA to the final (destination) waypoint of the active flightplan.
206 If the active waypoint is not part of the active flightplan, then
207 display the ETA to the active waypoint. */
208 // TODO - implement the above properly - we haven't below!
210 if(fp->waypoints.size()) {
211 wid = fp->waypoints[fp->waypoints.size() - 1]->id;
216 _kln89->DrawText(wid, 2, 4, 1);
217 double tsec = _kln89->GetTimeToWaypoint(wid);
219 _kln89->DrawText("----", 2, 11, 1);
221 int etah = (int)tsec / 3600;
222 int etam = ((int)tsec - etah * 3600) / 60;
223 etah += atoi(fgGetString("/instrumentation/clock/indicated-hour"));
224 etam += atoi(fgGetString("/instrumentation/clock/indicated-min"));
233 int n = snprintf(buf, 6, "%02i%02i", etah, etam);
234 _kln89->DrawText((string)buf, 2, 15-n, 1);
237 _kln89->DrawText("----", 2, 11, 1);
240 _kln89->DrawText("----", 2, 11, 1);
242 _kln89->DrawText("Flight", 2, 0, 0);
243 if(_kln89->_departed) {
244 int eh = (int)_kln89->_elapsedTime / 3600;
245 int em = ((int)_kln89->_elapsedTime - eh * 3600) / 60;
247 int n = snprintf(buf, 6, "%i:%02i", eh, em);
248 _kln89->DrawText((string)buf, 2, 15-n, 0);
250 _kln89->DrawText("-:--", 2, 11, 0);
252 } else { // if(3 == _subPage)
253 // Switch the cursor off if scan-pull is out on this page.
254 if(fgGetBool("/instrumentation/kln89/scan-pull")) { _kln89->_mode = KLN89_MODE_DISP; }
255 // The moving map page the core KLN89 class draws this.
256 if(_kln89->_mapOrientation == 2 && _kln89->_groundSpeed_kts < 2) {
257 // Don't draw it if in track up mode and groundspeed < 2kts, as per real-life unit.
259 _kln89->DrawMap(!_suspendAVS);
261 // Now draw any annotation over it.
262 int scale = KLN89MapScales[_kln89->_mapScaleUnits][_kln89->_mapScaleIndex];
263 string scle_str = GPSitoa(scale);
266 // Draw a background quad to encompass on/off for the first three at 'off' length
267 _kln89->DrawMapQuad(28, 9, 48, 36, true);
268 _kln89->DrawMapText("SUA:", 1, 27, true);
269 if(!(_menuPos == 0 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawSUA ? "on" : "off"), 29, 27, true);
270 if(_menuPos == 0) _kln89->DrawLine(28, 27, 48, 27);
271 _kln89->DrawMapText("VOR:", 1, 18, true);
272 if(!(_menuPos == 1 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawVOR ? "on" : "off"), 29, 18, true);
273 if(_menuPos == 1) _kln89->DrawLine(28, 18, 48, 18);
274 _kln89->DrawMapText("APT:", 1, 9, true);
275 if(!(_menuPos == 2 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawApt ? "on" : "off"), 29, 9, true);
276 if(_menuPos == 2) _kln89->DrawLine(28, 9, 48, 9);
277 _kln89->DrawMapQuad(0, 0, 27, 8, true);
278 if(!(_menuPos == 3 && _kln89->_blink)) {
279 if(_kln89->_mapOrientation == 0) {
280 _kln89->DrawMapText("N", 1, 0, true);
281 _kln89->DrawMapUpArrow(7, 1);
282 } else if(_kln89->_mapOrientation == 1) {
283 _kln89->DrawMapText("DTK", 1, 0, true);
284 _kln89->DrawMapUpArrow(21, 1);
286 // Don't bother with heading up for now!
287 _kln89->DrawMapText("TK", 1, 0, true);
288 _kln89->DrawMapUpArrow(14, 1);
291 if(_menuPos == 3) _kln89->DrawLine(0, 0, 27, 0);
294 if(!_kln89->_blink) {
295 _kln89->DrawMapText("Menu?", 1, 9, true);
297 _kln89->DrawLine(0, 9, 34, 9);
299 _kln89->DrawMapQuad(0, 9, 34, 17, true);
302 _kln89->DrawMapText("Menu?", 1, 9, true);
304 // right-justify the scale when _uLinePos == 3
305 if(!(_uLinePos == 3 && _kln89->_blink)) _kln89->DrawMapText(scle_str, (_uLinePos == 3 ? 29 - (scle_str.size() * 7) : 1), 0, true);
306 if(_uLinePos == 3) _kln89->DrawLine(0, 0, 27, 0);
309 // Just draw the scale
310 _kln89->DrawMapText(scle_str, 1, 0, true);
312 // If the scan-pull knob is out, draw one of the waypoints (if applicable).
313 if(fgGetBool("/instrumentation/kln89/scan-pull")) {
314 if(_kln89->_activeFP->waypoints.size()) {
315 //cout << "Need to draw a waypoint!\n";
316 _kln89->DrawLine(70, 0, 111, 0);
317 if(!_kln89->_blink) {
318 //_kln89->DrawMapQuad(45, 0, 97, 8, true);
320 _scanWpIndex = _kln89->GetActiveWaypointIndex();
323 _kln89->DrawMapText(_kln89->_activeFP->waypoints[_scanWpIndex]->id, 71, 0, true);
327 // And do part of the field 1 update, since NAV 4 is a special case for the last line.
328 _kln89->DrawChar('>', 1, 0, 0);
329 if(crsr && _uLinePos == 1) _kln89->Underline(1, 1, 0, 5);
330 if(!(crsr && _uLinePos == 1 && _kln89->_blink)) {
331 if(_kln89->_obsMode && _nav4DataSnippet == 0) _nav4DataSnippet = 1;
333 switch(_nav4DataSnippet) {
336 _kln89->DrawLabel("DTK", -39, 6);
337 // TODO - check we have an active FP / dtk and draw dashes if not.
339 snprintf(buf0, 4, "%03i", (int)(_kln89->_dtkMag));
340 _kln89->DrawText((string)buf0, 1, 3, 0);
344 _kln89->DrawSpeed(_kln89->_groundSpeed_kts, 1, 5, 0);
348 tsec = _kln89->GetETE();
350 _kln89->DrawText("--:--", 1, 1, 0);
352 int eteh = (int)tsec / 3600;
353 int etem = ((int)tsec - eteh * 3600) / 60;
355 int n = snprintf(buf, 6, "%02i:%02i", eteh, etem);
356 _kln89->DrawText((string)buf, 1, 6-n, 0);
360 // Cross-track correction
361 double x = _kln89->CalcCrossTrackDeviation();
363 _kln89->DrawSpecialChar(3, 1, 5, 0);
365 _kln89->DrawSpecialChar(7, 1, 5, 0);
369 x = fabs(x * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001));
371 n = snprintf(buf3, 6, "%0.2f", x);
372 } else if(x < 100.0) {
373 n = snprintf(buf3, 6, "%0.1f", x);
375 n = snprintf(buf3, 6, "%i", (int)(x+0.5));
377 _kln89->DrawText((string)buf3, 1, 5-n, 0);
383 KLN89Page::Update(dt);
386 // Returns the id string of the selected waypoint on NAV4 if valid, else returns an empty string.
387 string KLN89NavPage::GetNav4WpId() {
389 if(fgGetBool("/instrumentation/kln89/scan-pull")) {
390 if(_kln89->_activeFP->waypoints.size()) {
392 return(_kln89->_activeWaypoint.id);
394 return(_kln89->_activeFP->waypoints[_scanWpIndex]->id);
402 void KLN89NavPage::LooseFocus() {
407 void KLN89NavPage::CrsrPressed() {
408 if(_kln89->_mode == KLN89_MODE_DISP) {
409 // Crsr just switched off
412 // Crsr just switched on
421 void KLN89NavPage::EntPressed() {
422 if(_kln89->_mode == KLN89_MODE_CRSR) {
423 if(_subPage == 3 && _uLinePos == 2 && !_menuActive) {
431 void KLN89NavPage::ClrPressed() {
432 if(_kln89->_mode == KLN89_MODE_CRSR) {
436 if(_cdiFormat > 2) _cdiFormat = 0;
437 } else if(_uLinePos == 2) {
439 if(_vnv > 2) _vnv = 0;
444 _suspendAVS = !_suspendAVS;
446 } else if(_uLinePos == 1) {
448 if(_nav4DataSnippet > 3) _nav4DataSnippet = 0;
453 _suspendAVS = !_suspendAVS;
458 void KLN89NavPage::Knob1Left1() {
459 if(_kln89->_mode == KLN89_MODE_CRSR) {
460 if(!(_subPage == 3 && _menuActive)) {
461 if(_uLinePos > 0) _uLinePos--;
463 if(_menuPos > 0) _menuPos--;
468 void KLN89NavPage::Knob1Right1() {
469 if(_kln89->_mode == KLN89_MODE_CRSR) {
471 if(_uLinePos < 2) _uLinePos++;
472 } else if(_subPage == 2) {
475 // NAV 4 - this is complicated by whether the menu is displayed or not.
477 if(_menuPos < 3) _menuPos++;
479 if(_uLinePos < 3) _uLinePos++;
485 void KLN89NavPage::Knob2Left1() {
486 // If the inner-knob is out on the nav4 page, the only effect is to cycle the displayed waypoint.
487 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) {
488 if(_kln89->_activeFP->waypoints.size()) { // TODO - find out what happens when scan-pull is on on nav4 without an active FP.
489 // It's unlikely that we could get here without _scanWpSet, but theoretically possible, so we need to cover it.
491 _scanWpIndex = _kln89->GetActiveWaypointIndex();
494 if(0 == _scanWpIndex) {
495 _scanWpIndex = _kln89->_activeFP->waypoints.size() - 1;
503 if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
504 KLN89Page::Knob2Left1();
508 if(_uLinePos == 1 && _cdiFormat == 2) {
509 _kln89->CDIFSDIncrease();
511 } else if(_subPage == 3) {
514 _kln89->_drawSUA = !_kln89->_drawSUA;
515 } else if(_menuPos == 1) {
516 _kln89->_drawVOR = !_kln89->_drawVOR;
517 } else if(_menuPos == 2) {
518 _kln89->_drawApt = !_kln89->_drawApt;
520 if(_kln89->_mapOrientation == 0) {
521 // Don't allow heading up for now
522 _kln89->_mapOrientation = 2;
524 _kln89->_mapOrientation--;
526 _kln89->UpdateMapHeading();
528 } else if(_uLinePos == 3) {
530 if(_kln89->_mapScaleIndex == 0) {
531 _kln89->_mapScaleIndex = 20;
533 _kln89->_mapScaleIndex--;
539 void KLN89NavPage::Knob2Right1() {
540 // If the inner-knob is out on the nav4 page, the only effect is to cycle the displayed waypoint.
541 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) {
542 if(_kln89->_activeFP->waypoints.size()) { // TODO - find out what happens when scan-pull is on on nav4 without an active FP.
543 // It's unlikely that we could get here without _scanWpSet, but theoretically possible, so we need to cover it.
545 _scanWpIndex = _kln89->GetActiveWaypointIndex();
549 if(_scanWpIndex > _kln89->_activeFP->waypoints.size() - 1) {
556 if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
557 KLN89Page::Knob2Right1();
561 if(_uLinePos == 1 && _cdiFormat == 2) {
562 _kln89->CDIFSDDecrease();
564 } else if(_subPage == 3) {
567 _kln89->_drawSUA = !_kln89->_drawSUA;
568 } else if(_menuPos == 1) {
569 _kln89->_drawVOR = !_kln89->_drawVOR;
570 } else if(_menuPos == 2) {
571 _kln89->_drawApt = !_kln89->_drawApt;
573 if(_kln89->_mapOrientation >= 2) {
574 // Don't allow heading up for now
575 _kln89->_mapOrientation = 0;
577 _kln89->_mapOrientation++;
579 _kln89->UpdateMapHeading();
581 } else if(_uLinePos == 3) {
583 if(_kln89->_mapScaleIndex == 20) {
584 _kln89->_mapScaleIndex = 0;
586 _kln89->_mapScaleIndex++;