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?
41 KLN89NavPage::~KLN89NavPage() {
44 void KLN89NavPage::Update(double dt) {
45 GPSFlightPlan* fp = ((KLN89*)_parent)->_activeFP;
46 GPSWaypoint* awp = _parent->GetActiveWaypoint();
47 bool crsr = (_kln89->_mode == KLN89_MODE_CRSR);
48 bool blink = _kln89->_blink;
49 double lat = _kln89->_gpsLat * SG_RADIANS_TO_DEGREES;
50 double lon = _kln89->_gpsLon * SG_RADIANS_TO_DEGREES;
53 if(_kln89->_navFlagged) {
54 _kln89->DrawText("> F L A G", 2, 0, 2);
55 _kln89->DrawText("DTK --- TK ---", 2, 0, 1);
56 _kln89->DrawText(">--- To --:--", 2, 0, 0);
57 _kln89->DrawSpecialChar(0, 2, 7, 1);
58 _kln89->DrawSpecialChar(0, 2, 15, 1);
59 _kln89->DrawSpecialChar(0, 2, 4, 0);
60 _kln89->DrawSpecialChar(1, 2, 3, 2);
61 _kln89->DrawSpecialChar(1, 2, 4, 2);
62 _kln89->DrawSpecialChar(1, 2, 6, 2);
63 _kln89->DrawSpecialChar(1, 2, 10, 2);
64 _kln89->DrawSpecialChar(1, 2, 12, 2);
65 _kln89->DrawSpecialChar(1, 2, 13, 2);
68 _kln89->DrawDTO(2, 7, 3);
70 if(!(_kln89->_waypointAlert && _kln89->_blink)) {
71 _kln89->DrawSpecialChar(3, 2, 8, 3);
74 _kln89->DrawText(awp->id, 2, 10, 3);
75 if(!_kln89->_dto && !_kln89->_obsMode && !_kln89->_fromWaypoint.id.empty()) {
76 _kln89->DrawText(_kln89->_fromWaypoint.id, 2, 1, 3);
78 if(!(crsr && blink && _uLinePos == 1)) {
81 } else if(_cdiFormat == 1) {
82 _kln89->DrawText("Fly", 2, 2, 2);
83 double x = _kln89->CalcCrossTrackDeviation();
84 // TODO - check the R/L from sign of x below - I *think* it holds but not sure!
85 // Note also that we're setting Fly R or L based on the aircraft
86 // position only, not the heading. Not sure if this is correct or not.
87 _kln89->DrawText(x < 0.0 ? "R" : "L", 2, 6, 2);
90 x = fabs(x * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001));
92 n = snprintf(buf, 6, "%0.2f", x);
93 } else if(x < 100.0) {
94 n = snprintf(buf, 6, "%0.1f", x);
96 n = snprintf(buf, 6, "%i", (int)(x+0.5));
98 _kln89->DrawText((string)buf, 2, 13-n, 2);
99 _kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "km", 2, 13, 2);
101 _kln89->DrawText("CDI Scale:", 2, 1, 2);
102 double d = _kln89->_cdiScales[_kln89->_currentCdiScaleIndex] * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001);
106 snprintf(buf, 4, "%2.1f", d);
109 snprintf(buf, 5, "%2.2f", d);
110 // trim the leading zero
112 s = s.substr(1, s.size() - 1);
114 _kln89->DrawText(s, 2, 11, 2);
115 _kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "km", 2, 14, 2);
118 _kln89->DrawChar('>', 2, 0, 2);
119 _kln89->DrawChar('>', 2, 0, 0);
121 if(_uLinePos == 1) _kln89->Underline(2, 1, 2, 15);
122 else if(_uLinePos == 2) _kln89->Underline(2, 1, 0, 9);
124 // Desired and actual magnetic track
125 if(!_kln89->_obsMode) {
126 _kln89->DrawText("DTK", 2, 0, 1);
127 _kln89->DrawHeading((int)_kln89->_dtkMag, 2, 7, 1);
129 _kln89->DrawText("TK", 2, 9, 1);
130 if(_kln89->_groundSpeed_ms > 3) { // about 6 knots, don't know exactly what value to disable track
131 // The trouble with relying on FG gps's track value is we don't know when it's valid.
132 _kln89->DrawHeading((int)_kln89->_magTrackDeg, 2, 15, 1);
134 _kln89->DrawText("---", 2, 12, 1);
135 _kln89->DrawSpecialChar(0, 2, 15, 1);
138 // Radial to/from active waypoint.
139 // TODO - Not sure if this either is or should be true or mag!!!!!!!
140 if(!(crsr && blink && _uLinePos == 2)) {
142 _kln89->DrawHeading((int)_kln89->GetHeadingToActiveWaypoint(), 2, 4, 0);
143 _kln89->DrawText("To", 2, 5, 0);
144 } else if(1 == _vnv) {
145 _kln89->DrawHeading((int)_kln89->GetHeadingFromActiveWaypoint(), 2, 4, 0);
146 _kln89->DrawText("Fr", 2, 5, 0);
148 _kln89->DrawText("Vnv Off", 2, 1, 0);
151 // It seems that the floating point groundspeed must be at least 30kt
152 // for an ETA to be calculated. Note that this means that (integer) 30kt
153 // can appear in the frame 1 display both with and without an ETA displayed.
154 // TODO - need to switch off track (and heading bug change) based on instantaneous speed as well
155 // since the long gps lag filter means we can still be displaying when stopped on ground.
156 if(_kln89->_groundSpeed_kts > 30.0) {
157 // Assuming eta display is always hh:mm
158 // Does it ever switch to seconds when close?
159 if(_kln89->_eta / 3600.0 > 100.0) {
160 // More that 100 hours ! - Doesn't fit.
161 _kln89->DrawText("--:--", 2, 11, 0);
163 _kln89->DrawTime(_kln89->_eta, 2, 15, 0);
166 _kln89->DrawText("--:--", 2, 11, 0);
169 } else if(1 == _subPage) {
171 _kln89->DrawChar('>', 2, 1, 3);
172 if(!(crsr && blink && _uLinePos == 1)) _kln89->DrawText("PRESENT POSN", 2, 2, 3);
173 if(crsr && _uLinePos == 1) _kln89->Underline(2, 2, 3, 12);
174 if(0 == _posFormat) {
176 _kln89->DrawLatitude(lat, 2, 3, 1);
177 _kln89->DrawLongitude(lon, 2, 3, 0);
179 // Ref from wp - defaults to nearest vor (and resets to default when page left and re-entered).
181 } else if(2 == _subPage) {
182 _kln89->DrawText("Time", 2, 0, 3);
183 // TODO - hardwired to UTC at the moment
184 _kln89->DrawText("UTC", 2, 6, 3);
185 string th = fgGetString("/instrumentation/clock/indicated-hour");
186 string tm = fgGetString("/instrumentation/clock/indicated-min");
187 if(th.size() == 1) th = "0" + th;
188 if(tm.size() == 1) tm = "0" + tm;
189 _kln89->DrawText(th + tm, 2, 11, 3);
190 _kln89->DrawText("Depart", 2, 0, 2);
191 _kln89->DrawText(_kln89->_departureTimeString, 2, 11, 2);
192 _kln89->DrawText("ETA", 2, 0, 1);
193 if(_kln89->_departed) {
194 /* Rules of ETA waypoint are:
195 If the active waypoint is part of the active flightplan, then display
196 the ETA to the final (destination) waypoint of the active flightplan.
197 If the active waypoint is not part of the active flightplan, then
198 display the ETA to the active waypoint. */
199 // TODO - implement the above properly - we haven't below!
201 if(fp->waypoints.size()) {
202 wid = fp->waypoints[fp->waypoints.size() - 1]->id;
207 _kln89->DrawText(wid, 2, 4, 1);
208 double tsec = _kln89->GetTimeToWaypoint(wid);
210 _kln89->DrawText("----", 2, 11, 1);
212 int etah = (int)tsec / 3600;
213 int etam = ((int)tsec - etah * 3600) / 60;
214 etah += atoi(fgGetString("/instrumentation/clock/indicated-hour"));
215 etam += atoi(fgGetString("/instrumentation/clock/indicated-min"));
224 int n = snprintf(buf, 6, "%02i%02i", etah, etam);
225 _kln89->DrawText((string)buf, 2, 15-n, 1);
228 _kln89->DrawText("----", 2, 11, 1);
231 _kln89->DrawText("----", 2, 11, 1);
233 _kln89->DrawText("Flight", 2, 0, 0);
234 if(_kln89->_departed) {
235 int eh = (int)_kln89->_elapsedTime / 3600;
236 int em = ((int)_kln89->_elapsedTime - eh * 3600) / 60;
238 int n = snprintf(buf, 6, "%i:%02i", eh, em);
239 _kln89->DrawText((string)buf, 2, 15-n, 0);
241 _kln89->DrawText("-:--", 2, 11, 0);
244 // The moving map page the core KLN89 class draws this.
245 if(_kln89->_mapOrientation == 2 && _kln89->_groundSpeed_kts < 2) {
246 // Don't draw it if in track up mode and groundspeed < 2kts, as per real-life unit.
248 _kln89->DrawMap(!_suspendAVS);
250 // Now draw any annotation over it.
251 int scale = KLN89MapScales[_kln89->_mapScaleUnits][_kln89->_mapScaleIndex];
252 string scle_str = GPSitoa(scale);
255 // Draw a background quad to encompass on/off for the first three at 'off' length
256 _kln89->DrawMapQuad(28, 9, 48, 36, true);
257 _kln89->DrawMapText("SUA:", 1, 27, true);
258 if(!(_menuPos == 0 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawSUA ? "on" : "off"), 29, 27, true);
259 if(_menuPos == 0) _kln89->DrawLine(28, 27, 48, 27);
260 _kln89->DrawMapText("VOR:", 1, 18, true);
261 if(!(_menuPos == 1 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawVOR ? "on" : "off"), 29, 18, true);
262 if(_menuPos == 1) _kln89->DrawLine(28, 18, 48, 18);
263 _kln89->DrawMapText("APT:", 1, 9, true);
264 if(!(_menuPos == 2 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawApt ? "on" : "off"), 29, 9, true);
265 if(_menuPos == 2) _kln89->DrawLine(28, 9, 48, 9);
266 _kln89->DrawMapQuad(0, 0, 27, 8, true);
267 if(!(_menuPos == 3 && _kln89->_blink)) {
268 if(_kln89->_mapOrientation == 0) {
269 _kln89->DrawMapText("N", 1, 0, true);
270 _kln89->DrawMapUpArrow(7, 1);
271 } else if(_kln89->_mapOrientation == 1) {
272 _kln89->DrawMapText("DTK", 1, 0, true);
273 _kln89->DrawMapUpArrow(21, 1);
275 // Don't bother with heading up for now!
276 _kln89->DrawMapText("TK", 1, 0, true);
277 _kln89->DrawMapUpArrow(14, 1);
280 if(_menuPos == 3) _kln89->DrawLine(0, 0, 27, 0);
283 if(!_kln89->_blink) {
284 _kln89->DrawMapText("Menu?", 1, 9, true);
286 _kln89->DrawLine(0, 9, 34, 9);
288 _kln89->DrawMapQuad(0, 9, 34, 17, true);
291 _kln89->DrawMapText("Menu?", 1, 9, true);
293 // right-justify the scale when _uLinePos == 3
294 if(!(_uLinePos == 3 && _kln89->_blink)) _kln89->DrawMapText(scle_str, (_uLinePos == 3 ? 29 - (scle_str.size() * 7) : 1), 0, true);
295 if(_uLinePos == 3) _kln89->DrawLine(0, 0, 27, 0);
298 // Just draw the scale
299 _kln89->DrawMapText(scle_str, 1, 0, true);
301 // And do part of the field 1 update, since NAV 4 is a special case for the last line.
302 _kln89->DrawChar('>', 1, 0, 0);
303 if(crsr && _uLinePos == 1) _kln89->Underline(1, 1, 0, 5);
304 if(!(crsr && _uLinePos == 1 && _kln89->_blink)) {
305 if(_kln89->_obsMode && _nav4DataSnippet == 0) _nav4DataSnippet = 1;
307 switch(_nav4DataSnippet) {
310 _kln89->DrawLabel("DTK", -39, 6);
311 // TODO - check we have an active FP / dtk and draw dashes if not.
313 snprintf(buf0, 4, "%03i", (int)(_kln89->_dtkMag));
314 _kln89->DrawText((string)buf0, 1, 3, 0);
318 _kln89->DrawSpeed(_kln89->_groundSpeed_kts, 1, 5, 0);
322 tsec = _kln89->GetETE();
324 _kln89->DrawText("--:--", 1, 1, 0);
326 int eteh = (int)tsec / 3600;
327 int etem = ((int)tsec - eteh * 3600) / 60;
329 int n = snprintf(buf, 6, "%02i:%02i", eteh, etem);
330 _kln89->DrawText((string)buf, 1, 6-n, 0);
334 // Cross-track correction
335 double x = _kln89->CalcCrossTrackDeviation();
337 _kln89->DrawSpecialChar(3, 1, 5, 0);
339 _kln89->DrawSpecialChar(7, 1, 5, 0);
343 x = fabs(x * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001));
345 n = snprintf(buf3, 6, "%0.2f", x);
346 } else if(x < 100.0) {
347 n = snprintf(buf3, 6, "%0.1f", x);
349 n = snprintf(buf3, 6, "%i", (int)(x+0.5));
351 _kln89->DrawText((string)buf3, 1, 5-n, 0);
357 KLN89Page::Update(dt);
360 void KLN89NavPage::LooseFocus() {
364 void KLN89NavPage::CrsrPressed() {
365 if(_kln89->_mode == KLN89_MODE_DISP) {
366 // Crsr just switched off
369 // Crsr just switched on
378 void KLN89NavPage::EntPressed() {
379 if(_kln89->_mode == KLN89_MODE_CRSR) {
380 if(_subPage == 3 && _uLinePos == 2 && !_menuActive) {
388 void KLN89NavPage::ClrPressed() {
389 if(_kln89->_mode == KLN89_MODE_CRSR) {
393 if(_cdiFormat > 2) _cdiFormat = 0;
394 } else if(_uLinePos == 2) {
396 if(_vnv > 2) _vnv = 0;
401 _suspendAVS = !_suspendAVS;
403 } else if(_uLinePos == 1) {
405 if(_nav4DataSnippet > 3) _nav4DataSnippet = 0;
410 _suspendAVS = !_suspendAVS;
415 void KLN89NavPage::Knob1Left1() {
416 if(_kln89->_mode == KLN89_MODE_CRSR) {
417 if(!(_subPage == 3 && _menuActive)) {
418 if(_uLinePos > 0) _uLinePos--;
420 if(_menuPos > 0) _menuPos--;
425 void KLN89NavPage::Knob1Right1() {
426 if(_kln89->_mode == KLN89_MODE_CRSR) {
428 if(_uLinePos < 2) _uLinePos++;
429 } else if(_subPage == 2) {
432 // NAV 4 - this is complicated by whether the menu is displayed or not.
434 if(_menuPos < 3) _menuPos++;
436 if(_uLinePos < 3) _uLinePos++;
442 void KLN89NavPage::Knob2Left1() {
443 if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
444 KLN89Page::Knob2Left1();
448 if(_uLinePos == 1 && _cdiFormat == 2) {
449 _kln89->CDIFSDIncrease();
451 } else if(_subPage == 3) {
454 _kln89->_drawSUA = !_kln89->_drawSUA;
455 } else if(_menuPos == 1) {
456 _kln89->_drawVOR = !_kln89->_drawVOR;
457 } else if(_menuPos == 2) {
458 _kln89->_drawApt = !_kln89->_drawApt;
460 if(_kln89->_mapOrientation == 0) {
461 // Don't allow heading up for now
462 _kln89->_mapOrientation = 2;
464 _kln89->_mapOrientation--;
466 _kln89->UpdateMapHeading();
468 } else if(_uLinePos == 3) {
470 if(_kln89->_mapScaleIndex == 0) {
471 _kln89->_mapScaleIndex = 20;
473 _kln89->_mapScaleIndex--;
479 void KLN89NavPage::Knob2Right1() {
480 if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
481 KLN89Page::Knob2Right1();
485 if(_uLinePos == 1 && _cdiFormat == 2) {
486 _kln89->CDIFSDDecrease();
488 } else if(_subPage == 3) {
491 _kln89->_drawSUA = !_kln89->_drawSUA;
492 } else if(_menuPos == 1) {
493 _kln89->_drawVOR = !_kln89->_drawVOR;
494 } else if(_menuPos == 2) {
495 _kln89->_drawApt = !_kln89->_drawApt;
497 if(_kln89->_mapOrientation >= 2) {
498 // Don't allow heading up for now
499 _kln89->_mapOrientation = 0;
501 _kln89->_mapOrientation++;
503 _kln89->UpdateMapHeading();
505 } else if(_uLinePos == 3) {
507 if(_kln89->_mapScaleIndex == 20) {
508 _kln89->_mapScaleIndex = 0;
510 _kln89->_mapScaleIndex++;