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>
27 #include <QMessageBox>
30 #include <simgear/timing/timestamp.hxx>
31 #include <simgear/props/props_io.hxx>
32 #include <simgear/structure/exception.hxx>
33 #include <simgear/misc/sg_path.hxx>
35 #include "ui_Launcher.h"
36 #include "EditRatingsFilterDialog.hxx"
38 #include <Main/globals.hxx>
39 #include <Navaids/NavDataCache.hxx>
40 #include <Airports/airport.hxx>
41 #include <Airports/dynamics.hxx> // for parking
42 #include <Main/options.hxx>
44 using namespace flightgear;
46 const int MAX_RECENT_AIRPORTS = 32;
47 const int MAX_RECENT_AIRCRAFT = 20;
49 namespace { // anonymous namespace
51 const int AircraftPathRole = Qt::UserRole + 1;
52 const int AircraftAuthorsRole = Qt::UserRole + 2;
53 const int AircraftVariantRole = Qt::UserRole + 3;
54 const int AircraftVariantCountRole = Qt::UserRole + 4;
55 const int AircraftRatingRole = Qt::UserRole + 100;
56 const int AircraftVariantDescriptionRole = Qt::UserRole + 200;
60 NavDataCache* cache = NavDataCache::instance();
61 if (cache->isRebuildRequired()) {
62 QProgressDialog rebuildProgress("Initialising navigation data, this may take several minutes",
63 QString() /* cancel text */,
65 rebuildProgress.setWindowModality(Qt::WindowModal);
66 rebuildProgress.show();
68 while (!cache->rebuild()) {
69 // sleep to give the rebuild thread more time
70 SGTimeStamp::sleepForMSec(50);
71 rebuildProgress.setValue(0);
72 QCoreApplication::processEvents();
81 // oh for C++11 initialisers
82 for (int i=0; i<4; ++i) ratings[i] = 0;
85 AircraftItem(QDir dir, QString filePath)
87 for (int i=0; i<4; ++i) ratings[i] = 0;
90 readProperties(filePath.toStdString(), &root);
92 if (!root.hasChild("sim")) {
93 throw sg_io_exception(std::string("Malformed -set.xml file"), filePath.toStdString());
96 SGPropertyNode_ptr sim = root.getNode("sim");
99 description = sim->getStringValue("description");
100 authors = sim->getStringValue("author");
102 if (sim->hasChild("rating")) {
103 parseRatings(sim->getNode("rating"));
106 if (sim->hasChild("variant-of")) {
107 variantOf = sim->getStringValue("variant-of");
110 if (dir.exists("thumbnail.jpg")) {
111 thumbnail.load(dir.filePath("thumbnail.jpg"));
112 // resize to the standard size
113 if (thumbnail.height() > 128) {
114 thumbnail = thumbnail.scaledToHeight(128);
120 // the file-name without -set.xml suffix
121 QString baseName() const
123 QString fn = QFileInfo(path).fileName();
124 fn.truncate(fn.count() - 8);
135 QList<AircraftItem*> variants;
137 void parseRatings(SGPropertyNode_ptr ratingsNode)
139 ratings[0] = ratingsNode->getIntValue("FDM");
140 ratings[1] = ratingsNode->getIntValue("systems");
141 ratings[2] = ratingsNode->getIntValue("cockpit");
142 ratings[3] = ratingsNode->getIntValue("model");
146 class AircraftScanThread : public QThread
150 AircraftScanThread(QStringList dirsToScan) :
157 /** thread-safe access to items already scanned */
158 QList<AircraftItem*> items()
160 QList<AircraftItem*> result;
161 QMutexLocker g(&m_lock);
162 result.swap(m_items);
177 Q_FOREACH(QString d, m_dirs) {
178 scanAircraftDir(QDir(d));
186 void scanAircraftDir(QDir path)
189 filters << "*-set.xml";
190 Q_FOREACH(QFileInfo child, path.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
191 QDir childDir(child.absoluteFilePath());
192 QMap<QString, AircraftItem*> baseAircraft;
193 QList<AircraftItem*> variants;
195 Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
197 AircraftItem* item = new AircraftItem(childDir, xmlChild.absoluteFilePath());
198 if (item->variantOf.isNull()) {
199 baseAircraft.insert(item->baseName(), item);
201 variants.append(item);
203 } catch (sg_exception& e) {
210 } // of set.xml iteration
212 // bind variants to their principals
213 Q_FOREACH(AircraftItem* item, variants) {
214 if (!baseAircraft.contains(item->variantOf)) {
215 qWarning() << "can't find principal aircraft " << item->variantOf << " for variant:" << item->path;
220 baseAircraft.value(item->variantOf)->variants.append(item);
223 // lock mutex whil we modify the items array
225 QMutexLocker g(&m_lock);
226 m_items.append(baseAircraft.values());
230 } // of subdir iteration
235 QList<AircraftItem*> m_items;
239 class AircraftItemModel : public QAbstractListModel
243 AircraftItemModel(QObject* pr) :
244 QAbstractListModel(pr)
247 Q_FOREACH(std::string ap, globals->get_aircraft_paths()) {
248 dirs << QString::fromStdString(ap);
251 SGPath rootAircraft(globals->get_fg_root());
252 rootAircraft.append("Aircraft");
253 dirs << QString::fromStdString(rootAircraft.str());
255 m_scanThread = new AircraftScanThread(dirs);
256 connect(m_scanThread, &AircraftScanThread::finished, this,
257 &AircraftItemModel::onScanFinished);
258 connect(m_scanThread, &AircraftScanThread::addedItems,
259 this, &AircraftItemModel::onScanResults);
260 m_scanThread->start();
266 m_scanThread->setDone();
267 m_scanThread->wait(1000);
272 virtual int rowCount(const QModelIndex& parent) const
274 return m_items.size();
277 virtual QVariant data(const QModelIndex& index, int role) const
279 if (role == AircraftVariantRole) {
280 return m_activeVariant.at(index.row());
283 const AircraftItem* item(m_items.at(index.row()));
285 if (role == AircraftVariantCountRole) {
286 return item->variants.count();
289 if (role >= AircraftVariantDescriptionRole) {
290 int variantIndex = role - AircraftVariantDescriptionRole;
291 return item->variants.at(variantIndex)->description;
294 quint32 variantIndex = m_activeVariant.at(index.row());
296 if (variantIndex <= item->variants.count()) {
297 // show the selected variant
298 item = item->variants.at(variantIndex - 1);
302 if (role == Qt::DisplayRole) {
303 return item->description;
304 } else if (role == Qt::DecorationRole) {
305 return item->thumbnail;
306 } else if (role == AircraftPathRole) {
308 } else if (role == AircraftAuthorsRole) {
309 return item->authors;
310 } else if ((role >= AircraftRatingRole) && (role < AircraftVariantDescriptionRole)) {
311 return item->ratings[role - AircraftRatingRole];
312 } else if (role == Qt::ToolTipRole) {
319 virtual bool setData(const QModelIndex &index, const QVariant &value, int role)
321 if (role == AircraftVariantRole) {
322 m_activeVariant[index.row()] = value.toInt();
323 emit dataChanged(index, index);
330 QModelIndex indexOfAircraftPath(QString path) const
332 for (int row=0; row <m_items.size(); ++row) {
333 const AircraftItem* item(m_items.at(row));
334 if (item->path == path) {
339 return QModelIndex();
345 QList<AircraftItem*> newItems = m_scanThread->items();
346 if (newItems.isEmpty())
349 int firstRow = m_items.count();
350 int lastRow = firstRow + newItems.count() - 1;
351 beginInsertRows(QModelIndex(), firstRow, lastRow);
352 m_items.append(newItems);
354 // default variants in all cases
355 for (int i=0; i< newItems.count(); ++i) {
356 m_activeVariant.append(0);
361 void onScanFinished()
368 AircraftScanThread* m_scanThread;
369 QList<AircraftItem*> m_items;
370 QList<quint32> m_activeVariant;
373 class AircraftItemDelegate : public QStyledItemDelegate
377 static const int MARGIN = 4;
378 static const int ARROW_SIZE = 20;
380 AircraftItemDelegate(QListView* view) :
383 view->viewport()->installEventFilter(this);
385 m_leftArrowIcon.load(":/left-arrow-icon");
386 m_rightArrowIcon.load(":/right-arrow-icon");
389 virtual void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
391 // selection feedback rendering
392 if (option.state & QStyle::State_Selected) {
393 QLinearGradient grad(option.rect.topLeft(), option.rect.bottomLeft());
394 grad.setColorAt(0.0, QColor(152, 163, 180));
395 grad.setColorAt(1.0, QColor(90, 107, 131));
397 QBrush backgroundBrush(grad);
398 painter->fillRect(option.rect, backgroundBrush);
400 painter->setPen(QColor(90, 107, 131));
401 painter->drawLine(option.rect.topLeft(), option.rect.topRight());
405 QRect contentRect = option.rect.adjusted(MARGIN, MARGIN, -MARGIN, -MARGIN);
407 QPixmap thumbnail = index.data(Qt::DecorationRole).value<QPixmap>();
408 painter->drawPixmap(contentRect.topLeft(), thumbnail);
411 painter->setPen(QColor(0x7f, 0x7f, 0x7f));
412 painter->setBrush(Qt::NoBrush);
413 painter->drawRect(contentRect.left(), contentRect.top(), thumbnail.width(), thumbnail.height());
415 int variantCount = index.data(AircraftVariantCountRole).toInt();
416 int currentVariant =index.data(AircraftVariantRole).toInt();
417 QString description = index.data(Qt::DisplayRole).toString();
418 contentRect.setLeft(contentRect.left() + MARGIN + thumbnail.width());
420 painter->setPen(Qt::black);
425 QRect descriptionRect = contentRect.adjusted(ARROW_SIZE, 0, -ARROW_SIZE, 0),
428 if (variantCount > 0) {
429 bool canLeft = (currentVariant > 0);
430 bool canRight = (currentVariant < variantCount );
432 QRect leftArrowRect = leftCycleArrowRect(option.rect, index);
434 painter->drawPixmap(leftArrowRect.topLeft() + QPoint(2, 2), m_leftArrowIcon);
437 QRect rightArrowRect = rightCycleArrowRect(option.rect, index);
439 painter->drawPixmap(rightArrowRect.topLeft() + QPoint(2, 2), m_rightArrowIcon);
443 painter->drawText(descriptionRect, Qt::TextWordWrap, description, &actualBounds);
445 QString authors = index.data(AircraftAuthorsRole).toString();
450 QRect authorsRect = descriptionRect;
451 authorsRect.moveTop(actualBounds.bottom() + MARGIN);
452 painter->drawText(authorsRect, Qt::TextWordWrap,
453 QString("by: %1").arg(authors),
456 QRect r = contentRect;
457 r.setWidth(contentRect.width() / 2);
458 r.moveTop(actualBounds.bottom() + MARGIN);
461 drawRating(painter, "Flight model:", r, index.data(AircraftRatingRole).toInt());
462 r.moveTop(r.bottom());
463 drawRating(painter, "Systems:", r, index.data(AircraftRatingRole + 1).toInt());
465 r.moveTop(actualBounds.bottom() + MARGIN);
466 r.moveLeft(r.right());
467 drawRating(painter, "Cockpit:", r, index.data(AircraftRatingRole + 2).toInt());
468 r.moveTop(r.bottom());
469 drawRating(painter, "Exterior model:", r, index.data(AircraftRatingRole + 3).toInt());
472 virtual QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const
474 return QSize(500, 128 + (MARGIN * 2));
477 virtual bool eventFilter( QObject*, QEvent* event )
479 if ( event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease )
481 QMouseEvent* me = static_cast< QMouseEvent* >( event );
482 QModelIndex index = m_view->indexAt( me->pos() );
483 int variantCount = index.data(AircraftVariantCountRole).toInt();
484 int variantIndex = index.data(AircraftVariantRole).toInt();
486 if ( (event->type() == QEvent::MouseButtonRelease) && (variantCount > 0) )
488 QRect vr = m_view->visualRect(index);
489 QRect leftCycleRect = leftCycleArrowRect(vr, index),
490 rightCycleRect = rightCycleArrowRect(vr, index);
492 if ((variantIndex > 0) && leftCycleRect.contains(me->pos())) {
493 m_view->model()->setData(index, variantIndex - 1, AircraftVariantRole);
494 emit variantChanged(index);
496 } else if ((variantIndex < variantCount) && rightCycleRect.contains(me->pos())) {
497 m_view->model()->setData(index, variantIndex + 1, AircraftVariantRole);
498 emit variantChanged(index);
502 } // of mouse button press or release
508 void variantChanged(const QModelIndex& index);
511 QRect leftCycleArrowRect(const QRect& visualRect, const QModelIndex& index) const
513 QRect contentRect = visualRect.adjusted(MARGIN, MARGIN, -MARGIN, -MARGIN);
514 QPixmap thumbnail = index.data(Qt::DecorationRole).value<QPixmap>();
515 contentRect.setLeft(contentRect.left() + MARGIN + thumbnail.width());
517 QRect r = contentRect;
518 r.setRight(r.left() + ARROW_SIZE);
519 r.setBottom(r.top() + ARROW_SIZE);
524 QRect rightCycleArrowRect(const QRect& visualRect, const QModelIndex& index) const
526 QRect contentRect = visualRect.adjusted(MARGIN, MARGIN, -MARGIN, -MARGIN);
527 QPixmap thumbnail = index.data(Qt::DecorationRole).value<QPixmap>();
528 contentRect.setLeft(contentRect.left() + MARGIN + thumbnail.width());
530 QRect r = contentRect;
531 r.setLeft(r.right() - ARROW_SIZE);
532 r.setBottom(r.top() + ARROW_SIZE);
537 void drawRating(QPainter* painter, QString label, const QRect& box, int value) const
539 const int DOT_SIZE = 10;
540 const int DOT_MARGIN = 4;
543 dotBox.setLeft(box.right() - (DOT_MARGIN * 6 + DOT_SIZE * 5));
545 painter->setPen(Qt::black);
547 textBox.setRight(dotBox.left() - DOT_MARGIN);
548 painter->drawText(textBox, Qt::AlignVCenter | Qt::AlignRight, label);
550 painter->setPen(Qt::NoPen);
551 QRect dot(dotBox.left() + DOT_MARGIN,
552 dotBox.center().y() - (DOT_SIZE / 2),
555 for (int i=0; i<5; ++i) {
556 painter->setBrush((i < value) ? QColor(0x3f, 0x3f, 0x3f) : QColor(0xaf, 0xaf, 0xaf));
557 painter->drawEllipse(dot);
558 dot.moveLeft(dot.right() + DOT_MARGIN);
563 QPixmap m_leftArrowIcon,
567 class ArgumentsTokenizer
573 explicit Arg(QString k, QString v = QString()) : arg(k), value(v) {}
579 QList<Arg> tokenize(QString in) const
582 const int len = in.count();
588 for (; index < len; ++index) {
590 nc = index < (len - 1) ? in.at(index + 1) : QChar();
594 if (c == QChar('-')) {
595 if (nc == QChar('-')) {
600 // should we pemit single hyphen arguments?
601 // choosing to fail for now
604 } else if (c.isSpace()) {
610 if (c == QChar('=')) {
613 } else if (c.isSpace()) {
615 result.append(Arg(key));
617 // could check for illegal charatcers here
623 if (c == QChar('"')) {
625 } else if (c.isSpace()) {
627 result.append(Arg(key, value));
634 if (c == QChar('\\')) {
635 // check for escaped double-quote inside quoted value
636 if (nc == QChar('"')) {
639 } else if (c == QChar('"')) {
646 } // of character loop
648 // ensure last argument isn't lost
650 result.append(Arg(key));
651 } else if (state == Value) {
652 result.append(Arg(key, value));
667 } // of anonymous namespace
669 class AirportSearchModel : public QAbstractListModel
673 AirportSearchModel() :
674 m_searchActive(false)
678 void setSearch(QString t)
685 std::string term(t.toUpper().toStdString());
686 // try ICAO lookup first
687 FGAirportRef ref = FGAirport::findByIdent(term);
689 m_ids.push_back(ref->guid());
690 m_airports.push_back(ref);
692 m_search.reset(new NavDataCache::ThreadedAirportSearch(term));
693 QTimer::singleShot(100, this, SLOT(onSearchResultsPoll()));
694 m_searchActive = true;
700 bool isSearchActive() const
702 return m_searchActive;
705 virtual int rowCount(const QModelIndex&) const
707 // if empty, return 1 for special 'no matches'?
711 virtual QVariant data(const QModelIndex& index, int role) const
713 if (!index.isValid())
716 FGAirportRef apt = m_airports[index.row()];
718 apt = FGPositioned::loadById<FGAirport>(m_ids[index.row()]);
719 m_airports[index.row()] = apt;
722 if (role == Qt::DisplayRole) {
723 QString name = QString::fromStdString(apt->name());
724 return QString("%1: %2").arg(QString::fromStdString(apt->ident())).arg(name);
727 if (role == Qt::EditRole) {
728 return QString::fromStdString(apt->ident());
731 if (role == Qt::UserRole) {
732 return static_cast<qlonglong>(m_ids[index.row()]);
738 QString firstIdent() const
743 if (!m_airports.front().valid()) {
744 m_airports[0] = FGPositioned::loadById<FGAirport>(m_ids.front());
747 return QString::fromStdString(m_airports.front()->ident());
751 void searchComplete();
754 void onSearchResultsPoll()
756 PositionedIDVec newIds = m_search->results();
758 beginInsertRows(QModelIndex(), m_ids.size(), newIds.size() - 1);
759 for (unsigned int i=m_ids.size(); i < newIds.size(); ++i) {
760 m_ids.push_back(newIds[i]);
761 m_airports.push_back(FGAirportRef()); // null ref
765 if (m_search->isComplete()) {
766 m_searchActive = false;
768 emit searchComplete();
770 QTimer::singleShot(100, this, SLOT(onSearchResultsPoll()));
775 PositionedIDVec m_ids;
776 mutable std::vector<FGAirportRef> m_airports;
778 QScopedPointer<NavDataCache::ThreadedAirportSearch> m_search;
781 class AircraftProxyModel : public QSortFilterProxyModel
785 AircraftProxyModel(QObject* pr) :
786 QSortFilterProxyModel(pr),
787 m_ratingsFilter(true)
789 for (int i=0; i<4; ++i) {
794 void setRatings(int* ratings)
796 ::memcpy(m_ratings, ratings, sizeof(int) * 4);
801 void setRatingFilterEnabled(bool e)
803 if (e == m_ratingsFilter) {
812 bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
814 if (!QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent)) {
818 if (m_ratingsFilter) {
819 QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
820 for (int i=0; i<4; ++i) {
821 if (m_ratings[i] > index.data(AircraftRatingRole + i).toInt()) {
831 bool m_ratingsFilter;
835 QtLauncher::QtLauncher() :
839 m_ui.reset(new Ui::Launcher);
842 #if QT_VERSION >= 0x050300
843 // don't require Qt 5.3
844 m_ui->commandLineArgs->setPlaceholderText("--option=value --prop:/sim/name=value");
847 #if QT_VERSION >= 0x050200
848 m_ui->aircraftFilter->setClearButtonEnabled(true);
851 for (int i=0; i<4; ++i) {
852 m_ratingFilters[i] = 3;
855 m_airportsModel = new AirportSearchModel;
856 m_ui->searchList->setModel(m_airportsModel);
857 connect(m_ui->searchList, &QListView::clicked,
858 this, &QtLauncher::onAirportChoiceSelected);
859 connect(m_airportsModel, &AirportSearchModel::searchComplete,
860 this, &QtLauncher::onAirportSearchComplete);
862 SGPath p = SGPath::documents();
863 p.append("FlightGear");
864 p.append("Aircraft");
865 m_customAircraftDir = QString::fromStdString(p.str());
866 m_ui->customAircraftDirLabel->setText(QString("Custom aircraft folder: %1").arg(m_customAircraftDir));
868 globals->append_aircraft_path(m_customAircraftDir.toStdString());
870 // create and configure the proxy model
871 m_aircraftProxy = new AircraftProxyModel(this);
872 m_aircraftProxy->setSourceModel(new AircraftItemModel(this));
874 m_aircraftProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
875 m_aircraftProxy->setSortCaseSensitivity(Qt::CaseInsensitive);
876 m_aircraftProxy->setSortRole(Qt::DisplayRole);
877 m_aircraftProxy->setDynamicSortFilter(true);
879 m_ui->aircraftList->setModel(m_aircraftProxy);
880 m_ui->aircraftList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
881 AircraftItemDelegate* delegate = new AircraftItemDelegate(m_ui->aircraftList);
882 m_ui->aircraftList->setItemDelegate(delegate);
883 m_ui->aircraftList->setSelectionMode(QAbstractItemView::SingleSelection);
884 connect(m_ui->aircraftList, &QListView::clicked,
885 this, &QtLauncher::onAircraftSelected);
886 connect(delegate, &AircraftItemDelegate::variantChanged,
887 this, &QtLauncher::onAircraftSelected);
889 connect(m_ui->runwayCombo, SIGNAL(currentIndexChanged(int)),
890 this, SLOT(updateAirportDescription()));
891 connect(m_ui->parkingCombo, SIGNAL(currentIndexChanged(int)),
892 this, SLOT(updateAirportDescription()));
893 connect(m_ui->runwayRadio, SIGNAL(toggled(bool)),
894 this, SLOT(updateAirportDescription()));
895 connect(m_ui->parkingRadio, SIGNAL(toggled(bool)),
896 this, SLOT(updateAirportDescription()));
897 connect(m_ui->onFinalCheckbox, SIGNAL(toggled(bool)),
898 this, SLOT(updateAirportDescription()));
901 connect(m_ui->runButton, SIGNAL(clicked()), this, SLOT(onRun()));
902 connect(m_ui->quitButton, SIGNAL(clicked()), this, SLOT(onQuit()));
903 connect(m_ui->airportEdit, SIGNAL(returnPressed()),
904 this, SLOT(onSearchAirports()));
906 connect(m_ui->aircraftFilter, &QLineEdit::textChanged,
907 m_aircraftProxy, &QSortFilterProxyModel::setFilterFixedString);
909 connect(m_ui->airportHistory, &QPushButton::clicked,
910 this, &QtLauncher::onPopupAirportHistory);
911 connect(m_ui->aircraftHistory, &QPushButton::clicked,
912 this, &QtLauncher::onPopupAircraftHistory);
916 connect(m_ui->openAircraftDirButton, &QPushButton::clicked,
917 this, &QtLauncher::onOpenCustomAircraftDir);
919 QAction* qa = new QAction(this);
920 qa->setShortcut(QKeySequence("Ctrl+Q"));
921 connect(qa, &QAction::triggered, this, &QtLauncher::onQuit);
924 connect(m_ui->editRatingFilter, &QPushButton::clicked,
925 this, &QtLauncher::onEditRatingsFilter);
926 connect(m_ui->ratingsFilterCheck, &QAbstractButton::toggled,
927 m_aircraftProxy, &AircraftProxyModel::setRatingFilterEnabled);
929 QIcon historyIcon(":/history-icon");
930 m_ui->aircraftHistory->setIcon(historyIcon);
931 m_ui->airportHistory->setIcon(historyIcon);
933 m_ui->searchIcon->setPixmap(QPixmap(":/search-icon"));
935 connect(m_ui->timeOfDayCombo, SIGNAL(currentIndexChanged(int)),
936 this, SLOT(updateSettingsSummary()));
937 connect(m_ui->seasonCombo, SIGNAL(currentIndexChanged(int)),
938 this, SLOT(updateSettingsSummary()));
939 connect(m_ui->fetchRealWxrCheckbox, SIGNAL(toggled(bool)),
940 this, SLOT(updateSettingsSummary()));
941 connect(m_ui->rembrandtCheckbox, SIGNAL(toggled(bool)),
942 this, SLOT(updateSettingsSummary()));
943 connect(m_ui->terrasyncCheck, SIGNAL(toggled(bool)),
944 this, SLOT(updateSettingsSummary()));
945 connect(m_ui->startPausedCheck, SIGNAL(toggled(bool)),
946 this, SLOT(updateSettingsSummary()));
947 connect(m_ui->msaaCheckbox, SIGNAL(toggled(bool)),
948 this, SLOT(updateSettingsSummary()));
950 connect(m_ui->rembrandtCheckbox, SIGNAL(toggled(bool)),
951 this, SLOT(onRembrandtToggled(bool)));
953 updateSettingsSummary();
955 connect(m_ui->addSceneryPath, &QToolButton::clicked,
956 this, &QtLauncher::onAddSceneryPath);
957 connect(m_ui->removeSceneryPath, &QToolButton::clicked,
958 this, &QtLauncher::onRemoveSceneryPath);
961 QtLauncher::~QtLauncher()
966 bool QtLauncher::runLauncherDialog()
968 Q_INIT_RESOURCE(resources);
970 // startup the nav-cache now. This pre-empts normal startup of
971 // the cache, but no harm done. (Providing scenery paths are consistent)
975 // setup scenery paths now, especially TerraSync path for airport
976 // parking locations (after they're downloaded)
980 if (dlg.result() != QDialog::Accepted) {
987 void QtLauncher::restoreSettings()
990 m_ui->rembrandtCheckbox->setChecked(settings.value("enable-rembrandt", false).toBool());
991 m_ui->terrasyncCheck->setChecked(settings.value("enable-terrasync", true).toBool());
992 m_ui->fullScreenCheckbox->setChecked(settings.value("start-fullscreen", false).toBool());
993 m_ui->msaaCheckbox->setChecked(settings.value("enable-msaa", false).toBool());
994 m_ui->fetchRealWxrCheckbox->setChecked(settings.value("enable-realwx", true).toBool());
995 m_ui->startPausedCheck->setChecked(settings.value("start-paused", false).toBool());
996 m_ui->timeOfDayCombo->setCurrentIndex(settings.value("timeofday", 0).toInt());
997 m_ui->seasonCombo->setCurrentIndex(settings.value("season", 0).toInt());
999 // full paths to -set.xml files
1000 m_recentAircraft = settings.value("recent-aircraft").toStringList();
1002 if (!m_recentAircraft.empty()) {
1003 m_selectedAircraft = m_recentAircraft.front();
1005 // select the default C172p
1008 updateSelectedAircraft();
1011 m_recentAirports = settings.value("recent-airports").toStringList();
1012 if (!m_recentAirports.empty()) {
1013 setAirport(FGAirport::findByIdent(m_recentAirports.front().toStdString()));
1015 updateAirportDescription();
1018 m_ui->ratingsFilterCheck->setChecked(settings.value("ratings-filter", true).toBool());
1020 Q_FOREACH(QVariant v, settings.value("min-ratings").toList()) {
1021 m_ratingFilters[index++] = v.toInt();
1024 m_aircraftProxy->setRatingFilterEnabled(m_ui->ratingsFilterCheck->isChecked());
1025 m_aircraftProxy->setRatings(m_ratingFilters);
1027 QStringList sceneryPaths = settings.value("scenery-paths").toStringList();
1028 m_ui->sceneryPathsList->addItems(sceneryPaths);
1030 m_ui->commandLineArgs->setPlainText(settings.value("additional-args").toString());
1033 void QtLauncher::saveSettings()
1036 settings.setValue("enable-rembrandt", m_ui->rembrandtCheckbox->isChecked());
1037 settings.setValue("enable-terrasync", m_ui->terrasyncCheck->isChecked());
1038 settings.setValue("enable-msaa", m_ui->msaaCheckbox->isChecked());
1039 settings.setValue("start-fullscreen", m_ui->fullScreenCheckbox->isChecked());
1040 settings.setValue("enable-realwx", m_ui->fetchRealWxrCheckbox->isChecked());
1041 settings.setValue("start-paused", m_ui->startPausedCheck->isChecked());
1042 settings.setValue("ratings-filter", m_ui->ratingsFilterCheck->isChecked());
1043 settings.setValue("recent-aircraft", m_recentAircraft);
1044 settings.setValue("recent-airports", m_recentAirports);
1045 settings.setValue("timeofday", m_ui->timeOfDayCombo->currentIndex());
1046 settings.setValue("season", m_ui->seasonCombo->currentIndex());
1049 for (int i=0; i<m_ui->sceneryPathsList->count(); ++i) {
1050 paths.append(m_ui->sceneryPathsList->item(i)->text());
1053 settings.setValue("scenery-paths", paths);
1054 settings.setValue("additional-args", m_ui->commandLineArgs->toPlainText());
1057 void QtLauncher::setEnableDisableOptionFromCheckbox(QCheckBox* cbox, QString name) const
1059 flightgear::Options* opt = flightgear::Options::sharedInstance();
1060 std::string stdName(name.toStdString());
1061 if (cbox->isChecked()) {
1062 opt->addOption("enable-" + stdName, "");
1064 opt->addOption("disable-" + stdName, "");
1068 void QtLauncher::onRun()
1072 flightgear::Options* opt = flightgear::Options::sharedInstance();
1073 setEnableDisableOptionFromCheckbox(m_ui->terrasyncCheck, "terrasync");
1074 setEnableDisableOptionFromCheckbox(m_ui->fetchRealWxrCheckbox, "real-weather-fetch");
1075 setEnableDisableOptionFromCheckbox(m_ui->rembrandtCheckbox, "rembrandt");
1076 setEnableDisableOptionFromCheckbox(m_ui->fullScreenCheckbox, "fullscreen");
1077 setEnableDisableOptionFromCheckbox(m_ui->startPausedCheck, "freeze");
1079 // MSAA is more complex
1080 if (!m_ui->rembrandtCheckbox->isChecked()) {
1081 if (m_ui->msaaCheckbox->isChecked()) {
1082 globals->get_props()->setIntValue("/sim/rendering/multi-sample-buffers", 1);
1083 globals->get_props()->setIntValue("/sim/rendering/multi-samples", 4);
1085 globals->get_props()->setIntValue("/sim/rendering/multi-sample-buffers", 0);
1090 if (!m_selectedAircraft.isEmpty()) {
1091 QFileInfo setFileInfo(m_selectedAircraft);
1092 opt->addOption("aircraft-dir", setFileInfo.dir().absolutePath().toStdString());
1093 QString setFile = setFileInfo.fileName();
1094 Q_ASSERT(setFile.endsWith("-set.xml"));
1095 setFile.truncate(setFile.count() - 8); // drop the '-set.xml' portion
1096 opt->addOption("aircraft", setFile.toStdString());
1098 // manage aircraft history
1099 if (m_recentAircraft.contains(m_selectedAircraft))
1100 m_recentAircraft.removeOne(m_selectedAircraft);
1101 m_recentAircraft.prepend(m_selectedAircraft);
1102 if (m_recentAircraft.size() > MAX_RECENT_AIRCRAFT)
1103 m_recentAircraft.pop_back();
1106 // airport / location
1107 if (m_selectedAirport) {
1108 opt->addOption("airport", m_selectedAirport->ident());
1111 if (m_ui->runwayRadio->isChecked()) {
1112 int index = m_ui->runwayCombo->itemData(m_ui->runwayCombo->currentIndex()).toInt();
1113 if ((index >= 0) && m_selectedAirport) {
1114 // explicit runway choice
1115 opt->addOption("runway", m_selectedAirport->getRunwayByIndex(index)->ident());
1118 if (m_ui->onFinalCheckbox->isChecked()) {
1119 opt->addOption("glideslope", "3.0");
1120 opt->addOption("offset-distance", "10.0"); // in nautical miles
1122 } else if (m_ui->parkingRadio->isChecked()) {
1123 // parking selection
1124 opt->addOption("parkpos", m_ui->parkingCombo->currentText().toStdString());
1128 if (m_ui->timeOfDayCombo->currentIndex() != 0) {
1129 QString dayval = m_ui->timeOfDayCombo->currentText().toLower();
1130 opt->addOption("timeofday", dayval.toStdString());
1133 if (m_ui->seasonCombo->currentIndex() != 0) {
1134 QString dayval = m_ui->timeOfDayCombo->currentText().toLower();
1135 opt->addOption("season", dayval.toStdString());
1139 for (int i=0; i<m_ui->sceneryPathsList->count(); ++i) {
1140 QString path = m_ui->sceneryPathsList->item(i)->text();
1141 opt->addOption("fg-scenery", path.toStdString());
1144 // additional arguments
1145 ArgumentsTokenizer tk;
1146 Q_FOREACH(ArgumentsTokenizer::Arg a, tk.tokenize(m_ui->commandLineArgs->toPlainText())) {
1147 if (a.arg.startsWith("prop:")) {
1148 QString v = a.arg.mid(5) + "=" + a.value;
1149 opt->addOption("prop", v.toStdString());
1151 opt->addOption(a.arg.toStdString(), a.value.toStdString());
1158 void QtLauncher::onQuit()
1163 void QtLauncher::onSearchAirports()
1165 QString search = m_ui->airportEdit->text();
1166 m_airportsModel->setSearch(search);
1168 if (m_airportsModel->isSearchActive()) {
1169 m_ui->searchStatusText->setText(QString("Searching for '%1'").arg(search));
1170 m_ui->locationStack->setCurrentIndex(2);
1171 } else if (m_airportsModel->rowCount(QModelIndex()) == 1) {
1172 QString ident = m_airportsModel->firstIdent();
1173 setAirport(FGAirport::findByIdent(ident.toStdString()));
1174 m_ui->locationStack->setCurrentIndex(0);
1178 void QtLauncher::onAirportSearchComplete()
1180 int numResults = m_airportsModel->rowCount(QModelIndex());
1181 if (numResults == 0) {
1182 m_ui->searchStatusText->setText(QString("No matching airports for '%1'").arg(m_ui->airportEdit->text()));
1183 } else if (numResults == 1) {
1184 QString ident = m_airportsModel->firstIdent();
1185 setAirport(FGAirport::findByIdent(ident.toStdString()));
1186 m_ui->locationStack->setCurrentIndex(0);
1188 m_ui->locationStack->setCurrentIndex(1);
1192 void QtLauncher::onAirportChanged()
1194 m_ui->runwayCombo->setEnabled(m_selectedAirport);
1195 m_ui->parkingCombo->setEnabled(m_selectedAirport);
1196 m_ui->airportDiagram->setAirport(m_selectedAirport);
1198 m_ui->runwayRadio->setChecked(true); // default back to runway mode
1199 // unelss multiplayer is enabled ?
1201 if (!m_selectedAirport) {
1202 m_ui->airportDescription->setText(QString());
1203 m_ui->airportDiagram->setEnabled(false);
1207 m_ui->airportDiagram->setEnabled(true);
1209 m_ui->runwayCombo->clear();
1210 m_ui->runwayCombo->addItem("Automatic", -1);
1211 for (unsigned int r=0; r<m_selectedAirport->numRunways(); ++r) {
1212 FGRunwayRef rwy = m_selectedAirport->getRunwayByIndex(r);
1213 // add runway with index as data role
1214 m_ui->runwayCombo->addItem(QString::fromStdString(rwy->ident()), r);
1216 m_ui->airportDiagram->addRunway(rwy);
1219 m_ui->parkingCombo->clear();
1220 FGAirportDynamics* dynamics = m_selectedAirport->getDynamics();
1221 PositionedIDVec parkings = NavDataCache::instance()->airportItemsOfType(
1222 m_selectedAirport->guid(),
1223 FGPositioned::PARKING);
1224 if (parkings.empty()) {
1225 m_ui->parkingCombo->setEnabled(false);
1226 m_ui->parkingRadio->setEnabled(false);
1228 m_ui->parkingCombo->setEnabled(true);
1229 m_ui->parkingRadio->setEnabled(true);
1230 Q_FOREACH(PositionedID parking, parkings) {
1231 FGParking* park = dynamics->getParking(parking);
1232 m_ui->parkingCombo->addItem(QString::fromStdString(park->getName()),
1233 static_cast<qlonglong>(parking));
1235 m_ui->airportDiagram->addParking(park);
1240 void QtLauncher::updateAirportDescription()
1242 if (!m_selectedAirport) {
1243 m_ui->airportDescription->setText(QString("No airport selected"));
1247 QString ident = QString::fromStdString(m_selectedAirport->ident()),
1248 name = QString::fromStdString(m_selectedAirport->name());
1249 QString locationOnAirport;
1250 if (m_ui->runwayRadio->isChecked()) {
1251 bool onFinal = m_ui->onFinalCheckbox->isChecked();
1252 QString runwayName = (m_ui->runwayCombo->currentIndex() == 0) ?
1254 QString("runway %1").arg(m_ui->runwayCombo->currentText());
1257 locationOnAirport = QString("on 10-mile final to %1").arg(runwayName);
1259 locationOnAirport = QString("on %1").arg(runwayName);
1261 } else if (m_ui->parkingRadio->isChecked()) {
1262 locationOnAirport = QString("at parking position %1").arg(m_ui->parkingCombo->currentText());
1265 m_ui->airportDescription->setText(QString("%2 (%1): %3").arg(ident).arg(name).arg(locationOnAirport));
1268 void QtLauncher::onAirportChoiceSelected(const QModelIndex& index)
1270 m_ui->locationStack->setCurrentIndex(0);
1271 setAirport(FGPositioned::loadById<FGAirport>(index.data(Qt::UserRole).toULongLong()));
1274 void QtLauncher::onAircraftSelected(const QModelIndex& index)
1276 m_selectedAircraft = index.data(AircraftPathRole).toString();
1277 updateSelectedAircraft();
1280 void QtLauncher::updateSelectedAircraft()
1283 QFileInfo info(m_selectedAircraft);
1284 AircraftItem item(info.dir(), m_selectedAircraft);
1285 m_ui->thumbnail->setPixmap(item.thumbnail);
1286 m_ui->aircraftDescription->setText(item.description);
1287 } catch (sg_exception& e) {
1288 m_ui->thumbnail->setPixmap(QPixmap());
1289 m_ui->aircraftDescription->setText("");
1293 void QtLauncher::onPopupAirportHistory()
1295 if (m_recentAirports.isEmpty()) {
1300 Q_FOREACH(QString aptCode, m_recentAirports) {
1301 FGAirportRef apt = FGAirport::findByIdent(aptCode.toStdString());
1302 QString name = QString::fromStdString(apt->name());
1303 QAction* act = m.addAction(QString("%1 - %2").arg(aptCode).arg(name));
1304 act->setData(aptCode);
1307 QPoint popupPos = m_ui->airportHistory->mapToGlobal(m_ui->airportHistory->rect().bottomLeft());
1308 QAction* triggered = m.exec(popupPos);
1310 FGAirportRef apt = FGAirport::findByIdent(triggered->data().toString().toStdString());
1312 m_ui->airportEdit->clear();
1313 m_ui->locationStack->setCurrentIndex(0);
1317 QModelIndex QtLauncher::proxyIndexForAircraftPath(QString path) const
1319 return m_aircraftProxy->mapFromSource(sourceIndexForAircraftPath(path));
1322 QModelIndex QtLauncher::sourceIndexForAircraftPath(QString path) const
1324 AircraftItemModel* sourceModel = qobject_cast<AircraftItemModel*>(m_aircraftProxy->sourceModel());
1325 Q_ASSERT(sourceModel);
1326 return sourceModel->indexOfAircraftPath(path);
1329 void QtLauncher::onPopupAircraftHistory()
1331 if (m_recentAircraft.isEmpty()) {
1336 Q_FOREACH(QString path, m_recentAircraft) {
1337 QModelIndex index = sourceIndexForAircraftPath(path);
1338 if (!index.isValid()) {
1342 QAction* act = m.addAction(index.data(Qt::DisplayRole).toString());
1346 QPoint popupPos = m_ui->aircraftHistory->mapToGlobal(m_ui->aircraftHistory->rect().bottomLeft());
1347 QAction* triggered = m.exec(popupPos);
1349 m_selectedAircraft = triggered->data().toString();
1350 QModelIndex index = proxyIndexForAircraftPath(m_selectedAircraft);
1351 m_ui->aircraftList->selectionModel()->setCurrentIndex(index,
1352 QItemSelectionModel::ClearAndSelect);
1353 m_ui->aircraftFilter->clear();
1354 updateSelectedAircraft();
1358 void QtLauncher::setAirport(FGAirportRef ref)
1360 if (m_selectedAirport == ref)
1363 m_selectedAirport = ref;
1367 // maintain the recent airport list
1368 QString icao = QString::fromStdString(ref->ident());
1369 if (m_recentAirports.contains(icao)) {
1371 m_recentAirports.removeOne(icao);
1372 m_recentAirports.push_front(icao);
1374 // insert and trim list if necessary
1375 m_recentAirports.push_front(icao);
1376 if (m_recentAirports.size() > MAX_RECENT_AIRPORTS) {
1377 m_recentAirports.pop_back();
1382 updateAirportDescription();
1385 void QtLauncher::onOpenCustomAircraftDir()
1387 QFileInfo info(m_customAircraftDir);
1388 if (!info.exists()) {
1389 int result = QMessageBox::question(this, "Create folder?",
1390 "The custom aircraft folder does not exist, create it now?",
1391 QMessageBox::Yes | QMessageBox::No,
1393 if (result == QMessageBox::No) {
1397 QDir d(m_customAircraftDir);
1398 d.mkpath(m_customAircraftDir);
1401 QUrl u = QUrl::fromLocalFile(m_customAircraftDir);
1402 QDesktopServices::openUrl(u);
1405 void QtLauncher::onEditRatingsFilter()
1407 EditRatingsFilterDialog dialog(this);
1408 dialog.setRatings(m_ratingFilters);
1411 if (dialog.result() == QDialog::Accepted) {
1413 for (int i=0; i<4; ++i) {
1414 m_ratingFilters[i] = dialog.getRating(i);
1415 vl.append(m_ratingFilters[i]);
1417 m_aircraftProxy->setRatings(m_ratingFilters);
1420 settings.setValue("min-ratings", vl);
1424 void QtLauncher::updateSettingsSummary()
1426 QStringList summary;
1427 if (m_ui->timeOfDayCombo->currentIndex() > 0) {
1428 summary.append(QString(m_ui->timeOfDayCombo->currentText().toLower()));
1431 if (m_ui->seasonCombo->currentIndex() > 0) {
1432 summary.append(QString(m_ui->seasonCombo->currentText().toLower()));
1435 if (m_ui->rembrandtCheckbox->isChecked()) {
1436 summary.append("Rembrandt enabled");
1437 } else if (m_ui->msaaCheckbox->isChecked()) {
1438 summary.append("anti-aliasing");
1441 if (m_ui->fetchRealWxrCheckbox->isChecked()) {
1442 summary.append("live weather");
1445 if (m_ui->terrasyncCheck->isChecked()) {
1446 summary.append("automatic scenery downloads");
1449 if (m_ui->startPausedCheck->isChecked()) {
1450 summary.append("paused");
1453 QString s = summary.join(", ");
1454 s[0] = s[0].toUpper();
1455 m_ui->settingsDescription->setText(s);
1458 void QtLauncher::onAddSceneryPath()
1460 QString path = QFileDialog::getExistingDirectory(this, tr("Choose scenery folder"));
1461 if (!path.isEmpty()) {
1462 m_ui->sceneryPathsList->addItem(path);
1467 void QtLauncher::onRemoveSceneryPath()
1469 if (m_ui->sceneryPathsList->currentItem()) {
1470 delete m_ui->sceneryPathsList->currentItem();
1475 void QtLauncher::onRembrandtToggled(bool b)
1477 // Rembrandt and multi-sample are exclusive
1478 m_ui->msaaCheckbox->setEnabled(!b);
1481 #include "QtLauncher.moc"