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?
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)
254 // Switch the cursor off if scan-pull is out on this page.
255 if(fgGetBool("/instrumentation/kln89/scan-pull")) { _kln89->_mode = KLN89_MODE_DISP; }
256 // The moving map page the core KLN89 class draws this.
257 if(_kln89->_mapOrientation == 2 && _kln89->_groundSpeed_kts < 2) {
258 // Don't draw it if in track up mode and groundspeed < 2kts, as per real-life unit.
260 _kln89->DrawMap(!_suspendAVS);
262 // Now draw any annotation over it.
263 int scale = KLN89MapScales[_kln89->_mapScaleUnits][_kln89->_mapScaleIndex];
264 string scle_str = GPSitoa(scale);
267 // Draw a background quad to encompass on/off for the first three at 'off' length
268 _kln89->DrawMapQuad(28, 9, 48, 36, true);
269 _kln89->DrawMapText("SUA:", 1, 27, true);
270 if(!(_menuPos == 0 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawSUA ? "on" : "off"), 29, 27, true);
271 if(_menuPos == 0) _kln89->DrawLine(28, 27, 48, 27);
272 _kln89->DrawMapText("VOR:", 1, 18, true);
273 if(!(_menuPos == 1 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawVOR ? "on" : "off"), 29, 18, true);
274 if(_menuPos == 1) _kln89->DrawLine(28, 18, 48, 18);
275 _kln89->DrawMapText("APT:", 1, 9, true);
276 if(!(_menuPos == 2 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawApt ? "on" : "off"), 29, 9, true);
277 if(_menuPos == 2) _kln89->DrawLine(28, 9, 48, 9);
278 _kln89->DrawMapQuad(0, 0, 27, 8, true);
279 if(!(_menuPos == 3 && _kln89->_blink)) {
280 if(_kln89->_mapOrientation == 0) {
281 _kln89->DrawMapText("N", 1, 0, true);
282 _kln89->DrawMapUpArrow(7, 1);
283 } else if(_kln89->_mapOrientation == 1) {
284 _kln89->DrawMapText("DTK", 1, 0, true);
285 _kln89->DrawMapUpArrow(21, 1);
287 // Don't bother with heading up for now!
288 _kln89->DrawMapText("TK", 1, 0, true);
289 _kln89->DrawMapUpArrow(14, 1);
292 if(_menuPos == 3) _kln89->DrawLine(0, 0, 27, 0);
295 if(!_kln89->_blink) {
296 _kln89->DrawMapText("Menu?", 1, 9, true);
298 _kln89->DrawLine(0, 9, 34, 9);
300 _kln89->DrawMapQuad(0, 9, 34, 17, true);
303 _kln89->DrawMapText("Menu?", 1, 9, true);
305 // right-justify the scale when _uLinePos == 3
306 if(!(_uLinePos == 3 && _kln89->_blink)) _kln89->DrawMapText(scle_str, (_uLinePos == 3 ? 29 - (scle_str.size() * 7) : 1), 0, true);
307 if(_uLinePos == 3) _kln89->DrawLine(0, 0, 27, 0);
310 // Just draw the scale
311 _kln89->DrawMapText(scle_str, 1, 0, true);
313 // If the scan-pull knob is out, draw one of the waypoints (if applicable).
314 if(fgGetBool("/instrumentation/kln89/scan-pull")) {
315 if(_kln89->_activeFP->waypoints.size()) {
316 //cout << "Need to draw a waypoint!\n";
317 _kln89->DrawLine(70, 0, 111, 0);
318 if(!_kln89->_blink) {
319 //_kln89->DrawMapQuad(45, 0, 97, 8, true);
321 _scanWpIndex = _kln89->GetActiveWaypointIndex();
324 _kln89->DrawMapText(_kln89->_activeFP->waypoints[_scanWpIndex]->id, 71, 0, true);
328 // And do part of the field 1 update, since NAV 4 is a special case for the last line.
329 _kln89->DrawChar('>', 1, 0, 0);
330 if(crsr && _uLinePos == 1) _kln89->Underline(1, 1, 0, 5);
331 if(!(crsr && _uLinePos == 1 && _kln89->_blink)) {
332 if(_kln89->_obsMode && _nav4DataSnippet == 0) _nav4DataSnippet = 1;
334 switch(_nav4DataSnippet) {
337 _kln89->DrawLabel("DTK", -39, 6);
338 // TODO - check we have an active FP / dtk and draw dashes if not.
340 snprintf(buf0, 4, "%03i", (int)(_kln89->_dtkMag));
341 _kln89->DrawText((string)buf0, 1, 3, 0);
345 _kln89->DrawSpeed(_kln89->_groundSpeed_kts, 1, 5, 0);
349 tsec = _kln89->GetETE();
351 _kln89->DrawText("--:--", 1, 1, 0);
353 int eteh = (int)tsec / 3600;
354 int etem = ((int)tsec - eteh * 3600) / 60;
356 int n = snprintf(buf, 6, "%02i:%02i", eteh, etem);
357 _kln89->DrawText((string)buf, 1, 6-n, 0);
361 // Cross-track correction
362 double x = _kln89->CalcCrossTrackDeviation();
364 _kln89->DrawSpecialChar(3, 1, 5, 0);
366 _kln89->DrawSpecialChar(7, 1, 5, 0);
370 x = fabs(x * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001));
372 n = snprintf(buf3, 6, "%0.2f", x);
373 } else if(x < 100.0) {
374 n = snprintf(buf3, 6, "%0.1f", x);
376 n = snprintf(buf3, 6, "%i", (int)(x+0.5));
378 _kln89->DrawText((string)buf3, 1, 5-n, 0);
384 KLN89Page::Update(dt);
387 // Returns the id string of the selected waypoint on NAV4 if valid, else returns an empty string.
388 string KLN89NavPage::GetNav4WpId() {
390 if(fgGetBool("/instrumentation/kln89/scan-pull")) {
391 if(_kln89->_activeFP->waypoints.size()) {
393 return(_kln89->_activeWaypoint.id);
395 return(_kln89->_activeFP->waypoints[_scanWpIndex]->id);
403 void KLN89NavPage::LooseFocus() {
408 void KLN89NavPage::CrsrPressed() {
409 if(_kln89->_mode == KLN89_MODE_DISP) {
410 // Crsr just switched off
413 // Crsr just switched on
422 void KLN89NavPage::EntPressed() {
423 if(_kln89->_mode == KLN89_MODE_CRSR) {
424 if(_subPage == 3 && _uLinePos == 2 && !_menuActive) {
432 void KLN89NavPage::ClrPressed() {
433 if(_kln89->_mode == KLN89_MODE_CRSR) {
437 if(_cdiFormat > 2) _cdiFormat = 0;
438 } else if(_uLinePos == 2) {
440 if(_vnv > 2) _vnv = 0;
445 _suspendAVS = !_suspendAVS;
447 } else if(_uLinePos == 1) {
449 if(_nav4DataSnippet > 3) _nav4DataSnippet = 0;
454 _suspendAVS = !_suspendAVS;
459 void KLN89NavPage::Knob1Left1() {
460 if(_kln89->_mode == KLN89_MODE_CRSR) {
461 if(!(_subPage == 3 && _menuActive)) {
462 if(_uLinePos > 0) _uLinePos--;
464 if(_menuPos > 0) _menuPos--;
469 void KLN89NavPage::Knob1Right1() {
470 if(_kln89->_mode == KLN89_MODE_CRSR) {
472 if(_uLinePos < 2) _uLinePos++;
473 } else if(_subPage == 2) {
476 // NAV 4 - this is complicated by whether the menu is displayed or not.
478 if(_menuPos < 3) _menuPos++;
480 if(_uLinePos < 3) _uLinePos++;
486 void KLN89NavPage::Knob2Left1() {
487 // If the inner-knob is out on the nav4 page, the only effect is to cycle the displayed waypoint.
488 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) {
489 if(_kln89->_activeFP->waypoints.size()) { // TODO - find out what happens when scan-pull is on on nav4 without an active FP.
490 // It's unlikely that we could get here without _scanWpSet, but theoretically possible, so we need to cover it.
492 _scanWpIndex = _kln89->GetActiveWaypointIndex();
495 if(0 == _scanWpIndex) {
496 _scanWpIndex = _kln89->_activeFP->waypoints.size() - 1;
504 if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
505 KLN89Page::Knob2Left1();
509 if(_uLinePos == 1 && _cdiFormat == 2) {
510 _kln89->CDIFSDIncrease();
512 } else if(_subPage == 3) {
515 _kln89->_drawSUA = !_kln89->_drawSUA;
516 } else if(_menuPos == 1) {
517 _kln89->_drawVOR = !_kln89->_drawVOR;
518 } else if(_menuPos == 2) {
519 _kln89->_drawApt = !_kln89->_drawApt;
521 if(_kln89->_mapOrientation == 0) {
522 // Don't allow heading up for now
523 _kln89->_mapOrientation = 2;
525 _kln89->_mapOrientation--;
527 _kln89->UpdateMapHeading();
529 } else if(_uLinePos == 3) {
531 if(_kln89->_mapScaleIndex == 0) {
532 _kln89->_mapScaleIndex = 20;
534 _kln89->_mapScaleIndex--;
540 void KLN89NavPage::Knob2Right1() {
541 // If the inner-knob is out on the nav4 page, the only effect is to cycle the displayed waypoint.
542 if(3 == _subPage && fgGetBool("/instrumentation/kln89/scan-pull")) {
543 if(_kln89->_activeFP->waypoints.size()) { // TODO - find out what happens when scan-pull is on on nav4 without an active FP.
544 // It's unlikely that we could get here without _scanWpSet, but theoretically possible, so we need to cover it.
546 _scanWpIndex = _kln89->GetActiveWaypointIndex();
550 if(_scanWpIndex > static_cast<int>(_kln89->_activeFP->waypoints.size()) - 1) {
557 if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
558 KLN89Page::Knob2Right1();
562 if(_uLinePos == 1 && _cdiFormat == 2) {
563 _kln89->CDIFSDDecrease();
565 } else if(_subPage == 3) {
568 _kln89->_drawSUA = !_kln89->_drawSUA;
569 } else if(_menuPos == 1) {
570 _kln89->_drawVOR = !_kln89->_drawVOR;
571 } else if(_menuPos == 2) {
572 _kln89->_drawApt = !_kln89->_drawApt;
574 if(_kln89->_mapOrientation >= 2) {
575 // Don't allow heading up for now
576 _kln89->_mapOrientation = 0;
578 _kln89->_mapOrientation++;
580 _kln89->UpdateMapHeading();
582 } else if(_uLinePos == 3) {
584 if(_kln89->_mapScaleIndex == 20) {
585 _kln89->_mapScaleIndex = 0;
587 _kln89->_mapScaleIndex++;