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., 675 Mass Ave, Cambridge, MA 02139, USA.
24 #include "kln89_page_nav.hxx"
25 #include <Main/fg_props.hxx>
27 KLN89NavPage::KLN89NavPage(KLN89* parent)
32 _posFormat = 0; // Check - should this default to ref from waypoint?
42 KLN89NavPage::~KLN89NavPage() {
45 void KLN89NavPage::Update(double dt) {
46 GPSFlightPlan* fp = ((KLN89*)_parent)->_activeFP;
47 GPSWaypoint* awp = _parent->GetActiveWaypoint();
48 // Scan-pull out on nav4 page switches off the cursor
49 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) { _kln89->_mode = KLN89_MODE_DISP; }
50 bool crsr = (_kln89->_mode == KLN89_MODE_CRSR);
51 bool blink = _kln89->_blink;
52 double lat = _kln89->_gpsLat * SG_RADIANS_TO_DEGREES;
53 double lon = _kln89->_gpsLon * SG_RADIANS_TO_DEGREES;
55 if(_subPage != 3) { _scanWpSet = false; }
58 if(_kln89->_navFlagged) {
59 _kln89->DrawText("> F L A G", 2, 0, 2);
60 _kln89->DrawText("DTK --- TK ---", 2, 0, 1);
61 _kln89->DrawText(">--- To --:--", 2, 0, 0);
62 _kln89->DrawSpecialChar(0, 2, 7, 1);
63 _kln89->DrawSpecialChar(0, 2, 15, 1);
64 _kln89->DrawSpecialChar(0, 2, 4, 0);
65 _kln89->DrawSpecialChar(1, 2, 3, 2);
66 _kln89->DrawSpecialChar(1, 2, 4, 2);
67 _kln89->DrawSpecialChar(1, 2, 6, 2);
68 _kln89->DrawSpecialChar(1, 2, 10, 2);
69 _kln89->DrawSpecialChar(1, 2, 12, 2);
70 _kln89->DrawSpecialChar(1, 2, 13, 2);
73 _kln89->DrawDTO(2, 7, 3);
75 if(!(_kln89->_waypointAlert && _kln89->_blink)) {
76 _kln89->DrawSpecialChar(3, 2, 8, 3);
79 _kln89->DrawText(awp->id, 2, 10, 3);
80 if(!_kln89->_dto && !_kln89->_obsMode && !_kln89->_fromWaypoint.id.empty()) {
81 _kln89->DrawText(_kln89->_fromWaypoint.id, 2, 1, 3);
83 if(!(crsr && blink && _uLinePos == 1)) {
86 } else if(_cdiFormat == 1) {
87 _kln89->DrawText("Fly", 2, 2, 2);
88 double x = _kln89->CalcCrossTrackDeviation();
89 // TODO - check the R/L from sign of x below - I *think* it holds but not sure!
90 // Note also that we're setting Fly R or L based on the aircraft
91 // position only, not the heading. Not sure if this is correct or not.
92 _kln89->DrawText(x < 0.0 ? "R" : "L", 2, 6, 2);
95 x = fabs(x * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001));
97 n = snprintf(buf, 6, "%0.2f", x);
98 } else if(x < 100.0) {
99 n = snprintf(buf, 6, "%0.1f", x);
101 n = snprintf(buf, 6, "%i", (int)(x+0.5));
103 _kln89->DrawText((string)buf, 2, 13-n, 2);
104 _kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "km", 2, 13, 2);
106 _kln89->DrawText("CDI Scale:", 2, 1, 2);
107 double d = _kln89->_cdiScales[_kln89->_currentCdiScaleIndex] * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001);
111 snprintf(buf, 4, "%2.1f", d);
114 snprintf(buf, 5, "%2.2f", d);
115 // trim the leading zero
117 s = s.substr(1, s.size() - 1);
119 _kln89->DrawText(s, 2, 11, 2);
120 _kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "km", 2, 14, 2);
123 _kln89->DrawChar('>', 2, 0, 2);
124 _kln89->DrawChar('>', 2, 0, 0);
126 if(_uLinePos == 1) _kln89->Underline(2, 1, 2, 15);
127 else if(_uLinePos == 2) _kln89->Underline(2, 1, 0, 9);
129 // Desired and actual magnetic track
130 if(!_kln89->_obsMode) {
131 _kln89->DrawText("DTK", 2, 0, 1);
132 _kln89->DrawHeading((int)_kln89->_dtkMag, 2, 7, 1);
134 _kln89->DrawText("TK", 2, 9, 1);
135 if(_kln89->_groundSpeed_ms > 3) { // about 6 knots, don't know exactly what value to disable track
136 // The trouble with relying on FG gps's track value is we don't know when it's valid.
137 _kln89->DrawHeading((int)_kln89->_magTrackDeg, 2, 15, 1);
139 _kln89->DrawText("---", 2, 12, 1);
140 _kln89->DrawSpecialChar(0, 2, 15, 1);
143 // Radial to/from active waypoint.
144 // TODO - Not sure if this either is or should be true or mag!!!!!!!
145 if(!(crsr && blink && _uLinePos == 2)) {
147 _kln89->DrawHeading((int)_kln89->GetHeadingToActiveWaypoint(), 2, 4, 0);
148 _kln89->DrawText("To", 2, 5, 0);
149 } else if(1 == _vnv) {
150 _kln89->DrawHeading((int)_kln89->GetHeadingFromActiveWaypoint(), 2, 4, 0);
151 _kln89->DrawText("Fr", 2, 5, 0);
153 _kln89->DrawText("Vnv Off", 2, 1, 0);
156 // It seems that the floating point groundspeed must be at least 30kt
157 // for an ETA to be calculated. Note that this means that (integer) 30kt
158 // can appear in the frame 1 display both with and without an ETA displayed.
159 // TODO - need to switch off track (and heading bug change) based on instantaneous speed as well
160 // since the long gps lag filter means we can still be displaying when stopped on ground.
161 if(_kln89->_groundSpeed_kts > 30.0) {
162 // Assuming eta display is always hh:mm
163 // Does it ever switch to seconds when close?
164 if(_kln89->_eta / 3600.0 > 100.0) {
165 // More that 100 hours ! - Doesn't fit.
166 _kln89->DrawText("--:--", 2, 11, 0);
168 _kln89->DrawTime(_kln89->_eta, 2, 15, 0);
171 _kln89->DrawText("--:--", 2, 11, 0);
174 } else if(1 == _subPage) {
176 _kln89->DrawChar('>', 2, 1, 3);
177 if(!(crsr && blink && _uLinePos == 1)) _kln89->DrawText("PRESENT POSN", 2, 2, 3);
178 if(crsr && _uLinePos == 1) _kln89->Underline(2, 2, 3, 12);
179 if(0 == _posFormat) {
181 _kln89->DrawLatitude(lat, 2, 3, 1);
182 _kln89->DrawLongitude(lon, 2, 3, 0);
184 // Ref from wp - defaults to nearest vor (and resets to default when page left and re-entered).
186 } else if(2 == _subPage) {
187 _kln89->DrawText("Time", 2, 0, 3);
188 // TODO - hardwired to UTC at the moment
189 _kln89->DrawText("UTC", 2, 6, 3);
190 string th = fgGetString("/instrumentation/clock/indicated-hour");
191 string tm = fgGetString("/instrumentation/clock/indicated-min");
192 if(th.size() == 1) th = "0" + th;
193 if(tm.size() == 1) tm = "0" + tm;
194 _kln89->DrawText(th + tm, 2, 11, 3);
195 _kln89->DrawText("Depart", 2, 0, 2);
196 _kln89->DrawText(_kln89->_departureTimeString, 2, 11, 2);
197 _kln89->DrawText("ETA", 2, 0, 1);
198 if(_kln89->_departed) {
199 /* Rules of ETA waypoint are:
200 If the active waypoint is part of the active flightplan, then display
201 the ETA to the final (destination) waypoint of the active flightplan.
202 If the active waypoint is not part of the active flightplan, then
203 display the ETA to the active waypoint. */
204 // TODO - implement the above properly - we haven't below!
206 if(fp->waypoints.size()) {
207 wid = fp->waypoints[fp->waypoints.size() - 1]->id;
212 _kln89->DrawText(wid, 2, 4, 1);
213 double tsec = _kln89->GetTimeToWaypoint(wid);
215 _kln89->DrawText("----", 2, 11, 1);
217 int etah = (int)tsec / 3600;
218 int etam = ((int)tsec - etah * 3600) / 60;
219 etah += atoi(fgGetString("/instrumentation/clock/indicated-hour"));
220 etam += atoi(fgGetString("/instrumentation/clock/indicated-min"));
229 int n = snprintf(buf, 6, "%02i%02i", etah, etam);
230 _kln89->DrawText((string)buf, 2, 15-n, 1);
233 _kln89->DrawText("----", 2, 11, 1);
236 _kln89->DrawText("----", 2, 11, 1);
238 _kln89->DrawText("Flight", 2, 0, 0);
239 if(_kln89->_departed) {
240 int eh = (int)_kln89->_elapsedTime / 3600;
241 int em = ((int)_kln89->_elapsedTime - eh * 3600) / 60;
243 int n = snprintf(buf, 6, "%i:%02i", eh, em);
244 _kln89->DrawText((string)buf, 2, 15-n, 0);
246 _kln89->DrawText("-:--", 2, 11, 0);
248 } else { // if(3 == _subPage)
249 // Switch the cursor off if scan-pull is out on this page.
250 if(fgGetBool("/instrumentation/kln89/scan-pull")) { _kln89->_mode = KLN89_MODE_DISP; }
251 // The moving map page the core KLN89 class draws this.
252 if(_kln89->_mapOrientation == 2 && _kln89->_groundSpeed_kts < 2) {
253 // Don't draw it if in track up mode and groundspeed < 2kts, as per real-life unit.
255 _kln89->DrawMap(!_suspendAVS);
257 // Now draw any annotation over it.
258 int scale = KLN89MapScales[_kln89->_mapScaleUnits][_kln89->_mapScaleIndex];
259 string scle_str = GPSitoa(scale);
262 // Draw a background quad to encompass on/off for the first three at 'off' length
263 _kln89->DrawMapQuad(28, 9, 48, 36, true);
264 _kln89->DrawMapText("SUA:", 1, 27, true);
265 if(!(_menuPos == 0 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawSUA ? "on" : "off"), 29, 27, true);
266 if(_menuPos == 0) _kln89->DrawLine(28, 27, 48, 27);
267 _kln89->DrawMapText("VOR:", 1, 18, true);
268 if(!(_menuPos == 1 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawVOR ? "on" : "off"), 29, 18, true);
269 if(_menuPos == 1) _kln89->DrawLine(28, 18, 48, 18);
270 _kln89->DrawMapText("APT:", 1, 9, true);
271 if(!(_menuPos == 2 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawApt ? "on" : "off"), 29, 9, true);
272 if(_menuPos == 2) _kln89->DrawLine(28, 9, 48, 9);
273 _kln89->DrawMapQuad(0, 0, 27, 8, true);
274 if(!(_menuPos == 3 && _kln89->_blink)) {
275 if(_kln89->_mapOrientation == 0) {
276 _kln89->DrawMapText("N", 1, 0, true);
277 _kln89->DrawMapUpArrow(7, 1);
278 } else if(_kln89->_mapOrientation == 1) {
279 _kln89->DrawMapText("DTK", 1, 0, true);
280 _kln89->DrawMapUpArrow(21, 1);
282 // Don't bother with heading up for now!
283 _kln89->DrawMapText("TK", 1, 0, true);
284 _kln89->DrawMapUpArrow(14, 1);
287 if(_menuPos == 3) _kln89->DrawLine(0, 0, 27, 0);
290 if(!_kln89->_blink) {
291 _kln89->DrawMapText("Menu?", 1, 9, true);
293 _kln89->DrawLine(0, 9, 34, 9);
295 _kln89->DrawMapQuad(0, 9, 34, 17, true);
298 _kln89->DrawMapText("Menu?", 1, 9, true);
300 // right-justify the scale when _uLinePos == 3
301 if(!(_uLinePos == 3 && _kln89->_blink)) _kln89->DrawMapText(scle_str, (_uLinePos == 3 ? 29 - (scle_str.size() * 7) : 1), 0, true);
302 if(_uLinePos == 3) _kln89->DrawLine(0, 0, 27, 0);
305 // Just draw the scale
306 _kln89->DrawMapText(scle_str, 1, 0, true);
308 // If the scan-pull knob is out, draw one of the waypoints (if applicable).
309 if(fgGetBool("/instrumentation/kln89/scan-pull")) {
310 if(_kln89->_activeFP->waypoints.size()) {
311 //cout << "Need to draw a waypoint!\n";
312 _kln89->DrawLine(70, 0, 111, 0);
313 if(!_kln89->_blink) {
314 //_kln89->DrawMapQuad(45, 0, 97, 8, true);
316 _scanWpIndex = _kln89->GetActiveWaypointIndex();
319 _kln89->DrawMapText(_kln89->_activeFP->waypoints[_scanWpIndex]->id, 71, 0, true);
323 // And do part of the field 1 update, since NAV 4 is a special case for the last line.
324 _kln89->DrawChar('>', 1, 0, 0);
325 if(crsr && _uLinePos == 1) _kln89->Underline(1, 1, 0, 5);
326 if(!(crsr && _uLinePos == 1 && _kln89->_blink)) {
327 if(_kln89->_obsMode && _nav4DataSnippet == 0) _nav4DataSnippet = 1;
329 switch(_nav4DataSnippet) {
332 _kln89->DrawLabel("DTK", -39, 6);
333 // TODO - check we have an active FP / dtk and draw dashes if not.
335 snprintf(buf0, 4, "%03i", (int)(_kln89->_dtkMag));
336 _kln89->DrawText((string)buf0, 1, 3, 0);
340 _kln89->DrawSpeed(_kln89->_groundSpeed_kts, 1, 5, 0);
344 tsec = _kln89->GetETE();
346 _kln89->DrawText("--:--", 1, 1, 0);
348 int eteh = (int)tsec / 3600;
349 int etem = ((int)tsec - eteh * 3600) / 60;
351 int n = snprintf(buf, 6, "%02i:%02i", eteh, etem);
352 _kln89->DrawText((string)buf, 1, 6-n, 0);
356 // Cross-track correction
357 double x = _kln89->CalcCrossTrackDeviation();
359 _kln89->DrawSpecialChar(3, 1, 5, 0);
361 _kln89->DrawSpecialChar(7, 1, 5, 0);
365 x = fabs(x * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001));
367 n = snprintf(buf3, 6, "%0.2f", x);
368 } else if(x < 100.0) {
369 n = snprintf(buf3, 6, "%0.1f", x);
371 n = snprintf(buf3, 6, "%i", (int)(x+0.5));
373 _kln89->DrawText((string)buf3, 1, 5-n, 0);
379 KLN89Page::Update(dt);
382 // Returns the id string of the selected waypoint on NAV4 if valid, else returns an empty string.
383 string KLN89NavPage::GetNav4WpId() {
385 if(fgGetBool("/instrumentation/kln89/scan-pull")) {
386 if(_kln89->_activeFP->waypoints.size()) {
388 return(_kln89->_activeWaypoint.id);
390 return(_kln89->_activeFP->waypoints[_scanWpIndex]->id);
398 void KLN89NavPage::LooseFocus() {
403 void KLN89NavPage::CrsrPressed() {
404 if(_kln89->_mode == KLN89_MODE_DISP) {
405 // Crsr just switched off
408 // Crsr just switched on
417 void KLN89NavPage::EntPressed() {
418 if(_kln89->_mode == KLN89_MODE_CRSR) {
419 if(_subPage == 3 && _uLinePos == 2 && !_menuActive) {
427 void KLN89NavPage::ClrPressed() {
428 if(_kln89->_mode == KLN89_MODE_CRSR) {
432 if(_cdiFormat > 2) _cdiFormat = 0;
433 } else if(_uLinePos == 2) {
435 if(_vnv > 2) _vnv = 0;
440 _suspendAVS = !_suspendAVS;
442 } else if(_uLinePos == 1) {
444 if(_nav4DataSnippet > 3) _nav4DataSnippet = 0;
449 _suspendAVS = !_suspendAVS;
454 void KLN89NavPage::Knob1Left1() {
455 if(_kln89->_mode == KLN89_MODE_CRSR) {
456 if(!(_subPage == 3 && _menuActive)) {
457 if(_uLinePos > 0) _uLinePos--;
459 if(_menuPos > 0) _menuPos--;
464 void KLN89NavPage::Knob1Right1() {
465 if(_kln89->_mode == KLN89_MODE_CRSR) {
467 if(_uLinePos < 2) _uLinePos++;
468 } else if(_subPage == 2) {
471 // NAV 4 - this is complicated by whether the menu is displayed or not.
473 if(_menuPos < 3) _menuPos++;
475 if(_uLinePos < 3) _uLinePos++;
481 void KLN89NavPage::Knob2Left1() {
482 // If the inner-knob is out on the nav4 page, the only effect is to cycle the displayed waypoint.
483 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) {
484 if(_kln89->_activeFP->waypoints.size()) { // TODO - find out what happens when scan-pull is on on nav4 without an active FP.
485 // It's unlikely that we could get here without _scanWpSet, but theoretically possible, so we need to cover it.
487 _scanWpIndex = _kln89->GetActiveWaypointIndex();
490 if(0 == _scanWpIndex) {
491 _scanWpIndex = _kln89->_activeFP->waypoints.size() - 1;
499 if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
500 KLN89Page::Knob2Left1();
504 if(_uLinePos == 1 && _cdiFormat == 2) {
505 _kln89->CDIFSDIncrease();
507 } else if(_subPage == 3) {
510 _kln89->_drawSUA = !_kln89->_drawSUA;
511 } else if(_menuPos == 1) {
512 _kln89->_drawVOR = !_kln89->_drawVOR;
513 } else if(_menuPos == 2) {
514 _kln89->_drawApt = !_kln89->_drawApt;
516 if(_kln89->_mapOrientation == 0) {
517 // Don't allow heading up for now
518 _kln89->_mapOrientation = 2;
520 _kln89->_mapOrientation--;
522 _kln89->UpdateMapHeading();
524 } else if(_uLinePos == 3) {
526 if(_kln89->_mapScaleIndex == 0) {
527 _kln89->_mapScaleIndex = 20;
529 _kln89->_mapScaleIndex--;
535 void KLN89NavPage::Knob2Right1() {
536 // If the inner-knob is out on the nav4 page, the only effect is to cycle the displayed waypoint.
537 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) {
538 if(_kln89->_activeFP->waypoints.size()) { // TODO - find out what happens when scan-pull is on on nav4 without an active FP.
539 // It's unlikely that we could get here without _scanWpSet, but theoretically possible, so we need to cover it.
541 _scanWpIndex = _kln89->GetActiveWaypointIndex();
545 if(_scanWpIndex > _kln89->_activeFP->waypoints.size() - 1) {
552 if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
553 KLN89Page::Knob2Right1();
557 if(_uLinePos == 1 && _cdiFormat == 2) {
558 _kln89->CDIFSDDecrease();
560 } else if(_subPage == 3) {
563 _kln89->_drawSUA = !_kln89->_drawSUA;
564 } else if(_menuPos == 1) {
565 _kln89->_drawVOR = !_kln89->_drawVOR;
566 } else if(_menuPos == 2) {
567 _kln89->_drawApt = !_kln89->_drawApt;
569 if(_kln89->_mapOrientation >= 2) {
570 // Don't allow heading up for now
571 _kln89->_mapOrientation = 0;
573 _kln89->_mapOrientation++;
575 _kln89->UpdateMapHeading();
577 } else if(_uLinePos == 3) {
579 if(_kln89->_mapScaleIndex == 20) {
580 _kln89->_mapScaleIndex = 0;
582 _kln89->_mapScaleIndex++;