1 #include "QtLauncher.hxx"
4 #include <QProgressDialog>
5 #include <QCoreApplication>
6 #include <QAbstractListModel>
15 #include <QMutexLocker>
19 #include <QSortFilterProxyModel>
21 #include <QDesktopServices>
24 #include <QStyledItemDelegate>
25 #include <QLinearGradient>
26 #include <QFileDialog>
29 #include <simgear/timing/timestamp.hxx>
30 #include <simgear/props/props_io.hxx>
31 #include <simgear/structure/exception.hxx>
32 #include <simgear/misc/sg_path.hxx>
34 #include "ui_Launcher.h"
35 #include "EditRatingsFilterDialog.hxx"
37 #include <Main/globals.hxx>
38 #include <Navaids/NavDataCache.hxx>
39 #include <Airports/airport.hxx>
40 #include <Airports/dynamics.hxx> // for parking
41 #include <Main/options.hxx>
43 using namespace flightgear;
45 const int MAX_RECENT_AIRPORTS = 32;
46 const int MAX_RECENT_AIRCRAFT = 20;
48 namespace { // anonymous namespace
50 const int AircraftPathRole = Qt::UserRole + 1;
51 const int AircraftAuthorsRole = Qt::UserRole + 2;
52 const int AircraftRatingRole = Qt::UserRole + 100;
56 NavDataCache* cache = NavDataCache::instance();
57 if (cache->isRebuildRequired()) {
58 QProgressDialog rebuildProgress("Initialising navigation data, this may take several minutes",
59 QString() /* cancel text */,
61 rebuildProgress.setWindowModality(Qt::WindowModal);
62 rebuildProgress.show();
64 while (!cache->rebuild()) {
65 // sleep to give the rebuild thread more time
66 SGTimeStamp::sleepForMSec(50);
67 rebuildProgress.setValue(0);
68 QCoreApplication::processEvents();
76 // oh for C++11 initialisers
77 for (int i=0; i<4; ++i) ratings[i] = 0;
80 AircraftItem(QDir dir, QString filePath)
82 for (int i=0; i<4; ++i) ratings[i] = 0;
85 readProperties(filePath.toStdString(), &root);
87 if (!root.hasChild("sim")) {
88 throw sg_io_exception(std::string("Malformed -set.xml file"), filePath.toStdString());
91 SGPropertyNode_ptr sim = root.getNode("sim");
94 description = sim->getStringValue("description");
95 authors = sim->getStringValue("author");
97 if (sim->hasChild("rating")) {
98 parseRatings(sim->getNode("rating"));
101 if (dir.exists("thumbnail.jpg")) {
102 thumbnail.load(dir.filePath("thumbnail.jpg"));
103 // resize to the standard size
104 if (thumbnail.height() > 128) {
105 thumbnail = thumbnail.scaledToHeight(128);
118 void parseRatings(SGPropertyNode_ptr ratingsNode)
120 ratings[0] = ratingsNode->getIntValue("FDM");
121 ratings[1] = ratingsNode->getIntValue("systems");
122 ratings[2] = ratingsNode->getIntValue("cockpit");
123 ratings[3] = ratingsNode->getIntValue("model");
127 class AircraftScanThread : public QThread
131 AircraftScanThread(QStringList dirsToScan) :
138 /** thread-safe access to items already scanned */
139 QList<AircraftItem> items()
141 QList<AircraftItem> result;
142 QMutexLocker g(&m_lock);
143 result.swap(m_items);
158 Q_FOREACH(QString d, m_dirs) {
159 scanAircraftDir(QDir(d));
167 void scanAircraftDir(QDir path)
170 filters << "*-set.xml";
171 Q_FOREACH(QFileInfo child, path.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
172 QDir childDir(child.absoluteFilePath());
173 Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
175 AircraftItem item(childDir, xmlChild.absoluteFilePath());
176 // lock mutex whil we modify the items array
178 QMutexLocker g(&m_lock);
179 m_items.append(item);
181 } catch (sg_exception& e) {
188 } // of set.xml iteration
191 } // of subdir iteration
196 QList<AircraftItem> m_items;
200 class AircraftItemModel : public QAbstractListModel
204 AircraftItemModel(QObject* pr) :
205 QAbstractListModel(pr)
208 Q_FOREACH(std::string ap, globals->get_aircraft_paths()) {
209 dirs << QString::fromStdString(ap);
212 SGPath rootAircraft(globals->get_fg_root());
213 rootAircraft.append("Aircraft");
214 dirs << QString::fromStdString(rootAircraft.str());
216 m_scanThread = new AircraftScanThread(dirs);
217 connect(m_scanThread, &AircraftScanThread::finished, this,
218 &AircraftItemModel::onScanFinished);
219 connect(m_scanThread, &AircraftScanThread::addedItems,
220 this, &AircraftItemModel::onScanResults);
221 m_scanThread->start();
227 m_scanThread->setDone();
228 m_scanThread->wait(1000);
233 virtual int rowCount(const QModelIndex& parent) const
235 return m_items.size();
238 virtual QVariant data(const QModelIndex& index, int role) const
240 const AircraftItem& item(m_items.at(index.row()));
241 if (role == Qt::DisplayRole) {
242 return item.description;
243 } else if (role == Qt::DecorationRole) {
244 return item.thumbnail;
245 } else if (role == AircraftPathRole) {
247 } else if (role == AircraftAuthorsRole) {
249 } else if (role >= AircraftRatingRole) {
250 return item.ratings[role - AircraftRatingRole];
251 } else if (role == Qt::ToolTipRole) {
258 QModelIndex indexOfAircraftPath(QString path) const
260 for (int row=0; row <m_items.size(); ++row) {
261 const AircraftItem& item(m_items.at(row));
262 if (item.path == path) {
267 return QModelIndex();
273 QList<AircraftItem> newItems = m_scanThread->items();
274 if (newItems.isEmpty())
277 int firstRow = m_items.count();
278 int lastRow = firstRow + newItems.count() - 1;
279 beginInsertRows(QModelIndex(), firstRow, lastRow);
280 m_items.append(newItems);
284 void onScanFinished()
291 AircraftScanThread* m_scanThread;
292 QList<AircraftItem> m_items;
295 class AircraftItemDelegate : public QStyledItemDelegate
298 static const int MARGIN = 4;
300 virtual void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
302 // selection feedback rendering
303 if (option.state & QStyle::State_Selected) {
304 QLinearGradient grad(option.rect.topLeft(), option.rect.bottomLeft());
305 grad.setColorAt(0.0, QColor(152, 163, 180));
306 grad.setColorAt(1.0, QColor(90, 107, 131));
308 QBrush backgroundBrush(grad);
309 painter->fillRect(option.rect, backgroundBrush);
311 painter->setPen(QColor(90, 107, 131));
312 painter->drawLine(option.rect.topLeft(), option.rect.topRight());
316 QRect contentRect = option.rect.adjusted(MARGIN, MARGIN, -MARGIN, -MARGIN);
318 QPixmap thumbnail = index.data(Qt::DecorationRole).value<QPixmap>();
319 painter->drawPixmap(contentRect.topLeft(), thumbnail);
322 painter->setPen(QColor(0x7f, 0x7f, 0x7f));
323 painter->setBrush(Qt::NoBrush);
324 painter->drawRect(contentRect.left(), contentRect.top(), thumbnail.width(), thumbnail.height());
326 QString description = index.data(Qt::DisplayRole).toString();
327 contentRect.setLeft(contentRect.left() + MARGIN + thumbnail.width());
329 painter->setPen(Qt::black);
335 painter->drawText(contentRect, Qt::TextWordWrap, description, &actualBounds);
337 QString authors = index.data(AircraftAuthorsRole).toString();
342 QRect authorsRect = contentRect;
343 authorsRect.moveTop(actualBounds.bottom() + MARGIN);
344 painter->drawText(authorsRect, Qt::TextWordWrap,
345 QString("by: %1").arg(authors),
348 QRect r = contentRect;
349 r.setWidth(contentRect.width() / 2);
350 r.moveTop(actualBounds.bottom() + MARGIN);
353 drawRating(painter, "Flight model:", r, index.data(AircraftRatingRole).toInt());
354 r.moveTop(r.bottom());
355 drawRating(painter, "Systems:", r, index.data(AircraftRatingRole + 1).toInt());
357 r.moveTop(actualBounds.bottom() + MARGIN);
358 r.moveLeft(r.right());
359 drawRating(painter, "Cockpit:", r, index.data(AircraftRatingRole + 2).toInt());
360 r.moveTop(r.bottom());
361 drawRating(painter, "Exterior model:", r, index.data(AircraftRatingRole + 3).toInt());
366 virtual QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const
368 return QSize(500, 128 + (MARGIN * 2));
372 void drawRating(QPainter* painter, QString label, const QRect& box, int value) const
374 const int DOT_SIZE = 10;
375 const int DOT_MARGIN = 4;
378 dotBox.setLeft(box.right() - (DOT_MARGIN * 6 + DOT_SIZE * 5));
380 painter->setPen(Qt::black);
382 textBox.setRight(dotBox.left() - DOT_MARGIN);
383 painter->drawText(textBox, Qt::AlignVCenter | Qt::AlignRight, label);
385 painter->setPen(Qt::NoPen);
386 QRect dot(dotBox.left() + DOT_MARGIN,
387 dotBox.center().y() - (DOT_SIZE / 2),
390 for (int i=0; i<5; ++i) {
391 painter->setBrush((i < value) ? QColor(0x3f, 0x3f, 0x3f) : QColor(0xaf, 0xaf, 0xaf));
392 painter->drawEllipse(dot);
393 dot.moveLeft(dot.right() + DOT_MARGIN);
398 } // of anonymous namespace
400 class AirportSearchModel : public QAbstractListModel
404 AirportSearchModel() :
405 m_searchActive(false)
409 void setSearch(QString t)
416 std::string term(t.toUpper().toStdString());
417 // try ICAO lookup first
418 FGAirportRef ref = FGAirport::findByIdent(term);
420 m_ids.push_back(ref->guid());
421 m_airports.push_back(ref);
423 m_search.reset(new NavDataCache::ThreadedAirportSearch(term));
424 QTimer::singleShot(100, this, SLOT(onSearchResultsPoll()));
425 m_searchActive = true;
431 bool isSearchActive() const
433 return m_searchActive;
436 virtual int rowCount(const QModelIndex&) const
438 // if empty, return 1 for special 'no matches'?
442 virtual QVariant data(const QModelIndex& index, int role) const
444 if (!index.isValid())
447 FGAirportRef apt = m_airports[index.row()];
449 apt = FGPositioned::loadById<FGAirport>(m_ids[index.row()]);
450 m_airports[index.row()] = apt;
453 if (role == Qt::DisplayRole) {
454 QString name = QString::fromStdString(apt->name());
455 return QString("%1: %2").arg(QString::fromStdString(apt->ident())).arg(name);
458 if (role == Qt::EditRole) {
459 return QString::fromStdString(apt->ident());
462 if (role == Qt::UserRole) {
463 return static_cast<qlonglong>(m_ids[index.row()]);
469 QString firstIdent() const
474 if (!m_airports.front().valid()) {
475 m_airports[0] = FGPositioned::loadById<FGAirport>(m_ids.front());
478 return QString::fromStdString(m_airports.front()->ident());
482 void searchComplete();
485 void onSearchResultsPoll()
487 PositionedIDVec newIds = m_search->results();
489 beginInsertRows(QModelIndex(), m_ids.size(), newIds.size() - 1);
490 for (unsigned int i=m_ids.size(); i < newIds.size(); ++i) {
491 m_ids.push_back(newIds[i]);
492 m_airports.push_back(FGAirportRef()); // null ref
496 if (m_search->isComplete()) {
497 m_searchActive = false;
499 emit searchComplete();
501 QTimer::singleShot(100, this, SLOT(onSearchResultsPoll()));
506 PositionedIDVec m_ids;
507 mutable std::vector<FGAirportRef> m_airports;
509 QScopedPointer<NavDataCache::ThreadedAirportSearch> m_search;
512 class AircraftProxyModel : public QSortFilterProxyModel
516 AircraftProxyModel(QObject* pr) :
517 QSortFilterProxyModel(pr),
518 m_ratingsFilter(true)
520 for (int i=0; i<4; ++i) {
525 void setRatings(int* ratings)
527 ::memcpy(m_ratings, ratings, sizeof(int) * 4);
532 void setRatingFilterEnabled(bool e)
534 if (e == m_ratingsFilter) {
543 bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
545 if (!QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent)) {
549 if (m_ratingsFilter) {
550 QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
551 for (int i=0; i<4; ++i) {
552 if (m_ratings[i] > index.data(AircraftRatingRole + i).toInt()) {
562 bool m_ratingsFilter;
566 QtLauncher::QtLauncher() :
570 m_ui.reset(new Ui::Launcher);
573 for (int i=0; i<4; ++i) {
574 m_ratingFilters[i] = 3;
577 m_airportsModel = new AirportSearchModel;
578 m_ui->searchList->setModel(m_airportsModel);
579 connect(m_ui->searchList, &QListView::clicked,
580 this, &QtLauncher::onAirportChoiceSelected);
581 connect(m_airportsModel, &AirportSearchModel::searchComplete,
582 this, &QtLauncher::onAirportSearchComplete);
584 SGPath p = SGPath::documents();
585 p.append("FlightGear");
586 p.append("Aircraft");
587 m_customAircraftDir = QString::fromStdString(p.str());
588 m_ui->customAircraftDirLabel->setText(QString("Custom aircraft folder: %1").arg(m_customAircraftDir));
590 globals->append_aircraft_path(m_customAircraftDir.toStdString());
592 // create and configure the proxy model
593 m_aircraftProxy = new AircraftProxyModel(this);
594 m_aircraftProxy->setSourceModel(new AircraftItemModel(this));
596 m_aircraftProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
597 m_aircraftProxy->setSortCaseSensitivity(Qt::CaseInsensitive);
598 m_aircraftProxy->setSortRole(Qt::DisplayRole);
599 m_aircraftProxy->setDynamicSortFilter(true);
601 m_ui->aircraftList->setModel(m_aircraftProxy);
602 m_ui->aircraftList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
603 m_ui->aircraftList->setItemDelegate(new AircraftItemDelegate);
604 m_ui->aircraftList->setSelectionMode(QAbstractItemView::SingleSelection);
605 connect(m_ui->aircraftList, &QListView::clicked,
606 this, &QtLauncher::onAircraftSelected);
608 connect(m_ui->runwayCombo, SIGNAL(currentIndexChanged(int)),
609 this, SLOT(updateAirportDescription()));
610 connect(m_ui->parkingCombo, SIGNAL(currentIndexChanged(int)),
611 this, SLOT(updateAirportDescription()));
612 connect(m_ui->runwayRadio, SIGNAL(toggled(bool)),
613 this, SLOT(updateAirportDescription()));
614 connect(m_ui->parkingRadio, SIGNAL(toggled(bool)),
615 this, SLOT(updateAirportDescription()));
616 connect(m_ui->onFinalCheckbox, SIGNAL(toggled(bool)),
617 this, SLOT(updateAirportDescription()));
620 connect(m_ui->runButton, SIGNAL(clicked()), this, SLOT(onRun()));
621 connect(m_ui->quitButton, SIGNAL(clicked()), this, SLOT(onQuit()));
622 connect(m_ui->airportEdit, SIGNAL(returnPressed()),
623 this, SLOT(onSearchAirports()));
625 connect(m_ui->aircraftFilter, &QLineEdit::textChanged,
626 m_aircraftProxy, &QSortFilterProxyModel::setFilterFixedString);
628 connect(m_ui->airportHistory, &QPushButton::clicked,
629 this, &QtLauncher::onPopupAirportHistory);
630 connect(m_ui->aircraftHistory, &QPushButton::clicked,
631 this, &QtLauncher::onPopupAircraftHistory);
635 connect(m_ui->openAircraftDirButton, &QPushButton::clicked,
636 this, &QtLauncher::onOpenCustomAircraftDir);
638 QAction* qa = new QAction(this);
639 qa->setShortcut(QKeySequence("Ctrl+Q"));
640 connect(qa, &QAction::triggered, this, &QtLauncher::onQuit);
643 connect(m_ui->editRatingFilter, &QPushButton::clicked,
644 this, &QtLauncher::onEditRatingsFilter);
645 connect(m_ui->ratingsFilterCheck, &QAbstractButton::toggled,
646 m_aircraftProxy, &AircraftProxyModel::setRatingFilterEnabled);
648 QIcon historyIcon(":/history-icon");
649 m_ui->aircraftHistory->setIcon(historyIcon);
650 m_ui->airportHistory->setIcon(historyIcon);
652 m_ui->searchIcon->setPixmap(QPixmap(":/search-icon"));
654 connect(m_ui->timeOfDayCombo, SIGNAL(currentIndexChanged(int)),
655 this, SLOT(updateSettingsSummary()));
656 connect(m_ui->fetchRealWxrCheckbox, SIGNAL(toggled(bool)),
657 this, SLOT(updateSettingsSummary()));
658 connect(m_ui->rembrandtCheckbox, SIGNAL(toggled(bool)),
659 this, SLOT(updateSettingsSummary()));
660 connect(m_ui->terrasyncCheck, SIGNAL(toggled(bool)),
661 this, SLOT(updateSettingsSummary()));
662 connect(m_ui->startPausedCheck, SIGNAL(toggled(bool)),
663 this, SLOT(updateSettingsSummary()));
665 updateSettingsSummary();
667 connect(m_ui->addSceneryPath, &QToolButton::clicked,
668 this, &QtLauncher::onAddSceneryPath);
669 connect(m_ui->removeSceneryPath, &QToolButton::clicked,
670 this, &QtLauncher::onRemoveSceneryPath);
673 QtLauncher::~QtLauncher()
678 bool QtLauncher::runLauncherDialog()
680 Q_INIT_RESOURCE(resources);
682 // startup the nav-cache now. This pre-empts normal startup of
683 // the cache, but no harm done. (Providing scenery paths are consistent)
687 // setup scenery paths now, especially TerraSync path for airport
688 // parking locations (after they're downloaded)
692 if (dlg.result() != QDialog::Accepted) {
699 void QtLauncher::restoreSettings()
702 m_ui->rembrandtCheckbox->setChecked(settings.value("enable-rembrandt", false).toBool());
703 m_ui->terrasyncCheck->setChecked(settings.value("enable-terrasync", true).toBool());
704 m_ui->fullScreenCheckbox->setChecked(settings.value("start-fullscreen", false).toBool());
705 m_ui->msaaCheckbox->setChecked(settings.value("enable-msaa", false).toBool());
706 m_ui->fetchRealWxrCheckbox->setChecked(settings.value("enable-realwx", true).toBool());
707 m_ui->startPausedCheck->setChecked(settings.value("start-paused", false).toBool());
708 m_ui->timeOfDayCombo->setCurrentIndex(settings.value("timeofday", 0).toInt());
710 // full paths to -set.xml files
711 m_recentAircraft = settings.value("recent-aircraft").toStringList();
713 if (!m_recentAircraft.empty()) {
714 m_selectedAircraft = m_recentAircraft.front();
716 // select the default C172p
719 updateSelectedAircraft();
722 m_recentAirports = settings.value("recent-airports").toStringList();
723 if (!m_recentAirports.empty()) {
724 setAirport(FGAirport::findByIdent(m_recentAirports.front().toStdString()));
726 updateAirportDescription();
729 m_ui->ratingsFilterCheck->setChecked(settings.value("ratings-filter", true).toBool());
731 Q_FOREACH(QVariant v, settings.value("min-ratings").toList()) {
732 m_ratingFilters[index++] = v.toInt();
735 m_aircraftProxy->setRatingFilterEnabled(m_ui->ratingsFilterCheck->isChecked());
736 m_aircraftProxy->setRatings(m_ratingFilters);
738 QStringList sceneryPaths = settings.value("scenery-paths").toStringList();
739 m_ui->sceneryPathsList->addItems(sceneryPaths);
742 void QtLauncher::saveSettings()
745 settings.setValue("enable-rembrandt", m_ui->rembrandtCheckbox->isChecked());
746 settings.setValue("enable-terrasync", m_ui->terrasyncCheck->isChecked());
747 settings.setValue("enable-msaa", m_ui->msaaCheckbox->isChecked());
748 settings.setValue("start-fullscreen", m_ui->fullScreenCheckbox->isChecked());
749 settings.setValue("enable-realwx", m_ui->fetchRealWxrCheckbox->isChecked());
750 settings.setValue("start-paused", m_ui->startPausedCheck->isChecked());
751 settings.setValue("ratings-filter", m_ui->ratingsFilterCheck->isChecked());
752 settings.setValue("recent-aircraft", m_recentAircraft);
753 settings.setValue("recent-airports", m_recentAirports);
754 settings.setValue("timeofday", m_ui->timeOfDayCombo->currentIndex());
757 for (int i=0; i<m_ui->sceneryPathsList->count(); ++i) {
758 paths.append(m_ui->sceneryPathsList->item(i)->text());
761 settings.setValue("scenery-paths", paths);
764 void QtLauncher::setEnableDisableOptionFromCheckbox(QCheckBox* cbox, QString name) const
766 flightgear::Options* opt = flightgear::Options::sharedInstance();
767 std::string stdName(name.toStdString());
768 if (cbox->isChecked()) {
769 opt->addOption("enable-" + stdName, "");
771 opt->addOption("disable-" + stdName, "");
775 void QtLauncher::onRun()
779 flightgear::Options* opt = flightgear::Options::sharedInstance();
780 setEnableDisableOptionFromCheckbox(m_ui->terrasyncCheck, "terrasync");
781 setEnableDisableOptionFromCheckbox(m_ui->fetchRealWxrCheckbox, "real-weather-fetch");
782 setEnableDisableOptionFromCheckbox(m_ui->rembrandtCheckbox, "rembrandt");
783 setEnableDisableOptionFromCheckbox(m_ui->fullScreenCheckbox, "fullscreen");
784 setEnableDisableOptionFromCheckbox(m_ui->startPausedCheck, "freeze");
787 if (!m_selectedAircraft.isEmpty()) {
788 QFileInfo setFileInfo(m_selectedAircraft);
789 opt->addOption("aircraft-dir", setFileInfo.dir().absolutePath().toStdString());
790 QString setFile = setFileInfo.fileName();
791 Q_ASSERT(setFile.endsWith("-set.xml"));
792 setFile.truncate(setFile.count() - 8); // drop the '-set.xml' portion
793 opt->addOption("aircraft", setFile.toStdString());
795 // manage aircraft history
796 if (m_recentAircraft.contains(m_selectedAircraft))
797 m_recentAircraft.removeOne(m_selectedAircraft);
798 m_recentAircraft.prepend(m_selectedAircraft);
799 if (m_recentAircraft.size() > MAX_RECENT_AIRCRAFT)
800 m_recentAircraft.pop_back();
802 qDebug() << Q_FUNC_INFO << "recent aircraft is now" << m_recentAircraft;
805 // airport / location
806 if (m_selectedAirport) {
807 opt->addOption("airport", m_selectedAirport->ident());
810 if (m_ui->runwayRadio->isChecked()) {
811 int index = m_ui->runwayCombo->currentData().toInt();
813 // explicit runway choice
814 opt->addOption("runway", m_selectedAirport->getRunwayByIndex(index)->ident());
817 if (m_ui->onFinalCheckbox->isChecked()) {
818 opt->addOption("glideslope", "3.0");
819 opt->addOption("offset-distance", "10.0"); // in nautical miles
821 } else if (m_ui->parkingRadio->isChecked()) {
827 if (m_ui->timeOfDayCombo->currentIndex() != 0) {
828 QString dayval = m_ui->timeOfDayCombo->currentText().toLower();
829 opt->addOption("timeofday", dayval.toStdString());
833 for (int i=0; i<m_ui->sceneryPathsList->count(); ++i) {
834 QString path = m_ui->sceneryPathsList->item(i)->text();
835 opt->addOption("fg-scenery", path.toStdString());
841 void QtLauncher::onQuit()
846 void QtLauncher::onSearchAirports()
848 QString search = m_ui->airportEdit->text();
849 m_airportsModel->setSearch(search);
851 if (m_airportsModel->isSearchActive()) {
852 m_ui->searchStatusText->setText(QString("Searching for '%1'").arg(search));
853 m_ui->locationStack->setCurrentIndex(2);
854 } else if (m_airportsModel->rowCount(QModelIndex()) == 1) {
855 QString ident = m_airportsModel->firstIdent();
856 setAirport(FGAirport::findByIdent(ident.toStdString()));
857 m_ui->locationStack->setCurrentIndex(0);
861 void QtLauncher::onAirportSearchComplete()
863 int numResults = m_airportsModel->rowCount(QModelIndex());
864 if (numResults == 0) {
865 m_ui->searchStatusText->setText(QString("No matching airports for '%1'").arg(m_ui->airportEdit->text()));
866 } else if (numResults == 1) {
867 QString ident = m_airportsModel->firstIdent();
868 setAirport(FGAirport::findByIdent(ident.toStdString()));
869 m_ui->locationStack->setCurrentIndex(0);
871 m_ui->locationStack->setCurrentIndex(1);
875 void QtLauncher::onAirportChanged()
877 m_ui->runwayCombo->setEnabled(m_selectedAirport);
878 m_ui->parkingCombo->setEnabled(m_selectedAirport);
879 m_ui->airportDiagram->setAirport(m_selectedAirport);
881 m_ui->runwayRadio->setChecked(true); // default back to runway mode
882 // unelss multiplayer is enabled ?
884 if (!m_selectedAirport) {
885 m_ui->airportDescription->setText(QString());
886 m_ui->airportDiagram->setEnabled(false);
890 m_ui->airportDiagram->setEnabled(true);
892 m_ui->runwayCombo->clear();
893 m_ui->runwayCombo->addItem("Automatic", -1);
894 for (unsigned int r=0; r<m_selectedAirport->numRunways(); ++r) {
895 FGRunwayRef rwy = m_selectedAirport->getRunwayByIndex(r);
896 // add runway with index as data role
897 m_ui->runwayCombo->addItem(QString::fromStdString(rwy->ident()), r);
899 m_ui->airportDiagram->addRunway(rwy);
902 m_ui->parkingCombo->clear();
903 FGAirportDynamics* dynamics = m_selectedAirport->getDynamics();
904 PositionedIDVec parkings = NavDataCache::instance()->airportItemsOfType(
905 m_selectedAirport->guid(),
906 FGPositioned::PARKING);
907 if (parkings.empty()) {
908 m_ui->parkingCombo->setEnabled(false);
909 m_ui->parkingRadio->setEnabled(false);
911 m_ui->parkingCombo->setEnabled(true);
912 m_ui->parkingRadio->setEnabled(true);
913 Q_FOREACH(PositionedID parking, parkings) {
914 FGParking* park = dynamics->getParking(parking);
915 m_ui->parkingCombo->addItem(QString::fromStdString(park->getName()),
916 static_cast<qlonglong>(parking));
918 m_ui->airportDiagram->addParking(park);
923 void QtLauncher::updateAirportDescription()
925 if (!m_selectedAirport) {
926 m_ui->airportDescription->setText(QString("No airport selected"));
930 QString ident = QString::fromStdString(m_selectedAirport->ident()),
931 name = QString::fromStdString(m_selectedAirport->name());
932 QString locationOnAirport;
933 if (m_ui->runwayRadio->isChecked()) {
934 bool onFinal = m_ui->onFinalCheckbox->isChecked();
935 QString runwayName = (m_ui->runwayCombo->currentIndex() == 0) ?
937 QString("runway %1").arg(m_ui->runwayCombo->currentText());
940 locationOnAirport = QString("on 10-mile final to %1").arg(runwayName);
942 locationOnAirport = QString("on %1").arg(runwayName);
944 } else if (m_ui->parkingRadio->isChecked()) {
945 locationOnAirport = QString("at parking position %1").arg(m_ui->parkingCombo->currentText());
948 m_ui->airportDescription->setText(QString("%2 (%1): %3").arg(ident).arg(name).arg(locationOnAirport));
951 void QtLauncher::onAirportChoiceSelected(const QModelIndex& index)
953 m_ui->locationStack->setCurrentIndex(0);
954 setAirport(FGPositioned::loadById<FGAirport>(index.data(Qt::UserRole).toULongLong()));
957 void QtLauncher::onAircraftSelected(const QModelIndex& index)
959 m_selectedAircraft = index.data(AircraftPathRole).toString();
960 updateSelectedAircraft();
963 void QtLauncher::updateSelectedAircraft()
966 QFileInfo info(m_selectedAircraft);
967 AircraftItem item(info.dir(), m_selectedAircraft);
968 m_ui->thumbnail->setPixmap(item.thumbnail);
969 m_ui->aircraftDescription->setText(item.description);
970 } catch (sg_exception& e) {
971 m_ui->thumbnail->setPixmap(QPixmap());
972 m_ui->aircraftDescription->setText("");
976 void QtLauncher::onPopupAirportHistory()
978 if (m_recentAirports.isEmpty()) {
983 Q_FOREACH(QString aptCode, m_recentAirports) {
984 FGAirportRef apt = FGAirport::findByIdent(aptCode.toStdString());
985 QString name = QString::fromStdString(apt->name());
986 QAction* act = m.addAction(QString("%1 - %2").arg(aptCode).arg(name));
987 act->setData(aptCode);
990 QPoint popupPos = m_ui->airportHistory->mapToGlobal(m_ui->airportHistory->rect().bottomLeft());
991 QAction* triggered = m.exec(popupPos);
993 FGAirportRef apt = FGAirport::findByIdent(triggered->data().toString().toStdString());
995 m_ui->airportEdit->clear();
996 m_ui->locationStack->setCurrentIndex(0);
1000 QModelIndex QtLauncher::proxyIndexForAircraftPath(QString path) const
1002 return m_aircraftProxy->mapFromSource(sourceIndexForAircraftPath(path));
1005 QModelIndex QtLauncher::sourceIndexForAircraftPath(QString path) const
1007 AircraftItemModel* sourceModel = qobject_cast<AircraftItemModel*>(m_aircraftProxy->sourceModel());
1008 Q_ASSERT(sourceModel);
1009 return sourceModel->indexOfAircraftPath(path);
1012 void QtLauncher::onPopupAircraftHistory()
1014 if (m_recentAircraft.isEmpty()) {
1019 Q_FOREACH(QString path, m_recentAircraft) {
1020 QModelIndex index = sourceIndexForAircraftPath(path);
1021 if (!index.isValid()) {
1025 QAction* act = m.addAction(index.data(Qt::DisplayRole).toString());
1029 QPoint popupPos = m_ui->aircraftHistory->mapToGlobal(m_ui->aircraftHistory->rect().bottomLeft());
1030 QAction* triggered = m.exec(popupPos);
1032 m_selectedAircraft = triggered->data().toString();
1033 QModelIndex index = proxyIndexForAircraftPath(m_selectedAircraft);
1034 m_ui->aircraftList->selectionModel()->setCurrentIndex(index,
1035 QItemSelectionModel::ClearAndSelect);
1036 m_ui->aircraftFilter->clear();
1037 updateSelectedAircraft();
1041 void QtLauncher::setAirport(FGAirportRef ref)
1043 if (m_selectedAirport == ref)
1046 m_selectedAirport = ref;
1050 // maintain the recent airport list
1051 QString icao = QString::fromStdString(ref->ident());
1052 if (m_recentAirports.contains(icao)) {
1054 m_recentAirports.removeOne(icao);
1055 m_recentAirports.push_front(icao);
1057 // insert and trim list if necessary
1058 m_recentAirports.push_front(icao);
1059 if (m_recentAirports.size() > MAX_RECENT_AIRPORTS) {
1060 m_recentAirports.pop_back();
1065 updateAirportDescription();
1068 void QtLauncher::onOpenCustomAircraftDir()
1070 QUrl u = QUrl::fromLocalFile(m_customAircraftDir);
1071 QDesktopServices::openUrl(u);
1074 void QtLauncher::onEditRatingsFilter()
1076 EditRatingsFilterDialog dialog(this);
1077 dialog.setRatings(m_ratingFilters);
1080 if (dialog.result() == QDialog::Accepted) {
1082 for (int i=0; i<4; ++i) {
1083 m_ratingFilters[i] = dialog.getRating(i);
1084 vl.append(m_ratingFilters[i]);
1086 m_aircraftProxy->setRatings(m_ratingFilters);
1089 settings.setValue("min-ratings", vl);
1093 void QtLauncher::updateSettingsSummary()
1095 QStringList summary;
1096 if (m_ui->timeOfDayCombo->currentIndex() > 0) {
1097 summary.append(QString(m_ui->timeOfDayCombo->currentText().toLower()));
1100 if (m_ui->rembrandtCheckbox->isChecked()) {
1101 summary.append("Rembrandt enabled");
1104 if (m_ui->fetchRealWxrCheckbox->isChecked()) {
1105 summary.append("live weather");
1108 if (m_ui->terrasyncCheck->isChecked()) {
1109 summary.append("automatic scenery downloads");
1112 if (m_ui->startPausedCheck->isChecked()) {
1113 summary.append("paused");
1116 QString s = summary.join(", ");
1117 s[0] = s[0].toUpper();
1118 m_ui->settingsDescription->setText(s);
1121 void QtLauncher::onAddSceneryPath()
1123 QString path = QFileDialog::getExistingDirectory(this, tr("Choose scenery folder"));
1124 if (!path.isEmpty()) {
1125 m_ui->sceneryPathsList->addItem(path);
1130 void QtLauncher::onRemoveSceneryPath()
1132 if (m_ui->sceneryPathsList->currentItem()) {
1133 delete m_ui->sceneryPathsList->currentItem();
1138 #include "QtLauncher.moc"