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