]> git.mxchange.org Git - flightgear.git/commitdiff
GPS / route-manager: add new custom widget to display the waypoints list.
authorjmt <jmt>
Sun, 21 Feb 2010 20:44:17 +0000 (20:44 +0000)
committerTim Moore <timoore33@gmail.com>
Sun, 21 Feb 2010 21:26:14 +0000 (22:26 +0100)
Supports various new editing features, including dragging to re-order, and
+/- keys to adjust the target altitude for a waypoint. Also displays some
additional information, and will display *even* more once I land airways/
SID/STAR support.

src/Autopilot/route_mgr.cxx
src/Autopilot/route_mgr.hxx
src/GUI/Makefile.am
src/GUI/WaypointList.cxx [new file with mode: 0644]
src/GUI/WaypointList.hxx [new file with mode: 0644]
src/GUI/dialog.cxx
src/GUI/dialog.hxx

index d6d65cc208331f6e59f1cb940ffeae4dc518ada9..64263dc25749eedfc0913bef2722a6548293da71 100644 (file)
@@ -172,6 +172,7 @@ void FGRouteMgr::init() {
   wpn->getChild("eta", 0, true);
   
   _route->clear();
+  _route->set_current(0);
   update_mirror();
   
   _pathNode = fgGetNode(RM "file-path", 0, true);
@@ -285,18 +286,33 @@ void FGRouteMgr::setETAPropertyFromDistance(SGPropertyNode_ptr aProp, double aDi
     aProp->setStringValue( eta_str );
 }
 
-void FGRouteMgr::add_waypoint( const SGWayPoint& wp, int n ) {
+void FGRouteMgr::add_waypoint( const SGWayPoint& wp, int n )
+{
   _route->add_waypoint( wp, n );
     
-  if (_route->current_index() > n) {
+  if ((n >= 0) && (_route->current_index() > n)) {
     _route->set_current(_route->current_index() + 1);
   }
   
+  waypointsChanged();
+}
+
+void FGRouteMgr::waypointsChanged()
+{
+  double routeDistanceNm = _route->total_distance() * SG_METER_TO_NM;
+  totalDistance->setDoubleValue(routeDistanceNm);
+  double cruiseSpeedKts = cruise->getDoubleValue("speed", 0.0);
+  if (cruiseSpeedKts > 1.0) {
+    // very very crude approximation, doesn't allow for climb / descent
+    // performance or anything else at all
+    ete->setDoubleValue(routeDistanceNm / cruiseSpeedKts * (60.0 * 60.0));
+  }
+
   update_mirror();
   _edited->fireValueChanged();
+  checkFinished();
 }
 
-
 SGWayPoint FGRouteMgr::pop_waypoint( int n ) {
   if ( _route->size() <= 0 ) {
     return SGWayPoint();
@@ -313,10 +329,7 @@ SGWayPoint FGRouteMgr::pop_waypoint( int n ) {
   SGWayPoint wp = _route->get_waypoint(n);
   _route->delete_waypoint(n);
     
-  update_mirror();
-  _edited->fireValueChanged();
-  checkFinished();
-  
+  waypointsChanged();
   return wp;
 }
 
@@ -467,9 +480,15 @@ void FGRouteMgr::InputListener::valueChanged(SGPropertyNode *prop)
       mgr->loadRoute();
     } else if (!strcmp(s, "@SAVE")) {
       mgr->saveRoute();
-    } else if (!strcmp(s, "@POP"))
-        mgr->pop_waypoint(0);
-    else if (!strncmp(s, "@DELETE", 7))
+    } else if (!strcmp(s, "@POP")) {
+      SG_LOG(SG_AUTOPILOT, SG_WARN, "route-manager @POP command is deprecated");
+    } else if (!strcmp(s, "@NEXT")) {
+      mgr->jumpToIndex(mgr->currentWaypoint() + 1);
+    } else if (!strcmp(s, "@PREVIOUS")) {
+      mgr->jumpToIndex(mgr->currentWaypoint() - 1);
+    } else if (!strncmp(s, "@JUMP", 5)) {
+      mgr->jumpToIndex(atoi(s + 5));
+    } else if (!strncmp(s, "@DELETE", 7))
         mgr->pop_waypoint(atoi(s + 7));
     else if (!strncmp(s, "@INSERT", 7)) {
         char *r;
@@ -529,16 +548,6 @@ bool FGRouteMgr::activate()
   }
 
   _route->set_current(0);
-  
-  double routeDistanceNm = _route->total_distance() * SG_METER_TO_NM;
-  totalDistance->setDoubleValue(routeDistanceNm);
-  double cruiseSpeedKts = cruise->getDoubleValue("speed", 0.0);
-  if (cruiseSpeedKts > 1.0) {
-    // very very crude approximation, doesn't allow for climb / descent
-    // performance or anything else at all
-    ete->setDoubleValue(routeDistanceNm / cruiseSpeedKts * (60.0 * 60.0));
-  }
-  
   active->setBoolValue(true);
   SG_LOG(SG_AUTOPILOT, SG_INFO, "route-manager, activate route ok");
   return true;
@@ -576,11 +585,6 @@ bool FGRouteMgr::checkFinished()
 
 void FGRouteMgr::jumpToIndex(int index)
 {
-  if (!active->getBoolValue()) {
-    SG_LOG(SG_AUTOPILOT, SG_ALERT, "trying to sequence waypoints with no active route");
-    return;
-  }
-
   if ((index < 0) || (index >= _route->size())) {
     SG_LOG(SG_AUTOPILOT, SG_ALERT, "passed invalid index (" << 
       index << ") to FGRouteMgr::jumpToIndex");
@@ -637,6 +641,16 @@ int FGRouteMgr::currentWaypoint() const
   return _route->current_index();
 }
 
+void FGRouteMgr::setWaypointTargetAltitudeFt(unsigned int index, int altFt)
+{
+  SGWayPoint wp = _route->get_waypoint(index);
+  wp.setTargetAltFt(altFt);
+  // simplest way to update a waypoint is to remove and re-add it
+  _route->delete_waypoint(index);
+  _route->add_waypoint(wp, index);
+  waypointsChanged();
+}
+
 void FGRouteMgr::saveRoute()
 {
   SGPath path(_pathNode->getStringValue());
index b096572591533419f4a429cedde07d820c64240a..a8168924807a5ec582759dfd63becd271d527ce6 100644 (file)
@@ -113,6 +113,12 @@ private:
      */
     SGWayPoint* make_waypoint(const string& target);
     
+    /**
+     * Helper to keep various pieces of state in sync when the SGRoute is
+     * modified (waypoints added, inserted, removed). Notably, this fires the
+     * 'edited' signal.
+     */
+    void waypointsChanged();
     
     void update_mirror();
     
@@ -188,6 +194,11 @@ public:
      */
     void jumpToIndex(int index);
     
+    /**
+     * 
+     */
+    void setWaypointTargetAltitudeFt(unsigned int index, int altFt);
+    
     void saveRoute();
     void loadRoute();
 };
index 890807f2a9734b5cc12f2d4d673784cbc273d6b6..d0ede62b61fc5e028141f979d79dba798742becc 100644 (file)
@@ -16,13 +16,15 @@ endif
 libGUI_a_SOURCES = \
         new_gui.cxx new_gui.hxx \
         dialog.cxx dialog.hxx \
-       menubar.cxx menubar.hxx \
-       gui.cxx gui.h gui_funcs.cxx \
-       fonts.cxx \
-       AirportList.cxx AirportList.hxx \
+        menubar.cxx menubar.hxx \
+        gui.cxx gui.h gui_funcs.cxx \
+        fonts.cxx \
+        AirportList.cxx AirportList.hxx \
         property_list.cxx property_list.hxx \
         layout.cxx layout-props.cxx layout.hxx \
-       SafeTexFont.cxx SafeTexFont.hxx
+        SafeTexFont.cxx SafeTexFont.hxx \
+        WaypointList.cxx WaypointList.hxx \
+    
 
 INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/src
 
diff --git a/src/GUI/WaypointList.cxx b/src/GUI/WaypointList.cxx
new file mode 100644 (file)
index 0000000..0ad02a3
--- /dev/null
@@ -0,0 +1,772 @@
+
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include "WaypointList.hxx"
+
+#include <algorithm>
+#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 <Autopilot/route_mgr.hxx>
+
+enum {
+  SCROLL_NO = 0,
+  SCROLL_UP,
+  SCROLL_DOWN
+};
+  
+static const int DRAG_START_DISTANCE_PX = 5;
+  
+class RouteManagerWaypointModel : 
+  public WaypointList::Model, 
+  public SGPropertyChangeListener
+{
+public:
+  RouteManagerWaypointModel()
+  {
+    _rm = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
+    
+    SGPropertyNode* routeEdited = fgGetNode("/autopilot/route-manager/signals/edited", true);
+    routeEdited->addChangeListener(this);
+  }
+  
+  virtual ~RouteManagerWaypointModel()
+  {
+    SGPropertyNode* routeEdited = fgGetNode("/autopilot/route-manager/signals/edited", true);
+    routeEdited->removeChangeListener(this);
+  }
+  
+// implement WaypointList::Model
+  virtual unsigned int numWaypoints() const
+  {
+    return _rm->size();
+  }
+  
+  virtual int currentWaypoint() const
+  {
+    return _rm->currentWaypoint();
+  }
+  
+  virtual SGWayPoint waypointAt(unsigned int index) const
+  {
+    return _rm->get_waypoint(index);
+  }
+
+  virtual void deleteAt(unsigned int index)
+  {
+    _rm->pop_waypoint(index);
+  }
+  
+  virtual void setWaypointTargetAltitudeFt(unsigned int index, int altFt)
+  {
+    _rm->setWaypointTargetAltitudeFt(index, altFt);
+  }
+  
+  virtual void moveWaypointToIndex(unsigned int srcIndex, unsigned int destIndex)
+  {
+    if (destIndex > srcIndex) {
+      --destIndex;
+    }
+    
+    SGWayPoint wp = _rm->pop_waypoint(srcIndex);
+    _rm->add_waypoint(wp, destIndex);
+  }
+  
+  virtual void setUpdateCallback(SGCallback* cb)
+  {
+    _cb = cb;
+  }
+    
+// implement SGPropertyChangeListener
+  void valueChanged(SGPropertyNode *prop)
+  {
+    if (_cb) {
+      (*_cb)();
+    }
+  }
+private:
+  FGRouteMgr* _rm;
+  SGCallback* _cb;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static void drawClippedString(puFont& font, const char* s, int x, int y, int maxWidth)
+{
+  int fullWidth = font.getStringWidth(s);
+  if (fullWidth <= maxWidth) { // common case, easy and efficent
+    font.drawString(s, x, y);
+    return;
+  }
+  
+  int len = strlen(s);
+  char buf[len];
+  memcpy(buf, s, len);
+  do {
+    buf[--len] = 0;
+    fullWidth = font.getStringWidth(buf);
+  } while (fullWidth > maxWidth);
+  
+  font.drawString(buf, x, y);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+WaypointList::WaypointList(int x, int y, int width, int height) :
+  puFrame(x, y, width, height),
+  GUI_ID(FGCLASS_WAYPOINTLIST),
+  _scrollPx(0),
+  _dragging(false),
+  _dragScroll(SCROLL_NO),
+  _showLatLon(false),
+  _model(NULL),
+  _updateCallback(NULL),
+  _scrollCallback(NULL)
+{
+  // pretend to be a list, so fgPopup doesn't mess with our mouse events
+  type |= PUCLASS_LIST;  
+  setModel(new RouteManagerWaypointModel());
+  setSize(width, height);
+  setValue(-1);
+}
+
+WaypointList::~WaypointList()
+{
+  delete _model;
+  delete _updateCallback;
+  delete _scrollCallback;
+}
+
+void WaypointList::setUpdateCallback(SGCallback* cb)
+{
+  _updateCallback = cb;
+}
+
+void WaypointList::setScrollCallback(SGCallback* cb)
+{
+  _scrollCallback = cb;
+}
+
+void WaypointList::setSize(int width, int height)
+{
+  double scrollP = getVScrollPercent();
+  _heightPx = height;
+  puFrame::setSize(width, height);
+  
+  if (wantsVScroll()) {
+    setVScrollPercent(scrollP);
+  } else {
+    _scrollPx = 0;
+  }
+}
+
+int WaypointList::checkHit ( int button, int updown, int x, int y )
+{
+  if ( isHit( x, y ) || ( puActiveWidget () == this ) )
+  {
+    doHit ( button, updown, x, y ) ;
+    return TRUE ;
+  }
+
+  return FALSE ;
+}
+
+
+void WaypointList::doHit( int button, int updown, int x, int y )
+{
+  puFrame::doHit(button, updown, x, y);  
+  if (updown == PU_DRAG) {
+    handleDrag(x, y);
+    return;
+  }
+  
+  if (button != active_mouse_button) {
+    return;
+  }
+      
+  if (updown == PU_UP) {
+    puDeactivateWidget();
+    if (_dragging) {
+      doDrop(x, y);
+      return;
+    }
+  } else if (updown == PU_DOWN) {
+    puSetActiveWidget(this, x, y);
+    _mouseDownX = x;
+    _mouseDownY = y;
+    return;
+  }
+  
+// update selection
+  int row = rowForY(y - abox.min[1]);
+  if (row >= (int) _model->numWaypoints()) {
+    row = -1; // 'no selection'
+  }
+
+  if (row == getSelected()) {
+    _showLatLon = !_showLatLon;
+    puPostRefresh();
+    return;
+  }
+  
+  setSelected(row);
+}
+
+void WaypointList::handleDrag(int x, int y)
+{
+  if (!_dragging) {
+    // don't start drags immediately, require a certain mouse movement first
+    int manhattanLength = abs(x - _mouseDownX) + abs(y - _mouseDownY);
+    if (manhattanLength < DRAG_START_DISTANCE_PX) {
+      return;
+    }
+    
+    _dragSourceRow = rowForY(y - abox.min[1]);
+    _dragging = true;
+    _dragScroll = SCROLL_NO;
+  }
+  
+  if (y < abox.min[1]) {
+    if (_dragScroll != SCROLL_DOWN) {
+      _dragScroll = SCROLL_DOWN;
+      _dragScrollTime.stamp();
+    }
+  } else if (y > abox.max[1]) {
+    if (_dragScroll != SCROLL_UP) {
+      _dragScroll = SCROLL_UP;
+      _dragScrollTime.stamp();
+    }
+  } else {
+    _dragScroll = SCROLL_NO;
+    _dragTargetRow = rowForY(y - abox.min[1] - (rowHeightPx() / 2));
+  }
+}
+
+void WaypointList::doDrop(int x, int y)
+{
+  _dragging = false;
+  puDeactivateWidget();
+  
+  if ((y < abox.min[1]) || (y >= abox.max[1])) {
+    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);
+  }
+}
+
+void WaypointList::invokeDownCallback(void)
+{
+  _dragging = false;
+  _dragScroll = SCROLL_NO;
+  SG_LOG(SG_GENERAL, SG_INFO, "cancel drag");
+}
+
+int WaypointList::rowForY(int y) const
+{
+  if (!_model) {
+    return -1;
+  }
+  
+  // flip y to increase down, not up (as rows do)
+  int flipY = _heightPx - y;
+  int row = (flipY + _scrollPx) / rowHeightPx();
+  return row;
+}
+
+void WaypointList::draw( int dx, int dy )
+{
+  puFrame::draw(dx, dy);
+
+  if (!_model) {
+    return;
+  }
+
+  if (_dragScroll != SCROLL_NO) {
+    doDragScroll();
+  }
+  
+  glEnable(GL_SCISSOR_TEST);
+  GLint sx = (int) abox.min[0],
+    sy = abox.min[1];
+  GLsizei w = (GLsizei) abox.max[0] - abox.min[0],
+    h = _heightPx;
+    
+  sx += border_thickness;
+  sy += border_thickness;
+  w -= 2 * border_thickness;
+  h -= 2 * border_thickness;
+    
+  glScissor(sx + dx, sy + dy, w, h);
+  int row = firstVisibleRow(), 
+    final = lastVisibleRow(),
+    rowHeight = rowHeightPx(),
+    y = rowHeight;
+  
+  y -= (_scrollPx % rowHeight); // partially draw the first row
+  
+  for ( ; row <= final; ++row, y += rowHeight) {
+    drawRow(dx, dy, row, y);
+  } // of row drawing iteration
+  
+  glDisable(GL_SCISSOR_TEST);
+    
+  if (_dragging) {
+    // draw the insert marker after the rows
+    int insertY = (_dragTargetRow * rowHeight) - _scrollPx;
+    SG_CLAMP_RANGE(insertY, 0, std::min(_heightPx, totalHeightPx()));
+    
+    glColor4f(1.0f, 0.5f, 0.0f, 0.8f);
+    glLineWidth(3.0f);
+    glBegin(GL_LINES);
+      glVertex2f(dx + abox.min[0], dy + abox.max[1] - insertY);
+      glVertex2f(dx + abox.max[0], dy + abox.max[1] - insertY);
+    glEnd();
+  }
+}
+
+void WaypointList::drawRow(int dx, int dy, int rowIndex, int y)
+{
+  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);
+  
+  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);
+  }
+  
+  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());
+  }
+  
+  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);
+  }
+
+  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
+    if (altHundredFt < 100) {
+      count = ::snprintf(buffer, 128, "%d'", altHundredFt * 100);
+    } else { // display as a flight-level
+      count = ::snprintf(buffer, 128, "FL%d", altHundredFt);
+    }
+    
+    legendFont.drawString(buffer, xx + 400 + PUSTR_LGAP, yy);
+  } // 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);
+  }
+}
+
+const double SCROLL_PX_SEC = 200.0;
+
+void WaypointList::doDragScroll()
+{
+  double dt = (SGTimeStamp::now() - _dragScrollTime).toSecs();
+  _dragScrollTime.stamp();
+  int deltaPx = (int)(dt * SCROLL_PX_SEC);
+  
+  if (_dragScroll == SCROLL_UP) {
+    _scrollPx = _scrollPx - deltaPx;
+    SG_CLAMP_RANGE(_scrollPx, 0, scrollRangePx());
+    _dragTargetRow = firstVisibleRow();
+  } else {
+    _scrollPx = _scrollPx + deltaPx;
+    SG_CLAMP_RANGE(_scrollPx, 0, scrollRangePx());
+    _dragTargetRow = lastFullyVisibleRow() + 1;
+  }
+  
+  if (_scrollCallback) {
+    (*_scrollCallback)();
+  }
+}
+
+int WaypointList::getSelected()
+{
+  return getIntegerValue();
+}
+
+void WaypointList::setSelected(int rowIndex)
+{
+  if (rowIndex == getSelected()) {
+    return;
+  }
+  
+  setValue(rowIndex);
+  invokeCallback();
+  if (rowIndex == -1) {
+    return;
+  }
+
+  ensureRowVisible(rowIndex);
+}
+
+void WaypointList::ensureRowVisible(int rowIndex)
+{
+  if ((rowIndex >= firstFullyVisibleRow()) && (rowIndex <= lastFullyVisibleRow())) {
+    return; // already visible, fine
+  }
+  
+  // ideal position would place the desired row in the middle of the
+  // visible section - hence subtract half the visible height.
+  int targetScrollPx = (rowIndex * rowHeightPx()) - (_heightPx / 2);
+  
+  // clamp the scroll value to something valid
+  SG_CLAMP_RANGE(targetScrollPx, 0, scrollRangePx());
+  _scrollPx = targetScrollPx;
+  
+  puPostRefresh();
+  if (_scrollCallback) { // keep scroll observers in sync
+    (*_scrollCallback)();
+  }
+}
+
+unsigned int WaypointList::numWaypoints() const
+{
+  if (!_model) {
+    return 0;
+  }
+  
+  return _model->numWaypoints();
+}
+
+bool WaypointList::wantsVScroll() const
+{
+  return totalHeightPx() > _heightPx;
+}
+
+float WaypointList::getVScrollPercent() const
+{
+  float scrollRange = scrollRangePx();
+  if (scrollRange < 1.0f) {
+    return 0.0;
+  }
+  
+  return _scrollPx / scrollRange;
+}
+
+float WaypointList::getVScrollThumbPercent() const
+{
+  return _heightPx / (float) totalHeightPx();
+}
+
+void WaypointList::setVScrollPercent(float perc)
+{
+  float scrollRange = scrollRangePx();
+  _scrollPx = (int)(scrollRange * perc);
+}
+
+int WaypointList::firstVisibleRow() const
+{
+  return _scrollPx / rowHeightPx();
+}
+
+int WaypointList::firstFullyVisibleRow() const
+{
+  int rh = rowHeightPx();
+  return (_scrollPx + rh - 1) / rh;
+}
+  
+int WaypointList::numVisibleRows() const
+{
+  int rh = rowHeightPx();
+  int topOffset = _scrollPx % rh; // pixels of first visible row
+  return (_heightPx - topOffset + rh - 1) / rh;
+
+}
+
+int WaypointList::numFullyVisibleRows() const
+{
+  int rh = rowHeightPx();
+  int topOffset = _scrollPx % rh; // pixels of first visible row
+  return (_heightPx - topOffset) / rh;
+}
+
+int WaypointList::rowHeightPx() const
+{
+  return legendFont.getStringHeight() + PUSTR_BGAP;
+}
+
+int WaypointList::scrollRangePx() const
+{
+  return std::max(0, totalHeightPx() - _heightPx);
+}
+
+int WaypointList::totalHeightPx() const
+{
+  if (!_model) {
+    return 0;
+  }
+  
+  return (int) _model->numWaypoints() * rowHeightPx();
+}
+
+int WaypointList::lastFullyVisibleRow() const
+{
+  int row = firstFullyVisibleRow() + numFullyVisibleRows();
+  return std::min(row, (int) _model->numWaypoints() - 1);
+}
+
+int WaypointList::lastVisibleRow() const
+{
+  int row = firstVisibleRow() + numVisibleRows();
+  return std::min(row, (int) _model->numWaypoints() - 1);
+}
+
+void WaypointList::setModel(Model* model)
+{
+  if (_model) {
+    delete _model;
+  }
+  
+  _model = model;
+  _model->setUpdateCallback(make_callback(this, &WaypointList::modelUpdateCallback));
+  
+  puPostRefresh();
+}
+
+int WaypointList::checkKey (int key, int updown )
+{
+  if ((updown == PU_UP) || !isVisible () || !isActive () || (window != puGetWindow())) {
+    return FALSE ;
+  }
+  
+  switch (key)
+  {
+  case PU_KEY_HOME:
+    setSelected(0);
+    break;
+
+  case PU_KEY_END:
+    setSelected(_model->numWaypoints() - 1);
+    break ;
+
+  case PU_KEY_UP        :
+  case PU_KEY_PAGE_UP   :
+    if (getSelected() >= 0) {
+      setSelected(getSelected() - 1);
+    }
+    break ;
+
+  case PU_KEY_DOWN      :
+  case PU_KEY_PAGE_DOWN : {
+    int newSel = getSelected() + 1;
+    if (newSel >= (int) _model->numWaypoints()) {
+      setSelected(-1);
+    } else {
+      setSelected(newSel);
+    }
+    break ;
+  }
+  
+  case '-':
+    if (getSelected() >= 0) {
+      int newAlt = wayptAltFtHundreds(getSelected()) - 10;
+      if (newAlt < 0) {
+        _model->setWaypointTargetAltitudeFt(getSelected(), -9999);
+      } else {
+        _model->setWaypointTargetAltitudeFt(getSelected(), newAlt * 100);
+      }
+    }
+    break;
+    
+  case '=':
+    if (getSelected() >= 0) {
+      int newAlt = wayptAltFtHundreds(getSelected()) + 10;
+      if (newAlt < 0) {
+        _model->setWaypointTargetAltitudeFt(getSelected(), 0);
+      } else {
+        _model->setWaypointTargetAltitudeFt(getSelected(), newAlt * 100);
+      }
+    }
+    break;
+  
+  case 0x7f: // delete
+    if (getSelected() >= 0) {
+      int index = getSelected();
+      _model->deleteAt(index);
+      setSelected(index - 1);
+    }
+    break;
+
+  default :
+    return FALSE;
+  }
+
+  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
+  
+  if (_updateCallback) {
+    (*_updateCallback)();
+  }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+
+static void handle_scrollbar(puObject* scrollbar)
+{
+  ScrolledWaypointList* self = (ScrolledWaypointList*)scrollbar->getUserData();
+  self->setScrollPercent(scrollbar->getFloatValue());
+}
+
+static void waypointListCb(puObject* wpl)
+{
+  ScrolledWaypointList* self = (ScrolledWaypointList*)wpl->getUserData();
+  self->setValue(wpl->getIntegerValue());
+  self->invokeCallback();
+}
+
+ScrolledWaypointList::ScrolledWaypointList(int x, int y, int width, int height) :
+  puGroup(x,y),
+  _scrollWidth(16)
+{
+  // ensure our type is compound, so fgPopup::applySize doesn't descend into
+  // us, and try to cast our children's user-data to GUIInfo*.
+  type |= PUCLASS_LIST;
+  
+  init(width, height);
+}
+
+void ScrolledWaypointList::setValue(float v)
+{
+  puGroup::setValue(v);
+  _list->setValue(v);
+}
+
+void ScrolledWaypointList::setValue(int v)
+{
+  puGroup::setValue(v);
+  _list->setValue(v);
+}
+
+void ScrolledWaypointList::init(int w, int h)
+{
+  _list = new WaypointList(0, 0, w, h);
+  _list->setUpdateCallback(make_callback(this, &ScrolledWaypointList::modelUpdated));
+  _hasVScroll = _list->wantsVScroll();
+  _list->setUserData(this);
+  _list->setCallback(waypointListCb);
+  
+  _list->setScrollCallback(make_callback(this, &ScrolledWaypointList::updateScroll));
+  
+  _scrollbar = new puaScrollBar(w - _scrollWidth, 0, h, 
+    1 /*arrow*/, 1 /* vertical */, _scrollWidth);
+  _scrollbar->setMinValue(0.0);
+  _scrollbar->setMaxValue(1.0);
+  _scrollbar->setUserData(this);
+  _scrollbar->setCallback(handle_scrollbar);
+  close(); // close the group
+  
+  setSize(w, h);
+}
+
+void ScrolledWaypointList::modelUpdated()
+{  
+  int w, h;
+  getSize(&w, &h);
+  updateWantsScroll(w, h);
+}
+
+void ScrolledWaypointList::setScrollPercent(float v)
+{
+  // slider's min is the bottom, so invert the value
+  _list->setVScrollPercent(1.0f - v); 
+}
+
+void ScrolledWaypointList::setSize(int w, int h)
+{
+  updateWantsScroll(w, h);
+}
+
+void ScrolledWaypointList::updateWantsScroll(int w, int h)
+{
+  _hasVScroll = _list->wantsVScroll();
+  
+  if (_hasVScroll) {
+    _scrollbar->reveal();
+    _scrollbar->setPosition(w - _scrollWidth, 0);
+    _scrollbar->setSize(_scrollWidth, h);
+    _list->setSize(w - _scrollWidth, h);
+    updateScroll();
+  } else {
+    _scrollbar->hide();
+    _list->setSize(w, h);
+  }
+}
+
+void ScrolledWaypointList::updateScroll()
+{
+ // _scrollbar->setMaxValue(_list->numWaypoints());
+  _scrollbar->setValue(1.0f - _list->getVScrollPercent());
+  _scrollbar->setSliderFraction(_list->getVScrollThumbPercent());
+}
+
diff --git a/src/GUI/WaypointList.hxx b/src/GUI/WaypointList.hxx
new file mode 100644 (file)
index 0000000..165a1da
--- /dev/null
@@ -0,0 +1,171 @@
+/**
+ * WaypointList.hxx - scrolling list of waypoints, with special formatting
+ */
+
+#ifndef GUI_WAYPOINT_LIST_HXX
+#define GUI_WAYPOINT_LIST_HXX
+
+#include <simgear/compiler.h>
+#include <simgear/timing/timestamp.hxx>
+
+#include <plib/pu.h>
+
+#include "dialog.hxx" // for GUI_ID
+
+// forward decls
+class puaScrollBar;
+class SGWayPoint;
+class SGCallback;
+
+class WaypointList : public puFrame, public GUI_ID
+{
+public:
+  WaypointList(int x, int y, int width, int height);
+  virtual ~WaypointList();
+
+  virtual void setSize(int width, int height);
+  virtual int checkHit ( int button, int updown, int x, int y);
+  virtual void doHit( int button, int updown, int x, int y ) ;
+  virtual void draw( int dx, int dy ) ;
+  virtual int checkKey(int key, int updown);
+  virtual void invokeDownCallback (void);
+  
+  void setSelected(int rowIndex);
+  int getSelected();
+  
+  /**
+   * Do we want a vertical scrollbar (or similar)
+   */
+  bool wantsVScroll() const;
+  
+  /**
+   * Get scrollbar position as a percentage of total range.
+   * returns negative number if scrolling is not possible
+   */
+  float getVScrollPercent() const;
+  
+  /**
+   *
+   */
+  void setVScrollPercent(float perc);
+  
+  /**
+   * Get required thumb size as percentage of total height
+   */
+  float getVScrollThumbPercent() const;
+  
+  int numVisibleRows() const;
+  
+  void ensureRowVisible(int row);
+  
+  void setUpdateCallback(SGCallback* cb);
+  void setScrollCallback(SGCallback* cb);
+  
+  /**
+   * Abstract interface for waypoint source
+   */
+  class Model
+  {
+  public:
+    virtual ~Model() { }
+    
+    virtual unsigned int numWaypoints() const = 0;
+    virtual int currentWaypoint() const = 0;
+    virtual SGWayPoint waypointAt(unsigned int index) const = 0;
+  
+  // update notifications
+    virtual void setUpdateCallback(SGCallback* cb) = 0;
+  
+  // editing operations
+    virtual void deleteAt(unsigned int index) = 0;
+    virtual void setWaypointTargetAltitudeFt(unsigned int index, int altFt) = 0;
+    virtual void moveWaypointToIndex(unsigned int srcIndex, unsigned int dstIndex) = 0;
+  };
+  
+  void setModel(Model* model);
+  
+  unsigned int numWaypoints() const;
+protected:
+
+private:
+  void drawRow(int dx, int dy, int rowIndex, int yOrigin);
+
+  void handleDrag(int x, int y);
+  void doDrop(int x, int y);
+  void doDragScroll();
+  
+  /**
+   * Pixel height of a row, including padding
+   */
+  int rowHeightPx() const;
+  
+  /**
+   * model row corresponding to an on-screen y-value
+   */
+  int rowForY(int y) const;
+  
+  /**
+   * reutrn rowheight * total number of rows, i.e the height we'd
+   * need to be to show every row without scrolling
+   */
+  int totalHeightPx() const;
+  
+  /**
+   * Pixel scroll range, based on widget height and number of rows
+   */
+  int scrollRangePx() const;
+  
+  int firstVisibleRow() const;
+  int lastVisibleRow() const;
+
+  int numFullyVisibleRows() const;
+  int firstFullyVisibleRow() const;
+  int lastFullyVisibleRow() const;
+  
+  int wayptAltFtHundreds(int index) const;
+  
+  void modelUpdateCallback();
+  
+  int _scrollPx; // scroll ammount (in pixels)
+  int _heightPx;
+  
+  bool _dragging;
+  int _dragSourceRow;
+  int _dragTargetRow;
+  int _mouseDownX, _mouseDownY;
+  
+  int _dragScroll;
+  SGTimeStamp _dragScrollTime;
+
+  bool _showLatLon;
+  Model* _model;
+  SGCallback* _updateCallback;
+  SGCallback* _scrollCallback;
+};
+
+class ScrolledWaypointList : public puGroup
+{
+public:
+  ScrolledWaypointList(int x, int y, int width, int height);
+  
+  virtual void setSize(int width, int height);
+  
+  void setScrollPercent(float v);
+  
+  virtual void setValue(float v);
+  virtual void setValue(int v);
+private:  
+  void init(int w, int h);
+  
+  void updateScroll();
+  void updateWantsScroll(int w, int h);
+  
+  void modelUpdated();
+  
+  puaScrollBar* _scrollbar;
+  WaypointList* _list;
+  int _scrollWidth;
+  bool _hasVScroll;
+};
+
+#endif // GUI_WAYPOINT_LIST_HXX
index 95fe5f04c1fad5f40f56783fb492facf157f66b0..fcc0f5a97e430f19e4d9b71d3598f165d7335f46 100644 (file)
@@ -13,7 +13,7 @@
 #include "AirportList.hxx"
 #include "property_list.hxx"
 #include "layout.hxx"
-
+#include "WaypointList.hxx"
 
 enum format_type { f_INVALID, f_INT, f_LONG, f_FLOAT, f_DOUBLE, f_STRING };
 static const int FORMAT_BUFSIZE = 255;
@@ -561,9 +561,14 @@ FGDialog::updateValues (const char *objectName)
     
     puObject *widget = _propertyObjects[i]->object;
     int widgetType = widget->getType();
-    if ((widgetType & PUCLASS_LIST) && (dynamic_cast<GUI_ID *>(widget)->id & FGCLASS_LIST)) {
-      fgList *pl = static_cast<fgList*>(widget);
-      pl->update();
+    if (widgetType & PUCLASS_LIST) {
+      GUI_ID* gui_id = dynamic_cast<GUI_ID *>(widget);
+      if (gui_id && (gui_id->id & FGCLASS_LIST)) {
+        fgList *pl = static_cast<fgList*>(widget);
+        pl->update();
+      } else {
+        copy_to_pui(_propertyObjects[i]->node, widget);
+      }
     } else if (widgetType & PUCLASS_COMBOBOX) {
       fgComboBox* combo = static_cast<fgComboBox*>(widget);
       combo->update();
@@ -865,6 +870,10 @@ FGDialog::makeObject (SGPropertyNode *props, int parentWidth, int parentHeight)
         setupObject(obj, props);
         setColor(obj, props, EDITFIELD);
         return obj;
+    } else if (type == "waypointlist") {
+        ScrolledWaypointList* obj = new ScrolledWaypointList(x, y, width, height);
+        setupObject(obj, props);
+        return obj;
     } else {
         return 0;
     }
index 63aef704b1d2ec3354c7b4261749a2054db43515..e08dbe75ae2c414d3d5edc5b927dda34ea3f2f35 100644 (file)
@@ -22,6 +22,8 @@ using std::vector;
 #define FGCLASS_LIST          0x00000001
 #define FGCLASS_AIRPORTLIST   0x00000002
 #define FGCLASS_PROPERTYLIST  0x00000004
+#define FGCLASS_WAYPOINTLIST  0x00000008
+
 class GUI_ID { public: GUI_ID(int id) : id(id) {} virtual ~GUI_ID() {} int id; };