]> git.mxchange.org Git - flightgear.git/blob - src/GUI/WaypointList.cxx
Merge branch 'next' of http://git.gitorious.org/fg/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     int currentWpIndex = currentWaypoint();
87     WayptRef w(_rm->removeWayptAtIndex(srcIndex));
88     SG_LOG(SG_GENERAL, SG_INFO, "wpt:" << w->ident());
89     _rm->insertWayptAtIndex(w, destIndex);
90
91     if (srcIndex == currentWpIndex) {
92         // current waypoint was moved
93         _rm->jumpToIndex(destIndex);
94     }
95   }
96   
97   virtual void setUpdateCallback(SGCallback* cb)
98   {
99     _cb = cb;
100   }
101     
102 // implement SGPropertyChangeListener
103   void valueChanged(SGPropertyNode *prop)
104   {
105     if (_cb) {
106       (*_cb)();
107     }
108   }
109 private:
110   FGRouteMgr* _rm;
111   SGCallback* _cb;
112 };
113
114 //////////////////////////////////////////////////////////////////////////////
115
116 static void drawClippedString(puFont& font, const char* s, int x, int y, int maxWidth)
117 {
118   int fullWidth = font.getStringWidth(s);
119   if (fullWidth <= maxWidth) { // common case, easy and efficent
120     font.drawString(s, x, y);
121     return;
122   }
123   
124   std::string buf(s);
125   int len = buf.size();
126   do {
127     buf.resize(--len);
128     fullWidth = font.getStringWidth(buf.c_str());
129   } while (fullWidth > maxWidth);
130   
131   font.drawString(buf.c_str(), x, y);
132 }
133
134 //////////////////////////////////////////////////////////////////////////////
135
136 WaypointList::WaypointList(int x, int y, int width, int height) :
137   puFrame(x, y, width, height),
138   GUI_ID(FGCLASS_WAYPOINTLIST),
139   _scrollPx(0),
140   _dragging(false),
141   _dragScroll(SCROLL_NO),
142   _showLatLon(false),
143   _model(NULL),
144   _updateCallback(NULL),
145   _scrollCallback(NULL),
146   _blink(false)
147 {
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);
152   setValue(-1);
153   
154   _blinkTimer.stamp();
155 }
156
157 WaypointList::~WaypointList()
158 {
159   delete _model;
160   delete _updateCallback;
161   delete _scrollCallback;
162 }
163
164 void WaypointList::setUpdateCallback(SGCallback* cb)
165 {
166   _updateCallback = cb;
167 }
168
169 void WaypointList::setScrollCallback(SGCallback* cb)
170 {
171   _scrollCallback = cb;
172 }
173
174 void WaypointList::setSize(int width, int height)
175 {
176   double scrollP = getVScrollPercent();
177   _heightPx = height;
178   puFrame::setSize(width, height);
179   
180   if (wantsVScroll()) {
181     setVScrollPercent(scrollP);
182   } else {
183     _scrollPx = 0;
184   }
185 }
186
187 int WaypointList::checkHit ( int button, int updown, int x, int y )
188 {
189   if ( isHit( x, y ) || ( puActiveWidget () == this ) )
190   {
191     doHit ( button, updown, x, y ) ;
192     return TRUE ;
193   }
194
195   return FALSE ;
196 }
197
198
199 void WaypointList::doHit( int button, int updown, int x, int y )
200 {
201   puFrame::doHit(button, updown, x, y);  
202   if (updown == PU_DRAG) {
203     handleDrag(x, y);
204     return;
205   }
206   
207   if (button != active_mouse_button) {
208     return;
209   }
210       
211   if (updown == PU_UP) {
212     puDeactivateWidget();
213     if (_dragging) {
214       doDrop(x, y);
215       return;
216     }
217   } else if (updown == PU_DOWN) {
218     puSetActiveWidget(this, x, y);
219     _mouseDownX = x;
220     _mouseDownY = y;
221     return;
222   }
223   
224 // update selection
225   int row = rowForY(y - abox.min[1]);
226   if (row >= (int) _model->numWaypoints()) {
227     row = -1; // 'no selection'
228   }
229
230   if (row == getSelected()) {
231     _showLatLon = !_showLatLon;
232     puPostRefresh();
233     return;
234   }
235   
236   setSelected(row);
237 }
238
239 void WaypointList::handleDrag(int x, int y)
240 {
241   if (!_dragging) {
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) {
245       return;
246     }
247     
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
252     }
253     
254     _dragging = true;
255     _dragScroll = SCROLL_NO;
256   }
257   
258   if (y < abox.min[1]) {
259     if (_dragScroll != SCROLL_DOWN) {
260       _dragScroll = SCROLL_DOWN;
261       _dragScrollTime.stamp();
262     }
263   } else if (y > abox.max[1]) {
264     if (_dragScroll != SCROLL_UP) {
265       _dragScroll = SCROLL_UP;
266       _dragScrollTime.stamp();
267     }
268   } else {
269     _dragScroll = SCROLL_NO;
270     _dragTargetRow = rowForY(y - abox.min[1] - (rowHeightPx() / 2));
271   }
272 }
273
274 void WaypointList::doDrop(int x, int y)
275 {
276   _dragging = false;
277   puDeactivateWidget();
278   
279   SG_LOG(SG_GENERAL, SG_INFO, "doDrop");
280   
281   if ((y < abox.min[1]) || (y >= abox.max[1])) {
282     SG_LOG(SG_GENERAL, SG_INFO, "y out of bounds:" << y);
283     return;
284   }
285   
286   if (_dragSourceRow == _dragTargetRow) {
287     SG_LOG(SG_GENERAL, SG_INFO, "source and target row match");
288     return;
289   }
290   
291   _model->moveWaypointToIndex(_dragSourceRow, _dragTargetRow);
292   
293   // keep row indexes linged up when moving an item down the list
294   if (_dragSourceRow < _dragTargetRow) {
295     --_dragTargetRow;
296   }
297   
298   setSelected(_dragTargetRow);
299 }
300
301 void WaypointList::invokeDownCallback(void)
302 {
303   _dragging = false;
304   _dragScroll = SCROLL_NO;
305   SG_LOG(SG_GENERAL, SG_INFO, "cancel drag");
306 }
307
308 int WaypointList::rowForY(int y) const
309 {
310   if (!_model) {
311     return -1;
312   }
313   
314   // flip y to increase down, not up (as rows do)
315   int flipY = _heightPx - y;
316   int row = (flipY + _scrollPx) / rowHeightPx();
317   return row;
318 }
319
320 void WaypointList::draw( int dx, int dy )
321 {
322   puFrame::draw(dx, dy);
323
324   if (!_model) {
325     return;
326   }
327
328   if (_dragScroll != SCROLL_NO) {
329     doDragScroll();
330   }
331   
332   double dt = (SGTimeStamp::now() - _blinkTimer).toSecs();
333   if (dt > BLINK_TIME) {
334     _blinkTimer.stamp();
335     _blink = !_blink;
336   }
337   
338   glEnable(GL_SCISSOR_TEST);
339   GLint sx = (int) abox.min[0],
340     sy = abox.min[1];
341   GLsizei w = (GLsizei) abox.max[0] - abox.min[0],
342     h = _heightPx;
343     
344   sx += border_thickness;
345   sy += border_thickness;
346   w -= 2 * border_thickness;
347   h -= 2 * border_thickness;
348     
349   glScissor(sx + dx, sy + dy, w, h);
350   int row = firstVisibleRow(), 
351     final = lastVisibleRow(),
352     rowHeight = rowHeightPx(),
353     y = rowHeight;
354   
355   y -= (_scrollPx % rowHeight); // partially draw the first row
356   
357   _arrowWidth = legendFont.getStringWidth(">");
358   for ( ; row <= final; ++row, y += rowHeight) {
359     drawRow(dx, dy, row, y);
360   } // of row drawing iteration
361   
362   glDisable(GL_SCISSOR_TEST);
363     
364   if (_dragging) {
365     // draw the insert marker after the rows
366     int insertY = (_dragTargetRow * rowHeight) - _scrollPx;
367     SG_CLAMP_RANGE(insertY, 0, std::min(_heightPx, totalHeightPx()));
368     
369     glColor4f(1.0f, 0.5f, 0.0f, 0.8f);
370     glLineWidth(3.0f);
371     glBegin(GL_LINES);
372       glVertex2f(dx + abox.min[0], dy + abox.max[1] - insertY);
373       glVertex2f(dx + abox.max[0], dy + abox.max[1] - insertY);
374     glEnd();
375   }
376 }
377
378 void WaypointList::drawRow(int dx, int dy, int rowIndex, int y)
379 {
380   flightgear::Waypt* wp(_model->waypointAt(rowIndex));
381     
382   bool isSelected = (rowIndex == getSelected());
383   bool isCurrent = (rowIndex == _model->currentWaypoint());
384   bool isDragSource = (_dragging && (rowIndex == _dragSourceRow));
385   
386   puBox bkgBox = abox;
387   bkgBox.min[1] = abox.max[1] - y;
388   bkgBox.max[1] = bkgBox.min[1] + rowHeightPx();
389   
390   puColour col;
391   puFont* f = &legendFont;
392   bool drawBox = false;
393   
394   if (wp->flag(WPT_MISS)) {
395     drawBox = true;
396     puSetColor(col, 1.0, 0.0, 0.0, 0.3);  // red
397   } else if (wp->flag(WPT_ARRIVAL)) {
398     drawBox = true;
399     puSetColor(col, 0.0, 0.0, 0.0, 0.3);
400   } else if (wp->flag(WPT_DEPARTURE)) {
401     drawBox = true;
402     puSetColor(col, 0.0, 0.0, 0.0, 0.3);
403   }
404   
405   if (isDragSource) {
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);
411   }
412   
413   if (isCurrent) {
414     glColor4f (1.0, 0.5, 0.0, 1.0) ;
415   } else {
416     glColor4fv ( colour [ PUCOL_LEGEND ] ) ;
417   }
418   
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
422   
423   if (isCurrent) {
424     f->drawString(">", xx, yy);
425   }
426   
427   int x = xx;
428   x += _arrowWidth + PUSTR_LGAP;
429   
430   // row textual data
431
432   char buffer[128];
433   int count = ::snprintf(buffer, 128, "%03d   %-5s", rowIndex, wp->ident().c_str());
434  
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());
439   }
440
441   drawClippedString(legendFont, buffer, x, yy, 300);
442   x += 300 + PUSTR_LGAP;
443   
444   if (_showLatLon) {
445     SGGeod p(wp->position());
446     char ns = (p.getLatitudeDeg() > 0.0) ? 'N' : 'S';
447     char ew = (p.getLongitudeDeg() > 0.0) ? 'E' : 'W';
448     
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) {
452     double courseDeg;
453     double distanceM;
454     Waypt* prev = _model->waypointAt(rowIndex - 1);
455     boost::tie(courseDeg, distanceM) = wp->courseAndDistanceFrom(prev->position());
456   
457     ::snprintf(buffer, 128 - count, "%03.0f %5.1fnm",
458       courseDeg, distanceM * SG_METER_TO_NM);
459   }
460
461   f->drawString(buffer, x, yy);
462   x += 100 + PUSTR_LGAP;
463   
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);
470     }
471     
472     f->drawString(buffer, x, yy);
473   } // of valid wp altitude
474   x += 60 + PUSTR_LGAP;
475   
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);
482   }
483   
484   if (isDragSource) {
485     puSetColor(col, 1.0, 0.5, 0.0, 0.5);
486     bkgBox.draw(dx, dy, PUSTYLE_PLAIN, &col, false, 0);
487   }
488 }
489
490 const double SCROLL_PX_SEC = 200.0;
491
492 void WaypointList::doDragScroll()
493 {
494   double dt = (SGTimeStamp::now() - _dragScrollTime).toSecs();
495   _dragScrollTime.stamp();
496   int deltaPx = (int)(dt * SCROLL_PX_SEC);
497   
498   if (_dragScroll == SCROLL_UP) {
499     _scrollPx = _scrollPx - deltaPx;
500     SG_CLAMP_RANGE(_scrollPx, 0, scrollRangePx());
501     _dragTargetRow = firstVisibleRow();
502   } else {
503     _scrollPx = _scrollPx + deltaPx;
504     SG_CLAMP_RANGE(_scrollPx, 0, scrollRangePx());
505     _dragTargetRow = lastFullyVisibleRow() + 1;
506   }
507   
508   if (_scrollCallback) {
509     (*_scrollCallback)();
510   }
511 }
512
513 int WaypointList::getSelected()
514 {
515   return getIntegerValue();
516 }
517
518 void WaypointList::setSelected(int rowIndex)
519 {
520   if (rowIndex == getSelected()) {
521     return;
522   }
523   
524   setValue(rowIndex);
525   invokeCallback();
526   if (rowIndex == -1) {
527     return;
528   }
529
530   ensureRowVisible(rowIndex);
531 }
532
533 void WaypointList::ensureRowVisible(int rowIndex)
534 {
535   if ((rowIndex >= firstFullyVisibleRow()) && (rowIndex <= lastFullyVisibleRow())) {
536     return; // already visible, fine
537   }
538   
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);
542   
543   // clamp the scroll value to something valid
544   SG_CLAMP_RANGE(targetScrollPx, 0, scrollRangePx());
545   _scrollPx = targetScrollPx;
546   
547   puPostRefresh();
548   if (_scrollCallback) { // keep scroll observers in sync
549     (*_scrollCallback)();
550   }
551 }
552
553 unsigned int WaypointList::numWaypoints() const
554 {
555   if (!_model) {
556     return 0;
557   }
558   
559   return _model->numWaypoints();
560 }
561
562 bool WaypointList::wantsVScroll() const
563 {
564   return totalHeightPx() > _heightPx;
565 }
566
567 float WaypointList::getVScrollPercent() const
568 {
569   float scrollRange = scrollRangePx();
570   if (scrollRange < 1.0f) {
571     return 0.0;
572   }
573   
574   return _scrollPx / scrollRange;
575 }
576
577 float WaypointList::getVScrollThumbPercent() const
578 {
579   return _heightPx / (float) totalHeightPx();
580 }
581
582 void WaypointList::setVScrollPercent(float perc)
583 {
584   float scrollRange = scrollRangePx();
585   _scrollPx = (int)(scrollRange * perc);
586 }
587
588 int WaypointList::firstVisibleRow() const
589 {
590   return _scrollPx / rowHeightPx();
591 }
592
593 int WaypointList::firstFullyVisibleRow() const
594 {
595   int rh = rowHeightPx();
596   return (_scrollPx + rh - 1) / rh;
597 }
598   
599 int WaypointList::numVisibleRows() const
600 {
601   int rh = rowHeightPx();
602   int topOffset = _scrollPx % rh; // pixels of first visible row
603   return (_heightPx - topOffset + rh - 1) / rh;
604
605 }
606
607 int WaypointList::numFullyVisibleRows() const
608 {
609   int rh = rowHeightPx();
610   int topOffset = _scrollPx % rh; // pixels of first visible row
611   return (_heightPx - topOffset) / rh;
612 }
613
614 int WaypointList::rowHeightPx() const
615 {
616   return legendFont.getStringHeight() + PUSTR_BGAP;
617 }
618
619 int WaypointList::scrollRangePx() const
620 {
621   return std::max(0, totalHeightPx() - _heightPx);
622 }
623
624 int WaypointList::totalHeightPx() const
625 {
626   if (!_model) {
627     return 0;
628   }
629   
630   return (int) _model->numWaypoints() * rowHeightPx();
631 }
632
633 int WaypointList::lastFullyVisibleRow() const
634 {
635   int row = firstFullyVisibleRow() + numFullyVisibleRows();
636   return std::min(row, (int) _model->numWaypoints() - 1);
637 }
638
639 int WaypointList::lastVisibleRow() const
640 {
641   int row = firstVisibleRow() + numVisibleRows();
642   return std::min(row, (int) _model->numWaypoints() - 1);
643 }
644
645 void WaypointList::setModel(Model* model)
646 {
647   if (_model) {
648     delete _model;
649   }
650   
651   _model = model;
652   _model->setUpdateCallback(make_callback(this, &WaypointList::modelUpdateCallback));
653   
654   puPostRefresh();
655 }
656
657 int WaypointList::checkKey (int key, int updown )
658 {
659   if ((updown == PU_UP) || !isVisible () || !isActive () || (window != puGetWindow())) {
660     return FALSE ;
661   }
662   
663   switch (key)
664   {
665   case PU_KEY_HOME:
666     setSelected(0);
667     break;
668
669   case PU_KEY_END:
670     setSelected(_model->numWaypoints() - 1);
671     break ;
672
673   case PU_KEY_UP        :
674   case PU_KEY_PAGE_UP   :
675     if (getSelected() >= 0) {
676       setSelected(getSelected() - 1);
677     }
678     break ;
679
680   case PU_KEY_DOWN      :
681   case PU_KEY_PAGE_DOWN : {
682     int newSel = getSelected() + 1;
683     if (newSel >= (int) _model->numWaypoints()) {
684       setSelected(-1);
685     } else {
686       setSelected(newSel);
687     }
688     break ;
689   }
690   
691   case '-':
692     if (getSelected() >= 0) {
693       Waypt* wp = _model->waypointAt(getSelected());
694       if (wp->flag(WPT_GENERATED)) {
695         break;
696       }
697       
698       if (wp->altitudeRestriction() != RESTRICT_NONE) {
699         int curAlt = (static_cast<int>(wp->altitudeFt()) + 50) / 100;
700         if (curAlt <= 0) {
701           wp->setAltitude(0, RESTRICT_NONE);
702         } else {
703           wp->setAltitude((curAlt - 10) * 100, wp->altitudeRestriction());
704         }
705       }
706     }
707     break;
708     
709   case '=':
710     if (getSelected() >= 0) {
711       flightgear::Waypt* wp = _model->waypointAt(getSelected());
712       if (wp->flag(WPT_GENERATED)) {
713         break;
714       }
715         
716       if (wp->altitudeRestriction() == RESTRICT_NONE) {
717         wp->setAltitude(1000, RESTRICT_AT);
718       } else {
719         int curAlt = (static_cast<int>(wp->altitudeFt()) + 50) / 100;
720         wp->setAltitude((curAlt + 10) * 100, wp->altitudeRestriction());
721       }
722     }
723     break;
724   
725   case 0x7f: // delete
726     if (getSelected() >= 0) {
727       int index = getSelected();
728       Waypt* wp = _model->waypointAt(getSelected());
729       if (wp->flag(WPT_GENERATED)) {
730         break;
731       }
732       
733       _model->deleteAt(index);
734       setSelected(index - 1);
735     }
736     break;
737
738   default :
739     return FALSE;
740   }
741
742   return TRUE ;
743 }
744
745 void WaypointList::modelUpdateCallback()
746 {
747   // local stuff
748   
749   if (_updateCallback) {
750     (*_updateCallback)();
751   }
752 }
753
754 //////////////////////////////////////////////////////////////////////////////
755
756
757 static void handle_scrollbar(puObject* scrollbar)
758 {
759   ScrolledWaypointList* self = (ScrolledWaypointList*)scrollbar->getUserData();
760   self->setScrollPercent(scrollbar->getFloatValue());
761 }
762
763 static void waypointListCb(puObject* wpl)
764 {
765   ScrolledWaypointList* self = (ScrolledWaypointList*)wpl->getUserData();
766   self->setValue(wpl->getIntegerValue());
767   self->invokeCallback();
768 }
769
770 ScrolledWaypointList::ScrolledWaypointList(int x, int y, int width, int height) :
771   puGroup(x,y),
772   _scrollWidth(16)
773 {
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;
777   
778   init(width, height);
779 }
780
781 void ScrolledWaypointList::setValue(float v)
782 {
783   puGroup::setValue(v);
784   _list->setValue(v);
785 }
786
787 void ScrolledWaypointList::setValue(int v)
788 {
789   puGroup::setValue(v);
790   _list->setValue(v);
791 }
792
793 void ScrolledWaypointList::init(int w, int h)
794 {
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);
800   
801   _list->setScrollCallback(make_callback(this, &ScrolledWaypointList::updateScroll));
802   
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
810   
811   setSize(w, h);
812 }
813
814 void ScrolledWaypointList::modelUpdated()
815 {  
816   int w, h;
817   getSize(&w, &h);
818   updateWantsScroll(w, h);
819 }
820
821 void ScrolledWaypointList::setScrollPercent(float v)
822 {
823   // slider's min is the bottom, so invert the value
824   _list->setVScrollPercent(1.0f - v); 
825 }
826
827 void ScrolledWaypointList::setSize(int w, int h)
828 {
829   updateWantsScroll(w, h);
830   puGroup::setSize(w, h);
831 }
832
833 void ScrolledWaypointList::updateWantsScroll(int w, int h)
834 {
835   _hasVScroll = _list->wantsVScroll();
836   
837   if (_hasVScroll) {
838     _scrollbar->reveal();
839     _scrollbar->setPosition(w - _scrollWidth, 0);
840     _scrollbar->setSize(_scrollWidth, h);
841     _list->setSize(w - _scrollWidth, h);
842     updateScroll();
843   } else {
844     _scrollbar->hide();
845     _list->setSize(w, h);
846   }
847 }
848
849 void ScrolledWaypointList::updateScroll()
850 {
851  // _scrollbar->setMaxValue(_list->numWaypoints());
852   _scrollbar->setValue(1.0f - _list->getVScrollPercent());
853   _scrollbar->setSliderFraction(_list->getVScrollThumbPercent());
854 }
855