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"
26 KLN89NavPage::KLN89NavPage(KLN89* parent)
31 _posFormat = 0; // Check - should this default to ref from waypoint?
40 KLN89NavPage::~KLN89NavPage() {
43 void KLN89NavPage::Update(double dt) {
44 GPSFlightPlan* fp = ((KLN89*)_parent)->_activeFP;
45 GPSWaypoint* awp = _parent->GetActiveWaypoint();
46 bool crsr = (_kln89->_mode == KLN89_MODE_CRSR);
47 bool blink = _kln89->_blink;
48 double lat = _kln89->_gpsLat * SG_RADIANS_TO_DEGREES;
49 double lon = _kln89->_gpsLon * SG_RADIANS_TO_DEGREES;
52 if(_kln89->_navFlagged) {
53 _kln89->DrawText("> F L A G", 2, 0, 2);
54 _kln89->DrawText("DTK --- TK ---", 2, 0, 1);
55 _kln89->DrawText(">--- To --:--", 2, 0, 0);
56 _kln89->DrawSpecialChar(0, 2, 7, 1);
57 _kln89->DrawSpecialChar(0, 2, 15, 1);
58 _kln89->DrawSpecialChar(0, 2, 4, 0);
59 _kln89->DrawSpecialChar(1, 2, 3, 2);
60 _kln89->DrawSpecialChar(1, 2, 4, 2);
61 _kln89->DrawSpecialChar(1, 2, 6, 2);
62 _kln89->DrawSpecialChar(1, 2, 10, 2);
63 _kln89->DrawSpecialChar(1, 2, 12, 2);
64 _kln89->DrawSpecialChar(1, 2, 13, 2);
67 _kln89->DrawDTO(2, 7, 3);
69 if(!(_kln89->_waypointAlert && _kln89->_blink)) {
70 _kln89->DrawSpecialChar(3, 2, 8, 3);
73 _kln89->DrawText(awp->id, 2, 10, 3);
74 if(!_kln89->_dto && !_kln89->_obsMode && !_kln89->_fromWaypoint.id.empty()) {
75 _kln89->DrawText(_kln89->_fromWaypoint.id, 2, 1, 3);
77 if(!(crsr && blink && _uLinePos == 1)) {
80 } else if(_cdiFormat == 1) {
81 _kln89->DrawText("Fly", 2, 2, 2);
82 double x = _kln89->CalcCrossTrackDeviation();
83 // TODO - check the R/L from sign of x below - I *think* it holds but not sure!
84 // Note also that we're setting Fly R or L based on the aircraft
85 // position only, not the heading. Not sure if this is correct or not.
86 _kln89->DrawText(x < 0.0 ? "R" : "L", 2, 6, 2);
89 x = fabs(x * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001));
91 n = snprintf(buf, 6, "%0.2f", x);
92 } else if(x < 100.0) {
93 n = snprintf(buf, 6, "%0.1f", x);
95 n = snprintf(buf, 6, "%i", (int)(x+0.5));
97 _kln89->DrawText((string)buf, 2, 13-n, 2);
98 _kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "km", 2, 13, 2);
100 _kln89->DrawText("CDI Scale:", 2, 1, 2);
101 double d = _kln89->_cdiScales[_kln89->_currentCdiScaleIndex] * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001);
105 snprintf(buf, 4, "%2.1f", d);
108 snprintf(buf, 5, "%2.2f", d);
109 // trim the leading zero
111 s = s.substr(1, s.size() - 1);
113 _kln89->DrawText(s, 2, 11, 2);
114 _kln89->DrawText(_kln89->_distUnits == GPS_DIST_UNITS_NM ? "nm" : "km", 2, 14, 2);
117 _kln89->DrawChar('>', 2, 0, 2);
118 _kln89->DrawChar('>', 2, 0, 0);
120 if(_uLinePos == 1) _kln89->Underline(2, 1, 2, 15);
121 else if(_uLinePos == 2) _kln89->Underline(2, 1, 0, 9);
123 // Desired and actual magnetic track
124 if(!_kln89->_obsMode) {
125 _kln89->DrawText("DTK", 2, 0, 1);
126 _kln89->DrawHeading((int)_kln89->_dtkMag, 2, 7, 1);
128 _kln89->DrawText("TK", 2, 9, 1);
129 if(_kln89->_groundSpeed_ms > 3) { // about 6 knots, don't know exactly what value to disable track
130 // The trouble with relying on FG gps's track value is we don't know when it's valid.
131 _kln89->DrawHeading((int)_kln89->_magTrackDeg, 2, 15, 1);
133 _kln89->DrawText("---", 2, 12, 1);
134 _kln89->DrawSpecialChar(0, 2, 15, 1);
137 // Radial to/from active waypoint.
138 // TODO - Not sure if this either is or should be true or mag!!!!!!!
139 if(!(crsr && blink && _uLinePos == 2)) {
141 _kln89->DrawHeading((int)_kln89->GetHeadingToActiveWaypoint(), 2, 4, 0);
142 _kln89->DrawText("To", 2, 5, 0);
143 } else if(1 == _vnv) {
144 _kln89->DrawHeading((int)_kln89->GetHeadingFromActiveWaypoint(), 2, 4, 0);
145 _kln89->DrawText("Fr", 2, 5, 0);
147 _kln89->DrawText("Vnv Off", 2, 1, 0);
150 // It seems that the floating point groundspeed must be at least 30kt
151 // for an ETA to be calculated. Note that this means that (integer) 30kt
152 // can appear in the frame 1 display both with and without an ETA displayed.
153 // TODO - need to switch off track (and heading bug change) based on instantaneous speed as well
154 // since the long gps lag filter means we can still be displaying when stopped on ground.
155 if(_kln89->_groundSpeed_kts > 30.0) {
156 // Assuming eta display is always hh:mm
157 // Does it ever switch to seconds when close?
158 if(_kln89->_eta / 3600.0 > 100.0) {
159 // More that 100 hours ! - Doesn't fit.
160 _kln89->DrawText("--:--", 2, 11, 0);
162 _kln89->DrawTime(_kln89->_eta, 2, 15, 0);
165 _kln89->DrawText("--:--", 2, 11, 0);
168 } else if(1 == _subPage) {
170 _kln89->DrawChar('>', 2, 1, 3);
171 if(!(crsr && blink && _uLinePos == 1)) _kln89->DrawText("PRESENT POSN", 2, 2, 3);
172 if(crsr && _uLinePos == 1) _kln89->Underline(2, 2, 3, 12);
173 if(0 == _posFormat) {
175 _kln89->DrawLatitude(lat, 2, 3, 1);
176 _kln89->DrawLongitude(lon, 2, 3, 0);
178 // Ref from wp - defaults to nearest vor (and resets to default when page left and re-entered).
180 } else if(2 == _subPage) {
181 _kln89->DrawText("Time", 2, 0, 3);
182 // TODO - hardwired to UTC at the moment
183 _kln89->DrawText("UTC", 2, 6, 3);
184 string th = fgGetString("/instrumentation/clock/indicated-hour");
185 string tm = fgGetString("/instrumentation/clock/indicated-min");
186 if(th.size() == 1) th = "0" + th;
187 if(tm.size() == 1) tm = "0" + tm;
188 _kln89->DrawText(th + tm, 2, 11, 3);
189 _kln89->DrawText("Depart", 2, 0, 2);
190 _kln89->DrawText(_kln89->_departureTimeString, 2, 11, 2);
191 _kln89->DrawText("ETA", 2, 0, 1);
192 if(_kln89->_departed) {
193 /* Rules of ETA waypoint are:
194 If the active waypoint is part of the active flightplan, then display
195 the ETA to the final (destination) waypoint of the active flightplan.
196 If the active waypoint is not part of the active flightplan, then
197 display the ETA to the active waypoint. */
198 // TODO - implement the above properly - we haven't below!
200 if(fp->waypoints.size()) {
201 wid = fp->waypoints[fp->waypoints.size() - 1]->id;
206 _kln89->DrawText(wid, 2, 4, 1);
207 double tsec = _kln89->GetTimeToWaypoint(wid);
209 _kln89->DrawText("----", 2, 11, 1);
211 int etah = (int)tsec / 3600;
212 int etam = ((int)tsec - etah * 3600) / 60;
213 etah += atoi(fgGetString("/instrumentation/clock/indicated-hour"));
214 etam += atoi(fgGetString("/instrumentation/clock/indicated-min"));
223 int n = snprintf(buf, 6, "%02i%02i", etah, etam);
224 _kln89->DrawText((string)buf, 2, 15-n, 1);
227 _kln89->DrawText("----", 2, 11, 1);
230 _kln89->DrawText("----", 2, 11, 1);
232 _kln89->DrawText("Flight", 2, 0, 0);
233 if(_kln89->_departed) {
234 int eh = (int)_kln89->_elapsedTime / 3600;
235 int em = ((int)_kln89->_elapsedTime - eh * 3600) / 60;
237 int n = snprintf(buf, 6, "%i:%02i", eh, em);
238 _kln89->DrawText((string)buf, 2, 15-n, 0);
240 _kln89->DrawText("-:--", 2, 11, 0);
243 // The moving map page the core KLN89 class draws this.
244 if(_kln89->_mapOrientation == 2 && _kln89->_groundSpeed_kts < 2) {
245 // Don't draw it if in track up mode and groundspeed < 2kts, as per real-life unit.
247 _kln89->DrawMap(!_suspendAVS);
249 // Now draw any annotation over it.
250 int scale = KLN89MapScales[_kln89->_mapScaleUnits][_kln89->_mapScaleIndex];
251 string scle_str = GPSitoa(scale);
254 // Draw a background quad to encompass on/off for the first three at 'off' length
255 _kln89->DrawMapQuad(28, 9, 48, 36, true);
256 _kln89->DrawMapText("SUA:", 1, 27, true);
257 if(!(_menuPos == 0 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawSUA ? "on" : "off"), 29, 27, true);
258 if(_menuPos == 0) _kln89->DrawLine(28, 27, 48, 27);
259 _kln89->DrawMapText("VOR:", 1, 18, true);
260 if(!(_menuPos == 1 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawVOR ? "on" : "off"), 29, 18, true);
261 if(_menuPos == 1) _kln89->DrawLine(28, 18, 48, 18);
262 _kln89->DrawMapText("APT:", 1, 9, true);
263 if(!(_menuPos == 2 && _kln89->_blink)) _kln89->DrawMapText((_kln89->_drawApt ? "on" : "off"), 29, 9, true);
264 if(_menuPos == 2) _kln89->DrawLine(28, 9, 48, 9);
265 _kln89->DrawMapQuad(0, 0, 27, 8, true);
266 if(!(_menuPos == 3 && _kln89->_blink)) {
267 if(_kln89->_mapOrientation == 0) {
268 _kln89->DrawMapText("N", 1, 0, true);
269 _kln89->DrawMapUpArrow(7, 1);
270 } else if(_kln89->_mapOrientation == 1) {
271 _kln89->DrawMapText("DTK", 1, 0, true);
272 _kln89->DrawMapUpArrow(21, 1);
274 // Don't bother with heading up for now!
275 _kln89->DrawMapText("TK", 1, 0, true);
276 _kln89->DrawMapUpArrow(14, 1);
279 if(_menuPos == 3) _kln89->DrawLine(0, 0, 27, 0);
282 if(!_kln89->_blink) {
283 _kln89->DrawMapText("Menu?", 1, 9, true);
285 _kln89->DrawLine(0, 9, 34, 9);
287 _kln89->DrawMapQuad(0, 9, 34, 17, true);
290 _kln89->DrawMapText("Menu?", 1, 9, true);
292 // right-justify the scale when _uLinePos == 3
293 if(!(_uLinePos == 3 && _kln89->_blink)) _kln89->DrawMapText(scle_str, (_uLinePos == 3 ? 29 - (scle_str.size() * 7) : 1), 0, true);
294 if(_uLinePos == 3) _kln89->DrawLine(0, 0, 27, 0);
297 // Just draw the scale
298 _kln89->DrawMapText(scle_str, 1, 0, true);
300 // And do part of the field 1 update, since NAV 4 is a special case for the last line.
301 _kln89->DrawChar('>', 1, 0, 0);
302 if(crsr && _uLinePos == 1) _kln89->Underline(1, 1, 0, 5);
303 if(!(crsr && _uLinePos == 1 && _kln89->_blink)) {
304 if(_kln89->_obsMode && _nav4DataSnippet == 0) _nav4DataSnippet = 1;
306 switch(_nav4DataSnippet) {
309 _kln89->DrawLabel("DTK", -39, 6);
310 // TODO - check we have an active FP / dtk and draw dashes if not.
312 snprintf(buf0, 4, "%03i", (int)(_kln89->_dtkMag));
313 _kln89->DrawText((string)buf0, 1, 3, 0);
317 _kln89->DrawSpeed(_kln89->_groundSpeed_kts, 1, 5, 0);
321 tsec = _kln89->GetETE();
323 _kln89->DrawText("--:--", 1, 1, 0);
325 int eteh = (int)tsec / 3600;
326 int etem = ((int)tsec - eteh * 3600) / 60;
328 int n = snprintf(buf, 6, "%02i:%02i", eteh, etem);
329 _kln89->DrawText((string)buf, 1, 6-n, 0);
333 // Cross-track correction
334 double x = _kln89->CalcCrossTrackDeviation();
336 _kln89->DrawSpecialChar(3, 1, 5, 0);
338 _kln89->DrawSpecialChar(7, 1, 5, 0);
342 x = fabs(x * (_kln89->_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001));
344 n = snprintf(buf3, 6, "%0.2f", x);
345 } else if(x < 100.0) {
346 n = snprintf(buf3, 6, "%0.1f", x);
348 n = snprintf(buf3, 6, "%i", (int)(x+0.5));
350 _kln89->DrawText((string)buf3, 1, 5-n, 0);
356 KLN89Page::Update(dt);
359 void KLN89NavPage::LooseFocus() {
363 void KLN89NavPage::CrsrPressed() {
364 if(_kln89->_mode == KLN89_MODE_DISP) {
365 // Crsr just switched off
368 // Crsr just switched on
377 void KLN89NavPage::EntPressed() {
378 if(_kln89->_mode == KLN89_MODE_CRSR) {
379 if(_subPage == 3 && _uLinePos == 2 && !_menuActive) {
387 void KLN89NavPage::ClrPressed() {
388 if(_kln89->_mode == KLN89_MODE_CRSR) {
392 if(_cdiFormat > 2) _cdiFormat = 0;
393 } else if(_uLinePos == 2) {
395 if(_vnv > 2) _vnv = 0;
400 _suspendAVS = !_suspendAVS;
402 } else if(_uLinePos == 1) {
404 if(_nav4DataSnippet > 3) _nav4DataSnippet = 0;
409 _suspendAVS = !_suspendAVS;
414 void KLN89NavPage::Knob1Left1() {
415 if(_kln89->_mode == KLN89_MODE_CRSR) {
416 if(!(_subPage == 3 && _menuActive)) {
417 if(_uLinePos > 0) _uLinePos--;
419 if(_menuPos > 0) _menuPos--;
424 void KLN89NavPage::Knob1Right1() {
425 if(_kln89->_mode == KLN89_MODE_CRSR) {
427 if(_uLinePos < 2) _uLinePos++;
428 } else if(_subPage == 2) {
431 // NAV 4 - this is complicated by whether the menu is displayed or not.
433 if(_menuPos < 3) _menuPos++;
435 if(_uLinePos < 3) _uLinePos++;
441 void KLN89NavPage::Knob2Left1() {
442 if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
443 KLN89Page::Knob2Left1();
447 if(_uLinePos == 1 && _cdiFormat == 2) {
448 _kln89->CDIFSDIncrease();
450 } else if(_subPage == 3) {
453 _kln89->_drawSUA = !_kln89->_drawSUA;
454 } else if(_menuPos == 1) {
455 _kln89->_drawVOR = !_kln89->_drawVOR;
456 } else if(_menuPos == 2) {
457 _kln89->_drawApt = !_kln89->_drawApt;
459 if(_kln89->_mapOrientation == 0) {
460 // Don't allow heading up for now
461 _kln89->_mapOrientation = 2;
463 _kln89->_mapOrientation--;
465 _kln89->UpdateMapHeading();
467 } else if(_uLinePos == 3) {
469 if(_kln89->_mapScaleIndex == 0) {
470 _kln89->_mapScaleIndex = 20;
472 _kln89->_mapScaleIndex--;
478 void KLN89NavPage::Knob2Right1() {
479 if(_kln89->_mode != KLN89_MODE_CRSR || _uLinePos == 0) {
480 KLN89Page::Knob2Right1();
484 if(_uLinePos == 1 && _cdiFormat == 2) {
485 _kln89->CDIFSDDecrease();
487 } else if(_subPage == 3) {
490 _kln89->_drawSUA = !_kln89->_drawSUA;
491 } else if(_menuPos == 1) {
492 _kln89->_drawVOR = !_kln89->_drawVOR;
493 } else if(_menuPos == 2) {
494 _kln89->_drawApt = !_kln89->_drawApt;
496 if(_kln89->_mapOrientation >= 2) {
497 // Don't allow heading up for now
498 _kln89->_mapOrientation = 0;
500 _kln89->_mapOrientation++;
502 _kln89->UpdateMapHeading();
504 } else if(_uLinePos == 3) {
506 if(_kln89->_mapScaleIndex == 20) {
507 _kln89->_mapScaleIndex = 0;
509 _kln89->_mapScaleIndex++;