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