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 _kln89->DrawText(_kln89->_fromWaypoint.id, 2, 1, 3);
88 if(!(crsr && blink && _uLinePos == 1)) {
91 } else if(_cdiFormat == 1) {
92 _kln89->DrawText("Fly", 2, 2, 2);
93 double x = _kln89->CalcCrossTrackDeviation();
94 // TODO - check the R/L from sign of x below - I *think* it holds but not sure!
95 // Note also that we're setting Fly R or L based on the aircraft
96 // position only, not the heading. Not sure if this is correct or not.
97 _kln89->DrawText(x < 0.0 ? "R" : "L", 2, 6, 2);
100 x = fabs(x * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001));
102 n = snprintf(buf, 6, "%0.2f", x);
103 } else if(x < 100.0) {
104 n = snprintf(buf, 6, "%0.1f", x);
106 n = snprintf(buf, 6, "%i", (int)(x+0.5));
108 _kln89->DrawText((string)buf, 2, 13-n, 2);
109 _kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "km", 2, 13, 2);
111 _kln89->DrawText("CDI Scale:", 2, 1, 2);
112 double d = _kln89->_cdiScales[_kln89->_currentCdiScaleIndex] * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001);
116 snprintf(buf, 4, "%2.1f", d);
119 snprintf(buf, 5, "%2.2f", d);
120 // trim the leading zero
122 s = s.substr(1, s.size() - 1);
124 _kln89->DrawText(s, 2, 11, 2);
125 _kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "km", 2, 14, 2);
128 _kln89->DrawChar('>', 2, 0, 2);
129 _kln89->DrawChar('>', 2, 0, 0);
131 if(_uLinePos == 1) _kln89->Underline(2, 1, 2, 15);
132 else if(_uLinePos == 2) _kln89->Underline(2, 1, 0, 9);
134 // Desired and actual magnetic track
135 if(!_kln89->_obsMode) {
136 _kln89->DrawText("DTK", 2, 0, 1);
137 _kln89->DrawHeading((int)_kln89->_dtkMag, 2, 7, 1);
139 _kln89->DrawText("TK", 2, 9, 1);
140 if(_kln89->_groundSpeed_ms > 3) { // about 6 knots, don't know exactly what value to disable track
141 // The trouble with relying on FG gps's track value is we don't know when it's valid.
142 _kln89->DrawHeading((int)_kln89->_magTrackDeg, 2, 15, 1);
144 _kln89->DrawText("---", 2, 12, 1);
145 _kln89->DrawSpecialChar(0, 2, 15, 1);
148 // Radial to/from active waypoint.
149 // TODO - Not sure if this either is or should be true or mag!!!!!!!
150 if(!(crsr && blink && _uLinePos == 2)) {
152 _kln89->DrawHeading((int)_kln89->GetHeadingToActiveWaypoint(), 2, 4, 0);
153 _kln89->DrawText("To", 2, 5, 0);
154 } else if(1 == _vnv) {
155 _kln89->DrawHeading((int)_kln89->GetHeadingFromActiveWaypoint(), 2, 4, 0);
156 _kln89->DrawText("Fr", 2, 5, 0);
158 _kln89->DrawText("Vnv Off", 2, 1, 0);
161 // It seems that the floating point groundspeed must be at least 30kt
162 // for an ETA to be calculated. Note that this means that (integer) 30kt
163 // can appear in the frame 1 display both with and without an ETA displayed.
164 // TODO - need to switch off track (and heading bug change) based on instantaneous speed as well
165 // since the long gps lag filter means we can still be displaying when stopped on ground.
166 if(_kln89->_groundSpeed_kts > 30.0) {
167 // Assuming eta display is always hh:mm
168 // Does it ever switch to seconds when close?
169 if(_kln89->_eta / 3600.0 > 100.0) {
170 // More that 100 hours ! - Doesn't fit.
171 _kln89->DrawText("--:--", 2, 11, 0);
173 _kln89->DrawTime(_kln89->_eta, 2, 15, 0);
176 _kln89->DrawText("--:--", 2, 11, 0);
179 } else if(1 == _subPage) {
181 _kln89->DrawChar('>', 2, 1, 3);
182 if(!(crsr && blink && _uLinePos == 1)) _kln89->DrawText("PRESENT POSN", 2, 2, 3);
183 if(crsr && _uLinePos == 1) _kln89->Underline(2, 2, 3, 12);
184 if(0 == _posFormat) {
186 _kln89->DrawLatitude(lat, 2, 3, 1);
187 _kln89->DrawLongitude(lon, 2, 3, 0);
189 // Ref from wp - defaults to nearest vor (and resets to default when page left and re-entered).
191 } else if(2 == _subPage) {
192 _kln89->DrawText("Time", 2, 0, 3);
193 // TODO - hardwired to UTC at the moment
194 _kln89->DrawText("UTC", 2, 6, 3);
195 string th = fgGetString("/instrumentation/clock/indicated-hour");
196 string tm = fgGetString("/instrumentation/clock/indicated-min");
197 if(th.size() == 1) th = "0" + th;
198 if(tm.size() == 1) tm = "0" + tm;
199 _kln89->DrawText(th + tm, 2, 11, 3);
200 _kln89->DrawText("Depart", 2, 0, 2);
201 _kln89->DrawText(_kln89->_departureTimeString, 2, 11, 2);
202 _kln89->DrawText("ETA", 2, 0, 1);
203 if(_kln89->_departed) {
204 /* Rules of ETA waypoint are:
205 If the active waypoint is part of the active flightplan, then display
206 the ETA to the final (destination) waypoint of the active flightplan.
207 If the active waypoint is not part of the active flightplan, then
208 display the ETA to the active waypoint. */
209 // TODO - implement the above properly - we haven't below!
211 if(fp->waypoints.size()) {
212 wid = fp->waypoints[fp->waypoints.size() - 1]->id;
217 _kln89->DrawText(wid, 2, 4, 1);
218 double tsec = _kln89->GetTimeToWaypoint(wid);
220 _kln89->DrawText("----", 2, 11, 1);
222 int etah = (int)tsec / 3600;
223 int etam = ((int)tsec - etah * 3600) / 60;
224 etah += atoi(fgGetString("/instrumentation/clock/indicated-hour"));
225 etam += atoi(fgGetString("/instrumentation/clock/indicated-min"));
234 int n = snprintf(buf, 6, "%02i%02i", etah, etam);
235 _kln89->DrawText((string)buf, 2, 15-n, 1);
238 _kln89->DrawText("----", 2, 11, 1);
241 _kln89->DrawText("----", 2, 11, 1);
243 _kln89->DrawText("Flight", 2, 0, 0);
244 if(_kln89->_departed) {
245 int eh = (int)_kln89->_elapsedTime / 3600;
246 int em = ((int)_kln89->_elapsedTime - eh * 3600) / 60;
248 int n = snprintf(buf, 6, "%i:%02i", eh, em);
249 _kln89->DrawText((string)buf, 2, 15-n, 0);
251 _kln89->DrawText("-:--", 2, 11, 0);
253 } else { // if(3 == _subPage)
255 // Switch the cursor off if scan-pull is out on this page.
257 if(fgGetBool("/instrumentation/kln89/scan-pull")) { _kln89->_mode = KLN89_MODE_DISP; }
260 // Draw the moving map if valid.
261 // We call the core KLN89 class to do this.
263 if(_kln89->_mapOrientation == 2 && _kln89->_groundSpeed_kts < 2) {
264 // Don't draw it if in track up mode and groundspeed < 2kts, as per real-life unit.
266 _kln89->DrawMap(!_suspendAVS);
270 // Now that the map has been drawn, add the annotation (scale, etc).
272 int scale = KLN89MapScales[_kln89->_mapScaleUnits][_kln89->_mapScaleIndex];
273 string scle_str = GPSitoa(scale);
276 // Draw a background quad to encompass on/off for the first three at 'off' length
277 _kln89->DrawMapQuad(28, 9, 48, 36, true);
278 _kln89->DrawMapText("SUA:", 1, 27, true);
279 if(!(_menuPos == 0 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawSUA ? "on" : "off"), 29, 27, true);
280 if(_menuPos == 0) _kln89->DrawLine(28, 27, 48, 27);
281 _kln89->DrawMapText("VOR:", 1, 18, true);
282 if(!(_menuPos == 1 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawVOR ? "on" : "off"), 29, 18, true);
283 if(_menuPos == 1) _kln89->DrawLine(28, 18, 48, 18);
284 _kln89->DrawMapText("APT:", 1, 9, true);
285 if(!(_menuPos == 2 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawApt ? "on" : "off"), 29, 9, true);
286 if(_menuPos == 2) _kln89->DrawLine(28, 9, 48, 9);
287 _kln89->DrawMapQuad(0, 0, 27, 8, true);
288 if(!(_menuPos == 3 && _kln89->_blink)) {
289 if(_kln89->_mapOrientation == 0) {
290 _kln89->DrawMapText("N", 1, 0, true);
291 _kln89->DrawMapUpArrow(7, 1);
292 } else if(_kln89->_mapOrientation == 1) {
293 _kln89->DrawMapText("DTK", 1, 0, true);
294 _kln89->DrawMapUpArrow(21, 1);
296 // Don't bother with heading up for now!
297 _kln89->DrawMapText("TK", 1, 0, true);
298 _kln89->DrawMapUpArrow(14, 1);
301 if(_menuPos == 3) _kln89->DrawLine(0, 0, 27, 0);
304 if(!_kln89->_blink) {
305 _kln89->DrawMapText("Menu?", 1, 9, true);
307 _kln89->DrawLine(0, 9, 34, 9);
309 _kln89->DrawMapQuad(0, 9, 34, 17, true);
312 _kln89->DrawMapText("Menu?", 1, 9, true);
314 // right-justify the scale when _uLinePos == 3
315 if(!(_uLinePos == 3 && _kln89->_blink)) _kln89->DrawMapText(scle_str, (_uLinePos == 3 ? 29 - (scle_str.size() * 7) : 1), 0, true);
316 if(_uLinePos == 3) _kln89->DrawLine(0, 0, 27, 0);
319 // Just draw the scale
320 _kln89->DrawMapText(scle_str, 1, 0, true);
322 // If the scan-pull knob is out, draw one of the waypoints (if applicable).
323 if(fgGetBool("/instrumentation/kln89/scan-pull")) {
324 if(_kln89->_activeFP->waypoints.size()) {
325 //cout << "Need to draw a waypoint!\n";
326 _kln89->DrawLine(70, 0, 111, 0);
327 if(!_kln89->_blink) {
328 //_kln89->DrawMapQuad(45, 0, 97, 8, true);
330 _scanWpIndex = _kln89->GetActiveWaypointIndex();
333 _kln89->DrawMapText(_kln89->_activeFP->waypoints[_scanWpIndex]->id, 71, 0, true);
339 // Do part of the field 1 update, since NAV 4 is a special case for the last line.
341 _kln89->DrawChar('>', 1, 0, 0);
342 if(crsr && _uLinePos == 1) _kln89->Underline(1, 1, 0, 5);
343 if(!(crsr && _uLinePos == 1 && _kln89->_blink)) {
344 if(_kln89->_obsMode && _nav4DataSnippet == 0) _nav4DataSnippet = 1;
346 switch(_nav4DataSnippet) {
349 _kln89->DrawLabel("DTK", -39, 6);
350 // TODO - check we have an active FP / dtk and draw dashes if not.
352 snprintf(buf0, 4, "%03i", (int)(_kln89->_dtkMag));
353 _kln89->DrawText((string)buf0, 1, 3, 0);
357 _kln89->DrawSpeed(_kln89->_groundSpeed_kts, 1, 5, 0);
361 tsec = _kln89->GetETE();
363 _kln89->DrawText("--:--", 1, 1, 0);
365 int eteh = (int)tsec / 3600;
366 int etem = ((int)tsec - eteh * 3600) / 60;
368 int n = snprintf(buf, 6, "%02i:%02i", eteh, etem);
369 _kln89->DrawText((string)buf, 1, 6-n, 0);
373 // Cross-track correction
374 double x = _kln89->CalcCrossTrackDeviation();
376 _kln89->DrawSpecialChar(3, 1, 5, 0);
378 _kln89->DrawSpecialChar(7, 1, 5, 0);
382 x = fabs(x * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001));
384 n = snprintf(buf3, 6, "%0.2f", x);
385 } else if(x < 100.0) {
386 n = snprintf(buf3, 6, "%0.1f", x);
388 n = snprintf(buf3, 6, "%i", (int)(x+0.5));
390 _kln89->DrawText((string)buf3, 1, 5-n, 0);
396 KLN89Page::Update(dt);
399 // Returns the id string of the selected waypoint on NAV4 if valid, else returns an empty string.
400 string KLN89NavPage::GetNav4WpId() {
402 if(fgGetBool("/instrumentation/kln89/scan-pull")) {
403 if(_kln89->_activeFP->waypoints.size()) {
405 return(_kln89->_activeWaypoint.id);
407 return(_kln89->_activeFP->waypoints[_scanWpIndex]->id);
415 void KLN89NavPage::LooseFocus() {
420 void KLN89NavPage::CrsrPressed() {
421 if(_kln89->_mode == KLN89_MODE_DISP) {
422 // Crsr just switched off
425 // Crsr just switched on
434 void KLN89NavPage::EntPressed() {
435 if(_kln89->_mode == KLN89_MODE_CRSR) {
436 if(_subPage == 3 && _uLinePos == 2 && !_menuActive) {
444 void KLN89NavPage::ClrPressed() {
445 if(_kln89->_mode == KLN89_MODE_CRSR) {
449 if(_cdiFormat > 2) _cdiFormat = 0;
450 } else if(_uLinePos == 2) {
452 if(_vnv > 2) _vnv = 0;
457 _suspendAVS = !_suspendAVS;
459 } else if(_uLinePos == 1) {
461 if(_nav4DataSnippet > 3) _nav4DataSnippet = 0;
466 _suspendAVS = !_suspendAVS;
471 void KLN89NavPage::Knob1Left1() {
472 if(_kln89->_mode == KLN89_MODE_CRSR) {
473 if(!(_subPage == 3 && _menuActive)) {
474 if(_uLinePos > 0) _uLinePos--;
476 if(_menuPos > 0) _menuPos--;
481 void KLN89NavPage::Knob1Right1() {
482 if(_kln89->_mode == KLN89_MODE_CRSR) {
484 if(_uLinePos < 2) _uLinePos++;
485 } else if(_subPage == 2) {
488 // NAV 4 - this is complicated by whether the menu is displayed or not.
490 if(_menuPos < 3) _menuPos++;
492 if(_uLinePos < 3) _uLinePos++;
498 void KLN89NavPage::Knob2Left1() {
499 // If the inner-knob is out on the nav4 page, the only effect is to cycle the displayed waypoint.
500 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) {
501 if(_kln89->_activeFP->waypoints.size()) { // TODO - find out what happens when scan-pull is on on nav4 without an active FP.
502 // It's unlikely that we could get here without _scanWpSet, but theoretically possible, so we need to cover it.
504 _scanWpIndex = _kln89->GetActiveWaypointIndex();
507 if(0 == _scanWpIndex) {
508 _scanWpIndex = _kln89->_activeFP->waypoints.size() - 1;
516 if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
517 KLN89Page::Knob2Left1();
521 if(_uLinePos == 1 && _cdiFormat == 2) {
522 _kln89->CDIFSDIncrease();
524 } else if(_subPage == 3) {
527 _kln89->_drawSUA = !_kln89->_drawSUA;
528 } else if(_menuPos == 1) {
529 _kln89->_drawVOR = !_kln89->_drawVOR;
530 } else if(_menuPos == 2) {
531 _kln89->_drawApt = !_kln89->_drawApt;
533 if(_kln89->_mapOrientation == 0) {
534 // Don't allow heading up for now
535 _kln89->_mapOrientation = 2;
537 _kln89->_mapOrientation--;
539 _kln89->UpdateMapHeading();
541 } else if(_uLinePos == 3) {
543 if(_kln89->_mapScaleIndex == 0) {
544 _kln89->_mapScaleIndex = 20;
546 _kln89->_mapScaleIndex--;
552 void KLN89NavPage::Knob2Right1() {
553 // If the inner-knob is out on the nav4 page, the only effect is to cycle the displayed waypoint.
554 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) {
555 if(_kln89->_activeFP->waypoints.size()) { // TODO - find out what happens when scan-pull is on on nav4 without an active FP.
556 // It's unlikely that we could get here without _scanWpSet, but theoretically possible, so we need to cover it.
558 _scanWpIndex = _kln89->GetActiveWaypointIndex();
562 if(_scanWpIndex > static_cast<int>(_kln89->_activeFP->waypoints.size()) - 1) {
569 if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
570 KLN89Page::Knob2Right1();
574 if(_uLinePos == 1 && _cdiFormat == 2) {
575 _kln89->CDIFSDDecrease();
577 } else if(_subPage == 3) {
580 _kln89->_drawSUA = !_kln89->_drawSUA;
581 } else if(_menuPos == 1) {
582 _kln89->_drawVOR = !_kln89->_drawVOR;
583 } else if(_menuPos == 2) {
584 _kln89->_drawApt = !_kln89->_drawApt;
586 if(_kln89->_mapOrientation >= 2) {
587 // Don't allow heading up for now
588 _kln89->_mapOrientation = 0;
590 _kln89->_mapOrientation++;
592 _kln89->UpdateMapHeading();
594 } else if(_uLinePos == 3) {
596 if(_kln89->_mapScaleIndex == 20) {
597 _kln89->_mapScaleIndex = 0;
599 _kln89->_mapScaleIndex++;