1 // kln89_page.cxx - a class to manage the simulation of a KLN89
2 //                  GPS unit.  Note that this is primarily the
3 //                  simulation of the user interface and display
4 //                  - the core GPS calculations such as position
5 //                  and waypoint sequencing are done (or should
6 //                  be done) by FG code.
7 //
8 // Written by David Luff, started 2005.
9 //
10 // Copyright (C) 2005 - David C Luff - daveluff AT ntlworld.com
11 //
12 // This program is free software; you can redistribute it and/or
13 // modify it under the terms of the GNU General Public License as
14 // published by the Free Software Foundation; either version 2 of the
15 // License, or (at your option) any later version.
16 //
17 // This program is distributed in the hope that it will be useful, but
18 // WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20 // General Public License for more details.
21 //
22 // You should have received a copy of the GNU General Public License
23 // along with this program; if not, write to the Free Software
24 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
25 //
26 // $Id$
27 
28 #include "kln89.hxx"
29 #include "kln89_page.hxx"
30 #include "kln89_page_apt.hxx"
31 #include "kln89_page_vor.hxx"
32 #include "kln89_page_ndb.hxx"
33 #include "kln89_page_int.hxx"
34 #include "kln89_page_usr.hxx"
35 #include "kln89_page_act.hxx"
36 #include "kln89_page_nav.hxx"
37 #include "kln89_page_fpl.hxx"
38 #include "kln89_page_cal.hxx"
39 #include "kln89_page_set.hxx"
40 #include "kln89_page_oth.hxx"
41 #include "kln89_page_alt.hxx"
42 #include "kln89_page_dir.hxx"
43 #include "kln89_page_nrst.hxx"
44 #include "kln89_symbols.hxx"
45 #include <iostream>
46 
47 #include <Main/fg_props.hxx>
48 #include <simgear/structure/commands.hxx>
49 #include <Airports/airport.hxx>
50 
51 #include <cstdio>
52 
53 using std::cout;
54 using std::string;
55 
56 // Command callbacks for FlightGear
57 
do_kln89_msg_pressed(const SGPropertyNode * arg,SGPropertyNode * root)58 static bool do_kln89_msg_pressed(const SGPropertyNode * arg, SGPropertyNode * root) {
59 	//cout << "do_kln89_msg_pressed called!\n";
60 	KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
61 	gps->MsgPressed();
62 	return(true);
63 }
64 
do_kln89_obs_pressed(const SGPropertyNode * arg,SGPropertyNode * root)65 static bool do_kln89_obs_pressed(const SGPropertyNode * arg, SGPropertyNode * root) {
66 	//cout << "do_kln89_obs_pressed called!\n";
67 	KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
68 	gps->OBSPressed();
69 	return(true);
70 }
71 
do_kln89_alt_pressed(const SGPropertyNode * arg,SGPropertyNode * root)72 static bool do_kln89_alt_pressed(const SGPropertyNode * arg, SGPropertyNode * root) {
73 	//cout << "do_kln89_alt_pressed called!\n";
74 	KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
75 	gps->AltPressed();
76 	return(true);
77 }
78 
do_kln89_nrst_pressed(const SGPropertyNode * arg,SGPropertyNode * root)79 static bool do_kln89_nrst_pressed(const SGPropertyNode * arg, SGPropertyNode * root) {
80 	KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
81 	gps->NrstPressed();
82 	return(true);
83 }
84 
do_kln89_dto_pressed(const SGPropertyNode * arg,SGPropertyNode * root)85 static bool do_kln89_dto_pressed(const SGPropertyNode * arg, SGPropertyNode * root) {
86 	KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
87 	gps->DtoPressed();
88 	return(true);
89 }
90 
do_kln89_clr_pressed(const SGPropertyNode * arg,SGPropertyNode * root)91 static bool do_kln89_clr_pressed(const SGPropertyNode * arg, SGPropertyNode * root) {
92 	KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
93 	gps->ClrPressed();
94 	return(true);
95 }
96 
do_kln89_ent_pressed(const SGPropertyNode * arg,SGPropertyNode * root)97 static bool do_kln89_ent_pressed(const SGPropertyNode * arg, SGPropertyNode * root) {
98 	KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
99 	gps->EntPressed();
100 	return(true);
101 }
102 
do_kln89_crsr_pressed(const SGPropertyNode * arg,SGPropertyNode * root)103 static bool do_kln89_crsr_pressed(const SGPropertyNode * arg, SGPropertyNode * root) {
104 	KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
105 	gps->CrsrPressed();
106 	return(true);
107 }
108 
do_kln89_knob1left1(const SGPropertyNode * arg,SGPropertyNode * root)109 static bool do_kln89_knob1left1(const SGPropertyNode * arg, SGPropertyNode * root) {
110 	KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
111 	gps->Knob1Left1();
112 	return(true);
113 }
114 
do_kln89_knob1right1(const SGPropertyNode * arg,SGPropertyNode * root)115 static bool do_kln89_knob1right1(const SGPropertyNode * arg, SGPropertyNode * root) {
116 	KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
117 	gps->Knob1Right1();
118 	return(true);
119 }
120 
do_kln89_knob2left1(const SGPropertyNode * arg,SGPropertyNode * root)121 static bool do_kln89_knob2left1(const SGPropertyNode * arg, SGPropertyNode * root) {
122 	KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
123 	gps->Knob2Left1();
124 	return(true);
125 }
126 
do_kln89_knob2right1(const SGPropertyNode * arg,SGPropertyNode * root)127 static bool do_kln89_knob2right1(const SGPropertyNode * arg, SGPropertyNode * root) {
128 	KLN89* gps = (KLN89*)globals->get_subsystem("kln89");
129 	gps->Knob2Right1();
130 	return(true);
131 }
132 
133 // End command callbacks
134 
KLN89(RenderArea2D * instrument)135 KLN89::KLN89(RenderArea2D* instrument)
136 : DCLGPS(instrument) {
137 	_mode = KLN89_MODE_DISP;
138 	_blink = false;
139 	_cum_dt = 0.0;
140 	_nFields = 2;
141 	_maxFields = 2;
142 	_xBorder = 0;
143 	_yBorder = 4;
144 	// ..Field..[0] => no fields in action
145 	_xFieldBorder[0] = 0;
146 	_xFieldBorder[1] = 0;
147 	_yFieldBorder[0] = 0;
148 	_yFieldBorder[1] = 0;
149 	_xFieldBorder[2] = 2;
150 	_yFieldBorder[2] = 0;
151 	_xFieldStart[0] = 0;
152 	_xFieldStart[1] = 0;
153 	_xFieldStart[2] = 45;
154 	_yFieldStart[0] = 0;
155 	_yFieldStart[1] = 0;
156 	_yFieldStart[2] = 0;
157 
158 	//_pixelated = true;
159 	_pixelated = false;
160 
161 	// Cyclic pages
162 	_pages.clear();
163 	KLN89Page* apt_page = new KLN89AptPage(this);
164 	_pages.push_back(apt_page);
165 	KLN89Page* vor_page = new KLN89VorPage(this);
166 	_pages.push_back(vor_page);
167 	KLN89Page* ndb_page = new KLN89NDBPage(this);
168 	_pages.push_back(ndb_page);
169 	KLN89Page* int_page = new KLN89IntPage(this);
170 	_pages.push_back(int_page);
171 	KLN89Page* usr_page = new KLN89UsrPage(this);
172 	_pages.push_back(usr_page);
173 	KLN89Page* act_page = new KLN89ActPage(this);
174 	_pages.push_back(act_page);
175 	KLN89Page* nav_page = new KLN89NavPage(this);
176 	_pages.push_back(nav_page);
177 	KLN89Page* fpl_page = new KLN89FplPage(this);
178 	_pages.push_back(fpl_page);
179 	KLN89Page* cal_page = new KLN89CalPage(this);
180 	_pages.push_back(cal_page);
181 	KLN89Page* set_page = new KLN89SetPage(this);
182 	_pages.push_back(set_page);
183 	KLN89Page* oth_page = new KLN89OthPage(this);
184 	_pages.push_back(oth_page);
185 	_nPages = _pages.size();
186 	_curPage = 0;
187 
188 	// Other pages
189 	_alt_page = new KLN89AltPage(this);
190 	_dir_page = new KLN89DirPage(this);
191 	_nrst_page = new KLN89NrstPage(this);
192 
193 	_activePage = apt_page;
194 	_obsMode = false;
195 	_dto = false;
196 	_fullLegMode = true;
197 	_obsHeading = 215;
198 
199 	// User-settable configuration.  Eventually this should be user-achivable in order that settings can be persistent between sessions.
200 	_altUnits = GPS_ALT_UNITS_FT;
201 	_baroUnits = GPS_PRES_UNITS_IN;
202 	_velUnits = GPS_VEL_UNITS_KT;
203 	_distUnits = GPS_DIST_UNITS_NM;
204 	_suaAlertEnabled = false;
205 	_altAlertEnabled = false;
206 	_minDisplayBrightness = 4;
207 	_defaultFirstChar = 'A';
208 
209 	if(_baroUnits == GPS_PRES_UNITS_IN) {
210 		_userBaroSetting = 2992;
211 	} else {
212 		_userBaroSetting = 1013;
213 	}
214 
215 	_maxFlightPlans = 26;
216 	for(unsigned int i=0; i<_maxFlightPlans; ++i) {
217 		GPSFlightPlan* fp = new GPSFlightPlan;
218 		fp->waypoints.clear();
219 		_flightPlans.push_back(fp);
220 	}
221 	_activeFP = _flightPlans[0];
222 
223 	_entJump = _clrJump = -1;
224 	_jumpRestoreCrsr = false;
225 
226 	_dispMsg = false;
227 
228 	_dtoReview = false;
229 
230 	// Moving map stuff
231 	_mapOrientation = 0;
232 	_mapHeading = 0.0;
233 	_mapHeadingUpdateTimer = 0.0;
234 	_drawSUA = false;
235 	_drawVOR = false;
236 	_drawApt = true;
237 	//_mapScaleIndex = 20;
238 	_mapScaleIndex = 7;	// I think that the above is more accurate for no-flightplan default, but this is more sane for initial testing!
239 	_mapScaleAuto = true;
240 
241 	// Mega-hack - hardwire airport town and state names for the FG base area since we don't have any data for these at the moment
242 	// TODO - do this better one day!
243 	_airportTowns["KSFO"] = "San Francisco";
244 	_airportTowns["KSQL"] = "San Carlos";
245 	_airportTowns["KPAO"] = "Palo Alto";
246 	_airportTowns["KNUQ"] = "Mountain View";
247 	_airportTowns["KSJC"] = "San Jose";
248 	_airportTowns["KRHV"] = "San Jose";
249 	_airportTowns["E16"] = "San Martin";
250 	_airportTowns["KWVI"] = "Watsonville";
251 	_airportTowns["KOAK"] = "Oakland";
252 	_airportTowns["KHWD"] = "Hayward";
253 	_airportTowns["KLVK"] = "Livermore";
254 	_airportTowns["KCCR"] = "Concord";
255 	_airportTowns["KTCY"] = "Tracy";
256 	_airportTowns["KSCK"] = "Stockton";
257 	_airportTowns["KHAF"] = "Half Moon Bay";
258 
259 	_airportStates["KSFO"] = "CA";
260 	_airportStates["KSQL"] = "CA";
261 	_airportStates["KPAO"] = "CA";
262 	_airportStates["KNUQ"] = "CA";
263 	_airportStates["KSJC"] = "CA";
264 	_airportStates["KRHV"] = "CA";
265 	_airportStates["E16"] = "CA";
266 	_airportStates["KWVI"] = "CA";
267 	_airportStates["KOAK"] = "CA";
268 	_airportStates["KHWD"] = "CA";
269 	_airportStates["KLVK"] = "CA";
270 	_airportStates["KCCR"] = "CA";
271 	_airportStates["KTCY"] = "CA";
272 	_airportStates["KSCK"] = "CA";
273 	_airportStates["KHAF"] = "CA";
274 }
275 
~KLN89()276 KLN89::~KLN89() {
277 	for(unsigned int i=0; i<_pages.size(); ++i) {
278 		delete _pages[i];
279 	}
280 
281 	delete _alt_page;
282 	delete _dir_page;
283 	delete _nrst_page;
284 
285 	for(unsigned int i=0; i<_maxFlightPlans; ++i) {
286 		ClearFlightPlan(i);
287 		delete _flightPlans[i];
288 	}
289 }
290 
bind()291 void KLN89::bind() {
292 	fgTie("/instrumentation/gps/message-alert", this, &KLN89::GetMsgAlert);
293 	DCLGPS::bind();
294 }
295 
unbind()296 void KLN89::unbind() {
297 	fgUntie("/instrumentation/gps/message-alert");
298 	DCLGPS::unbind();
299 }
300 
init()301 void KLN89::init() {
302 	globals->get_commands()->addCommand("kln89_msg_pressed", do_kln89_msg_pressed);
303 	globals->get_commands()->addCommand("kln89_obs_pressed", do_kln89_obs_pressed);
304 	globals->get_commands()->addCommand("kln89_alt_pressed", do_kln89_alt_pressed);
305 	globals->get_commands()->addCommand("kln89_nrst_pressed", do_kln89_nrst_pressed);
306 	globals->get_commands()->addCommand("kln89_dto_pressed", do_kln89_dto_pressed);
307 	globals->get_commands()->addCommand("kln89_clr_pressed", do_kln89_clr_pressed);
308 	globals->get_commands()->addCommand("kln89_ent_pressed", do_kln89_ent_pressed);
309 	globals->get_commands()->addCommand("kln89_crsr_pressed", do_kln89_crsr_pressed);
310 	globals->get_commands()->addCommand("kln89_knob1left1", do_kln89_knob1left1);
311 	globals->get_commands()->addCommand("kln89_knob1right1", do_kln89_knob1right1);
312 	globals->get_commands()->addCommand("kln89_knob2left1", do_kln89_knob2left1);
313 	globals->get_commands()->addCommand("kln89_knob2right1", do_kln89_knob2right1);
314 
315 	DCLGPS::init();
316 }
317 
update(double dt)318 void KLN89::update(double dt) {
319 	// Run any positional calc's required first
320 	DCLGPS::update(dt);
321 
322 	// Set the display brightness.  This should be reduced in response to falling light
323 	// (i.e. nighttime), or the user covering the photocell that detects the light level.
324 	// At the moment I don't know how to detect nighttime or actual light level, so only
325 	// respond to the photocell being obscured.
326 	// TODO - reduce the brightness in response to nighttime / lowlight.
327 	float rgba[4] = {1.0, 0.0, 0.0, 1.0};
328 	if(fgGetBool("/instrumentation/kln89/photocell-obscured")) {
329 		rgba[0] -= (9 - _minDisplayBrightness) * 0.05;
330 	}
331 	_instrument->SetPixelColor(rgba);
332 
333 	_cum_dt += dt;
334 	if(_blink) {
335 		if(_cum_dt > 0.2) {
336 			_cum_dt = 0.0;
337 			_blink = false;
338 		}
339 	} else {
340 		if(_cum_dt > 0.8) {
341 			_cum_dt = 0.0;
342 			_blink = true;
343 		}
344 	}
345 
346 	_mapHeadingUpdateTimer += dt;
347 	if(_mapHeadingUpdateTimer > 1.0) {
348 		UpdateMapHeading();
349 		_mapHeadingUpdateTimer = 0.0;
350 	}
351 
352 	_instrument->Flush();
353 	_instrument->DrawBackground();
354 
355 	if(_dispMsg) {
356 		if(_messageStack.empty()) {
357 			DrawText("No Message", 0, 5, 2);
358 		} else {
359 			// TODO - parse the message string for special strings that indicate degrees signs etc!
360 			DrawText(*_messageStack.begin(), 0, 0, 3);
361 		}
362 		return;
363 	} else {
364 		if(!_messageStack.empty()) {
365 			DrawMessageAlert();
366 		}
367 	}
368 
369 	// Draw the indicator that shows which page we are on.
370 	if(_curPage == 6 && _activePage->GetSubPage() == 3) {
371 		// Don't draw the bar on the nav-4 page
372 	} else if((_activePage != _nrst_page) && (_activePage != _dir_page) && (_activePage != _alt_page) && (!_dispMsg)) {
373 		// Don't draw the bar on the NRST, DTO or MSG pages
374 		DrawBar(_curPage);
375 	}
376 
377 	_activePage->Update(dt);
378 }
379 
CreateDefaultFlightPlans()380 void KLN89::CreateDefaultFlightPlans() {
381 	// TODO - read these in from preferences.xml or similar instead!!!!
382 	// Create some hardwired default flightplans for testing.
383 	vector<string> ids;
384 	vector<GPSWpType> wps;
385 
386 	ids.clear();
387 	wps.clear();
388 	ids.push_back("KLSN");
389 	wps.push_back(GPS_WP_APT);
390 	ids.push_back("VOLTA");
391 	wps.push_back(GPS_WP_INT);
392 	ids.push_back("C83");
393 	wps.push_back(GPS_WP_APT);
394 	CreateFlightPlan(_flightPlans[5], ids, wps);
395 
396 	ids.clear();
397 	wps.clear();
398 	ids.push_back("KCCR");
399 	wps.push_back(GPS_WP_APT);
400 	ids.push_back("KHAF");
401 	wps.push_back(GPS_WP_APT);
402 	CreateFlightPlan(_flightPlans[4], ids, wps);
403 
404 	ids.clear();
405 	wps.clear();
406 	ids.push_back("KLVK");
407 	wps.push_back(GPS_WP_APT);
408 	ids.push_back("OAK");
409 	wps.push_back(GPS_WP_VOR);
410 	ids.push_back("PORTE");
411 	wps.push_back(GPS_WP_INT);
412 	ids.push_back("KHAF");
413 	wps.push_back(GPS_WP_APT);
414 	CreateFlightPlan(_flightPlans[3], ids, wps);
415 
416 	ids.clear();
417 	wps.clear();
418 	ids.push_back("KDPA");
419 	wps.push_back(GPS_WP_APT);
420 	ids.push_back("OBK");
421 	wps.push_back(GPS_WP_VOR);
422 	ids.push_back("ENW");
423 	wps.push_back(GPS_WP_VOR);
424 	ids.push_back("KRAC");
425 	wps.push_back(GPS_WP_APT);
426 	CreateFlightPlan(_flightPlans[2], ids, wps);
427 	//cout << "Size of FP2 WP list is " << _flightPlans[2]->waypoints.size() << '\n';
428 
429 	ids.clear();
430 	wps.clear();
431 	ids.push_back("KSFO");
432 	ids.push_back("KOAK");
433 	wps.push_back(GPS_WP_APT);
434 	wps.push_back(GPS_WP_APT);
435 	CreateFlightPlan(_flightPlans[1], ids, wps);
436 
437 	ids.clear();
438 	wps.clear();
439 	//ids.push_back("KOSH");
440 	ids.push_back("KSFO");
441 	ids.push_back("KHAF");
442 	ids.push_back("OSI");
443 	ids.push_back("KSQL");
444 	//ids.push_back("KPAO");
445 	//ids.push_back("KHWD");
446 	wps.push_back(GPS_WP_APT);
447 	wps.push_back(GPS_WP_APT);
448 	wps.push_back(GPS_WP_VOR);
449 	wps.push_back(GPS_WP_APT);
450 	//wps.push_back(GPS_WP_APT);
451 	//wps.push_back(GPS_WP_APT);
452 	CreateFlightPlan(_flightPlans[0], ids, wps);
453 
454 	/*
455 	ids.clear();
456 	wps.clear();
457 	ids.push_back("KLVK");
458 	ids.push_back("KHWD");
459 	wps.push_back(GPS_WP_APT);
460 	wps.push_back(GPS_WP_APT);
461 	CreateFlightPlan(_flightPlans[0], ids, wps);
462 	*/
463 }
464 
SetBaroUnits(int n,bool wrap)465 void KLN89::SetBaroUnits(int n, bool wrap) {
466 	if(n < 1) {
467 		_baroUnits = (KLN89PressureUnits)(wrap ? 3 : 1);
468 	} else if(n > 3) {
469 		_baroUnits = (KLN89PressureUnits)(wrap ? 1 : 3);
470 	} else {
471 		_baroUnits = (KLN89PressureUnits)n;
472 	}
473 }
474 
Knob1Right1()475 void KLN89::Knob1Right1() {
476 	if(_mode == KLN89_MODE_DISP) {
477 		_activePage->LooseFocus();
478 		if(_cleanUpPage >= 0) {
479 			_pages[(unsigned int)_cleanUpPage]->CleanUp();
480 			_cleanUpPage = -1;
481 		}
482 		_curPage++;
483 		if(_curPage >= _pages.size()) _curPage = 0;
484 		_activePage = _pages[_curPage];
485 	} else {
486 		_activePage->Knob1Right1();
487 	}
488 	update(0.0);
489 }
490 
Knob1Left1()491 void KLN89::Knob1Left1() {
492 	if(_mode == KLN89_MODE_DISP) {
493 		_activePage->LooseFocus();
494 		if(_cleanUpPage >= 0) {
495 			_pages[(unsigned int)_cleanUpPage]->CleanUp();
496 			_cleanUpPage = -1;
497 		}
498 		if(_curPage == 0) {
499 			_curPage = _pages.size() - 1;
500 		} else {
501 			_curPage--;
502 		}
503 		_activePage = _pages[_curPage];
504 	} else {
505 		_activePage->Knob1Left1();
506 	}
507 	update(0.0);
508 }
509 
Knob2Left1()510 void KLN89::Knob2Left1() {
511 	_activePage->Knob2Left1();
512 }
513 
Knob2Right1()514 void KLN89::Knob2Right1() {
515 	_activePage->Knob2Right1();
516 }
517 
CrsrPressed()518 void KLN89::CrsrPressed() {
519 	_dispMsg = false;
520 	// CRSR cannot be switched off on nrst page.
521 	if(_activePage == _nrst_page) { return; }
522 	// CRSR is always off when inner-knob is out on nav4 page.
523 	if(_curPage == 6 && _activePage->GetSubPage() == 3 && fgGetBool("/instrumentation/kln89/scan-pull")) { return; }
524 	if(_cleanUpPage >= 0) {
525 		_pages[(unsigned int)_cleanUpPage]->CleanUp();
526 		_cleanUpPage = -1;
527 	}
528 	_jumpRestoreCrsr = false;
529 	_entJump = _clrJump = -1;
530 	((KLN89Page*)_activePage)->SetEntInvert(false);
531 	if(_mode == KLN89_MODE_DISP) {
532 		_mode = KLN89_MODE_CRSR;
533 		_activePage->CrsrPressed();
534 	} else {
535 		_mode = KLN89_MODE_DISP;
536 		_activePage->CrsrPressed();
537 	}
538 	update(0.0);
539 }
540 
EntPressed()541 void KLN89::EntPressed() {
542 	if(_entJump >= 0) {
543 		if(_curPage < 5) {
544 			// one of the data pages.  Signal ent pressed to it here, and ent pressed to the call back page a few lines further down.
545 			// Ie. 2 ent pressed signals in this case is deliberate.
546 			_activePage->EntPressed();
547 		}
548 		_curPage = _entJump;
549 		_activePage = _pages[(unsigned int)_entJump];
550 		if(_jumpRestoreCrsr) _mode = KLN89_MODE_CRSR;
551 		_entJump = _clrJump = -1;
552 	}
553 	if(_activePage == _dir_page) {
554 		_dir_page->EntPressed();
555 		_mode = KLN89_MODE_DISP;
556 		_activePage = _pages[_curPage];
557 	} else {
558 		_activePage->EntPressed();
559 	}
560 }
561 
ClrPressed()562 void KLN89::ClrPressed() {
563 	if(_clrJump >= 0) {
564 		_curPage = _clrJump;
565 		_activePage = _pages[(unsigned int)_clrJump];
566 		if(_jumpRestoreCrsr) _mode = KLN89_MODE_CRSR;
567 		_entJump = _clrJump = -1;
568 	}
569 	_activePage->ClrPressed();
570 }
571 
DtoPressed()572 void KLN89::DtoPressed() {
573 	if(_activePage != _dir_page) {
574 		// Figure out which waypoint the dir page should display, according to the following rules:
575 		// 1. If the FPL 0 page is displayed AND the cursor is over one of the waypoints, display that waypoint.
576 		// 2. If the NAV 4 page is displayed with the inner knob pulled out, display the waypoint highlighted in the lower RH corner of the nav page.
577 		// 3. If any of APT, VOR, NDB, INT, USR or ACT pages is displayed then display the waypoint being viewed.
578 		// 4. If none of the above, display the active waypoint, unless the active waypoint is the MAP of an approach and it has been flown past
579 		// (no waypoint sequence past the MAP), in which case display the first waypoint of the missed approach procedure.
580 		// 5. If none of the above (i.e. no active waypoint) then display blanks.
581 		if(_curPage <= 5) {
582 			// APT, VOR, NDB, INT, USR or ACT
583 			if(!_activePage->GetId().empty()) {	// Guard against no user waypoints defined
584 				_dir_page->SetId(_activePage->GetId());
585 			} else {
586 				_dir_page->SetId(_activeWaypoint.id);
587 			}
588 		} else if(_curPage == 6 && _activePage->GetSubPage() == 3 && fgGetBool("/instrumentation/kln89/scan-pull") && ! _activeFP->waypoints.empty()) {
589 			// NAV 4
590 			_dir_page->SetId(((KLN89NavPage*)_activePage)->GetNav4WpId());
591 		} else if(_curPage == 7 && _activePage->GetSubPage() == 0 && _mode == KLN89_MODE_CRSR) {
592 			// FPL 0
593 			if(!_activePage->GetId().empty()) {
594 				//cout << "Not empty!!!\n";
595 				_dir_page->SetId(_activePage->GetId());
596 			} else {
597 				//cout << "empty :-(\n";
598 				_dir_page->SetId(_activeWaypoint.id);
599 			}
600 		} else {
601 			_dir_page->SetId(_activeWaypoint.id);
602 		}
603 		// This need to come after the bit before otherwise the FPL or NAV4 page clears their current ID when it looses focus.
604 		_activePage->LooseFocus();
605 		_activePage = _dir_page;
606 		_mode = KLN89_MODE_CRSR;
607 	}
608 }
609 
NrstPressed()610 void KLN89::NrstPressed() {
611 	if(_activePage != _nrst_page) {
612 		_activePage->LooseFocus();	// TODO - check whether we should call loose focus here
613 		_lastActivePage = _activePage;
614 		_activePage = _nrst_page;
615 		_lastMode = _mode;
616 		_mode = KLN89_MODE_CRSR;
617 	} else {
618 		_activePage = _lastActivePage;
619 		_mode = _lastMode;
620 	}
621 }
622 
AltPressed()623 void KLN89::AltPressed() {
624 	if(_activePage != _alt_page) {
625 		_activePage->LooseFocus();	// TODO - check whether we should call loose focus here
626 		_lastActivePage = _activePage;
627 		_alt_page->SetSubPage(0);
628 		_activePage = _alt_page;
629 		_lastMode = _mode;
630 		_mode = KLN89_MODE_CRSR;
631 	} else {
632 		_alt_page->LooseFocus();
633 		if(_alt_page->GetSubPage() == 0) {
634 			_alt_page->SetSubPage(1);
635 			_mode = KLN89_MODE_CRSR;
636 		} else {
637 			_activePage = _lastActivePage;
638 			_mode = _lastMode;
639 		}
640 	}
641 }
642 
OBSPressed()643 void KLN89::OBSPressed() {
644 	ToggleOBSMode();
645 	if(_obsMode) {
646 		if(!fgGetBool("/instrumentation/nav/slaved-to-gps")) {
647 			// NOTE: this only applies to ORS 02 firmware, in ORS 01
648 			// CRSR mode is not automatically set when OBS is started.
649 			_mode = KLN89_MODE_CRSR;
650 		}
651 		_activePage->OBSPressed();
652 	}
653 }
654 
MsgPressed()655 void KLN89::MsgPressed() {
656 	// TODO - handle persistent messages such as SUA alerting.
657 	// (The message annunciation flashes before first view, but afterwards remains continuously lit with the message available
658 	// until the potential conflict no longer pertains).
659 	if(_dispMsg && ! _messageStack.empty()) {
660 		_messageStack.pop_front();
661 	}
662 	_dispMsg = !_dispMsg;
663 }
664 
ToggleOBSMode()665 void KLN89::ToggleOBSMode() {
666 	DCLGPS::ToggleOBSMode();
667 }
668 
DtoInitiate(const string & id)669 void KLN89::DtoInitiate(const string& id) {
670 	_dtoReview = false;
671 	// Set the current page to NAV1
672 	_curPage = 6;
673 	_activePage = _pages[_curPage];
674 	_activePage->SetSubPage(0);
675 	// TODO - need to output a scratchpad message with the new course, but we don't know it yet!
676 	// Call the base class to actually initiate the DTO.
677 	DCLGPS::DtoInitiate(id);
678 }
679 
SetMinDisplayBrightness(int n)680 void KLN89::SetMinDisplayBrightness(int n) {
681 	_minDisplayBrightness = n;
682 	if(_minDisplayBrightness < 1) _minDisplayBrightness = 1;
683 	if(_minDisplayBrightness > 9) _minDisplayBrightness = 9;
684 }
685 
DecrementMinDisplayBrightness()686 void KLN89::DecrementMinDisplayBrightness() {
687 	_minDisplayBrightness--;
688 	if(_minDisplayBrightness < 1) _minDisplayBrightness = 1;
689 }
690 
IncrementMinDisplayBrightness()691 void KLN89::IncrementMinDisplayBrightness() {
692 	_minDisplayBrightness++;
693 	if(_minDisplayBrightness > 9) _minDisplayBrightness = 9;
694 }
695 
DrawBar(int page)696 void KLN89::DrawBar(int page) {
697 	int px = 1 + (page * 15);
698 	int py = 1;
699 	for(int i=0; i<7; ++i) {
700 		// Ugh - this is crude and inefficient!
701 		_instrument->DrawPixel(px+i, py);
702 		_instrument->DrawPixel(px+i, py+1);
703 	}
704 }
705 
706 // Convert moving map to instrument co-ordinates
MapToInstrument(int & x,int & y)707 void KLN89::MapToInstrument(int &x, int &y) {
708 	x += _xBorder + _xFieldBorder[2] + _xFieldStart[2];
709 }
710 
711 // Draw a pixel specified in instrument co-ords, but clipped to the map region
712 //void KLN89::DrawInstrMapPixel(int x, int y) {
713 
714 /*
715 // Clip, translate and draw a map pixel
716 // If we didn't need per-pixel clipping, it would be cheaper to translate object rather than pixel positions.
717 void KLN89::DrawMapPixel(int x, int y, bool invert) {
718 	if(x < 0 || x > 111 || y < 0 || y > 39)  return;
719 	x += _xBorder + _xFieldBorder[2] + _xFieldStart[2];
720 	_instrument->DrawPixel(x, y, invert);
721 }
722 */
723 
724 // HACK - use something FG provides
gps_min(const double & a,const double & b)725 static double gps_min(const double &a, const double &b) {
726 	return(a <= b ? a : b);
727 }
728 
729 #if 0
730 static double gps_max(const double &a, const double &b) {
731 	return(a >= b ? a : b);
732 }
733 #endif
734 
UpdateMapHeading()735 void KLN89::UpdateMapHeading() {
736 	switch(_mapOrientation) {
737 	case 0:		// North up
738 		_mapHeading = 0.0;
739 		break;
740 	case 1:		// DTK up
741 		_mapHeading = _dtkTrue;
742 		break;
743 	case 2:		// Track up
744 		_mapHeading = _track;
745 		break;
746 	}
747 }
748 
749 // The screen area allocated to the moving map is 111 x 40 pixels.
750 // In North up mode, the user position marker is at 57, 20. (Map co-ords).
DrawMap(bool draw_avs)751 void KLN89::DrawMap(bool draw_avs) {
752 	// Set the clipping region to the moving map part of the display
753 	int xstart = _xBorder + _xFieldBorder[2] + _xFieldStart[2];
754 	_instrument->SetClipRegion(xstart, 0, xstart + 110, 39);
755 
756 	_mapScaleUnits = (int)_distUnits;
757 	_mapScale = (double)(KLN89MapScales[_mapScaleUnits][_mapScaleIndex]);
758 
759 	//cout << "Map scale = " << _mapScale << '\n';
760 
761 	double mapScaleMeters = _mapScale * (_mapScaleUnits == 0 ? SG_NM_TO_METER : 1000);
762 
763 	// TODO - use an aligned projection when either DTK or TK up!
764 	AlignedProjection mapProj(SGGeod::fromRad(_gpsLon, _gpsLat), _mapHeading);
765 	double meter_per_pix = (_mapOrientation == 0 ? mapScaleMeters / 20.0f : mapScaleMeters / 29.0f);
766 //	SGGeod bottomLeft = mapProj.ConvertFromLocal(SGVec3d(gps_max(-57.0 * meter_per_pix, -50000), gps_max((_mapOrientation == 0 ? -20.0 * meter_per_pix : -11.0 * meter_per_pix), -25000), 0.0));
767 //	SGGeod topRight = mapProj.ConvertFromLocal(SGVec3d(gps_min(54.0 * meter_per_pix, 50000), gps_min((_mapOrientation == 0 ? 20.0 * meter_per_pix : 29.0 * meter_per_pix), 25000), 0.0));
768 
769 
770 
771 
772 	// Draw Airport labels first (but not one's that are waypoints)
773 	// Draw Airports first (but not one's that are waypoints)
774 	// Ditto for VORs (not sure if SUA/VOR/Airport ordering is important or not).
775 	// Ditto for SUA
776 	// Then flighttrack
777 	// Then waypoints
778 	// Then waypoint labels (not sure if this should be before or after waypoints)
779 	// Then user pos.
780 	// Annotation then gets drawn by Nav page, NOT this function.
781 
782 	if(_drawApt && draw_avs) {
783 		/*
784 		bool have_apt = _overlays->FindArpByRegion(&apt, bottomLeft.lat(), bottomLeft.lon(), topRight.lat(), topRight.lon());
785 		//cout << "Vors enclosed are: ";
786 		// Draw all the labels first...
787 		for(unsigned int i=0; i<apt.size(); ++i) {
788 			//cout << nav[i]->id << ' ';
789 			Point3D p = mapProj.ConvertToLocal(Point3D(apt[i]->lon * SG_RADIANS_TO_DEGREES, apt[i]->lat * SG_RADIANS_TO_DEGREES, 0.0));
790 			//cout << p << " .... ";
791 			int mx = int(p.x() / meter_per_pix) + 56;
792 			int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
793 			//cout << "mx = " << mx << ", my = " << my << '\n';
794 			bool right_align = (p.x() < 0.0);
795 			DrawLabel(apt[i]->id, mx + (right_align ? -2 : 3), my + (p.y() < 0.0 ? -7 : 3), right_align);
796 			// I think that we probably should have -1 in the right_align case above to match the real life instrument.
797 		}
798 		// ...and then all the Apts.
799 		for(unsigned int i=0; i<apt.size(); ++i) {
800 			//cout << nav[i]->id << ' ';
801 			Point3D p = mapProj.ConvertToLocal(Point3D(apt[i]->lon * SG_RADIANS_TO_DEGREES, apt[i]->lat * SG_RADIANS_TO_DEGREES, 0.0));
802 			//cout << p << " .... ";
803 			int mx = int(p.x() / meter_per_pix) + 56;
804 			int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
805 			//cout << "mx = " << mx << ", my = " << my << '\n';
806 			DrawApt(mx, my);
807 		}
808 		//cout << '\n';
809 		*/
810 	}
811 	/*
812 	if(_drawVOR && draw_avs) {
813 		Overlays::nav_array_type nav;
814 		bool have_vor = _overlays->FindVorByRegion(&nav, bottomLeft.lat(), bottomLeft.lon(), topRight.lat(), topRight.lon());
815 		//cout << "Vors enclosed are: ";
816 		// Draw all the labels first...
817 		for(unsigned int i=0; i<nav.size(); ++i) {
818 			//cout << nav[i]->id << ' ';
819 			Point3D p = mapProj.ConvertToLocal(Point3D(nav[i]->lon * SG_RADIANS_TO_DEGREES, nav[i]->lat * SG_RADIANS_TO_DEGREES, 0.0));
820 			//cout << p << " .... ";
821 			int mx = int(p.x() / meter_per_pix) + 56;
822 			int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
823 			//cout << "mx = " << mx << ", my = " << my << '\n';
824 			bool right_align = (p.x() < 0.0);
825 			DrawLabel(nav[i]->id, mx + (right_align ? -2 : 3), my + (p.y() < 0.0 ? -7 : 3), right_align);
826 			// I think that we probably should have -1 in the right_align case above to match the real life instrument.
827 		}
828 		// ...and then all the VORs.
829 		for(unsigned int i=0; i<nav.size(); ++i) {
830 			//cout << nav[i]->id << ' ';
831 			Point3D p = mapProj.ConvertToLocal(Point3D(nav[i]->lon * SG_RADIANS_TO_DEGREES, nav[i]->lat * SG_RADIANS_TO_DEGREES, 0.0));
832 			//cout << p << " .... ";
833 			int mx = int(p.x() / meter_per_pix) + 56;
834 			int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
835 			//cout << "mx = " << mx << ", my = " << my << '\n';
836 			DrawVOR(mx, my);
837 		}
838 		//cout << '\n';
839 	}
840 	*/
841 
842 	// FlightTrack
843 	if(_activeFP->waypoints.size() > 1) {
844 		vector<int> xvec, yvec, qvec;	// qvec stores the quadrant that each waypoint label should
845 										// be drawn in (relative to the waypoint).
846 										// 1 = NE, 2 = SE, 3 = SW, 4 = NW.
847 		double save_h = 0.0; // Each pass, save a heading from the previous one for label quadrant determination.
848 		bool drawTrack = true;
849 		for(unsigned int i=1; i<_activeFP->waypoints.size(); ++i) {
850 			GPSWaypoint* wp0 = _activeFP->waypoints[i-1];
851 			GPSWaypoint* wp1 = _activeFP->waypoints[i];
852 			SGVec3d p0 = mapProj.ConvertToLocal(SGGeod::fromRad(wp0->lon, wp0->lat));
853 			SGVec3d p1 = mapProj.ConvertToLocal(SGGeod::fromRad(wp1->lon, wp1->lat));
854 			int mx0 = int(p0.x() / meter_per_pix + 0.5) + 56;
855 			int my0 = int(p0.y() / meter_per_pix + 0.5) + (_mapOrientation == 0 ? 19 : 10);
856 			int mx1 = int(p1.x() / meter_per_pix + 0.5) + 56;
857 			int my1 = int(p1.y() / meter_per_pix + 0.5) + (_mapOrientation == 0 ? 19 : 10);
858 			if(i == 1) {
859 				xvec.push_back(mx0);
860 				yvec.push_back(my0);
861 				double h = GetGreatCircleCourse(wp0->lat, wp0->lon, wp1->lat, wp1->lon) * SG_RADIANS_TO_DEGREES;
862 				// Adjust for map orientation
863 				h -= _mapHeading;
864 				qvec.push_back(GetLabelQuadrant(h));
865 				//cout << "i = " << i << ", h = " << h << ", qvec[0] = " << qvec[0] << '\n';
866 			}
867 			xvec.push_back(mx1);
868 			yvec.push_back(my1);
869 			if(drawTrack) { DrawLine(mx0, my0, mx1, my1); }
870 			if(i != 1) {
871 				double h = GetGreatCircleCourse(wp0->lat, wp0->lon, wp1->lat, wp1->lon) * SG_RADIANS_TO_DEGREES;
872 				// Adjust for map orientation
873 				h -= _mapHeading;
874 				qvec.push_back(GetLabelQuadrant(save_h, h));
875 			}
876 			save_h = GetGreatCircleCourse(wp1->lat, wp1->lon, wp0->lat, wp0->lon) * SG_RADIANS_TO_DEGREES;
877 			// Adjust for map orientation
878 			save_h -= _mapHeading;
879 			if(i == _activeFP->waypoints.size() - 1) {
880 				qvec.push_back(GetLabelQuadrant(save_h));
881 			}
882 			// Don't draw flight track beyond the missed approach point of an approach
883 			if(_approachLoaded) {
884 				//cout << "Waypoints are " << wp0->id << " and " << wp1->id << '\n';
885 				//cout << "Types are " << wp0->appType << " and " << wp1->appType << '\n';
886 				if(wp1->appType == GPS_MAP) {
887 					drawTrack = false;
888 				}
889 			}
890 		}
891 		// ASSERT(xvec.size() == yvec.size() == qvec.size() == _activeFP->waypoints.size());
892 		for(unsigned int i=0; i<xvec.size(); ++i) {
893 			DrawWaypoint(xvec[i], yvec[i]);
894 			bool right_align = (qvec[i] > 2);
895 			bool top = (qvec[i] == 1 || qvec[i] == 4);
896 			// TODO - not sure if labels should be drawn in sequence with waypoints and flightpaths,
897 			// or all before or all afterwards.  Doesn't matter a huge deal though.
898 			DrawLabel(_activeFP->waypoints[i]->id, xvec[i] + (right_align ? -2 : 3), yvec[i] + (top ? 3 : -7), right_align);
899 		}
900 	}
901 
902 	// User pos
903 	if(_mapOrientation == 0) {
904 		// North up
905 		DrawUser1(56, 19);
906 	} else if(_mapOrientation == 1) {
907 		// DTK up
908 		DrawUser1(56, 10);
909 	} else if(_mapOrientation == 2) {
910 		// TK up
911 		DrawUser2(56, 10);
912 	} else {
913 		// Heading up
914 		// TODO - don't know what to do here!
915 	}
916 
917 	// And finally, reset the clip region to stop the rest of the code going pear-shaped!
918 	_instrument->ResetClipRegion();
919 }
920 
921 // Get the quadrant to draw the label of the start or end waypoint (i.e. one with only one track from it).
922 // Heading specified FROM the waypoint.
923 // 4 | 1
924 // -----
925 // 3 | 2
GetLabelQuadrant(double h)926 int KLN89::GetLabelQuadrant(double h) {
927 	while(h < 0.0) h += 360.0;
928 	while(h > 360.0) h -= 360.0;
929 	if(h < 90.0) return(3);
930 	if(h < 180.0) return(4);
931 	if(h < 270.0) return(1);
932 	return(2);
933 }
934 
935 // Get the quadrant to draw the label of an en-route waypoint,
936 // with BOTH tracks specified as headings FROM the waypoint.
937 // 4 | 1
938 // -----
939 // 3 | 2
GetLabelQuadrant(double h1,double h2)940 int KLN89::GetLabelQuadrant(double h1, double h2) {
941 	while(h1 < 0.0) h1 += 360.0;
942 	while(h1 > 360.0) h1 -= 360.0;
943 	while(h2 < 0.0) h2 += 360.0;
944 	while(h2 > 360.0) h2 -= 360.0;
945 	double max_min_diff = 0.0;
946 	int quad = 1;
947 	for(int i=0; i<4; ++i) {
948 		double h = 45 + (90 * i);
949 		double diff1 = fabs(h - h1);
950 		if(diff1 > 180) diff1 = 360 - diff1;
951 		double diff2 = fabs(h - h2);
952 		if(diff2 > 180) diff2 = 360 - diff2;
953 		double min_diff = gps_min(diff1, diff2);
954 		if(min_diff > max_min_diff) {
955 			max_min_diff = min_diff;
956 			quad = i + 1;
957 		}
958 	}
959 	//cout << "GetLabelQuadrant, h1 = " << h1 << ", h2 = " << h2 << ", quad = " << quad << '\n';
960 	return(quad);
961 }
962 
963 // Draw the diamond style of user pos
964 //
965 //    o
966 //   oxo
967 //  oxxxo
968 // oxxxxxo
969 //  oxxxo
970 //   oxo
971 //    o
972 //
DrawUser1(int x,int y)973 void KLN89::DrawUser1(int x, int y) {
974 	MapToInstrument(x, y);
975 	int min_j = 0, max_j = 0;
976 	for(int i=-3; i<=3; ++i) {
977 		for(int j=min_j; j<=max_j; ++j) {
978 			_instrument->DrawPixel(x+j, y+i, (j == min_j || j == max_j ? true : false));
979 		}
980 		if(i < 0) {
981 			min_j--;
982 			max_j++;
983 		} else {
984 			min_j++;
985 			max_j--;
986 		}
987 	}
988 }
989 
990 // Draw the airplane style of user pos
991 // Define the origin to be the midpoint of the *fuselage*
DrawUser2(int x,int y)992 void KLN89::DrawUser2(int x, int y) {
993 	MapToInstrument(x, y);
994 
995 	// Draw the background as three black quads first
996 	_instrument->DrawQuad(x-2, y-3, x+2, y-1, true);
997 	_instrument->DrawQuad(x-3, y, x+3, y+2, true);
998 	_instrument->DrawQuad(x-1, y+3, x+1, y+3, true);
999 
1000 	if(_pixelated) {
1001 		for(int j=y-2; j<=y+2; ++j) {
1002 			_instrument->DrawPixel(x, j);
1003 		}
1004 		for(int i=x-1; i<=x+1; ++i) {
1005 			_instrument->DrawPixel(i, y-2);
1006 		}
1007 		for(int i=x-2; i<=x+2; ++i) {
1008 			_instrument->DrawPixel(i, y+1);
1009 		}
1010 	} else {
1011 		_instrument->DrawQuad(x, y-2, x, y+2);
1012 		_instrument->DrawQuad(x-1, y-2, x+1, y-2);
1013 		_instrument->DrawQuad(x-2, y+1, x+2, y+1);
1014 	}
1015 }
1016 
1017 // Draw an airport symbol on the moving map
1018 //
1019 //  ooo
1020 // ooxoo
1021 // oxxxo
1022 // ooxoo
1023 //  ooo
1024 //
DrawApt(int x,int y)1025 void KLN89::DrawApt(int x, int y) {
1026 	MapToInstrument(x, y);
1027 
1028 	int j = y-2;
1029 	int i;
1030 	for(i=x-1; i<=x+1; ++i) _instrument->DrawPixel(i, j, true);
1031 	++j;
1032 	for(i=x-2; i<=x+2; ++i) _instrument->DrawPixel(i, j, (i != x ? true : false));
1033 	++j;
1034 	for(i=x-2; i<=x+2; ++i) _instrument->DrawPixel(i, j, (std::abs(i - x) > 1 ? true : false));
1035 	++j;
1036 	for(i=x-2; i<=x+2; ++i) _instrument->DrawPixel(i, j, (i != x ? true : false));
1037 	++j;
1038 	for(i=x-1; i<=x+1; ++i) _instrument->DrawPixel(i, j, true);
1039 }
1040 
1041 // Draw a waypoint on the moving map
1042 //
1043 // ooooo
1044 // oxxxo
1045 // oxxxo
1046 // oxxxo
1047 // ooooo
1048 //
DrawWaypoint(int x,int y)1049 void KLN89::DrawWaypoint(int x, int y) {
1050 	MapToInstrument(x, y);
1051 	_instrument->SetDebugging(true);
1052 
1053 	// Draw black background
1054 	_instrument->DrawQuad(x-2, y-2, x+2, y+2, true);
1055 
1056 	// Draw the coloured square
1057 	if(_pixelated) {
1058 		for(int i=x-1; i<=x+1; ++i) {
1059 			for(int j=y-1; j<=y+1; ++j) {
1060 				_instrument->DrawPixel(i, j);
1061 			}
1062 		}
1063 	} else {
1064 		_instrument->DrawQuad(x-1, y-1, x+1, y+1);
1065 	}
1066 	_instrument->SetDebugging(false);
1067 }
1068 
1069 // Draw a VOR on the moving map
1070 //
1071 // ooooo
1072 // oxxxo
1073 // oxoxo
1074 // oxxxo
1075 // ooooo
1076 //
DrawVOR(int x,int y)1077 void KLN89::DrawVOR(int x, int y) {
1078 	// Cheat - draw a waypoint and then a black pixel in the middle.
1079 	// Need to call Waypoint draw *before* translating co-ords.
1080 	DrawWaypoint(x, y);
1081 	MapToInstrument(x, y);
1082 	_instrument->DrawPixel(x, y, true);
1083 }
1084 
1085 // Draw a line on the moving map
DrawLine(int x1,int y1,int x2,int y2)1086 void KLN89::DrawLine(int x1, int y1, int x2, int y2) {
1087 	MapToInstrument(x1, y1);
1088 	MapToInstrument(x2, y2);
1089 	_instrument->DrawLine(x1, y1, x2, y2);
1090 }
1091 
DrawMapUpArrow(int x,int y)1092 void KLN89::DrawMapUpArrow(int x, int y) {
1093 	MapToInstrument(x, y);
1094 	if(_pixelated) {
1095 		for(int j=0; j<7; ++j) {
1096 			_instrument->DrawPixel(x + 2, y + j);
1097 		}
1098 	} else {
1099 		_instrument->DrawQuad(x+2, y, x+2, y+6);
1100 	}
1101 	_instrument->DrawPixel(x, y+4);
1102 	_instrument->DrawPixel(x+1, y+5);
1103 	_instrument->DrawPixel(x+3, y+5);
1104 	_instrument->DrawPixel(x+4, y+4);
1105 }
1106 
1107 // Draw a quad on the moving map
DrawMapQuad(int x1,int y1,int x2,int y2,bool invert)1108 void KLN89::DrawMapQuad(int x1, int y1, int x2, int y2, bool invert) {
1109 	MapToInstrument(x1, y1);
1110 	MapToInstrument(x2, y2);
1111 	_instrument->DrawQuad(x1, y1, x2, y2, invert);
1112 }
1113 
1114 // Draw an airport or waypoint label on the moving map
1115 // Specify position by the map pixel co-ordinate of the left or right, bottom, of the *visible* portion of the label.
1116 // The black background quad will automatically overlap this by 1 pixel.
DrawLabel(const string & s,int x1,int y1,bool right_align)1117 void KLN89::DrawLabel(const string& s, int x1, int y1, bool right_align) {
1118 	MapToInstrument(x1, y1);
1119 	if(!right_align) {
1120 		for(unsigned int i=0; i<s.size(); ++i) {
1121 			char c = s[i];
1122 			x1 += DrawSmallChar(c, x1, y1);
1123 			x1 ++;
1124 		}
1125 	} else {
1126 		for(int i=(int)(s.size()-1); i>=0; --i) {
1127 			char c = s[i];
1128 			x1 -= DrawSmallChar(c, x1, y1, right_align);
1129 			x1--;
1130 		}
1131 	}
1132 }
1133 
DrawCDI()1134 void KLN89::DrawCDI() {
1135 	// Scale
1136 	for(int i=0; i<5; ++i) {
1137 		DrawSpecialChar(2, 2, 3+i, 2);
1138 		DrawSpecialChar(1, 2, 9+i, 2);
1139 	}
1140 
1141 	int field = 2;
1142 	int px = 8 * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field] + 2;
1143 	int py = 2 * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1144 
1145 	// Deflection bar
1146 	// Every 7 pixels deflection left or right is one dot on the scale, and hence 1/5 FSD.
1147 	// Maximum deflection is 37 pixels left, or 38 pixels right !?!
1148 	double xtd = CalcCrossTrackDeviation();
1149 	int deflect;
1150 	if(_cdiScaleTransition) {
1151 		double dots = (xtd / _currentCdiScale) * 5.0;
1152 		deflect = (int)(dots * 7.0 * -1.0);
1153 		// TODO - for all these I think I should add 0.5 before casting to int, and *then* multiply by -1.  Possibly!
1154 	} else {
1155 		if(0 == _currentCdiScaleIndex) {	// 5.0nm FSD => 1 nm per dot => 7 pixels per nm.
1156 			deflect = (int)(xtd * 7.0 * -1.0);	// The -1.0 is because we move the 'needle' indicating the course, not the plane.
1157 		} else if(1 == _currentCdiScaleIndex) {
1158 			deflect = (int)(xtd * 35.0 * -1.0);
1159 		} else {	// 0.3 == _cdiScale
1160 			deflect = (int)(xtd * 116.6666666667 * -1.0);
1161 		}
1162 	}
1163 	if(deflect > 38) deflect = 38;
1164 	if(deflect < -37) deflect = -37;
1165 	if(_pixelated) {
1166 		for(int j=0; j<9; ++j) {
1167 			_instrument->DrawPixel(px + deflect, py+j);
1168 			_instrument->DrawPixel(px + deflect + 1, py+j);
1169 		}
1170 	} else {
1171 		_instrument->DrawQuad(px + deflect, py, px + deflect + 1, py + 8);
1172 	}
1173 
1174 	// To/From indicator
1175 	px-=4;
1176 	py+=2;
1177 	for(int j=4; j>=0; --j) {
1178 		int k = 10 - (2*j);
1179 		for(int i=0; i<k; ++i) {
1180 			_instrument->DrawPixel(px+j+i, (_headingBugTo ? py+j : py+4-j));
1181 			// At the extremities, draw the outlining dark pixel
1182 			if(i == 0 || i == k-1) {
1183 				_instrument->DrawPixel(px+j+i, (_headingBugTo ? py+j+1 : py+3-j), true);
1184 			}
1185 		}
1186 	}
1187 }
1188 
DrawLegTail(int py)1189 void KLN89::DrawLegTail(int py) {
1190 	int px = 0 * 7 + _xBorder + _xFieldBorder[2] + _xFieldStart[2];
1191 	py = py * 9 + _yBorder + _yFieldBorder[2] + _yFieldStart[2];
1192 
1193 	px++;
1194 	py+=3;
1195 	py++;	// Hack - not sure if this represents a border issue.
1196 
1197 	for(int i=0; i<9; ++i) _instrument->DrawPixel(px, py+i);
1198 	for(int i2=0; i2<5; ++i2) _instrument->DrawPixel(px+i2, py+9);
1199 }
1200 
DrawLongLegTail(int py)1201 void KLN89::DrawLongLegTail(int py) {
1202 	int px = 0 * 7 + _xBorder + _xFieldBorder[2] + _xFieldStart[2];
1203 	py = py * 9 + _yBorder + _yFieldBorder[2] + _yFieldStart[2];
1204 
1205 	px++;
1206 	py+=3;
1207 	py++;	// Hack - not sure if this represents a border issue.
1208 
1209 	for(int i=0; i<18; ++i) _instrument->DrawPixel(px, py+i);
1210 	for(int i2=0; i2<5; ++i2) _instrument->DrawPixel(px+i2, py+18);
1211 }
1212 
DrawHalfLegTail(int py)1213 void KLN89::DrawHalfLegTail(int py) {
1214 }
1215 
DrawDivider()1216 void KLN89::DrawDivider() {
1217 	int px = _xFieldStart[2] - 1;
1218 	int py = _yBorder;
1219 	for(int i=0; i<36; ++i) {
1220 		_instrument->DrawPixel(px, py+i);
1221 	}
1222 }
1223 
DrawEnt(int field,int px,int py)1224 void KLN89::DrawEnt(int field, int px, int py) {
1225 	px = px * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1226 	py = py * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field] + 1;
1227 
1228 	px++;	// Not sure why we need px++, but it seems to work!
1229 	py++;
1230 
1231 	// E
1232 	for(int i=0; i<5; ++i) _instrument->DrawPixel(px, py+i);
1233 	_instrument->DrawPixel(px+1, py);
1234 	_instrument->DrawPixel(px+2, py);
1235 	_instrument->DrawPixel(px+1, py+2);
1236 	_instrument->DrawPixel(px+1, py+4);
1237 	_instrument->DrawPixel(px+2, py+4);
1238 
1239 	px += 4;
1240 	// N
1241 	for(int i=0; i<4; ++i) _instrument->DrawPixel(px, py+i);
1242 	_instrument->DrawPixel(px+1, py+2);
1243 	_instrument->DrawPixel(px+2, py+1);
1244 	for(int i=0; i<4; ++i) _instrument->DrawPixel(px+3, py+i);
1245 
1246 	px += 5;
1247 	// T
1248 	_instrument->DrawPixel(px, py+3);
1249 	for(int i=0; i<4; ++i) _instrument->DrawPixel(px+1, py+i);
1250 	_instrument->DrawPixel(px+2, py+3);
1251 }
1252 
DrawMessageAlert()1253 void KLN89::DrawMessageAlert() {
1254 	// TODO - draw the proper message indicator
1255 	if(!_blink) {
1256 		int px = _xBorder + _xFieldBorder[1] + _xFieldStart[1];
1257 		int py = 1 * 9 + _yBorder + _yFieldBorder[1] + _yFieldStart[1] + 1;
1258 
1259 		px++;	// Not sure why we need px++, but it seems to work!
1260 		py++;
1261 
1262 		DrawText("  ", 1, 0, 1, false, 99);
1263 		_instrument->DrawQuad(px+1, py-1, px+2, py+5, true);
1264 		_instrument->DrawQuad(px+3, py+3, px+3, py+5, true);
1265 		_instrument->DrawQuad(px+4, py+2, px+4, py+4, true);
1266 		_instrument->DrawQuad(px+5, py+1, px+6, py+3, true);
1267 		_instrument->DrawQuad(px+7, py+2, px+7, py+4, true);
1268 		_instrument->DrawQuad(px+8, py+3, px+8, py+5, true);
1269 		_instrument->DrawQuad(px+9, py-1, px+10, py+5, true);
1270 	}
1271 }
1272 
Underline(int field,int px,int py,int len)1273 void KLN89::Underline(int field, int px, int py, int len) {
1274 	px = px * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1275 	py = py * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1276 	for(int i=0; i<(len*7); ++i) {
1277 		_instrument->DrawPixel(px, py);
1278 		++px;
1279 	}
1280 }
1281 
DrawKPH(int field,int cx,int cy)1282 void KLN89::DrawKPH(int field, int cx, int cy) {
1283 	// Add some border
1284 	int px = cx * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1285 	int py = cy * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1286 
1287 	px++;
1288 	py++;
1289 
1290 	for(int j=0; j<=4; ++j) {
1291 		_instrument->DrawPixel(px, py + 2 +j);
1292 		_instrument->DrawPixel(px + 8, py + j);
1293 		if(j <= 1) {
1294 			_instrument->DrawPixel(px + 11, py + j);
1295 			_instrument->DrawPixel(px + 9 + j, py + 2);
1296 		}
1297 	}
1298 
1299 	for(int i=0; i<=6; ++i) {
1300 		if(i <= 2) {
1301 			_instrument->DrawPixel(px + 1 + i, py + 4 + i);
1302 			_instrument->DrawPixel(px + 1 + i, py + (4 - i));
1303 		}
1304 		_instrument->DrawPixel(px + 2 + i, py + i);
1305 	}
1306 }
1307 
DrawDTO(int field,int cx,int cy)1308 void KLN89::DrawDTO(int field, int cx, int cy) {
1309 	DrawSpecialChar(6, field, cx, cy);
1310 	if(!(_waypointAlert && _blink)) {
1311 		DrawSpecialChar(3, field, cx+1, cy);
1312 	}
1313 
1314 	int px = cx * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1315 	int py = cy * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1316 
1317 	px++;
1318 	py++;
1319 
1320 	// Fill in the gap between the 'D' and the arrow.
1321 	_instrument->DrawPixel(px+5, py+3);
1322 }
1323 
1324 // Takes character position
DrawChar(char c,int field,int px,int py,bool bold,bool invert)1325 void KLN89::DrawChar(char c, int field, int px, int py, bool bold, bool invert) {
1326 	// Ignore field for now
1327 	// Add some border
1328 	px = px * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1329 	py = py * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1330 
1331 	// Draw an orange background for inverted characters
1332 	if(invert) {
1333 		for(int i=0; i<7; ++i) {
1334 			for(int j=0; j<9; ++j) {
1335 				_instrument->DrawPixel(px + i, py + j);
1336 			}
1337 		}
1338 	}
1339 
1340 	if(c < 33) return;  // space
1341 
1342 	// Render normal decimal points in bold floats
1343 	if(c == '.') bold = false;
1344 
1345 	++py;	// Shift the char up by one pixel
1346 	for(int j=7; j>=0; --j) {
1347 		char c1 = (bold ? NumbersBold[c-48][j] : UpperAlpha[c-33][j]);
1348 		// Don't do the last column for now (ie. j = 1, not 0)
1349 		for(int i=5; i>=0; --i) {
1350 			if(c1 & (01 << i)) {
1351 				_instrument->DrawPixel(px, py, invert);
1352 			}
1353 			++px;
1354 		}
1355 		px -= 6;
1356 		++py;
1357 	}
1358 }
1359 
1360 // Takes pixel position
DrawFreeChar(char c,int x,int y,bool draw_background)1361 void KLN89::DrawFreeChar(char c, int x, int y, bool draw_background) {
1362 
1363 	if(draw_background) {
1364 		_instrument->DrawQuad(x, y, x+6, y+8, true);
1365 	}
1366 
1367 	if(c < 33) return;  // space
1368 
1369 	++y;	// Shift the char up by one pixel
1370 	for(int j=7; j>=0; --j) {
1371 		char c1 = UpperAlpha[c-33][j];
1372 		// Don't do the last column for now (ie. j = 1, not 0)
1373 		for(int i=5; i>=0; --i) {
1374 			if(c1 & (01 << i)) {
1375 				_instrument->DrawPixel(x, y);
1376 			}
1377 			++x;
1378 		}
1379 		x -= 6;
1380 		++y;
1381 	}
1382 }
1383 
1384 // Takes instrument pixel co-ordinates.
1385 // Position is specified by the bottom of the *visible* portion, by default the left position unless align_right is true.
1386 // The return value is the pixel width of the visible portion
DrawSmallChar(char c,int x,int y,bool align_right)1387 int KLN89::DrawSmallChar(char c, int x, int y, bool align_right) {
1388 	// calculate the index into the SmallChar array
1389 	int idx;
1390 	if(c > 47 && c < 58) {
1391 		// number
1392 		idx = c - 48;
1393 	} else if(c > 64 && c < 91) {
1394 		// Uppercase letter
1395 		idx = c - 55;
1396 	} else {
1397 		return(0);
1398 	}
1399 
1400 	char n = SmallChar[idx][0];		// Width of visible portion
1401 	if(align_right) x -= n;
1402 
1403 	// background
1404 	_instrument->DrawQuad(x - 1, y - 1, x + n, y + 5, true);
1405 
1406 	for(int j=7; j>=3; --j) {
1407 		char c1 = SmallChar[idx][j];
1408 		for(int i=n-1; i>=0; --i) {
1409 			if(c1 & (01 << i)) {
1410 				_instrument->DrawPixel(x, y);
1411 			}
1412 			++x;
1413 		}
1414 		x -= n;
1415 		++y;
1416 	}
1417 
1418 	return(n);
1419 }
1420 
1421 // Takes character position
DrawSpecialChar(char c,int field,int cx,int cy,bool bold)1422 void KLN89::DrawSpecialChar(char c, int field, int cx, int cy, bool bold) {
1423 	if(c > 7) {
1424 		cout << "ERROR - requested special char outside array bounds!\n";
1425 		return;  // Increment this as SpecialChar grows
1426 	}
1427 
1428 	// Convert character to pixel position.
1429 	// Ignore field for now
1430 	// Add some border
1431 	int px = cx * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1432 	int py = cy * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1433 	++py;	// Total hack - the special chars were coming out 1 pixel too low!
1434 	for(int i=7; i>=0; --i) {
1435 		char c1 = SpecialChar[(int)c][i];
1436 		// Don't do the last column for now (ie. j = 1, not 0)
1437 		for(int j=5; j>=0; --j) {
1438 			if(c1 & (01 << j)) {
1439 				_instrument->DrawPixel(px, py);
1440 			}
1441 			++px;
1442 		}
1443 		px -= 6;
1444 		++py;
1445 	}
1446 }
1447 
DrawText(const string & s,int field,int px,int py,bool bold,int invert)1448 void KLN89::DrawText(const string& s, int field, int px, int py, bool bold, int invert) {
1449 	for(int i = 0; i < (int)s.size(); ++i) {
1450 		DrawChar(s[(unsigned int)i], field, px+i, py, bold, (invert == i || invert == 99));
1451 	}
1452 }
1453 
DrawMapText(const string & s,int x,int y,bool draw_background)1454 void KLN89::DrawMapText(const string& s, int x, int y, bool draw_background) {
1455 	MapToInstrument(x, y);
1456 	if(draw_background) {
1457 		//_instrument->DrawQuad(x, y, x + (7 * s.size()) - 1, y + 8, true);
1458 		_instrument->DrawQuad(x - 1, y, x + (7 * s.size()) - 2, y + 8, true);
1459 		// The minus 1 and minus 2 are an ugly hack to disguise the fact that I've lost track of exactly what's going on!
1460 	}
1461 
1462 	for(int i = 0; i < (int)s.size(); ++i) {
1463 		DrawFreeChar(s[(unsigned int)i], x+(i * 7)-1, y);
1464 	}
1465 }
1466 
DrawLatitude(double d,int field,int px,int py)1467 void KLN89::DrawLatitude(double d, int field, int px, int py) {
1468 	DrawChar((d >= 0 ? 'N' : 'S'), field, px, py);
1469 	d = fabs(d);
1470 	px += 1;
1471 	// TODO - sanity check input to ensure major lat field can only ever by 2 chars wide
1472 	char buf[8];
1473 	// Don't know whether to zero pad the below for single digits or not?
1474 	//cout << d << ", " << (int)d << '\n';
1475 	// 3 not 2 in size before for trailing \0
1476 	int n = snprintf(buf, 3, "%i", (int)d);
1477 	string s = buf;
1478 	//cout << s << "... " << n << '\n';
1479 	DrawText(s, field, px+(3-n), py);
1480 	n = snprintf(buf, 7, "%05.2f'", ((double)(d - (int)d)) * 60.0f);
1481 	s = buf;
1482 	px += 3;
1483 	DrawSpecialChar(0, field, px, py);	// Degrees symbol
1484 	px++;
1485 	DrawText(s, field, px, py);
1486 }
1487 
DrawLongitude(double d,int field,int px,int py)1488 void KLN89::DrawLongitude(double d, int field, int px, int py) {
1489 	DrawChar((d >= 0 ? 'E' : 'W'), field, px, py);
1490 	d = fabs(d);
1491 	px += 1;
1492 	// TODO - sanity check input to ensure major lat field can only ever be 2 chars wide
1493 	char buf[8];
1494 	// Don't know whether to zero pad the below for single digits or not?
1495 	//cout << d << ", " << (int)d << '\n';
1496 	// 4 not 3 in size before for trailing \0
1497 	int n = snprintf(buf, 4, "%i", (int)d);
1498 	string s = buf;
1499 	//cout << s << "... " << n << '\n';
1500 	DrawText(s, field, px+(3-n), py);
1501 	n = snprintf(buf, 7, "%05.2f'", ((double)(d - (int)d)) * 60.0f);
1502 	s = buf;
1503 	px += 3;
1504 	DrawSpecialChar(0, field, px, py);	// Degrees symbol
1505 	px++;
1506 	DrawText(s, field, px, py);
1507 }
1508 
DrawFreq(double d,int field,int px,int py)1509 void KLN89::DrawFreq(double d, int field, int px, int py) {
1510 	if(d >= 1000) d /= 100.0f;
1511 	char buf[8];
1512 	snprintf(buf, 7, "%6.2f", d);
1513 	string s = buf;
1514 	DrawText(s, field, px, py);
1515 }
1516 
DrawTime(double time,int field,int px,int py)1517 void KLN89::DrawTime(double time, int field, int px, int py) {
1518 	int hrs = (int)(time / 3600);
1519 	int mins = (int)(ceil((time - (hrs * 3600)) / 60.0));
1520 	char buf[10];
1521 	int n;
1522 	if(time >= 60.0) {
1523 		// Draw hr:min
1524 		n = snprintf(buf, 9, "%i:%02i", hrs, mins);
1525 	} else {
1526 		// Draw :secs
1527 		n = snprintf(buf, 4, ":%02i", (int)time);
1528 	}
1529 	string s = buf;
1530 	DrawText(s, field, px - n + 1, py);
1531 }
1532 
DrawHeading(int h,int field,int px,int py)1533 void KLN89::DrawHeading(int h, int field, int px, int py) {
1534 	char buf[4];
1535 	snprintf(buf, 4, "%i", h);
1536 	string s = buf;
1537 	DrawText(s, field, px - s.size(), py);
1538 	DrawSpecialChar(0, field, px, py);	// Degrees symbol
1539 }
1540 
DrawDist(double d,int field,int px,int py)1541 void KLN89::DrawDist(double d, int field, int px, int py) {
1542 	d *= (_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001);
1543 	char buf[10];
1544 	snprintf(buf, 9, "%i", (int)(d + 0.5));
1545 	string s = buf;
1546 	s += (_distUnits == GPS_DIST_UNITS_NM ? "nm" : "Km");
1547 	DrawText(s, field, px - s.size() + 1, py);
1548 }
1549 
DrawSpeed(double v,int field,int px,int py,int decimal)1550 void KLN89::DrawSpeed(double v, int field, int px, int py, int decimal) {
1551 	// TODO - implement variable decimal places
1552 	v *= (_velUnits == GPS_VEL_UNITS_KT ? 1.0 : 0.51444444444 * 0.001 * 3600.0);
1553 	char buf[10];
1554 	snprintf(buf, 9, "%i", (int)(v + 0.5));
1555 	string s = buf;
1556 	if(_velUnits == GPS_VEL_UNITS_KT) {
1557 		s += "kt";
1558 		DrawText(s, field, px - s.size() + 1, py);
1559 	} else {
1560 		DrawText(s, field, px - s.size() - 1, py);
1561 		DrawKPH(field, px - 1, py);
1562 	}
1563 }
1564 
DrawDirDistField(double lat,double lon,int field,int px,int py,bool to_flag,bool cursel)1565 void KLN89::DrawDirDistField(double lat, double lon, int field, int px, int py, bool to_flag, bool cursel) {
1566 	DrawChar('>', field, px, py);
1567 	char buf[8];
1568 	double h;
1569 	if(to_flag) {
1570 		h = GetMagHeadingFromTo(_gpsLat, _gpsLon, lat, lon);
1571 	} else {
1572 		h = GetMagHeadingFromTo(lat, lon, _gpsLat, _gpsLon);
1573 	}
1574 	while(h < 0.0) h += 360.0;
1575 	while(h > 360.0) h -= 360.0;
1576 	snprintf(buf, 4, "%3i", (int)(h + 0.5));
1577 	string s = buf;
1578 	if(!(cursel && _blink)) {
1579 		DrawText(s, field, px + 4 - s.size(), py);
1580 		DrawSpecialChar(0, field, px+4, py);
1581 		DrawText((to_flag ? "To" : "Fr"), field, px+5, py);
1582 		if(cursel) Underline(field, px + 1, py, 6);
1583 	}
1584 	//double d = GetHorizontalSeparation(_gpsLat, _gpsLon, lat, lon);
1585 	//d *= (_distUnits == GPS_DIST_UNITS_NM ? SG_METER_TO_NM : 0.001);
1586 	double d = GetGreatCircleDistance(_gpsLat, _gpsLon, lat, lon);
1587 	d *= (_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001);
1588 	if(d >= 100.0) {
1589 		snprintf(buf, 7, "%5i", (int)(d + 0.5));
1590 	} else {
1591 		snprintf(buf, 7, "%4.1f", d);
1592 	}
1593 	s = buf;
1594 	DrawText(s, field, px + 12 - s.size(), py);
1595 	DrawText((_distUnits == GPS_DIST_UNITS_NM ? "nm" : "Km"), field, px + 12, py);
1596 }
1597 
IncChar(char c,bool gap,bool wrap)1598 char KLN89::IncChar(char c, bool gap, bool wrap) {
1599 	if(c == '9') return(wrap ? (gap ? ' ' : 'A') : '9');
1600 	if(c == 'Z') return('0');
1601 	if(c == ' ') return('A');
1602 	return(c + 1);
1603 }
1604 
DecChar(char c,bool gap,bool wrap)1605 char KLN89::DecChar(char c, bool gap, bool wrap) {
1606 	if(c == 'A') return(wrap ? (gap ? ' ' : '9') : 'A');
1607 	if(c == '0') return('Z');
1608 	if(c == ' ') return('9');
1609 	return(c - 1);
1610 }
1611 
1612 
1613 // Register the subsystem.
1614 #if 0
1615 SGSubsystemMgr::InstancedRegistrant<KLN89> registrantKLN89;
1616 #endif
1617