7 #include "WaypointList.hxx"
10 #include <boost/tuple/tuple.hpp>
12 #include <plib/puAux.h>
14 #include <simgear/structure/callback.hxx>
15 #include <simgear/sg_inlines.h>
17 #include <Main/globals.hxx>
18 #include <Main/fg_props.hxx>
20 #include <Navaids/positioned.hxx>
21 #include <Navaids/routePath.hxx>
22 #include <Autopilot/route_mgr.hxx>
24 // select if the widget grabs keys necessary to fly aircraft from the keyboard,
25 // or not. See http://code.google.com/p/flightgear-bugs/issues/detail?id=338
26 // for discussion about why / what is going on.
27 #define AVOID_FLIGHT_KEYS 1
29 using namespace flightgear;
37 static const double BLINK_TIME = 0.3;
38 static const int DRAG_START_DISTANCE_PX = 5;
40 class FlightPlanWaypointModel :
41 public WaypointList::Model,
42 public SGPropertyChangeListener
45 FlightPlanWaypointModel(flightgear::FlightPlan* fp) :
48 SGPropertyNode* routeEdited = fgGetNode("/autopilot/route-manager/signals/edited", true);
49 SGPropertyNode* flightplanChanged = fgGetNode("/autopilot/route-manager/signals/flightplan-changed", true);
50 routeEdited->addChangeListener(this);
51 flightplanChanged->addChangeListener(this);
54 ~FlightPlanWaypointModel()
56 SGPropertyNode* routeEdited = fgGetNode("/autopilot/route-manager/signals/edited", true);
57 SGPropertyNode* flightplanChanged = fgGetNode("/autopilot/route-manager/signals/flightplan-changed", true);
58 routeEdited->removeChangeListener(this);
59 flightplanChanged->removeChangeListener(this);
62 // implement WaypointList::Model
63 virtual unsigned int numWaypoints() const
65 return _fp->numLegs();
68 virtual int currentWaypoint() const
70 return _fp->currentIndex();
73 virtual flightgear::Waypt* waypointAt(unsigned int index) const
75 if (index >= numWaypoints()) {
79 return _fp->legAtIndex(index)->waypoint();
82 virtual flightgear::FlightPlan* flightplan() const
87 virtual void deleteAt(unsigned int index)
89 _fp->deleteIndex(index);
92 virtual void moveWaypointToIndex(unsigned int srcIndex, unsigned int destIndex)
94 SG_LOG(SG_GENERAL, SG_INFO, "moveWaypoint: from " << srcIndex << " to " << destIndex);
95 if (destIndex > srcIndex) {
99 int currentWpIndex = currentWaypoint();
101 WayptRef w = _fp->legAtIndex(srcIndex)->waypoint();
102 _fp->deleteIndex(srcIndex);
103 _fp->insertWayptAtIndex(w, destIndex);
105 if ((signed int) srcIndex == currentWpIndex) {
106 // current waypoint was moved
107 _fp->setCurrentIndex(destIndex);
111 virtual void setUpdateCallback(SGCallback* cb)
116 // implement SGPropertyChangeListener
117 void valueChanged(SGPropertyNode *prop)
119 if (prop->getNameString() == "edited") {
125 if (prop->getNameString() == "flightplan-changed") {
127 static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"))->flightPlan();
131 flightgear::FlightPlan* _fp;
135 //////////////////////////////////////////////////////////////////////////////
137 static void drawClippedString(puFont& font, const char* s, int x, int y, int maxWidth)
139 int fullWidth = font.getStringWidth(s);
140 if (fullWidth <= maxWidth) { // common case, easy and efficent
141 font.drawString(s, x, y);
146 int len = buf.size();
149 fullWidth = font.getStringWidth(buf.c_str());
150 } while (fullWidth > maxWidth);
152 font.drawString(buf.c_str(), x, y);
155 //////////////////////////////////////////////////////////////////////////////
157 WaypointList::WaypointList(int x, int y, int width, int height) :
158 puFrame(x, y, width, height),
159 GUI_ID(FGCLASS_WAYPOINTLIST),
162 _dragScroll(SCROLL_NO),
165 _updateCallback(NULL),
166 _scrollCallback(NULL),
169 // pretend to be a list, so fgPopup doesn't mess with our mouse events
170 type |= PUCLASS_LIST;
171 flightgear::FlightPlan* fp =
172 static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"))->flightPlan();
173 setModel(new FlightPlanWaypointModel(fp));
174 setSize(width, height);
180 WaypointList::~WaypointList()
183 delete _updateCallback;
184 delete _scrollCallback;
187 void WaypointList::setUpdateCallback(SGCallback* cb)
189 _updateCallback = cb;
192 void WaypointList::setScrollCallback(SGCallback* cb)
194 _scrollCallback = cb;
197 void WaypointList::setSize(int width, int height)
199 double scrollP = getVScrollPercent();
201 puFrame::setSize(width, height);
203 if (wantsVScroll()) {
204 setVScrollPercent(scrollP);
210 int WaypointList::checkHit ( int button, int updown, int x, int y )
212 if ( isHit( x, y ) || ( puActiveWidget () == this ) )
214 doHit ( button, updown, x, y ) ;
222 void WaypointList::doHit( int button, int updown, int x, int y )
224 puFrame::doHit(button, updown, x, y);
225 if (updown == PU_DRAG) {
230 if (button != active_mouse_button) {
234 if (updown == PU_UP) {
235 puDeactivateWidget();
240 } else if (updown == PU_DOWN) {
241 puSetActiveWidget(this, x, y);
248 int row = rowForY(y - abox.min[1]);
249 if (row >= (int) _model->numWaypoints()) {
250 row = -1; // 'no selection'
253 if (row == getSelected()) {
254 _showLatLon = !_showLatLon;
262 void WaypointList::handleDrag(int x, int y)
265 // don't start drags immediately, require a certain mouse movement first
266 int manhattanLength = abs(x - _mouseDownX) + abs(y - _mouseDownY);
267 if (manhattanLength < DRAG_START_DISTANCE_PX) {
271 _dragSourceRow = rowForY(y - abox.min[1]);
272 Waypt* wp = _model->waypointAt(_dragSourceRow);
273 if (!wp || wp->flag(WPT_GENERATED) || (wp->type() == "discontinuity")) {
274 return; // don't allow generated points to be dragged
278 _dragScroll = SCROLL_NO;
281 if (y < abox.min[1]) {
282 if (_dragScroll != SCROLL_DOWN) {
283 _dragScroll = SCROLL_DOWN;
284 _dragScrollTime.stamp();
286 } else if (y > abox.max[1]) {
287 if (_dragScroll != SCROLL_UP) {
288 _dragScroll = SCROLL_UP;
289 _dragScrollTime.stamp();
292 _dragScroll = SCROLL_NO;
293 _dragTargetRow = rowForY(y - abox.min[1] - (rowHeightPx() / 2));
297 void WaypointList::doDrop(int x, int y)
300 puDeactivateWidget();
302 SG_LOG(SG_GENERAL, SG_INFO, "doDrop");
304 if ((y < abox.min[1]) || (y >= abox.max[1])) {
305 SG_LOG(SG_GENERAL, SG_INFO, "y out of bounds:" << y);
309 if (_dragSourceRow == _dragTargetRow) {
310 SG_LOG(SG_GENERAL, SG_INFO, "source and target row match");
314 _model->moveWaypointToIndex(_dragSourceRow, _dragTargetRow);
316 // keep row indexes linged up when moving an item down the list
317 if (_dragSourceRow < _dragTargetRow) {
321 setSelected(_dragTargetRow);
324 void WaypointList::invokeDownCallback(void)
327 _dragScroll = SCROLL_NO;
328 SG_LOG(SG_GENERAL, SG_INFO, "cancel drag");
331 int WaypointList::rowForY(int y) const
337 // flip y to increase down, not up (as rows do)
338 int flipY = _heightPx - y;
339 int row = (flipY + _scrollPx) / rowHeightPx();
343 void WaypointList::draw( int dx, int dy )
345 puFrame::draw(dx, dy);
351 if (_dragScroll != SCROLL_NO) {
355 double dt = (SGTimeStamp::now() - _blinkTimer).toSecs();
356 if (dt > BLINK_TIME) {
361 glEnable(GL_SCISSOR_TEST);
362 GLint sx = (int) abox.min[0],
364 GLsizei w = (GLsizei) abox.max[0] - abox.min[0],
367 sx += border_thickness;
368 sy += border_thickness;
369 w -= 2 * border_thickness;
370 h -= 2 * border_thickness;
372 glScissor(sx + dx, sy + dy, w, h);
373 int row = firstVisibleRow(),
374 final = lastVisibleRow(),
375 rowHeight = rowHeightPx(),
378 y -= (_scrollPx % rowHeight); // partially draw the first row
380 _arrowWidth = legendFont.getStringWidth(">");
382 RoutePath path(_model->flightplan());
384 for ( ; row <= final; ++row, y += rowHeight) {
385 drawRow(dx, dy, row, y, path);
386 } // of row drawing iteration
388 glDisable(GL_SCISSOR_TEST);
391 // draw the insert marker after the rows
392 int insertY = (_dragTargetRow * rowHeight) - _scrollPx;
393 SG_CLAMP_RANGE(insertY, 0, std::min(_heightPx, totalHeightPx()));
395 glColor4f(1.0f, 0.5f, 0.0f, 0.8f);
398 glVertex2f(dx + abox.min[0], dy + abox.max[1] - insertY);
399 glVertex2f(dx + abox.max[0], dy + abox.max[1] - insertY);
404 void WaypointList::drawRow(int dx, int dy, int rowIndex, int y,
405 const RoutePath& path)
407 flightgear::Waypt* wp(_model->waypointAt(rowIndex));
409 bool isSelected = (rowIndex == getSelected());
410 bool isCurrent = (rowIndex == _model->currentWaypoint());
411 bool isDragSource = (_dragging && (rowIndex == _dragSourceRow));
414 bkgBox.min[1] = abox.max[1] - y;
415 bkgBox.max[1] = bkgBox.min[1] + rowHeightPx();
418 puFont* f = &legendFont;
419 bool drawBox = false;
421 if (wp->flag(WPT_MISS)) {
423 puSetColor(col, 1.0, 0.0, 0.0, 0.3); // red
424 } else if (wp->flag(WPT_ARRIVAL) || wp->flag(WPT_DEPARTURE)) {
426 puSetColor(col, 0.0, 0.0, 0.0, 0.3);
427 } else if (wp->flag(WPT_APPROACH)) {
429 puSetColor(col, 0.0, 0.0, 0.1, 0.3);
433 // draw later, on *top* of text string
434 } else if (isSelected) { // -PLAIN means selected, apparently
435 bkgBox.draw(dx, dy, -PUSTYLE_PLAIN, colour, false, 0);
436 } else if (drawBox) {
437 bkgBox.draw(dx, dy, PUSTYLE_PLAIN, &col, false, 0);
441 glColor4f (1.0, 0.5, 0.0, 1.0) ;
443 glColor4fv ( colour [ PUCOL_LEGEND ] ) ;
446 int xx = dx + abox.min[0] + PUSTR_LGAP;
447 int yy = dy + abox.max[1] - y ;
448 yy += 4; // center text in row height
451 f->drawString(">", xx, yy);
455 x += _arrowWidth + PUSTR_LGAP;
456 drawRowText(x, yy, rowIndex, path);
460 puSetColor(col, 1.0, 0.5, 0.0, 0.5);
461 bkgBox.draw(dx, dy, PUSTYLE_PLAIN, &col, false, 0);
465 void WaypointList::drawRowText(int x, int baseline, int rowIndex, const RoutePath& path)
467 flightgear::Waypt* wp(_model->waypointAt(rowIndex));
468 const bool isDiscontinuity = (wp->type() == "discontinuity");
469 const bool isVia = (wp->type() == "via");
475 // VIA has long ident but no name
476 count = ::snprintf(buffer, 128, "%03d %s", rowIndex, wp->ident().c_str());
477 drawClippedString(legendFont, buffer, x, baseline, 300);
478 x += 300 + PUSTR_LGAP;
480 count = ::snprintf(buffer, 128, "%03d %-5s", rowIndex, wp->ident().c_str());
482 FGPositioned* src = wp->source();
483 if (src && !src->name().empty() && (src->name() != wp->ident())) {
484 // append name if present, and different to id
485 ::snprintf(buffer + count, 128 - count, " (%s)", src->name().c_str());
488 drawClippedString(legendFont, buffer, x, baseline, 300);
489 x += 300 + PUSTR_LGAP;
491 if (isDiscontinuity) {
496 // only show for non-dynamic waypoints
497 if (!wp->flag(WPT_DYNAMIC)) {
498 SGGeod p(wp->position());
499 char ns = (p.getLatitudeDeg() > 0.0) ? 'N' : 'S';
500 char ew = (p.getLongitudeDeg() > 0.0) ? 'E' : 'W';
502 ::snprintf(buffer, 128 - count, "%4.2f%c %4.2f%c",
503 fabs(p.getLongitudeDeg()), ew, fabs(p.getLatitudeDeg()), ns);
507 } else if (rowIndex > 0) {
508 double courseDeg = path.trackForIndex(rowIndex);
509 double distanceM = path.distanceForIndex(rowIndex);
510 ::snprintf(buffer, 128 - count, "%03.0f %5.1fnm",
511 courseDeg, distanceM * SG_METER_TO_NM);
513 } // of is not a VIA waypoint
515 puFont* f = &legendFont;
516 f->drawString(buffer, x, baseline);
517 x += 100 + PUSTR_LGAP;
519 if (wp->altitudeRestriction() != RESTRICT_NONE) {
520 char aboveAtBelow = ' ';
521 if (wp->altitudeRestriction() == RESTRICT_ABOVE) {
523 } else if (wp->altitudeRestriction() == RESTRICT_BELOW) {
527 int altHundredFt = (wp->altitudeFt() + 50) / 100; // round to nearest 100ft
528 if (altHundredFt < 100) {
529 count = ::snprintf(buffer, 128, "%d'%c", altHundredFt * 100, aboveAtBelow);
530 } else { // display as a flight-level
531 count = ::snprintf(buffer, 128, "FL%d%c", altHundredFt, aboveAtBelow);
534 f->drawString(buffer, x, baseline);
535 } // of valid wp altitude
536 x += 60 + PUSTR_LGAP;
538 if (wp->speedRestriction() == SPEED_RESTRICT_MACH) {
539 count = ::snprintf(buffer, 126, "%03.2fM", wp->speedMach());
540 f->drawString(buffer, x, baseline);
541 } else if (wp->speedRestriction() != RESTRICT_NONE) {
542 count = ::snprintf(buffer, 126, "%dKts", (int) wp->speedKts());
543 f->drawString(buffer, x, baseline);
547 const double SCROLL_PX_SEC = 200.0;
549 void WaypointList::doDragScroll()
551 double dt = (SGTimeStamp::now() - _dragScrollTime).toSecs();
552 _dragScrollTime.stamp();
553 int deltaPx = (int)(dt * SCROLL_PX_SEC);
555 if (_dragScroll == SCROLL_UP) {
556 _scrollPx = _scrollPx - deltaPx;
557 SG_CLAMP_RANGE(_scrollPx, 0, scrollRangePx());
558 _dragTargetRow = firstVisibleRow();
560 _scrollPx = _scrollPx + deltaPx;
561 SG_CLAMP_RANGE(_scrollPx, 0, scrollRangePx());
562 _dragTargetRow = lastFullyVisibleRow() + 1;
565 if (_scrollCallback) {
566 (*_scrollCallback)();
570 int WaypointList::getSelected()
572 return getIntegerValue();
575 void WaypointList::setSelected(int rowIndex)
577 if (rowIndex == getSelected()) {
583 if (rowIndex == -1) {
587 ensureRowVisible(rowIndex);
590 void WaypointList::ensureRowVisible(int rowIndex)
592 if ((rowIndex >= firstFullyVisibleRow()) && (rowIndex <= lastFullyVisibleRow())) {
593 return; // already visible, fine
596 // ideal position would place the desired row in the middle of the
597 // visible section - hence subtract half the visible height.
598 int targetScrollPx = (rowIndex * rowHeightPx()) - (_heightPx / 2);
600 // clamp the scroll value to something valid
601 SG_CLAMP_RANGE(targetScrollPx, 0, scrollRangePx());
602 _scrollPx = targetScrollPx;
605 if (_scrollCallback) { // keep scroll observers in sync
606 (*_scrollCallback)();
610 unsigned int WaypointList::numWaypoints() const
616 return _model->numWaypoints();
619 bool WaypointList::wantsVScroll() const
621 return totalHeightPx() > _heightPx;
624 float WaypointList::getVScrollPercent() const
626 float scrollRange = scrollRangePx();
627 if (scrollRange < 1.0f) {
631 return _scrollPx / scrollRange;
634 float WaypointList::getVScrollThumbPercent() const
636 return _heightPx / (float) totalHeightPx();
639 void WaypointList::setVScrollPercent(float perc)
641 float scrollRange = scrollRangePx();
642 _scrollPx = (int)(scrollRange * perc);
645 int WaypointList::firstVisibleRow() const
647 return _scrollPx / rowHeightPx();
650 int WaypointList::firstFullyVisibleRow() const
652 int rh = rowHeightPx();
653 return (_scrollPx + rh - 1) / rh;
656 int WaypointList::numVisibleRows() const
658 int rh = rowHeightPx();
659 int topOffset = _scrollPx % rh; // pixels of first visible row
660 return (_heightPx - topOffset + rh - 1) / rh;
664 int WaypointList::numFullyVisibleRows() const
666 int rh = rowHeightPx();
667 int topOffset = _scrollPx % rh; // pixels of first visible row
668 return (_heightPx - topOffset) / rh;
671 int WaypointList::rowHeightPx() const
673 return legendFont.getStringHeight() + PUSTR_BGAP;
676 int WaypointList::scrollRangePx() const
678 return std::max(0, totalHeightPx() - _heightPx);
681 int WaypointList::totalHeightPx() const
687 return (int) _model->numWaypoints() * rowHeightPx();
690 int WaypointList::lastFullyVisibleRow() const
692 int row = firstFullyVisibleRow() + numFullyVisibleRows();
693 return std::min(row, (int) _model->numWaypoints() - 1);
696 int WaypointList::lastVisibleRow() const
698 int row = firstVisibleRow() + numVisibleRows();
699 return std::min(row, (int) _model->numWaypoints() - 1);
702 void WaypointList::setModel(Model* model)
709 _model->setUpdateCallback(make_callback(this, &WaypointList::modelUpdateCallback));
714 int WaypointList::checkKey (int key, int updown )
716 if ((updown == PU_UP) || !isVisible () || !isActive () || (window != puGetWindow())) {
720 #ifdef AVOID_FLIGHT_KEYS
731 setSelected(_model->numWaypoints() - 1);
735 case PU_KEY_PAGE_UP :
736 if (getSelected() >= 0) {
737 setSelected(getSelected() - 1);
742 case PU_KEY_PAGE_DOWN : {
743 int newSel = getSelected() + 1;
744 if (newSel >= (int) _model->numWaypoints()) {
753 if (getSelected() >= 0) {
754 Waypt* wp = _model->waypointAt(getSelected());
755 if (wp->flag(WPT_GENERATED)) {
759 if (wp->altitudeRestriction() != RESTRICT_NONE) {
760 int curAlt = (static_cast<int>(wp->altitudeFt()) + 50) / 100;
762 wp->setAltitude(0, RESTRICT_NONE);
764 wp->setAltitude((curAlt - 10) * 100, wp->altitudeRestriction());
771 if (getSelected() >= 0) {
772 flightgear::Waypt* wp = _model->waypointAt(getSelected());
773 if (wp->flag(WPT_GENERATED)) {
777 if (wp->altitudeRestriction() == RESTRICT_NONE) {
778 wp->setAltitude(1000, RESTRICT_AT);
780 int curAlt = (static_cast<int>(wp->altitudeFt()) + 50) / 100;
781 wp->setAltitude((curAlt + 10) * 100, wp->altitudeRestriction());
787 if (getSelected() >= 0) {
788 int index = getSelected();
789 Waypt* wp = _model->waypointAt(getSelected());
790 if (wp->flag(WPT_GENERATED)) {
794 _model->deleteAt(index);
795 setSelected(index - 1);
806 void WaypointList::modelUpdateCallback()
810 if (_updateCallback) {
811 (*_updateCallback)();
815 //////////////////////////////////////////////////////////////////////////////
818 static void handle_scrollbar(puObject* scrollbar)
820 ScrolledWaypointList* self = (ScrolledWaypointList*)scrollbar->getUserData();
821 self->setScrollPercent(scrollbar->getFloatValue());
824 static void waypointListCb(puObject* wpl)
826 ScrolledWaypointList* self = (ScrolledWaypointList*)wpl->getUserData();
827 self->setValue(wpl->getIntegerValue());
828 self->invokeCallback();
831 ScrolledWaypointList::ScrolledWaypointList(int x, int y, int width, int height) :
835 // ensure our type is compound, so fgPopup::applySize doesn't descend into
836 // us, and try to cast our children's user-data to GUIInfo*.
837 type |= PUCLASS_LIST;
842 void ScrolledWaypointList::setValue(float v)
844 puGroup::setValue(v);
848 void ScrolledWaypointList::setValue(int v)
850 puGroup::setValue(v);
854 void ScrolledWaypointList::init(int w, int h)
856 _list = new WaypointList(0, 0, w, h);
857 _list->setUpdateCallback(make_callback(this, &ScrolledWaypointList::modelUpdated));
858 _hasVScroll = _list->wantsVScroll();
859 _list->setUserData(this);
860 _list->setCallback(waypointListCb);
862 _list->setScrollCallback(make_callback(this, &ScrolledWaypointList::updateScroll));
864 _scrollbar = new puaScrollBar(w - _scrollWidth, 0, h,
865 1 /*arrow*/, 1 /* vertical */, _scrollWidth);
866 _scrollbar->setMinValue(0.0);
867 _scrollbar->setMaxValue(1.0);
868 _scrollbar->setUserData(this);
869 _scrollbar->setCallback(handle_scrollbar);
870 close(); // close the group
875 void ScrolledWaypointList::modelUpdated()
879 updateWantsScroll(w, h);
882 void ScrolledWaypointList::setScrollPercent(float v)
884 // slider's min is the bottom, so invert the value
885 _list->setVScrollPercent(1.0f - v);
888 void ScrolledWaypointList::setSize(int w, int h)
890 updateWantsScroll(w, h);
891 puGroup::setSize(w, h);
894 void ScrolledWaypointList::updateWantsScroll(int w, int h)
896 _hasVScroll = _list->wantsVScroll();
899 _scrollbar->reveal();
900 _scrollbar->setPosition(w - _scrollWidth, 0);
901 _scrollbar->setSize(_scrollWidth, h);
902 _list->setSize(w - _scrollWidth, h);
906 _list->setSize(w, h);
910 void ScrolledWaypointList::updateScroll()
912 // _scrollbar->setMaxValue(_list->numWaypoints());
913 _scrollbar->setValue(1.0f - _list->getVScrollPercent());
914 _scrollbar->setSliderFraction(_list->getVScrollThumbPercent());