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