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);
116 fullWidth = font.getStringWidth(buf);
117 } while (fullWidth > maxWidth);
119 font.drawString(buf, x, y);
122 //////////////////////////////////////////////////////////////////////////////
124 WaypointList::WaypointList(int x, int y, int width, int height) :
125 puFrame(x, y, width, height),
126 GUI_ID(FGCLASS_WAYPOINTLIST),
129 _dragScroll(SCROLL_NO),
132 _updateCallback(NULL),
133 _scrollCallback(NULL)
135 // pretend to be a list, so fgPopup doesn't mess with our mouse events
136 type |= PUCLASS_LIST;
137 setModel(new RouteManagerWaypointModel());
138 setSize(width, height);
142 WaypointList::~WaypointList()
145 delete _updateCallback;
146 delete _scrollCallback;
149 void WaypointList::setUpdateCallback(SGCallback* cb)
151 _updateCallback = cb;
154 void WaypointList::setScrollCallback(SGCallback* cb)
156 _scrollCallback = cb;
159 void WaypointList::setSize(int width, int height)
161 double scrollP = getVScrollPercent();
163 puFrame::setSize(width, height);
165 if (wantsVScroll()) {
166 setVScrollPercent(scrollP);
172 int WaypointList::checkHit ( int button, int updown, int x, int y )
174 if ( isHit( x, y ) || ( puActiveWidget () == this ) )
176 doHit ( button, updown, x, y ) ;
184 void WaypointList::doHit( int button, int updown, int x, int y )
186 puFrame::doHit(button, updown, x, y);
187 if (updown == PU_DRAG) {
192 if (button != active_mouse_button) {
196 if (updown == PU_UP) {
197 puDeactivateWidget();
202 } else if (updown == PU_DOWN) {
203 puSetActiveWidget(this, x, y);
210 int row = rowForY(y - abox.min[1]);
211 if (row >= (int) _model->numWaypoints()) {
212 row = -1; // 'no selection'
215 if (row == getSelected()) {
216 _showLatLon = !_showLatLon;
224 void WaypointList::handleDrag(int x, int y)
227 // don't start drags immediately, require a certain mouse movement first
228 int manhattanLength = abs(x - _mouseDownX) + abs(y - _mouseDownY);
229 if (manhattanLength < DRAG_START_DISTANCE_PX) {
233 _dragSourceRow = rowForY(y - abox.min[1]);
235 _dragScroll = SCROLL_NO;
238 if (y < abox.min[1]) {
239 if (_dragScroll != SCROLL_DOWN) {
240 _dragScroll = SCROLL_DOWN;
241 _dragScrollTime.stamp();
243 } else if (y > abox.max[1]) {
244 if (_dragScroll != SCROLL_UP) {
245 _dragScroll = SCROLL_UP;
246 _dragScrollTime.stamp();
249 _dragScroll = SCROLL_NO;
250 _dragTargetRow = rowForY(y - abox.min[1] - (rowHeightPx() / 2));
254 void WaypointList::doDrop(int x, int y)
257 puDeactivateWidget();
259 if ((y < abox.min[1]) || (y >= abox.max[1])) {
263 if (_dragSourceRow != _dragTargetRow) {
264 _model->moveWaypointToIndex(_dragSourceRow, _dragTargetRow);
266 // keep row indexes linged up when moving an item down the list
267 if (_dragSourceRow < _dragTargetRow) {
271 setSelected(_dragTargetRow);
275 void WaypointList::invokeDownCallback(void)
278 _dragScroll = SCROLL_NO;
279 SG_LOG(SG_GENERAL, SG_INFO, "cancel drag");
282 int WaypointList::rowForY(int y) const
288 // flip y to increase down, not up (as rows do)
289 int flipY = _heightPx - y;
290 int row = (flipY + _scrollPx) / rowHeightPx();
294 void WaypointList::draw( int dx, int dy )
296 puFrame::draw(dx, dy);
302 if (_dragScroll != SCROLL_NO) {
306 glEnable(GL_SCISSOR_TEST);
307 GLint sx = (int) abox.min[0],
309 GLsizei w = (GLsizei) abox.max[0] - abox.min[0],
312 sx += border_thickness;
313 sy += border_thickness;
314 w -= 2 * border_thickness;
315 h -= 2 * border_thickness;
317 glScissor(sx + dx, sy + dy, w, h);
318 int row = firstVisibleRow(),
319 final = lastVisibleRow(),
320 rowHeight = rowHeightPx(),
323 y -= (_scrollPx % rowHeight); // partially draw the first row
325 for ( ; row <= final; ++row, y += rowHeight) {
326 drawRow(dx, dy, row, y);
327 } // of row drawing iteration
329 glDisable(GL_SCISSOR_TEST);
332 // draw the insert marker after the rows
333 int insertY = (_dragTargetRow * rowHeight) - _scrollPx;
334 SG_CLAMP_RANGE(insertY, 0, std::min(_heightPx, totalHeightPx()));
336 glColor4f(1.0f, 0.5f, 0.0f, 0.8f);
339 glVertex2f(dx + abox.min[0], dy + abox.max[1] - insertY);
340 glVertex2f(dx + abox.max[0], dy + abox.max[1] - insertY);
345 void WaypointList::drawRow(int dx, int dy, int rowIndex, int y)
347 bool isSelected = (rowIndex == getSelected());
348 bool isCurrent = (rowIndex == _model->currentWaypoint());
349 bool isDragSource = (_dragging && (rowIndex == _dragSourceRow));
352 bkgBox.min[1] = abox.max[1] - y;
353 bkgBox.max[1] = bkgBox.min[1] + rowHeightPx();
355 puColour currentColor;
356 puSetColor(currentColor, 1.0, 1.0, 0.0, 0.5);
359 // draw later, on *top* of text string
360 } else if (isCurrent) {
361 bkgBox.draw(dx, dy, PUSTYLE_PLAIN, ¤tColor, false, 0);
362 } else if (isSelected) { // -PLAIN means selected, apparently
363 bkgBox.draw(dx, dy, -PUSTYLE_PLAIN, colour, false, 0);
366 int xx = dx + abox.min[0] + PUSTR_LGAP;
367 int yy = dy + abox.max[1] - y ;
368 yy += 4; // center text in row height
371 const SGWayPoint wp(_model->waypointAt(rowIndex));
373 int count = ::snprintf(buffer, 128, "%03d %-5s", rowIndex, wp.get_id().c_str());
375 if (wp.get_name().size() > 0 && (wp.get_name() != wp.get_id())) {
376 // append name if present, and different to id
377 ::snprintf(buffer + count, 128 - count, " (%s)", wp.get_name().c_str());
380 glColor4fv ( colour [ PUCOL_LEGEND ] ) ;
381 drawClippedString(legendFont, buffer, xx, yy, 300);
384 char ns = (wp.get_target_lat() > 0.0) ? 'N' : 'S';
385 char ew = (wp.get_target_lon() > 0.0) ? 'E' : 'W';
387 ::snprintf(buffer, 128 - count, "%4.2f%c %4.2f%c",
388 fabs(wp.get_target_lon()), ew, fabs(wp.get_target_lat()), ns);
390 ::snprintf(buffer, 128 - count, "%03.0f %5.1fnm",
391 wp.get_track(), wp.get_distance() * SG_METER_TO_NM);
394 legendFont.drawString(buffer, xx + 300 + PUSTR_LGAP, yy);
396 int altFt = (int) wp.get_target_alt() * SG_METER_TO_FEET;
398 int altHundredFt = (altFt + 50) / 100; // round to nearest 100ft
399 if (altHundredFt < 100) {
400 count = ::snprintf(buffer, 128, "%d'", altHundredFt * 100);
401 } else { // display as a flight-level
402 count = ::snprintf(buffer, 128, "FL%d", altHundredFt);
405 legendFont.drawString(buffer, xx + 400 + PUSTR_LGAP, yy);
406 } // of valid wp altitude
409 puSetColor(currentColor, 1.0, 0.5, 0.0, 0.5);
410 bkgBox.draw(dx, dy, PUSTYLE_PLAIN, ¤tColor, false, 0);
414 const double SCROLL_PX_SEC = 200.0;
416 void WaypointList::doDragScroll()
418 double dt = (SGTimeStamp::now() - _dragScrollTime).toSecs();
419 _dragScrollTime.stamp();
420 int deltaPx = (int)(dt * SCROLL_PX_SEC);
422 if (_dragScroll == SCROLL_UP) {
423 _scrollPx = _scrollPx - deltaPx;
424 SG_CLAMP_RANGE(_scrollPx, 0, scrollRangePx());
425 _dragTargetRow = firstVisibleRow();
427 _scrollPx = _scrollPx + deltaPx;
428 SG_CLAMP_RANGE(_scrollPx, 0, scrollRangePx());
429 _dragTargetRow = lastFullyVisibleRow() + 1;
432 if (_scrollCallback) {
433 (*_scrollCallback)();
437 int WaypointList::getSelected()
439 return getIntegerValue();
442 void WaypointList::setSelected(int rowIndex)
444 if (rowIndex == getSelected()) {
450 if (rowIndex == -1) {
454 ensureRowVisible(rowIndex);
457 void WaypointList::ensureRowVisible(int rowIndex)
459 if ((rowIndex >= firstFullyVisibleRow()) && (rowIndex <= lastFullyVisibleRow())) {
460 return; // already visible, fine
463 // ideal position would place the desired row in the middle of the
464 // visible section - hence subtract half the visible height.
465 int targetScrollPx = (rowIndex * rowHeightPx()) - (_heightPx / 2);
467 // clamp the scroll value to something valid
468 SG_CLAMP_RANGE(targetScrollPx, 0, scrollRangePx());
469 _scrollPx = targetScrollPx;
472 if (_scrollCallback) { // keep scroll observers in sync
473 (*_scrollCallback)();
477 unsigned int WaypointList::numWaypoints() const
483 return _model->numWaypoints();
486 bool WaypointList::wantsVScroll() const
488 return totalHeightPx() > _heightPx;
491 float WaypointList::getVScrollPercent() const
493 float scrollRange = scrollRangePx();
494 if (scrollRange < 1.0f) {
498 return _scrollPx / scrollRange;
501 float WaypointList::getVScrollThumbPercent() const
503 return _heightPx / (float) totalHeightPx();
506 void WaypointList::setVScrollPercent(float perc)
508 float scrollRange = scrollRangePx();
509 _scrollPx = (int)(scrollRange * perc);
512 int WaypointList::firstVisibleRow() const
514 return _scrollPx / rowHeightPx();
517 int WaypointList::firstFullyVisibleRow() const
519 int rh = rowHeightPx();
520 return (_scrollPx + rh - 1) / rh;
523 int WaypointList::numVisibleRows() const
525 int rh = rowHeightPx();
526 int topOffset = _scrollPx % rh; // pixels of first visible row
527 return (_heightPx - topOffset + rh - 1) / rh;
531 int WaypointList::numFullyVisibleRows() const
533 int rh = rowHeightPx();
534 int topOffset = _scrollPx % rh; // pixels of first visible row
535 return (_heightPx - topOffset) / rh;
538 int WaypointList::rowHeightPx() const
540 return legendFont.getStringHeight() + PUSTR_BGAP;
543 int WaypointList::scrollRangePx() const
545 return std::max(0, totalHeightPx() - _heightPx);
548 int WaypointList::totalHeightPx() const
554 return (int) _model->numWaypoints() * rowHeightPx();
557 int WaypointList::lastFullyVisibleRow() const
559 int row = firstFullyVisibleRow() + numFullyVisibleRows();
560 return std::min(row, (int) _model->numWaypoints() - 1);
563 int WaypointList::lastVisibleRow() const
565 int row = firstVisibleRow() + numVisibleRows();
566 return std::min(row, (int) _model->numWaypoints() - 1);
569 void WaypointList::setModel(Model* model)
576 _model->setUpdateCallback(make_callback(this, &WaypointList::modelUpdateCallback));
581 int WaypointList::checkKey (int key, int updown )
583 if ((updown == PU_UP) || !isVisible () || !isActive () || (window != puGetWindow())) {
594 setSelected(_model->numWaypoints() - 1);
598 case PU_KEY_PAGE_UP :
599 if (getSelected() >= 0) {
600 setSelected(getSelected() - 1);
605 case PU_KEY_PAGE_DOWN : {
606 int newSel = getSelected() + 1;
607 if (newSel >= (int) _model->numWaypoints()) {
616 if (getSelected() >= 0) {
617 int newAlt = wayptAltFtHundreds(getSelected()) - 10;
619 _model->setWaypointTargetAltitudeFt(getSelected(), -9999);
621 _model->setWaypointTargetAltitudeFt(getSelected(), newAlt * 100);
627 if (getSelected() >= 0) {
628 int newAlt = wayptAltFtHundreds(getSelected()) + 10;
630 _model->setWaypointTargetAltitudeFt(getSelected(), 0);
632 _model->setWaypointTargetAltitudeFt(getSelected(), newAlt * 100);
638 if (getSelected() >= 0) {
639 int index = getSelected();
640 _model->deleteAt(index);
641 setSelected(index - 1);
652 int WaypointList::wayptAltFtHundreds(int index) const
654 double alt = _model->waypointAt(index).get_target_alt();
659 int altFt = (int) alt * SG_METER_TO_FEET;
660 return (altFt + 50) / 100; // round to nearest 100ft
663 void WaypointList::modelUpdateCallback()
667 if (_updateCallback) {
668 (*_updateCallback)();
672 //////////////////////////////////////////////////////////////////////////////
675 static void handle_scrollbar(puObject* scrollbar)
677 ScrolledWaypointList* self = (ScrolledWaypointList*)scrollbar->getUserData();
678 self->setScrollPercent(scrollbar->getFloatValue());
681 static void waypointListCb(puObject* wpl)
683 ScrolledWaypointList* self = (ScrolledWaypointList*)wpl->getUserData();
684 self->setValue(wpl->getIntegerValue());
685 self->invokeCallback();
688 ScrolledWaypointList::ScrolledWaypointList(int x, int y, int width, int height) :
692 // ensure our type is compound, so fgPopup::applySize doesn't descend into
693 // us, and try to cast our children's user-data to GUIInfo*.
694 type |= PUCLASS_LIST;
699 void ScrolledWaypointList::setValue(float v)
701 puGroup::setValue(v);
705 void ScrolledWaypointList::setValue(int v)
707 puGroup::setValue(v);
711 void ScrolledWaypointList::init(int w, int h)
713 _list = new WaypointList(0, 0, w, h);
714 _list->setUpdateCallback(make_callback(this, &ScrolledWaypointList::modelUpdated));
715 _hasVScroll = _list->wantsVScroll();
716 _list->setUserData(this);
717 _list->setCallback(waypointListCb);
719 _list->setScrollCallback(make_callback(this, &ScrolledWaypointList::updateScroll));
721 _scrollbar = new puaScrollBar(w - _scrollWidth, 0, h,
722 1 /*arrow*/, 1 /* vertical */, _scrollWidth);
723 _scrollbar->setMinValue(0.0);
724 _scrollbar->setMaxValue(1.0);
725 _scrollbar->setUserData(this);
726 _scrollbar->setCallback(handle_scrollbar);
727 close(); // close the group
732 void ScrolledWaypointList::modelUpdated()
736 updateWantsScroll(w, h);
739 void ScrolledWaypointList::setScrollPercent(float v)
741 // slider's min is the bottom, so invert the value
742 _list->setVScrollPercent(1.0f - v);
745 void ScrolledWaypointList::setSize(int w, int h)
747 updateWantsScroll(w, h);
750 void ScrolledWaypointList::updateWantsScroll(int w, int h)
752 _hasVScroll = _list->wantsVScroll();
755 _scrollbar->reveal();
756 _scrollbar->setPosition(w - _scrollWidth, 0);
757 _scrollbar->setSize(_scrollWidth, h);
758 _list->setSize(w - _scrollWidth, h);
762 _list->setSize(w, h);
766 void ScrolledWaypointList::updateScroll()
768 // _scrollbar->setMaxValue(_list->numWaypoints());
769 _scrollbar->setValue(1.0f - _list->getVScrollPercent());
770 _scrollbar->setSliderFraction(_list->getVScrollThumbPercent());