7 #include "WaypointList.hxx"
10 #include <plib/puAux.h>
12 #include <simgear/route/waypoint.hxx>
13 #include <simgear/structure/callback.hxx>
14 #include <simgear/sg_inlines.h>
16 #include <Main/globals.hxx>
17 #include <Main/fg_props.hxx>
19 #include <Autopilot/route_mgr.hxx>
27 static const int DRAG_START_DISTANCE_PX = 5;
29 class RouteManagerWaypointModel :
30 public WaypointList::Model,
31 public SGPropertyChangeListener
34 RouteManagerWaypointModel()
36 _rm = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
38 SGPropertyNode* routeEdited = fgGetNode("/autopilot/route-manager/signals/edited", true);
39 routeEdited->addChangeListener(this);
42 virtual ~RouteManagerWaypointModel()
44 SGPropertyNode* routeEdited = fgGetNode("/autopilot/route-manager/signals/edited", true);
45 routeEdited->removeChangeListener(this);
48 // implement WaypointList::Model
49 virtual unsigned int numWaypoints() const
54 virtual int currentWaypoint() const
56 return _rm->currentWaypoint();
59 virtual SGWayPoint waypointAt(unsigned int index) const
61 return _rm->get_waypoint(index);
64 virtual void deleteAt(unsigned int index)
66 _rm->pop_waypoint(index);
69 virtual void setWaypointTargetAltitudeFt(unsigned int index, int altFt)
71 _rm->setWaypointTargetAltitudeFt(index, altFt);
74 virtual void moveWaypointToIndex(unsigned int srcIndex, unsigned int destIndex)
76 if (destIndex > srcIndex) {
80 SGWayPoint wp = _rm->pop_waypoint(srcIndex);
81 _rm->add_waypoint(wp, destIndex);
84 virtual void setUpdateCallback(SGCallback* cb)
89 // implement SGPropertyChangeListener
90 void valueChanged(SGPropertyNode *prop)
101 //////////////////////////////////////////////////////////////////////////////
103 static void drawClippedString(puFont& font, const char* s, int x, int y, int maxWidth)
105 int fullWidth = font.getStringWidth(s);
106 if (fullWidth <= maxWidth) { // common case, easy and efficent
107 font.drawString(s, x, y);
112 int len = buf.size();
115 fullWidth = font.getStringWidth(buf.c_str());
116 } while (fullWidth > maxWidth);
118 font.drawString(buf.c_str(), x, y);
121 //////////////////////////////////////////////////////////////////////////////
123 WaypointList::WaypointList(int x, int y, int width, int height) :
124 puFrame(x, y, width, height),
125 GUI_ID(FGCLASS_WAYPOINTLIST),
128 _dragScroll(SCROLL_NO),
131 _updateCallback(NULL),
132 _scrollCallback(NULL)
134 // pretend to be a list, so fgPopup doesn't mess with our mouse events
135 type |= PUCLASS_LIST;
136 setModel(new RouteManagerWaypointModel());
137 setSize(width, height);
141 WaypointList::~WaypointList()
144 delete _updateCallback;
145 delete _scrollCallback;
148 void WaypointList::setUpdateCallback(SGCallback* cb)
150 _updateCallback = cb;
153 void WaypointList::setScrollCallback(SGCallback* cb)
155 _scrollCallback = cb;
158 void WaypointList::setSize(int width, int height)
160 double scrollP = getVScrollPercent();
162 puFrame::setSize(width, height);
164 if (wantsVScroll()) {
165 setVScrollPercent(scrollP);
171 int WaypointList::checkHit ( int button, int updown, int x, int y )
173 if ( isHit( x, y ) || ( puActiveWidget () == this ) )
175 doHit ( button, updown, x, y ) ;
183 void WaypointList::doHit( int button, int updown, int x, int y )
185 puFrame::doHit(button, updown, x, y);
186 if (updown == PU_DRAG) {
191 if (button != active_mouse_button) {
195 if (updown == PU_UP) {
196 puDeactivateWidget();
201 } else if (updown == PU_DOWN) {
202 puSetActiveWidget(this, x, y);
209 int row = rowForY(y - abox.min[1]);
210 if (row >= (int) _model->numWaypoints()) {
211 row = -1; // 'no selection'
214 if (row == getSelected()) {
215 _showLatLon = !_showLatLon;
223 void WaypointList::handleDrag(int x, int y)
226 // don't start drags immediately, require a certain mouse movement first
227 int manhattanLength = abs(x - _mouseDownX) + abs(y - _mouseDownY);
228 if (manhattanLength < DRAG_START_DISTANCE_PX) {
232 _dragSourceRow = rowForY(y - abox.min[1]);
234 _dragScroll = SCROLL_NO;
237 if (y < abox.min[1]) {
238 if (_dragScroll != SCROLL_DOWN) {
239 _dragScroll = SCROLL_DOWN;
240 _dragScrollTime.stamp();
242 } else if (y > abox.max[1]) {
243 if (_dragScroll != SCROLL_UP) {
244 _dragScroll = SCROLL_UP;
245 _dragScrollTime.stamp();
248 _dragScroll = SCROLL_NO;
249 _dragTargetRow = rowForY(y - abox.min[1] - (rowHeightPx() / 2));
253 void WaypointList::doDrop(int x, int y)
256 puDeactivateWidget();
258 if ((y < abox.min[1]) || (y >= abox.max[1])) {
262 if (_dragSourceRow != _dragTargetRow) {
263 _model->moveWaypointToIndex(_dragSourceRow, _dragTargetRow);
265 // keep row indexes linged up when moving an item down the list
266 if (_dragSourceRow < _dragTargetRow) {
270 setSelected(_dragTargetRow);
274 void WaypointList::invokeDownCallback(void)
277 _dragScroll = SCROLL_NO;
278 SG_LOG(SG_GENERAL, SG_INFO, "cancel drag");
281 int WaypointList::rowForY(int y) const
287 // flip y to increase down, not up (as rows do)
288 int flipY = _heightPx - y;
289 int row = (flipY + _scrollPx) / rowHeightPx();
293 void WaypointList::draw( int dx, int dy )
295 puFrame::draw(dx, dy);
301 if (_dragScroll != SCROLL_NO) {
305 glEnable(GL_SCISSOR_TEST);
306 GLint sx = (int) abox.min[0],
308 GLsizei w = (GLsizei) abox.max[0] - abox.min[0],
311 sx += border_thickness;
312 sy += border_thickness;
313 w -= 2 * border_thickness;
314 h -= 2 * border_thickness;
316 glScissor(sx + dx, sy + dy, w, h);
317 int row = firstVisibleRow(),
318 final = lastVisibleRow(),
319 rowHeight = rowHeightPx(),
322 y -= (_scrollPx % rowHeight); // partially draw the first row
324 for ( ; row <= final; ++row, y += rowHeight) {
325 drawRow(dx, dy, row, y);
326 } // of row drawing iteration
328 glDisable(GL_SCISSOR_TEST);
331 // draw the insert marker after the rows
332 int insertY = (_dragTargetRow * rowHeight) - _scrollPx;
333 SG_CLAMP_RANGE(insertY, 0, std::min(_heightPx, totalHeightPx()));
335 glColor4f(1.0f, 0.5f, 0.0f, 0.8f);
338 glVertex2f(dx + abox.min[0], dy + abox.max[1] - insertY);
339 glVertex2f(dx + abox.max[0], dy + abox.max[1] - insertY);
344 void WaypointList::drawRow(int dx, int dy, int rowIndex, int y)
346 bool isSelected = (rowIndex == getSelected());
347 bool isCurrent = (rowIndex == _model->currentWaypoint());
348 bool isDragSource = (_dragging && (rowIndex == _dragSourceRow));
351 bkgBox.min[1] = abox.max[1] - y;
352 bkgBox.max[1] = bkgBox.min[1] + rowHeightPx();
354 puColour currentColor;
355 puSetColor(currentColor, 1.0, 1.0, 0.0, 0.5);
358 // draw later, on *top* of text string
359 } else if (isCurrent) {
360 bkgBox.draw(dx, dy, PUSTYLE_PLAIN, ¤tColor, false, 0);
361 } else if (isSelected) { // -PLAIN means selected, apparently
362 bkgBox.draw(dx, dy, -PUSTYLE_PLAIN, colour, false, 0);
365 int xx = dx + abox.min[0] + PUSTR_LGAP;
366 int yy = dy + abox.max[1] - y ;
367 yy += 4; // center text in row height
370 const SGWayPoint wp(_model->waypointAt(rowIndex));
372 int count = ::snprintf(buffer, 128, "%03d %-5s", rowIndex, wp.get_id().c_str());
374 if (wp.get_name().size() > 0 && (wp.get_name() != wp.get_id())) {
375 // append name if present, and different to id
376 ::snprintf(buffer + count, 128 - count, " (%s)", wp.get_name().c_str());
379 glColor4fv ( colour [ PUCOL_LEGEND ] ) ;
380 drawClippedString(legendFont, buffer, xx, yy, 300);
383 char ns = (wp.get_target_lat() > 0.0) ? 'N' : 'S';
384 char ew = (wp.get_target_lon() > 0.0) ? 'E' : 'W';
386 ::snprintf(buffer, 128 - count, "%4.2f%c %4.2f%c",
387 fabs(wp.get_target_lon()), ew, fabs(wp.get_target_lat()), ns);
389 ::snprintf(buffer, 128 - count, "%03.0f %5.1fnm",
390 wp.get_track(), wp.get_distance() * SG_METER_TO_NM);
393 legendFont.drawString(buffer, xx + 300 + PUSTR_LGAP, yy);
395 int altFt = (int) wp.get_target_alt() * SG_METER_TO_FEET;
397 int altHundredFt = (altFt + 50) / 100; // round to nearest 100ft
398 if (altHundredFt < 100) {
399 count = ::snprintf(buffer, 128, "%d'", altHundredFt * 100);
400 } else { // display as a flight-level
401 count = ::snprintf(buffer, 128, "FL%d", altHundredFt);
404 legendFont.drawString(buffer, xx + 400 + PUSTR_LGAP, yy);
405 } // of valid wp altitude
408 puSetColor(currentColor, 1.0, 0.5, 0.0, 0.5);
409 bkgBox.draw(dx, dy, PUSTYLE_PLAIN, ¤tColor, false, 0);
413 const double SCROLL_PX_SEC = 200.0;
415 void WaypointList::doDragScroll()
417 double dt = (SGTimeStamp::now() - _dragScrollTime).toSecs();
418 _dragScrollTime.stamp();
419 int deltaPx = (int)(dt * SCROLL_PX_SEC);
421 if (_dragScroll == SCROLL_UP) {
422 _scrollPx = _scrollPx - deltaPx;
423 SG_CLAMP_RANGE(_scrollPx, 0, scrollRangePx());
424 _dragTargetRow = firstVisibleRow();
426 _scrollPx = _scrollPx + deltaPx;
427 SG_CLAMP_RANGE(_scrollPx, 0, scrollRangePx());
428 _dragTargetRow = lastFullyVisibleRow() + 1;
431 if (_scrollCallback) {
432 (*_scrollCallback)();
436 int WaypointList::getSelected()
438 return getIntegerValue();
441 void WaypointList::setSelected(int rowIndex)
443 if (rowIndex == getSelected()) {
449 if (rowIndex == -1) {
453 ensureRowVisible(rowIndex);
456 void WaypointList::ensureRowVisible(int rowIndex)
458 if ((rowIndex >= firstFullyVisibleRow()) && (rowIndex <= lastFullyVisibleRow())) {
459 return; // already visible, fine
462 // ideal position would place the desired row in the middle of the
463 // visible section - hence subtract half the visible height.
464 int targetScrollPx = (rowIndex * rowHeightPx()) - (_heightPx / 2);
466 // clamp the scroll value to something valid
467 SG_CLAMP_RANGE(targetScrollPx, 0, scrollRangePx());
468 _scrollPx = targetScrollPx;
471 if (_scrollCallback) { // keep scroll observers in sync
472 (*_scrollCallback)();
476 unsigned int WaypointList::numWaypoints() const
482 return _model->numWaypoints();
485 bool WaypointList::wantsVScroll() const
487 return totalHeightPx() > _heightPx;
490 float WaypointList::getVScrollPercent() const
492 float scrollRange = scrollRangePx();
493 if (scrollRange < 1.0f) {
497 return _scrollPx / scrollRange;
500 float WaypointList::getVScrollThumbPercent() const
502 return _heightPx / (float) totalHeightPx();
505 void WaypointList::setVScrollPercent(float perc)
507 float scrollRange = scrollRangePx();
508 _scrollPx = (int)(scrollRange * perc);
511 int WaypointList::firstVisibleRow() const
513 return _scrollPx / rowHeightPx();
516 int WaypointList::firstFullyVisibleRow() const
518 int rh = rowHeightPx();
519 return (_scrollPx + rh - 1) / rh;
522 int WaypointList::numVisibleRows() const
524 int rh = rowHeightPx();
525 int topOffset = _scrollPx % rh; // pixels of first visible row
526 return (_heightPx - topOffset + rh - 1) / rh;
530 int WaypointList::numFullyVisibleRows() const
532 int rh = rowHeightPx();
533 int topOffset = _scrollPx % rh; // pixels of first visible row
534 return (_heightPx - topOffset) / rh;
537 int WaypointList::rowHeightPx() const
539 return legendFont.getStringHeight() + PUSTR_BGAP;
542 int WaypointList::scrollRangePx() const
544 return std::max(0, totalHeightPx() - _heightPx);
547 int WaypointList::totalHeightPx() const
553 return (int) _model->numWaypoints() * rowHeightPx();
556 int WaypointList::lastFullyVisibleRow() const
558 int row = firstFullyVisibleRow() + numFullyVisibleRows();
559 return std::min(row, (int) _model->numWaypoints() - 1);
562 int WaypointList::lastVisibleRow() const
564 int row = firstVisibleRow() + numVisibleRows();
565 return std::min(row, (int) _model->numWaypoints() - 1);
568 void WaypointList::setModel(Model* model)
575 _model->setUpdateCallback(make_callback(this, &WaypointList::modelUpdateCallback));
580 int WaypointList::checkKey (int key, int updown )
582 if ((updown == PU_UP) || !isVisible () || !isActive () || (window != puGetWindow())) {
593 setSelected(_model->numWaypoints() - 1);
597 case PU_KEY_PAGE_UP :
598 if (getSelected() >= 0) {
599 setSelected(getSelected() - 1);
604 case PU_KEY_PAGE_DOWN : {
605 int newSel = getSelected() + 1;
606 if (newSel >= (int) _model->numWaypoints()) {
615 if (getSelected() >= 0) {
616 int newAlt = wayptAltFtHundreds(getSelected()) - 10;
618 _model->setWaypointTargetAltitudeFt(getSelected(), -9999);
620 _model->setWaypointTargetAltitudeFt(getSelected(), newAlt * 100);
626 if (getSelected() >= 0) {
627 int newAlt = wayptAltFtHundreds(getSelected()) + 10;
629 _model->setWaypointTargetAltitudeFt(getSelected(), 0);
631 _model->setWaypointTargetAltitudeFt(getSelected(), newAlt * 100);
637 if (getSelected() >= 0) {
638 int index = getSelected();
639 _model->deleteAt(index);
640 setSelected(index - 1);
651 int WaypointList::wayptAltFtHundreds(int index) const
653 double alt = _model->waypointAt(index).get_target_alt();
658 int altFt = (int) alt * SG_METER_TO_FEET;
659 return (altFt + 50) / 100; // round to nearest 100ft
662 void WaypointList::modelUpdateCallback()
666 if (_updateCallback) {
667 (*_updateCallback)();
671 //////////////////////////////////////////////////////////////////////////////
674 static void handle_scrollbar(puObject* scrollbar)
676 ScrolledWaypointList* self = (ScrolledWaypointList*)scrollbar->getUserData();
677 self->setScrollPercent(scrollbar->getFloatValue());
680 static void waypointListCb(puObject* wpl)
682 ScrolledWaypointList* self = (ScrolledWaypointList*)wpl->getUserData();
683 self->setValue(wpl->getIntegerValue());
684 self->invokeCallback();
687 ScrolledWaypointList::ScrolledWaypointList(int x, int y, int width, int height) :
691 // ensure our type is compound, so fgPopup::applySize doesn't descend into
692 // us, and try to cast our children's user-data to GUIInfo*.
693 type |= PUCLASS_LIST;
698 void ScrolledWaypointList::setValue(float v)
700 puGroup::setValue(v);
704 void ScrolledWaypointList::setValue(int v)
706 puGroup::setValue(v);
710 void ScrolledWaypointList::init(int w, int h)
712 _list = new WaypointList(0, 0, w, h);
713 _list->setUpdateCallback(make_callback(this, &ScrolledWaypointList::modelUpdated));
714 _hasVScroll = _list->wantsVScroll();
715 _list->setUserData(this);
716 _list->setCallback(waypointListCb);
718 _list->setScrollCallback(make_callback(this, &ScrolledWaypointList::updateScroll));
720 _scrollbar = new puaScrollBar(w - _scrollWidth, 0, h,
721 1 /*arrow*/, 1 /* vertical */, _scrollWidth);
722 _scrollbar->setMinValue(0.0);
723 _scrollbar->setMaxValue(1.0);
724 _scrollbar->setUserData(this);
725 _scrollbar->setCallback(handle_scrollbar);
726 close(); // close the group
731 void ScrolledWaypointList::modelUpdated()
735 updateWantsScroll(w, h);
738 void ScrolledWaypointList::setScrollPercent(float v)
740 // slider's min is the bottom, so invert the value
741 _list->setVScrollPercent(1.0f - v);
744 void ScrolledWaypointList::setSize(int w, int h)
746 updateWantsScroll(w, h);
749 void ScrolledWaypointList::updateWantsScroll(int w, int h)
751 _hasVScroll = _list->wantsVScroll();
754 _scrollbar->reveal();
755 _scrollbar->setPosition(w - _scrollWidth, 0);
756 _scrollbar->setSize(_scrollWidth, h);
757 _list->setSize(w - _scrollWidth, h);
761 _list->setSize(w, h);
765 void ScrolledWaypointList::updateScroll()
767 // _scrollbar->setMaxValue(_list->numWaypoints());
768 _scrollbar->setValue(1.0f - _list->getVScrollPercent());
769 _scrollbar->setSliderFraction(_list->getVScrollThumbPercent());