diff --git a/src/Airports/gnnode.cxx b/src/Airports/gnnode.cxx index 98fd60483..b7d89a56c 100644 --- a/src/Airports/gnnode.cxx +++ b/src/Airports/gnnode.cxx @@ -16,8 +16,10 @@ using namespace flightgear; * FGTaxiNode *************************************************************************/ -FGTaxiNode::FGTaxiNode(int index, const SGGeod& pos, bool aOnRunway, int aHoldType) : - FGPositioned(TRANSIENT_ID, FGPositioned::PARKING, "", pos), +FGTaxiNode::FGTaxiNode(int index, const SGGeod& pos, + bool aOnRunway, int aHoldType, + const std::string& ident) : + FGPositioned(TRANSIENT_ID, FGPositioned::PARKING, ident, pos), m_index(index), isOnRunway(aOnRunway), holdType(aHoldType), diff --git a/src/Airports/gnnode.hxx b/src/Airports/gnnode.hxx index 437bc5a86..0c2739cfe 100644 --- a/src/Airports/gnnode.hxx +++ b/src/Airports/gnnode.hxx @@ -31,7 +31,8 @@ protected: bool m_isPushback; public: - FGTaxiNode(int index, const SGGeod& pos, bool aOnRunway, int aHoldType); + FGTaxiNode(int index, const SGGeod& pos, bool aOnRunway, int aHoldType, + const std::string& ident = {}); virtual ~FGTaxiNode(); void setElevation(double val); diff --git a/src/Airports/parking.cxx b/src/Airports/parking.cxx index b5c8f1ef0..63565ac23 100644 --- a/src/Airports/parking.cxx +++ b/src/Airports/parking.cxx @@ -42,10 +42,9 @@ FGParking::FGParking(int index, const std::string& name, const std::string& aType, const std::string& codes) : - FGTaxiNode(index, pos, false, 0), + FGTaxiNode(index, pos, false, 0, name), heading(aHeading), radius(aRadius), - parkingName(name), type(aType), airlineCodes(codes) { diff --git a/src/Airports/parking.hxx b/src/Airports/parking.hxx index 9957c8c7b..21364714a 100644 --- a/src/Airports/parking.hxx +++ b/src/Airports/parking.hxx @@ -42,7 +42,6 @@ class FGParking : public FGTaxiNode private: const double heading; const double radius; - const std::string parkingName; const std::string type; const std::string airlineCodes; FGTaxiNodeRef pushBackPoint; @@ -64,10 +63,7 @@ public: std::string getType () const { return type; }; std::string getCodes () const { return airlineCodes;}; - std::string getName () const { return parkingName; }; - - // TODO do parkings have different name and ident? - virtual const std::string& name() const { return parkingName; } + std::string getName () const { return ident(); }; void setPushBackPoint(const FGTaxiNodeRef& node); FGTaxiNodeRef getPushBackPoint () { return pushBackPoint; }; diff --git a/src/GUI/AirportDiagram.cxx b/src/GUI/AirportDiagram.cxx index 07a563d5b..6a616136e 100644 --- a/src/GUI/AirportDiagram.cxx +++ b/src/GUI/AirportDiagram.cxx @@ -33,8 +33,12 @@ #include #include #include +#include #include +#include + +#include "QmlPositioned.hxx" static double distanceToLineSegment(const QVector2D& p, const QVector2D& a, const QVector2D& b, double* outT = NULL) @@ -86,7 +90,7 @@ static double unitLengthAfterMapping(const QTransform& t) return QVector2D(tVec).length(); } -AirportDiagram::AirportDiagram(QWidget* pr) : +AirportDiagram::AirportDiagram(QQuickItem* pr) : BaseDiagram(pr), m_approachDistanceNm(-1.0) { @@ -133,10 +137,29 @@ void AirportDiagram::setAirport(FGAirportRef apt) m_airport = apt; m_projectionCenter = apt ? apt->geod() : SGGeod(); m_runways.clear(); - m_approachDistanceNm = -1.0; m_parking.clear(); + m_parking.clear(); m_helipads.clear(); if (apt) { + if (apt->type() == FGPositioned::HELIPORT) { + for (unsigned int r=0; rnumHelipads(); ++r) { + FGHelipadRef pad = apt->getHelipadByIndex(r); + // add pad with index as data role + addHelipad(pad); + } + } else { + for (unsigned int r=0; rnumRunways(); ++r) { + addRunway(apt->getRunwayByIndex(r)); + } + } + + FGGroundNetwork* ground = apt->groundNetwork(); + if (ground && ground->exists()) { + for (auto park : ground->allParkings()) { + addParking(park); + } + } // of was able to get ground-network + buildTaxiways(); buildPavements(); } @@ -148,48 +171,23 @@ void AirportDiagram::setAirport(FGAirportRef apt) update(); } -FGRunwayRef AirportDiagram::selectedRunway() const +void AirportDiagram::setSelection(QmlPositioned* pos) { - return m_selectedRunway; -} - -void AirportDiagram::setSelectedRunway(FGRunwayRef r) -{ - if (r == m_selectedRunway) { + if (pos && (m_selection == pos->inner())) { return; } - m_selectedParking.clear(); - m_selectedHelipad.clear(); - m_selectedRunway = r; - update(); -} - -void AirportDiagram::setSelectedHelipad(FGHelipadRef pad) -{ - if (pad == m_selectedHelipad) { - return; + if (!pos) { + m_selection.clear(); + } else { + m_selection = pos->inner(); } - - m_selectedParking.clear(); - m_selectedRunway.clear(); - m_selectedHelipad = pad; + emit selectionChanged(); + recomputeBounds(true); update(); } -void AirportDiagram::setSelectedParking(FGParkingRef park) -{ - if (m_selectedParking == park) { - return; - } - - m_selectedRunway.clear(); - m_selectedHelipad.clear(); - m_selectedParking = park; - update(); -} - -void AirportDiagram::setApproachExtensionDistance(double distanceNm) +void AirportDiagram::setApproachExtensionNm(double distanceNm) { if (m_approachDistanceNm == distanceNm) { return; @@ -198,6 +196,38 @@ void AirportDiagram::setApproachExtensionDistance(double distanceNm) m_approachDistanceNm = distanceNm; recomputeBounds(true); update(); + emit approachExtensionChanged(); +} + +double AirportDiagram::approachExtensionNm() const +{ + return m_approachDistanceNm; +} + +QmlPositioned* AirportDiagram::selection() const +{ + if (!m_selection) + return nullptr; + + return new QmlPositioned{m_selection}; +} + +qlonglong AirportDiagram::airportGuid() const +{ + if (!m_airport) + return 0; + return m_airport->guid(); +} + +void AirportDiagram::setAirportGuid(qlonglong guid) +{ + if (guid == -1) { + m_airport.clear(); + } else { + m_airport = fgpositioned_cast(flightgear::NavDataCache::instance()->loadById(guid)); + } + setAirport(m_airport); + emit airportChanged(); } void AirportDiagram::addRunway(FGRunwayRef rwy) @@ -233,16 +263,17 @@ void AirportDiagram::doComputeBounds() } Q_FOREACH(const ParkingData& p, m_parking) { - extendBounds(p.pt); + extendBounds(p.pt, 10.0); } Q_FOREACH(const HelipadData& p, m_helipads) { - extendBounds(p.pt); + extendBounds(p.pt, 20.0); } - if (m_selectedRunway && (m_approachDistanceNm > 0.0)) { + FGRunway* runwaySelection = fgpositioned_cast(m_selection); + if (runwaySelection && (m_approachDistanceNm > 0.0)) { double d = SG_NM_TO_METER * m_approachDistanceNm; - QPointF pt = project(m_selectedRunway->pointOnCenterline(-d)); + QPointF pt = project(runwaySelection->pointOnCenterline(-d)); extendBounds(pt); } } @@ -263,11 +294,9 @@ void AirportDiagram::addHelipad(FGHelipadRef pad) update(); } - void AirportDiagram::paintContents(QPainter* p) { QTransform t = p->transform(); - // pavements QBrush brush(QColor(0x9f, 0x9f, 0x9f)); Q_FOREACH(const QPainterPath& path, m_pavements) { @@ -305,11 +334,13 @@ void AirportDiagram::paintContents(QPainter* p) SGGeod aircraftPos; int headingDeg; + FGRunway* runwaySelection = fgpositioned_cast(m_selection); + // now draw the runways for real Q_FOREACH(const RunwayData& r, m_runways) { QColor color(Qt::magenta); - if ((r.runway == m_selectedRunway) || (r.runway->reciprocalRunway() == m_selectedRunway)) { + if ((r.runway == runwaySelection) || (r.runway->reciprocalRunway() == runwaySelection)) { color = Qt::yellow; } @@ -329,7 +360,7 @@ void AirportDiagram::paintContents(QPainter* p) // invert scaling factor so we can use screen pixel sizes here p->scale(1.0 / m_scale, 1.0/ m_scale); - p->setPen((r.runway == m_selectedRunway) ? Qt::yellow : Qt::magenta); + p->setPen((r.runway == runwaySelection) ? Qt::yellow : Qt::magenta); p->drawText(QRect(-100, 5, 200, 200), ident, Qt::AlignHCenter | Qt::AlignTop); FGRunway* recip = r.runway->reciprocalRunway(); @@ -340,28 +371,28 @@ void AirportDiagram::paintContents(QPainter* p) p->rotate(recip->headingDeg()); p->scale(1.0 / m_scale, 1.0/ m_scale); - p->setPen((r.runway->reciprocalRunway() == m_selectedRunway) ? Qt::yellow : Qt::magenta); + p->setPen((r.runway->reciprocalRunway() == runwaySelection) ? Qt::yellow : Qt::magenta); p->drawText(QRect(-100, 5, 200, 200), recipIdent, Qt::AlignHCenter | Qt::AlignTop); } - if (m_selectedRunway) { + if (runwaySelection) { drawAircraft = true; - aircraftPos = m_selectedRunway->geod(); - headingDeg = m_selectedRunway->headingDeg(); + aircraftPos = runwaySelection->geod(); + headingDeg = runwaySelection->headingDeg(); } - if (m_selectedRunway && (m_approachDistanceNm > 0.0)) { + if (runwaySelection && (m_approachDistanceNm > 0.0)) { p->setTransform(t); // draw approach extension point double d = SG_NM_TO_METER * m_approachDistanceNm; - QPointF pt = project(m_selectedRunway->pointOnCenterline(-d)); - QPointF pt2 = project(m_selectedRunway->geod()); + QPointF pt = project(runwaySelection->pointOnCenterline(-d)); + QPointF pt2 = project(runwaySelection->geod()); QPen pen(Qt::yellow); pen.setWidth(2.0 / m_scale); p->setPen(pen); p->drawLine(pt, pt2); - aircraftPos = m_selectedRunway->pointOnCenterline(-d); + aircraftPos = runwaySelection->pointOnCenterline(-d); } if (drawAircraft) { @@ -388,23 +419,26 @@ void AirportDiagram::paintContents(QPainter* p) void AirportDiagram::drawHelipads(QPainter* painter) { - QTransform t = painter->transform(); + FGHelipad* selectedHelipad = fgpositioned_cast(m_selection); + Q_FOREACH(const HelipadData& p, m_helipads) { - painter->setTransform(t); + painter->save(); painter->translate(p.pt); - if (p.helipad == m_selectedHelipad) { + if (p.helipad == selectedHelipad) { painter->setBrush(Qt::yellow); } else { painter->setBrush(Qt::magenta); } painter->drawPath(m_helipadIconPath); + painter->restore(); } } void AirportDiagram::drawParking(QPainter* painter, const ParkingData& p) const { + painter->save(); painter->translate(p.pt); double hdg = p.parking->getHeading(); @@ -420,8 +454,10 @@ void AirportDiagram::drawParking(QPainter* painter, const ParkingData& p) const } painter->rotate(hdg); + painter->setPen(Qt::NoPen); - if (p.parking == m_selectedParking) { + FGParking* selectedParking = fgpositioned_cast(m_selection); + if (p.parking == selectedParking) { painter->setBrush(Qt::yellow); } else { painter->setBrush(QColor(255, 196, 196)); // kind of pink @@ -431,7 +467,7 @@ void AirportDiagram::drawParking(QPainter* painter, const ParkingData& p) const // ensure the selection colour is quite visible, by not filling // with white when selected - if (p.parking != m_selectedParking) { + if (p.parking != selectedParking) { painter->fillRect(labelRect, Qt::white); } @@ -450,39 +486,39 @@ void AirportDiagram::drawParking(QPainter* painter, const ParkingData& p) const // draw text painter->setPen(Qt::black); painter->drawText(labelRect, textFlags, parkingName); + painter->restore(); } AirportDiagram::ParkingData AirportDiagram::findParkingData(const FGParkingRef &pk) const { + FGParking* selectedParking = fgpositioned_cast(m_selection); + if (!selectedParking) + return {}; + Q_FOREACH(const ParkingData& p, m_parking) { - if (p.parking == m_selectedParking) { + if (p.parking == selectedParking) { return p; } } - return ParkingData(); + return {}; } void AirportDiagram::drawParkings(QPainter* painter) const { - painter->save(); - QTransform t = painter->transform(); + FGParking* selectedParking = fgpositioned_cast(m_selection); Q_FOREACH(const ParkingData& p, m_parking) { - if (p.parking == m_selectedParking) { + if (p.parking == selectedParking) { continue; // skip and draw last } - painter->setTransform(t); drawParking(painter, p); } - if (m_selectedParking) { - painter->setTransform(t); - drawParking(painter, findParkingData(m_selectedParking)); + if (selectedParking) { + drawParking(painter, findParkingData(selectedParking)); } - - painter->restore(); } void AirportDiagram::drawILS(QPainter* painter, FGRunwayRef runway) const @@ -514,12 +550,14 @@ void AirportDiagram::drawILS(QPainter* painter, FGRunwayRef runway) const void AirportDiagram::mouseReleaseEvent(QMouseEvent* me) { - if (me->button() != Qt::LeftButton) { - return; - } - + me->accept(); QTransform t(transform()); double minWidth = 8.0 * unitLengthAfterMapping(t.inverted()); +#if 0 + QImage img(width(), height(), QImage::Format_ARGB32); + QPainter imgPaint(&img); + imgPaint.setPen(QPen(Qt::cyan, 1)); +#endif Q_FOREACH(const RunwayData& r, m_runways) { QPainterPath pp = pathForRunway(r, t, minWidth); @@ -528,11 +566,8 @@ void AirportDiagram::mouseReleaseEvent(QMouseEvent* me) QPointF p1(t.map(r.p1)), p2(t.map(r.p2)); double param; distanceToLineSegment(QVector2D(me->pos()), QVector2D(p1), QVector2D(p2), ¶m); - if (param > 0.5) { - emit clickedRunway(r.runway->reciprocalRunway()); - } else { - emit clickedRunway(r.runway); - } + const FGRunwayRef clickedRunway = (param > 0.5) ? FGRunwayRef{r.runway->reciprocalRunway()} : r.runway; + emit clicked(new QmlPositioned{clickedRunway}); return; } } // of runways iteration @@ -540,18 +575,22 @@ void AirportDiagram::mouseReleaseEvent(QMouseEvent* me) Q_FOREACH(const ParkingData& parking, m_parking) { QPainterPath pp = pathForParking(parking, t); if (pp.contains(me->pos())) { - emit clickedParking(parking.parking); + emit clicked(new QmlPositioned{parking.parking}); return; } } Q_FOREACH(const HelipadData& pad, m_helipads) { QPainterPath pp = pathForHelipad(pad, t); + //imgPaint.drawPath(pp); if (pp.contains(me->pos())) { - emit clickedHelipad(pad.helipad); + emit clicked(new QmlPositioned{pad.helipad}); return; } } +#if 0 + img.save("/Users/jmt/Desktop/img.png"); +#endif } QPainterPath AirportDiagram::pathForRunway(const RunwayData& r, const QTransform& t, diff --git a/src/GUI/AirportDiagram.hxx b/src/GUI/AirportDiagram.hxx index 99c6038ee..385767bd1 100644 --- a/src/GUI/AirportDiagram.hxx +++ b/src/GUI/AirportDiagram.hxx @@ -23,15 +23,25 @@ #include "BaseDiagram.hxx" +#include + #include #include #include +// forward decls +class QmlPositioned; + class AirportDiagram : public BaseDiagram { Q_OBJECT + + Q_PROPERTY(QmlPositioned* selection READ selection WRITE setSelection NOTIFY selectionChanged) + Q_PROPERTY(qlonglong airport READ airportGuid WRITE setAirportGuid NOTIFY airportChanged) + + Q_PROPERTY(double approachExtensionNm READ approachExtensionNm WRITE setApproachExtensionNm NOTIFY approachExtensionChanged) public: - AirportDiagram(QWidget* pr); + AirportDiagram(QQuickItem* pr = nullptr); virtual ~AirportDiagram(); void setAirport(FGAirportRef apt); @@ -40,24 +50,30 @@ public: void addParking(FGParkingRef park); void addHelipad(FGHelipadRef pad); - FGRunwayRef selectedRunway() const; - void setSelectedRunway(FGRunwayRef r); + QmlPositioned* selection() const; - void setSelectedHelipad(FGHelipadRef pad); - void setSelectedParking(FGParkingRef park); + void setSelection(QmlPositioned* pos); + + void setApproachExtensionNm(double distanceNm); + double approachExtensionNm() const; + + qlonglong airportGuid() const; + void setAirportGuid(qlonglong guid); - void setApproachExtensionDistance(double distanceNm); Q_SIGNALS: - void clickedRunway(FGRunwayRef rwy); - void clickedHelipad(FGHelipadRef pad); - void clickedParking(FGParkingRef park); + void clicked(QmlPositioned* pos); + + void selectionChanged(); + void airportChanged(); + void approachExtensionChanged(); + protected: - virtual void mouseReleaseEvent(QMouseEvent* me); + void mouseReleaseEvent(QMouseEvent* me) override; - void paintContents(QPainter*) Q_DECL_OVERRIDE; + void paintContents(QPainter*) override; - void doComputeBounds() Q_DECL_OVERRIDE; + void doComputeBounds() override; private: struct RunwayData { QPointF p1, p2; @@ -115,11 +131,9 @@ private: QPainterPath m_parkingIconPath, // arrow points right m_parkingIconLeftPath; // arrow points left double m_approachDistanceNm; - FGRunwayRef m_selectedRunway; - FGParkingRef m_selectedParking; - FGHelipadRef m_selectedHelipad; QPainterPath m_helipadIconPath; + FGPositionedRef m_selection; }; #endif // of GUI_AIRPORT_DIAGRAM_HXX diff --git a/src/GUI/BaseDiagram.cxx b/src/GUI/BaseDiagram.cxx index 4395ac35d..d0a34eb87 100644 --- a/src/GUI/BaseDiagram.cxx +++ b/src/GUI/BaseDiagram.cxx @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -39,6 +40,7 @@ const double rec = 6378137; // earth radius, equator (?) const double rpol = 6356752.314; // earth radius, polar (?) const double MINIMUM_SCALE = 0.002; +const double MAXIMUM_SCALE = 2.0; //Returns Earth radius at a given latitude (Ellipsoide equation with two equal axis) static double earth_radius_lat( double lat ) @@ -48,14 +50,15 @@ static double earth_radius_lat( double lat ) return 1.0 / sqrt( a * a + b * b ); } -BaseDiagram::BaseDiagram(QWidget* pr) : - QWidget(pr), +BaseDiagram::BaseDiagram(QQuickItem* pr) : + QQuickPaintedItem(pr), m_autoScalePan(true), m_wheelAngleDeltaAccumulator(0) { - setSizePolicy(QSizePolicy::MinimumExpanding, - QSizePolicy::MinimumExpanding); - setMinimumSize(100, 100); + setAcceptedMouseButtons(Qt::LeftButton); + setFlag(ItemHasContents); + setOpaquePainting(true); + setAntialiasing(true); } QTransform BaseDiagram::transform() const @@ -98,11 +101,15 @@ void BaseDiagram::extendRect(QRectF &r, const QPointF &p) } } -void BaseDiagram::paintEvent(QPaintEvent*) +QRect BaseDiagram::rect() const { - QPainter p(this); - p.setRenderHints(QPainter::Antialiasing); - p.fillRect(rect(), QColor(0x3f, 0x3f, 0x3f)); + return QRect(0, 0, width(), height()); +} + +void BaseDiagram::paint(QPainter* p) +{ + //p->setRenderHints(QPainter::Antialiasing); + p->fillRect(rect(), QColor(0x3f, 0x3f, 0x3f)); if (m_autoScalePan) { // fit bounds within our available space, allowing for a margin @@ -110,24 +117,26 @@ void BaseDiagram::paintEvent(QPaintEvent*) double ratioInX = (width() - MARGIN * 2) / m_bounds.width(); double ratioInY = (height() - MARGIN * 2) / m_bounds.height(); m_scale = std::min(ratioInX, ratioInY); + SG_CLAMP_RANGE(m_scale, MINIMUM_SCALE, MAXIMUM_SCALE); } - QTransform t(transform()); - p.setTransform(t); + m_baseDeviceTransform = p->deviceTransform(); + m_viewportTransform = transform(); + p->setWorldTransform(m_viewportTransform * m_baseDeviceTransform); - paintPolygonData(&p); - - paintNavaids(&p); - - paintContents(&p); + paintPolygonData(p); + paintNavaids(p); + paintContents(p); } void BaseDiagram::paintAirplaneIcon(QPainter* painter, const SGGeod& geod, int headingDeg) { QPointF pos = project(geod); QPixmap pix(":/airplane-icon"); - pos = painter->transform().map(pos); - painter->resetTransform(); + pos = m_viewportTransform.map(pos); + painter->save(); + painter->setWorldTransform(m_baseDeviceTransform); + painter->translate(pos.x(), pos.y()); painter->rotate(headingDeg); @@ -135,16 +144,17 @@ void BaseDiagram::paintAirplaneIcon(QPainter* painter, const SGGeod& geod, int h QRect airplaneIconRect = pix.rect(); airplaneIconRect.moveCenter(QPoint(0,0)); painter->drawPixmap(airplaneIconRect, pix); + + painter->restore(); } void BaseDiagram::paintPolygonData(QPainter* painter) { - QTransform xf = painter->transform(); - QTransform invT = xf.inverted(); - - SGGeod topLeft = unproject(invT.map(rect().topLeft()), m_projectionCenter); - SGGeod viewCenter = unproject(invT.map(rect().center()), m_projectionCenter); - SGGeod bottomRight = unproject(invT.map(rect().bottomRight()), m_projectionCenter); + QTransform invT = m_viewportTransform.inverted(); + const auto geom = rect(); + SGGeod topLeft = unproject(invT.map(geom.topLeft()), m_projectionCenter); + SGGeod viewCenter = unproject(invT.map(geom.center()), m_projectionCenter); + SGGeod bottomRight = unproject(invT.map(geom.bottomRight()), m_projectionCenter); double drawRangeNm = std::max(SGGeodesy::distanceNm(viewCenter, topLeft), SGGeodesy::distanceNm(viewCenter, bottomRight)); @@ -155,35 +165,31 @@ void BaseDiagram::paintPolygonData(QPainter* painter) QPen waterPen(QColor(64, 64, 255), 1); waterPen.setCosmetic(true); painter->setPen(waterPen); - flightgear::PolyLineList::const_iterator it; - for (it=lines.begin(); it != lines.end(); ++it) { - paintGeodVec(painter, (*it)->points()); + for (auto line : lines) { + paintGeodVec(painter, line->points()); } lines = flightgear::PolyLine::linesNearPos(viewCenter, drawRangeNm, flightgear::PolyLine::URBAN); - for (it=lines.begin(); it != lines.end(); ++it) { - fillClosedGeodVec(painter, QColor(192, 192, 96), (*it)->points()); + for (auto line : lines) { + fillClosedGeodVec(painter, QColor(192, 192, 96), line->points()); } lines = flightgear::PolyLine::linesNearPos(viewCenter, drawRangeNm, flightgear::PolyLine::RIVER); painter->setPen(waterPen); - for (it=lines.begin(); it != lines.end(); ++it) { - paintGeodVec(painter, (*it)->points()); + for (auto line : lines) { + paintGeodVec(painter, line->points()); } lines = flightgear::PolyLine::linesNearPos(viewCenter, drawRangeNm, flightgear::PolyLine::LAKE); - for (it=lines.begin(); it != lines.end(); ++it) { - fillClosedGeodVec(painter, QColor(128, 128, 255), - (*it)->points()); + for (auto line : lines) { + fillClosedGeodVec(painter, QColor(128, 128, 255), line->points()); } - - } void BaseDiagram::paintGeodVec(QPainter* painter, const flightgear::SGGeodVec& vec) @@ -216,17 +222,17 @@ class MapFilter : public FGPositioned::TypeFilter { public: - MapFilter(LauncherAircraftType aircraft) + MapFilter(LauncherController::AircraftType aircraft) { // addType(FGPositioned::FIX); addType(FGPositioned::NDB); addType(FGPositioned::VOR); - if (aircraft == Helicopter) { + if (aircraft == LauncherController::Helicopter) { addType(FGPositioned::HELIPAD); } - if (aircraft == Seaplane) { + if (aircraft == LauncherController::Seaplane) { addType(FGPositioned::SEAPORT); } else { addType(FGPositioned::AIRPORT); @@ -252,12 +258,11 @@ public: void BaseDiagram::splitItems(const FGPositionedList& in, FGPositionedList& navaids, FGPositionedList& ports) { - FGPositionedList::const_iterator it = in.begin(); - for (; it != in.end(); ++it) { - if (FGPositioned::isAirportType(it->ptr())) { - ports.push_back(*it); + for (auto p : in) { + if (FGPositioned::isAirportType(p)) { + ports.push_back(p); } else { - navaids.push_back(*it); + navaids.push_back(p); } } } @@ -273,10 +278,8 @@ bool orderAirportsByRunwayLength(const FGPositionedRef& a, void BaseDiagram::paintNavaids(QPainter* painter) { - QTransform xf = painter->transform(); - painter->setTransform(QTransform()); // reset to identity - QTransform invT = xf.inverted(); - + painter->save(); + QTransform invT = m_viewportTransform.inverted(); SGGeod topLeft = unproject(invT.map(rect().topLeft()), m_projectionCenter); SGGeod viewCenter = unproject(invT.map(rect().center()), m_projectionCenter); @@ -300,19 +303,17 @@ void BaseDiagram::paintNavaids(QPainter* painter) m_labelRects.clear(); m_labelRects.reserve(items.size()); + painter->setTransform(m_baseDeviceTransform); - FGPositionedList::const_iterator it; - for (it = ports.begin(); it != ports.end(); ++it) { - paintNavaid(painter, xf, *it); + for (auto port : ports) { + paintNavaid(painter, port); } - for (it = navaids.begin(); it != navaids.end(); ++it) { - paintNavaid(painter, xf, *it); + for (auto nav : navaids) { + paintNavaid(painter, nav); } - - // restore transform - painter->setTransform(xf); + painter->restore(); } QRect boundsOfLines(const QVector& lines) @@ -325,7 +326,7 @@ QRect boundsOfLines(const QVector& lines) return r; } -void BaseDiagram::paintNavaid(QPainter* painter, const QTransform& t, const FGPositionedRef &pos) +void BaseDiagram::paintNavaid(QPainter* painter, const FGPositionedRef &pos) { if (isNavaidIgnored(pos)) return; @@ -341,9 +342,11 @@ void BaseDiagram::paintNavaid(QPainter* painter, const QTransform& t, const FGPo if (apt->hasHardRunwayOfLengthFt(minRunwayLengthFt)) { drawAsIcon = false; - painter->setTransform(t); QVector lines = projectAirportRuwaysWithCenter(apt, m_projectionCenter); + painter->save(); + painter->setTransform(m_viewportTransform * m_baseDeviceTransform); + QPen pen(QColor(0x03, 0x83, 0xbf), 8); pen.setCosmetic(true); painter->setPen(pen); @@ -354,15 +357,15 @@ void BaseDiagram::paintNavaid(QPainter* painter, const QTransform& t, const FGPo painter->setPen(linePen); painter->drawLines(lines); - painter->resetTransform(); + iconRect = m_viewportTransform.mapRect(boundsOfLines(lines)); - iconRect = t.mapRect(boundsOfLines(lines)); + painter->restore(); } } if (drawAsIcon) { QPixmap pm = iconForPositioned(pos); - QPointF loc = t.map(project(pos->geod())); + QPointF loc = m_viewportTransform.map(project(pos->geod())); iconRect = pm.rect(); iconRect.moveCenter(loc.toPoint()); painter->drawPixmap(iconRect, pm); @@ -510,8 +513,13 @@ QRect BaseDiagram::labelPositioned(const QRect& itemRect, void BaseDiagram::mousePressEvent(QMouseEvent *me) { + if (!hasActiveFocus()) { + forceActiveFocus(Qt::MouseFocusReason); + } + m_lastMousePos = me->pos(); m_didPan = false; + me->accept(); } void BaseDiagram::mouseMoveEvent(QMouseEvent *me) @@ -558,7 +566,7 @@ void BaseDiagram::wheelEvent(QWheelEvent *we) m_scale *= 0.75; } - SG_CLAMP_RANGE(m_scale, MINIMUM_SCALE, 1.0); + SG_CLAMP_RANGE(m_scale, MINIMUM_SCALE, MAXIMUM_SCALE); update(); } @@ -585,7 +593,7 @@ void BaseDiagram::doComputeBounds() // no-op in the base class } -void BaseDiagram::extendBounds(const QPointF& p) +void BaseDiagram::extendBounds(const QPointF& p, double radiusM) { // this check added after a bug where apt.dat reports SCSL as // https://airportguide.com/airport/info/AG0003 (British Columbia) @@ -598,7 +606,12 @@ void BaseDiagram::extendBounds(const QPointF& p) return; } - extendRect(m_bounds, p); + if (radiusM > 0.0) { + extendRect(m_bounds, p - QPointF(radiusM, radiusM)); + extendRect(m_bounds, p + QPointF(radiusM, radiusM)); + } else { + extendRect(m_bounds, p); + } } QPointF BaseDiagram::project(const SGGeod& geod, const SGGeod& center) @@ -787,7 +800,7 @@ QVector BaseDiagram::projectAirportRuwaysWithCenter(FGAirportRef apt, co return r; } -void BaseDiagram::setAircraftType(LauncherAircraftType type) +void BaseDiagram::setAircraftType(LauncherController::AircraftType type) { m_aircraftType = type; update(); diff --git a/src/GUI/BaseDiagram.hxx b/src/GUI/BaseDiagram.hxx index 1ea041167..c4a0d8dde 100644 --- a/src/GUI/BaseDiagram.hxx +++ b/src/GUI/BaseDiagram.hxx @@ -21,23 +21,23 @@ #ifndef GUI_BASEDIAGRAM_HXX #define GUI_BASEDIAGRAM_HXX -#include #include #include +#include +#include #include #include #include #include +#include "LauncherController.hxx" -#include "QtLauncher_fwd.hxx" - -class BaseDiagram : public QWidget +class BaseDiagram : public QQuickPaintedItem { Q_OBJECT public: - BaseDiagram(QWidget* pr); + BaseDiagram(QQuickItem* pr = nullptr); enum IconOption { @@ -54,14 +54,16 @@ public: static QVector projectAirportRuwaysIntoRect(FGAirportRef apt, const QRectF& bounds); static QVector projectAirportRuwaysWithCenter(FGAirportRef apt, const SGGeod &c); - void setAircraftType(LauncherAircraftType type); + void setAircraftType(LauncherController::AircraftType type); + + QRect rect() const; protected: - virtual void paintEvent(QPaintEvent* pe); + void paint(QPainter* p) override; - virtual void mousePressEvent(QMouseEvent* me); - virtual void mouseMoveEvent(QMouseEvent* me); + void mousePressEvent(QMouseEvent* me) override; + void mouseMoveEvent(QMouseEvent* me) override; - virtual void wheelEvent(QWheelEvent* we); + void wheelEvent(QWheelEvent* we) override; virtual void paintContents(QPainter*); @@ -71,7 +73,7 @@ protected: virtual void doComputeBounds(); - void extendBounds(const QPointF& p); + void extendBounds(const QPointF& p, double radiusM = 1.0); QPointF project(const SGGeod& geod) const; QTransform transform() const; @@ -85,7 +87,7 @@ protected: QPointF m_panOffset, m_lastMousePos; int m_wheelAngleDeltaAccumulator; bool m_didPan; - LauncherAircraftType m_aircraftType; + LauncherController::AircraftType m_aircraftType = LauncherController::Airplane; static void extendRect(QRectF& r, const QPointF& p); @@ -118,6 +120,8 @@ private: int & flags /* out parameter */) const; QRect labelPositioned(const QRect &itemRect, const QSize &bounds, LabelPosition lp) const; + QTransform m_baseDeviceTransform; + QTransform m_viewportTransform; QVector m_ignored; mutable QHash m_labelPositions; @@ -126,9 +130,7 @@ private: static int textFlagsForLabelPosition(LabelPosition pos); void splitItems(const FGPositionedList &in, FGPositionedList &navaids, FGPositionedList &ports); - void paintNavaid(QPainter *painter, - const QTransform& t, - const FGPositionedRef &pos); + void paintNavaid(QPainter *painter, const FGPositionedRef &pos); void paintPolygonData(QPainter *painter); void paintGeodVec(QPainter *painter, const flightgear::SGGeodVec &vec); void fillClosedGeodVec(QPainter *painter, const QColor &color, const flightgear::SGGeodVec &vec); diff --git a/src/GUI/CMakeLists.txt b/src/GUI/CMakeLists.txt index fbb64a7e5..c9dd546e1 100644 --- a/src/GUI/CMakeLists.txt +++ b/src/GUI/CMakeLists.txt @@ -69,7 +69,6 @@ endif() if (HAVE_QT) qt5_wrap_ui(uic_sources Launcher.ui SetupRootDialog.ui - LocationWidget.ui InstallSceneryDialog.ui ) qt5_add_resources(qrc_sources resources.qrc) @@ -90,14 +89,14 @@ if (HAVE_QT) AircraftModel.cxx CatalogListModel.cxx CatalogListModel.hxx - LocationWidget.cxx - LocationWidget.hxx QtMessageBox.cxx QtMessageBox.hxx QtFileDialog.cxx QtFileDialog.hxx InstallSceneryDialog.hxx InstallSceneryDialog.cxx + LocationController.cxx + LocationController.hxx ToolboxButton.cpp ToolboxButton.h LauncherArgumentTokenizer.cxx @@ -122,6 +121,8 @@ if (HAVE_QT) LauncherController.hxx AddOnsController.cxx AddOnsController.hxx + PixmapImageItem.cxx + PixmapImageItem.hxx ${uic_sources} ${qrc_sources} ${qml_sources}) @@ -150,6 +151,12 @@ if (HAVE_QT) ThumbnailImageItem.hxx PopupWindowTracker.cxx PopupWindowTracker.hxx + QmlPositioned.hxx + QmlPositioned.cxx + QmlNavCacheWrapper.hxx + QmlNavCacheWrapper.cxx + QmlRadioButtonHelper.cxx + QmlRadioButtonHelper.hxx ) set_property(TARGET fgqmlui PROPERTY AUTOMOC ON) diff --git a/src/GUI/Launcher.ui b/src/GUI/Launcher.ui index 47eb938c7..d16abd9e5 100644 --- a/src/GUI/Launcher.ui +++ b/src/GUI/Launcher.ui @@ -299,7 +299,9 @@ - + + + @@ -360,12 +362,6 @@ QWidget
QtQuickWidgets/QQuickWidget
- - LocationWidget - QWidget -
GUI/LocationWidget.hxx
- 1 -
ToolboxButton QPushButton diff --git a/src/GUI/LauncherController.cxx b/src/GUI/LauncherController.cxx index e4e0f0c1f..779ab47aa 100644 --- a/src/GUI/LauncherController.cxx +++ b/src/GUI/LauncherController.cxx @@ -9,6 +9,7 @@ #include #include #include +#include // simgear headers #include @@ -38,26 +39,29 @@ #include "DefaultAircraftLocator.hxx" #include "LaunchConfig.hxx" #include "AircraftModel.hxx" - -// remove me once location widget is ported to Quick -#include "LocationWidget.hxx" +#include "LocationController.hxx" +#include "QmlPositioned.hxx" +#include "PixmapImageItem.hxx" +#include "AirportDiagram.hxx" +#include "NavaidDiagram.hxx" +#include "QmlRadioButtonHelper.hxx" using namespace simgear::pkg; -LauncherController::LauncherController(QObject *parent, - LocationWidget* loc) : - QObject(parent), - m_locationWidget_FIXME(loc) +LauncherController::LauncherController(QObject *parent) : + QObject(parent) { m_serversModel = new MPServersModel(this); + m_location = new LocationController(this); m_locationHistory = new RecentLocationsModel(this); m_selectedAircraftInfo = new QmlAircraftInfo(this); m_config = new LaunchConfig(this); connect(m_config, &LaunchConfig::collect, this, &LauncherController::collectAircraftArgs); - connect(m_locationWidget_FIXME, &LocationWidget::descriptionChanged, + m_location->setLaunchConfig(m_config); + connect(m_location, &LocationController::descriptionChanged, this, &LauncherController::summaryChanged); initQML(); @@ -91,6 +95,9 @@ LauncherController::LauncherController(QObject *parent, void LauncherController::initQML() { + qmlRegisterUncreatableType("FlightGear.Launcher", 1, 0, "LauncherController", "no"); + qmlRegisterUncreatableType("FlightGear.Launcher", 1, 0, "LocationController", "no"); + qmlRegisterType("FlightGear.Launcher", 1, 0, "ArgumentTokenizer"); qmlRegisterUncreatableType("FlightGear.Launcher", 1, 0, "QAIM", "no"); qmlRegisterUncreatableType("FlightGear.Launcher", 1, 0, "AircraftProxyModel", "no"); @@ -108,6 +115,15 @@ void LauncherController::initQML() qmlRegisterType("FlightGear.Launcher", 1, 0, "ThumbnailImage"); qmlRegisterType("FlightGear.Launcher", 1, 0, "PreviewImage"); + qmlRegisterType("FlightGear", 1, 0, "Positioned"); + // this is a Q_GADGET, but we need to register it for use in return types, etc + qRegisterMetaType(); + + qmlRegisterType("FlightGear", 1, 0, "PixmapImage"); + qmlRegisterType("FlightGear", 1, 0, "AirportDiagram"); + qmlRegisterType("FlightGear", 1, 0, "NavaidDiagram"); + qmlRegisterType("FlightGear", 1, 0, "RadioButtonGroup"); + QNetworkDiskCache* diskCache = new QNetworkDiskCache(this); SGPath cachePath = globals->get_fg_home() / "PreviewsCache"; diskCache->setCacheDirectory(QString::fromStdString(cachePath.utf8Str())); @@ -132,7 +148,7 @@ void LauncherController::restoreSettings() } - m_locationWidget_FIXME->restoreSettings(); + m_location->restoreSettings(); QVariantMap currentLocation = m_locationHistory->mostRecent(); if (currentLocation.isEmpty()) { // use the default @@ -143,7 +159,7 @@ void LauncherController::restoreSettings() currentLocation["location-apt-runway"] = "active"; } // otherwise we failed to find the default airport in the nav-db :( } - m_locationWidget_FIXME->restoreLocation(currentLocation); + m_location->restoreLocation(currentLocation); updateSelectedAircraft(); m_serversModel->requestRestore(); @@ -199,7 +215,7 @@ void LauncherController::doRun() m_aircraftHistory->insert(m_selectedAircraft); - QVariant locSet = m_locationWidget_FIXME->saveLocation(); + QVariant locSet = m_location->saveLocation(); m_locationHistory->insert(locSet); // aircraft paths @@ -257,8 +273,7 @@ void LauncherController::doApply() globals->get_props()->setStringValue("/sim/aircraft-dir", aircraftDir); } - // location - m_locationWidget_FIXME->setLocationProperties(); + m_location->setLocationProperties(); saveSettings(); } @@ -268,14 +283,13 @@ QString LauncherController::selectAircraftStateAutomatically() if (!m_selectedAircraftInfo) return {}; - if (m_locationWidget_FIXME->isAirborneLocation()) { - if (m_selectedAircraftInfo->hasState("approach")) - return "approach"; + if (m_location->isAirborneLocation() && m_selectedAircraftInfo->hasState("approach")) + { + return "approach"; } - if (m_locationWidget_FIXME->isParkedLocation()) { - if (m_selectedAircraftInfo->hasState("parked")) - return "parked"; + if (m_location->isParkedLocation() && m_selectedAircraftInfo->hasState("parked")) { + return "parked"; } else { // also try 'engines-running'? if (m_selectedAircraftInfo->hasState("take-off")) @@ -299,14 +313,12 @@ void LauncherController::updateSelectedAircraft() m_selectedAircraftInfo->setUri(m_selectedAircraft); QModelIndex index = m_aircraftModel->indexOfAircraftURI(m_selectedAircraft); if (index.isValid()) { - LauncherAircraftType aircraftType = Airplane; + m_aircraftType = Airplane; if (index.data(AircraftIsHelicopterRole).toBool()) { - aircraftType = Helicopter; + m_aircraftType = Helicopter; } else if (index.data(AircraftIsSeaplaneRole).toBool()) { - aircraftType = Seaplane; + m_aircraftType = Seaplane; } - - m_locationWidget_FIXME->setAircraftType(aircraftType); } emit canFlyChanged(); @@ -380,10 +392,9 @@ QmlAircraftInfo *LauncherController::selectedAircraftInfo() const void LauncherController::restoreLocation(QVariant var) { - m_locationWidget_FIXME->restoreLocation(var.toMap()); + m_location->restoreLocation(var.toMap()); } - QUrl LauncherController::selectedAircraft() const { return m_selectedAircraft; @@ -458,11 +469,6 @@ QStringList LauncherController::combinedSummary() const return m_settingsSummary + m_environmentSummary; } -QString LauncherController::locationDescription() const -{ - return m_locationWidget_FIXME->locationDescription(); -} - simgear::pkg::PackageRef LauncherController::packageForAircraftURI(QUrl uri) const { if (uri.scheme() != "package") { diff --git a/src/GUI/LauncherController.hxx b/src/GUI/LauncherController.hxx index bd63c88c6..a2487047c 100644 --- a/src/GUI/LauncherController.hxx +++ b/src/GUI/LauncherController.hxx @@ -35,9 +35,9 @@ class RecentAircraftModel; class RecentLocationsModel; class MPServersModel; class AircraftItemModel; -class LocationWidget; class QQuickItem; class LaunchConfig; +class LocationController; class LauncherController : public QObject { @@ -49,6 +49,8 @@ class LauncherController : public QObject Q_PROPERTY(AircraftItemModel* baseAircraftModel MEMBER m_aircraftModel CONSTANT) + Q_PROPERTY(LocationController* location MEMBER m_location CONSTANT) + Q_PROPERTY(MPServersModel* mpServersModel MEMBER m_serversModel CONSTANT) Q_PROPERTY(QUrl selectedAircraft READ selectedAircraft WRITE setSelectedAircraft NOTIFY selectedAircraftChanged) @@ -61,7 +63,6 @@ class LauncherController : public QObject Q_PROPERTY(QStringList settingsSummary READ settingsSummary WRITE setSettingsSummary NOTIFY summaryChanged) Q_PROPERTY(QStringList environmentSummary READ environmentSummary WRITE setEnvironmentSummary NOTIFY summaryChanged) - Q_PROPERTY(QString locationDescription READ locationDescription NOTIFY summaryChanged) Q_PROPERTY(QStringList combinedSummary READ combinedSummary NOTIFY summaryChanged) Q_PROPERTY(QString versionString READ versionString CONSTANT) @@ -70,9 +71,10 @@ class LauncherController : public QObject Q_PROPERTY(RecentLocationsModel* locationHistory READ locationHistory CONSTANT) Q_PROPERTY(bool canFly READ canFly NOTIFY canFlyChanged) + + Q_PROPERTY(AircraftType aircraftType READ aircraftType NOTIFY selectedAircraftChanged) public: - explicit LauncherController(QObject *parent, - LocationWidget* loc); + explicit LauncherController(QObject *parent); void initQML(); @@ -114,8 +116,6 @@ public: QStringList combinedSummary() const; - QString locationDescription() const; - QString versionString() const; RecentAircraftModel* aircraftHistory(); @@ -144,6 +144,22 @@ public: void restoreSettings(); void saveSettings(); + + LocationController* location() const + { return m_location; } + + enum AircraftType + { + Airplane = 0, + Seaplane, + Helicopter, + Airship + }; + + Q_ENUM(AircraftType) + + AircraftType aircraftType() const + { return m_aircraftType; } signals: void selectedAircraftChanged(QUrl selectedAircraft); @@ -195,8 +211,10 @@ private: AircraftProxyModel* m_aircraftSearchModel; AircraftProxyModel* m_browseAircraftModel; MPServersModel* m_serversModel = nullptr; + LocationController* m_location = nullptr; QUrl m_selectedAircraft; + AircraftType m_aircraftType = Airplane; int m_ratingFilters[4] = {3, 3, 3, 3}; LaunchConfig* m_config = nullptr; QmlAircraftInfo* m_selectedAircraftInfo = nullptr; @@ -204,8 +222,6 @@ private: QStringList m_settingsSummary, m_environmentSummary; RecentAircraftModel* m_aircraftHistory = nullptr; RecentLocationsModel* m_locationHistory = nullptr; - - LocationWidget* m_locationWidget_FIXME = nullptr; }; #endif // LAUNCHERCONTROLLER_HXX diff --git a/src/GUI/LauncherMainWindow.cxx b/src/GUI/LauncherMainWindow.cxx index fd70e1676..73626025d 100644 --- a/src/GUI/LauncherMainWindow.cxx +++ b/src/GUI/LauncherMainWindow.cxx @@ -44,6 +44,7 @@ #include "DefaultAircraftLocator.hxx" #include "AddOnsController.hxx" #include "CatalogListModel.hxx" +#include "LocationController.hxx" #include "ui_Launcher.h" @@ -69,14 +70,12 @@ LauncherMainWindow::LauncherMainWindow() : #endif - m_controller = new LauncherController(this, m_ui->location); + m_controller = new LauncherController(this); m_controller->initQML(); connect(m_controller, &LauncherController::canFlyChanged, this, &LauncherMainWindow::onCanFlyChanged); - m_ui->location->setLaunchConfig(m_controller->config()); - QMenu* toolsMenu = mb->addMenu(tr("Tools")); QAction* restoreDefaultsAction = toolsMenu->addAction(tr("Restore defaults...")); connect(restoreDefaultsAction, &QAction::triggered, @@ -143,6 +142,18 @@ LauncherMainWindow::LauncherMainWindow() : this, &LauncherMainWindow::onQuickStatusChanged); m_ui->aircraftList->setSource(QUrl("qrc:///qml/AircraftList.qml")); + ///////////// + // location + m_ui->location->setResizeMode(QQuickWidget::SizeRootObjectToView); + m_ui->location->engine()->addImportPath("qrc:///"); + m_ui->location->engine()->rootContext()->setContextProperty("_launcher", m_controller); + m_ui->location->engine()->rootContext()->setContextProperty("_config", m_controller->config()); + m_ui->location->engine()->rootContext()->setContextProperty("_location", m_controller->location()); + connect( m_ui->location, &QQuickWidget::statusChanged, + this, &LauncherMainWindow::onQuickStatusChanged); + m_ui->location->setSource(QUrl("qrc:///qml/Location.qml")); + + ///////////// // settings m_ui->settings->engine()->addImportPath("qrc:///"); QQmlContext* settingsContext = m_ui->settings->engine()->rootContext(); diff --git a/src/GUI/LocationController.cxx b/src/GUI/LocationController.cxx new file mode 100644 index 000000000..099b5cf08 --- /dev/null +++ b/src/GUI/LocationController.cxx @@ -0,0 +1,1116 @@ +// LocationController.cxx - GUI launcher dialog using Qt5 +// +// Written by James Turner, started October 2015. +// +// Copyright (C) 2015 James Turner +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#include "LocationController.hxx" + +#include +#include +#include +#include +#include +#include + +#include "AirportDiagram.hxx" +#include "NavaidDiagram.hxx" +#include "LaunchConfig.hxx" +#include "DefaultAircraftLocator.hxx" + +#include +#include + +#include
+#include +#include +#include
+#include
+#include
// for fgSetDouble + +using namespace flightgear; + +const unsigned int MAX_RECENT_LOCATIONS = 64; + +QString fixNavaidName(QString s) +{ + // split into words + QStringList words = s.split(QChar(' ')); + QStringList changedWords; + Q_FOREACH(QString w, words) { + QString up = w.toUpper(); + + // expand common abbreviations + // note these are not translated, since they are abbreivations + // for English-langauge airports, mostly in the US/Canada + if (up == "FLD") { + changedWords.append("Field"); + continue; + } + + if (up == "CO") { + changedWords.append("County"); + continue; + } + + if ((up == "MUNI") || (up == "MUN")) { + changedWords.append("Municipal"); + continue; + } + + if (up == "MEM") { + changedWords.append("Memorial"); + continue; + } + + if (up == "RGNL") { + changedWords.append("Regional"); + continue; + } + + if (up == "CTR") { + changedWords.append("Center"); + continue; + } + + if (up == "INTL") { + changedWords.append("International"); + continue; + } + + // occurs in many Australian airport names in our DB + if (up == "(NSW)") { + changedWords.append("(New South Wales)"); + continue; + } + + if ((up == "VOR") || (up == "NDB") + || (up == "VOR-DME") || (up == "VORTAC") + || (up == "NDB-DME") + || (up == "AFB") || (up == "RAF")) + { + changedWords.append(w); + continue; + } + + if ((up =="[X]") || (up == "[H]") || (up == "[S]")) { + continue; // consume + } + + QChar firstChar = w.at(0).toUpper(); + w = w.mid(1).toLower(); + w.prepend(firstChar); + + changedWords.append(w); + } + + return changedWords.join(QChar(' ')); +} + +QString formatGeodAsString(const SGGeod& geod) +{ + QChar ns = (geod.getLatitudeDeg() > 0.0) ? 'N' : 'S'; + QChar ew = (geod.getLongitudeDeg() > 0.0) ? 'E' : 'W'; + + return QString::number(fabs(geod.getLongitudeDeg()), 'f',2 ) + ew + " " + + QString::number(fabs(geod.getLatitudeDeg()), 'f',2 ) + ns; +} + +QVariant savePositionList(const FGPositionedList& posList) +{ + QVariantList vl; + FGPositionedList::const_iterator it; + for (it = posList.begin(); it != posList.end(); ++it) { + QVariantMap vm; + FGPositionedRef pos = *it; + vm.insert("ident", QString::fromStdString(pos->ident())); + vm.insert("type", pos->type()); + vm.insert("lat", pos->geod().getLatitudeDeg()); + vm.insert("lon", pos->geod().getLongitudeDeg()); + vl.append(vm); + } + return vl; +} + +FGPositionedList loadPositionedList(QVariant v) +{ + QVariantList vl = v.toList(); + FGPositionedList result; + result.reserve(vl.size()); + NavDataCache* cache = NavDataCache::instance(); + + Q_FOREACH(QVariant v, vl) { + QVariantMap vm = v.toMap(); + std::string ident(vm.value("ident").toString().toStdString()); + double lat = vm.value("lat").toDouble(); + double lon = vm.value("lon").toDouble(); + FGPositioned::Type ty(static_cast(vm.value("type").toInt())); + FGPositioned::TypeFilter filter(ty); + FGPositionedRef pos = cache->findClosestWithIdent(ident, + SGGeod::fromDeg(lon, lat), + &filter); + if (pos) + result.push_back(pos); + } + + return result; +} + +class IdentSearchFilter : public FGPositioned::TypeFilter +{ +public: + IdentSearchFilter(LauncherController::AircraftType aircraft) + { + addType(FGPositioned::VOR); + addType(FGPositioned::FIX); + addType(FGPositioned::NDB); + + if (aircraft == LauncherController::Helicopter) { + addType(FGPositioned::HELIPAD); + } + + if (aircraft == LauncherController::Seaplane) { + addType(FGPositioned::SEAPORT); + } else { + addType(FGPositioned::AIRPORT); + } + } +}; + +class NavSearchModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(bool isSearchActive READ isSearchActive NOTIFY searchActiveChanged) + + enum Roles { + GeodRole = Qt::UserRole + 1, + GuidRole = Qt::UserRole + 2, + IdentRole = Qt::UserRole + 3, + NameRole = Qt::UserRole + 4, + IconRole = Qt::UserRole + 5, + TypeRole = Qt::UserRole + 6, + NavFrequencyRole = Qt::UserRole + 7 + }; + +public: + NavSearchModel() { } + + enum AircraftType + { + Airplane = LauncherController::Airplane, + Seaplane = LauncherController::Seaplane, + Helicopter = LauncherController::Helicopter, + Airship = LauncherController::Airship + }; + + Q_ENUM(AircraftType) + + Q_INVOKABLE void setSearch(QString t, AircraftType aircraft) + { + beginResetModel(); + + m_items.clear(); + m_ids.clear(); + + std::string term(t.toUpper().toStdString()); + + IdentSearchFilter filter(static_cast(aircraft)); + FGPositionedList exactMatches = NavDataCache::instance()->findAllWithIdent(term, &filter, true); + m_ids.reserve(exactMatches.size()); + m_items.reserve(exactMatches.size()); + for (auto match : exactMatches) { + m_ids.push_back(match->guid()); + m_items.push_back(match); + } + endResetModel(); + + m_search.reset(new NavDataCache::ThreadedGUISearch(term)); + QTimer::singleShot(100, this, SLOT(onSearchResultsPoll())); + m_searchActive = true; + emit searchActiveChanged(); + } + + bool isSearchActive() const + { + return m_searchActive; + } + + int rowCount(const QModelIndex&) const override + { + return m_ids.size(); + } + + QVariant data(const QModelIndex& index, int role) const override + { + if (!index.isValid()) + return QVariant(); + + FGPositionedRef pos = itemAtRow(index.row()); + switch (role) { + case GuidRole: return static_cast(pos->guid()); + case IdentRole: return QString::fromStdString(pos->ident()); + case NameRole: + return fixNavaidName(QString::fromStdString(pos->name())); + + case NavFrequencyRole: { + FGNavRecord* nav = fgpositioned_cast(pos); + return nav ? nav->get_freq() : 0; + } + + case TypeRole: return static_cast(pos->type()); + case IconRole: + return AirportDiagram::iconForPositioned(pos, + AirportDiagram::SmallIcons | AirportDiagram::LargeAirportPlans); + } + + return {}; + } + + FGPositionedRef itemAtRow(unsigned int row) const + { + FGPositionedRef pos = m_items[row]; + if (!pos.valid()) { + pos = NavDataCache::instance()->loadById(m_ids[row]); + m_items[row] = pos; + } + + return pos; + } + + void setItems(const FGPositionedList& items) + { + beginResetModel(); + m_searchActive = false; + m_items = items; + + m_ids.clear(); + for (unsigned int i=0; i < items.size(); ++i) { + m_ids.push_back(m_items[i]->guid()); + } + + endResetModel(); + emit searchActiveChanged(); + } + + QHash roleNames() const override + { + QHash result = QAbstractListModel::roleNames(); + + result[GeodRole] = "geod"; + result[GuidRole] = "guid"; + result[IdentRole] = "ident"; + result[NameRole] = "name"; + result[IconRole] = "icon"; + result[TypeRole] = "type"; + result[NavFrequencyRole] = "frequency"; + return result; + } + + +Q_SIGNALS: + void searchComplete(); + void searchActiveChanged(); + +private slots: + + void onSearchResultsPoll() + { + if (m_search.isNull()) { + return; + } + + PositionedIDVec newIds = m_search->results(); + if (!newIds.empty()) { + m_ids.reserve(newIds.size()); + beginInsertRows(QModelIndex(), m_ids.size(), newIds.size() - 1); + for (auto id : newIds) { + m_ids.push_back(id); + m_items.push_back({}); // null ref + } + endInsertRows(); + } + + if (m_search->isComplete()) { + m_searchActive = false; + m_search.reset(); + emit searchComplete(); + emit searchActiveChanged(); + } else { + QTimer::singleShot(100, this, SLOT(onSearchResultsPoll())); + } + } + +private: + PositionedIDVec m_ids; + mutable FGPositionedList m_items; + bool m_searchActive = false; + QScopedPointer m_search; +}; + + +LocationController::LocationController(QObject *parent) : + QObject(parent) +{ + qmlRegisterUncreatableType("FlightGear.Launcher", 1, 0, "NavSearchModel", "no"); + m_searchModel = new NavSearchModel; + + m_detailQml = new QmlPositioned(this); + // chain location and offset updated to description + connect(this, &LocationController::baseLocationChanged, + this, &LocationController::descriptionChanged); + connect(this, &LocationController::configChanged, + this, &LocationController::descriptionChanged); + connect(this, &LocationController::offsetChanged, + this, &LocationController::descriptionChanged); +} + +LocationController::~LocationController() +{ +} + +void LocationController::setLaunchConfig(LaunchConfig *config) +{ + m_config = config; + connect(m_config, &LaunchConfig::collect, this, &LocationController::onCollectConfig); +} + +void LocationController::restoreSettings() +{ + QSettings settings; + m_recentLocations = loadPositionedList(settings.value("recent-locations")); +} + +bool LocationController::isParkedLocation() const +{ + if (m_airportLocation) { + if (m_detailLocation && (m_detailLocation->type() == FGPositioned::PARKING)) { + return true; + } + } + + // treat all other ground starts as taxi or on runway, i.e engines + // running if possible + return false; +} + +bool LocationController::isAirborneLocation() const +{ + const bool altIsPositive = (m_altitudeFt > 0); + + if (m_locationIsLatLon) { + return altIsPositive; + } + + if (m_airportLocation) { + const bool onRunway = (m_detailLocation && (m_detailLocation->type() == FGPositioned::RUNWAY)); + if (onRunway && m_offsetEnabled) { + // in this case no altitude might be set, but we assume + // it's still an airborne pos + return true; + } + + // this allows for people using offsets from a parking position or + // similar weirdness :) + return altIsPositive; + } + + // relative to a navaid or fix - base off altitude. + return altIsPositive; +} + +int LocationController::offsetRadial() const +{ + return m_offsetRadial; +} + +void LocationController::setBaseGeod(QmlGeod geod) +{ + if (m_locationIsLatLon && (m_geodLocation == geod.geod())) + return; + + m_locationIsLatLon = true; + m_geodLocation = geod.geod(); + m_location.clear(); + m_airportLocation.clear(); + m_detailLocation.clear(); + emit baseLocationChanged(); +} + +void LocationController::setBaseLocation(QmlPositioned* pos) +{ + if (pos->inner() == m_location) + return; + + m_locationIsLatLon = false; + m_location = pos->inner(); + m_detailLocation.clear(); + m_detailQml->setGuid(0); + + if (FGPositioned::isAirportType(m_location.ptr())) { + m_airportLocation = static_cast(m_location.ptr()); + } else { + m_airportLocation.clear(); + } + emit baseLocationChanged(); +} + +void LocationController::setDetailLocation(QmlPositioned* pos) +{ + if (pos && (pos->inner() == m_detailLocation)) + return; + + if (!pos) { + m_detailLocation.clear(); + m_detailQml->setInner({}); + } else { + m_detailLocation = pos->inner(); + m_useActiveRunway = false; + m_detailQml->setInner(pos->inner()); + } + + emit configChanged(); +} + +QmlGeod LocationController::baseGeod() const +{ + if (m_locationIsLatLon) + return m_geodLocation; + + if (m_location) + return QmlGeod(m_location->geod()); + + return {}; +} + +bool LocationController::isAirportLocation() const +{ + return m_airportLocation; +} + +void LocationController::setUseActiveRunway(bool b) +{ + if (b == m_useActiveRunway) + return; + + m_useActiveRunway = b; + if (m_useActiveRunway) { + m_detailLocation.clear(); // clear any specific runway + } + emit configChanged(); +} + +void LocationController::addToRecent(QmlPositioned* pos) +{ + addToRecent(pos->inner()); +} + +QObjectList LocationController::airportRunways() const +{ + if (!m_airportLocation) + return {}; + + QObjectList result; + if (m_airportLocation->isHeliport()) { + // helipads + for (unsigned int r=0; rnumHelipads(); ++r) { + auto p = new QmlPositioned(m_airportLocation->getHelipadByIndex(r).ptr()); + QQmlEngine::setObjectOwnership(p, QQmlEngine::JavaScriptOwnership); + result.push_back(p); + } + } else { + // regular runways + for (int r=0; rnumRunways(); ++r) { + auto p = new QmlPositioned(m_airportLocation->getRunwayByIndex(r).ptr()); + QQmlEngine::setObjectOwnership(p, QQmlEngine::JavaScriptOwnership); + result.push_back(p); + } + } + + return result; +} + +QObjectList LocationController::airportParkings() const +{ + if (!m_airportLocation) + return {}; + + QObjectList result; + for (auto park : m_airportLocation->groundNetwork()->allParkings()) { + auto p = new QmlPositioned(park); + QQmlEngine::setObjectOwnership(p, QQmlEngine::JavaScriptOwnership); + result.push_back(p); + } + return result; +} + +void LocationController::showHistoryInSearchModel() +{ + // prepend the default location + FGPositionedList locs = m_recentLocations; + const std::string defaultICAO = flightgear::defaultAirportICAO(); + + auto it = std::find_if(locs.begin(), locs.end(), [defaultICAO](FGPositionedRef pos) { + return pos->ident() == defaultICAO; + }); + + if (it == locs.end()) { + FGAirportRef apt = FGAirport::findByIdent(defaultICAO); + locs.insert(locs.begin(), apt); + } + + m_searchModel->setItems(locs); +} + +bool parseStringAsGeod(const QString& s, SGGeod& result) +{ + int commaPos = s.indexOf(QChar(',')); + if (commaPos < 0) + return false; + + bool ok; + double lon = s.leftRef(commaPos).toDouble(&ok); + if (!ok) + return false; + + double lat = s.midRef(commaPos+1).toDouble(&ok); + if (!ok) + return false; + + result = SGGeod::fromDeg(lon, lat); + return true; +} + +QmlGeod LocationController::parseStringAsGeod(QString string) const +{ + SGGeod g; + if (!::parseStringAsGeod(string, g)) { + return {}; + } + + return QmlGeod(g); +} + +QmlPositioned *LocationController::detail() const +{ + return m_detailQml; +} + +void LocationController::setOffsetRadial(int offsetRadial) +{ + if (m_offsetRadial == offsetRadial) + return; + + m_offsetRadial = offsetRadial; + emit offsetChanged(); +} + +void LocationController::setOffsetNm(double offsetNm) +{ + if (qFuzzyCompare(m_offsetNm, offsetNm)) + return; + + m_offsetNm = offsetNm; + emit offsetChanged(); +} + +void LocationController::setOffsetEnabled(bool offsetEnabled) +{ + if (m_offsetEnabled == offsetEnabled) + return; + + m_offsetEnabled = offsetEnabled; + emit offsetChanged(); +} + +void LocationController::setOnFinal(bool onFinal) +{ + if (m_onFinal == onFinal) + return; + + m_onFinal = onFinal; + emit configChanged(); +} + +void LocationController::setTuneNAV1(bool tuneNAV1) +{ + if (m_tuneNAV1 == tuneNAV1) + return; + + m_tuneNAV1 = tuneNAV1; + emit configChanged(); +} + +void LocationController::setUseAvailableParking(bool useAvailableParking) +{ + if (m_useAvailableParking == useAvailableParking) + return; + + m_useAvailableParking = useAvailableParking; + if (m_useAvailableParking) { + m_detailLocation.clear(); // clear any specific runway + } + emit configChanged(); +} + +void LocationController::restoreLocation(QVariantMap l) +{ + if (l.contains("location-lat")) { + m_locationIsLatLon = true; + m_geodLocation = SGGeod::fromDeg(l.value("location-lon").toDouble(), + l.value("location-lat").toDouble()); + } else if (l.contains("location-id")) { + m_location = NavDataCache::instance()->loadById(l.value("location-id").toULongLong()); + m_locationIsLatLon = false; + if (FGPositioned::isAirportType(m_location.ptr())) { + m_airportLocation = static_cast(m_location.ptr()); + } + } + + m_altitudeFt = l.value("altitude", 6000).toInt(); + m_airspeedKnots = l.value("speed", 120).toInt(); + m_headingDeg = l.value("heading").toInt(); + m_offsetEnabled = l.value("offset-enabled").toBool(); + m_offsetRadial = l.value("offset-bearing").toInt(); + m_offsetNm = l.value("offset-distance", 10).toInt(); + m_tuneNAV1 = l.value("tune-nav1-radio").toBool(); + + if (m_airportLocation) { + m_useActiveRunway = false; + m_detailLocation.clear(); + + if (l.contains("location-apt-runway")) { + QString runway = l.value("location-apt-runway").toString(); + if (runway == "active") { + m_useActiveRunway = true; + } else { + m_detailLocation = m_airportLocation->getRunwayByIdent(runway.toStdString()); + } + } else if (l.contains("location-apt-parking")) { + QString parking = l.value("location-apt-parking").toString(); + m_detailLocation = m_airportLocation->groundNetwork()->findParkingByName(parking.toStdString()); + } + + m_onFinal = l.value("location-on-final").toBool(); + m_offsetNm = l.value("location-apt-final-distance").toInt(); + } // of location is an airport + + baseLocationChanged(); + configChanged(); + offsetChanged(); +} + +bool LocationController::shouldStartPaused() const +{ + if (!m_location) { + return false; // defaults to on-ground at the default airport + } + + if (m_airportLocation) { + return m_onFinal; + } else { + // navaid, start paused + return true; + } + + return false; +} + +QVariantMap LocationController::saveLocation() const +{ + QVariantMap locationSet; + if (m_locationIsLatLon) { + locationSet.insert("location-lat", m_geodLocation.getLatitudeDeg()); + locationSet.insert("location-lon", m_geodLocation.getLongitudeDeg()); + } else if (m_location) { + locationSet.insert("location-id", static_cast(m_location->guid())); + + if (m_airportLocation) { + locationSet.insert("location-on-final", m_onFinal); + locationSet.insert("location-apt-final-distance", m_offsetNm); + if (m_useActiveRunway) { + locationSet.insert("location-apt-runway", "ACTIVE"); + } else if (m_detailLocation->type() == FGPositioned::RUNWAY) { + locationSet.insert("location-apt-runway", QString::fromStdString(m_detailLocation->ident())); + } else if (m_detailLocation->type() == FGPositioned::PARKING) { + locationSet.insert("location-apt-parking", QString::fromStdString(m_detailLocation->ident())); + } + } // of location is an airport + } // of m_location is valid + + locationSet.insert("altitude", m_altitudeFt); + locationSet.insert("speed", m_airspeedKnots); + locationSet.insert("offset-enabled", m_offsetEnabled); + locationSet.insert("offset-bearing", m_offsetRadial); + locationSet.insert("offset-distance",m_offsetNm); + locationSet.insert("text", description()); + locationSet.insert("tune-nav1-radio", m_tuneNAV1); + + return locationSet; +} + +void LocationController::setLocationProperties() +{ + SGPropertyNode_ptr presets = fgGetNode("/sim/presets", true); + + QStringList props = QStringList() << "vor-id" << "fix" << "ndb-id" << + "runway-requested" << "navaid-id" << "offset-azimuth-deg" << + "offset-distance-nm" << "glideslope-deg" << + "speed-set" << "on-ground" << "airspeed-kt" << + "airport-id" << "runway" << "parkpos"; + + Q_FOREACH(QString s, props) { + SGPropertyNode* c = presets->getChild(s.toStdString()); + if (c) { + c->clearValue(); + } + } + + if (m_locationIsLatLon) { + fgSetDouble("/sim/presets/latitude-deg", m_geodLocation.getLatitudeDeg()); + fgSetDouble("/position/latitude-deg", m_geodLocation.getLatitudeDeg()); + fgSetDouble("/sim/presets/longitude-deg", m_geodLocation.getLongitudeDeg()); + fgSetDouble("/position/longitude-deg", m_geodLocation.getLongitudeDeg()); + + applyPositionOffset(); + return; + } + + fgSetDouble("/sim/presets/latitude-deg", 9999.0); + fgSetDouble("/sim/presets/longitude-deg", 9999.0); + fgSetDouble("/sim/presets/altitude-ft", -9999.0); + fgSetDouble("/sim/presets/heading-deg", 9999.0); + + if (!m_location) { + return; + } + + if (m_airportLocation) { + fgSetString("/sim/presets/airport-id", m_airportLocation->ident()); + fgSetBool("/sim/presets/on-ground", true); + fgSetBool("/sim/presets/airport-requested", true); + + const bool onRunway = (m_detailLocation && (m_detailLocation->type() == FGPositioned::RUNWAY)); + const bool atParking = (m_detailLocation && (m_detailLocation->type() == FGPositioned::PARKING)); + if (m_useActiveRunway) { + // automatic runway choice + // we can't set navaid here + } else if (onRunway) { + if (m_airportLocation->type() == FGPositioned::AIRPORT) { + // explicit runway choice + fgSetString("/sim/presets/runway", m_detailLocation->ident() ); + fgSetBool("/sim/presets/runway-requested", true ); + + // set nav-radio 1 based on selected runway + FGRunway* runway = static_cast(m_detailLocation.ptr()); + if (m_tuneNAV1 && runway->ILS()) { + double mhz = runway->ILS()->get_freq() / 100.0; + fgSetDouble("/instrumentation/nav[0]/radials/selected-deg", runway->headingDeg()); + fgSetDouble("/instrumentation/nav[0]/frequencies/selected-mhz", mhz); + } + + if (m_onFinal) { + fgSetDouble("/sim/presets/glideslope-deg", 3.0); + fgSetDouble("/sim/presets/offset-distance-nm", m_offsetNm); + fgSetBool("/sim/presets/on-ground", false); + } + } else if (m_airportLocation->type() == FGPositioned::HELIPORT) { + // explicit pad choice + fgSetString("/sim/presets/runway", m_detailLocation->ident() ); + fgSetBool("/sim/presets/runway-requested", true ); + } + } else if (atParking) { + // parking selection + fgSetString("/sim/presets/parkpos", m_detailLocation->ident()); + } + // of location is an airport + } else { + fgSetString("/sim/presets/airport-id", ""); + + // location is a navaid + // note setting the ident here is ambigious, we really only need and + // want the 'navaid-id' property. However setting the 'real' option + // gives a better UI experience (eg existing Position in Air dialog) + FGPositioned::Type ty = m_location->type(); + switch (ty) { + case FGPositioned::VOR: + fgSetString("/sim/presets/vor-id", m_location->ident()); + setNavRadioOption(); + break; + + case FGPositioned::NDB: + fgSetString("/sim/presets/ndb-id", m_location->ident()); + setNavRadioOption(); + break; + + case FGPositioned::FIX: + fgSetString("/sim/presets/fix", m_location->ident()); + break; + default: + break; + }; + + // set disambiguation property + globals->get_props()->setIntValue("/sim/presets/navaid-id", + static_cast(m_location->guid())); + + applyPositionOffset(); + } // of navaid location +} + +void LocationController::applyPositionOffset() +{ + if (m_altitudeFt > 0) { + m_config->setArg("altitude", QString::number(m_altitudeFt)); + } + + m_config->setArg("vc", QString::number(m_airspeedKnots)); + m_config->setArg("heading", QString::number(m_headingDeg)); + + if (m_offsetEnabled) { + // flip direction of azimuth to balance the flip done in fgApplyStartOffset + // I don't know why that flip exists but changing it there will break + // command-line compatability so compensating here instead + int offsetAzimuth = m_offsetRadial - 180; + m_config->setArg("offset-azimuth", QString::number(offsetAzimuth)); + m_config->setArg("offset-distance", QString::number(m_offsetNm)); + } +} + +void LocationController::onCollectConfig() +{ + if (m_locationIsLatLon) { + m_config->setArg("lat", QString::number(m_geodLocation.getLatitudeDeg())); + m_config->setArg("lon", QString::number(m_geodLocation.getLongitudeDeg())); + applyPositionOffset(); + return; + } + + if (!m_location) { + return; + } + + if (m_airportLocation) { + m_config->setArg("airport", QString::fromStdString(m_airportLocation->ident())); + const bool onRunway = (m_detailLocation && (m_detailLocation->type() == FGPositioned::RUNWAY)); + const bool atParking = (m_detailLocation && (m_detailLocation->type() == FGPositioned::PARKING)); + + if (m_useActiveRunway) { + // pick by default + } else if (onRunway) { + if (m_airportLocation->type() == FGPositioned::AIRPORT) { + m_config->setArg("runway", QString::fromStdString(m_detailLocation->ident())); + + // set nav-radio 1 based on selected runway + FGRunway* runway = static_cast(m_detailLocation.ptr()); + if (runway->ILS()) { + double mhz = runway->ILS()->get_freq() / 100.0; + m_config->setArg("nav1", QString("%1:%2").arg(runway->headingDeg()).arg(mhz)); + } + + if (m_onFinal) { + m_config->setArg("glideslope", std::string("3.0")); + m_config->setArg("offset-distance", QString::number(m_offsetNm)); + m_config->setArg("on-ground", std::string("false")); + } + } else if (m_airportLocation->type() == FGPositioned::HELIPORT) { + m_config->setArg("runway", QString::fromStdString(m_detailLocation->ident())); + } + } else if (atParking) { + // parking selection + m_config->setArg("parkpos", QString::fromStdString(m_detailLocation->ident())); + } + // of location is an airport + } else { + // location is a navaid + // note setting the ident here is ambigious, we really only need and + // want the 'navaid-id' property. However setting the 'real' option + // gives a better UI experience (eg existing Position in Air dialog) + FGPositioned::Type ty = m_location->type(); + switch (ty) { + case FGPositioned::VOR: + m_config->setArg("vor", m_location->ident()); + setNavRadioOption(); + break; + + case FGPositioned::NDB: + m_config->setArg("ndb", m_location->ident()); + setNavRadioOption(); + break; + + case FGPositioned::FIX: + m_config->setArg("fix", m_location->ident()); + break; + default: + break; + }; + + // set disambiguation property + m_config->setProperty("/sim/presets/navaid-id", QString::number(m_location->guid())); + applyPositionOffset(); + } // of navaid location +} + +void LocationController::setNavRadioOption() +{ + if (!m_tuneNAV1) + return; + + if (m_location->type() == FGPositioned::VOR) { + FGNavRecordRef nav(static_cast(m_location.ptr())); + double mhz = nav->get_freq() / 100.0; + int heading = 0; // add heading support + QString navOpt = QString("%1:%2").arg(heading).arg(mhz); + m_config->setArg("nav1", navOpt); + } else { + FGNavRecordRef nav(static_cast(m_location.ptr())); + int khz = nav->get_freq() / 100; + int heading = 0; + QString adfOpt = QString("%1:%2").arg(heading).arg(khz); + m_config->setArg("adf1", adfOpt); + } +} + +void LocationController::onAirportRunwayClicked(FGRunwayRef rwy) +{ + // if (rwy) { + // m_ui->runwayRadio->setChecked(true); + // int rwyIndex = m_ui->runwayCombo->findText(QString::fromStdString(rwy->ident())); + // m_ui->runwayCombo->setCurrentIndex(rwyIndex); + // m_ui->airportDiagram->setSelectedRunway(rwy); + // } + + // updateDescription(); +} + +void LocationController::onAirportParkingClicked(FGParkingRef park) +{ + // if (park) { + // m_ui->parkingRadio->setChecked(true); + // int parkingIndex = m_ui->parkingCombo->findData(park->getIndex()); + // m_ui->parkingCombo->setCurrentIndex(parkingIndex); + // m_ui->airportDiagram->setSelectedParking(park); + // } + + // updateDescription(); +} + +QString compassPointFromHeading(int heading) +{ + const int labelArc = 360 / 8; + heading += (labelArc >> 1); + SG_NORMALIZE_RANGE(heading, 0, 359); + + switch (heading / labelArc) { + case 0: return "N"; + case 1: return "NE"; + case 2: return "E"; + case 3: return "SE"; + case 4: return "S"; + case 5: return "SW"; + case 6: return "W"; + case 7: return "NW"; + } + + return QString(); +} + +QString LocationController::description() const +{ + if (!m_location) { + if (m_locationIsLatLon) { + return tr("at position %1").arg(formatGeodAsString(m_geodLocation)); + } + + return tr("No location selected"); + } + + QString ident = QString::fromStdString(m_location->ident()), + name = QString::fromStdString(m_location->name()); + + name = fixNavaidName(name); + + if (m_airportLocation) { + const bool onRunway = (m_detailLocation && (m_detailLocation->type() == FGPositioned::RUNWAY)); + const bool atParking = (m_detailLocation && (m_detailLocation->type() == FGPositioned::PARKING)); + QString locationOnAirport; + + if (m_useActiveRunway) { + if (m_onFinal) { + locationOnAirport = tr("on %1-mile final to active runway").arg(m_offsetNm); + } else { + locationOnAirport = tr("on active runway"); + } + } if (onRunway) { + QString runwayName = QString("runway %1").arg(QString::fromStdString(m_detailLocation->ident())); + + if (m_onFinal) { + locationOnAirport = tr("on %2-mile final to %1").arg(runwayName).arg(m_offsetNm); + } else { + locationOnAirport = tr("on %1").arg(runwayName); + } + } else if (atParking) { + locationOnAirport = tr("at parking position %1").arg(QString::fromStdString(m_detailLocation->ident())); + } + + return tr("%2 (%1): %3").arg(ident).arg(name).arg(locationOnAirport); + } else { + QString offsetDesc = tr("at"); + if (m_offsetEnabled) { + offsetDesc = tr("%1nm %2 of"). + arg(m_offsetNm, 0, 'f', 1). + arg(compassPointFromHeading(m_offsetRadial)); + } + + QString navaidType; + switch (m_location->type()) { + case FGPositioned::VOR: + navaidType = QString("VOR"); break; + case FGPositioned::NDB: + navaidType = QString("NDB"); break; + case FGPositioned::FIX: + return tr("%2 waypoint %1").arg(ident).arg(offsetDesc); + default: + // unsupported type + break; + } + + return tr("%4 %1 %2 (%3)").arg(navaidType).arg(ident).arg(name).arg(offsetDesc); + } + + return tr("No location selected"); +} + + +void LocationController::addToRecent(FGPositionedRef pos) +{ + auto it = std::find(m_recentLocations.begin(), + m_recentLocations.end(), pos); + if (it != m_recentLocations.end()) { + m_recentLocations.erase(it); + } + + if (m_recentLocations.size() >= MAX_RECENT_LOCATIONS) { + m_recentLocations.pop_back(); + } + + m_recentLocations.insert(m_recentLocations.begin(), pos); + QSettings settings; + settings.setValue("recent-locations", savePositionList(m_recentLocations)); +} + +#include "LocationController.moc" diff --git a/src/GUI/LocationController.hxx b/src/GUI/LocationController.hxx new file mode 100644 index 000000000..cd4ed8f47 --- /dev/null +++ b/src/GUI/LocationController.hxx @@ -0,0 +1,203 @@ +// LocationController.hxx - GUI launcher dialog using Qt5 +// +// Written by James Turner, started October 2015. +// +// Copyright (C) 2015 James Turner +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#ifndef LOCATION_CONTROLLER_HXX +#define LOCATION_CONTROLLER_HXX + +#include + +#include +#include + +#include "QtLauncher_fwd.hxx" +#include "LaunchConfig.hxx" +#include "QmlPositioned.hxx" + +class NavSearchModel; + +class LocationController : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString description READ description NOTIFY descriptionChanged) + + Q_PROPERTY(NavSearchModel* searchModel MEMBER m_searchModel CONSTANT) + + Q_PROPERTY(QList airportRunways READ airportRunways NOTIFY baseLocationChanged) + Q_PROPERTY(QList airportParkings READ airportParkings NOTIFY baseLocationChanged) + + Q_PROPERTY(bool offsetEnabled READ offsetEnabled WRITE setOffsetEnabled NOTIFY offsetChanged) + Q_PROPERTY(int offsetRadial READ offsetRadial WRITE setOffsetRadial NOTIFY offsetChanged) + Q_PROPERTY(bool offsetBearingIsTrue MEMBER m_offsetBearingIsTrue NOTIFY offsetChanged) + Q_PROPERTY(double offsetNm READ offsetNm WRITE setOffsetNm NOTIFY offsetChanged) + + Q_PROPERTY(int headingDeg MEMBER m_headingDeg NOTIFY configChanged) + Q_PROPERTY(int altitudeFt MEMBER m_altitudeFt NOTIFY configChanged) + Q_PROPERTY(int airspeedKnots MEMBER m_airspeedKnots NOTIFY configChanged) + Q_PROPERTY(bool onFinal READ onFinal WRITE setOnFinal NOTIFY configChanged) + + Q_PROPERTY(bool isAirportLocation READ isAirportLocation NOTIFY baseLocationChanged) + Q_PROPERTY(bool useActiveRunway READ useActiveRunway WRITE setUseActiveRunway NOTIFY configChanged) + Q_PROPERTY(bool useAvailableParking READ useAvailableParking WRITE setUseAvailableParking NOTIFY configChanged) + + Q_PROPERTY(bool tuneNAV1 READ tuneNAV1 WRITE setTuneNAV1 NOTIFY configChanged) + Q_PROPERTY(QmlGeod baseGeod READ baseGeod WRITE setBaseGeod NOTIFY baseLocationChanged) + + Q_PROPERTY(QmlPositioned* detail READ detail CONSTANT) +public: + explicit LocationController(QObject *parent = nullptr); + ~LocationController(); + + void setLaunchConfig(LaunchConfig* config); + + QString description() const; + + void setBaseLocation(FGPositionedRef ref); + + bool shouldStartPaused() const; + + void setLocationProperties(); + + void restoreLocation(QVariantMap l); + QVariantMap saveLocation() const; + + void restoreSettings(); + + /// used to automatically select aircraft state + bool isParkedLocation() const; + + /// used to automatically select aircraft state + bool isAirborneLocation() const; + + int offsetRadial() const; + + double offsetNm() const + { + return m_offsetNm; + } + + Q_INVOKABLE void setBaseLocation(QmlPositioned* pos); + + Q_INVOKABLE void setDetailLocation(QmlPositioned* pos); + + QmlGeod baseGeod() const; + void setBaseGeod(QmlGeod geod); + + bool isAirportLocation() const; + + bool offsetEnabled() const + { + return m_offsetEnabled; + } + + bool onFinal() const + { + return m_onFinal; + } + + void setUseActiveRunway(bool b); + + bool useActiveRunway() const + { + return m_useActiveRunway; + } + + Q_INVOKABLE void addToRecent(QmlPositioned* pos); + + QObjectList airportRunways() const; + QObjectList airportParkings() const; + + Q_INVOKABLE void showHistoryInSearchModel(); + + Q_INVOKABLE QmlGeod parseStringAsGeod(QString string) const; + + bool tuneNAV1() const + { + return m_tuneNAV1; + } + + QmlPositioned* detail() const; + + bool useAvailableParking() const + { + return m_useAvailableParking; + } + +public slots: + void setOffsetRadial(int offsetRadial); + + void setOffsetNm(double offsetNm); + + void setOffsetEnabled(bool offsetEnabled); + + void setOnFinal(bool onFinal); + + void setTuneNAV1(bool tuneNAV1); + + void setUseAvailableParking(bool useAvailableParking); + +Q_SIGNALS: + void descriptionChanged(); + void offsetChanged(); + void baseLocationChanged(); + void configChanged(); + +private Q_SLOTS: + void onCollectConfig(); +private: + + void onSearchComplete(); + + void onAirportRunwayClicked(FGRunwayRef rwy); + void onAirportParkingClicked(FGParkingRef park); + + + void addToRecent(FGPositionedRef pos); + + void setNavRadioOption(); + + void applyPositionOffset(); + + NavSearchModel* m_searchModel = nullptr; + + FGPositionedRef m_location; + FGAirportRef m_airportLocation; // valid if m_location is an FGAirport + FGPositionedRef m_detailLocation; // parking stand or runway detail + bool m_locationIsLatLon = false; + SGGeod m_geodLocation; + + FGPositionedList m_recentLocations; + LaunchConfig* m_config = nullptr; + QmlPositioned* m_detailQml = nullptr; + + bool m_offsetEnabled = false; + int m_offsetRadial = 0; + double m_offsetNm = 0.0; + bool m_offsetBearingIsTrue = false; + int m_headingDeg = 0; + int m_altitudeFt= -9999; + int m_airspeedKnots = 120; + bool m_onFinal = false; + bool m_useActiveRunway = true; + bool m_tuneNAV1; + bool m_useAvailableParking; +}; + +#endif // LOCATION_CONTROLLER_HXX diff --git a/src/GUI/LocationWidget.cxx b/src/GUI/LocationWidget.cxx deleted file mode 100644 index 2ac1129c9..000000000 --- a/src/GUI/LocationWidget.cxx +++ /dev/null @@ -1,1206 +0,0 @@ -// LocationWidget.cxx - GUI launcher dialog using Qt5 -// -// Written by James Turner, started October 2015. -// -// Copyright (C) 2015 James Turner -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation; either version 2 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -#include "LocationWidget.hxx" -#include "ui_LocationWidget.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "AirportDiagram.hxx" -#include "NavaidDiagram.hxx" -#include "LaunchConfig.hxx" -#include "DefaultAircraftLocator.hxx" - -#include -#include - -#include
-#include -#include -#include
-#include
-#include
// for fgSetDouble - -using namespace flightgear; - -const unsigned int MAX_RECENT_LOCATIONS = 64; - -QString fixNavaidName(QString s) -{ - // split into words - QStringList words = s.split(QChar(' ')); - QStringList changedWords; - Q_FOREACH(QString w, words) { - QString up = w.toUpper(); - - // expand common abbreviations - // note these are not translated, since they are abbreivations - // for English-langauge airports, mostly in the US/Canada - if (up == "FLD") { - changedWords.append("Field"); - continue; - } - - if (up == "CO") { - changedWords.append("County"); - continue; - } - - if ((up == "MUNI") || (up == "MUN")) { - changedWords.append("Municipal"); - continue; - } - - if (up == "MEM") { - changedWords.append("Memorial"); - continue; - } - - if (up == "RGNL") { - changedWords.append("Regional"); - continue; - } - - if (up == "CTR") { - changedWords.append("Center"); - continue; - } - - if (up == "INTL") { - changedWords.append("International"); - continue; - } - - // occurs in many Australian airport names in our DB - if (up == "(NSW)") { - changedWords.append("(New South Wales)"); - continue; - } - - if ((up == "VOR") || (up == "NDB") - || (up == "VOR-DME") || (up == "VORTAC") - || (up == "NDB-DME") - || (up == "AFB") || (up == "RAF")) - { - changedWords.append(w); - continue; - } - - if ((up =="[X]") || (up == "[H]") || (up == "[S]")) { - continue; // consume - } - - QChar firstChar = w.at(0).toUpper(); - w = w.mid(1).toLower(); - w.prepend(firstChar); - - changedWords.append(w); - } - - return changedWords.join(QChar(' ')); -} - -QString formatGeodAsString(const SGGeod& geod) -{ - QChar ns = (geod.getLatitudeDeg() > 0.0) ? 'N' : 'S'; - QChar ew = (geod.getLongitudeDeg() > 0.0) ? 'E' : 'W'; - - return QString::number(fabs(geod.getLongitudeDeg()), 'f',2 ) + ew + " " + - QString::number(fabs(geod.getLatitudeDeg()), 'f',2 ) + ns; -} - -bool parseStringAsGeod(const QString& s, SGGeod& result) -{ - int commaPos = s.indexOf(QChar(',')); - if (commaPos < 0) - return false; - - bool ok; - double lon = s.leftRef(commaPos).toDouble(&ok); - if (!ok) - return false; - - double lat = s.midRef(commaPos+1).toDouble(&ok); - if (!ok) - return false; - - result = SGGeod::fromDeg(lon, lat); - return true; -} - -QVariant savePositionList(const FGPositionedList& posList) -{ - QVariantList vl; - FGPositionedList::const_iterator it; - for (it = posList.begin(); it != posList.end(); ++it) { - QVariantMap vm; - FGPositionedRef pos = *it; - vm.insert("ident", QString::fromStdString(pos->ident())); - vm.insert("type", pos->type()); - vm.insert("lat", pos->geod().getLatitudeDeg()); - vm.insert("lon", pos->geod().getLongitudeDeg()); - vl.append(vm); - } - return vl; -} - -FGPositionedList loadPositionedList(QVariant v) -{ - QVariantList vl = v.toList(); - FGPositionedList result; - result.reserve(vl.size()); - NavDataCache* cache = NavDataCache::instance(); - - Q_FOREACH(QVariant v, vl) { - QVariantMap vm = v.toMap(); - std::string ident(vm.value("ident").toString().toStdString()); - double lat = vm.value("lat").toDouble(); - double lon = vm.value("lon").toDouble(); - FGPositioned::Type ty(static_cast(vm.value("type").toInt())); - FGPositioned::TypeFilter filter(ty); - FGPositionedRef pos = cache->findClosestWithIdent(ident, - SGGeod::fromDeg(lon, lat), - &filter); - if (pos) - result.push_back(pos); - } - - return result; -} - -class IdentSearchFilter : public FGPositioned::TypeFilter -{ -public: - IdentSearchFilter(LauncherAircraftType aircraft) - { - addType(FGPositioned::VOR); - addType(FGPositioned::FIX); - addType(FGPositioned::NDB); - - // aircraft type isn't reliable yet, until we ensure - // most aircraft are tagged accordingly with helicopter, - // seaplane, etc. Hnece disabling this logic for now -#if 0 - if (aircraft == Helicopter) { - addType(FGPositioned::HELIPORT); - } - - if (aircraft == Seaplane) { - addType(FGPositioned::SEAPORT); - } -#else - addType(FGPositioned::HELIPORT); - addType(FGPositioned::SEAPORT); -#endif - // always include airports regardless of acft type - addType(FGPositioned::AIRPORT); - } -}; - -class NavSearchModel : public QAbstractListModel -{ - Q_OBJECT -public: - NavSearchModel() : - m_searchActive(false) - { - } - - void setSearch(QString t, LauncherAircraftType aircraft) - { - beginResetModel(); - - m_items.clear(); - m_ids.clear(); - - std::string term(t.toUpper().toStdString()); - - IdentSearchFilter filter(aircraft); - FGPositionedList exactMatches = NavDataCache::instance()->findAllWithIdent(term, &filter, true); - m_ids.reserve(exactMatches.size()); - m_items.reserve(exactMatches.size()); - for (auto match : exactMatches) { - m_ids.push_back(match->guid()); - m_items.push_back(match); - } - endResetModel(); - - m_search.reset(new NavDataCache::ThreadedGUISearch(term)); - QTimer::singleShot(100, this, SLOT(onSearchResultsPoll())); - m_searchActive = true; - } - - bool isSearchActive() const - { - return m_searchActive; - } - - virtual int rowCount(const QModelIndex&) const - { - // if empty, return 1 for special 'no matches'? - return m_ids.size(); - } - - virtual QVariant data(const QModelIndex& index, int role) const - { - if (!index.isValid()) - return QVariant(); - - FGPositionedRef pos = itemAtRow(index.row()); - if (role == Qt::DisplayRole) { - if (pos->type() == FGPositioned::FIX) { - // fixes don't have a name, show position instead - return QString("Fix %1 (%2)").arg(QString::fromStdString(pos->ident())) - .arg(formatGeodAsString(pos->geod())); - } else { - QString name = fixNavaidName(QString::fromStdString(pos->name())); - return QString("%1: %2").arg(QString::fromStdString(pos->ident())).arg(name); - } - } - - if (role == Qt::DecorationRole) { - return AirportDiagram::iconForPositioned(pos, - AirportDiagram::SmallIcons | AirportDiagram::LargeAirportPlans); - } - - if (role == Qt::EditRole) { - return QString::fromStdString(pos->ident()); - } - - if (role == Qt::UserRole) { - return static_cast(m_ids[index.row()]); - } - - return QVariant(); - } - - FGPositionedRef itemAtRow(unsigned int row) const - { - FGPositionedRef pos = m_items[row]; - if (!pos.valid()) { - pos = NavDataCache::instance()->loadById(m_ids[row]); - m_items[row] = pos; - } - - return pos; - } - - void setItems(const FGPositionedList& items) - { - beginResetModel(); - m_searchActive = false; - m_items = items; - - m_ids.clear(); - for (unsigned int i=0; i < items.size(); ++i) { - m_ids.push_back(m_items[i]->guid()); - } - - endResetModel(); - } - -Q_SIGNALS: - void searchComplete(); - -private slots: - - void onSearchResultsPoll() - { - if (m_search.isNull()) { - return; - } - - PositionedIDVec newIds = m_search->results(); - if (!newIds.empty()) { - m_ids.reserve(newIds.size()); - beginInsertRows(QModelIndex(), m_ids.size(), newIds.size() - 1); - for (auto id : newIds) { - m_ids.push_back(id); - m_items.push_back({}); // null ref - } - endInsertRows(); - } - - if (m_search->isComplete()) { - m_searchActive = false; - m_search.reset(); - emit searchComplete(); - } else { - QTimer::singleShot(100, this, SLOT(onSearchResultsPoll())); - } - } - -private: - PositionedIDVec m_ids; - mutable FGPositionedList m_items; - bool m_searchActive; - QScopedPointer m_search; -}; - - -LocationWidget::LocationWidget(QWidget *parent) : - QWidget(parent), - m_ui(new Ui::LocationWidget), - m_locationIsLatLon(false), - m_aircraftType(Airplane) -{ - m_ui->setupUi(this); - - QIcon historyIcon(":/history-icon"); - m_ui->searchHistory->setIcon(historyIcon); - - QByteArray format; - m_ui->searchIcon->setMovie(new QMovie(":/spinner", format, this)); - - m_searchModel = new NavSearchModel; - m_ui->searchResultsList->setModel(m_searchModel); - connect(m_ui->searchResultsList, &QListView::clicked, - this, &LocationWidget::onSearchResultSelected); - connect(m_searchModel, &NavSearchModel::searchComplete, - this, &LocationWidget::onSearchComplete); - - connect(m_ui->runwayCombo, SIGNAL(currentIndexChanged(int)), - this, SLOT(updateDescription())); - connect(m_ui->parkingCombo, SIGNAL(currentIndexChanged(int)), - this, SLOT(updateDescription())); - connect(m_ui->runwayRadio, SIGNAL(toggled(bool)), - this, SLOT(updateDescription())); - connect(m_ui->parkingRadio, SIGNAL(toggled(bool)), - this, SLOT(updateDescription())); - connect(m_ui->onFinalCheckbox, SIGNAL(toggled(bool)), - this, SLOT(updateDescription())); - connect(m_ui->approachDistanceSpin, SIGNAL(valueChanged(int)), - this, SLOT(updateDescription())); - - connect(m_ui->airportDiagram, &AirportDiagram::clickedRunway, - this, &LocationWidget::onAirportRunwayClicked); - connect(m_ui->airportDiagram, &AirportDiagram::clickedParking, - this, &LocationWidget::onAirportParkingClicked); - connect(m_ui->airportDiagram, &AirportDiagram::clickedHelipad, - this, &LocationWidget::onAirportHelipadClicked); - - connect(m_ui->locationSearchEdit, &QLineEdit::returnPressed, - this, &LocationWidget::onSearch); - - connect(m_ui->searchHistory, &QPushButton::clicked, - this, &LocationWidget::onShowHistory); - - connect(m_ui->trueBearing, &QCheckBox::toggled, - this, &LocationWidget::onOffsetBearingTrueChanged); - connect(m_ui->offsetGroup, &QGroupBox::toggled, - this, &LocationWidget::onOffsetEnabledToggled); - connect(m_ui->trueBearing, &QCheckBox::toggled, this, - &LocationWidget::onOffsetDataChanged); - connect(m_ui->offsetBearingSpinbox, SIGNAL(valueChanged(int)), - this, SLOT(onOffsetDataChanged())); - connect(m_ui->offsetNmSpinbox, SIGNAL(valueChanged(double)), - this, SLOT(onOffsetDataChanged())); - connect(m_ui->headingSpinbox, SIGNAL(valueChanged(int)), - this, SLOT(onHeadingChanged())); - - m_backButton = new QToolButton(this); - m_backButton->setGeometry(0, 0, 64, 32); - m_backButton->setText(tr("<< Back")); - m_backButton->raise(); - - connect(m_backButton, &QAbstractButton::clicked, - this, &LocationWidget::onBackToSearch); - -// force various pieces of UI into sync - onOffsetEnabledToggled(m_ui->offsetGroup->isChecked()); - onOffsetBearingTrueChanged(m_ui->trueBearing->isChecked()); - onBackToSearch(); -} - -LocationWidget::~LocationWidget() -{ - delete m_ui; -} - -void LocationWidget::setLaunchConfig(LaunchConfig *config) -{ - m_config = config; - connect(m_config, &LaunchConfig::collect, this, &LocationWidget::onCollectConfig); -} - -void LocationWidget::restoreSettings() -{ - QSettings settings; - m_recentLocations = loadPositionedList(settings.value("recent-locations")); - onShowHistory(); -} - -bool LocationWidget::isParkedLocation() const -{ - if (FGPositioned::isAirportType(m_location.ptr())) { - if (m_ui->parkingRadio->isChecked()) { - return true; - } - } - - // treat all other ground starts as taxi or on runway, i.e engines - // running if possible - return false; -} - -bool LocationWidget::isAirborneLocation() const -{ - const int altitude = m_ui->altitudeSpinbox->value(); - const bool altIsPositive = (altitude > 0); - const bool offsetSet = m_ui->offsetGroup->isChecked(); - - if (m_locationIsLatLon) { - return altIsPositive; - } - - if (FGPositioned::isAirportType(m_location.ptr())) { - if (m_ui->runwayRadio->isChecked() && m_ui->onFinalCheckbox->isChecked()) { - // in this case no altitude migth be set, but we assume - // it's still an airborne pos - return true; - } - - return false; - } - - // relative to a navaid or fix - base off altitude. - return altIsPositive; -} - -void LocationWidget::restoreLocation(QVariantMap l) -{ - if (l.contains("location-lat")) { - m_locationIsLatLon = true; - m_geodLocation = SGGeod::fromDeg(l.value("location-lon").toDouble(), - l.value("location-lat").toDouble()); - } else if (l.contains("location-id")) { - m_location = NavDataCache::instance()->loadById(l.value("location-id").toULongLong()); - m_locationIsLatLon = false; - } - - m_ui->altitudeSpinbox->setValue(l.value("altitude", 6000).toInt()); - m_ui->airspeedSpinbox->setValue(l.value("speed", 120).toInt()); - m_ui->headingSpinbox->setValue(l.value("heading").toInt()); - m_ui->offsetGroup->setChecked(l.value("offset-enabled").toBool()); - m_ui->offsetBearingSpinbox->setValue(l.value("offset-bearing").toInt()); - m_ui->offsetNmSpinbox->setValue(l.value("offset-distance", 10).toInt()); - - onLocationChanged(); - - // now we've loaded airport location data (potentially), we can apply - // more settings - if (FGPositioned::isAirportType(m_location.ptr())) { - if (l.contains("location-apt-runway")) { - QString runway = l.value("location-apt-runway").toString(); - int index = m_ui->runwayCombo->findText(runway); - if (index < 0) { - index = 0; // revert to 'active' option - } - m_ui->runwayRadio->setChecked(true); - m_ui->runwayCombo->setCurrentIndex(index); - } else if (l.contains("location-apt-parking")) { - QString parking = l.value("location-apt-parking").toString(); - int index = m_ui->parkingCombo->findText(parking); - if (index >= 0) { - m_ui->parkingRadio->setChecked(true); - m_ui->parkingCombo->setCurrentIndex(index); - } - } - - m_ui->onFinalCheckbox->setChecked(l.value("location-on-final").toBool()); - m_ui->approachDistanceSpin->setValue(l.value("location-apt-final-distance").toInt()); - } // of location is an airport - - updateDescription(); -} - -bool LocationWidget::shouldStartPaused() const -{ - if (!m_location) { - return false; // defaults to on-ground at KSFO - } - - if (FGPositioned::isAirportType(m_location.ptr())) { - return m_ui->onFinalCheckbox->isChecked(); - } else { - // navaid, start paused - return true; - } -} - -QVariantMap LocationWidget::saveLocation() const -{ - QVariantMap locationSet; - if (m_locationIsLatLon) { - locationSet.insert("location-lat", m_geodLocation.getLatitudeDeg()); - locationSet.insert("location-lon", m_geodLocation.getLongitudeDeg()); - } else if (m_location) { - locationSet.insert("location-id", static_cast(m_location->guid())); - - if (FGPositioned::isAirportType(m_location.ptr())) { - locationSet.insert("location-on-final", m_ui->onFinalCheckbox->isChecked()); - locationSet.insert("location-apt-final-distance", m_ui->approachDistanceSpin->value()); - if (m_ui->runwayRadio->isChecked()) { - if (m_ui->runwayCombo->currentIndex() > 0) { - locationSet.insert("location-apt-runway", m_ui->runwayCombo->currentText()); - } else { - locationSet.insert("location-apt-runway", "active"); - } - } else if (m_ui->parkingRadio->isChecked()) { - locationSet.insert("location-apt-parking", m_ui->parkingCombo->currentText()); - } - } // of location is an airport - } // of m_location is valid - - - locationSet.insert("altitude", m_ui->altitudeSpinbox->value()); - locationSet.insert("speed", m_ui->airspeedSpinbox->value()); - locationSet.insert("offset-enabled", m_ui->offsetGroup->isChecked()); - locationSet.insert("offset-bearing", m_ui->offsetBearingSpinbox->value()); - locationSet.insert("offset-distance", m_ui->offsetNmSpinbox->value()); - - locationSet.insert("text", locationDescription()); - - return locationSet; -} - -void LocationWidget::setLocationProperties() -{ - SGPropertyNode_ptr presets = fgGetNode("/sim/presets", true); - - QStringList props = QStringList() << "vor-id" << "fix" << "ndb-id" << - "runway-requested" << "navaid-id" << "offset-azimuth-deg" << - "offset-distance-nm" << "glideslope-deg" << - "speed-set" << "on-ground" << "airspeed-kt" << - "airport-id" << "runway" << "parkpos"; - - Q_FOREACH(QString s, props) { - SGPropertyNode* c = presets->getChild(s.toStdString()); - if (c) { - c->clearValue(); - } - } - - if (m_locationIsLatLon) { - fgSetDouble("/sim/presets/latitude-deg", m_geodLocation.getLatitudeDeg()); - fgSetDouble("/position/latitude-deg", m_geodLocation.getLatitudeDeg()); - fgSetDouble("/sim/presets/longitude-deg", m_geodLocation.getLongitudeDeg()); - fgSetDouble("/position/longitude-deg", m_geodLocation.getLongitudeDeg()); - - applyPositionOffset(); - return; - } - - fgSetDouble("/sim/presets/latitude-deg", 9999.0); - fgSetDouble("/sim/presets/longitude-deg", 9999.0); - fgSetDouble("/sim/presets/altitude-ft", -9999.0); - fgSetDouble("/sim/presets/heading-deg", 9999.0); - - if (!m_location) { - return; - } - - if (FGPositioned::isAirportType(m_location.ptr())) { - FGAirport* apt = static_cast(m_location.ptr()); - fgSetString("/sim/presets/airport-id", apt->ident()); - fgSetBool("/sim/presets/on-ground", true); - fgSetBool("/sim/presets/airport-requested", true); - - if (m_ui->runwayRadio->isChecked()) { - if (apt->type() == FGPositioned::AIRPORT) { - int index = m_ui->runwayCombo->itemData(m_ui->runwayCombo->currentIndex()).toInt(); - if (index >= 0) { - // explicit runway choice - FGRunwayRef runway = apt->getRunwayByIndex(index); - fgSetString("/sim/presets/runway", runway->ident() ); - fgSetBool("/sim/presets/runway-requested", true ); - - // set nav-radio 1 based on selected runway - if (runway->ILS()) { - double mhz = runway->ILS()->get_freq() / 100.0; - fgSetDouble("/instrumentation/nav[0]/radials/selected-deg", runway->headingDeg()); - fgSetDouble("/instrumentation/nav[0]/frequencies/selected-mhz", mhz); - } - } - - if (m_ui->onFinalCheckbox->isChecked()) { - fgSetDouble("/sim/presets/glideslope-deg", 3.0); - fgSetDouble("/sim/presets/offset-distance-nm", m_ui->approachDistanceSpin->value()); - fgSetBool("/sim/presets/on-ground", false); - } - } else if (apt->type() == FGPositioned::HELIPORT) { - int index = m_ui->runwayCombo->itemData(m_ui->runwayCombo->currentIndex()).toInt(); - if (index >= 0) { - // explicit pad choice - FGHelipadRef pad = apt->getHelipadByIndex(index); - fgSetString("/sim/presets/runway", pad->ident() ); - fgSetBool("/sim/presets/runway-requested", true ); - } - } else { - qWarning() << Q_FUNC_INFO << "implement me"; - } - - } else if (m_ui->parkingRadio->isChecked()) { - // parking selection - fgSetString("/sim/presets/parkpos", m_ui->parkingCombo->currentText().toStdString()); - } - // of location is an airport - } else { - fgSetString("/sim/presets/airport-id", ""); - - // location is a navaid - // note setting the ident here is ambigious, we really only need and - // want the 'navaid-id' property. However setting the 'real' option - // gives a better UI experience (eg existing Position in Air dialog) - FGPositioned::Type ty = m_location->type(); - switch (ty) { - case FGPositioned::VOR: - fgSetString("/sim/presets/vor-id", m_location->ident()); - setNavRadioOption(); - break; - - case FGPositioned::NDB: - fgSetString("/sim/presets/ndb-id", m_location->ident()); - setNavRadioOption(); - break; - - case FGPositioned::FIX: - fgSetString("/sim/presets/fix", m_location->ident()); - break; - default: - break; - }; - - // set disambiguation property - globals->get_props()->setIntValue("/sim/presets/navaid-id", - static_cast(m_location->guid())); - - applyPositionOffset(); - } // of navaid location -} - -void LocationWidget::applyPositionOffset() -{ - if (m_ui->altitudeSpinbox->value() > 0) { - m_config->setArg("altitude", QString::number(m_ui->altitudeSpinbox->value())); - } - - m_config->setArg("vc", QString::number(m_ui->airspeedSpinbox->value())); - m_config->setArg("heading", QString::number(m_ui->headingSpinbox->value())); - - if (m_ui->offsetGroup->isChecked()) { - // flip direction of azimuth to balance the flip done in fgApplyStartOffset - // I don't know why that flip exists but changing it there will break - // command-line compatability so compensating here instead - int offsetAzimuth = m_ui->offsetBearingSpinbox->value() - 180; - m_config->setArg("offset-azimuth", QString::number(offsetAzimuth)); - m_config->setArg("offset-distance", QString::number(m_ui->offsetNmSpinbox->value())); - } -} - -void LocationWidget::onCollectConfig() -{ - if (m_locationIsLatLon) { - m_config->setArg("lat", QString::number(m_geodLocation.getLatitudeDeg())); - m_config->setArg("lon", QString::number(m_geodLocation.getLongitudeDeg())); - applyPositionOffset(); - return; - } - - if (!m_location) { - return; - } - - if (FGPositioned::isAirportType(m_location.ptr())) { - FGAirport* apt = static_cast(m_location.ptr()); - m_config->setArg("airport", QString::fromStdString(apt->ident())); - - if (m_ui->runwayRadio->isChecked()) { - if (apt->type() == FGPositioned::AIRPORT) { - int index = m_ui->runwayCombo->itemData(m_ui->runwayCombo->currentIndex()).toInt(); - if (index >= 0) { - // explicit runway choice - FGRunwayRef runway = apt->getRunwayByIndex(index); - m_config->setArg("runway", QString::fromStdString(runway->ident())); - - // set nav-radio 1 based on selected runway - if (runway->ILS()) { - double mhz = runway->ILS()->get_freq() / 100.0; - m_config->setArg("nav1", QString("%1:%2").arg(runway->headingDeg()).arg(mhz)); - } - } - - if (m_ui->onFinalCheckbox->isChecked()) { - m_config->setArg("glideslope", std::string("3.0")); - m_config->setArg("offset-distance", QString::number(m_ui->approachDistanceSpin->value())); - m_config->setArg("on-ground", std::string("false")); - } - } else if (apt->type() == FGPositioned::HELIPORT) { - int index = m_ui->runwayCombo->itemData(m_ui->runwayCombo->currentIndex()).toInt(); - if (index >= 0) { - // explicit pad choice - FGHelipadRef pad = apt->getHelipadByIndex(index); - m_config->setArg("runway", pad->ident()); - } - } else { - qWarning() << Q_FUNC_INFO << "implement me"; - } - - } else if (m_ui->parkingRadio->isChecked()) { - // parking selection - m_config->setArg("parkpos", m_ui->parkingCombo->currentText()); - } - // of location is an airport - } else { - // location is a navaid - // note setting the ident here is ambigious, we really only need and - // want the 'navaid-id' property. However setting the 'real' option - // gives a better UI experience (eg existing Position in Air dialog) - FGPositioned::Type ty = m_location->type(); - switch (ty) { - case FGPositioned::VOR: - m_config->setArg("vor", m_location->ident()); - setNavRadioOption(); - break; - - case FGPositioned::NDB: - m_config->setArg("ndb", m_location->ident()); - setNavRadioOption(); - break; - - case FGPositioned::FIX: - m_config->setArg("fix", m_location->ident()); - break; - default: - break; - }; - - // set disambiguation property - m_config->setProperty("/sim/presets/navaid-id", QString::number(m_location->guid())); - applyPositionOffset(); - } // of navaid location -} - -void LocationWidget::setNavRadioOption() -{ - if (m_location->type() == FGPositioned::VOR) { - FGNavRecordRef nav(static_cast(m_location.ptr())); - double mhz = nav->get_freq() / 100.0; - int heading = 0; // add heading support - QString navOpt = QString("%1:%2").arg(heading).arg(mhz); - m_config->setArg("nav1", navOpt); - } else { - FGNavRecordRef nav(static_cast(m_location.ptr())); - int khz = nav->get_freq() / 100; - int heading = 0; - QString adfOpt = QString("%1:%2").arg(heading).arg(khz); - m_config->setArg("adf1", adfOpt); - } -} - -void LocationWidget::onSearch() -{ - QString search = m_ui->locationSearchEdit->text(); - - m_locationIsLatLon = parseStringAsGeod(search, m_geodLocation); - if (m_locationIsLatLon) { - m_ui->searchIcon->setVisible(false); - m_ui->searchStatusText->setText(QString("Position '%1'").arg(formatGeodAsString(m_geodLocation))); - m_location.clear(); - onLocationChanged(); - updateDescription(); - return; - } - - m_searchModel->setSearch(search, m_aircraftType); - - if (m_searchModel->isSearchActive()) { - m_ui->searchStatusText->setText(tr("Searching for '%1'").arg(search)); - m_ui->searchIcon->setVisible(true); - m_ui->searchIcon->movie()->start(); - } else if (m_searchModel->rowCount(QModelIndex()) == 1) { - setBaseLocation(m_searchModel->itemAtRow(0)); - } -} - -void LocationWidget::onSearchComplete() -{ - QString search = m_ui->locationSearchEdit->text(); - m_ui->searchIcon->setVisible(false); - m_ui->searchStatusText->setText(tr("Results for '%1'").arg(search)); - - int numResults = m_searchModel->rowCount(QModelIndex()); - if (numResults == 0) { - m_ui->searchStatusText->setText(tr("No matches for '%1'").arg(search)); - } else if (numResults == 1) { - addToRecent(m_searchModel->itemAtRow(0)); - setBaseLocation(m_searchModel->itemAtRow(0)); - } -} - -void LocationWidget::onLocationChanged() -{ - bool locIsAirport = FGPositioned::isAirportType(m_location.ptr()); - if (!m_location && !m_locationIsLatLon) { - onBackToSearch(); - return; - } - - m_backButton->show(); - - if (locIsAirport) { - m_ui->stack->setCurrentIndex(0); - FGAirport* apt = static_cast(m_location.ptr()); - m_ui->airportDiagram->setAirport(apt); - - m_ui->runwayRadio->setChecked(true); // default back to runway mode - // unless multiplayer is enabled ? - m_ui->airportDiagram->setEnabled(true); - - m_ui->runwayCombo->clear(); - m_ui->runwayCombo->addItem("Automatic", -1); - - if (apt->type() == FGPositioned::HELIPORT) { - for (unsigned int r=0; rnumHelipads(); ++r) { - FGHelipadRef pad = apt->getHelipadByIndex(r); - // add pad with index as data role - m_ui->runwayCombo->addItem(QString::fromStdString(pad->ident()), r); - - m_ui->airportDiagram->addHelipad(pad); - } - } else { - for (unsigned int r=0; rnumRunways(); ++r) { - FGRunwayRef rwy = apt->getRunwayByIndex(r); - // add runway with index as data role - m_ui->runwayCombo->addItem(QString::fromStdString(rwy->ident()), r); - - m_ui->airportDiagram->addRunway(rwy); - } - } - - m_ui->parkingCombo->clear(); - FGGroundNetwork* ground = apt->groundNetwork(); - if (ground && ground->exists()) { - FGParkingList parkings = ground->allParkings(); - if (parkings.empty()) { - m_ui->parkingCombo->setEnabled(false); - m_ui->parkingRadio->setEnabled(false); - } else { - m_ui->parkingCombo->setEnabled(true); - m_ui->parkingRadio->setEnabled(true); - - FGParkingList::const_iterator it; - for (it = parkings.begin(); it != parkings.end(); ++it) { - m_ui->parkingCombo->addItem(QString::fromStdString((*it)->getName()), - (*it)->getIndex()); - - m_ui->airportDiagram->addParking(*it); - } - } // of have parkings - } // of was able to create dynamics - - const QString airportName = QString::fromStdString(apt->name()); - const QString icao = QString::fromStdString(apt->ident()); - - m_ui->titleLabel->setText(tr("%1 (%2)").arg(airportName).arg(icao)); - } else if (m_locationIsLatLon) { - m_ui->stack->setCurrentIndex(1); - m_ui->navaidDiagram->setGeod(m_geodLocation); - } else if (m_location) { - // navaid - m_ui->stack->setCurrentIndex(1); - m_ui->navaidDiagram->setNavaid(m_location); - - const QString name = QString::fromStdString(m_location->name()); - const QString ident = QString::fromStdString(m_location->ident()); - m_ui->navTitleLabel->setText(tr("%1 (%2)").arg(name).arg(ident)); - } -} - -void LocationWidget::onOffsetEnabledToggled(bool on) -{ - m_ui->navaidDiagram->setOffsetEnabled(on); - updateDescription(); -} - -void LocationWidget::onAirportRunwayClicked(FGRunwayRef rwy) -{ - if (rwy) { - m_ui->runwayRadio->setChecked(true); - int rwyIndex = m_ui->runwayCombo->findText(QString::fromStdString(rwy->ident())); - m_ui->runwayCombo->setCurrentIndex(rwyIndex); - m_ui->airportDiagram->setSelectedRunway(rwy); - } - - updateDescription(); -} - -void LocationWidget::onAirportParkingClicked(FGParkingRef park) -{ - if (park) { - m_ui->parkingRadio->setChecked(true); - int parkingIndex = m_ui->parkingCombo->findData(park->getIndex()); - m_ui->parkingCombo->setCurrentIndex(parkingIndex); - m_ui->airportDiagram->setSelectedParking(park); - } - - updateDescription(); -} - -void LocationWidget::onAirportHelipadClicked(FGHelipadRef pad) -{ - if (pad) { - m_ui->runwayRadio->setChecked(true); - int rwyIndex = m_ui->runwayCombo->findText(QString::fromStdString(pad->ident())); - m_ui->runwayCombo->setCurrentIndex(rwyIndex); - m_ui->airportDiagram->setSelectedHelipad(pad); - } - - updateDescription(); -} - -QString compassPointFromHeading(int heading) -{ - const int labelArc = 360 / 8; - heading += (labelArc >> 1); - SG_NORMALIZE_RANGE(heading, 0, 359); - - switch (heading / labelArc) { - case 0: return "N"; - case 1: return "NE"; - case 2: return "E"; - case 3: return "SE"; - case 4: return "S"; - case 5: return "SW"; - case 6: return "W"; - case 7: return "NW"; - } - - return QString(); -} - -QString LocationWidget::locationDescription() const -{ - if (!m_location) { - if (m_locationIsLatLon) { - return tr("at position %1").arg(formatGeodAsString(m_geodLocation)); - } - - return tr("No location selected"); - } - - bool locIsAirport = FGPositioned::isAirportType(m_location.ptr()); - QString ident = QString::fromStdString(m_location->ident()), - name = QString::fromStdString(m_location->name()); - - name = fixNavaidName(name); - - if (locIsAirport) { - //FGAirport* apt = static_cast(m_location.ptr()); - QString locationOnAirport; - - if (m_ui->runwayRadio->isChecked()) { - bool onFinal = m_ui->onFinalCheckbox->isChecked(); - int comboIndex = m_ui->runwayCombo->currentIndex(); - QString runwayName = (comboIndex == 0) ? - "active runway" : - QString("runway %1").arg(m_ui->runwayCombo->currentText()); - - if (onFinal) { - int finalDistance = m_ui->approachDistanceSpin->value(); - locationOnAirport = tr("on %2-mile final to %1").arg(runwayName).arg(finalDistance); - } else { - locationOnAirport = tr("on %1").arg(runwayName); - } - } else if (m_ui->parkingRadio->isChecked()) { - locationOnAirport = tr("at parking position %1").arg(m_ui->parkingCombo->currentText()); - } - - return tr("%2 (%1): %3").arg(ident).arg(name).arg(locationOnAirport); - } else { - QString offsetDesc = tr("at"); - if (m_ui->offsetGroup->isChecked()) { - offsetDesc = tr("%1nm %2 of"). - arg(m_ui->offsetNmSpinbox->value(), 0, 'f', 1). - arg(compassPointFromHeading(m_ui->offsetBearingSpinbox->value())); - } - - QString navaidType; - switch (m_location->type()) { - case FGPositioned::VOR: - navaidType = QString("VOR"); break; - case FGPositioned::NDB: - navaidType = QString("NDB"); break; - case FGPositioned::FIX: - return tr("%2 waypoint %1").arg(ident).arg(offsetDesc); - default: - // unsupported type - break; - } - - return tr("%4 %1 %2 (%3)").arg(navaidType).arg(ident).arg(name).arg(offsetDesc); - } - - return tr("No location selected"); -} - - -void LocationWidget::updateDescription() -{ - bool locIsAirport = FGPositioned::isAirportType(m_location.ptr()); - if (locIsAirport) { - FGAirport* apt = static_cast(m_location.ptr()); - - if (m_ui->runwayRadio->isChecked()) { - int comboIndex = m_ui->runwayCombo->currentIndex(); - int runwayIndex = m_ui->runwayCombo->itemData(comboIndex).toInt(); - if (apt->type() == FGPositioned::HELIPORT) { - FGHelipadRef pad = (runwayIndex >= 0) ? - apt->getHelipadByIndex(runwayIndex) : FGHelipadRef(); - m_ui->airportDiagram->setSelectedHelipad(pad); - } else { - // we can't figure out the active runway in the launcher (yet) - FGRunwayRef rwy = (runwayIndex >= 0) ? - apt->getRunwayByIndex(runwayIndex) : FGRunwayRef(); - m_ui->airportDiagram->setSelectedRunway(rwy); - } - } else if (m_ui->parkingRadio->isChecked()) { - int groundNetIndex = m_ui->parkingCombo->currentData().toInt(); - FGParkingRef park = apt->groundNetwork()->getParkingByIndex(groundNetIndex); - m_ui->airportDiagram->setSelectedParking(park); - } - - if (m_ui->onFinalCheckbox->isChecked()) { - m_ui->airportDiagram->setApproachExtensionDistance(m_ui->approachDistanceSpin->value()); - } else { - m_ui->airportDiagram->setApproachExtensionDistance(0.0); - } - } - - emit descriptionChanged(locationDescription()); -} - -void LocationWidget::onSearchResultSelected(const QModelIndex& index) -{ - FGPositionedRef pos = m_searchModel->itemAtRow(index.row()); - addToRecent(pos); - setBaseLocation(pos); -} - -void LocationWidget::onOffsetBearingTrueChanged(bool on) -{ - m_ui->offsetBearingLabel->setText(on ? tr("True bearing:") : - tr("Magnetic bearing:")); -} - -void LocationWidget::addToRecent(FGPositionedRef pos) -{ - FGPositionedList::iterator it = std::find(m_recentLocations.begin(), - m_recentLocations.end(), - pos); - if (it != m_recentLocations.end()) { - m_recentLocations.erase(it); - } - - if (m_recentLocations.size() >= MAX_RECENT_LOCATIONS) { - m_recentLocations.pop_back(); - } - - m_recentLocations.insert(m_recentLocations.begin(), pos); - QSettings settings; - settings.setValue("recent-locations", savePositionList(m_recentLocations)); -} - -static void prependLocation(FGPositionedList& locs, const std::string& airportIdent) -{ - auto it = std::find_if(locs.begin(), locs.end(), [airportIdent](FGPositionedRef pos) { - return pos->ident() == airportIdent; - }); - - if (it == locs.end()) { - FGAirportRef apt = FGAirport::findByIdent(airportIdent); - locs.insert(locs.begin(), apt); - } -} - -void LocationWidget::onShowHistory() -{ - FGPositionedList locs = m_recentLocations; - - // prepend the C172P tutorial airport - prependLocation(locs, "PHTO"); - - // and then prepend our default airport - prependLocation(locs, flightgear::defaultAirportICAO()); - - m_searchModel->setItems(locs); -} - -void LocationWidget::setBaseLocation(FGPositionedRef ref) -{ - m_locationIsLatLon = false; -// don't change location if we're on the same location. We must check -// the current stack index, otherwise there's no way back into the same -// location after using the back button. - if ((m_location == ref) && (m_ui->stack->currentIndex() != 2)) { - return; - } - - m_location = ref; - onLocationChanged(); - - updateDescription(); -} - -void LocationWidget::setAircraftType(LauncherAircraftType ty) -{ - m_aircraftType = ty; - // nothing happens until next search - m_ui->navaidDiagram->setAircraftType(ty); - m_ui->airportDiagram->setAircraftType(ty); -} - -void LocationWidget::onOffsetDataChanged() -{ - m_ui->navaidDiagram->setOffsetEnabled(m_ui->offsetGroup->isChecked()); - m_ui->navaidDiagram->setOffsetBearingDeg(m_ui->offsetBearingSpinbox->value()); - m_ui->navaidDiagram->setOffsetDistanceNm(m_ui->offsetNmSpinbox->value()); - - updateDescription(); -} - -void LocationWidget::onHeadingChanged() -{ - m_ui->navaidDiagram->setHeadingDeg(m_ui->headingSpinbox->value()); -} - -void LocationWidget::onBackToSearch() -{ - m_ui->stack->setCurrentIndex(2); - m_backButton->hide(); -} - -#include "LocationWidget.moc" diff --git a/src/GUI/LocationWidget.hxx b/src/GUI/LocationWidget.hxx deleted file mode 100644 index 55302d44c..000000000 --- a/src/GUI/LocationWidget.hxx +++ /dev/null @@ -1,117 +0,0 @@ -// LocationWidget.hxx - GUI launcher dialog using Qt5 -// -// Written by James Turner, started October 2015. -// -// Copyright (C) 2015 James Turner -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation; either version 2 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -#ifndef LOCATIONWIDGET_H -#define LOCATIONWIDGET_H - -#include - -#include - -#include -#include - -#include "LaunchConfig.hxx" -#include "QtLauncher_fwd.hxx" - -namespace Ui { - class LocationWidget; -} - -class NavSearchModel; - -class LocationWidget : public QWidget -{ - Q_OBJECT - -public: - explicit LocationWidget(QWidget *parent = 0); - ~LocationWidget(); - - void setLaunchConfig(LaunchConfig* config); - - QString locationDescription() const; - - void setBaseLocation(FGPositionedRef ref); - - void setAircraftType(LauncherAircraftType ty); - - bool shouldStartPaused() const; - - void setLocationProperties(); - - void restoreLocation(QVariantMap l); - QVariantMap saveLocation() const; - - void restoreSettings(); - - /// used to automatically select aircraft state - bool isParkedLocation() const; - - /// used to automatically select aircraft state - bool isAirborneLocation() const; -Q_SIGNALS: - void descriptionChanged(QString t); - -private Q_SLOTS: - void updateDescription(); - void onLocationChanged(); - void onOffsetDataChanged(); - void onHeadingChanged(); - - void onCollectConfig(); -private: - - void onSearch(); - void onSearchResultSelected(const QModelIndex& index); - void onSearchComplete(); - - void onAirportRunwayClicked(FGRunwayRef rwy); - void onAirportParkingClicked(FGParkingRef park); - void onAirportHelipadClicked(FGHelipadRef pad); - - void onOffsetBearingTrueChanged(bool on); - - void addToRecent(FGPositionedRef pos); - - void onOffsetEnabledToggled(bool on); - void onBackToSearch(); - void setNavRadioOption(); - void onShowHistory(); - - void applyPositionOffset(); - - Ui::LocationWidget *m_ui; - - NavSearchModel* m_searchModel; - - FGPositionedRef m_location; - bool m_locationIsLatLon; - SGGeod m_geodLocation; - - QToolButton* m_backButton; - - FGPositionedList m_recentLocations; - LauncherAircraftType m_aircraftType; - - LaunchConfig* m_config = nullptr; -}; - -#endif // LOCATIONWIDGET_H diff --git a/src/GUI/LocationWidget.ui b/src/GUI/LocationWidget.ui deleted file mode 100644 index c5d4b80be..000000000 --- a/src/GUI/LocationWidget.ui +++ /dev/null @@ -1,442 +0,0 @@ - - - LocationWidget - - - - 0 - 0 - 864 - 683 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - nm - - - 10 - - - - - - - Parking: - - - - - - - Runway: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - On final approach at distance: - - - - - - - TextLabel - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - ft - - - -1000 - - - 120000 - - - 50 - - - 5000 - - - - - - - - Barometric altitude (ASL) - - - - - Above ground (AGL) - - - - - Flight Level (FL) - - - - - - - - Altitude: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Offset - - - true - - - false - - - - 4 - - - 4 - - - 4 - - - 4 - - - - - Bearing: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - true - - - 359 - - - 5 - - - - - - - True - - - - - - - Distance: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - nm - - - 1 - - - 10000.000000000000000 - - - 10.000000000000000 - - - - - - - - - - Airspeed: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Heading: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - true - - - 359 - - - - - - - kts - - - 9999 - - - 120 - - - - - - - TextLabel - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 200 - 200 - - - - - - - - - - 2 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - Search: - - - - - - - Enter an ICAO code, navaid or name, then press Enter - - - true - - - - - - - - 0 - 0 - - - - false - - - false - - - - - - - - - - - - - - Qt::AlignHCenter|Qt::AlignTop - - - - - - - - 16 - 16 - - - - TextLabel - - - Qt::AlignBottom|Qt::AlignHCenter - - - - - - - - - - - - - - - - - AirportDiagram - QWidget -
GUI/AirportDiagram.hxx
- 1 -
- - NavaidDiagram - QWidget -
GUI/NavaidDiagram.hxx
- 1 -
-
- - -
diff --git a/src/GUI/NavaidDiagram.cxx b/src/GUI/NavaidDiagram.cxx index 0f58ad83a..3f335ba6f 100644 --- a/src/GUI/NavaidDiagram.cxx +++ b/src/GUI/NavaidDiagram.cxx @@ -27,7 +27,9 @@ #include #include -NavaidDiagram::NavaidDiagram(QWidget* pr) : +#include + +NavaidDiagram::NavaidDiagram(QQuickItem* pr) : BaseDiagram(pr), m_offsetEnabled(false), m_offsetDistanceNm(5.0), @@ -37,12 +39,28 @@ NavaidDiagram::NavaidDiagram(QWidget* pr) : } -void NavaidDiagram::setNavaid(FGPositionedRef nav) +void NavaidDiagram::setNavaid(qlonglong nav) { - m_navaid = nav; - m_projectionCenter = nav ? nav->geod() : SGGeod(); - m_geod = nav->geod(); + m_navaid = fgpositioned_cast(flightgear::NavDataCache::instance()->loadById(nav)); + m_projectionCenter = m_navaid ? m_navaid->geod() : SGGeod(); + m_geod = m_projectionCenter; recomputeBounds(true); + emit locationChanged(); +} + +qlonglong NavaidDiagram::navaid() const +{ + return m_navaid->guid(); +} + +void NavaidDiagram::setGeod(QmlGeod geod) +{ + setGeod(geod.geod()); +} + +QmlGeod NavaidDiagram::geod() const +{ + return QmlGeod(m_geod); } void NavaidDiagram::setGeod(const SGGeod &geod) @@ -51,6 +69,7 @@ void NavaidDiagram::setGeod(const SGGeod &geod) m_geod = geod; m_projectionCenter = m_geod; recomputeBounds(true); + emit locationChanged(); } void NavaidDiagram::setOffsetEnabled(bool offset) @@ -59,24 +78,28 @@ void NavaidDiagram::setOffsetEnabled(bool offset) return; m_offsetEnabled = offset; recomputeBounds(true); + emit offsetChanged(); } void NavaidDiagram::setOffsetDistanceNm(double distanceNm) { m_offsetDistanceNm = distanceNm; update(); + emit offsetChanged(); } void NavaidDiagram::setOffsetBearingDeg(int bearingDeg) { m_offsetBearingDeg = bearingDeg; update(); + emit offsetChanged(); } void NavaidDiagram::setHeadingDeg(int headingDeg) { m_headingDeg = headingDeg; update(); + emit offsetChanged(); } void NavaidDiagram::paintContents(QPainter *painter) diff --git a/src/GUI/NavaidDiagram.hxx b/src/GUI/NavaidDiagram.hxx index dd22c6b57..a179fd11c 100644 --- a/src/GUI/NavaidDiagram.hxx +++ b/src/GUI/NavaidDiagram.hxx @@ -22,7 +22,7 @@ #define GUI_NAVAID_DIAGRAM_HXX #include "BaseDiagram.hxx" -#include +#include "QmlPositioned.hxx" #include #include @@ -30,28 +30,50 @@ class NavaidDiagram : public BaseDiagram { Q_OBJECT -public: - NavaidDiagram(QWidget* pr); - void setNavaid(FGPositionedRef nav); + Q_PROPERTY(qlonglong navaid READ navaid WRITE setNavaid NOTIFY locationChanged) + Q_PROPERTY(QmlGeod geod READ geod WRITE setGeod NOTIFY locationChanged) + + Q_PROPERTY(int headingDeg READ headingDeg WRITE setHeadingDeg NOTIFY offsetChanged) + Q_PROPERTY(int offsetBearingDeg READ offsetBearingDeg WRITE setOffsetBearingDeg NOTIFY offsetChanged) + Q_PROPERTY(bool offsetEnabled READ isOffsetEnabled WRITE setOffsetEnabled NOTIFY offsetChanged) + Q_PROPERTY(double offsetDistanceNm READ offsetDistanceNm WRITE setOffsetDistanceNm NOTIFY offsetChanged) +public: + NavaidDiagram(QQuickItem* pr = nullptr); + + void setNavaid(qlonglong nav); + qlonglong navaid() const; + + void setGeod(QmlGeod geod); + QmlGeod geod() const; void setGeod(const SGGeod& geod); - bool isOffsetEnabled() const; + bool isOffsetEnabled() const + { return m_offsetEnabled; } + void setOffsetEnabled(bool offset); void setOffsetDistanceNm(double distanceNm); - double offsetDistanceNm() const; + double offsetDistanceNm() const + { return m_offsetDistanceNm; } void setOffsetBearingDeg(int bearingDeg); - int offsetBearingDeg() const; + int offsetBearingDeg() const + { return m_offsetBearingDeg; } void setHeadingDeg(int headingDeg); - void headingDeg() const; -protected: - void paintContents(QPainter *) Q_DECL_OVERRIDE; + int headingDeg() const + { return m_headingDeg; } - void doComputeBounds() Q_DECL_OVERRIDE; +signals: + void locationChanged(); + void offsetChanged(); + +protected: + void paintContents(QPainter *) override; + + void doComputeBounds() override; private: FGPositionedRef m_navaid; SGGeod m_geod; diff --git a/src/GUI/PixmapImageItem.cxx b/src/GUI/PixmapImageItem.cxx new file mode 100644 index 000000000..a94d7ccaa --- /dev/null +++ b/src/GUI/PixmapImageItem.cxx @@ -0,0 +1,50 @@ +// PixmapImageItem.cxx - display a QPixmap/QImage directly +// +// Written by James Turner, started April 2018. +// +// Copyright (C) 2018 James Turner +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#include "PixmapImageItem.hxx" + +#include + +PixmapImageItem::PixmapImageItem(QQuickItem* parent) : + QQuickPaintedItem(parent) +{ + +} + +void PixmapImageItem::paint(QPainter *painter) +{ + if (_image.isNull()) + return; + + QRect rect(0, 0, width(), height()); + painter->drawImage(rect, _image); +} + +void PixmapImageItem::setImage(QImage img) +{ + if (img == _image) + return; + + _image = img; + update(); + const auto sz = img.size(); + setImplicitSize(sz.width(), sz.height()); + emit imageChanged(); +} diff --git a/src/GUI/PixmapImageItem.hxx b/src/GUI/PixmapImageItem.hxx new file mode 100644 index 000000000..99939ccad --- /dev/null +++ b/src/GUI/PixmapImageItem.hxx @@ -0,0 +1,50 @@ +// PixmapImageItem.hxx - display a QPixmap/QImage directly +// +// Written by James Turner, started April 2018. +// +// Copyright (C) 2018 James Turner +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +#ifndef PIXMAPIMAGEITEM_HXX +#define PIXMAPIMAGEITEM_HXX + +#include +#include + +class PixmapImageItem : public QQuickPaintedItem +{ + Q_OBJECT + + Q_PROPERTY(QImage image READ image WRITE setImage NOTIFY imageChanged) +public: + PixmapImageItem(QQuickItem* parent = nullptr); + + void paint(QPainter* painter) override; + + QImage image() const + { return _image; } + + void setImage(QImage img); + +signals: + void imageChanged(); + +private: + QImage _image; +}; + +#endif // PIXMAPIMAGEITEM_HXX diff --git a/src/GUI/QmlNavCacheWrapper.cxx b/src/GUI/QmlNavCacheWrapper.cxx new file mode 100644 index 000000000..fb6b89461 --- /dev/null +++ b/src/GUI/QmlNavCacheWrapper.cxx @@ -0,0 +1,26 @@ +// QmlNavCacheWrapper.cxx - Expose NavData to Qml +// +// Written by James Turner, started April 2018. +// +// Copyright (C) 2018 James Turner +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#include "QmlNavCacheWrapper.hxx" + +QmlNavCacheWrapper::QmlNavCacheWrapper() +{ + +} diff --git a/src/GUI/QmlNavCacheWrapper.hxx b/src/GUI/QmlNavCacheWrapper.hxx new file mode 100644 index 000000000..d6358adb9 --- /dev/null +++ b/src/GUI/QmlNavCacheWrapper.hxx @@ -0,0 +1,38 @@ +// QmlNavCacheWrapper.hxx - Expose NavData to Qml +// +// Written by James Turner, started April 2018. +// +// Copyright (C) 2018 James Turner +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#ifndef QMLNAVCACHEWRAPPER_HXX +#define QMLNAVCACHEWRAPPER_HXX + +#include + +/** + * @brief QmlNavCacheWrapper wraps and exposes NavData to Qml as singleton + * service + */ +class QmlNavCacheWrapper : public QObject +{ + Q_OBJECT +public: + + QmlNavCacheWrapper(); +}; + +#endif // QMLNAVCACHEWRAPPER_HXX diff --git a/src/GUI/QmlPositioned.cxx b/src/GUI/QmlPositioned.cxx new file mode 100644 index 000000000..42395e7bd --- /dev/null +++ b/src/GUI/QmlPositioned.cxx @@ -0,0 +1,317 @@ +// QmlPositioned.cxx - Expose NavData to Qml +// +// Written by James Turner, started April 2018. +// +// Copyright (C) 2018 James Turner +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#include "QmlPositioned.hxx" + +#include + +#include +#include +#include +#include +#include + +using namespace flightgear; + +QmlGeod::QmlGeod() : + m_data(SGGeod::fromDeg(-9999.0, -9999.0)) +{ +} + +QmlGeod::QmlGeod(const SGGeod &geod) : + m_data(geod) +{ +} + +double QmlGeod::latitudeDeg() const +{ + return m_data.getLatitudeDeg(); +} + +double QmlGeod::longitudeDeg() const +{ + return m_data.getLongitudeDeg(); +} + +double QmlGeod::latitudeRad() const +{ + return m_data.getLatitudeRad(); +} + +double QmlGeod::longitudeRad() const +{ + return m_data.getLongitudeRad(); +} + +double QmlGeod::elevationFt() const +{ + return m_data.getElevationFt(); +} + +bool QmlGeod::valid() const +{ + return m_data.isValid(); +} + +double QmlGeod::elevationM() const +{ + return m_data.getElevationM(); +} + +void QmlGeod::setLatitudeDeg(double latitudeDeg) +{ + if (qFuzzyCompare(m_data.getLatitudeDeg(), latitudeDeg)) + return; + + m_data.setLatitudeDeg(latitudeDeg); + // emit latitudeChanged(); +} + +void QmlGeod::setLongitudeDeg(double longitudeDeg) +{ + if (qFuzzyCompare(m_data.getLongitudeDeg(), longitudeDeg)) + return; + + m_data.setLongitudeDeg(longitudeDeg); + // emit longitudeChanged(); +} + +void QmlGeod::setLatitudeRad(double latitudeRad) +{ + if (qFuzzyCompare(m_data.getLatitudeRad(), latitudeRad)) + return; + + m_data.setLatitudeRad(latitudeRad); + // emit latitudeChanged(); +} + +void QmlGeod::setLongitudeRad(double longitudeRad) +{ + if (qFuzzyCompare(m_data.getLongitudeRad(), longitudeRad)) + return; + + m_data.setLongitudeRad(longitudeRad); + // emit longitudeChanged(); +} + +void QmlGeod::setElevationM(double elevationM) +{ + if (qFuzzyCompare(m_data.getElevationM(), elevationM)) + return; + + m_data.setElevationM(elevationM); + // emit elevationChanged(); +} + +void QmlGeod::setElevationFt(double elevationFt) +{ + if (qFuzzyCompare(m_data.getElevationFt(), elevationFt)) + return; + + m_data.setElevationFt(elevationFt); + // emit elevationChanged(); +} + +///////////////////////////////////////////////////////////////////////////////// + +QmlPositioned::QmlPositioned(QObject *parent) : QObject(parent) +{ + +} + +QmlPositioned::QmlPositioned(FGPositionedRef p) : + m_pos(p) +{ +} + +void QmlPositioned::setInner(FGPositionedRef p) +{ + if (!p) { + m_pos.clear(); + } else { + m_pos = p; + } +} + +FGPositionedRef QmlPositioned::inner() const +{ + return m_pos; +} + +bool QmlPositioned::valid() const +{ + return m_pos.valid(); +} + +QString QmlPositioned::ident() const +{ + if (!m_pos) + return {}; + + return QString::fromStdString(m_pos->ident()); +} + +QString QmlPositioned::name() const +{ + if (!m_pos) + return {}; + + return QString::fromStdString(m_pos->name()); +} + +qlonglong QmlPositioned::guid() const +{ + if (!m_pos) + return 0; + + return m_pos->guid(); +} + +QmlPositioned::Type QmlPositioned::type() const +{ + if (!m_pos) + return Invalid; + + return static_cast(m_pos->type()); +} + +void QmlPositioned::setGuid(qlonglong guid) +{ + if (m_pos && (m_pos->guid()) == guid) + return; + + m_pos = NavDataCache::instance()->loadById(guid); + + emit guidChanged(); + emit infoChanged(); +} + +bool QmlPositioned::isAirportType() const +{ + return FGPositioned::isAirportType(m_pos.get()); +} + +bool QmlPositioned::isRunwayType() const +{ + return FGPositioned::isRunwayType(m_pos.get()); +} + +bool QmlPositioned::isNavaidType() const +{ + return FGPositioned::isNavaidType(m_pos.get()); +} + +QmlGeod* QmlPositioned::geod() const +{ + if (!m_pos) + return nullptr; + + return new QmlGeod(m_pos->geod()); +} + +double QmlPositioned::navaidFrequencyMHz() const +{ + FGNavRecord* nav = fgpositioned_cast(m_pos); + if (!nav) + return 0.0; + + qWarning() << Q_FUNC_INFO << "check me!"; + return nav->get_freq() / 1000.0; +} + +double QmlPositioned::navaidRangeNm() const +{ + FGNavRecord* nav = fgpositioned_cast(m_pos); + if (!nav) + return 0.0; + + return nav->get_range(); +} + +QmlPositioned *QmlPositioned::navaidRunway() const +{ + FGNavRecord* nav = fgpositioned_cast(m_pos); + if (!nav || !nav->runway()) + return nullptr; + + return new QmlPositioned(nav->runway()); +} + +QmlPositioned *QmlPositioned::colocatedDME() const +{ + FGNavRecord* nav = fgpositioned_cast(m_pos); + if (!nav || !nav->hasDME()) + return nullptr; + + return new QmlPositioned(flightgear::NavDataCache::instance()->loadById(nav->colocatedDME())); +} + +QmlPositioned *QmlPositioned::owningAirport() const +{ + FGRunway* runway = fgpositioned_cast(m_pos); + if (runway) { + return new QmlPositioned(runway->airport()); + } + + return nullptr; +} + +double QmlPositioned::runwayHeadingDeg() const +{ + FGRunway* runway = fgpositioned_cast(m_pos); + if (runway) { + return runway->headingDeg(); + } + + return 0.0; +} + +double QmlPositioned::runwayLengthFt() const +{ + FGRunway* runway = fgpositioned_cast(m_pos); + if (runway) { + return runway->lengthFt(); + } + + return 0.0; +} + +bool QmlPositioned::equals(QmlPositioned *other) const +{ + return (other && (other->inner() == inner())); +} + +bool QmlPositioned::airportHasParkings() const +{ + if (!isAirportType()) + return false; + + FGAirport* apt = fgpositioned_cast(m_pos); + if (!apt) + return false; + + return !apt->groundNetwork()->allParkings().empty(); +} + +bool operator==(const QmlPositioned& p1, const QmlPositioned& p2) +{ + return p1.inner() == p2.inner(); +} + diff --git a/src/GUI/QmlPositioned.hxx b/src/GUI/QmlPositioned.hxx new file mode 100644 index 000000000..243a70646 --- /dev/null +++ b/src/GUI/QmlPositioned.hxx @@ -0,0 +1,186 @@ +// QmlPositioned.hxx - Expose NavData to Qml +// +// Written by James Turner, started April 2018. +// +// Copyright (C) 2018 James Turner +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#ifndef QMLPOSITIONED_HXX +#define QMLPOSITIONED_HXX + +#include + +#include + +/** + * @brief Expose an SGGeod as Qml-friendly class + */ +class QmlGeod +{ + Q_GADGET + + + Q_PROPERTY(double latitudeDeg READ latitudeDeg WRITE setLatitudeDeg) + Q_PROPERTY(double longitudeDeg READ longitudeDeg WRITE setLongitudeDeg) + + Q_PROPERTY(double latitudeRad READ latitudeRad WRITE setLatitudeRad) + Q_PROPERTY(double longitudeRad READ longitudeRad WRITE setLongitudeRad) + + Q_PROPERTY(double elevationM READ elevationM WRITE setElevationM) + Q_PROPERTY(double elevationFt READ elevationFt WRITE setElevationFt) + + Q_PROPERTY(bool valid READ valid) +public: + QmlGeod(); + QmlGeod(const SGGeod& geod); + + SGGeod geod() const + { return m_data; } + + + double latitudeDeg() const; + double longitudeDeg() const; + double latitudeRad() const; + double longitudeRad() const; + double elevationM() const; + double elevationFt() const; + + bool valid() const; +public slots: + void setLatitudeDeg(double latitudeDeg); + void setLongitudeDeg(double longitudeDeg); + void setLatitudeRad(double latitudeRad); + void setLongitudeRad(double longitudeRad); + void setElevationM(double elevationM); + void setElevationFt(double elevationFt); + +private: + SGGeod m_data; +}; + + +Q_DECLARE_METATYPE(QmlGeod) + +class QmlPositioned : public QObject +{ + Q_OBJECT +public: + explicit QmlPositioned(QObject *parent = nullptr); + + explicit QmlPositioned(FGPositionedRef p); + + // proxy FGPositioned type values + enum Type { + Invalid = FGPositioned::INVALID, + Airport = FGPositioned::AIRPORT, + Heliport = FGPositioned::HELIPORT, + Seaport = FGPositioned::SEAPORT, + Runway = FGPositioned::RUNWAY, + Helipad = FGPositioned::HELIPAD, + Taxiway = FGPositioned::TAXIWAY, + Pavement = FGPositioned::PAVEMENT, + Waypoint = FGPositioned::WAYPOINT, + Fix = FGPositioned::FIX, + NDB = FGPositioned::NDB, + VOR = FGPositioned::VOR, + ILS = FGPositioned::ILS, + Localizer = FGPositioned::LOC, + Glideslope = FGPositioned::GS, + OuterMarker = FGPositioned::OM, + MiddleMarker = FGPositioned::MM, + InnerMarker = FGPositioned::IM, + DME = FGPositioned::DME, + TACAN = FGPositioned::TACAN, + MobileTACAN = FGPositioned::MOBILE_TACAN, + Tower = FGPositioned::TOWER, + Parking = FGPositioned::PARKING, + Country = FGPositioned::COUNTRY, + City = FGPositioned::CITY, + Town = FGPositioned::TOWN, + Village = FGPositioned::VILLAGE + }; + + Q_ENUM(Type) + + Q_PROPERTY(QString ident READ ident NOTIFY infoChanged) + Q_PROPERTY(QString name READ name NOTIFY infoChanged) + Q_PROPERTY(Type type READ type NOTIFY infoChanged) + + Q_PROPERTY(bool valid READ valid NOTIFY infoChanged) + Q_PROPERTY(QmlGeod* geod READ geod NOTIFY infoChanged) + Q_PROPERTY(qlonglong guid READ guid WRITE setGuid NOTIFY guidChanged) + + Q_PROPERTY(bool isAirportType READ isAirportType NOTIFY infoChanged) + Q_PROPERTY(bool isRunwayType READ isRunwayType NOTIFY infoChanged) + Q_PROPERTY(bool isNavaidType READ isNavaidType NOTIFY infoChanged) + + Q_PROPERTY(double navaidFrequencyMHz READ navaidFrequencyMHz NOTIFY infoChanged) + Q_PROPERTY(double navaidRangeNm READ navaidRangeNm NOTIFY infoChanged) + + Q_PROPERTY(QmlPositioned* colocatedDME READ colocatedDME NOTIFY infoChanged) + Q_PROPERTY(QmlPositioned* navaidRunway READ navaidRunway NOTIFY infoChanged) + Q_PROPERTY(QmlPositioned* owningAirport READ owningAirport NOTIFY infoChanged) + + Q_PROPERTY(double runwayHeadingDeg READ runwayHeadingDeg NOTIFY infoChanged) + Q_PROPERTY(double runwayLengthFt READ runwayLengthFt NOTIFY infoChanged) + + Q_PROPERTY(bool airportHasParkings READ airportHasParkings NOTIFY infoChanged) + + void setInner(FGPositionedRef p); + FGPositionedRef inner() const; + bool valid() const; + + QString ident() const; + QString name() const; + qlonglong guid() const; + Type type() const; + + bool isAirportType() const; + bool isRunwayType() const; + bool isNavaidType() const; + + QmlGeod* geod() const; + + double navaidFrequencyMHz() const; + double navaidRangeNm() const; + + QmlPositioned* navaidRunway() const; + QmlPositioned* colocatedDME() const; + + // owning airport if one exists + QmlPositioned* owningAirport() const; + + double runwayHeadingDeg() const; + double runwayLengthFt() const; + + Q_INVOKABLE bool equals(QmlPositioned* other) const; + + bool airportHasParkings() const; + +public slots: + void setGuid(qlonglong guid); + +signals: + void guidChanged(); + void infoChanged(); + +private: + FGPositionedRef m_pos; +}; + +bool operator==(const QmlPositioned& p1, const QmlPositioned& p2); + +#endif // QMLPOSITIONED_HXX diff --git a/src/GUI/QtLauncher_fwd.hxx b/src/GUI/QtLauncher_fwd.hxx index cb80f480e..e87172135 100644 --- a/src/GUI/QtLauncher_fwd.hxx +++ b/src/GUI/QtLauncher_fwd.hxx @@ -1,15 +1,11 @@ #ifndef QTGUI_FWD_H #define QTGUI_FWD_H -enum LauncherAircraftType -{ - Airplane = 0, - Seaplane, - Helicopter, - Airship -}; +#include extern QString fixNavaidName(QString s); + + #endif // QTGUI_FWD_H diff --git a/src/GUI/qml/DoubleSpinbox.qml b/src/GUI/qml/DoubleSpinbox.qml index 329615187..dd6f0bbce 100644 --- a/src/GUI/qml/DoubleSpinbox.qml +++ b/src/GUI/qml/DoubleSpinbox.qml @@ -15,6 +15,7 @@ FocusScope { property alias prefix: prefix.text property alias maxDigits: edit.maximumLength property int step: 1 + property bool live: false implicitHeight: editFrame.height // we have a margin between the frame and the label, and on each @@ -25,8 +26,11 @@ FocusScope { function incrementValue() { if (edit.activeFocus) { - value = Math.min(parseFloat(edit.text) + root.step, root.max) - edit.text = value + var newValue = Math.min(parseFloat(edit.text) + root.step, root.max) + edit.text = newValue + if (live) { + commit(newValue); + } } else { commit(Math.min(value + root.step, root.max)) } @@ -35,8 +39,11 @@ FocusScope { function decrementValue() { if (edit.activeFocus) { - value = Math.max(parseFloat(edit.text) - root.step, root.min) - edit.text = value + var newValue = Math.max(parseFloat(edit.text) - root.step, root.min) + edit.text = newValue + if (live) { + commit(newValue); + } } else { commit(Math.max(value - root.step, root.min)) } @@ -75,6 +82,17 @@ FocusScope { } } + // timer to commit the value when in live mode + Timer { + id: liveEditTimer + interval: 800 + onTriggered: { + if (edit.activeFocus) { + commit(parseInt(edit.text)); + } + } + } + Binding { when: !edit.activeFocus target: edit @@ -133,6 +151,13 @@ FocusScope { selectAll(); } else { commit(parseFloat(text)) + liveEditTimer.stop(); + } + } + + onTextChanged: { + if (activeFocus && root.live) { + liveEditTimer.restart(); } } } diff --git a/src/GUI/qml/IntegerSpinbox.qml b/src/GUI/qml/IntegerSpinbox.qml index 63572eae3..8a9b8c742 100644 --- a/src/GUI/qml/IntegerSpinbox.qml +++ b/src/GUI/qml/IntegerSpinbox.qml @@ -13,6 +13,7 @@ FocusScope { property alias prefix: prefix.text property alias maxDigits: edit.maximumLength property int step: 1 + property bool live: false implicitHeight: editFrame.height // we have a margin between the frame and the label, and on each @@ -23,8 +24,11 @@ FocusScope { function incrementValue() { if (edit.activeFocus) { - value = Math.min(parseInt(edit.text) + root.step, root.max) - edit.text = value + var newValue = Math.min(parseInt(edit.text) + root.step, root.max) + edit.text = newValue + if (live) { + commit(newValue); + } } else { commit(Math.min(value + root.step, root.max)) } @@ -33,8 +37,11 @@ FocusScope { function decrementValue() { if (edit.activeFocus) { - value = Math.max(parseInt(edit.text) - root.step, root.min) - edit.text = value + var newValue = Math.max(parseInt(edit.text) - root.step, root.min) + edit.text = newValue + if (live) { + commit(newValue); + } } else { commit(Math.max(value - root.step, root.min)) } @@ -73,6 +80,17 @@ FocusScope { } } + // timer to commit the value when in live mode + Timer { + id: liveEditTimer + interval: 800 + onTriggered: { + if (edit.activeFocus) { + commit(parseInt(edit.text)); + } + } + } + Binding { when: !edit.activeFocus target: edit @@ -131,6 +149,13 @@ FocusScope { selectAll(); } else { commit(parseInt(text)) + liveEditTimer.stop(); + } + } + + onTextChanged: { + if (activeFocus && root.live) { + liveEditTimer.restart(); } } } diff --git a/src/GUI/qml/Location.qml b/src/GUI/qml/Location.qml new file mode 100644 index 000000000..6857bbdd8 --- /dev/null +++ b/src/GUI/qml/Location.qml @@ -0,0 +1,248 @@ +import QtQuick 2.4 +import FlightGear 1.0 +import FlightGear.Launcher 1.0 +import "." + +Item { + id: root + + property bool __searchActive: false + property string lastSearch + + function backToSearch() + { + detailLoader.sourceComponent = null + } + + function selectLocation(guid, type) + { + selectedLocation.guid = guid; + _location.setBaseLocation(selectedLocation) + _location.addToRecent(selectedLocation); + + if (selectedLocation.isAirportType) { + detailLoader.sourceComponent = airportDetails + } else { + detailLoader.sourceComponent = navaidDetails + } + } + + Component.onCompleted: { + _location.showHistoryInSearchModel() + } + + Positioned { + id: selectedLocation + } + + Component { + id: airportDetails + LocationAirportView { + id: airportView + } + } + + + Component { + id: navaidDetails + LocationNavaidView { + id: navaidView + } + } + + Rectangle { + anchors.fill: parent + color: "white" + } + + Component { + id: locationSearchDelegate + Rectangle { + id: delegateRoot + height: delegateContent.height + Style.margin + width: searchView.width + + function itemDescription() + { + if (model.type === Positioned.Fix) return model.ident + + if (model.type === Positioned.VOR) { + var freq = (model.frequency / 100).toFixed(3); + return "%1 - %2 (%3 MHz)".arg(model.ident).arg(model.name).arg(freq); + } + + // general case + return "%1 - %2".arg(model.ident).arg(model.name); + } + + + Item { + id: delegateContent + height: Math.max(delegateIcon.height, delegateText.height) + width: parent.width + + Rectangle { + visible: delegateMouse.containsMouse + color: "#cfcfcf" + } + + PixmapImage { + id: delegateIcon + anchors.left: parent.left + anchors.leftMargin: Style.margin + anchors.verticalCenter: parent.verticalCenter + image: model.icon + } + + Text { + id: delegateText + anchors.right: parent.right + anchors.left: delegateIcon.right + anchors.rightMargin: Style.margin + anchors.leftMargin: Style.margin + anchors.verticalCenter: parent.verticalCenter + text: delegateRoot.itemDescription(); + wrapMode: Text.WordWrap + } + + MouseArea { + id: delegateMouse + anchors.fill: parent + hoverEnabled: true + onClicked: { + root.selectLocation(model.guid, model.type); + } + } + } + + Item { + id: footer + height: Style.margin + width: parent.width + anchors.bottom: parent.bottom + + Rectangle { + color: Style.frameColor + height: 1 + width: parent.width - Style.strutSize + anchors.centerIn: parent + } + } + } + } + + SearchButton { + id: searchButton + + anchors.right: parent.right + anchors.top: parent.top + anchors.left: parent.left + anchors.margins: Style.margin + + autoSubmit: false + placeholder: qsTr("Enter a navaid or airport ID, name or a latitude & longitude"); + + onSearch: { + // when th search term is cleared, show the history + if (term == "") { + _location.showHistoryInSearchModel(); + return; + } + + var geod = _location.parseStringAsGeod(term) + if (geod.valid) { + console.info("REMOVE-ME: Setting lat-lon location") + _location.baseGeod = geod + selectedLocation.guid = 0; + detailLoader.sourceComponent = navaidDetails + return; + } + + root.lastSearch = term; + _location.searchModel.setSearch(term, _launcher.aircraftType) + } + } + + Rectangle { + id: headerSplit + color: Style.frameColor + height: 1 + width: parent.width - Style.inset + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: searchButton.bottom + anchors.topMargin: Style.margin + } + + ListView { + id: searchView + anchors.top: headerSplit.bottom + anchors.topMargin: Style.margin + width: parent.width + anchors.bottom: parent.bottom + model: _location.searchModel + delegate: locationSearchDelegate + clip: true + + header: Item { + visible: _location.searchModel.isSearchActive + width: parent.width + height: visible ? 50 : 0 + + Text { + text: qsTr("Searching") + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.horizontalCenter + } + + AnimatedImage { + source: "qrc://linear-spinner" + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.horizontalCenter + } + + } + + footer: Item { + width: parent.width + height: noResultsText.height + visible: (parent.count === 0) && !_location.searchModel.isSearchActive + Text { + id: noResultsText + width: parent.width + text: qsTr("No results for found search '%1'").arg(root.lastSearch) + wrapMode: Text.WordWrap + } + } + } + + // scrollbar + + Loader { + id: detailLoader + anchors.fill: parent + visible: sourceComponent != null + + onStatusChanged: { + if (status == Loader.Ready) { + if (selectedLocation.valid) { + item.location = selectedLocation.guid + } else { + // lon-lat + item.geod = _location.baseGeod + } + } + } + } + + Button { + anchors { left: parent.left; top: parent.top; margins: Style.margin } + width: Style.strutSize + visible: detailLoader.visible + + id: backButton + text: "< Back" + onClicked: { + root.backToSearch(); + } + } +} diff --git a/src/GUI/qml/LocationAirportView.qml b/src/GUI/qml/LocationAirportView.qml new file mode 100644 index 000000000..7e40cd666 --- /dev/null +++ b/src/GUI/qml/LocationAirportView.qml @@ -0,0 +1,278 @@ +import QtQuick 2.4 +import FlightGear 1.0 +import FlightGear.Launcher 1.0 +import "." + +Item { + id: root + property alias location: airportData.guid + + Positioned { + id: airportData + onGuidChanged: _location.setBaseLocation(this) + } + + readonly property bool isHeliport: airportData.type === Positioned.Heliport + readonly property bool haveParking: airportData.airportHasParkings + + AirportDiagram { + id: diagram + anchors.fill: parent + airport: airportData.guid + + onClicked: { + if (pos === null) + return; + + _location.setDetailLocation(pos) + diagram.selection = pos + syncUIFromController(); + } + + approachExtensionNm: _location.onFinal ? _location.offsetNm : -1.0 + } + + // not very declarative, try to remove this over time + function syncUIFromController() + { + runwayRadio.selected = (_location.detail.isRunwayType); + parkingRadio.selected = (_location.detail.type == Positioned.Parking); + + if (_location.detail.isRunwayType) { + runwayChoice.syncCurrentIndex(); + } else if (_location.detail.type == Positioned.Parking) { + parkingChoice.syncCurrentIndex(); + } + } + + RadioButtonGroup { + id: radioGroup + } + + Component.onCompleted: { + syncUIFromController(); + } + + Rectangle { + id: panel + + color: "transparent" + border.width: 1 + border.color: Style.frameColor + + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + margins: Style.strutSize + } + + height: selectionGrid.height + Style.margin * 2 + + // set opacity here only, so we don't make the whole summary pannel translucent + Rectangle { + id: background + anchors.fill: parent + z: -1 + opacity: Style.panelOpacity + color: "white" + } + + Column { + id: selectionGrid + spacing: Style.margin + width: parent.width + + Text { // heading text + id: airportHeading + width: parent.width + text: isHeliport ? qsTr("Heliport: ") + airportData.ident + " / " + airportData.name + : qsTr("Airport: ") + airportData.ident + " / " + airportData.name + } + + Row { + width: parent.width + spacing: Style.margin + + RadioButton { + id: runwayRadio + anchors.verticalCenter: parent.verticalCenter + group: radioGroup + + onClicked: { + if (selected) runwayChoice.setLocation(); + } + } + + Text { + text: isHeliport ? qsTr("Pad") : qsTr("Runway") + anchors.verticalCenter: parent.verticalCenter + } + + PopupChoice { + id: runwayChoice + model: _location.airportRunways + displayRole: "ident" + width: parent.width * 0.5 + anchors.verticalCenter: parent.verticalCenter + headerText: qsTr("Active") + enabled: runwayRadio.selected + + onCurrentIndexChanged: { + setLocation(); + } + + function setLocation() + { + if (currentIndex == -1) { + _location.useActiveRunway = true; + diagram.selection = null; + } else { + _location.setDetailLocation(_location.airportRunways[currentIndex]) + diagram.selection = _location.airportRunways[currentIndex] + } + } + + function syncCurrentIndex() + { + if (_location.useActiveRunway) { + currentIndex = -1; + return; + } + + for (var i=0; i < _location.airportRunways.length; ++i) { + if (_location.airportRunways[i].equals(_location.detail)) { + currentIndex = i; + return; + } + } + + // not found, default to active + currentIndex = -1; + } + } + } + + // runway offset row + Row { + x: Style.strutSize + + // no offset for helipads + visible: !isHeliport + + ToggleSwitch { + id: onFinalToggle + label: qsTr("On final approach at ") + anchors.verticalCenter: parent.verticalCenter + checked: _location.onFinal + enabled:runwayRadio.selected + onCheckedChanged: _location.onFinal = checked + } + + DoubleSpinbox { + id: offsetNmEdit + value: _location.offsetNm + onCommit: _location.offsetNm = newValue; + + suffix: "Nm" + min: 0.0 + max: 40.0 + decimals: 1 + maxDigits: 5 + live: true + + anchors.verticalCenter: parent.verticalCenter + enabled: runwayRadio.selected && onFinalToggle.checked + } + + Text { + text: qsTr(" from the threshold") + anchors.verticalCenter: parent.verticalCenter + } + } + + ToggleSwitch { + x: Style.strutSize + // no localizer for helipads + visible: !isHeliport + + // enable if selected runway has ILS + label: qsTr("Tune navigation radio (NAV1) to runway localizer") + checked: _location.tuneNAV1 + + onCheckedChanged: { + _location.tuneNAV1 = checked + } + } + + // parking row + Row { + width: parent.width + spacing: Style.margin + + // hide if there's no parking locations defined for this airport + visible: haveParking + + RadioButton { + id: parkingRadio + anchors.verticalCenter: parent.verticalCenter + group: radioGroup + + onClicked: { + if (selected) parkingChoice.setLocation(); + } + } + + Text { + text: qsTr("Parking") + anchors.verticalCenter: parent.verticalCenter + } + + PopupChoice { + id: parkingChoice + model: _location.airportParkings + displayRole: "name" + width: parent.width * 0.5 + anchors.verticalCenter: parent.verticalCenter + headerText: qsTr("Available") + enabled: parkingRadio.selected + + onCurrentIndexChanged: { + setLocation(); + } + + function syncCurrentIndex() + { + if (_location.useAvailableParking) { + currentIndex = -1; + return; + } + + for (var i=0; i < _location.airportParkings.length; ++i) { + if (_location.airportParkings[i].equals(_location.detail)) { + currentIndex = i; + return; + } + } + + // not found, default to available + currentIndex = -1; + } + + function setLocation() + { + if (currentIndex == -1) { + _location.useAvailableParking = true; + diagram.selection = null; + } else { + _location.setDetailLocation(_location.airportParkings[currentIndex]) + diagram.selection = _location.airportParkings[currentIndex] + } + } + } + } + + + } // main layout column + } // main panel rectangle +} diff --git a/src/GUI/qml/LocationNavaidView.qml b/src/GUI/qml/LocationNavaidView.qml new file mode 100644 index 000000000..86f745fa0 --- /dev/null +++ b/src/GUI/qml/LocationNavaidView.qml @@ -0,0 +1,182 @@ +import QtQuick 2.4 +import FlightGear 1.0 +import FlightGear.Launcher 1.0 +import "." + +Item { + property alias location: navaidData.guid + property alias geod: diagram.geod + + Positioned { + id: navaidData + onGuidChanged: { + if (guid > 0) { + diagram.navaid = guid + _location.setBaseLocation(this) + } + } + } + + NavaidDiagram { + id: diagram + anchors.fill: parent + + offsetEnabled: _location.offsetEnabled + offsetBearingDeg: _location.offsetRadial + offsetDistanceNm: _location.offsetNm + headingDeg: _location.headingDeg + } + + Rectangle { + id: panel + + color: "transparent" + border.width: 1 + border.color: Style.frameColor + + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + margins: Style.strutSize + } + + height: selectionGrid.height + Style.margin * 2 + + // set opacity here only, so we don't make the whole summary pannel translucent + Rectangle { + id: background + anchors.fill: parent + z: -1 + opacity: Style.panelOpacity + color: "white" + } + + Column { + id: selectionGrid + spacing: Style.margin + width: parent.width + + Text { // heading text + visible: navaidData.valid + id: heading + width: parent.width + text: "Navaid: " + navaidData.ident + " / " + navaidData.name + } + + Row { + height: childrenRect.height + width: parent.width + spacing: Style.margin + + IntegerSpinbox { + label: qsTr("Airspeed:") + suffix: "kts" + min: 0 + max: 10000 // more for spaceships? + step: 5 + maxDigits: 5 + value: _location.airspeedKnots + onCommit: _location.airspeedKnots = newValue + } + + IntegerSpinbox { + label: qsTr("Heading:") + suffix: "deg" // FIXME use Unicode degree symbol + min: 0 + max: 359 + live: true + maxDigits: 3 + value: _location.headingDeg + onCommit: _location.headingDeg = newValue + } + } + + Row { + height: childrenRect.height + width: parent.width + spacing: Style.margin + + IntegerSpinbox { + label: qsTr("Altitude:") + suffix: "ft" + min: -1000 // Dead Sea, Schiphol + max: 200000 + step: 100 + maxDigits: 6 + + visible: !altitudeTypeChoice.isFlightLevel + value: _location.altitudeFt + onCommit: _location.altitudeFt = newValue + } + + IntegerSpinbox { + label: qsTr("Altitude:") + prefix: "FL" + min: 0 + max: 1000 + step: 10 + maxDigits: 3 + visible: altitudeTypeChoice.isFlightLevel + } + + PopupChoice { + id: altitudeTypeChoice + + readonly property bool isFlightLevel: (currentIndex == 2) + + model: [qsTr("Above mean sea-level (MSL)"), + qsTr("Above ground (AGL)"), + qsTr("Flight-level")] + } + } + + // offset row + Row { + x: Style.strutSize + + ToggleSwitch { + id: offsetToggle + label: qsTr("Offset ") + anchors.verticalCenter: parent.verticalCenter + checked: _location.offsetEnabled + onCheckedChanged: { + _location.offsetEnabled = checked + } + } + + DoubleSpinbox { + id: offsetNmEdit + value: _location.offsetNm + onCommit: _location.offsetNm = newValue + min: 0.0 + max: 40.0 + suffix: "Nm" + maxDigits: 5 + decimals: 1 + live: true + anchors.verticalCenter: parent.verticalCenter + enabled: offsetToggle.checked + } + + Text { + text: qsTr(" on bearing ") + anchors.verticalCenter: parent.verticalCenter + } + + IntegerSpinbox { + id: offsetBearingEdit + suffix: "deg" // FIXME use Unicode degree symbol + min: 0 + max: 359 + maxDigits: 3 + live: true + anchors.verticalCenter: parent.verticalCenter + enabled: offsetToggle.checked + value: _location.offsetRadial + onCommit: _location.offsetRadial = newValue + } + } + } // main layout column + } // main panel rectangle +} diff --git a/src/GUI/qml/Summary.qml b/src/GUI/qml/Summary.qml index 1ad2b98f4..f445478f3 100644 --- a/src/GUI/qml/Summary.qml +++ b/src/GUI/qml/Summary.qml @@ -18,6 +18,13 @@ Item { color: "magenta" } + Connections { + target: _launcher + onAircraftTypeChanged: { + console.info("Aircraft type is now:" + _launcher.aircraftType) + } + } + PreviewImage { id: preview anchors.centerIn: parent @@ -151,7 +158,8 @@ Item { // TODO - make clickable, jump to to the aircraft in the installed // aircraft list Text { - text: _launcher.selectedAircraftInfo.name + text: _launcher.selectedAircraftInfo.name === "" ? + qsTr("No aircraft selected") : _launcher.selectedAircraftInfo.name font.pixelSize: Style.headingFontPixelSize } @@ -265,8 +273,9 @@ Item { // TODO - make clickable, jump to the location page Text { - text: _launcher.locationDescription + text: _launcher.location.description font.pixelSize: Style.headingFontPixelSize + width: summaryGrid.middleColumnWidth } HistoryPopup { diff --git a/src/GUI/resources.qrc b/src/GUI/resources.qrc index 86e063df0..8a6ef1508 100644 --- a/src/GUI/resources.qrc +++ b/src/GUI/resources.qrc @@ -72,6 +72,7 @@ qml/DateTimeValueEdit.qml qml/SettingsDateTimePicker.qml qml/Summary.qml + qml/Location.qml qml/HistoryPopup.qml qml/AddOns.qml qml/DragToReorderButton.qml @@ -86,6 +87,8 @@ qml/PathListDelegate.qml qml/AddCatalogPanel.qml qml/LineEdit.qml + qml/LocationAirportView.qml + qml/LocationNavaidView.qml qml/icons8-linear-spinner.gif qml/RadioButton.qml qml/IntegerSpinbox.qml