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