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