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:
parent
cb2c800434
commit
4468d785b5
7 changed files with 1016 additions and 35 deletions
|
@ -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,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());
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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
772
src/GUI/WaypointList.cxx
Normal 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, ¤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());
|
||||
}
|
||||
|
171
src/GUI/WaypointList.hxx
Normal file
171
src/GUI/WaypointList.hxx
Normal 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
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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; };
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue