]> git.mxchange.org Git - flightgear.git/commitdiff
Label de-overlapping for diagrams
authorJames Turner <zakalawe@mac.com>
Sun, 15 Nov 2015 13:53:35 +0000 (13:53 +0000)
committerJames Turner <zakalawe@mac.com>
Mon, 23 Nov 2015 00:48:20 +0000 (00:48 +0000)
src/GUI/BaseDiagram.cxx
src/GUI/BaseDiagram.hxx
src/GUI/LocationWidget.cxx

index 4c301a20ed86151d54d42e00faa8f1857e32568e..663f54cb0ae5d1f763c25dbb7c286011b984e204 100644 (file)
@@ -168,10 +168,12 @@ void BaseDiagram::paintNavaids(QPainter* painter)
     double drawRangeNm = SGGeodesy::distanceNm(m_projectionCenter, topLeft) + 10.0;
     //qDebug() << "draw range computed as:" << drawRangeNm;
 
+
     MapFilter f;
     FGPositionedList items = FGPositioned::findWithinRange(m_projectionCenter, drawRangeNm, &f);
 
-    // pass 0 - icons
+    m_labelRects.clear();
+    m_labelRects.reserve(items.size());
 
     FGPositionedList::const_iterator it;
     for (it = items.begin(); it != items.end(); ++it) {
@@ -204,14 +206,13 @@ void BaseDiagram::paintNavaids(QPainter* painter)
         }
 
         if (drawAsIcon) {
-            QPixmap pm = iconForPositioned(pos, false);
+            QPixmap pm = iconForPositioned(pos);
             QPointF loc = xf.map(project(pos->geod()));
+            QRect iconRect = pm.rect();
+            iconRect.moveCenter(loc.toPoint());
+            painter->drawPixmap(iconRect, pm);
 
-            QPointF iconLoc = loc - QPointF(pm.width() >> 1, pm.height() >> 1);
-            painter->drawPixmap(iconLoc, pm);
-
-            painter->setPen(QColor(0x03, 0x83, 0xbf));
-
+       // compute label text so we can measure it
             QString label;
             if (FGAirport::isAirportType(pos.ptr())) {
                 label = QString::fromStdString((*it)->name());
@@ -227,9 +228,15 @@ void BaseDiagram::paintNavaids(QPainter* painter)
                 label.append("\n").append(QString::number(nav->get_freq() / 100.0, 'f', 1));
             }
 
-            QRect labelBox(loc.x() + (pm.width()/2) + 4, loc.y() - 50, 100, 100);
-            painter->drawText(labelBox, Qt::AlignVCenter | Qt::AlignLeft | Qt::TextWordWrap,
-                              label);
+            QRect textBounds = painter->boundingRect(QRect(0, 0, 100, 100),
+                                                     Qt::TextWordWrap, label);
+            int textFlags;
+            textBounds = rectAndFlagsForLabel(pos->guid(), iconRect,
+                                              textBounds.size(),
+                                              textFlags);
+
+            painter->setPen(QColor(0x03, 0x83, 0xbf));
+            painter->drawText(textBounds, textFlags, label);
         }
     }
 
@@ -242,6 +249,102 @@ bool BaseDiagram::isNavaidIgnored(const FGPositionedRef &pos) const
     return m_ignored.contains(pos);
 }
 
+bool BaseDiagram::isLabelRectAvailable(const QRect &r) const
+{
+    Q_FOREACH(const QRect& lr, m_labelRects) {
+        if (lr.intersects(r))
+            return false;
+    }
+
+    return true;
+}
+
+int BaseDiagram::textFlagsForLabelPosition(LabelPosition pos)
+{
+#if 0
+    switch (pos) {
+    case LABEL_RIGHT:       return Qt::AlignLeft | Qt::AlignVCenter;
+    case LABEL_ABOVE:       return Qt::AlignHCenter | Qt::A
+    }
+#endif
+    return 0;
+}
+
+QRect BaseDiagram::rectAndFlagsForLabel(PositionedID guid, const QRect& item,
+                                        const QSize &bounds,
+                                        int& flags) const
+{
+    m_labelRects.append(item);
+    int pos = m_labelPositions.value(guid, LABEL_RIGHT);
+    bool firstAttempt = true;
+    flags = Qt::TextWordWrap;
+
+    while (pos < LAST_POSITION) {
+        QRect r = labelPositioned(item, bounds, static_cast<LabelPosition>(pos));
+        if (isLabelRectAvailable(r)) {
+            m_labelRects.append(r);
+            m_labelPositions[guid] = static_cast<LabelPosition>(pos);
+            flags |= textFlagsForLabelPosition(static_cast<LabelPosition>(pos));
+            return r;
+        } else if (firstAttempt && (pos != LABEL_RIGHT)) {
+            pos = LABEL_RIGHT;
+        } else {
+            ++pos;
+        }
+
+        firstAttempt = false;
+    }
+
+    return QRect(item.x(), item.y(), bounds.width(), bounds.height());
+}
+
+QRect BaseDiagram::labelPositioned(const QRect& itemRect,
+                                   const QSize& bounds,
+                                   LabelPosition lp) const
+{
+    const int SHORT_MARGIN = 4;
+    const int DIAGONAL_MARGIN = 20;
+
+    switch (lp) {
+    // cardinal compass points are short (close in)
+    case LABEL_RIGHT:
+        return QRect(itemRect.right() + SHORT_MARGIN,
+                     itemRect.center().y() - bounds.height() / 2,
+                     bounds.width(),
+                     bounds.height());
+    case LABEL_ABOVE:
+        return QRect(itemRect.center().x() - (bounds.width() / 2),
+                     itemRect.top() - (SHORT_MARGIN + bounds.height()),
+                     bounds.width(),
+                     bounds.height());
+    case LABEL_BELOW:
+        return QRect(itemRect.center().x() - (bounds.width() / 2),
+                     itemRect.bottom() + SHORT_MARGIN,
+                     bounds.width(),
+                     bounds.height());
+    case LABEL_LEFT:
+        return QRect(itemRect.left() - (SHORT_MARGIN + bounds.width()),
+                     itemRect.center().y() - bounds.height() / 2,
+                     bounds.width(),
+                     bounds.height());
+
+    // first diagonals are further out (to hopefully have a better chance
+    // of finding clear space
+
+    case LABEL_NE:
+        return QRect(itemRect.right() + DIAGONAL_MARGIN,
+                     itemRect.top() - (DIAGONAL_MARGIN + bounds.height()),
+                     bounds.width(),
+                     bounds.height());
+
+    default:
+        qWarning() << Q_FUNC_INFO << "Implement me";
+
+    }
+
+    return QRect(itemRect.x(), itemRect.y(), bounds.width(), bounds.height());
+}
+
 void BaseDiagram::mousePressEvent(QMouseEvent *me)
 {
     m_lastMousePos = me->pos();
@@ -394,9 +497,11 @@ QPointF BaseDiagram::project(const SGGeod& geod) const
     return project(geod, m_projectionCenter);
 }
 
-QPixmap BaseDiagram::iconForPositioned(const FGPositionedRef& pos, bool small)
+QPixmap BaseDiagram::iconForPositioned(const FGPositionedRef& pos,
+                                       const IconOptions& options)
 {
     // if airport type, check towered or untowered
+    bool small = options.testFlag(SmallIcons);
 
     bool isTowered = false;
     if (FGAirport::isAirportType(pos)) {
@@ -415,7 +520,7 @@ QPixmap BaseDiagram::iconForPositioned(const FGPositionedRef& pos, bool small)
         return QPixmap(":/vor-icon");
 
     case FGPositioned::AIRPORT:
-        return iconForAirport(static_cast<FGAirport*>(pos.ptr()));
+        return iconForAirport(static_cast<FGAirport*>(pos.ptr()), options);
 
     case FGPositioned::HELIPORT:
         return QPixmap(":/heliport-icon");
@@ -433,13 +538,13 @@ QPixmap BaseDiagram::iconForPositioned(const FGPositionedRef& pos, bool small)
     return QPixmap();
 }
 
-QPixmap BaseDiagram::iconForAirport(FGAirport* apt)
+QPixmap BaseDiagram::iconForAirport(FGAirport* apt, const IconOptions& options)
 {
     if (!apt->hasHardRunwayOfLengthFt(1500)) {
         return QPixmap(apt->hasTower() ? ":/airport-tower-icon" : ":/airport-icon");
     }
 
-    if (apt->hasHardRunwayOfLengthFt(8500)) {
+    if (options.testFlag(LargeAirportPlans) && apt->hasHardRunwayOfLengthFt(8500)) {
         QPixmap result(32, 32);
         result.fill(Qt::transparent);
         {
index 5bbd4bf83cce2f38c86e8bdcdf8dcd90d4830434..ce7291a6016a3b11fa84c3c760306b32c20dfb95 100644 (file)
@@ -23,6 +23,7 @@
 
 #include <QWidget>
 #include <QPainterPath>
+#include <QHash>
 
 #include <simgear/math/sg_geodesy.hxx>
 
@@ -35,8 +36,17 @@ class BaseDiagram : public QWidget
 public:
     BaseDiagram(QWidget* pr);
 
-    static QPixmap iconForPositioned(const FGPositionedRef &pos, bool small);
-    static QPixmap iconForAirport(FGAirport *apt);
+    enum IconOption
+    {
+        NoOptions = 0,
+        SmallIcons = 0x1,
+        LargeAirportPlans = 0x2
+    };
+
+    Q_DECLARE_FLAGS(IconOptions, IconOption)
+
+    static QPixmap iconForPositioned(const FGPositionedRef &pos, const IconOptions& options = NoOptions);
+    static QPixmap iconForAirport(FGAirport *apt, const IconOptions& options = NoOptions);
 
     static QVector<QLineF> projectAirportRuwaysIntoRect(FGAirportRef apt, const QRectF& bounds);
     static QVector<QLineF> projectAirportRuwaysWithCenter(FGAirportRef apt, const SGGeod &c);
@@ -79,11 +89,37 @@ protected:
 
     void paintAirplaneIcon(QPainter *painter, const SGGeod &geod, int headingDeg);
 private:
+    enum LabelPosition
+    {
+        LABEL_RIGHT = 0,
+        LABEL_ABOVE,
+        LABEL_BELOW,
+        LABEL_LEFT,
+        LABEL_NE,
+        LABEL_SE,
+        LABEL_SW,
+        LABEL_NW,
+        LAST_POSITION // marker value
+    };
+
     void paintNavaids(QPainter *p);
 
     bool isNavaidIgnored(const FGPositionedRef& pos) const;
 
+    bool isLabelRectAvailable(const QRect& r) const;
+    QRect rectAndFlagsForLabel(PositionedID guid, const QRect &item,
+                               const QSize &bounds,
+                               int & flags /* out parameter */) const;
+    QRect labelPositioned(const QRect &itemRect, const QSize &bounds, LabelPosition lp) const;
+
     QVector<FGPositionedRef> m_ignored;
+
+    mutable QHash<PositionedID, LabelPosition> m_labelPositions;
+    mutable QVector<QRect> m_labelRects;
+
+    static int textFlagsForLabelPosition(LabelPosition pos);
 };
 
+Q_DECLARE_OPERATORS_FOR_FLAGS(BaseDiagram::IconOptions)
+
 #endif // of GUI_BASEDIAGRAM_HXX
index 5eddbac8ae9743da855f0b511198bf8af59a2656..9e41f07e6d6f9e29f56228c0167eb713510b248f 100644 (file)
@@ -243,7 +243,8 @@ public:
         }
 
         if (role == Qt::DecorationRole) {
-            return AirportDiagram::iconForPositioned(pos, true);
+            return AirportDiagram::iconForPositioned(pos,
+                                                     AirportDiagram::SmallIcons | AirportDiagram::LargeAirportPlans);
         }
 
         if (role == Qt::EditRole) {