1
0
Fork 0

GPS / route-manager: add new custom widget to display the waypoints list.

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.
This commit is contained in:
jmt 2010-02-21 20:44:17 +00:00 committed by Tim Moore
parent cb2c800434
commit 4468d785b5
7 changed files with 1016 additions and 35 deletions

View 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());

View 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();
};

View 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

772
src/GUI/WaypointList.cxx Normal file
View file

@ -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());
}

171
src/GUI/WaypointList.hxx Normal file
View file

@ -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

View 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;
}

View 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; };