FlightGear next
NavaidSearchModel.cxx
Go to the documentation of this file.
1// NavaidSearchModel.cxx - expose navaids via a QabstractListModel
2//
3// Written by James Turner, started July 2018.
4//
5// Copyright (C) 2018 James Turner <james@flightgear.org>
6//
7// This program is free software; you can redistribute it and/or
8// modify it under the terms of the GNU General Public License as
9// published by the Free Software Foundation; either version 2 of the
10// License, or (at your option) any later version.
11//
12// This program is distributed in the hope that it will be useful, but
13// WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15// General Public License for more details.
16//
17// You should have received a copy of the GNU General Public License
18// along with this program; if not, write to the Free Software
19// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
21#include "NavaidSearchModel.hxx"
22
23#include <QTimer>
24
25#include "AirportDiagram.hxx"
26#include <Navaids/navrecord.hxx>
27#include "QmlPositioned.hxx"
28
29using namespace flightgear;
30
31QString fixNavaidName(QString s)
32{
33 // split into words
34 QStringList words = s.split(QChar(' '));
35 QStringList changedWords;
36 Q_FOREACH(QString w, words) {
37 if (w.isEmpty())
38 continue;
39
40 QString up = w.toUpper();
41
42 // expand common abbreviations
43 // note these are not translated, since they are abbreivations
44 // for English-langauge airports, mostly in the US/Canada
45 if (up == "FLD") {
46 changedWords.append("Field");
47 continue;
48 }
49
50 if (up == "CO") {
51 changedWords.append("County");
52 continue;
53 }
54
55 if ((up == "MUNI") || (up == "MUN")) {
56 changedWords.append("Municipal");
57 continue;
58 }
59
60 if (up == "MEM") {
61 changedWords.append("Memorial");
62 continue;
63 }
64
65 if (up == "RGNL") {
66 changedWords.append("Regional");
67 continue;
68 }
69
70 if (up == "CTR") {
71 changedWords.append("Center");
72 continue;
73 }
74
75 if (up == "INTL") {
76 changedWords.append("International");
77 continue;
78 }
79
80 // occurs in many Australian airport names in our DB
81 if (up == "(NSW)") {
82 changedWords.append("(New South Wales)");
83 continue;
84 }
85
86 if ((up == "VOR") || (up == "NDB")
87 || (up == "VOR-DME") || (up == "VORTAC")
88 || (up == "NDB-DME")
89 || (up == "AFB") || (up == "RAF"))
90 {
91 changedWords.append(w);
92 continue;
93 }
94
95 if ((up =="[X]") || (up == "[H]") || (up == "[S]")) {
96 continue; // consume
97 }
98
99 QChar firstChar = w.at(0).toUpper();
100 w = w.mid(1).toLower();
101 w.prepend(firstChar);
102
103 changedWords.append(w);
104 }
105
106 return changedWords.join(QChar(' '));
107}
108
109
111{
112public:
114 {
115 if (!airportsOnly) {
119 addType(FGPositioned::DME); // see custom pass() impl below
120 }
121
123
124 switch (aircraft) {
126 break;
127
130 break;
131
134 break;
135
136 default:
139 }
140 }
141
142 bool pass(FGPositioned* aPos) const override
143 {
144 bool ok = TypeFilter::pass(aPos);
145 const auto ty = aPos->type();
146
147 // filter only DMEs which are TACANs, until we have real TACANs in the DB
148 if (ok && (ty == FGPositioned::DME)) {
149 if (!simgear::strutils::ends_with(aPos->name(), "TACAN")) {
150 return false;
151 }
152 }
153
154 return ok;
155 }
156};
157
159{
160 beginResetModel();
161 m_items.clear();
162 m_ids.clear();
163 m_searchActive = false;
164 m_search.reset();
165 endResetModel();
166 emit searchActiveChanged();
168}
169
170qlonglong NavaidSearchModel::guidAtIndex(int index) const
171{
172 const size_t uIndex = static_cast<size_t>(index);
173 if ((index < 0) || (uIndex >= m_ids.size()))
174 return 0;
175
176 return m_ids.at(uIndex);
177}
178
180 QAbstractListModel(parent)
181{
182
183}
184
186{
187 beginResetModel();
188
189 m_items.clear();
190 m_ids.clear();
191
192 std::string term(t.toUpper().toStdString());
193
194 IdentSearchFilter filter(static_cast<LauncherController::AircraftType>(aircraft), m_airportsOnly);
195 FGPositionedList exactMatches = NavDataCache::instance()->findAllWithIdent(term, &filter, true);
196
197 m_ids.reserve(exactMatches.size());
198 m_items.reserve(exactMatches.size());
199 for (auto match : exactMatches) {
200 m_ids.push_back(match->guid());
201 m_items.push_back(match);
202 }
203
204 resort();
205 endResetModel();
206
207 m_search.reset(new NavDataCache::ThreadedGUISearch(term, m_airportsOnly));
208 QTimer::singleShot(100, this, SLOT(onSearchResultsPoll()));
209 m_searchActive = true;
210 emit searchActiveChanged();
212}
213
215{
216 return m_searchActive || (!m_items.empty());
217}
218
219int NavaidSearchModel::rowCount(const QModelIndex &) const
220{
221 if (m_maxResults > 0)
222 return std::min(static_cast<int>(m_ids.size()), m_maxResults);
223
224 return static_cast<int>(m_ids.size());
225}
226
227QVariant NavaidSearchModel::data(const QModelIndex &index, int role) const
228{
229 if (!index.isValid())
230 return QVariant();
231
232 FGPositionedRef pos = itemAtRow(index.row());
233 switch (role) {
234 case GuidRole: return static_cast<qlonglong>(pos->guid());
235 case IdentRole: return QString::fromStdString(pos->ident());
236 case NameRole:
237 return fixNavaidName(QString::fromStdString(pos->name()));
238
239 case NavFrequencyRole: {
241 return nav ? nav->get_freq() : 0;
242 }
243
244 case TypeRole: return static_cast<QmlPositioned::Type>(pos->type());
245 case IconRole:
248 }
249
250 return {};
251}
252
254{
255 FGPositionedRef pos = m_items[row];
256 if (!pos.valid()) {
257 pos = NavDataCache::instance()->loadById(m_ids[row]);
258 m_items[row] = pos;
259 }
260
261 return pos;
262}
263
265{
266 beginResetModel();
267 m_searchActive = false;
268 m_items = items;
269
270 m_ids.clear();
271 for (unsigned int i=0; i < items.size(); ++i) {
272 m_ids.push_back(m_items[i]->guid());
273 }
274
275 // don't sort in this case
276 endResetModel();
277 emit searchActiveChanged();
278}
279
280QHash<int, QByteArray> NavaidSearchModel::roleNames() const
281{
282 QHash<int, QByteArray> result = QAbstractListModel::roleNames();
283
284 result[GeodRole] = "geod";
285 result[GuidRole] = "guid";
286 result[IdentRole] = "ident";
287 result[NameRole] = "name";
288 result[IconRole] = "icon";
289 result[TypeRole] = "type";
290 result[NavFrequencyRole] = "frequency";
291 return result;
292}
293
295{
296 if (m_searchActive || (m_ids.size() != 1))
297 return 0; // no exact match
298
299 return m_ids.back(); // which is also the front
300}
301
303{
304 return static_cast<int>(m_ids.size());
305}
306
307void NavaidSearchModel::onSearchResultsPoll()
308{
309 if (m_search.isNull()) {
310 return;
311 }
312
313 PositionedIDVec newIds = m_search->results();
314 if (!newIds.empty()) {
315 beginResetModel(); // reset the model since we will re-sort
316 for (auto id : newIds) {
317 m_ids.push_back(id);
318 m_items.push_back({}); // null ref
319 }
320 resort();
321 endResetModel();
322 }
323
324 if (m_search->isComplete()) {
325 m_searchActive = false;
326 m_search.reset();
327 emit searchComplete();
328 emit searchActiveChanged();
330 } else {
331 QTimer::singleShot(100, this, SLOT(onSearchResultsPoll()));
332 }
333}
334
335void NavaidSearchModel::resort()
336{
337 if (!m_airportsOnly) {
338 return;
339 }
340
341 // clear m_items
342 std::fill(m_items.begin(), m_items.end(), FGPositionedRef{});
343
344 // build runway length cache
345 std::map<PositionedID, double> longestRunwayCache;
346 for (auto a : m_ids) {
348 if (apt) {
349 const auto rwy = apt->longestRunway();
350 if (rwy) {
351 longestRunwayCache[a] = rwy->lengthFt();
352 }
353 }
354 }
355
356 std::sort(m_ids.begin(), m_ids.end(),
357 [&longestRunwayCache](const PositionedID a, const PositionedID& b)
358 {
359 return longestRunwayCache[a] > longestRunwayCache[b];
360 });
361}
QString fixNavaidName(QString s)
#define i(x)
SGSharedPtr< FGAirport > FGAirportRef
static QPixmap iconForPositioned(const FGPositionedRef &pos, const IconOptions &options=NoOptions)
int get_freq() const
Definition navrecord.hxx:76
virtual const std::string & name() const
Return the name of this positioned.
static SGSharedPtr< T > loadById(PositionedID id)
@ DME
important that DME & TACAN are adjacent to keep the TacanFilter efficient - DMEs are proxies for TACA...
Type type() const
bool pass(FGPositioned *aPos) const override
Over-rideable filter method.
IdentSearchFilter(LauncherController::AircraftType aircraft, bool airportsOnly)
void setItems(const FGPositionedList &items)
QVariant data(const QModelIndex &index, int role) const override
int rowCount(const QModelIndex &) const override
NavaidSearchModel(QObject *parent=nullptr)
FGPositionedRef itemAtRow(unsigned int row) const
void haveExistingSearchChanged()
Q_INVOKABLE void setSearch(QString t, AircraftType aircraft=Unknown)
QHash< int, QByteArray > roleNames() const override
void searchActiveChanged()
Q_INVOKABLE void clear()
Q_INVOKABLE qlonglong guidAtIndex(int index) const
FGPositionedList findAllWithIdent(const std::string &ident, FGPositioned::Filter *filter, bool exact)
FGPositionedRef loadById(PositionedID guid)
retrieve an FGPositioned from the cache.
static NavDataCache * instance()
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
Definition Addon.cxx:53
SGSharedPtr< FGPositioned > FGPositionedRef
Definition airways.cxx:49
std::vector< PositionedID > PositionedIDVec
T * fgpositioned_cast(FGPositioned *p)
std::vector< FGPositionedRef > FGPositionedList
int64_t PositionedID