]> git.mxchange.org Git - flightgear.git/blob - src/GUI/WaypointList.cxx
Expand route-manager waypoint info, to support smarter FMS functions.
[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() == SPEED_RESTRICT_MACH) {
471     count = ::snprintf(buffer, 126, "%03.2fM", wp->speedMach());
472     f->drawString(buffer, x, yy);
473   } else if (wp->speedRestriction() != RESTRICT_NONE) {
474     count = ::snprintf(buffer, 126, "%dKts", (int) wp->speedKts());
475     f->drawString(buffer, x, yy);
476   }
477   
478   if (isDragSource) {
479     puSetColor(col, 1.0, 0.5, 0.0, 0.5);
480     bkgBox.draw(dx, dy, PUSTYLE_PLAIN, &col, false, 0);
481   }
482 }
483
484 const double SCROLL_PX_SEC = 200.0;
485
486 void WaypointList::doDragScroll()
487 {
488   double dt = (SGTimeStamp::now() - _dragScrollTime).toSecs();
489   _dragScrollTime.stamp();
490   int deltaPx = (int)(dt * SCROLL_PX_SEC);
491   
492   if (_dragScroll == SCROLL_UP) {
493     _scrollPx = _scrollPx - deltaPx;
494     SG_CLAMP_RANGE(_scrollPx, 0, scrollRangePx());
495     _dragTargetRow = firstVisibleRow();
496   } else {
497     _scrollPx = _scrollPx + deltaPx;
498     SG_CLAMP_RANGE(_scrollPx, 0, scrollRangePx());
499     _dragTargetRow = lastFullyVisibleRow() + 1;
500   }
501   
502   if (_scrollCallback) {
503     (*_scrollCallback)();
504   }
505 }
506
507 int WaypointList::getSelected()
508 {
509   return getIntegerValue();
510 }
511
512 void WaypointList::setSelected(int rowIndex)
513 {
514   if (rowIndex == getSelected()) {
515     return;
516   }
517   
518   setValue(rowIndex);
519   invokeCallback();
520   if (rowIndex == -1) {
521     return;
522   }
523
524   ensureRowVisible(rowIndex);
525 }
526
527 void WaypointList::ensureRowVisible(int rowIndex)
528 {
529   if ((rowIndex >= firstFullyVisibleRow()) && (rowIndex <= lastFullyVisibleRow())) {
530     return; // already visible, fine
531   }
532   
533   // ideal position would place the desired row in the middle of the
534   // visible section - hence subtract half the visible height.
535   int targetScrollPx = (rowIndex * rowHeightPx()) - (_heightPx / 2);
536   
537   // clamp the scroll value to something valid
538   SG_CLAMP_RANGE(targetScrollPx, 0, scrollRangePx());
539   _scrollPx = targetScrollPx;
540   
541   puPostRefresh();
542   if (_scrollCallback) { // keep scroll observers in sync
543     (*_scrollCallback)();
544   }
545 }
546
547 unsigned int WaypointList::numWaypoints() const
548 {
549   if (!_model) {
550     return 0;
551   }
552   
553   return _model->numWaypoints();
554 }
555
556 bool WaypointList::wantsVScroll() const
557 {
558   return totalHeightPx() > _heightPx;
559 }
560
561 float WaypointList::getVScrollPercent() const
562 {
563   float scrollRange = scrollRangePx();
564   if (scrollRange < 1.0f) {
565     return 0.0;
566   }
567   
568   return _scrollPx / scrollRange;
569 }
570
571 float WaypointList::getVScrollThumbPercent() const
572 {
573   return _heightPx / (float) totalHeightPx();
574 }
575
576 void WaypointList::setVScrollPercent(float perc)
577 {
578   float scrollRange = scrollRangePx();
579   _scrollPx = (int)(scrollRange * perc);
580 }
581
582 int WaypointList::firstVisibleRow() const
583 {
584   return _scrollPx / rowHeightPx();
585 }
586
587 int WaypointList::firstFullyVisibleRow() const
588 {
589   int rh = rowHeightPx();
590   return (_scrollPx + rh - 1) / rh;
591 }
592   
593 int WaypointList::numVisibleRows() const
594 {
595   int rh = rowHeightPx();
596   int topOffset = _scrollPx % rh; // pixels of first visible row
597   return (_heightPx - topOffset + rh - 1) / rh;
598
599 }
600
601 int WaypointList::numFullyVisibleRows() const
602 {
603   int rh = rowHeightPx();
604   int topOffset = _scrollPx % rh; // pixels of first visible row
605   return (_heightPx - topOffset) / rh;
606 }
607
608 int WaypointList::rowHeightPx() const
609 {
610   return legendFont.getStringHeight() + PUSTR_BGAP;
611 }
612
613 int WaypointList::scrollRangePx() const
614 {
615   return std::max(0, totalHeightPx() - _heightPx);
616 }
617
618 int WaypointList::totalHeightPx() const
619 {
620   if (!_model) {
621     return 0;
622   }
623   
624   return (int) _model->numWaypoints() * rowHeightPx();
625 }
626
627 int WaypointList::lastFullyVisibleRow() const
628 {
629   int row = firstFullyVisibleRow() + numFullyVisibleRows();
630   return std::min(row, (int) _model->numWaypoints() - 1);
631 }
632
633 int WaypointList::lastVisibleRow() const
634 {
635   int row = firstVisibleRow() + numVisibleRows();
636   return std::min(row, (int) _model->numWaypoints() - 1);
637 }
638
639 void WaypointList::setModel(Model* model)
640 {
641   if (_model) {
642     delete _model;
643   }
644   
645   _model = model;
646   _model->setUpdateCallback(make_callback(this, &WaypointList::modelUpdateCallback));
647   
648   puPostRefresh();
649 }
650
651 int WaypointList::checkKey (int key, int updown )
652 {
653   if ((updown == PU_UP) || !isVisible () || !isActive () || (window != puGetWindow())) {
654     return FALSE ;
655   }
656   
657   switch (key)
658   {
659   case PU_KEY_HOME:
660     setSelected(0);
661     break;
662
663   case PU_KEY_END:
664     setSelected(_model->numWaypoints() - 1);
665     break ;
666
667   case PU_KEY_UP        :
668   case PU_KEY_PAGE_UP   :
669     if (getSelected() >= 0) {
670       setSelected(getSelected() - 1);
671     }
672     break ;
673
674   case PU_KEY_DOWN      :
675   case PU_KEY_PAGE_DOWN : {
676     int newSel = getSelected() + 1;
677     if (newSel >= (int) _model->numWaypoints()) {
678       setSelected(-1);
679     } else {
680       setSelected(newSel);
681     }
682     break ;
683   }
684   
685   case '-':
686     if (getSelected() >= 0) {
687       Waypt* wp = _model->waypointAt(getSelected());
688       if (wp->flag(WPT_GENERATED)) {
689         break;
690       }
691       
692       if (wp->altitudeRestriction() != RESTRICT_NONE) {
693         int curAlt = (static_cast<int>(wp->altitudeFt()) + 50) / 100;
694         if (curAlt <= 0) {
695           wp->setAltitude(0, RESTRICT_NONE);
696         } else {
697           wp->setAltitude((curAlt - 10) * 100, wp->altitudeRestriction());
698         }
699       }
700     }
701     break;
702     
703   case '=':
704     if (getSelected() >= 0) {
705       flightgear::Waypt* wp = _model->waypointAt(getSelected());
706       if (wp->flag(WPT_GENERATED)) {
707         break;
708       }
709         
710       if (wp->altitudeRestriction() == RESTRICT_NONE) {
711         wp->setAltitude(1000, RESTRICT_AT);
712       } else {
713         int curAlt = (static_cast<int>(wp->altitudeFt()) + 50) / 100;
714         wp->setAltitude((curAlt + 10) * 100, wp->altitudeRestriction());
715       }
716     }
717     break;
718   
719   case 0x7f: // delete
720     if (getSelected() >= 0) {
721       int index = getSelected();
722       Waypt* wp = _model->waypointAt(getSelected());
723       if (wp->flag(WPT_GENERATED)) {
724         break;
725       }
726       
727       _model->deleteAt(index);
728       setSelected(index - 1);
729     }
730     break;
731
732   default :
733     return FALSE;
734   }
735
736   return TRUE ;
737 }
738
739 void WaypointList::modelUpdateCallback()
740 {
741   // local stuff
742   
743   if (_updateCallback) {
744     (*_updateCallback)();
745   }
746 }
747
748 //////////////////////////////////////////////////////////////////////////////
749
750
751 static void handle_scrollbar(puObject* scrollbar)
752 {
753   ScrolledWaypointList* self = (ScrolledWaypointList*)scrollbar->getUserData();
754   self->setScrollPercent(scrollbar->getFloatValue());
755 }
756
757 static void waypointListCb(puObject* wpl)
758 {
759   ScrolledWaypointList* self = (ScrolledWaypointList*)wpl->getUserData();
760   self->setValue(wpl->getIntegerValue());
761   self->invokeCallback();
762 }
763
764 ScrolledWaypointList::ScrolledWaypointList(int x, int y, int width, int height) :
765   puGroup(x,y),
766   _scrollWidth(16)
767 {
768   // ensure our type is compound, so fgPopup::applySize doesn't descend into
769   // us, and try to cast our children's user-data to GUIInfo*.
770   type |= PUCLASS_LIST;
771   
772   init(width, height);
773 }
774
775 void ScrolledWaypointList::setValue(float v)
776 {
777   puGroup::setValue(v);
778   _list->setValue(v);
779 }
780
781 void ScrolledWaypointList::setValue(int v)
782 {
783   puGroup::setValue(v);
784   _list->setValue(v);
785 }
786
787 void ScrolledWaypointList::init(int w, int h)
788 {
789   _list = new WaypointList(0, 0, w, h);
790   _list->setUpdateCallback(make_callback(this, &ScrolledWaypointList::modelUpdated));
791   _hasVScroll = _list->wantsVScroll();
792   _list->setUserData(this);
793   _list->setCallback(waypointListCb);
794   
795   _list->setScrollCallback(make_callback(this, &ScrolledWaypointList::updateScroll));
796   
797   _scrollbar = new puaScrollBar(w - _scrollWidth, 0, h, 
798     1 /*arrow*/, 1 /* vertical */, _scrollWidth);
799   _scrollbar->setMinValue(0.0);
800   _scrollbar->setMaxValue(1.0);
801   _scrollbar->setUserData(this);
802   _scrollbar->setCallback(handle_scrollbar);
803   close(); // close the group
804   
805   setSize(w, h);
806 }
807
808 void ScrolledWaypointList::modelUpdated()
809 {  
810   int w, h;
811   getSize(&w, &h);
812   updateWantsScroll(w, h);
813 }
814
815 void ScrolledWaypointList::setScrollPercent(float v)
816 {
817   // slider's min is the bottom, so invert the value
818   _list->setVScrollPercent(1.0f - v); 
819 }
820
821 void ScrolledWaypointList::setSize(int w, int h)
822 {
823   updateWantsScroll(w, h);
824   puGroup::setSize(w, h);
825 }
826
827 void ScrolledWaypointList::updateWantsScroll(int w, int h)
828 {
829   _hasVScroll = _list->wantsVScroll();
830   
831   if (_hasVScroll) {
832     _scrollbar->reveal();
833     _scrollbar->setPosition(w - _scrollWidth, 0);
834     _scrollbar->setSize(_scrollWidth, h);
835     _list->setSize(w - _scrollWidth, h);
836     updateScroll();
837   } else {
838     _scrollbar->hide();
839     _list->setSize(w, h);
840   }
841 }
842
843 void ScrolledWaypointList::updateScroll()
844 {
845  // _scrollbar->setMaxValue(_list->numWaypoints());
846   _scrollbar->setValue(1.0f - _list->getVScrollPercent());
847   _scrollbar->setSliderFraction(_list->getVScrollThumbPercent());
848 }
849