7 #include "WaypointList.hxx"
10 #include <boost/tuple/tuple.hpp>
12 #include <plib/puAux.h>
14 #include <simgear/route/waypoint.hxx>
15 #include <simgear/structure/callback.hxx>
16 #include <simgear/sg_inlines.h>
18 #include <Main/globals.hxx>
19 #include <Main/fg_props.hxx>
21 #include <Navaids/positioned.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 routeEdited->addChangeListener(this);
52 ~FlightPlanWaypointModel()
54 SGPropertyNode* routeEdited = fgGetNode("/autopilot/route-manager/signals/edited", true);
55 routeEdited->removeChangeListener(this);
58 // implement WaypointList::Model
59 virtual unsigned int numWaypoints() const
61 return _fp->numLegs();
64 virtual int currentWaypoint() const
66 return _fp->currentIndex();
69 virtual flightgear::Waypt* waypointAt(unsigned int index) const
71 if (index >= numWaypoints()) {
75 return _fp->legAtIndex(index)->waypoint();
78 virtual void deleteAt(unsigned int index)
80 _fp->deleteIndex(index);
83 virtual void moveWaypointToIndex(unsigned int srcIndex, unsigned int destIndex)
85 SG_LOG(SG_GENERAL, SG_INFO, "moveWaypoint: from " << srcIndex << " to " << destIndex);
86 if (destIndex > srcIndex) {
90 unsigned int currentWpIndex = currentWaypoint();
91 WayptRef w(waypointAt(currentWpIndex));
92 _fp->deleteIndex(currentWpIndex);
94 SG_LOG(SG_GENERAL, SG_INFO, "wpt:" << w->ident());
95 _fp->insertWayptAtIndex(w, destIndex);
97 if (srcIndex == currentWpIndex) {
98 // current waypoint was moved
99 _fp->setCurrentIndex(destIndex);
103 virtual void setUpdateCallback(SGCallback* cb)
108 // implement SGPropertyChangeListener
109 void valueChanged(SGPropertyNode *prop)
116 flightgear::FlightPlan* _fp;
120 //////////////////////////////////////////////////////////////////////////////
122 static void drawClippedString(puFont& font, const char* s, int x, int y, int maxWidth)
124 int fullWidth = font.getStringWidth(s);
125 if (fullWidth <= maxWidth) { // common case, easy and efficent
126 font.drawString(s, x, y);
131 int len = buf.size();
134 fullWidth = font.getStringWidth(buf.c_str());
135 } while (fullWidth > maxWidth);
137 font.drawString(buf.c_str(), x, y);
140 //////////////////////////////////////////////////////////////////////////////
142 WaypointList::WaypointList(int x, int y, int width, int height) :
143 puFrame(x, y, width, height),
144 GUI_ID(FGCLASS_WAYPOINTLIST),
147 _dragScroll(SCROLL_NO),
150 _updateCallback(NULL),
151 _scrollCallback(NULL),
154 // pretend to be a list, so fgPopup doesn't mess with our mouse events
155 type |= PUCLASS_LIST;
156 flightgear::FlightPlan* fp =
157 static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"))->flightPlan();
158 setModel(new FlightPlanWaypointModel(fp));
159 setSize(width, height);
165 WaypointList::~WaypointList()
168 delete _updateCallback;
169 delete _scrollCallback;
172 void WaypointList::setUpdateCallback(SGCallback* cb)
174 _updateCallback = cb;
177 void WaypointList::setScrollCallback(SGCallback* cb)
179 _scrollCallback = cb;
182 void WaypointList::setSize(int width, int height)
184 double scrollP = getVScrollPercent();
186 puFrame::setSize(width, height);
188 if (wantsVScroll()) {
189 setVScrollPercent(scrollP);
195 int WaypointList::checkHit ( int button, int updown, int x, int y )
197 if ( isHit( x, y ) || ( puActiveWidget () == this ) )
199 doHit ( button, updown, x, y ) ;
207 void WaypointList::doHit( int button, int updown, int x, int y )
209 puFrame::doHit(button, updown, x, y);
210 if (updown == PU_DRAG) {
215 if (button != active_mouse_button) {
219 if (updown == PU_UP) {
220 puDeactivateWidget();
225 } else if (updown == PU_DOWN) {
226 puSetActiveWidget(this, x, y);
233 int row = rowForY(y - abox.min[1]);
234 if (row >= (int) _model->numWaypoints()) {
235 row = -1; // 'no selection'
238 if (row == getSelected()) {
239 _showLatLon = !_showLatLon;
247 void WaypointList::handleDrag(int x, int y)
250 // don't start drags immediately, require a certain mouse movement first
251 int manhattanLength = abs(x - _mouseDownX) + abs(y - _mouseDownY);
252 if (manhattanLength < DRAG_START_DISTANCE_PX) {
256 _dragSourceRow = rowForY(y - abox.min[1]);
257 Waypt* wp = _model->waypointAt(_dragSourceRow);
258 if (!wp || wp->flag(WPT_GENERATED)) {
259 return; // don't allow generated points to be dragged
263 _dragScroll = SCROLL_NO;
266 if (y < abox.min[1]) {
267 if (_dragScroll != SCROLL_DOWN) {
268 _dragScroll = SCROLL_DOWN;
269 _dragScrollTime.stamp();
271 } else if (y > abox.max[1]) {
272 if (_dragScroll != SCROLL_UP) {
273 _dragScroll = SCROLL_UP;
274 _dragScrollTime.stamp();
277 _dragScroll = SCROLL_NO;
278 _dragTargetRow = rowForY(y - abox.min[1] - (rowHeightPx() / 2));
282 void WaypointList::doDrop(int x, int y)
285 puDeactivateWidget();
287 SG_LOG(SG_GENERAL, SG_INFO, "doDrop");
289 if ((y < abox.min[1]) || (y >= abox.max[1])) {
290 SG_LOG(SG_GENERAL, SG_INFO, "y out of bounds:" << y);
294 if (_dragSourceRow == _dragTargetRow) {
295 SG_LOG(SG_GENERAL, SG_INFO, "source and target row match");
299 _model->moveWaypointToIndex(_dragSourceRow, _dragTargetRow);
301 // keep row indexes linged up when moving an item down the list
302 if (_dragSourceRow < _dragTargetRow) {
306 setSelected(_dragTargetRow);
309 void WaypointList::invokeDownCallback(void)
312 _dragScroll = SCROLL_NO;
313 SG_LOG(SG_GENERAL, SG_INFO, "cancel drag");
316 int WaypointList::rowForY(int y) const
322 // flip y to increase down, not up (as rows do)
323 int flipY = _heightPx - y;
324 int row = (flipY + _scrollPx) / rowHeightPx();
328 void WaypointList::draw( int dx, int dy )
330 puFrame::draw(dx, dy);
336 if (_dragScroll != SCROLL_NO) {
340 double dt = (SGTimeStamp::now() - _blinkTimer).toSecs();
341 if (dt > BLINK_TIME) {
346 glEnable(GL_SCISSOR_TEST);
347 GLint sx = (int) abox.min[0],
349 GLsizei w = (GLsizei) abox.max[0] - abox.min[0],
352 sx += border_thickness;
353 sy += border_thickness;
354 w -= 2 * border_thickness;
355 h -= 2 * border_thickness;
357 glScissor(sx + dx, sy + dy, w, h);
358 int row = firstVisibleRow(),
359 final = lastVisibleRow(),
360 rowHeight = rowHeightPx(),
363 y -= (_scrollPx % rowHeight); // partially draw the first row
365 _arrowWidth = legendFont.getStringWidth(">");
366 for ( ; row <= final; ++row, y += rowHeight) {
367 drawRow(dx, dy, row, y);
368 } // of row drawing iteration
370 glDisable(GL_SCISSOR_TEST);
373 // draw the insert marker after the rows
374 int insertY = (_dragTargetRow * rowHeight) - _scrollPx;
375 SG_CLAMP_RANGE(insertY, 0, std::min(_heightPx, totalHeightPx()));
377 glColor4f(1.0f, 0.5f, 0.0f, 0.8f);
380 glVertex2f(dx + abox.min[0], dy + abox.max[1] - insertY);
381 glVertex2f(dx + abox.max[0], dy + abox.max[1] - insertY);
386 void WaypointList::drawRow(int dx, int dy, int rowIndex, int y)
388 flightgear::Waypt* wp(_model->waypointAt(rowIndex));
390 bool isSelected = (rowIndex == getSelected());
391 bool isCurrent = (rowIndex == _model->currentWaypoint());
392 bool isDragSource = (_dragging && (rowIndex == _dragSourceRow));
395 bkgBox.min[1] = abox.max[1] - y;
396 bkgBox.max[1] = bkgBox.min[1] + rowHeightPx();
399 puFont* f = &legendFont;
400 bool drawBox = false;
402 if (wp->flag(WPT_MISS)) {
404 puSetColor(col, 1.0, 0.0, 0.0, 0.3); // red
405 } else if (wp->flag(WPT_ARRIVAL)) {
407 puSetColor(col, 0.0, 0.0, 0.0, 0.3);
408 } else if (wp->flag(WPT_DEPARTURE)) {
410 puSetColor(col, 0.0, 0.0, 0.0, 0.3);
414 // draw later, on *top* of text string
415 } else if (isSelected) { // -PLAIN means selected, apparently
416 bkgBox.draw(dx, dy, -PUSTYLE_PLAIN, colour, false, 0);
417 } else if (drawBox) {
418 bkgBox.draw(dx, dy, PUSTYLE_PLAIN, &col, false, 0);
422 glColor4f (1.0, 0.5, 0.0, 1.0) ;
424 glColor4fv ( colour [ PUCOL_LEGEND ] ) ;
427 int xx = dx + abox.min[0] + PUSTR_LGAP;
428 int yy = dy + abox.max[1] - y ;
429 yy += 4; // center text in row height
432 f->drawString(">", xx, yy);
436 x += _arrowWidth + PUSTR_LGAP;
441 int count = ::snprintf(buffer, 128, "%03d %-5s", rowIndex, wp->ident().c_str());
443 FGPositioned* src = wp->source();
444 if (src && !src->name().empty() && (src->name() != wp->ident())) {
445 // append name if present, and different to id
446 ::snprintf(buffer + count, 128 - count, " (%s)", src->name().c_str());
449 drawClippedString(legendFont, buffer, x, yy, 300);
450 x += 300 + PUSTR_LGAP;
453 SGGeod p(wp->position());
454 char ns = (p.getLatitudeDeg() > 0.0) ? 'N' : 'S';
455 char ew = (p.getLongitudeDeg() > 0.0) ? 'E' : 'W';
457 ::snprintf(buffer, 128 - count, "%4.2f%c %4.2f%c",
458 fabs(p.getLongitudeDeg()), ew, fabs(p.getLatitudeDeg()), ns);
459 } else if (rowIndex > 0) {
462 Waypt* prev = _model->waypointAt(rowIndex - 1);
463 boost::tie(courseDeg, distanceM) = wp->courseAndDistanceFrom(prev->position());
465 ::snprintf(buffer, 128 - count, "%03.0f %5.1fnm",
466 courseDeg, distanceM * SG_METER_TO_NM);
469 f->drawString(buffer, x, yy);
470 x += 100 + PUSTR_LGAP;
472 if (wp->altitudeRestriction() != RESTRICT_NONE) {
473 int altHundredFt = (wp->altitudeFt() + 50) / 100; // round to nearest 100ft
474 if (altHundredFt < 100) {
475 count = ::snprintf(buffer, 128, "%d'", altHundredFt * 100);
476 } else { // display as a flight-level
477 count = ::snprintf(buffer, 128, "FL%d", altHundredFt);
480 f->drawString(buffer, x, yy);
481 } // of valid wp altitude
482 x += 60 + PUSTR_LGAP;
484 if (wp->speedRestriction() == SPEED_RESTRICT_MACH) {
485 count = ::snprintf(buffer, 126, "%03.2fM", wp->speedMach());
486 f->drawString(buffer, x, yy);
487 } else if (wp->speedRestriction() != RESTRICT_NONE) {
488 count = ::snprintf(buffer, 126, "%dKts", (int) wp->speedKts());
489 f->drawString(buffer, x, yy);
493 puSetColor(col, 1.0, 0.5, 0.0, 0.5);
494 bkgBox.draw(dx, dy, PUSTYLE_PLAIN, &col, false, 0);
498 const double SCROLL_PX_SEC = 200.0;
500 void WaypointList::doDragScroll()
502 double dt = (SGTimeStamp::now() - _dragScrollTime).toSecs();
503 _dragScrollTime.stamp();
504 int deltaPx = (int)(dt * SCROLL_PX_SEC);
506 if (_dragScroll == SCROLL_UP) {
507 _scrollPx = _scrollPx - deltaPx;
508 SG_CLAMP_RANGE(_scrollPx, 0, scrollRangePx());
509 _dragTargetRow = firstVisibleRow();
511 _scrollPx = _scrollPx + deltaPx;
512 SG_CLAMP_RANGE(_scrollPx, 0, scrollRangePx());
513 _dragTargetRow = lastFullyVisibleRow() + 1;
516 if (_scrollCallback) {
517 (*_scrollCallback)();
521 int WaypointList::getSelected()
523 return getIntegerValue();
526 void WaypointList::setSelected(int rowIndex)
528 if (rowIndex == getSelected()) {
534 if (rowIndex == -1) {
538 ensureRowVisible(rowIndex);
541 void WaypointList::ensureRowVisible(int rowIndex)
543 if ((rowIndex >= firstFullyVisibleRow()) && (rowIndex <= lastFullyVisibleRow())) {
544 return; // already visible, fine
547 // ideal position would place the desired row in the middle of the
548 // visible section - hence subtract half the visible height.
549 int targetScrollPx = (rowIndex * rowHeightPx()) - (_heightPx / 2);
551 // clamp the scroll value to something valid
552 SG_CLAMP_RANGE(targetScrollPx, 0, scrollRangePx());
553 _scrollPx = targetScrollPx;
556 if (_scrollCallback) { // keep scroll observers in sync
557 (*_scrollCallback)();
561 unsigned int WaypointList::numWaypoints() const
567 return _model->numWaypoints();
570 bool WaypointList::wantsVScroll() const
572 return totalHeightPx() > _heightPx;
575 float WaypointList::getVScrollPercent() const
577 float scrollRange = scrollRangePx();
578 if (scrollRange < 1.0f) {
582 return _scrollPx / scrollRange;
585 float WaypointList::getVScrollThumbPercent() const
587 return _heightPx / (float) totalHeightPx();
590 void WaypointList::setVScrollPercent(float perc)
592 float scrollRange = scrollRangePx();
593 _scrollPx = (int)(scrollRange * perc);
596 int WaypointList::firstVisibleRow() const
598 return _scrollPx / rowHeightPx();
601 int WaypointList::firstFullyVisibleRow() const
603 int rh = rowHeightPx();
604 return (_scrollPx + rh - 1) / rh;
607 int WaypointList::numVisibleRows() const
609 int rh = rowHeightPx();
610 int topOffset = _scrollPx % rh; // pixels of first visible row
611 return (_heightPx - topOffset + rh - 1) / rh;
615 int WaypointList::numFullyVisibleRows() const
617 int rh = rowHeightPx();
618 int topOffset = _scrollPx % rh; // pixels of first visible row
619 return (_heightPx - topOffset) / rh;
622 int WaypointList::rowHeightPx() const
624 return legendFont.getStringHeight() + PUSTR_BGAP;
627 int WaypointList::scrollRangePx() const
629 return std::max(0, totalHeightPx() - _heightPx);
632 int WaypointList::totalHeightPx() const
638 return (int) _model->numWaypoints() * rowHeightPx();
641 int WaypointList::lastFullyVisibleRow() const
643 int row = firstFullyVisibleRow() + numFullyVisibleRows();
644 return std::min(row, (int) _model->numWaypoints() - 1);
647 int WaypointList::lastVisibleRow() const
649 int row = firstVisibleRow() + numVisibleRows();
650 return std::min(row, (int) _model->numWaypoints() - 1);
653 void WaypointList::setModel(Model* model)
660 _model->setUpdateCallback(make_callback(this, &WaypointList::modelUpdateCallback));
665 int WaypointList::checkKey (int key, int updown )
667 if ((updown == PU_UP) || !isVisible () || !isActive () || (window != puGetWindow())) {
671 #ifdef AVOID_FLIGHT_KEYS
682 setSelected(_model->numWaypoints() - 1);
686 case PU_KEY_PAGE_UP :
687 if (getSelected() >= 0) {
688 setSelected(getSelected() - 1);
693 case PU_KEY_PAGE_DOWN : {
694 int newSel = getSelected() + 1;
695 if (newSel >= (int) _model->numWaypoints()) {
704 if (getSelected() >= 0) {
705 Waypt* wp = _model->waypointAt(getSelected());
706 if (wp->flag(WPT_GENERATED)) {
710 if (wp->altitudeRestriction() != RESTRICT_NONE) {
711 int curAlt = (static_cast<int>(wp->altitudeFt()) + 50) / 100;
713 wp->setAltitude(0, RESTRICT_NONE);
715 wp->setAltitude((curAlt - 10) * 100, wp->altitudeRestriction());
722 if (getSelected() >= 0) {
723 flightgear::Waypt* wp = _model->waypointAt(getSelected());
724 if (wp->flag(WPT_GENERATED)) {
728 if (wp->altitudeRestriction() == RESTRICT_NONE) {
729 wp->setAltitude(1000, RESTRICT_AT);
731 int curAlt = (static_cast<int>(wp->altitudeFt()) + 50) / 100;
732 wp->setAltitude((curAlt + 10) * 100, wp->altitudeRestriction());
738 if (getSelected() >= 0) {
739 int index = getSelected();
740 Waypt* wp = _model->waypointAt(getSelected());
741 if (wp->flag(WPT_GENERATED)) {
745 _model->deleteAt(index);
746 setSelected(index - 1);
757 void WaypointList::modelUpdateCallback()
761 if (_updateCallback) {
762 (*_updateCallback)();
766 //////////////////////////////////////////////////////////////////////////////
769 static void handle_scrollbar(puObject* scrollbar)
771 ScrolledWaypointList* self = (ScrolledWaypointList*)scrollbar->getUserData();
772 self->setScrollPercent(scrollbar->getFloatValue());
775 static void waypointListCb(puObject* wpl)
777 ScrolledWaypointList* self = (ScrolledWaypointList*)wpl->getUserData();
778 self->setValue(wpl->getIntegerValue());
779 self->invokeCallback();
782 ScrolledWaypointList::ScrolledWaypointList(int x, int y, int width, int height) :
786 // ensure our type is compound, so fgPopup::applySize doesn't descend into
787 // us, and try to cast our children's user-data to GUIInfo*.
788 type |= PUCLASS_LIST;
793 void ScrolledWaypointList::setValue(float v)
795 puGroup::setValue(v);
799 void ScrolledWaypointList::setValue(int v)
801 puGroup::setValue(v);
805 void ScrolledWaypointList::init(int w, int h)
807 _list = new WaypointList(0, 0, w, h);
808 _list->setUpdateCallback(make_callback(this, &ScrolledWaypointList::modelUpdated));
809 _hasVScroll = _list->wantsVScroll();
810 _list->setUserData(this);
811 _list->setCallback(waypointListCb);
813 _list->setScrollCallback(make_callback(this, &ScrolledWaypointList::updateScroll));
815 _scrollbar = new puaScrollBar(w - _scrollWidth, 0, h,
816 1 /*arrow*/, 1 /* vertical */, _scrollWidth);
817 _scrollbar->setMinValue(0.0);
818 _scrollbar->setMaxValue(1.0);
819 _scrollbar->setUserData(this);
820 _scrollbar->setCallback(handle_scrollbar);
821 close(); // close the group
826 void ScrolledWaypointList::modelUpdated()
830 updateWantsScroll(w, h);
833 void ScrolledWaypointList::setScrollPercent(float v)
835 // slider's min is the bottom, so invert the value
836 _list->setVScrollPercent(1.0f - v);
839 void ScrolledWaypointList::setSize(int w, int h)
841 updateWantsScroll(w, h);
842 puGroup::setSize(w, h);
845 void ScrolledWaypointList::updateWantsScroll(int w, int h)
847 _hasVScroll = _list->wantsVScroll();
850 _scrollbar->reveal();
851 _scrollbar->setPosition(w - _scrollWidth, 0);
852 _scrollbar->setSize(_scrollWidth, h);
853 _list->setSize(w - _scrollWidth, h);
857 _list->setSize(w, h);
861 void ScrolledWaypointList::updateScroll()
863 // _scrollbar->setMaxValue(_list->numWaypoints());
864 _scrollbar->setValue(1.0f - _list->getVScrollPercent());
865 _scrollbar->setSliderFraction(_list->getVScrollThumbPercent());