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 using namespace flightgear;
32 static const double BLINK_TIME = 0.3;
33 static const int DRAG_START_DISTANCE_PX = 5;
35 class RouteManagerWaypointModel :
36 public WaypointList::Model,
37 public SGPropertyChangeListener
40 RouteManagerWaypointModel()
42 _rm = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
44 SGPropertyNode* routeEdited = fgGetNode("/autopilot/route-manager/signals/edited", true);
45 routeEdited->addChangeListener(this);
48 virtual ~RouteManagerWaypointModel()
50 SGPropertyNode* routeEdited = fgGetNode("/autopilot/route-manager/signals/edited", true);
51 routeEdited->removeChangeListener(this);
54 // implement WaypointList::Model
55 virtual unsigned int numWaypoints() const
57 return _rm->numWaypts();
60 virtual int currentWaypoint() const
62 return _rm->currentIndex();
65 virtual flightgear::Waypt* waypointAt(unsigned int index) const
67 if (index >= numWaypoints()) {
71 return _rm->wayptAtIndex(index);
74 virtual void deleteAt(unsigned int index)
76 _rm->removeWayptAtIndex(index);
79 virtual void moveWaypointToIndex(unsigned int srcIndex, unsigned int destIndex)
81 SG_LOG(SG_GENERAL, SG_INFO, "moveWaypoint: from " << srcIndex << " to " << destIndex);
82 if (destIndex > srcIndex) {
86 unsigned int currentWpIndex = currentWaypoint();
87 WayptRef w(_rm->removeWayptAtIndex(srcIndex));
88 SG_LOG(SG_GENERAL, SG_INFO, "wpt:" << w->ident());
89 _rm->insertWayptAtIndex(w, destIndex);
91 if (srcIndex == currentWpIndex) {
92 // current waypoint was moved
93 _rm->jumpToIndex(destIndex);
97 virtual void setUpdateCallback(SGCallback* cb)
102 // implement SGPropertyChangeListener
103 void valueChanged(SGPropertyNode *prop)
114 //////////////////////////////////////////////////////////////////////////////
116 static void drawClippedString(puFont& font, const char* s, int x, int y, int maxWidth)
118 int fullWidth = font.getStringWidth(s);
119 if (fullWidth <= maxWidth) { // common case, easy and efficent
120 font.drawString(s, x, y);
125 int len = buf.size();
128 fullWidth = font.getStringWidth(buf.c_str());
129 } while (fullWidth > maxWidth);
131 font.drawString(buf.c_str(), x, y);
134 //////////////////////////////////////////////////////////////////////////////
136 WaypointList::WaypointList(int x, int y, int width, int height) :
137 puFrame(x, y, width, height),
138 GUI_ID(FGCLASS_WAYPOINTLIST),
141 _dragScroll(SCROLL_NO),
144 _updateCallback(NULL),
145 _scrollCallback(NULL),
148 // pretend to be a list, so fgPopup doesn't mess with our mouse events
149 type |= PUCLASS_LIST;
150 setModel(new RouteManagerWaypointModel());
151 setSize(width, height);
157 WaypointList::~WaypointList()
160 delete _updateCallback;
161 delete _scrollCallback;
164 void WaypointList::setUpdateCallback(SGCallback* cb)
166 _updateCallback = cb;
169 void WaypointList::setScrollCallback(SGCallback* cb)
171 _scrollCallback = cb;
174 void WaypointList::setSize(int width, int height)
176 double scrollP = getVScrollPercent();
178 puFrame::setSize(width, height);
180 if (wantsVScroll()) {
181 setVScrollPercent(scrollP);
187 int WaypointList::checkHit ( int button, int updown, int x, int y )
189 if ( isHit( x, y ) || ( puActiveWidget () == this ) )
191 doHit ( button, updown, x, y ) ;
199 void WaypointList::doHit( int button, int updown, int x, int y )
201 puFrame::doHit(button, updown, x, y);
202 if (updown == PU_DRAG) {
207 if (button != active_mouse_button) {
211 if (updown == PU_UP) {
212 puDeactivateWidget();
217 } else if (updown == PU_DOWN) {
218 puSetActiveWidget(this, x, y);
225 int row = rowForY(y - abox.min[1]);
226 if (row >= (int) _model->numWaypoints()) {
227 row = -1; // 'no selection'
230 if (row == getSelected()) {
231 _showLatLon = !_showLatLon;
239 void WaypointList::handleDrag(int x, int y)
242 // don't start drags immediately, require a certain mouse movement first
243 int manhattanLength = abs(x - _mouseDownX) + abs(y - _mouseDownY);
244 if (manhattanLength < DRAG_START_DISTANCE_PX) {
248 _dragSourceRow = rowForY(y - abox.min[1]);
249 Waypt* wp = _model->waypointAt(_dragSourceRow);
250 if (!wp || wp->flag(WPT_GENERATED)) {
251 return; // don't allow generated points to be dragged
255 _dragScroll = SCROLL_NO;
258 if (y < abox.min[1]) {
259 if (_dragScroll != SCROLL_DOWN) {
260 _dragScroll = SCROLL_DOWN;
261 _dragScrollTime.stamp();
263 } else if (y > abox.max[1]) {
264 if (_dragScroll != SCROLL_UP) {
265 _dragScroll = SCROLL_UP;
266 _dragScrollTime.stamp();
269 _dragScroll = SCROLL_NO;
270 _dragTargetRow = rowForY(y - abox.min[1] - (rowHeightPx() / 2));
274 void WaypointList::doDrop(int x, int y)
277 puDeactivateWidget();
279 SG_LOG(SG_GENERAL, SG_INFO, "doDrop");
281 if ((y < abox.min[1]) || (y >= abox.max[1])) {
282 SG_LOG(SG_GENERAL, SG_INFO, "y out of bounds:" << y);
286 if (_dragSourceRow == _dragTargetRow) {
287 SG_LOG(SG_GENERAL, SG_INFO, "source and target row match");
291 _model->moveWaypointToIndex(_dragSourceRow, _dragTargetRow);
293 // keep row indexes linged up when moving an item down the list
294 if (_dragSourceRow < _dragTargetRow) {
298 setSelected(_dragTargetRow);
301 void WaypointList::invokeDownCallback(void)
304 _dragScroll = SCROLL_NO;
305 SG_LOG(SG_GENERAL, SG_INFO, "cancel drag");
308 int WaypointList::rowForY(int y) const
314 // flip y to increase down, not up (as rows do)
315 int flipY = _heightPx - y;
316 int row = (flipY + _scrollPx) / rowHeightPx();
320 void WaypointList::draw( int dx, int dy )
322 puFrame::draw(dx, dy);
328 if (_dragScroll != SCROLL_NO) {
332 double dt = (SGTimeStamp::now() - _blinkTimer).toSecs();
333 if (dt > BLINK_TIME) {
338 glEnable(GL_SCISSOR_TEST);
339 GLint sx = (int) abox.min[0],
341 GLsizei w = (GLsizei) abox.max[0] - abox.min[0],
344 sx += border_thickness;
345 sy += border_thickness;
346 w -= 2 * border_thickness;
347 h -= 2 * border_thickness;
349 glScissor(sx + dx, sy + dy, w, h);
350 int row = firstVisibleRow(),
351 final = lastVisibleRow(),
352 rowHeight = rowHeightPx(),
355 y -= (_scrollPx % rowHeight); // partially draw the first row
357 _arrowWidth = legendFont.getStringWidth(">");
358 for ( ; row <= final; ++row, y += rowHeight) {
359 drawRow(dx, dy, row, y);
360 } // of row drawing iteration
362 glDisable(GL_SCISSOR_TEST);
365 // draw the insert marker after the rows
366 int insertY = (_dragTargetRow * rowHeight) - _scrollPx;
367 SG_CLAMP_RANGE(insertY, 0, std::min(_heightPx, totalHeightPx()));
369 glColor4f(1.0f, 0.5f, 0.0f, 0.8f);
372 glVertex2f(dx + abox.min[0], dy + abox.max[1] - insertY);
373 glVertex2f(dx + abox.max[0], dy + abox.max[1] - insertY);
378 void WaypointList::drawRow(int dx, int dy, int rowIndex, int y)
380 flightgear::Waypt* wp(_model->waypointAt(rowIndex));
382 bool isSelected = (rowIndex == getSelected());
383 bool isCurrent = (rowIndex == _model->currentWaypoint());
384 bool isDragSource = (_dragging && (rowIndex == _dragSourceRow));
387 bkgBox.min[1] = abox.max[1] - y;
388 bkgBox.max[1] = bkgBox.min[1] + rowHeightPx();
391 puFont* f = &legendFont;
392 bool drawBox = false;
394 if (wp->flag(WPT_MISS)) {
396 puSetColor(col, 1.0, 0.0, 0.0, 0.3); // red
397 } else if (wp->flag(WPT_ARRIVAL)) {
399 puSetColor(col, 0.0, 0.0, 0.0, 0.3);
400 } else if (wp->flag(WPT_DEPARTURE)) {
402 puSetColor(col, 0.0, 0.0, 0.0, 0.3);
406 // draw later, on *top* of text string
407 } else if (isSelected) { // -PLAIN means selected, apparently
408 bkgBox.draw(dx, dy, -PUSTYLE_PLAIN, colour, false, 0);
409 } else if (drawBox) {
410 bkgBox.draw(dx, dy, PUSTYLE_PLAIN, &col, false, 0);
414 glColor4f (1.0, 0.5, 0.0, 1.0) ;
416 glColor4fv ( colour [ PUCOL_LEGEND ] ) ;
419 int xx = dx + abox.min[0] + PUSTR_LGAP;
420 int yy = dy + abox.max[1] - y ;
421 yy += 4; // center text in row height
424 f->drawString(">", xx, yy);
428 x += _arrowWidth + PUSTR_LGAP;
433 int count = ::snprintf(buffer, 128, "%03d %-5s", rowIndex, wp->ident().c_str());
435 FGPositioned* src = wp->source();
436 if (src && !src->name().empty() && (src->name() != wp->ident())) {
437 // append name if present, and different to id
438 ::snprintf(buffer + count, 128 - count, " (%s)", src->name().c_str());
441 drawClippedString(legendFont, buffer, x, yy, 300);
442 x += 300 + PUSTR_LGAP;
445 SGGeod p(wp->position());
446 char ns = (p.getLatitudeDeg() > 0.0) ? 'N' : 'S';
447 char ew = (p.getLongitudeDeg() > 0.0) ? 'E' : 'W';
449 ::snprintf(buffer, 128 - count, "%4.2f%c %4.2f%c",
450 fabs(p.getLongitudeDeg()), ew, fabs(p.getLatitudeDeg()), ns);
451 } else if (rowIndex > 0) {
454 Waypt* prev = _model->waypointAt(rowIndex - 1);
455 boost::tie(courseDeg, distanceM) = wp->courseAndDistanceFrom(prev->position());
457 ::snprintf(buffer, 128 - count, "%03.0f %5.1fnm",
458 courseDeg, distanceM * SG_METER_TO_NM);
461 f->drawString(buffer, x, yy);
462 x += 100 + PUSTR_LGAP;
464 if (wp->altitudeRestriction() != RESTRICT_NONE) {
465 int altHundredFt = (wp->altitudeFt() + 50) / 100; // round to nearest 100ft
466 if (altHundredFt < 100) {
467 count = ::snprintf(buffer, 128, "%d'", altHundredFt * 100);
468 } else { // display as a flight-level
469 count = ::snprintf(buffer, 128, "FL%d", altHundredFt);
472 f->drawString(buffer, x, yy);
473 } // of valid wp altitude
474 x += 60 + PUSTR_LGAP;
476 if (wp->speedRestriction() == SPEED_RESTRICT_MACH) {
477 count = ::snprintf(buffer, 126, "%03.2fM", wp->speedMach());
478 f->drawString(buffer, x, yy);
479 } else if (wp->speedRestriction() != RESTRICT_NONE) {
480 count = ::snprintf(buffer, 126, "%dKts", (int) wp->speedKts());
481 f->drawString(buffer, x, yy);
485 puSetColor(col, 1.0, 0.5, 0.0, 0.5);
486 bkgBox.draw(dx, dy, PUSTYLE_PLAIN, &col, false, 0);
490 const double SCROLL_PX_SEC = 200.0;
492 void WaypointList::doDragScroll()
494 double dt = (SGTimeStamp::now() - _dragScrollTime).toSecs();
495 _dragScrollTime.stamp();
496 int deltaPx = (int)(dt * SCROLL_PX_SEC);
498 if (_dragScroll == SCROLL_UP) {
499 _scrollPx = _scrollPx - deltaPx;
500 SG_CLAMP_RANGE(_scrollPx, 0, scrollRangePx());
501 _dragTargetRow = firstVisibleRow();
503 _scrollPx = _scrollPx + deltaPx;
504 SG_CLAMP_RANGE(_scrollPx, 0, scrollRangePx());
505 _dragTargetRow = lastFullyVisibleRow() + 1;
508 if (_scrollCallback) {
509 (*_scrollCallback)();
513 int WaypointList::getSelected()
515 return getIntegerValue();
518 void WaypointList::setSelected(int rowIndex)
520 if (rowIndex == getSelected()) {
526 if (rowIndex == -1) {
530 ensureRowVisible(rowIndex);
533 void WaypointList::ensureRowVisible(int rowIndex)
535 if ((rowIndex >= firstFullyVisibleRow()) && (rowIndex <= lastFullyVisibleRow())) {
536 return; // already visible, fine
539 // ideal position would place the desired row in the middle of the
540 // visible section - hence subtract half the visible height.
541 int targetScrollPx = (rowIndex * rowHeightPx()) - (_heightPx / 2);
543 // clamp the scroll value to something valid
544 SG_CLAMP_RANGE(targetScrollPx, 0, scrollRangePx());
545 _scrollPx = targetScrollPx;
548 if (_scrollCallback) { // keep scroll observers in sync
549 (*_scrollCallback)();
553 unsigned int WaypointList::numWaypoints() const
559 return _model->numWaypoints();
562 bool WaypointList::wantsVScroll() const
564 return totalHeightPx() > _heightPx;
567 float WaypointList::getVScrollPercent() const
569 float scrollRange = scrollRangePx();
570 if (scrollRange < 1.0f) {
574 return _scrollPx / scrollRange;
577 float WaypointList::getVScrollThumbPercent() const
579 return _heightPx / (float) totalHeightPx();
582 void WaypointList::setVScrollPercent(float perc)
584 float scrollRange = scrollRangePx();
585 _scrollPx = (int)(scrollRange * perc);
588 int WaypointList::firstVisibleRow() const
590 return _scrollPx / rowHeightPx();
593 int WaypointList::firstFullyVisibleRow() const
595 int rh = rowHeightPx();
596 return (_scrollPx + rh - 1) / rh;
599 int WaypointList::numVisibleRows() const
601 int rh = rowHeightPx();
602 int topOffset = _scrollPx % rh; // pixels of first visible row
603 return (_heightPx - topOffset + rh - 1) / rh;
607 int WaypointList::numFullyVisibleRows() const
609 int rh = rowHeightPx();
610 int topOffset = _scrollPx % rh; // pixels of first visible row
611 return (_heightPx - topOffset) / rh;
614 int WaypointList::rowHeightPx() const
616 return legendFont.getStringHeight() + PUSTR_BGAP;
619 int WaypointList::scrollRangePx() const
621 return std::max(0, totalHeightPx() - _heightPx);
624 int WaypointList::totalHeightPx() const
630 return (int) _model->numWaypoints() * rowHeightPx();
633 int WaypointList::lastFullyVisibleRow() const
635 int row = firstFullyVisibleRow() + numFullyVisibleRows();
636 return std::min(row, (int) _model->numWaypoints() - 1);
639 int WaypointList::lastVisibleRow() const
641 int row = firstVisibleRow() + numVisibleRows();
642 return std::min(row, (int) _model->numWaypoints() - 1);
645 void WaypointList::setModel(Model* model)
652 _model->setUpdateCallback(make_callback(this, &WaypointList::modelUpdateCallback));
657 int WaypointList::checkKey (int key, int updown )
659 if ((updown == PU_UP) || !isVisible () || !isActive () || (window != puGetWindow())) {
670 setSelected(_model->numWaypoints() - 1);
674 case PU_KEY_PAGE_UP :
675 if (getSelected() >= 0) {
676 setSelected(getSelected() - 1);
681 case PU_KEY_PAGE_DOWN : {
682 int newSel = getSelected() + 1;
683 if (newSel >= (int) _model->numWaypoints()) {
692 if (getSelected() >= 0) {
693 Waypt* wp = _model->waypointAt(getSelected());
694 if (wp->flag(WPT_GENERATED)) {
698 if (wp->altitudeRestriction() != RESTRICT_NONE) {
699 int curAlt = (static_cast<int>(wp->altitudeFt()) + 50) / 100;
701 wp->setAltitude(0, RESTRICT_NONE);
703 wp->setAltitude((curAlt - 10) * 100, wp->altitudeRestriction());
710 if (getSelected() >= 0) {
711 flightgear::Waypt* wp = _model->waypointAt(getSelected());
712 if (wp->flag(WPT_GENERATED)) {
716 if (wp->altitudeRestriction() == RESTRICT_NONE) {
717 wp->setAltitude(1000, RESTRICT_AT);
719 int curAlt = (static_cast<int>(wp->altitudeFt()) + 50) / 100;
720 wp->setAltitude((curAlt + 10) * 100, wp->altitudeRestriction());
726 if (getSelected() >= 0) {
727 int index = getSelected();
728 Waypt* wp = _model->waypointAt(getSelected());
729 if (wp->flag(WPT_GENERATED)) {
733 _model->deleteAt(index);
734 setSelected(index - 1);
745 void WaypointList::modelUpdateCallback()
749 if (_updateCallback) {
750 (*_updateCallback)();
754 //////////////////////////////////////////////////////////////////////////////
757 static void handle_scrollbar(puObject* scrollbar)
759 ScrolledWaypointList* self = (ScrolledWaypointList*)scrollbar->getUserData();
760 self->setScrollPercent(scrollbar->getFloatValue());
763 static void waypointListCb(puObject* wpl)
765 ScrolledWaypointList* self = (ScrolledWaypointList*)wpl->getUserData();
766 self->setValue(wpl->getIntegerValue());
767 self->invokeCallback();
770 ScrolledWaypointList::ScrolledWaypointList(int x, int y, int width, int height) :
774 // ensure our type is compound, so fgPopup::applySize doesn't descend into
775 // us, and try to cast our children's user-data to GUIInfo*.
776 type |= PUCLASS_LIST;
781 void ScrolledWaypointList::setValue(float v)
783 puGroup::setValue(v);
787 void ScrolledWaypointList::setValue(int v)
789 puGroup::setValue(v);
793 void ScrolledWaypointList::init(int w, int h)
795 _list = new WaypointList(0, 0, w, h);
796 _list->setUpdateCallback(make_callback(this, &ScrolledWaypointList::modelUpdated));
797 _hasVScroll = _list->wantsVScroll();
798 _list->setUserData(this);
799 _list->setCallback(waypointListCb);
801 _list->setScrollCallback(make_callback(this, &ScrolledWaypointList::updateScroll));
803 _scrollbar = new puaScrollBar(w - _scrollWidth, 0, h,
804 1 /*arrow*/, 1 /* vertical */, _scrollWidth);
805 _scrollbar->setMinValue(0.0);
806 _scrollbar->setMaxValue(1.0);
807 _scrollbar->setUserData(this);
808 _scrollbar->setCallback(handle_scrollbar);
809 close(); // close the group
814 void ScrolledWaypointList::modelUpdated()
818 updateWantsScroll(w, h);
821 void ScrolledWaypointList::setScrollPercent(float v)
823 // slider's min is the bottom, so invert the value
824 _list->setVScrollPercent(1.0f - v);
827 void ScrolledWaypointList::setSize(int w, int h)
829 updateWantsScroll(w, h);
830 puGroup::setSize(w, h);
833 void ScrolledWaypointList::updateWantsScroll(int w, int h)
835 _hasVScroll = _list->wantsVScroll();
838 _scrollbar->reveal();
839 _scrollbar->setPosition(w - _scrollWidth, 0);
840 _scrollbar->setSize(_scrollWidth, h);
841 _list->setSize(w - _scrollWidth, h);
845 _list->setSize(w, h);
849 void ScrolledWaypointList::updateScroll()
851 // _scrollbar->setMaxValue(_list->numWaypoints());
852 _scrollbar->setValue(1.0f - _list->getVScrollPercent());
853 _scrollbar->setSliderFraction(_list->getVScrollThumbPercent());