From 9f5a4609d6adc4b1929002b7e91ba1ea3a3d0cf8 Mon Sep 17 00:00:00 2001 From: James Turner Date: Fri, 5 Jun 2015 09:26:40 +0100 Subject: [PATCH] Launcher: airport diagram runways can be clicked - indicate selected runway visually --- src/GUI/AirportDiagram.cxx | 122 ++++++++++++++++++++++++++++++++++--- src/GUI/AirportDiagram.hxx | 14 ++++- src/GUI/QtLauncher.cxx | 22 ++++++- src/GUI/QtLauncher.hxx | 2 + 4 files changed, 151 insertions(+), 9 deletions(-) diff --git a/src/GUI/AirportDiagram.cxx b/src/GUI/AirportDiagram.cxx index c460e2878..f347c16fd 100644 --- a/src/GUI/AirportDiagram.cxx +++ b/src/GUI/AirportDiagram.cxx @@ -20,8 +20,12 @@ #include "AirportDiagram.hxx" +#include + #include #include +#include +#include #include #include @@ -40,6 +44,49 @@ static float earth_radius_lat( float lat ) return 1.0f / sqrt( a * a + b * b ); } +static double distanceToLineSegment(const QVector2D& p, const QVector2D& a, + const QVector2D& b, double* outT = NULL) +{ + QVector2D ab(b - a); + QVector2D ac(p - a); + + // Squared length, to avoid a sqrt + const qreal len2 = ab.lengthSquared(); + + // Line null, the projection can't exist, we return the first point + if (qIsNull(len2)) { + if (outT) { + *outT = 0.0; + } + return (p - a).length(); + } + + // Parametric value of the projection on the line + const qreal t = (ac.x() * ab.x() + ac.y() * ab.y()) / len2; + + if (t < 0.0) { + // Point is before the first point + if (outT) { + *outT = 0.0; + } + return (p - a).length(); + } else if (t > 1.0) { + // Point is after the second point + if (outT) { + *outT = 1.0; + } + return (p - b).length(); + } else { + if (outT) { + *outT = t; + } + + const QVector2D proj = a + t * ab; + return (proj - p).length(); + } + + return 0.0; +} AirportDiagram::AirportDiagram(QWidget* pr) : QWidget(pr) @@ -65,6 +112,21 @@ void AirportDiagram::setAirport(FGAirportRef apt) update(); } +FGRunwayRef AirportDiagram::selectedRunway() const +{ + return m_selectedRunway; +} + +void AirportDiagram::setSelectedRunway(FGRunwayRef r) +{ + if (r == m_selectedRunway) { + return; + } + + m_selectedRunway = r; + update(); +} + void AirportDiagram::addRunway(FGRunwayRef rwy) { Q_FOREACH(RunwayData rd, m_runways) { @@ -94,6 +156,22 @@ void AirportDiagram::addParking(FGParking* park) update(); } +QTransform AirportDiagram::transform() const +{ + // fit bounds within our available space, allowing for a margin + const int MARGIN = 32; // pixels + double ratioInX = (width() - MARGIN * 2) / m_bounds.width(); + double ratioInY = (height() - MARGIN * 2) / m_bounds.height(); + double scale = std::min(ratioInX, ratioInY); + + QTransform t; + t.translate(width() / 2, height() / 2); // center projection origin in the widget + t.scale(scale, scale); + // center the bounding box (may not be at the origin) + t.translate(-m_bounds.center().x(), -m_bounds.center().y()); + return t; +} + void AirportDiagram::paintEvent(QPaintEvent* pe) { QPainter p(this); @@ -106,11 +184,7 @@ void AirportDiagram::paintEvent(QPaintEvent* pe) double ratioInY = (height() - MARGIN * 2) / m_bounds.height(); double scale = std::min(ratioInX, ratioInY); - QTransform t; - t.translate(width() / 2, height() / 2); // center projection origin in the widget - t.scale(scale, scale); - // center the bounding box (may not be at the origin) - t.translate(-m_bounds.center().x(), -m_bounds.center().y()); + QTransform t(transform()); p.setTransform(t); // pavements @@ -128,16 +202,22 @@ void AirportDiagram::paintEvent(QPaintEvent* pe) } // runways - QPen pen(Qt::magenta); QFont f; f.setPixelSize(14); p.setFont(f); Q_FOREACH(const RunwayData& r, m_runways) { + QColor color(Qt::magenta); + if ((r.runway == m_selectedRunway) || (r.runway->reciprocalRunway() == m_selectedRunway)) { + color = Qt::yellow; + } + p.setTransform(t); + QPen pen(color); pen.setWidth(r.widthM); p.setPen(pen); + p.drawLine(r.p1, r.p2); // draw idents @@ -147,7 +227,8 @@ void AirportDiagram::paintEvent(QPaintEvent* pe) p.rotate(r.runway->headingDeg()); // invert scaling factor so we can use screen pixel sizes here p.scale(1.0 / scale, 1.0/ scale); - + + p.setPen((r.runway == m_selectedRunway) ? Qt::yellow : Qt::magenta); p.drawText(QRect(-100, 5, 200, 200), ident, Qt::AlignHCenter | Qt::AlignTop); FGRunway* recip = r.runway->reciprocalRunway(); @@ -158,10 +239,37 @@ void AirportDiagram::paintEvent(QPaintEvent* pe) p.rotate(recip->headingDeg()); p.scale(1.0 / scale, 1.0/ scale); + p.setPen((r.runway->reciprocalRunway() == m_selectedRunway) ? Qt::yellow : Qt::magenta); p.drawText(QRect(-100, 5, 200, 200), recipIdent, Qt::AlignHCenter | Qt::AlignTop); } } +void AirportDiagram::mouseReleaseEvent(QMouseEvent* me) +{ + QTransform t(transform()); + double minDist = std::numeric_limits::max(); + FGRunwayRef bestRunway; + + Q_FOREACH(const RunwayData& r, m_runways) { + QPointF p1(t.map(r.p1)), p2(t.map(r.p2)); + double t; + double d = distanceToLineSegment(QVector2D(me->pos()), + QVector2D(p1), + QVector2D(p2), &t); + if (d < minDist) { + if (t > 0.5) { + bestRunway = r.runway->reciprocalRunway(); + } else { + bestRunway = r.runway; + } + minDist = d; + } + } + + if (minDist < 16.0) { + emit clickedRunway(bestRunway); + } +} void AirportDiagram::extendBounds(const QPointF& p) { diff --git a/src/GUI/AirportDiagram.hxx b/src/GUI/AirportDiagram.hxx index 7343bb24c..07eaf8297 100644 --- a/src/GUI/AirportDiagram.hxx +++ b/src/GUI/AirportDiagram.hxx @@ -26,6 +26,7 @@ class AirportDiagram : public QWidget { + Q_OBJECT public: AirportDiagram(QWidget* pr); @@ -33,17 +34,26 @@ public: void addRunway(FGRunwayRef rwy); void addParking(FGParking* park); + + FGRunwayRef selectedRunway() const; + void setSelectedRunway(FGRunwayRef r); +Q_SIGNALS: + void clickedRunway(FGRunwayRef rwy); + protected: virtual void paintEvent(QPaintEvent* pe); // wheel event for zoom // mouse drag for pan + + virtual void mouseReleaseEvent(QMouseEvent* me); private: void extendBounds(const QPointF& p); QPointF project(const SGGeod& geod) const; - + QTransform transform() const; + void buildTaxiways(); void buildPavements(); @@ -72,4 +82,6 @@ private: QList m_taxiways; QList m_pavements; + + FGRunwayRef m_selectedRunway; }; diff --git a/src/GUI/QtLauncher.cxx b/src/GUI/QtLauncher.cxx index a88c099a7..50b6dea07 100644 --- a/src/GUI/QtLauncher.cxx +++ b/src/GUI/QtLauncher.cxx @@ -443,6 +443,9 @@ QtLauncher::QtLauncher() : connect(m_ui->onFinalCheckbox, SIGNAL(toggled(bool)), this, SLOT(updateAirportDescription())); + + connect(m_ui->airportDiagram, &AirportDiagram::clickedRunway, + this, &QtLauncher::onAirportDiagramClicked); connect(m_ui->runButton, SIGNAL(clicked()), this, SLOT(onRun())); connect(m_ui->quitButton, SIGNAL(clicked()), this, SLOT(onQuit())); @@ -851,6 +854,17 @@ void QtLauncher::onAirportChanged() } } +void QtLauncher::onAirportDiagramClicked(FGRunwayRef rwy) +{ + if (rwy) { + m_ui->runwayRadio->setChecked(true); + int rwyIndex = m_ui->runwayCombo->findText(QString::fromStdString(rwy->ident())); + m_ui->runwayCombo->setCurrentIndex(rwyIndex); + } + + updateAirportDescription(); +} + void QtLauncher::onToggleTerrasync(bool enabled) { if (enabled) { @@ -900,7 +914,8 @@ void QtLauncher::updateAirportDescription() QString locationOnAirport; if (m_ui->runwayRadio->isChecked()) { bool onFinal = m_ui->onFinalCheckbox->isChecked(); - QString runwayName = (m_ui->runwayCombo->currentIndex() == 0) ? + int comboIndex = m_ui->runwayCombo->currentIndex(); + QString runwayName = (comboIndex == 0) ? "active runway" : QString("runway %1").arg(m_ui->runwayCombo->currentText()); @@ -909,6 +924,11 @@ void QtLauncher::updateAirportDescription() } else { locationOnAirport = QString("on %1").arg(runwayName); } + + int runwayIndex = m_ui->runwayCombo->itemData(comboIndex).toInt(); + FGRunwayRef rwy = (runwayIndex >= 0) ? + m_selectedAirport->getRunwayByIndex(runwayIndex) : FGRunwayRef(); + m_ui->airportDiagram->setSelectedRunway(rwy); } else if (m_ui->parkingRadio->isChecked()) { locationOnAirport = QString("at parking position %1").arg(m_ui->parkingCombo->currentText()); } diff --git a/src/GUI/QtLauncher.hxx b/src/GUI/QtLauncher.hxx index d705c82f0..6b6615af1 100644 --- a/src/GUI/QtLauncher.hxx +++ b/src/GUI/QtLauncher.hxx @@ -79,6 +79,8 @@ private slots: void onSubsytemIdleTimeout(); void onEditPaths(); + + void onAirportDiagramClicked(FGRunwayRef rwy); private: void setAirport(FGAirportRef ref); void updateSelectedAircraft(); -- 2.39.5