diff --git a/projects/VC7.1/FlightGear.vcproj b/projects/VC7.1/FlightGear.vcproj
index 9d0a96735..388947077 100755
--- a/projects/VC7.1/FlightGear.vcproj
+++ b/projects/VC7.1/FlightGear.vcproj
@@ -1826,6 +1826,8 @@
+
+
+
+
getChild("eta", 0, true);
_route->clear();
+ _route->set_current(0);
update_mirror();
_pathNode = fgGetNode(RM "file-path", 0, true);
@@ -285,17 +286,32 @@ 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);
}
- update_mirror();
- _edited->fireValueChanged();
+ 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 ) {
@@ -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());
diff --git a/src/Autopilot/route_mgr.hxx b/src/Autopilot/route_mgr.hxx
index b09657259..a81689248 100644
--- a/src/Autopilot/route_mgr.hxx
+++ b/src/Autopilot/route_mgr.hxx
@@ -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();
};
diff --git a/src/GUI/Makefile.am b/src/GUI/Makefile.am
index 890807f2a..5f8c98d0b 100644
--- a/src/GUI/Makefile.am
+++ b/src/GUI/Makefile.am
@@ -16,14 +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
layout_test_SOURCES = layout-test.cxx
diff --git a/src/GUI/WaypointList.cxx b/src/GUI/WaypointList.cxx
new file mode 100644
index 000000000..5498076e5
--- /dev/null
+++ b/src/GUI/WaypointList.cxx
@@ -0,0 +1,771 @@
+
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "WaypointList.hxx"
+
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+
+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(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;
+ }
+
+ std::string buf(s);
+ int len = buf.size();
+ do {
+ buf.resize(--len);
+ fullWidth = font.getStringWidth(buf.c_str());
+ } while (fullWidth > maxWidth);
+
+ font.drawString(buf.c_str(), 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, ¤tColor, 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, ¤tColor, 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
index 000000000..165a1dad3
--- /dev/null
+++ b/src/GUI/WaypointList.hxx
@@ -0,0 +1,171 @@
+/**
+ * WaypointList.hxx - scrolling list of waypoints, with special formatting
+ */
+
+#ifndef GUI_WAYPOINT_LIST_HXX
+#define GUI_WAYPOINT_LIST_HXX
+
+#include
+#include
+
+#include
+
+#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
diff --git a/src/GUI/dialog.cxx b/src/GUI/dialog.cxx
index e20a62b93..698268ce9 100644
--- a/src/GUI/dialog.cxx
+++ b/src/GUI/dialog.cxx
@@ -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;
@@ -569,9 +569,14 @@ FGDialog::updateValues (const char *objectName)
puObject *widget = _propertyObjects[i]->object;
int widgetType = widget->getType();
- if ((widgetType & PUCLASS_LIST) && (dynamic_cast(widget)->id & FGCLASS_LIST)) {
- fgList *pl = static_cast(widget);
- pl->update();
+ if (widgetType & PUCLASS_LIST) {
+ GUI_ID* gui_id = dynamic_cast(widget);
+ if (gui_id && (gui_id->id & FGCLASS_LIST)) {
+ fgList *pl = static_cast(widget);
+ pl->update();
+ } else {
+ copy_to_pui(_propertyObjects[i]->node, widget);
+ }
} else if (widgetType & PUCLASS_COMBOBOX) {
fgComboBox* combo = static_cast(widget);
combo->update();
@@ -873,6 +878,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;
}
diff --git a/src/GUI/dialog.hxx b/src/GUI/dialog.hxx
index 63aef704b..e08dbe75a 100644
--- a/src/GUI/dialog.hxx
+++ b/src/GUI/dialog.hxx
@@ -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; };