]> git.mxchange.org Git - flightgear.git/blobdiff - src/GUI/WaypointList.cxx
Launcher: Maintain aircraft selection better
[flightgear.git] / src / GUI / WaypointList.cxx
index 0ad02a30dd1549d9ce9f2256b87f1097a2e399c0..5b010f0d2690a7e6a9b14fce8dc0df9149b6510a 100644 (file)
 #include "WaypointList.hxx"
 
 #include <algorithm>
+#include <boost/tuple/tuple.hpp>
+
 #include <plib/puAux.h>
 
-#include <simgear/route/waypoint.hxx>
 #include <simgear/structure/callback.hxx>
 #include <simgear/sg_inlines.h>
 
 #include <Main/globals.hxx>
 #include <Main/fg_props.hxx>
 
+#include <Navaids/positioned.hxx>
+#include <Navaids/routePath.hxx>
 #include <Autopilot/route_mgr.hxx>
 
+// select if the widget grabs keys necessary to fly aircraft from the keyboard,
+// or not. See http://code.google.com/p/flightgear-bugs/issues/detail?id=338
+// for discussion about why / what is going on.
+#define AVOID_FLIGHT_KEYS 1
+
+using namespace flightgear;
+
 enum {
   SCROLL_NO = 0,
   SCROLL_UP,
   SCROLL_DOWN
 };
   
+static const double BLINK_TIME = 0.3;
 static const int DRAG_START_DISTANCE_PX = 5;
   
-class RouteManagerWaypointModel : 
+class FlightPlanWaypointModel : 
   public WaypointList::Model, 
   public SGPropertyChangeListener
 {
 public:
-  RouteManagerWaypointModel()
-  {
-    _rm = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
-    
+  FlightPlanWaypointModel(flightgear::FlightPlan* fp) :
+    _fp(fp)
+  {    
     SGPropertyNode* routeEdited = fgGetNode("/autopilot/route-manager/signals/edited", true);
+    SGPropertyNode* flightplanChanged = fgGetNode("/autopilot/route-manager/signals/flightplan-changed", true);
     routeEdited->addChangeListener(this);
+    flightplanChanged->addChangeListener(this);
   }
   
-  virtual ~RouteManagerWaypointModel()
+  ~FlightPlanWaypointModel()
   {
     SGPropertyNode* routeEdited = fgGetNode("/autopilot/route-manager/signals/edited", true);
+    SGPropertyNode* flightplanChanged = fgGetNode("/autopilot/route-manager/signals/flightplan-changed", true);
     routeEdited->removeChangeListener(this);
+    flightplanChanged->removeChangeListener(this);
   }
   
 // implement WaypointList::Model
   virtual unsigned int numWaypoints() const
   {
-    return _rm->size();
+    return _fp->numLegs();
   }
   
   virtual int currentWaypoint() const
   {
-    return _rm->currentWaypoint();
+    return _fp->currentIndex();
   }
   
-  virtual SGWayPoint waypointAt(unsigned int index) const
+  virtual flightgear::Waypt* waypointAt(unsigned int index) const
   {
-    return _rm->get_waypoint(index);
+    if (index >= numWaypoints()) {
+      return NULL;
+    }
+    
+    return _fp->legAtIndex(index)->waypoint();
   }
 
-  virtual void deleteAt(unsigned int index)
+  virtual flightgear::FlightPlan* flightplan() const
   {
-    _rm->pop_waypoint(index);
+    return _fp;
   }
   
-  virtual void setWaypointTargetAltitudeFt(unsigned int index, int altFt)
+  virtual void deleteAt(unsigned int index)
   {
-    _rm->setWaypointTargetAltitudeFt(index, altFt);
+    _fp->deleteIndex(index);
   }
   
   virtual void moveWaypointToIndex(unsigned int srcIndex, unsigned int destIndex)
   {
+    SG_LOG(SG_GENERAL, SG_INFO, "moveWaypoint: from " << srcIndex << " to " << destIndex);
     if (destIndex > srcIndex) {
       --destIndex;
     }
     
-    SGWayPoint wp = _rm->pop_waypoint(srcIndex);
-    _rm->add_waypoint(wp, destIndex);
+     int currentWpIndex = currentWaypoint();
+    
+    WayptRef w = _fp->legAtIndex(srcIndex)->waypoint();
+    _fp->deleteIndex(srcIndex);
+    _fp->insertWayptAtIndex(w, destIndex);
+
+    if ((signed int) srcIndex == currentWpIndex) {
+        // current waypoint was moved
+        _fp->setCurrentIndex(destIndex);
+    }
   }
   
   virtual void setUpdateCallback(SGCallback* cb)
@@ -89,12 +116,19 @@ public:
 // implement SGPropertyChangeListener
   void valueChanged(SGPropertyNode *prop)
   {
-    if (_cb) {
-      (*_cb)();
+    if (prop->getNameString() == "edited") {
+      if (_cb) {
+        (*_cb)();
+      }
+    }
+    
+    if (prop->getNameString() == "flightplan-changed") {
+      _fp = 
+        static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"))->flightPlan();
     }
   }
 private:
-  FGRouteMgr* _rm;
+  flightgear::FlightPlan* _fp;
   SGCallback* _cb;
 };
 
@@ -108,15 +142,14 @@ static void drawClippedString(puFont& font, const char* s, int x, int y, int max
     return;
   }
   
-  int len = strlen(s);
-  char buf[len];
-  memcpy(buf, s, len);
+  std::string buf(s);
+  int len = buf.size();
   do {
-    buf[--len] = 0;
-    fullWidth = font.getStringWidth(buf);
+    buf.resize(--len);
+    fullWidth = font.getStringWidth(buf.c_str());
   } while (fullWidth > maxWidth);
   
-  font.drawString(buf, x, y);
+  font.drawString(buf.c_str(), x, y);
 }
 
 //////////////////////////////////////////////////////////////////////////////
@@ -130,13 +163,18 @@ WaypointList::WaypointList(int x, int y, int width, int height) :
   _showLatLon(false),
   _model(NULL),
   _updateCallback(NULL),
-  _scrollCallback(NULL)
+  _scrollCallback(NULL),
+  _blink(false)
 {
   // pretend to be a list, so fgPopup doesn't mess with our mouse events
   type |= PUCLASS_LIST;  
-  setModel(new RouteManagerWaypointModel());
+  flightgear::FlightPlan* fp = 
+    static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"))->flightPlan();
+  setModel(new FlightPlanWaypointModel(fp));
   setSize(width, height);
   setValue(-1);
+  
+  _blinkTimer.stamp();
 }
 
 WaypointList::~WaypointList()
@@ -231,6 +269,11 @@ void WaypointList::handleDrag(int x, int y)
     }
     
     _dragSourceRow = rowForY(y - abox.min[1]);
+    Waypt* wp = _model->waypointAt(_dragSourceRow);
+    if (!wp || wp->flag(WPT_GENERATED) || (wp->type() == "discontinuity")) {
+      return; // don't allow generated points to be dragged
+    }
+    
     _dragging = true;
     _dragScroll = SCROLL_NO;
   }
@@ -256,20 +299,26 @@ void WaypointList::doDrop(int x, int y)
   _dragging = false;
   puDeactivateWidget();
   
+  SG_LOG(SG_GENERAL, SG_INFO, "doDrop");
+  
   if ((y < abox.min[1]) || (y >= abox.max[1])) {
+    SG_LOG(SG_GENERAL, SG_INFO, "y out of bounds:" << y);
     return;
   }
   
-  if (_dragSourceRow != _dragTargetRow) {
-    _model->moveWaypointToIndex(_dragSourceRow, _dragTargetRow);
-    
-    // keep row indexes linged up when moving an item down the list
-    if (_dragSourceRow < _dragTargetRow) {
-      --_dragTargetRow;
-    }
-    
-    setSelected(_dragTargetRow);
+  if (_dragSourceRow == _dragTargetRow) {
+    SG_LOG(SG_GENERAL, SG_INFO, "source and target row match");
+    return;
   }
+  
+  _model->moveWaypointToIndex(_dragSourceRow, _dragTargetRow);
+  
+  // keep row indexes linged up when moving an item down the list
+  if (_dragSourceRow < _dragTargetRow) {
+    --_dragTargetRow;
+  }
+  
+  setSelected(_dragTargetRow);
 }
 
 void WaypointList::invokeDownCallback(void)
@@ -303,6 +352,12 @@ void WaypointList::draw( int dx, int dy )
     doDragScroll();
   }
   
+  double dt = (SGTimeStamp::now() - _blinkTimer).toSecs();
+  if (dt > BLINK_TIME) {
+    _blinkTimer.stamp();
+    _blink = !_blink;
+  }
+  
   glEnable(GL_SCISSOR_TEST);
   GLint sx = (int) abox.min[0],
     sy = abox.min[1];
@@ -322,8 +377,12 @@ void WaypointList::draw( int dx, int dy )
   
   y -= (_scrollPx % rowHeight); // partially draw the first row
   
+  _arrowWidth = legendFont.getStringWidth(">");
+  
+  RoutePath path(_model->flightplan());
+  
   for ( ; row <= final; ++row, y += rowHeight) {
-    drawRow(dx, dy, row, y);
+    drawRow(dx, dy, row, y, path);
   } // of row drawing iteration
   
   glDisable(GL_SCISSOR_TEST);
@@ -342,72 +401,146 @@ void WaypointList::draw( int dx, int dy )
   }
 }
 
-void WaypointList::drawRow(int dx, int dy, int rowIndex, int y)
+void WaypointList::drawRow(int dx, int dy, int rowIndex, int y,
+                           const RoutePath& path)
 {
+  flightgear::Waypt* wp(_model->waypointAt(rowIndex));
+    
   bool isSelected = (rowIndex == getSelected());
   bool isCurrent = (rowIndex == _model->currentWaypoint());
   bool isDragSource = (_dragging && (rowIndex == _dragSourceRow));
-  
+
   puBox bkgBox = abox;
   bkgBox.min[1] = abox.max[1] - y;
   bkgBox.max[1] = bkgBox.min[1] + rowHeightPx();
   
-  puColour currentColor;
-  puSetColor(currentColor, 1.0, 1.0, 0.0, 0.5);
+  puColour col;
+  puFont* f = &legendFont;
+  bool drawBox = false;
+  
+  if (wp->flag(WPT_MISS)) {
+    drawBox = true;
+    puSetColor(col, 1.0, 0.0, 0.0, 0.3);  // red
+  } else if (wp->flag(WPT_ARRIVAL) || wp->flag(WPT_DEPARTURE)) {
+    drawBox = true;
+    puSetColor(col, 0.0, 0.0, 0.0, 0.3);
+  } else if (wp->flag(WPT_APPROACH)) {
+    drawBox = true;
+    puSetColor(col, 0.0, 0.0, 0.1, 0.3);
+  }
   
   if (isDragSource) {
     // draw later, on *top* of text string
-  } else  if (isCurrent) {
-    bkgBox.draw(dx, dy, PUSTYLE_PLAIN, &currentColor, false, 0);
   } else if (isSelected) { // -PLAIN means selected, apparently
     bkgBox.draw(dx, dy, -PUSTYLE_PLAIN, colour, false, 0);
+  } else if (drawBox) {
+    bkgBox.draw(dx, dy, PUSTYLE_PLAIN, &col, false, 0);
+  }
+  
+  if (isCurrent) {
+    glColor4f (1.0, 0.5, 0.0, 1.0) ;
+  } else {
+    glColor4fv ( colour [ PUCOL_LEGEND ] ) ;
   }
   
   int xx = dx + abox.min[0] + PUSTR_LGAP;
   int yy = dy + abox.max[1] - y ;
   yy += 4; // center text in row height
   
-  // row textual data
-  const SGWayPoint wp(_model->waypointAt(rowIndex));
-  char buffer[128];
-  int count = ::snprintf(buffer, 128, "%03d   %-5s", rowIndex, wp.get_id().c_str());
-  
-  if (wp.get_name().size() > 0 && (wp.get_name() != wp.get_id())) { 
-    // append name if present, and different to id
-    ::snprintf(buffer + count, 128 - count, " (%s)", wp.get_name().c_str());
+  if (isCurrent) {
+    f->drawString(">", xx, yy);
   }
   
-  glColor4fv ( colour [ PUCOL_LEGEND ] ) ;
-  drawClippedString(legendFont, buffer, xx, yy, 300);
-  
-  if (_showLatLon) {
-    char ns = (wp.get_target_lat() > 0.0) ? 'N' : 'S';
-    char ew = (wp.get_target_lon() > 0.0) ? 'E' : 'W';
-    
-    ::snprintf(buffer, 128 - count, "%4.2f%c %4.2f%c",
-      fabs(wp.get_target_lon()), ew, fabs(wp.get_target_lat()), ns);
-  } else {
-    ::snprintf(buffer, 128 - count, "%03.0f %5.1fnm",
-      wp.get_track(), wp.get_distance() * SG_METER_TO_NM);
+  int x = xx;
+  x += _arrowWidth + PUSTR_LGAP;
+    drawRowText(x, yy, rowIndex, path);
+
+
+  if (isDragSource) {
+    puSetColor(col, 1.0, 0.5, 0.0, 0.5);
+    bkgBox.draw(dx, dy, PUSTYLE_PLAIN, &col, false, 0);
   }
+}
 
-  legendFont.drawString(buffer, xx + 300 + PUSTR_LGAP, yy);
-  
-  int altFt = (int) wp.get_target_alt() * SG_METER_TO_FEET;
-  if (altFt > -9990) {
-    int altHundredFt = (altFt + 50) / 100; // round to nearest 100ft
+void WaypointList::drawRowText(int x, int baseline, int rowIndex, const RoutePath& path)
+{
+    flightgear::Waypt* wp(_model->waypointAt(rowIndex));
+    const bool isDiscontinuity = (wp->type() == "discontinuity");
+    const bool isVia = (wp->type() == "via");
+
+    char buffer[128];
+    int count;
+
+    if (isVia) {
+        // VIA has long ident but no name
+        count = ::snprintf(buffer, 128, "%03d   %s", rowIndex, wp->ident().c_str());
+        drawClippedString(legendFont, buffer, x, baseline, 300);
+        x += 300 + PUSTR_LGAP;
+    } else {
+        count = ::snprintf(buffer, 128, "%03d   %-5s", rowIndex, wp->ident().c_str());
+
+        FGPositioned* src = wp->source();
+        if (src && !src->name().empty() && (src->name() != wp->ident())) {
+          // append name if present, and different to id
+          ::snprintf(buffer + count, 128 - count, " (%s)", src->name().c_str());
+        }
+
+        drawClippedString(legendFont, buffer, x, baseline, 300);
+        x += 300 + PUSTR_LGAP;
+
+        if (isDiscontinuity) {
+            return;
+        }
+
+        if (_showLatLon) {
+          // only show for non-dynamic waypoints
+          if (!wp->flag(WPT_DYNAMIC)) {
+            SGGeod p(wp->position());
+            char ns = (p.getLatitudeDeg() > 0.0) ? 'N' : 'S';
+            char ew = (p.getLongitudeDeg() > 0.0) ? 'E' : 'W';
+
+            ::snprintf(buffer, 128 - count, "%4.2f%c %4.2f%c",
+              fabs(p.getLongitudeDeg()), ew, fabs(p.getLatitudeDeg()), ns);
+          } else {
+            buffer[0] = 0;
+          }
+        } else if (rowIndex > 0) {
+          double courseDeg = path.trackForIndex(rowIndex);
+          double distanceM = path.distanceForIndex(rowIndex);
+          ::snprintf(buffer, 128 - count, "%03.0f %5.1fnm",
+            courseDeg, distanceM * SG_METER_TO_NM);
+        }
+    } // of is not a VIA waypoint
+
+    puFont* f = &legendFont;
+  f->drawString(buffer, x, baseline);
+  x += 100 + PUSTR_LGAP;
+
+  if (wp->altitudeRestriction() != RESTRICT_NONE) {
+    char aboveAtBelow = ' ';
+    if (wp->altitudeRestriction() == RESTRICT_ABOVE) {
+      aboveAtBelow = 'A';
+    } else if (wp->altitudeRestriction() == RESTRICT_BELOW) {
+      aboveAtBelow = 'B';
+    }
+
+    int altHundredFt = (wp->altitudeFt() + 50) / 100; // round to nearest 100ft
     if (altHundredFt < 100) {
-      count = ::snprintf(buffer, 128, "%d'", altHundredFt * 100);
+      count = ::snprintf(buffer, 128, "%d'%c", altHundredFt * 100, aboveAtBelow);
     } else { // display as a flight-level
-      count = ::snprintf(buffer, 128, "FL%d", altHundredFt);
+      count = ::snprintf(buffer, 128, "FL%d%c", altHundredFt, aboveAtBelow);
     }
-    
-    legendFont.drawString(buffer, xx + 400 + PUSTR_LGAP, yy);
+
+    f->drawString(buffer, x, baseline);
   } // of valid wp altitude
-  
-  if (isDragSource) {
-    puSetColor(currentColor, 1.0, 0.5, 0.0, 0.5);
-    bkgBox.draw(dx, dy, PUSTYLE_PLAIN, &currentColor, false, 0);
+  x += 60 + PUSTR_LGAP;
+
+  if (wp->speedRestriction() == SPEED_RESTRICT_MACH) {
+    count = ::snprintf(buffer, 126, "%03.2fM", wp->speedMach());
+    f->drawString(buffer, x, baseline);
+  } else if (wp->speedRestriction() != RESTRICT_NONE) {
+    count = ::snprintf(buffer, 126, "%dKts", (int) wp->speedKts());
+    f->drawString(buffer, x, baseline);
   }
 }
 
@@ -583,6 +716,10 @@ int WaypointList::checkKey (int key, int updown )
   if ((updown == PU_UP) || !isVisible () || !isActive () || (window != puGetWindow())) {
     return FALSE ;
   }
+
+#ifdef AVOID_FLIGHT_KEYS
+    return FALSE;
+#endif
   
   switch (key)
   {
@@ -614,22 +751,34 @@ int WaypointList::checkKey (int key, int updown )
   
   case '-':
     if (getSelected() >= 0) {
-      int newAlt = wayptAltFtHundreds(getSelected()) - 10;
-      if (newAlt < 0) {
-        _model->setWaypointTargetAltitudeFt(getSelected(), -9999);
-      } else {
-        _model->setWaypointTargetAltitudeFt(getSelected(), newAlt * 100);
+      Waypt* wp = _model->waypointAt(getSelected());
+      if (wp->flag(WPT_GENERATED)) {
+        break;
+      }
+      
+      if (wp->altitudeRestriction() != RESTRICT_NONE) {
+        int curAlt = (static_cast<int>(wp->altitudeFt()) + 50) / 100;
+        if (curAlt <= 0) {
+          wp->setAltitude(0, RESTRICT_NONE);
+        } else {
+          wp->setAltitude((curAlt - 10) * 100, wp->altitudeRestriction());
+        }
       }
     }
     break;
     
   case '=':
     if (getSelected() >= 0) {
-      int newAlt = wayptAltFtHundreds(getSelected()) + 10;
-      if (newAlt < 0) {
-        _model->setWaypointTargetAltitudeFt(getSelected(), 0);
+      flightgear::Waypt* wp = _model->waypointAt(getSelected());
+      if (wp->flag(WPT_GENERATED)) {
+        break;
+      }
+        
+      if (wp->altitudeRestriction() == RESTRICT_NONE) {
+        wp->setAltitude(1000, RESTRICT_AT);
       } else {
-        _model->setWaypointTargetAltitudeFt(getSelected(), newAlt * 100);
+        int curAlt = (static_cast<int>(wp->altitudeFt()) + 50) / 100;
+        wp->setAltitude((curAlt + 10) * 100, wp->altitudeRestriction());
       }
     }
     break;
@@ -637,6 +786,11 @@ int WaypointList::checkKey (int key, int updown )
   case 0x7f: // delete
     if (getSelected() >= 0) {
       int index = getSelected();
+      Waypt* wp = _model->waypointAt(getSelected());
+      if (wp->flag(WPT_GENERATED)) {
+        break;
+      }
+      
       _model->deleteAt(index);
       setSelected(index - 1);
     }
@@ -649,17 +803,6 @@ int WaypointList::checkKey (int key, int updown )
   return TRUE ;
 }
 
-int WaypointList::wayptAltFtHundreds(int index) const
-{
-  double alt = _model->waypointAt(index).get_target_alt();
-  if (alt < -9990.0) {
-    return -9999;
-  }
-  
-  int altFt = (int) alt * SG_METER_TO_FEET;
-  return (altFt + 50) / 100; // round to nearest 100ft
-}
-
 void WaypointList::modelUpdateCallback()
 {
   // local stuff
@@ -745,6 +888,7 @@ void ScrolledWaypointList::setScrollPercent(float v)
 void ScrolledWaypointList::setSize(int w, int h)
 {
   updateWantsScroll(w, h);
+  puGroup::setSize(w, h);
 }
 
 void ScrolledWaypointList::updateWantsScroll(int w, int h)