diff --git a/src/Instrumentation/KLN89/kln89.cxx b/src/Instrumentation/KLN89/kln89.cxx index da1134bea..c028e17ae 100644 --- a/src/Instrumentation/KLN89/kln89.cxx +++ b/src/Instrumentation/KLN89/kln89.cxx @@ -206,6 +206,8 @@ KLN89::KLN89(RenderArea2D* instrument) _entRestoreCrsr = false; _dispMsg = false; + + _dtoReview = false; // Moving map stuff _mapOrientation = 0; @@ -365,11 +367,7 @@ void KLN89::CreateDefaultFlightPlans() { wps.clear(); ids.push_back("KCCR"); wps.push_back(GPS_WP_APT); - ids.push_back("SUZYE"); - wps.push_back(GPS_WP_INT); - ids.push_back("ALTAM"); - wps.push_back(GPS_WP_INT); - ids.push_back("C83"); + ids.push_back("KHAF"); wps.push_back(GPS_WP_APT); CreateFlightPlan(_flightPlans[4], ids, wps); @@ -527,9 +525,15 @@ void KLN89::ClrPressed() { void KLN89::DtoPressed() { if(_activePage != _dir_page) { - // Figure out which waypoint the dir page should display + // Figure out which waypoint the dir page should display, according to the following rules: + // 1. If the FPL 0 page is displayed AND the cursor is over one of the waypoints, display that waypoint. + // 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. + // 3. If any of APT, VOR, NDB, INT, USR or ACT pages is displayed then display the waypoint being viewed. + // 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 + // (no waypoint sequence past the MAP), in which case display the first waypoint of the missed approach procedure. + // 5. If none of the above (i.e. no active waypoint) then display blanks. if(_curPage <= 5) { - // Apt, Vor, Ndb, Int, Usr or Act + // APT, VOR, NDB, INT, USR or ACT if(!_activePage->GetId().empty()) { // Guard against no user waypoints defined _dir_page->SetId(_activePage->GetId()); } else { @@ -539,7 +543,6 @@ void KLN89::DtoPressed() { // NAV 4 _dir_page->SetId(((KLN89NavPage*)_activePage)->GetNav4WpId()); } else if(_curPage == 7 && _activePage->GetSubPage() == 0 && _mode == KLN89_MODE_CRSR) { - //cout << "Checking the fpl page!\n"; // FPL 0 if(!_activePage->GetId().empty()) { //cout << "Not empty!!!\n"; @@ -596,6 +599,17 @@ void KLN89::ToggleOBSMode() { DCLGPS::ToggleOBSMode(); } +void KLN89::DtoInitiate(const string& id) { + _dtoReview = false; + // Set the current page to NAV1 + _curPage = 6; + _activePage = _pages[_curPage]; + _activePage->SetSubPage(0); + // TODO - need to output a scratchpad message with the new course, but we don't know it yet! + // Call the base class to actually initiate the DTO. + DCLGPS::DtoInitiate(id); +} + void KLN89::DrawBar(int page) { int px = 1 + (page * 15); int py = 1; diff --git a/src/Instrumentation/KLN89/kln89.hxx b/src/Instrumentation/KLN89/kln89.hxx index 26dcccd3d..2206fb1b0 100644 --- a/src/Instrumentation/KLN89/kln89.hxx +++ b/src/Instrumentation/KLN89/kln89.hxx @@ -107,6 +107,9 @@ public: private: void ToggleOBSMode(); + + // Initiate Direct To operation to the supplied ID. + void DtoInitiate(const string& id); //----------------------- Drawing functions which take CHARACTER units ------------------------- // Render string s in display field field at position x, y @@ -282,6 +285,10 @@ private: // since button events get directed to the page that was active before the // message was displayed, not the message page itself. bool _dispMsg; // Set true while the message page is being displayed + + // Sometimes the datapages can be used to review a waypoint whilst the user makes a decision, + // and we need to remember why. + bool _dtoReview; // Set true when we a reviewing a waypoint for DTO operation. }; #endif // _KLN89_HXX diff --git a/src/Instrumentation/KLN89/kln89_page.cxx b/src/Instrumentation/KLN89/kln89_page.cxx index d3e5b8d4b..43c78550b 100644 --- a/src/Instrumentation/KLN89/kln89_page.cxx +++ b/src/Instrumentation/KLN89/kln89_page.cxx @@ -204,6 +204,12 @@ void KLN89Page::SetId(const string& s) { _id = s; } +void KLN89Page::SetSubPage(int n) { + if(n < 0) n = 0; + if(n >= _nSubPages) n = _nSubPages-1; + _subPage = n; +} + const string& KLN89Page::GetId() { return(_id); } diff --git a/src/Instrumentation/KLN89/kln89_page.hxx b/src/Instrumentation/KLN89/kln89_page.hxx index a4e686ab8..89ea5a7e7 100644 --- a/src/Instrumentation/KLN89/kln89_page.hxx +++ b/src/Instrumentation/KLN89/kln89_page.hxx @@ -69,6 +69,7 @@ public: virtual const string& GetId(); inline int GetSubPage() { return(_subPage); } + void SetSubPage(int n); inline int GetNSubPages() { return(_nSubPages); } diff --git a/src/Instrumentation/KLN89/kln89_page_apt.cxx b/src/Instrumentation/KLN89/kln89_page_apt.cxx index c17dbff12..96cceea0d 100644 --- a/src/Instrumentation/KLN89/kln89_page_apt.cxx +++ b/src/Instrumentation/KLN89/kln89_page_apt.cxx @@ -353,19 +353,19 @@ void KLN89AptPage::Update(double dt) { _kln89->DrawText("For This Airport", 2, 0, 0); } else { if(_iafDialog) { - _kln89->DrawText(_iaps[_curIap]->_abbrev, 2, 1, 3); + _kln89->DrawText(_iaps[_curIap]->_ident, 2, 1, 3); _kln89->DrawText(_iaps[_curIap]->_rwyStr, 2, 7, 3); - _kln89->DrawText(_iaps[_curIap]->_id, 2, 12, 3); + _kln89->DrawText(_iaps[_curIap]->_aptIdent, 2, 12, 3); _kln89->DrawText("IAF", 2, 2, 2); unsigned int line = 0; - for(unsigned int i=_iafStart; i<_IAF.size(); ++i) { + for(unsigned int i=_iafStart; i<_approachRoutes.size(); ++i) { if(line == 2) { - i = _IAF.size() - 1; + i = _approachRoutes.size() - 1; } // Assume that the IAF number is always single digit! _kln89->DrawText(GPSitoa(i+1), 2, 6, 2-line); if(!(_kln89->_mode == KLN89_MODE_CRSR && _kln89->_blink && _uLinePos == (line + 1))) { - _kln89->DrawText(_IAF[i]->id, 2, 8, 2-line); + _kln89->DrawText(_approachRoutes[i]->waypoints[0]->id, 2, 8, 2-line); } if(_kln89->_mode == KLN89_MODE_CRSR && _uLinePos == (line + 1) && !(_kln89->_blink )) { _kln89->Underline(2, 8, 2-line, 5); @@ -376,9 +376,9 @@ void KLN89AptPage::Update(double dt) { _kln89->DrawEnt(); } } else if(_addDialog) { - _kln89->DrawText(_iaps[_curIap]->_abbrev, 2, 1, 3); + _kln89->DrawText(_iaps[_curIap]->_ident, 2, 1, 3); _kln89->DrawText(_iaps[_curIap]->_rwyStr, 2, 7, 3); - _kln89->DrawText(_iaps[_curIap]->_id, 2, 12, 3); + _kln89->DrawText(_iaps[_curIap]->_aptIdent, 2, 12, 3); string s = GPSitoa(_fStart + 1); _kln89->DrawText(s, 2, 2-s.size(), 2); s = GPSitoa(_kln89->_approachFP->waypoints.size()); @@ -399,9 +399,9 @@ void KLN89AptPage::Update(double dt) { } } } else if(_replaceDialog) { - _kln89->DrawText(_iaps[_curIap]->_abbrev, 2, 1, 3); + _kln89->DrawText(_iaps[_curIap]->_ident, 2, 1, 3); _kln89->DrawText(_iaps[_curIap]->_rwyStr, 2, 7, 3); - _kln89->DrawText(_iaps[_curIap]->_id, 2, 12, 3); + _kln89->DrawText(_iaps[_curIap]->_aptIdent, 2, 12, 3); _kln89->DrawText("Replace Existing", 2, 0, 2); _kln89->DrawText("Approach", 2, 4, 1); if(_uLinePos > 0 && !(_kln89->_blink)) { @@ -411,24 +411,35 @@ void KLN89AptPage::Update(double dt) { } } else { _kln89->DrawText("IAP", 2, 11, 3); - int check = 0; bool selApp = false; if(_kln89->_mode == KLN89_MODE_CRSR && _uLinePos > 4) { selApp = true; if(!_kln89->_blink) _kln89->DrawEnt(); } - for(unsigned int i=0; i<_iaps.size(); ++i) { // TODO - do this properly when > 3 IAPs - string s = GPSitoa(i+1); - _kln89->DrawText(s, 2, 2 - s.size(), 2-i); - if(!(selApp && _uLinePos == 5+i && _kln89->_blink)) { - _kln89->DrawText(_iaps[i]->_abbrev, 2, 3, 2-i); - _kln89->DrawText(_iaps[i]->_rwyStr, 2, 9, 2-i); + // _maxULine pos should be 4 + iaps.size() at this point. + // Draw a maximum of 3 IAPs. + // If there are more than 3 IAPs for this airport, then we need to offset the start + // of the list if _uLinePos is pointing at the 4th or later IAP. + unsigned int offset = 0; + unsigned int index; + if(_uLinePos > 7) { + offset = _uLinePos - 7; + } + for(unsigned int i=0; i<3; ++i) { + index = offset + i; + if(index < _iaps.size()) { + string s = GPSitoa(index+1); + _kln89->DrawText(s, 2, 2 - s.size(), 2-i); + if(!(selApp && _uLinePos == index+5 && _kln89->_blink)) { + _kln89->DrawText(_iaps[index]->_ident, 2, 3, 2-i); + _kln89->DrawText(_iaps[index]->_rwyStr, 2, 9, 2-i); + } + if(selApp && _uLinePos == index+5 && !_kln89->_blink) { + _kln89->Underline(2, 3, 2-i, 9); + } + } else { + break; } - if(selApp && _uLinePos == 5+i && !_kln89->_blink) { - _kln89->Underline(2, 3, 2-i, 9); - } - check++; - if(check > 2) break; } } } @@ -571,7 +582,7 @@ void KLN89AptPage::CrsrPressed() { } else if(_subPage == 7) { // Don't *think* we need some of this since some of it we can only get to by pressing ENT, not CRSR. if(_iafDialog) { - _maxULinePos = _IAF.size(); + _maxULinePos = _approachRoutes.size(); _uLinePos = 1; } else if(_addDialog) { _maxULinePos = 1; @@ -607,7 +618,7 @@ void KLN89AptPage::ClrPressed() { } } else if(_addDialog) { _addDialog = false; - if(_IAF.size() > 1) { + if(_approachRoutes.size() > 1) { _iafDialog = true; _maxULinePos = 1; // Don't reset _curIaf since it is remembed. @@ -632,15 +643,19 @@ void KLN89AptPage::ClrPressed() { void KLN89AptPage::EntPressed() { if(_entInvert) { _entInvert = false; - _last_apt_id = _apt_id; - _apt_id = _save_apt_id; + if(_kln89->_dtoReview) { + _kln89->DtoInitiate(_apt_id); + } else { + _last_apt_id = _apt_id; + _apt_id = _save_apt_id; + } } else if(_subPage == 7 && _kln89->_mode == KLN89_MODE_CRSR && _uLinePos > 0) { // We are selecting an approach if(_iafDialog) { if(_uLinePos > 0) { // Record the IAF that was picked if(_uLinePos == 3) { - _curIaf = _IAF.size() - 1; + _curIaf = _approachRoutes.size() - 1; } else { _curIaf = _uLinePos - 1 + _iafStart; } @@ -648,9 +663,8 @@ void KLN89AptPage::EntPressed() { // TODO - delete the waypoints inside _approachFP before clearing them!!!!!!! _kln89->_approachFP->waypoints.clear(); GPSWaypoint* wp = new GPSWaypoint; - *wp = *_IAF[_curIaf]; // Need to make copies here since we're going to alter ID and type sometimes + *wp = *(_approachRoutes[_curIaf]->waypoints[0]); // Need to make copies here since we're going to alter ID and type sometimes string iafid = wp->id; - //wp->id += 'i'; _kln89->_approachFP->waypoints.push_back(wp); for(unsigned int i=0; i<_IAP.size(); ++i) { if(_IAP[i]->id != iafid) { // Don't duplicate waypoints that are part of the initial fix list and the approach procedure list. @@ -666,11 +680,6 @@ void KLN89AptPage::EntPressed() { _kln89->_approachFP->waypoints.push_back(wp); } } - // Only add 1 missed approach procedure waypoint for now. I think this might be standard always anyway. - wp = new GPSWaypoint; - *wp = *_MAP[0]; - //wp->id += 'h'; - _kln89->_approachFP->waypoints.push_back(wp); _iafDialog = false; _addDialog = true; _maxULinePos = _kln89->_approachFP->waypoints.size() + 1; @@ -702,7 +711,7 @@ void KLN89AptPage::EntPressed() { _kln89->_activeFP->waypoints.insert(_kln89->_activeFP->waypoints.end(), _kln89->_approachFP->waypoints.begin(), _kln89->_approachFP->waypoints.end()); } _kln89->_approachID = _apt_id; - _kln89->_approachAbbrev = _iaps[_curIap]->_abbrev; + _kln89->_approachAbbrev = _iaps[_curIap]->_ident; _kln89->_approachRwyStr = _iaps[_curIap]->_rwyStr; _kln89->_approachLoaded = true; //_kln89->_messageStack.push_back("*Press ALT To Set Baro"); @@ -717,20 +726,36 @@ void KLN89AptPage::EntPressed() { } else if(_replaceDialog) { // TODO - load the approach! } else if(_uLinePos > 4) { - _IAF.clear(); + _approachRoutes.clear(); _IAP.clear(); - _MAP.clear(); _curIaf = 0; - _IAF = ((FGNPIAP*)(_iaps[_uLinePos-5]))->_IAF; + _approachRoutes = ((FGNPIAP*)(_iaps[_uLinePos-5]))->_approachRoutes; _IAP = ((FGNPIAP*)(_iaps[_uLinePos-5]))->_IAP; - _MAP = ((FGNPIAP*)(_iaps[_uLinePos-5]))->_MAP; _curIap = _uLinePos - 5; // TODO - handle the start of list ! no. 1, and the end of list not sequential! _uLinePos = 1; - if(_IAF.size() > 1) { + if(_approachRoutes.size() > 1) { // More than 1 IAF - display the selection dialog _iafDialog = true; - _maxULinePos = _IAF.size(); + _maxULinePos = _approachRoutes.size(); } else { + // There is only 1 IAF, so load the waypoints into the approach flightplan here. + // TODO - there is nasty code duplication loading the approach FP between the case here where we have only one + // IAF and the case where we must choose the IAF from a list. Try to tidy this after it is all working properly. + _kln89->_approachFP->waypoints.clear(); + GPSWaypoint* wp = new GPSWaypoint; + *wp = *(_approachRoutes[0]->waypoints[0]); // Need to make copies here since we're going to alter ID and type sometimes + string iafid = wp->id; + _kln89->_approachFP->waypoints.push_back(wp); + for(unsigned int i=0; i<_IAP.size(); ++i) { + if(_IAP[i]->id != iafid) { // Don't duplicate waypoints that are part of the initial fix list and the approach procedure list. + // FIXME - allow the same waypoint to be both the IAF and the FAF in some + // approaches that have a procedure turn eg. KDLL + // Also allow MAF to be the same as IAF! + wp = new GPSWaypoint; + *wp = *_IAP[i]; + _kln89->_approachFP->waypoints.push_back(wp); + } + } _addDialog = true; _maxULinePos = 1; } @@ -802,6 +827,15 @@ void KLN89AptPage::Knob2Left1() { } else { _curRwyPage--; } + } else if(_subPage == 0) { + _subPage = 7; + // We have to set _uLinePos here even though the cursor isn't pressed, to + // ensure that the list displays properly. + if(_iaps.empty()) { + _uLinePos = 1; + } else { + _uLinePos = 5; + } } else { KLN89Page::Knob2Left1(); } @@ -845,6 +879,15 @@ void KLN89AptPage::Knob2Right1() { } else { _curFreqPage++; } + } else if(_subPage == 6) { + _subPage = 7; + // We have to set _uLinePos here even though the cursor isn't pressed, to + // ensure that the list displays properly. + if(_iaps.empty()) { + _uLinePos = 1; + } else { + _uLinePos = 5; + } } else { KLN89Page::Knob2Right1(); } diff --git a/src/Instrumentation/KLN89/kln89_page_apt.hxx b/src/Instrumentation/KLN89/kln89_page_apt.hxx index 33dd5603c..993fca550 100644 --- a/src/Instrumentation/KLN89/kln89_page_apt.hxx +++ b/src/Instrumentation/KLN89/kln89_page_apt.hxx @@ -65,11 +65,11 @@ private: iap_list_type _iaps; unsigned int _curIap; // The index into _iaps of the IAP we are currently selecting - vector _IAF; // The initial approach fix(es) + vector _approachRoutes; // The approach route(s) from the IAF(s) to the IF. vector _IAP; // The compulsory waypoints of the approach procedure (may duplicate one of the above). // _IAP includes the FAF and MAF. vector _MAP; // The missed approach procedure (doesn't include the MAF). - unsigned int _curIaf; // The index into _IAF of the IAF we are currently selecting, and then remembered as the one we selected + unsigned int _curIaf; // The index into _approachRoutes of the IAF we are currently selecting, and then remembered as the one we selected // Position in rwy pages unsigned int _curRwyPage; diff --git a/src/Instrumentation/KLN89/kln89_page_dir.cxx b/src/Instrumentation/KLN89/kln89_page_dir.cxx index de761232a..a8382b06f 100644 --- a/src/Instrumentation/KLN89/kln89_page_dir.cxx +++ b/src/Instrumentation/KLN89/kln89_page_dir.cxx @@ -26,12 +26,14 @@ #endif #include "kln89_page_dir.hxx" +#include
KLN89DirPage::KLN89DirPage(KLN89* parent) : KLN89Page(parent) { _nSubPages = 1; _subPage = 0; _name = "DIR"; + _maxULinePos = 4; _DToWpDispMode = 2; } @@ -43,16 +45,16 @@ void KLN89DirPage::Update(double dt) { _kln89->DrawText("DIRECT TO:", 2, 2, 3); if(_kln89->_mode == KLN89_MODE_CRSR) { + string s = _id; + while(s.size() < 5) s += ' '; if(_DToWpDispMode == 0) { - string s = _id; - while(s.size() < 5) s += ' '; if(!_kln89->_blink) { _kln89->DrawText(s, 2, 4, 1, false, 99); _kln89->DrawEnt(1, 0, 1); } } else if(_DToWpDispMode == 1) { if(!_kln89->_blink) { - // TODO + _kln89->DrawText(s, 2, 4, 1, false, _uLinePos); _kln89->DrawEnt(1, 0, 1); } _kln89->Underline(2, 4, 1, 5); @@ -67,13 +69,16 @@ void KLN89DirPage::Update(double dt) { KLN89Page::Update(dt); } +// This can only be called from the KLN89 when DTO is pressed from outside of the DIR page. +// DO NOT USE IT to set _id internally from the DIR page, since it initialises various state +// based on the assumption that the DIR page is being first entered. void KLN89DirPage::SetId(const string& s) { if(s.size()) { _id = s; - // TODO - fill in lat, lon, type - // or just pass in waypoints (probably better!) _DToWpDispMode = 0; - // TODO - this (above) should probably be dependent on whether s is a *valid* waypoint! + if(!_kln89->_activeFP->IsEmpty()) { + _DToWpDispIndex = (int)_kln89->_activeFP->waypoints.size() - 1; + } } else { _DToWpDispMode = 2; } @@ -81,6 +86,11 @@ void KLN89DirPage::SetId(const string& s) { _uLinePos = 1; // Needed to stop Leg flashing } +void KLN89DirPage::CrsrPressed() { + // Pressing CRSR clears the ID field (from sim). + _DToWpDispMode = 2; +} + void KLN89DirPage::ClrPressed() { if(_kln89->_mode == KLN89_MODE_CRSR) { if(_DToWpDispMode <= 1) { @@ -98,10 +108,203 @@ void KLN89DirPage::ClrPressed() { } void KLN89DirPage::EntPressed() { - //cout << "DTO ENT Pressed()\n"; - if(_id.empty()) { + // Trim any RH whitespace from _id + while(!_id.empty()) { + if(_id[_id.size()-1] == ' ') { + _id = _id.substr(0, _id.size()-1); + } else { + // Important to break, since usr waypoint names may contain space. + break; + } + } + if(_DToWpDispMode == 2 || _id.empty()) { _kln89->DtoCancel(); } else { - _kln89->DtoInitiate(_id); + if(_DToWpDispMode == 0) { + // It's a waypoint from the active flightplan - these get processed without data page review. + _kln89->DtoInitiate(_id); + } else { + // Display the appropriate data page for review (USR page if the ident is not currently valid) + _kln89->_dtoReview = true; + GPSWaypoint* wp = _kln89->FindFirstByExactId(_id); + if(wp) { + // Set the current page to be the appropriate data page + _kln89->_curPage = wp->type; + delete wp; + } else { + // Set the current page to be the user page + _kln89->_curPage = 4; + } + // set the page ID and entInvert, and activate the current page. + _kln89->_activePage = _kln89->_pages[_kln89->_curPage]; + _kln89->_activePage->SetId(_id); + _kln89->_activePage->SetEntInvert(true); + } + } +} + +void KLN89DirPage::Knob2Left1() { + if(_kln89->_mode == KLN89_MODE_CRSR) { + if(fgGetBool("/instrumentation/kln89/scan-pull")) { + if(_DToWpDispMode == 2) { + if(!_kln89->_activeFP->IsEmpty()) { + // Switch to mode 0, set the position to the end of the active flightplan *and* run the mode 0 case. + _DToWpDispMode = 0; + _DToWpDispIndex = (int)_kln89->_activeFP->waypoints.size() - 1; + } + } + if(_DToWpDispMode == 0) { + // If the knob is pulled out, then the unit cycles through the waypoints of the active flight plan + // (This is deduced from the Bendix-King sim, I haven't found it documented in the pilot guide). + // If the active flight plan is empty it clears the field (this is possible, e.g. if a data page was + // active when DTO was pressed). + if(!_kln89->_activeFP->IsEmpty()) { + if(_DToWpDispIndex == 0) { + _DToWpDispIndex = (int)_kln89->_activeFP->waypoints.size() - 1; + } else { + _DToWpDispIndex--; + } + _id = _kln89->_activeFP->waypoints[_DToWpDispIndex]->id; + } else { + _DToWpDispMode = 2; + } + } + // _DToWpDispMode == 1 is a NO-OP when the knob is out. + } else { + if(_DToWpDispMode == 0) { + // If the knob is not pulled out, then turning it transitions the DIR page to the waypoint selection mode + // and sets the waypoint to the first beginning with '9' + _id = "9"; + GPSWaypoint* wp = _kln89->FindFirstById(_id); + if(wp) { + _id = wp->id; + delete wp; + } + _uLinePos = 0; + _DToWpDispMode = 1; + } else if(_DToWpDispMode == 1) { + while(_id.size() < (_uLinePos + 1)) { + _id += ' '; + } + char ch = _id[_uLinePos]; + if(ch == ' ') { + ch = '9'; + } else if(ch == '0') { + ch = 'Z'; + } else if(ch == 'A') { + // It seems that blanks are allowed within the name, but not for the first character + if(_uLinePos == 0) { + ch = '9'; + } else { + ch = ' '; + } + } else { + ch--; + } + _id[_uLinePos] = ch; + GPSWaypoint* wp = _kln89->FindFirstById(_id.substr(0, _uLinePos+1)); + if(wp) { + _id = wp->id; + delete wp; + } + } else { + _id = "9"; + GPSWaypoint* wp = _kln89->FindFirstById(_id); + if(wp) { + _id = wp->id; + delete wp; + } + _uLinePos = 0; + _DToWpDispMode = 1; + } + } + } else { + // If the cursor is not displayed, then we return to the page that was displayed prior to DTO being pressed, + // and pass the knob turn to that page, whether pulled out or not. + _kln89->_activePage = _kln89->_pages[_kln89->_curPage]; + _kln89->_activePage->Knob2Left1(); + } +} + +void KLN89DirPage::Knob2Right1() { + if(_kln89->_mode == KLN89_MODE_CRSR) { + if(fgGetBool("/instrumentation/kln89/scan-pull")) { + if(_DToWpDispMode == 2) { + if(!_kln89->_activeFP->IsEmpty()) { + // Switch to mode 0, set the position to the end of the active flightplan *and* run the mode 0 case. + _DToWpDispMode = 0; + _DToWpDispIndex = (int)_kln89->_activeFP->waypoints.size() - 1; + } + } + if(_DToWpDispMode == 0) { + // If the knob is pulled out, then the unit cycles through the waypoints of the active flight plan + // (This is deduced from the Bendix-King sim, I haven't found it documented in the pilot guide). + // If the active flight plan is empty it clears the field (this is possible, e.g. if a data page was + // active when DTO was pressed). + if(!_kln89->_activeFP->IsEmpty()) { + if(_DToWpDispIndex == (int)_kln89->_activeFP->waypoints.size() - 1) { + _DToWpDispIndex = 0; + } else { + _DToWpDispIndex++; + } + _id = _kln89->_activeFP->waypoints[_DToWpDispIndex]->id; + } else { + _DToWpDispMode = 2; + } + } + // _DToWpDispMode == 1 is a NO-OP when the knob is out. + } else { + if(_DToWpDispMode == 0) { + // If the knob is not pulled out, then turning it transitions the DIR page to the waypoint selection mode + // and sets the waypoint to the first beginning with 'A' + _id = "A"; + GPSWaypoint* wp = _kln89->FindFirstById(_id); + if(wp) { + _id = wp->id; + delete wp; + } + _uLinePos = 0; + _DToWpDispMode = 1; + } else if(_DToWpDispMode == 1) { + while(_id.size() < (_uLinePos + 1)) { + _id += ' '; + } + char ch = _id[_uLinePos]; + if(ch == ' ') { + ch = 'A'; + } else if(ch == 'Z') { + ch = '0'; + } else if(ch == '9') { + // It seems that blanks are allowed within the name, but not for the first character + if(_uLinePos == 0) { + ch = 'A'; + } else { + ch = ' '; + } + } else { + ch++; + } + _id[_uLinePos] = ch; + GPSWaypoint* wp = _kln89->FindFirstById(_id.substr(0, _uLinePos+1)); + if(wp) { + _id = wp->id; + delete wp; + } + } else { + _id = "A"; + GPSWaypoint* wp = _kln89->FindFirstById(_id); + if(wp) { + _id = wp->id; + delete wp; + } + _uLinePos = 0; + _DToWpDispMode = 1; + } + } + } else { + // If the cursor is not displayed, then we return to the page that was displayed prior to DTO being pressed, + // and pass the knob turn to that page, whether pulled out or not. + _kln89->_activePage = _kln89->_pages[_kln89->_curPage]; + _kln89->_activePage->Knob2Right1(); } } diff --git a/src/Instrumentation/KLN89/kln89_page_dir.hxx b/src/Instrumentation/KLN89/kln89_page_dir.hxx index d9f66b691..ac5899327 100644 --- a/src/Instrumentation/KLN89/kln89_page_dir.hxx +++ b/src/Instrumentation/KLN89/kln89_page_dir.hxx @@ -36,8 +36,11 @@ public: void SetId(const string& s); + void CrsrPressed(); void ClrPressed(); void EntPressed(); + void Knob2Left1(); + void Knob2Right1(); private: // Waypoint display mode. @@ -47,6 +50,10 @@ private: // 2 => Blanks. These can be displayed flashing when the cursor is active (eg. when CLR is pressed) and are always displayed if the cursor is turned off. int _DToWpDispMode; + // Position of the list in the mode that scans through the active flight plan. + // This should be initialised to point at the final waypoint of the active flight plan when we enter mode zero above. + int _DToWpDispIndex; + // We need to save the mode when DTO gets pressed, since potentially this class handles page exit via. the CLR event handler KLN89Mode _saveMasterMode; }; diff --git a/src/Instrumentation/KLN89/kln89_page_int.cxx b/src/Instrumentation/KLN89/kln89_page_int.cxx index 694e06ce6..1edb2428d 100644 --- a/src/Instrumentation/KLN89/kln89_page_int.cxx +++ b/src/Instrumentation/KLN89/kln89_page_int.cxx @@ -195,8 +195,13 @@ void KLN89IntPage::ClrPressed() { void KLN89IntPage::EntPressed() { if(_entInvert) { _entInvert = false; - _last_int_id = _int_id; - _int_id = _save_int_id; + _entInvert = false; + if(_kln89->_dtoReview) { + _kln89->DtoInitiate(_int_id); + } else { + _last_int_id = _int_id; + _int_id = _save_int_id; + } } } diff --git a/src/Instrumentation/KLN89/kln89_page_ndb.cxx b/src/Instrumentation/KLN89/kln89_page_ndb.cxx index 0a70f1d63..b6b90bebd 100644 --- a/src/Instrumentation/KLN89/kln89_page_ndb.cxx +++ b/src/Instrumentation/KLN89/kln89_page_ndb.cxx @@ -158,8 +158,12 @@ void KLN89NDBPage::ClrPressed() { void KLN89NDBPage::EntPressed() { if(_entInvert) { _entInvert = false; - _last_ndb_id = _ndb_id; - _ndb_id = _save_ndb_id; + if(_kln89->_dtoReview) { + _kln89->DtoInitiate(_ndb_id); + } else { + _last_ndb_id = _ndb_id; + _ndb_id = _save_ndb_id; + } } } diff --git a/src/Instrumentation/KLN89/kln89_page_vor.cxx b/src/Instrumentation/KLN89/kln89_page_vor.cxx index a7719f495..414a8c487 100644 --- a/src/Instrumentation/KLN89/kln89_page_vor.cxx +++ b/src/Instrumentation/KLN89/kln89_page_vor.cxx @@ -170,8 +170,13 @@ void KLN89VorPage::ClrPressed() { void KLN89VorPage::EntPressed() { if(_entInvert) { _entInvert = false; - _last_vor_id = _vor_id; - _vor_id = _save_vor_id; + _entInvert = false; + if(_kln89->_dtoReview) { + _kln89->DtoInitiate(_vor_id); + } else { + _last_vor_id = _vor_id; + _vor_id = _save_vor_id; + } } } diff --git a/src/Instrumentation/dclgps.cxx b/src/Instrumentation/dclgps.cxx index af232d70b..d3da8e828 100644 --- a/src/Instrumentation/dclgps.cxx +++ b/src/Instrumentation/dclgps.cxx @@ -33,6 +33,7 @@ #include #include #include +#include #include @@ -238,6 +239,96 @@ void DCLGPS::init() { // Not sure if this should be here, but OK for now. CreateDefaultFlightPlans(); + + // Hack - hardwire some instrument approaches for development. + // These will shortly be replaced by a routine to read ARINC data from file instead. + FGNPIAP* iap; + GPSWaypoint* wp; + GPSFlightPlan* fp; + const GPSWaypoint* cwp; + + iap = new FGNPIAP; + iap->_aptIdent = "KHAF"; + iap->_ident = "R12-Y"; + iap->_name = ExpandSIAPIdent(iap->_ident); + iap->_rwyStr = "12"; + iap->_approachRoutes.clear(); + iap->_IAP.clear(); + // ------- + wp = new GPSWaypoint; + wp->id = "GOBBS"; + // Nasty using the find any function here, but it saves converting data from FGFix etc. + cwp = FindFirstByExactId(wp->id); + if(cwp) { + *wp = *cwp; + wp->appType = GPS_IAF; + fp = new GPSFlightPlan; + fp->waypoints.push_back(wp); + } else { + //cout << "Unable to find waypoint " << wp->id << '\n'; + } + // ------- + wp = new GPSWaypoint; + wp->id = "FUJCE"; + cwp = FindFirstByExactId(wp->id); + if(cwp) { + *wp = *cwp; + wp->appType = GPS_IAP; + fp->waypoints.push_back(wp); + iap->_approachRoutes.push_back(fp); + iap->_IAP.push_back(wp); + } else { + //cout << "Unable to find waypoint " << wp->id << '\n'; + } + // ------- + wp = new GPSWaypoint; + wp->id = "JEVXY"; + cwp = FindFirstByExactId(wp->id); + if(cwp) { + *wp = *cwp; + wp->appType = GPS_FAF; + iap->_IAP.push_back(wp); + } else { + //cout << "Unable to find waypoint " << wp->id << '\n'; + } + // ------- + wp = new GPSWaypoint; + wp->id = "RW12"; + wp->appType = GPS_MAP; + if(wp->id.substr(0, 2) == "RW" && wp->appType == GPS_MAP) { + // Assume that this is a missed-approach point based on the runway number, which appears to be standard for most approaches. + const FGAirport* apt = fgFindAirportID(iap->_aptIdent); + if(apt) { + // TODO - sanity check the waypoint ID to ensure we have a double digit number + FGRunway* rwy = apt->getRunwayByIdent(wp->id.substr(2, 2)); + if(rwy) { + wp->lat = rwy->begin().getLatitudeRad(); + wp->lon = rwy->begin().getLongitudeRad(); + } + } + } else { + cwp = FindFirstByExactId(wp->id); + if(cwp) { + *wp = *cwp; + wp->appType = GPS_MAP; + } else { + //cout << "Unable to find waypoint " << wp->id << '\n'; + } + } + iap->_IAP.push_back(wp); + // ------- + wp = new GPSWaypoint; + wp->id = "SEEMS"; + cwp = FindFirstByExactId(wp->id); + if(cwp) { + *wp = *cwp; + wp->appType = GPS_MAHP; + iap->_IAP.push_back(wp); + } else { + //cout << "Unable to find waypoint " << wp->id << '\n'; + } + // ------- + _np_iap[iap->_aptIdent].push_back(iap); } void DCLGPS::bind() { @@ -521,6 +612,79 @@ void DCLGPS::update(double dt) { } } +/* + Expand a SIAP ident to the full procedure name (as shown on the approach chart). + NOTE: Some of this is inferred from data, some is from documentation. + + Example expansions from ARINC 424-18 [and the airport they're taken from]: + "R10LY" <--> "RNAV (GPS) Y RWY 10 L" [KBOI] + "R10-Z" <--> "RNAV (GPS) Z RWY 10" [KHTO] + "S25" <--> "VOR or GPS RWY 25" [KHHR] + "P20" <--> "GPS RWY 20" [KDAN] + "NDB-B" <--> "NDB or GPS-B" [KDAW] + "NDBC" <--> "NDB or GPS-C" [KEMT] + "VDMA" <--> "VOR/DME or GPS-A" [KDAW] + "VDM-A" <--> "VOR/DME or GPS-A" [KEAG] + "VDMB" <--> "VOR/DME or GPS-B" [KDKX] + "VORA" <--> "VOR or GPS-A" [KEMT] + + It seems that there are 2 basic types of expansions; those that include + the runway and those that don't. Of those that don't, it seems that 2 + different positions within the string to encode the identifying letter + are used, i.e. with a dash and without. +*/ +string DCLGPS::ExpandSIAPIdent(const string& ident) { + string name; + bool has_rwy; + + switch(ident[0]) { + case 'N': name = "NDB or GPS"; has_rwy = false; break; + case 'P': name = "GPS"; has_rwy = true; break; + case 'R': name = "RNAV (GPS)"; has_rwy = true; break; + case 'S': name = "VOR or GPS"; has_rwy = true; break; + case 'V': + if(ident[1] == 'D') name = "VOR/DME or GPS"; + else name = "VOR or GPS"; + has_rwy = false; + break; + default: // TODO output a log message + break; + } + + if(has_rwy) { + // Add the identifying letter if present + if(ident.size() == 5) { + name += ' '; + name += ident[4]; + } + + // Add the runway + name += " RWY "; + name += ident.substr(1, 2); + + // Add a left/right/centre indication if present. + if(ident.size() > 3) { + if((ident[3] != '-') && (ident[3] != ' ')) { // Early versions of the spec allowed a blank instead of a dash so check for both + name += ' '; + name += ident[3]; + } + } + } else { + // Add the identifying letter, which I *think* should always be present, but seems to be inconsistent as to whether a dash is used. + if(ident.size() == 5) { + name += '-'; + name += ident[4]; + } else if(ident.size() == 4) { + name += '-'; + name += ident[3]; + } else { + // No suffix letter + } + } + + return(name); +} + GPSWaypoint* DCLGPS::GetActiveWaypoint() { return &_activeWaypoint; } @@ -553,21 +717,25 @@ double DCLGPS::GetCDIDeflection() const { } void DCLGPS::DtoInitiate(const string& s) { - //cout << "DtoInitiate, s = " << s << '\n'; const GPSWaypoint* wp = FindFirstByExactId(s); if(wp) { - //cout << "Waypoint found, starting dto operation!\n"; + // TODO - Currently we start DTO operation unconditionally, regardless of which mode we are in. + // In fact, the following rules apply: + // In LEG mode, start DTO as we currently do. + // In OBS mode, set the active waypoint to the requested waypoint, and then: + // If the KLN89 is not connected to an external HSI or CDI, set the OBS course to go direct to the waypoint. + // If the KLN89 *is* connected to an external HSI or CDI, it cannot set the course itself, and will display + // a scratchpad message with the course to set manually on the HSI/CDI. + // In both OBS cases, leave _dto false, since we don't need the virtual waypoint created. _dto = true; _activeWaypoint = *wp; _fromWaypoint.lat = _gpsLat; _fromWaypoint.lon = _gpsLon; _fromWaypoint.type = GPS_WP_VIRT; _fromWaypoint.id = "DTOWP"; - delete wp; + delete wp; } else { - //cout << "Waypoint not found, ignoring dto request\n"; - // Should bring up the user waypoint page, but we're not implementing that yet. - _dto = false; // TODO - implement this some day. + _dto = false; } } diff --git a/src/Instrumentation/dclgps.hxx b/src/Instrumentation/dclgps.hxx index 2f0d0f6e3..295a7ad64 100644 --- a/src/Instrumentation/dclgps.hxx +++ b/src/Instrumentation/dclgps.hxx @@ -126,9 +126,9 @@ public: virtual ~FGIAP() = 0; //protected: - string _id; // The ID of the airport this approach is for - string _name; // The approach name, eg "VOR/DME OR GPS-B" - string _abbrev; // The abbreviation the GPS unit uses - eg "VOR/D" in this instance. Possibly GPS model specific. + string _aptIdent; // The ident of the airport this approach is for + string _ident; // The approach ident. + string _name; // The full approach name. string _rwyStr; // The string used to specify the rwy - eg "B" in this instance. bool _precision; // True for precision approach, false for non-precision. }; @@ -140,10 +140,10 @@ public: ~FGNPIAP(); //private: public: - vector _IAF; // The initial approach fix(es) + vector _approachRoutes; // The approach route(s) from the IAF(s) to the IF. + // NOTE: It is an assumption in the code that uses this that there is a unique IAF per approach route. vector _IAP; // The compulsory waypoints of the approach procedure (may duplicate one of the above). - // _IAP includes the FAF and MAF. - vector _MAP; // The missed approach procedure (doesn't include the MAF). + // _IAP includes the FAF and MAF, and the missed approach waypoints. }; typedef vector < FGIAP* > iap_list_type; @@ -199,6 +199,9 @@ public: virtual void bind(); virtual void unbind(); virtual void update(double dt); + + // Expand a SIAP ident to the full procedure name. + string ExpandSIAPIdent(const string& ident); // Render string s in display field field at position x, y // WHERE POSITION IS IN CHARACTER UNITS! @@ -284,7 +287,7 @@ public: inline bool GetToFlag() const { return(_headingBugTo); } // Initiate Direct To operation to the supplied ID. - void DtoInitiate(const string& id); + virtual void DtoInitiate(const string& id); // Cancel Direct To operation void DtoCancel(); @@ -340,8 +343,8 @@ protected: protected: // Find first of any type of waypoint by id. (TODO - Possibly we should return multiple waypoints here). - GPSWaypoint* FindFirstById(const string& id) const; - GPSWaypoint* FindFirstByExactId(const string& id) const; + GPSWaypoint* FindFirstById(const string& id) const; + GPSWaypoint* FindFirstByExactId(const string& id) const; FGNavRecord* FindFirstVorById(const string& id, bool &multi, bool exact = false); FGNavRecord* FindFirstNDBById(const string& id, bool &multi, bool exact = false); @@ -350,8 +353,8 @@ protected: // Find the closest VOR to a position in RADIANS. FGNavRecord* FindClosestVor(double lat_rad, double lon_rad); - // helper to implement the above FindFirstXXX methods - FGPositioned* FindTypedFirstById(const std::string& id, FGPositioned::Type ty, bool &multi, bool exact); + // helper to implement the above FindFirstXXX methods + FGPositioned* FindTypedFirstById(const std::string& id, FGPositioned::Type ty, bool &multi, bool exact); // Position, orientation and velocity. // These should be read from FG's built-in GPS logic if possible.