]> git.mxchange.org Git - flightgear.git/blob - src/GUI/WaypointList.cxx
Merge branch 'next' of D:\Git_New\flightgear into next
[flightgear.git] / src / GUI / WaypointList.cxx
1
2
3 #ifdef HAVE_CONFIG_H
4 #  include "config.h"
5 #endif
6
7 #include "WaypointList.hxx"
8
9 #include <algorithm>
10 #include <boost/tuple/tuple.hpp>
11
12 #include <plib/puAux.h>
13
14 #include <simgear/route/waypoint.hxx>
15 #include <simgear/structure/callback.hxx>
16 #include <simgear/sg_inlines.h>
17
18 #include <Main/globals.hxx>
19 #include <Main/fg_props.hxx>
20
21 #include <Navaids/positioned.hxx>
22 #include <Autopilot/route_mgr.hxx>
23
24 using namespace flightgear;
25
26 enum {
27   SCROLL_NO = 0,
28   SCROLL_UP,
29   SCROLL_DOWN
30 };
31   
32 static const double BLINK_TIME = 0.3;
33 static const int DRAG_START_DISTANCE_PX = 5;
34   
35 class RouteManagerWaypointModel : 
36   public WaypointList::Model, 
37   public SGPropertyChangeListener
38 {
39 public:
40   RouteManagerWaypointModel()
41   {
42     _rm = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
43     
44     SGPropertyNode* routeEdited = fgGetNode("/autopilot/route-manager/signals/edited", true);
45     routeEdited->addChangeListener(this);
46   }
47   
48   virtual ~RouteManagerWaypointModel()
49   {
50     SGPropertyNode* routeEdited = fgGetNode("/autopilot/route-manager/signals/edited", true);
51     routeEdited->removeChangeListener(this);
52   }
53   
54 // implement WaypointList::Model
55   virtual unsigned int numWaypoints() const
56   {
57     return _rm->numWaypts();
58   }
59   
60   virtual int currentWaypoint() const
61   {
62     return _rm->currentIndex();
63   }
64   
65   virtual flightgear::Waypt* waypointAt(unsigned int index) const
66   {
67     if (index >= numWaypoints()) {
68       return NULL;
69     }
70     
71     return _rm->wayptAtIndex(index);
72   }
73
74   virtual void deleteAt(unsigned int index)
75   {
76     _rm->removeWayptAtIndex(index);
77   }
78   
79   virtual void moveWaypointToIndex(unsigned int srcIndex, unsigned int destIndex)
80   {
81     SG_LOG(SG_GENERAL, SG_INFO, "moveWaypoint: from " << srcIndex << " to " << destIndex);
82     if (destIndex > srcIndex) {
83       --destIndex;
84     }
85     
86     WayptRef w(_rm->removeWayptAtIndex(srcIndex));
87     SG_LOG(SG_GENERAL, SG_INFO, "wpt:" << w->ident());
88     _rm->insertWayptAtIndex(w, destIndex);
89   }
90   
91   virtual void setUpdateCallback(SGCallback* cb)
92   {
93     _cb = cb;
94   }
95     
96 // implement SGPropertyChangeListener
97   void valueChanged(SGPropertyNode *prop)
98   {
99     if (_cb) {
100       (*_cb)();
101     }
102   }
103 private:
104   FGRouteMgr* _rm;
105   SGCallback* _cb;
106 };
107
108 //////////////////////////////////////////////////////////////////////////////
109
110 static void drawClippedString(puFont& font, const char* s, int x, int y, int maxWidth)
111 {
112   int fullWidth = font.getStringWidth(s);
113   if (fullWidth <= maxWidth) { // common case, easy and efficent
114     font.drawString(s, x, y);
115     return;
116   }
117   
118   std::string buf(s);
119   int len = buf.size();
120   do {
121     buf.resize(--len);
122     fullWidth = font.getStringWidth(buf.c_str());
123   } while (fullWidth > maxWidth);
124   
125   font.drawString(buf.c_str(), x, y);
126 }
127
128 //////////////////////////////////////////////////////////////////////////////
129
130 WaypointList::WaypointList(int x, int y, int width, int height) :
131   puFrame(x, y, width, height),
132   GUI_ID(FGCLASS_WAYPOINTLIST),
133   _scrollPx(0),
134   _dragging(false),
135   _dragScroll(SCROLL_NO),
136   _showLatLon(false),
137   _model(NULL),
138   _updateCallback(NULL),
139   _scrollCallback(NULL),
140   _blink(false)
141 {
142   // pretend to be a list, so fgPopup doesn't mess with our mouse events
143   type |= PUCLASS_LIST;  
144   setModel(new RouteManagerWaypointModel());
145   setSize(width, height);
146   setValue(-1);
147   
148   _blinkTimer.stamp();
149 }
150
151 WaypointList::~WaypointList()
152 {
153   delete _model;
154   delete _updateCallback;
155   delete _scrollCallback;
156 }
157
158 void WaypointList::setUpdateCallback(SGCallback* cb)
159 {
160   _updateCallback = cb;
161 }
162
163 void WaypointList::setScrollCallback(SGCallback* cb)
164 {
165   _scrollCallback = cb;
166 }
167
168 void WaypointList::setSize(int width, int height)
169 {
170   double scrollP = getVScrollPercent();
171   _heightPx = height;
172   puFrame::setSize(width, height);
173   
174   if (wantsVScroll()) {
175     setVScrollPercent(scrollP);
176   } else {
177     _scrollPx = 0;
178   }
179 }
180
181 int WaypointList::checkHit ( int button, int updown, int x, int y )
182 {
183   if ( isHit( x, y ) || ( puActiveWidget () == this ) )
184   {
185     doHit ( button, updown, x, y ) ;
186     return TRUE ;
187   }
188
189   return FALSE ;
190 }
191
192
193 void WaypointList::doHit( int button, int updown, int x, int y )
194 {
195   puFrame::doHit(button, updown, x, y);  
196   if (updown == PU_DRAG) {
197     handleDrag(x, y);
198     return;
199   }
200   
201   if (button != active_mouse_button) {
202     return;
203   }
204       
205   if (updown == PU_UP) {
206     puDeactivateWidget();
207     if (_dragging) {
208       doDrop(x, y);
209       return;
210     }
211   } else if (updown == PU_DOWN) {
212     puSetActiveWidget(this, x, y);
213     _mouseDownX = x;
214     _mouseDownY = y;
215     return;
216   }
217   
218 // update selection
219   int row = rowForY(y - abox.min[1]);
220   if (row >= (int) _model->numWaypoints()) {
221     row = -1; // 'no selection'
222   }
223
224   if (row == getSelected()) {
225     _showLatLon = !_showLatLon;
226     puPostRefresh();
227     return;
228   }
229   
230   setSelected(row);
231 }
232
233 void WaypointList::handleDrag(int x, int y)
234 {
235   if (!_dragging) {
236     // don't start drags immediately, require a certain mouse movement first
237     int manhattanLength = abs(x - _mouseDownX) + abs(y - _mouseDownY);
238     if (manhattanLength < DRAG_START_DISTANCE_PX) {
239       return;
240     }
241     
242     _dragSourceRow = rowForY(y - abox.min[1]);
243     Waypt* wp = _model->waypointAt(_dragSourceRow);
244     if (!wp || wp->flag(WPT_GENERATED)) {
245       return; // don't allow generated points to be dragged
246     }
247     
248     _dragging = true;
249     _dragScroll = SCROLL_NO;
250   }
251   
252   if (y < abox.min[1]) {
253     if (_dragScroll != SCROLL_DOWN) {
254       _dragScroll = SCROLL_DOWN;
255       _dragScrollTime.stamp();
256     }
257   } else if (y > abox.max[1]) {
258     if (_dragScroll != SCROLL_UP) {
259       _dragScroll = SCROLL_UP;
260       _dragScrollTime.stamp();
261     }
262   } else {
263     _dragScroll = SCROLL_NO;
264     _dragTargetRow = rowForY(y - abox.min[1] - (rowHeightPx() / 2));
265   }
266 }
267
268 void WaypointList::doDrop(int x, int y)
269 {
270   _dragging = false;
271   puDeactivateWidget();
272   
273   SG_LOG(SG_GENERAL, SG_INFO, "doDrop");
274   
275   if ((y < abox.min[1]) || (y >= abox.max[1])) {
276     SG_LOG(SG_GENERAL, SG_INFO, "y out of bounds:" << y);
277     return;
278   }
279   
280   if (_dragSourceRow == _dragTargetRow) {
281     SG_LOG(SG_GENERAL, SG_INFO, "source and target row match");
282     return;
283   }
284   
285   _model->moveWaypointToIndex(_dragSourceRow, _dragTargetRow);
286   
287   // keep row indexes linged up when moving an item down the list
288   if (_dragSourceRow < _dragTargetRow) {
289     --_dragTargetRow;
290   }
291   
292   setSelected(_dragTargetRow);
293 }
294
295 void WaypointList::invokeDownCallback(void)
296 {
297   _dragging = false;
298   _dragScroll = SCROLL_NO;
299   SG_LOG(SG_GENERAL, SG_INFO, "cancel drag");
300 }
301
302 int WaypointList::rowForY(int y) const
303 {
304   if (!_model) {
305     return -1;
306   }
307   
308   // flip y to increase down, not up (as rows do)
309   int flipY = _heightPx - y;
310   int row = (flipY + _scrollPx) / rowHeightPx();
311   return row;
312 }
313
314 void WaypointList::draw( int dx, int dy )
315 {
316   puFrame::draw(dx, dy);
317
318   if (!_model) {
319     return;
320   }
321
322   if (_dragScroll != SCROLL_NO) {
323     doDragScroll();
324   }
325   
326   double dt = (SGTimeStamp::now() - _blinkTimer).toSecs();
327   if (dt > BLINK_TIME) {
328     _blinkTimer.stamp();
329     _blink = !_blink;
330   }
331   
332   glEnable(GL_SCISSOR_TEST);
333   GLint sx = (int) abox.min[0],
334     sy = abox.min[1];
335   GLsizei w = (GLsizei) abox.max[0] - abox.min[0],
336     h = _heightPx;
337     
338   sx += border_thickness;
339   sy += border_thickness;
340   w -= 2 * border_thickness;
341   h -= 2 * border_thickness;
342     
343   glScissor(sx + dx, sy + dy, w, h);
344   int row = firstVisibleRow(), 
345     final = lastVisibleRow(),
346     rowHeight = rowHeightPx(),
347     y = rowHeight;
348   
349   y -= (_scrollPx % rowHeight); // partially draw the first row
350   
351   _arrowWidth = legendFont.getStringWidth(">");
352   for ( ; row <= final; ++row, y += rowHeight) {
353     drawRow(dx, dy, row, y);
354   } // of row drawing iteration
355   
356   glDisable(GL_SCISSOR_TEST);
357     
358   if (_dragging) {
359     // draw the insert marker after the rows
360     int insertY = (_dragTargetRow * rowHeight) - _scrollPx;
361     SG_CLAMP_RANGE(insertY, 0, std::min(_heightPx, totalHeightPx()));
362     
363     glColor4f(1.0f, 0.5f, 0.0f, 0.8f);
364     glLineWidth(3.0f);
365     glBegin(GL_LINES);
366       glVertex2f(dx + abox.min[0], dy + abox.max[1] - insertY);
367       glVertex2f(dx + abox.max[0], dy + abox.max[1] - insertY);
368     glEnd();
369   }
370 }
371
372 void WaypointList::drawRow(int dx, int dy, int rowIndex, int y)
373 {
374   flightgear::Waypt* wp(_model->waypointAt(rowIndex));
375     
376   bool isSelected = (rowIndex == getSelected());
377   bool isCurrent = (rowIndex == _model->currentWaypoint());
378   bool isDragSource = (_dragging && (rowIndex == _dragSourceRow));
379   
380   puBox bkgBox = abox;
381   bkgBox.min[1] = abox.max[1] - y;
382   bkgBox.max[1] = bkgBox.min[1] + rowHeightPx();
383   
384   puColour col;
385   puFont* f = &legendFont;
386   bool drawBox = false;
387   
388   if (wp->flag(WPT_MISS)) {
389     drawBox = true;
390     puSetColor(col, 1.0, 0.0, 0.0, 0.3);  // red
391   } else if (wp->flag(WPT_ARRIVAL)) {
392     drawBox = true;
393     puSetColor(col, 0.0, 0.0, 0.0, 0.3);
394   } else if (wp->flag(WPT_DEPARTURE)) {
395     drawBox = true;
396     puSetColor(col, 0.0, 0.0, 0.0, 0.3);
397   }
398   
399   if (isDragSource) {
400     // draw later, on *top* of text string
401   } else if (isSelected) { // -PLAIN means selected, apparently
402     bkgBox.draw(dx, dy, -PUSTYLE_PLAIN, colour, false, 0);
403   } else if (drawBox) {
404     bkgBox.draw(dx, dy, PUSTYLE_PLAIN, &col, false, 0);
405   }
406   
407   if (isCurrent) {
408     glColor4f (1.0, 0.5, 0.0, 1.0) ;
409   } else {
410     glColor4fv ( colour [ PUCOL_LEGEND ] ) ;
411   }
412   
413   int xx = dx + abox.min[0] + PUSTR_LGAP;
414   int yy = dy + abox.max[1] - y ;
415   yy += 4; // center text in row height
416   
417   if (isCurrent) {
418     f->drawString(">", xx, yy);
419   }
420   
421   int x = xx;
422   x += _arrowWidth + PUSTR_LGAP;
423   
424   // row textual data
425
426   char buffer[128];
427   int count = ::snprintf(buffer, 128, "%03d   %-5s", rowIndex, wp->ident().c_str());
428  
429   FGPositioned* src = wp->source(); 
430   if (src && !src->name().empty() && (src->name() != wp->ident())) { 
431     // append name if present, and different to id
432     ::snprintf(buffer + count, 128 - count, " (%s)", src->name().c_str());
433   }
434
435   drawClippedString(legendFont, buffer, x, yy, 300);
436   x += 300 + PUSTR_LGAP;
437   
438   if (_showLatLon) {
439     SGGeod p(wp->position());
440     char ns = (p.getLatitudeDeg() > 0.0) ? 'N' : 'S';
441     char ew = (p.getLongitudeDeg() > 0.0) ? 'E' : 'W';
442     
443     ::snprintf(buffer, 128 - count, "%4.2f%c %4.2f%c",
444       fabs(p.getLongitudeDeg()), ew, fabs(p.getLatitudeDeg()), ns);
445   } else if (rowIndex > 0) {
446     double courseDeg;
447     double distanceM;
448     Waypt* prev = _model->waypointAt(rowIndex - 1);
449     boost::tie(courseDeg, distanceM) = wp->courseAndDistanceFrom(prev->position());
450   
451     ::snprintf(buffer, 128 - count, "%03.0f %5.1fnm",
452       courseDeg, distanceM * SG_METER_TO_NM);
453   }
454
455   f->drawString(buffer, x, yy);
456   x += 100 + PUSTR_LGAP;
457   
458   if (wp->altitudeRestriction() != RESTRICT_NONE) {
459     int altHundredFt = (wp->altitudeFt() + 50) / 100; // round to nearest 100ft
460     if (altHundredFt < 100) {
461       count = ::snprintf(buffer, 128, "%d'", altHundredFt * 100);
462     } else { // display as a flight-level
463       count = ::snprintf(buffer, 128, "FL%d", altHundredFt);
464     }
465     
466     f->drawString(buffer, x, yy);
467   } // of valid wp altitude
468   x += 60 + PUSTR_LGAP;
469   
470   if (wp->speedRestriction() != RESTRICT_NONE) {
471     count = ::snprintf(buffer, 126, "%dKts", (int) wp->speedKts());
472     f->drawString(buffer, x, yy);
473   }
474   
475   if (isDragSource) {
476     puSetColor(col, 1.0, 0.5, 0.0, 0.5);
477     bkgBox.draw(dx, dy, PUSTYLE_PLAIN, &col, false, 0);
478   }
479 }
480
481 const double SCROLL_PX_SEC = 200.0;
482
483 void WaypointList::doDragScroll()
484 {
485   double dt = (SGTimeStamp::now() - _dragScrollTime).toSecs();
486   _dragScrollTime.stamp();
487   int deltaPx = (int)(dt * SCROLL_PX_SEC);
488   
489   if (_dragScroll == SCROLL_UP) {
490     _scrollPx = _scrollPx - deltaPx;
491     SG_CLAMP_RANGE(_scrollPx, 0, scrollRangePx());
492     _dragTargetRow = firstVisibleRow();
493   } else {
494     _scrollPx = _scrollPx + deltaPx;
495     SG_CLAMP_RANGE(_scrollPx, 0, scrollRangePx());
496     _dragTargetRow = lastFullyVisibleRow() + 1;
497   }
498   
499   if (_scrollCallback) {
500     (*_scrollCallback)();
501   }
502 }
503
504 int WaypointList::getSelected()
505 {
506   return getIntegerValue();
507 }
508
509 void WaypointList::setSelected(int rowIndex)
510 {
511   if (rowIndex == getSelected()) {
512     return;
513   }
514   
515   setValue(rowIndex);
516   invokeCallback();
517   if (rowIndex == -1) {
518     return;
519   }
520
521   ensureRowVisible(rowIndex);
522 }
523
524 void WaypointList::ensureRowVisible(int rowIndex)
525 {
526   if ((rowIndex >= firstFullyVisibleRow()) && (rowIndex <= lastFullyVisibleRow())) {
527     return; // already visible, fine
528   }
529   
530   // ideal position would place the desired row in the middle of the
531   // visible section - hence subtract half the visible height.
532   int targetScrollPx = (rowIndex * rowHeightPx()) - (_heightPx / 2);
533   
534   // clamp the scroll value to something valid
535   SG_CLAMP_RANGE(targetScrollPx, 0, scrollRangePx());
536   _scrollPx = targetScrollPx;
537   
538   puPostRefresh();
539   if (_scrollCallback) { // keep scroll observers in sync
540     (*_scrollCallback)();
541   }
542 }
543
544 unsigned int WaypointList::numWaypoints() const
545 {
546   if (!_model) {
547     return 0;
548   }
549   
550   return _model->numWaypoints();
551 }
552
553 bool WaypointList::wantsVScroll() const
554 {
555   return totalHeightPx() > _heightPx;
556 }
557
558 float WaypointList::getVScrollPercent() const
559 {
560   float scrollRange = scrollRangePx();
561   if (scrollRange < 1.0f) {
562     return 0.0;
563   }
564   
565   return _scrollPx / scrollRange;
566 }
567
568 float WaypointList::getVScrollThumbPercent() const
569 {
570   return _heightPx / (float) totalHeightPx();
571 }
572
573 void WaypointList::setVScrollPercent(float perc)
574 {
575   float scrollRange = scrollRangePx();
576   _scrollPx = (int)(scrollRange * perc);
577 }
578
579 int WaypointList::firstVisibleRow() const
580 {
581   return _scrollPx / rowHeightPx();
582 }
583
584 int WaypointList::firstFullyVisibleRow() const
585 {
586   int rh = rowHeightPx();
587   return (_scrollPx + rh - 1) / rh;
588 }
589   
590 int WaypointList::numVisibleRows() const
591 {
592   int rh = rowHeightPx();
593   int topOffset = _scrollPx % rh; // pixels of first visible row
594   return (_heightPx - topOffset + rh - 1) / rh;
595
596 }
597
598 int WaypointList::numFullyVisibleRows() const
599 {
600   int rh = rowHeightPx();
601   int topOffset = _scrollPx % rh; // pixels of first visible row
602   return (_heightPx - topOffset) / rh;
603 }
604
605 int WaypointList::rowHeightPx() const
606 {
607   return legendFont.getStringHeight() + PUSTR_BGAP;
608 }
609
610 int WaypointList::scrollRangePx() const
611 {
612   return std::max(0, totalHeightPx() - _heightPx);
613 }
614
615 int WaypointList::totalHeightPx() const
616 {
617   if (!_model) {
618     return 0;
619   }
620   
621   return (int) _model->numWaypoints() * rowHeightPx();
622 }
623
624 int WaypointList::lastFullyVisibleRow() const
625 {
626   int row = firstFullyVisibleRow() + numFullyVisibleRows();
627   return std::min(row, (int) _model->numWaypoints() - 1);
628 }
629
630 int WaypointList::lastVisibleRow() const
631 {
632   int row = firstVisibleRow() + numVisibleRows();
633   return std::min(row, (int) _model->numWaypoints() - 1);
634 }
635
636 void WaypointList::setModel(Model* model)
637 {
638   if (_model) {
639     delete _model;
640   }
641   
642   _model = model;
643   _model->setUpdateCallback(make_callback(this, &WaypointList::modelUpdateCallback));
644   
645   puPostRefresh();
646 }
647
648 int WaypointList::checkKey (int key, int updown )
649 {
650   if ((updown == PU_UP) || !isVisible () || !isActive () || (window != puGetWindow())) {
651     return FALSE ;
652   }
653   
654   switch (key)
655   {
656   case PU_KEY_HOME:
657     setSelected(0);
658     break;
659
660   case PU_KEY_END:
661     setSelected(_model->numWaypoints() - 1);
662     break ;
663
664   case PU_KEY_UP        :
665   case PU_KEY_PAGE_UP   :
666     if (getSelected() >= 0) {
667       setSelected(getSelected() - 1);
668     }
669     break ;
670
671   case PU_KEY_DOWN      :
672   case PU_KEY_PAGE_DOWN : {
673     int newSel = getSelected() + 1;
674     if (newSel >= (int) _model->numWaypoints()) {
675       setSelected(-1);
676     } else {
677       setSelected(newSel);
678     }
679     break ;
680   }
681   
682   case '-':
683     if (getSelected() >= 0) {
684       Waypt* wp = _model->waypointAt(getSelected());
685       if (wp->flag(WPT_GENERATED)) {
686         break;
687       }
688       
689       if (wp->altitudeRestriction() != RESTRICT_NONE) {
690         int curAlt = (static_cast<int>(wp->altitudeFt()) + 50) / 100;
691         if (curAlt <= 0) {
692           wp->setAltitude(0, RESTRICT_NONE);
693         } else {
694           wp->setAltitude((curAlt - 10) * 100, wp->altitudeRestriction());
695         }
696       }
697     }
698     break;
699     
700   case '=':
701     if (getSelected() >= 0) {
702       flightgear::Waypt* wp = _model->waypointAt(getSelected());
703       if (wp->flag(WPT_GENERATED)) {
704         break;
705       }
706         
707       if (wp->altitudeRestriction() == RESTRICT_NONE) {
708         wp->setAltitude(1000, RESTRICT_AT);
709       } else {
710         int curAlt = (static_cast<int>(wp->altitudeFt()) + 50) / 100;
711         wp->setAltitude((curAlt + 10) * 100, wp->altitudeRestriction());
712       }
713     }
714     break;
715   
716   case 0x7f: // delete
717     if (getSelected() >= 0) {
718       int index = getSelected();
719       Waypt* wp = _model->waypointAt(getSelected());
720       if (wp->flag(WPT_GENERATED)) {
721         break;
722       }
723       
724       _model->deleteAt(index);
725       setSelected(index - 1);
726     }
727     break;
728
729   default :
730     return FALSE;
731   }
732
733   return TRUE ;
734 }
735
736 void WaypointList::modelUpdateCallback()
737 {
738   // local stuff
739   
740   if (_updateCallback) {
741     (*_updateCallback)();
742   }
743 }
744
745 //////////////////////////////////////////////////////////////////////////////
746
747
748 static void handle_scrollbar(puObject* scrollbar)
749 {
750   ScrolledWaypointList* self = (ScrolledWaypointList*)scrollbar->getUserData();
751   self->setScrollPercent(scrollbar->getFloatValue());
752 }
753
754 static void waypointListCb(puObject* wpl)
755 {
756   ScrolledWaypointList* self = (ScrolledWaypointList*)wpl->getUserData();
757   self->setValue(wpl->getIntegerValue());
758   self->invokeCallback();
759 }
760
761 ScrolledWaypointList::ScrolledWaypointList(int x, int y, int width, int height) :
762   puGroup(x,y),
763   _scrollWidth(16)
764 {
765   // ensure our type is compound, so fgPopup::applySize doesn't descend into
766   // us, and try to cast our children's user-data to GUIInfo*.
767   type |= PUCLASS_LIST;
768   
769   init(width, height);
770 }
771
772 void ScrolledWaypointList::setValue(float v)
773 {
774   puGroup::setValue(v);
775   _list->setValue(v);
776 }
777
778 void ScrolledWaypointList::setValue(int v)
779 {
780   puGroup::setValue(v);
781   _list->setValue(v);
782 }
783
784 void ScrolledWaypointList::init(int w, int h)
785 {
786   _list = new WaypointList(0, 0, w, h);
787   _list->setUpdateCallback(make_callback(this, &ScrolledWaypointList::modelUpdated));
788   _hasVScroll = _list->wantsVScroll();
789   _list->setUserData(this);
790   _list->setCallback(waypointListCb);
791   
792   _list->setScrollCallback(make_callback(this, &ScrolledWaypointList::updateScroll));
793   
794   _scrollbar = new puaScrollBar(w - _scrollWidth, 0, h, 
795     1 /*arrow*/, 1 /* vertical */, _scrollWidth);
796   _scrollbar->setMinValue(0.0);
797   _scrollbar->setMaxValue(1.0);
798   _scrollbar->setUserData(this);
799   _scrollbar->setCallback(handle_scrollbar);
800   close(); // close the group
801   
802   setSize(w, h);
803 }
804
805 void ScrolledWaypointList::modelUpdated()
806 {  
807   int w, h;
808   getSize(&w, &h);
809   updateWantsScroll(w, h);
810 }
811
812 void ScrolledWaypointList::setScrollPercent(float v)
813 {
814   // slider's min is the bottom, so invert the value
815   _list->setVScrollPercent(1.0f - v); 
816 }
817
818 void ScrolledWaypointList::setSize(int w, int h)
819 {
820   updateWantsScroll(w, h);
821 }
822
823 void ScrolledWaypointList::updateWantsScroll(int w, int h)
824 {
825   _hasVScroll = _list->wantsVScroll();
826   
827   if (_hasVScroll) {
828     _scrollbar->reveal();
829     _scrollbar->setPosition(w - _scrollWidth, 0);
830     _scrollbar->setSize(_scrollWidth, h);
831     _list->setSize(w - _scrollWidth, h);
832     updateScroll();
833   } else {
834     _scrollbar->hide();
835     _list->setSize(w, h);
836   }
837 }
838
839 void ScrolledWaypointList::updateScroll()
840 {
841  // _scrollbar->setMaxValue(_list->numWaypoints());
842   _scrollbar->setValue(1.0f - _list->getVScrollPercent());
843   _scrollbar->setSliderFraction(_list->getVScrollThumbPercent());
844 }
845