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);
386 virtual void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
388 // selection feedback rendering
389 if (option.state & QStyle::State_Selected) {
390 QLinearGradient grad(option.rect.topLeft(), option.rect.bottomLeft());
391 grad.setColorAt(0.0, QColor(152, 163, 180));
392 grad.setColorAt(1.0, QColor(90, 107, 131));
394 QBrush backgroundBrush(grad);
395 painter->fillRect(option.rect, backgroundBrush);
397 painter->setPen(QColor(90, 107, 131));
398 painter->drawLine(option.rect.topLeft(), option.rect.topRight());
402 QRect contentRect = option.rect.adjusted(MARGIN, MARGIN, -MARGIN, -MARGIN);
404 QPixmap thumbnail = index.data(Qt::DecorationRole).value<QPixmap>();
405 painter->drawPixmap(contentRect.topLeft(), thumbnail);
408 painter->setPen(QColor(0x7f, 0x7f, 0x7f));
409 painter->setBrush(Qt::NoBrush);
410 painter->drawRect(contentRect.left(), contentRect.top(), thumbnail.width(), thumbnail.height());
412 int variantCount = index.data(AircraftVariantCountRole).toInt();
413 int currentVariant =index.data(AircraftVariantRole).toInt();
414 QString description = index.data(Qt::DisplayRole).toString();
415 contentRect.setLeft(contentRect.left() + MARGIN + thumbnail.width());
417 painter->setPen(Qt::black);
422 QRect descriptionRect = contentRect.adjusted(ARROW_SIZE, 0, -ARROW_SIZE, 0),
425 if (variantCount > 0) {
426 bool canLeft = (currentVariant > 0);
427 bool canRight = (currentVariant < variantCount );
429 QRect leftArrowRect = leftCycleArrowRect(option.rect, index);
430 painter->fillRect(leftArrowRect, canLeft ? Qt::black : Qt::gray);
432 QRect rightArrowRect = rightCycleArrowRect(option.rect, index);
433 painter->fillRect(rightArrowRect, canRight ? Qt::black : Qt::gray);
436 painter->drawText(descriptionRect, Qt::TextWordWrap, description, &actualBounds);
438 QString authors = index.data(AircraftAuthorsRole).toString();
443 QRect authorsRect = descriptionRect;
444 authorsRect.moveTop(actualBounds.bottom() + MARGIN);
445 painter->drawText(authorsRect, Qt::TextWordWrap,
446 QString("by: %1").arg(authors),
449 QRect r = contentRect;
450 r.setWidth(contentRect.width() / 2);
451 r.moveTop(actualBounds.bottom() + MARGIN);
454 drawRating(painter, "Flight model:", r, index.data(AircraftRatingRole).toInt());
455 r.moveTop(r.bottom());
456 drawRating(painter, "Systems:", r, index.data(AircraftRatingRole + 1).toInt());
458 r.moveTop(actualBounds.bottom() + MARGIN);
459 r.moveLeft(r.right());
460 drawRating(painter, "Cockpit:", r, index.data(AircraftRatingRole + 2).toInt());
461 r.moveTop(r.bottom());
462 drawRating(painter, "Exterior model:", r, index.data(AircraftRatingRole + 3).toInt());
465 virtual QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const
467 return QSize(500, 128 + (MARGIN * 2));
470 virtual bool eventFilter( QObject*, QEvent* event )
472 if ( event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease )
474 QMouseEvent* me = static_cast< QMouseEvent* >( event );
475 QModelIndex index = m_view->indexAt( me->pos() );
476 int variantCount = index.data(AircraftVariantCountRole).toInt();
477 int variantIndex = index.data(AircraftVariantRole).toInt();
479 if ( (event->type() == QEvent::MouseButtonRelease) && (variantCount > 0) )
481 QRect vr = m_view->visualRect(index);
482 QRect leftCycleRect = leftCycleArrowRect(vr, index),
483 rightCycleRect = rightCycleArrowRect(vr, index);
485 if ((variantIndex > 0) && leftCycleRect.contains(me->pos())) {
486 m_view->model()->setData(index, variantIndex - 1, AircraftVariantRole);
487 emit variantChanged(index);
489 } else if ((variantIndex < variantCount) && rightCycleRect.contains(me->pos())) {
490 m_view->model()->setData(index, variantIndex + 1, AircraftVariantRole);
491 emit variantChanged(index);
495 } // of mouse button press or release
501 void variantChanged(const QModelIndex& index);
504 QRect leftCycleArrowRect(const QRect& visualRect, const QModelIndex& index) const
506 QRect contentRect = visualRect.adjusted(MARGIN, MARGIN, -MARGIN, -MARGIN);
507 QPixmap thumbnail = index.data(Qt::DecorationRole).value<QPixmap>();
508 contentRect.setLeft(contentRect.left() + MARGIN + thumbnail.width());
510 QRect r = contentRect;
511 r.setRight(r.left() + ARROW_SIZE);
512 r.setBottom(r.top() + ARROW_SIZE);
517 QRect rightCycleArrowRect(const QRect& visualRect, const QModelIndex& index) const
519 QRect contentRect = visualRect.adjusted(MARGIN, MARGIN, -MARGIN, -MARGIN);
520 QPixmap thumbnail = index.data(Qt::DecorationRole).value<QPixmap>();
521 contentRect.setLeft(contentRect.left() + MARGIN + thumbnail.width());
523 QRect r = contentRect;
524 r.setLeft(r.right() - ARROW_SIZE);
525 r.setBottom(r.top() + ARROW_SIZE);
530 void drawRating(QPainter* painter, QString label, const QRect& box, int value) const
532 const int DOT_SIZE = 10;
533 const int DOT_MARGIN = 4;
536 dotBox.setLeft(box.right() - (DOT_MARGIN * 6 + DOT_SIZE * 5));
538 painter->setPen(Qt::black);
540 textBox.setRight(dotBox.left() - DOT_MARGIN);
541 painter->drawText(textBox, Qt::AlignVCenter | Qt::AlignRight, label);
543 painter->setPen(Qt::NoPen);
544 QRect dot(dotBox.left() + DOT_MARGIN,
545 dotBox.center().y() - (DOT_SIZE / 2),
548 for (int i=0; i<5; ++i) {
549 painter->setBrush((i < value) ? QColor(0x3f, 0x3f, 0x3f) : QColor(0xaf, 0xaf, 0xaf));
550 painter->drawEllipse(dot);
551 dot.moveLeft(dot.right() + DOT_MARGIN);
558 class ArgumentsTokenizer
564 explicit Arg(QString k, QString v = QString()) : arg(k), value(v) {}
570 QList<Arg> tokenize(QString in) const
573 const int len = in.count();
579 for (; index < len; ++index) {
581 nc = index < (len - 1) ? in.at(index + 1) : QChar();
585 if (c == QChar('-')) {
586 if (nc == QChar('-')) {
591 // should we pemit single hyphen arguments?
592 // choosing to fail for now
595 } else if (c.isSpace()) {
601 if (c == QChar('=')) {
604 } else if (c.isSpace()) {
606 result.append(Arg(key));
608 // could check for illegal charatcers here
614 if (c == QChar('"')) {
616 } else if (c.isSpace()) {
618 result.append(Arg(key, value));
625 if (c == QChar('\\')) {
626 // check for escaped double-quote inside quoted value
627 if (nc == QChar('"')) {
630 } else if (c == QChar('"')) {
637 } // of character loop
639 // ensure last argument isn't lost
641 result.append(Arg(key));
642 } else if (state == Value) {
643 result.append(Arg(key, value));
658 } // of anonymous namespace
660 class AirportSearchModel : public QAbstractListModel
664 AirportSearchModel() :
665 m_searchActive(false)
669 void setSearch(QString t)
676 std::string term(t.toUpper().toStdString());
677 // try ICAO lookup first
678 FGAirportRef ref = FGAirport::findByIdent(term);
680 m_ids.push_back(ref->guid());
681 m_airports.push_back(ref);
683 m_search.reset(new NavDataCache::ThreadedAirportSearch(term));
684 QTimer::singleShot(100, this, SLOT(onSearchResultsPoll()));
685 m_searchActive = true;
691 bool isSearchActive() const
693 return m_searchActive;
696 virtual int rowCount(const QModelIndex&) const
698 // if empty, return 1 for special 'no matches'?
702 virtual QVariant data(const QModelIndex& index, int role) const
704 if (!index.isValid())
707 FGAirportRef apt = m_airports[index.row()];
709 apt = FGPositioned::loadById<FGAirport>(m_ids[index.row()]);
710 m_airports[index.row()] = apt;
713 if (role == Qt::DisplayRole) {
714 QString name = QString::fromStdString(apt->name());
715 return QString("%1: %2").arg(QString::fromStdString(apt->ident())).arg(name);
718 if (role == Qt::EditRole) {
719 return QString::fromStdString(apt->ident());
722 if (role == Qt::UserRole) {
723 return static_cast<qlonglong>(m_ids[index.row()]);
729 QString firstIdent() const
734 if (!m_airports.front().valid()) {
735 m_airports[0] = FGPositioned::loadById<FGAirport>(m_ids.front());
738 return QString::fromStdString(m_airports.front()->ident());
742 void searchComplete();
745 void onSearchResultsPoll()
747 PositionedIDVec newIds = m_search->results();
749 beginInsertRows(QModelIndex(), m_ids.size(), newIds.size() - 1);
750 for (unsigned int i=m_ids.size(); i < newIds.size(); ++i) {
751 m_ids.push_back(newIds[i]);
752 m_airports.push_back(FGAirportRef()); // null ref
756 if (m_search->isComplete()) {
757 m_searchActive = false;
759 emit searchComplete();
761 QTimer::singleShot(100, this, SLOT(onSearchResultsPoll()));
766 PositionedIDVec m_ids;
767 mutable std::vector<FGAirportRef> m_airports;
769 QScopedPointer<NavDataCache::ThreadedAirportSearch> m_search;
772 class AircraftProxyModel : public QSortFilterProxyModel
776 AircraftProxyModel(QObject* pr) :
777 QSortFilterProxyModel(pr),
778 m_ratingsFilter(true)
780 for (int i=0; i<4; ++i) {
785 void setRatings(int* ratings)
787 ::memcpy(m_ratings, ratings, sizeof(int) * 4);
792 void setRatingFilterEnabled(bool e)
794 if (e == m_ratingsFilter) {
803 bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
805 if (!QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent)) {
809 if (m_ratingsFilter) {
810 QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
811 for (int i=0; i<4; ++i) {
812 if (m_ratings[i] > index.data(AircraftRatingRole + i).toInt()) {
822 bool m_ratingsFilter;
826 QtLauncher::QtLauncher() :
830 m_ui.reset(new Ui::Launcher);
833 #if QT_VERSION >= 0x050300
834 // don't require Qt 5.3
835 m_ui->commandLineArgs->setPlaceholderText("--option=value --prop:/sim/name=value");
838 #if QT_VERSION >= 0x050200
839 m_ui->aircraftFilter->setClearButtonEnabled(true);
842 for (int i=0; i<4; ++i) {
843 m_ratingFilters[i] = 3;
846 m_airportsModel = new AirportSearchModel;
847 m_ui->searchList->setModel(m_airportsModel);
848 connect(m_ui->searchList, &QListView::clicked,
849 this, &QtLauncher::onAirportChoiceSelected);
850 connect(m_airportsModel, &AirportSearchModel::searchComplete,
851 this, &QtLauncher::onAirportSearchComplete);
853 SGPath p = SGPath::documents();
854 p.append("FlightGear");
855 p.append("Aircraft");
856 m_customAircraftDir = QString::fromStdString(p.str());
857 m_ui->customAircraftDirLabel->setText(QString("Custom aircraft folder: %1").arg(m_customAircraftDir));
859 globals->append_aircraft_path(m_customAircraftDir.toStdString());
861 // create and configure the proxy model
862 m_aircraftProxy = new AircraftProxyModel(this);
863 m_aircraftProxy->setSourceModel(new AircraftItemModel(this));
865 m_aircraftProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
866 m_aircraftProxy->setSortCaseSensitivity(Qt::CaseInsensitive);
867 m_aircraftProxy->setSortRole(Qt::DisplayRole);
868 m_aircraftProxy->setDynamicSortFilter(true);
870 m_ui->aircraftList->setModel(m_aircraftProxy);
871 m_ui->aircraftList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
872 AircraftItemDelegate* delegate = new AircraftItemDelegate(m_ui->aircraftList);
873 m_ui->aircraftList->setItemDelegate(delegate);
874 m_ui->aircraftList->setSelectionMode(QAbstractItemView::SingleSelection);
875 connect(m_ui->aircraftList, &QListView::clicked,
876 this, &QtLauncher::onAircraftSelected);
877 connect(delegate, &AircraftItemDelegate::variantChanged,
878 this, &QtLauncher::onAircraftSelected);
880 connect(m_ui->runwayCombo, SIGNAL(currentIndexChanged(int)),
881 this, SLOT(updateAirportDescription()));
882 connect(m_ui->parkingCombo, SIGNAL(currentIndexChanged(int)),
883 this, SLOT(updateAirportDescription()));
884 connect(m_ui->runwayRadio, SIGNAL(toggled(bool)),
885 this, SLOT(updateAirportDescription()));
886 connect(m_ui->parkingRadio, SIGNAL(toggled(bool)),
887 this, SLOT(updateAirportDescription()));
888 connect(m_ui->onFinalCheckbox, SIGNAL(toggled(bool)),
889 this, SLOT(updateAirportDescription()));
892 connect(m_ui->runButton, SIGNAL(clicked()), this, SLOT(onRun()));
893 connect(m_ui->quitButton, SIGNAL(clicked()), this, SLOT(onQuit()));
894 connect(m_ui->airportEdit, SIGNAL(returnPressed()),
895 this, SLOT(onSearchAirports()));
897 connect(m_ui->aircraftFilter, &QLineEdit::textChanged,
898 m_aircraftProxy, &QSortFilterProxyModel::setFilterFixedString);
900 connect(m_ui->airportHistory, &QPushButton::clicked,
901 this, &QtLauncher::onPopupAirportHistory);
902 connect(m_ui->aircraftHistory, &QPushButton::clicked,
903 this, &QtLauncher::onPopupAircraftHistory);
907 connect(m_ui->openAircraftDirButton, &QPushButton::clicked,
908 this, &QtLauncher::onOpenCustomAircraftDir);
910 QAction* qa = new QAction(this);
911 qa->setShortcut(QKeySequence("Ctrl+Q"));
912 connect(qa, &QAction::triggered, this, &QtLauncher::onQuit);
915 connect(m_ui->editRatingFilter, &QPushButton::clicked,
916 this, &QtLauncher::onEditRatingsFilter);
917 connect(m_ui->ratingsFilterCheck, &QAbstractButton::toggled,
918 m_aircraftProxy, &AircraftProxyModel::setRatingFilterEnabled);
920 QIcon historyIcon(":/history-icon");
921 m_ui->aircraftHistory->setIcon(historyIcon);
922 m_ui->airportHistory->setIcon(historyIcon);
924 m_ui->searchIcon->setPixmap(QPixmap(":/search-icon"));
926 connect(m_ui->timeOfDayCombo, SIGNAL(currentIndexChanged(int)),
927 this, SLOT(updateSettingsSummary()));
928 connect(m_ui->seasonCombo, SIGNAL(currentIndexChanged(int)),
929 this, SLOT(updateSettingsSummary()));
930 connect(m_ui->fetchRealWxrCheckbox, SIGNAL(toggled(bool)),
931 this, SLOT(updateSettingsSummary()));
932 connect(m_ui->rembrandtCheckbox, SIGNAL(toggled(bool)),
933 this, SLOT(updateSettingsSummary()));
934 connect(m_ui->terrasyncCheck, SIGNAL(toggled(bool)),
935 this, SLOT(updateSettingsSummary()));
936 connect(m_ui->startPausedCheck, SIGNAL(toggled(bool)),
937 this, SLOT(updateSettingsSummary()));
938 connect(m_ui->msaaCheckbox, SIGNAL(toggled(bool)),
939 this, SLOT(updateSettingsSummary()));
941 connect(m_ui->rembrandtCheckbox, SIGNAL(toggled(bool)),
942 this, SLOT(onRembrandtToggled(bool)));
944 updateSettingsSummary();
946 connect(m_ui->addSceneryPath, &QToolButton::clicked,
947 this, &QtLauncher::onAddSceneryPath);
948 connect(m_ui->removeSceneryPath, &QToolButton::clicked,
949 this, &QtLauncher::onRemoveSceneryPath);
952 QtLauncher::~QtLauncher()
957 bool QtLauncher::runLauncherDialog()
959 Q_INIT_RESOURCE(resources);
961 // startup the nav-cache now. This pre-empts normal startup of
962 // the cache, but no harm done. (Providing scenery paths are consistent)
966 // setup scenery paths now, especially TerraSync path for airport
967 // parking locations (after they're downloaded)
971 if (dlg.result() != QDialog::Accepted) {
978 void QtLauncher::restoreSettings()
981 m_ui->rembrandtCheckbox->setChecked(settings.value("enable-rembrandt", false).toBool());
982 m_ui->terrasyncCheck->setChecked(settings.value("enable-terrasync", true).toBool());
983 m_ui->fullScreenCheckbox->setChecked(settings.value("start-fullscreen", false).toBool());
984 m_ui->msaaCheckbox->setChecked(settings.value("enable-msaa", false).toBool());
985 m_ui->fetchRealWxrCheckbox->setChecked(settings.value("enable-realwx", true).toBool());
986 m_ui->startPausedCheck->setChecked(settings.value("start-paused", false).toBool());
987 m_ui->timeOfDayCombo->setCurrentIndex(settings.value("timeofday", 0).toInt());
988 m_ui->seasonCombo->setCurrentIndex(settings.value("season", 0).toInt());
990 // full paths to -set.xml files
991 m_recentAircraft = settings.value("recent-aircraft").toStringList();
993 if (!m_recentAircraft.empty()) {
994 m_selectedAircraft = m_recentAircraft.front();
996 // select the default C172p
999 updateSelectedAircraft();
1002 m_recentAirports = settings.value("recent-airports").toStringList();
1003 if (!m_recentAirports.empty()) {
1004 setAirport(FGAirport::findByIdent(m_recentAirports.front().toStdString()));
1006 updateAirportDescription();
1009 m_ui->ratingsFilterCheck->setChecked(settings.value("ratings-filter", true).toBool());
1011 Q_FOREACH(QVariant v, settings.value("min-ratings").toList()) {
1012 m_ratingFilters[index++] = v.toInt();
1015 m_aircraftProxy->setRatingFilterEnabled(m_ui->ratingsFilterCheck->isChecked());
1016 m_aircraftProxy->setRatings(m_ratingFilters);
1018 QStringList sceneryPaths = settings.value("scenery-paths").toStringList();
1019 m_ui->sceneryPathsList->addItems(sceneryPaths);
1021 m_ui->commandLineArgs->setPlainText(settings.value("additional-args").toString());
1024 void QtLauncher::saveSettings()
1027 settings.setValue("enable-rembrandt", m_ui->rembrandtCheckbox->isChecked());
1028 settings.setValue("enable-terrasync", m_ui->terrasyncCheck->isChecked());
1029 settings.setValue("enable-msaa", m_ui->msaaCheckbox->isChecked());
1030 settings.setValue("start-fullscreen", m_ui->fullScreenCheckbox->isChecked());
1031 settings.setValue("enable-realwx", m_ui->fetchRealWxrCheckbox->isChecked());
1032 settings.setValue("start-paused", m_ui->startPausedCheck->isChecked());
1033 settings.setValue("ratings-filter", m_ui->ratingsFilterCheck->isChecked());
1034 settings.setValue("recent-aircraft", m_recentAircraft);
1035 settings.setValue("recent-airports", m_recentAirports);
1036 settings.setValue("timeofday", m_ui->timeOfDayCombo->currentIndex());
1037 settings.setValue("season", m_ui->seasonCombo->currentIndex());
1040 for (int i=0; i<m_ui->sceneryPathsList->count(); ++i) {
1041 paths.append(m_ui->sceneryPathsList->item(i)->text());
1044 settings.setValue("scenery-paths", paths);
1045 settings.setValue("additional-args", m_ui->commandLineArgs->toPlainText());
1048 void QtLauncher::setEnableDisableOptionFromCheckbox(QCheckBox* cbox, QString name) const
1050 flightgear::Options* opt = flightgear::Options::sharedInstance();
1051 std::string stdName(name.toStdString());
1052 if (cbox->isChecked()) {
1053 opt->addOption("enable-" + stdName, "");
1055 opt->addOption("disable-" + stdName, "");
1059 void QtLauncher::onRun()
1063 flightgear::Options* opt = flightgear::Options::sharedInstance();
1064 setEnableDisableOptionFromCheckbox(m_ui->terrasyncCheck, "terrasync");
1065 setEnableDisableOptionFromCheckbox(m_ui->fetchRealWxrCheckbox, "real-weather-fetch");
1066 setEnableDisableOptionFromCheckbox(m_ui->rembrandtCheckbox, "rembrandt");
1067 setEnableDisableOptionFromCheckbox(m_ui->fullScreenCheckbox, "fullscreen");
1068 setEnableDisableOptionFromCheckbox(m_ui->startPausedCheck, "freeze");
1070 // MSAA is more complex
1071 if (!m_ui->rembrandtCheckbox->isChecked()) {
1072 if (m_ui->msaaCheckbox->isChecked()) {
1073 globals->get_props()->setIntValue("/sim/rendering/multi-sample-buffers", 1);
1074 globals->get_props()->setIntValue("/sim/rendering/multi-samples", 4);
1076 globals->get_props()->setIntValue("/sim/rendering/multi-sample-buffers", 0);
1081 if (!m_selectedAircraft.isEmpty()) {
1082 QFileInfo setFileInfo(m_selectedAircraft);
1083 opt->addOption("aircraft-dir", setFileInfo.dir().absolutePath().toStdString());
1084 QString setFile = setFileInfo.fileName();
1085 Q_ASSERT(setFile.endsWith("-set.xml"));
1086 setFile.truncate(setFile.count() - 8); // drop the '-set.xml' portion
1087 opt->addOption("aircraft", setFile.toStdString());
1089 // manage aircraft history
1090 if (m_recentAircraft.contains(m_selectedAircraft))
1091 m_recentAircraft.removeOne(m_selectedAircraft);
1092 m_recentAircraft.prepend(m_selectedAircraft);
1093 if (m_recentAircraft.size() > MAX_RECENT_AIRCRAFT)
1094 m_recentAircraft.pop_back();
1097 // airport / location
1098 if (m_selectedAirport) {
1099 opt->addOption("airport", m_selectedAirport->ident());
1102 if (m_ui->runwayRadio->isChecked()) {
1103 int index = m_ui->runwayCombo->itemData(m_ui->runwayCombo->currentIndex()).toInt();
1104 if ((index >= 0) && m_selectedAirport) {
1105 // explicit runway choice
1106 opt->addOption("runway", m_selectedAirport->getRunwayByIndex(index)->ident());
1109 if (m_ui->onFinalCheckbox->isChecked()) {
1110 opt->addOption("glideslope", "3.0");
1111 opt->addOption("offset-distance", "10.0"); // in nautical miles
1113 } else if (m_ui->parkingRadio->isChecked()) {
1114 // parking selection
1115 opt->addOption("parkpos", m_ui->parkingCombo->currentText().toStdString());
1119 if (m_ui->timeOfDayCombo->currentIndex() != 0) {
1120 QString dayval = m_ui->timeOfDayCombo->currentText().toLower();
1121 opt->addOption("timeofday", dayval.toStdString());
1124 if (m_ui->seasonCombo->currentIndex() != 0) {
1125 QString dayval = m_ui->timeOfDayCombo->currentText().toLower();
1126 opt->addOption("season", dayval.toStdString());
1130 for (int i=0; i<m_ui->sceneryPathsList->count(); ++i) {
1131 QString path = m_ui->sceneryPathsList->item(i)->text();
1132 opt->addOption("fg-scenery", path.toStdString());
1135 // additional arguments
1136 ArgumentsTokenizer tk;
1137 Q_FOREACH(ArgumentsTokenizer::Arg a, tk.tokenize(m_ui->commandLineArgs->toPlainText())) {
1138 if (a.arg.startsWith("prop:")) {
1139 QString v = a.arg.mid(5) + "=" + a.value;
1140 opt->addOption("prop", v.toStdString());
1142 opt->addOption(a.arg.toStdString(), a.value.toStdString());
1149 void QtLauncher::onQuit()
1154 void QtLauncher::onSearchAirports()
1156 QString search = m_ui->airportEdit->text();
1157 m_airportsModel->setSearch(search);
1159 if (m_airportsModel->isSearchActive()) {
1160 m_ui->searchStatusText->setText(QString("Searching for '%1'").arg(search));
1161 m_ui->locationStack->setCurrentIndex(2);
1162 } else if (m_airportsModel->rowCount(QModelIndex()) == 1) {
1163 QString ident = m_airportsModel->firstIdent();
1164 setAirport(FGAirport::findByIdent(ident.toStdString()));
1165 m_ui->locationStack->setCurrentIndex(0);
1169 void QtLauncher::onAirportSearchComplete()
1171 int numResults = m_airportsModel->rowCount(QModelIndex());
1172 if (numResults == 0) {
1173 m_ui->searchStatusText->setText(QString("No matching airports for '%1'").arg(m_ui->airportEdit->text()));
1174 } else if (numResults == 1) {
1175 QString ident = m_airportsModel->firstIdent();
1176 setAirport(FGAirport::findByIdent(ident.toStdString()));
1177 m_ui->locationStack->setCurrentIndex(0);
1179 m_ui->locationStack->setCurrentIndex(1);
1183 void QtLauncher::onAirportChanged()
1185 m_ui->runwayCombo->setEnabled(m_selectedAirport);
1186 m_ui->parkingCombo->setEnabled(m_selectedAirport);
1187 m_ui->airportDiagram->setAirport(m_selectedAirport);
1189 m_ui->runwayRadio->setChecked(true); // default back to runway mode
1190 // unelss multiplayer is enabled ?
1192 if (!m_selectedAirport) {
1193 m_ui->airportDescription->setText(QString());
1194 m_ui->airportDiagram->setEnabled(false);
1198 m_ui->airportDiagram->setEnabled(true);
1200 m_ui->runwayCombo->clear();
1201 m_ui->runwayCombo->addItem("Automatic", -1);
1202 for (unsigned int r=0; r<m_selectedAirport->numRunways(); ++r) {
1203 FGRunwayRef rwy = m_selectedAirport->getRunwayByIndex(r);
1204 // add runway with index as data role
1205 m_ui->runwayCombo->addItem(QString::fromStdString(rwy->ident()), r);
1207 m_ui->airportDiagram->addRunway(rwy);
1210 m_ui->parkingCombo->clear();
1211 FGAirportDynamics* dynamics = m_selectedAirport->getDynamics();
1212 PositionedIDVec parkings = NavDataCache::instance()->airportItemsOfType(
1213 m_selectedAirport->guid(),
1214 FGPositioned::PARKING);
1215 if (parkings.empty()) {
1216 m_ui->parkingCombo->setEnabled(false);
1217 m_ui->parkingRadio->setEnabled(false);
1219 m_ui->parkingCombo->setEnabled(true);
1220 m_ui->parkingRadio->setEnabled(true);
1221 Q_FOREACH(PositionedID parking, parkings) {
1222 FGParking* park = dynamics->getParking(parking);
1223 m_ui->parkingCombo->addItem(QString::fromStdString(park->getName()),
1224 static_cast<qlonglong>(parking));
1226 m_ui->airportDiagram->addParking(park);
1231 void QtLauncher::updateAirportDescription()
1233 if (!m_selectedAirport) {
1234 m_ui->airportDescription->setText(QString("No airport selected"));
1238 QString ident = QString::fromStdString(m_selectedAirport->ident()),
1239 name = QString::fromStdString(m_selectedAirport->name());
1240 QString locationOnAirport;
1241 if (m_ui->runwayRadio->isChecked()) {
1242 bool onFinal = m_ui->onFinalCheckbox->isChecked();
1243 QString runwayName = (m_ui->runwayCombo->currentIndex() == 0) ?
1245 QString("runway %1").arg(m_ui->runwayCombo->currentText());
1248 locationOnAirport = QString("on 10-mile final to %1").arg(runwayName);
1250 locationOnAirport = QString("on %1").arg(runwayName);
1252 } else if (m_ui->parkingRadio->isChecked()) {
1253 locationOnAirport = QString("at parking position %1").arg(m_ui->parkingCombo->currentText());
1256 m_ui->airportDescription->setText(QString("%2 (%1): %3").arg(ident).arg(name).arg(locationOnAirport));
1259 void QtLauncher::onAirportChoiceSelected(const QModelIndex& index)
1261 m_ui->locationStack->setCurrentIndex(0);
1262 setAirport(FGPositioned::loadById<FGAirport>(index.data(Qt::UserRole).toULongLong()));
1265 void QtLauncher::onAircraftSelected(const QModelIndex& index)
1267 m_selectedAircraft = index.data(AircraftPathRole).toString();
1268 updateSelectedAircraft();
1271 void QtLauncher::updateSelectedAircraft()
1274 QFileInfo info(m_selectedAircraft);
1275 AircraftItem item(info.dir(), m_selectedAircraft);
1276 m_ui->thumbnail->setPixmap(item.thumbnail);
1277 m_ui->aircraftDescription->setText(item.description);
1278 } catch (sg_exception& e) {
1279 m_ui->thumbnail->setPixmap(QPixmap());
1280 m_ui->aircraftDescription->setText("");
1284 void QtLauncher::onPopupAirportHistory()
1286 if (m_recentAirports.isEmpty()) {
1291 Q_FOREACH(QString aptCode, m_recentAirports) {
1292 FGAirportRef apt = FGAirport::findByIdent(aptCode.toStdString());
1293 QString name = QString::fromStdString(apt->name());
1294 QAction* act = m.addAction(QString("%1 - %2").arg(aptCode).arg(name));
1295 act->setData(aptCode);
1298 QPoint popupPos = m_ui->airportHistory->mapToGlobal(m_ui->airportHistory->rect().bottomLeft());
1299 QAction* triggered = m.exec(popupPos);
1301 FGAirportRef apt = FGAirport::findByIdent(triggered->data().toString().toStdString());
1303 m_ui->airportEdit->clear();
1304 m_ui->locationStack->setCurrentIndex(0);
1308 QModelIndex QtLauncher::proxyIndexForAircraftPath(QString path) const
1310 return m_aircraftProxy->mapFromSource(sourceIndexForAircraftPath(path));
1313 QModelIndex QtLauncher::sourceIndexForAircraftPath(QString path) const
1315 AircraftItemModel* sourceModel = qobject_cast<AircraftItemModel*>(m_aircraftProxy->sourceModel());
1316 Q_ASSERT(sourceModel);
1317 return sourceModel->indexOfAircraftPath(path);
1320 void QtLauncher::onPopupAircraftHistory()
1322 if (m_recentAircraft.isEmpty()) {
1327 Q_FOREACH(QString path, m_recentAircraft) {
1328 QModelIndex index = sourceIndexForAircraftPath(path);
1329 if (!index.isValid()) {
1333 QAction* act = m.addAction(index.data(Qt::DisplayRole).toString());
1337 QPoint popupPos = m_ui->aircraftHistory->mapToGlobal(m_ui->aircraftHistory->rect().bottomLeft());
1338 QAction* triggered = m.exec(popupPos);
1340 m_selectedAircraft = triggered->data().toString();
1341 QModelIndex index = proxyIndexForAircraftPath(m_selectedAircraft);
1342 m_ui->aircraftList->selectionModel()->setCurrentIndex(index,
1343 QItemSelectionModel::ClearAndSelect);
1344 m_ui->aircraftFilter->clear();
1345 updateSelectedAircraft();
1349 void QtLauncher::setAirport(FGAirportRef ref)
1351 if (m_selectedAirport == ref)
1354 m_selectedAirport = ref;
1358 // maintain the recent airport list
1359 QString icao = QString::fromStdString(ref->ident());
1360 if (m_recentAirports.contains(icao)) {
1362 m_recentAirports.removeOne(icao);
1363 m_recentAirports.push_front(icao);
1365 // insert and trim list if necessary
1366 m_recentAirports.push_front(icao);
1367 if (m_recentAirports.size() > MAX_RECENT_AIRPORTS) {
1368 m_recentAirports.pop_back();
1373 updateAirportDescription();
1376 void QtLauncher::onOpenCustomAircraftDir()
1378 QFileInfo info(m_customAircraftDir);
1379 if (!info.exists()) {
1380 int result = QMessageBox::question(this, "Create folder?",
1381 "The custom aircraft folder does not exist, create it now?",
1382 QMessageBox::Yes | QMessageBox::No,
1384 if (result == QMessageBox::No) {
1388 QDir d(m_customAircraftDir);
1389 d.mkpath(m_customAircraftDir);
1392 QUrl u = QUrl::fromLocalFile(m_customAircraftDir);
1393 QDesktopServices::openUrl(u);
1396 void QtLauncher::onEditRatingsFilter()
1398 EditRatingsFilterDialog dialog(this);
1399 dialog.setRatings(m_ratingFilters);
1402 if (dialog.result() == QDialog::Accepted) {
1404 for (int i=0; i<4; ++i) {
1405 m_ratingFilters[i] = dialog.getRating(i);
1406 vl.append(m_ratingFilters[i]);
1408 m_aircraftProxy->setRatings(m_ratingFilters);
1411 settings.setValue("min-ratings", vl);
1415 void QtLauncher::updateSettingsSummary()
1417 QStringList summary;
1418 if (m_ui->timeOfDayCombo->currentIndex() > 0) {
1419 summary.append(QString(m_ui->timeOfDayCombo->currentText().toLower()));
1422 if (m_ui->seasonCombo->currentIndex() > 0) {
1423 summary.append(QString(m_ui->seasonCombo->currentText().toLower()));
1426 if (m_ui->rembrandtCheckbox->isChecked()) {
1427 summary.append("Rembrandt enabled");
1428 } else if (m_ui->msaaCheckbox->isChecked()) {
1429 summary.append("anti-aliasing");
1432 if (m_ui->fetchRealWxrCheckbox->isChecked()) {
1433 summary.append("live weather");
1436 if (m_ui->terrasyncCheck->isChecked()) {
1437 summary.append("automatic scenery downloads");
1440 if (m_ui->startPausedCheck->isChecked()) {
1441 summary.append("paused");
1444 QString s = summary.join(", ");
1445 s[0] = s[0].toUpper();
1446 m_ui->settingsDescription->setText(s);
1449 void QtLauncher::onAddSceneryPath()
1451 QString path = QFileDialog::getExistingDirectory(this, tr("Choose scenery folder"));
1452 if (!path.isEmpty()) {
1453 m_ui->sceneryPathsList->addItem(path);
1458 void QtLauncher::onRemoveSceneryPath()
1460 if (m_ui->sceneryPathsList->currentItem()) {
1461 delete m_ui->sceneryPathsList->currentItem();
1466 void QtLauncher::onRembrandtToggled(bool b)
1468 // Rembrandt and multi-sample are exclusive
1469 m_ui->msaaCheckbox->setEnabled(!b);
1472 #include "QtLauncher.moc"