]> git.mxchange.org Git - flightgear.git/blob - src/GUI/WaypointList.cxx
Fix route-path bugs:
[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.trackForIndex(rowIndex);
485     double distanceM = path.distanceForIndex(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     char aboveAtBelow = ' ';
495     if (wp->altitudeRestriction() == RESTRICT_ABOVE) {
496       aboveAtBelow = 'A';
497     } else if (wp->altitudeRestriction() == RESTRICT_BELOW) {
498       aboveAtBelow = 'B';
499     }
500     
501     int altHundredFt = (wp->altitudeFt() + 50) / 100; // round to nearest 100ft
502     if (altHundredFt < 100) {
503       count = ::snprintf(buffer, 128, "%d'%c", altHundredFt * 100, aboveAtBelow);
504     } else { // display as a flight-level
505       count = ::snprintf(buffer, 128, "FL%d%c", altHundredFt, aboveAtBelow);
506     }
507     
508     f->drawString(buffer, x, yy);
509   } // of valid wp altitude
510   x += 60 + PUSTR_LGAP;
511   
512   if (wp->speedRestriction() == SPEED_RESTRICT_MACH) {
513     count = ::snprintf(buffer, 126, "%03.2fM", wp->speedMach());
514     f->drawString(buffer, x, yy);
515   } else if (wp->speedRestriction() != RESTRICT_NONE) {
516     count = ::snprintf(buffer, 126, "%dKts", (int) wp->speedKts());
517     f->drawString(buffer, x, yy);
518   }
519   
520   if (isDragSource) {
521     puSetColor(col, 1.0, 0.5, 0.0, 0.5);
522     bkgBox.draw(dx, dy, PUSTYLE_PLAIN, &col, false, 0);
523   }
524 }
525
526 const double SCROLL_PX_SEC = 200.0;
527
528 void WaypointList::doDragScroll()
529 {
530   double dt = (SGTimeStamp::now() - _dragScrollTime).toSecs();
531   _dragScrollTime.stamp();
532   int deltaPx = (int)(dt * SCROLL_PX_SEC);
533   
534   if (_dragScroll == SCROLL_UP) {
535     _scrollPx = _scrollPx - deltaPx;
536     SG_CLAMP_RANGE(_scrollPx, 0, scrollRangePx());
537     _dragTargetRow = firstVisibleRow();
538   } else {
539     _scrollPx = _scrollPx + deltaPx;
540     SG_CLAMP_RANGE(_scrollPx, 0, scrollRangePx());
541     _dragTargetRow = lastFullyVisibleRow() + 1;
542   }
543   
544   if (_scrollCallback) {
545     (*_scrollCallback)();
546   }
547 }
548
549 int WaypointList::getSelected()
550 {
551   return getIntegerValue();
552 }
553
554 void WaypointList::setSelected(int rowIndex)
555 {
556   if (rowIndex == getSelected()) {
557     return;
558   }
559   
560   setValue(rowIndex);
561   invokeCallback();
562   if (rowIndex == -1) {
563     return;
564   }
565
566   ensureRowVisible(rowIndex);
567 }
568
569 void WaypointList::ensureRowVisible(int rowIndex)
570 {
571   if ((rowIndex >= firstFullyVisibleRow()) && (rowIndex <= lastFullyVisibleRow())) {
572     return; // already visible, fine
573   }
574   
575   // ideal position would place the desired row in the middle of the
576   // visible section - hence subtract half the visible height.
577   int targetScrollPx = (rowIndex * rowHeightPx()) - (_heightPx / 2);
578   
579   // clamp the scroll value to something valid
580   SG_CLAMP_RANGE(targetScrollPx, 0, scrollRangePx());
581   _scrollPx = targetScrollPx;
582   
583   puPostRefresh();
584   if (_scrollCallback) { // keep scroll observers in sync
585     (*_scrollCallback)();
586   }
587 }
588
589 unsigned int WaypointList::numWaypoints() const
590 {
591   if (!_model) {
592     return 0;
593   }
594   
595   return _model->numWaypoints();
596 }
597
598 bool WaypointList::wantsVScroll() const
599 {
600   return totalHeightPx() > _heightPx;
601 }
602
603 float WaypointList::getVScrollPercent() const
604 {
605   float scrollRange = scrollRangePx();
606   if (scrollRange < 1.0f) {
607     return 0.0;
608   }
609   
610   return _scrollPx / scrollRange;
611 }
612
613 float WaypointList::getVScrollThumbPercent() const
614 {
615   return _heightPx / (float) totalHeightPx();
616 }
617
618 void WaypointList::setVScrollPercent(float perc)
619 {
620   float scrollRange = scrollRangePx();
621   _scrollPx = (int)(scrollRange * perc);
622 }
623
624 int WaypointList::firstVisibleRow() const
625 {
626   return _scrollPx / rowHeightPx();
627 }
628
629 int WaypointList::firstFullyVisibleRow() const
630 {
631   int rh = rowHeightPx();
632   return (_scrollPx + rh - 1) / rh;
633 }
634   
635 int WaypointList::numVisibleRows() const
636 {
637   int rh = rowHeightPx();
638   int topOffset = _scrollPx % rh; // pixels of first visible row
639   return (_heightPx - topOffset + rh - 1) / rh;
640
641 }
642
643 int WaypointList::numFullyVisibleRows() const
644 {
645   int rh = rowHeightPx();
646   int topOffset = _scrollPx % rh; // pixels of first visible row
647   return (_heightPx - topOffset) / rh;
648 }
649
650 int WaypointList::rowHeightPx() const
651 {
652   return legendFont.getStringHeight() + PUSTR_BGAP;
653 }
654
655 int WaypointList::scrollRangePx() const
656 {
657   return std::max(0, totalHeightPx() - _heightPx);
658 }
659
660 int WaypointList::totalHeightPx() const
661 {
662   if (!_model) {
663     return 0;
664   }
665   
666   return (int) _model->numWaypoints() * rowHeightPx();
667 }
668
669 int WaypointList::lastFullyVisibleRow() const
670 {
671   int row = firstFullyVisibleRow() + numFullyVisibleRows();
672   return std::min(row, (int) _model->numWaypoints() - 1);
673 }
674
675 int WaypointList::lastVisibleRow() const
676 {
677   int row = firstVisibleRow() + numVisibleRows();
678   return std::min(row, (int) _model->numWaypoints() - 1);
679 }
680
681 void WaypointList::setModel(Model* model)
682 {
683   if (_model) {
684     delete _model;
685   }
686   
687   _model = model;
688   _model->setUpdateCallback(make_callback(this, &WaypointList::modelUpdateCallback));
689   
690   puPostRefresh();
691 }
692
693 int WaypointList::checkKey (int key, int updown )
694 {
695   if ((updown == PU_UP) || !isVisible () || !isActive () || (window != puGetWindow())) {
696     return FALSE ;
697   }
698
699 #ifdef AVOID_FLIGHT_KEYS
700     return FALSE;
701 #endif
702   
703   switch (key)
704   {
705   case PU_KEY_HOME:
706     setSelected(0);
707     break;
708
709   case PU_KEY_END:
710     setSelected(_model->numWaypoints() - 1);
711     break ;
712
713   case PU_KEY_UP        :
714   case PU_KEY_PAGE_UP   :
715     if (getSelected() >= 0) {
716       setSelected(getSelected() - 1);
717     }
718     break ;
719
720   case PU_KEY_DOWN      :
721   case PU_KEY_PAGE_DOWN : {
722     int newSel = getSelected() + 1;
723     if (newSel >= (int) _model->numWaypoints()) {
724       setSelected(-1);
725     } else {
726       setSelected(newSel);
727     }
728     break ;
729   }
730   
731   case '-':
732     if (getSelected() >= 0) {
733       Waypt* wp = _model->waypointAt(getSelected());
734       if (wp->flag(WPT_GENERATED)) {
735         break;
736       }
737       
738       if (wp->altitudeRestriction() != RESTRICT_NONE) {
739         int curAlt = (static_cast<int>(wp->altitudeFt()) + 50) / 100;
740         if (curAlt <= 0) {
741           wp->setAltitude(0, RESTRICT_NONE);
742         } else {
743           wp->setAltitude((curAlt - 10) * 100, wp->altitudeRestriction());
744         }
745       }
746     }
747     break;
748     
749   case '=':
750     if (getSelected() >= 0) {
751       flightgear::Waypt* wp = _model->waypointAt(getSelected());
752       if (wp->flag(WPT_GENERATED)) {
753         break;
754       }
755         
756       if (wp->altitudeRestriction() == RESTRICT_NONE) {
757         wp->setAltitude(1000, RESTRICT_AT);
758       } else {
759         int curAlt = (static_cast<int>(wp->altitudeFt()) + 50) / 100;
760         wp->setAltitude((curAlt + 10) * 100, wp->altitudeRestriction());
761       }
762     }
763     break;
764   
765   case 0x7f: // delete
766     if (getSelected() >= 0) {
767       int index = getSelected();
768       Waypt* wp = _model->waypointAt(getSelected());
769       if (wp->flag(WPT_GENERATED)) {
770         break;
771       }
772       
773       _model->deleteAt(index);
774       setSelected(index - 1);
775     }
776     break;
777
778   default :
779     return FALSE;
780   }
781
782   return TRUE ;
783 }
784
785 void WaypointList::modelUpdateCallback()
786 {
787   // local stuff
788   
789   if (_updateCallback) {
790     (*_updateCallback)();
791   }
792 }
793
794 //////////////////////////////////////////////////////////////////////////////
795
796
797 static void handle_scrollbar(puObject* scrollbar)
798 {
799   ScrolledWaypointList* self = (ScrolledWaypointList*)scrollbar->getUserData();
800   self->setScrollPercent(scrollbar->getFloatValue());
801 }
802
803 static void waypointListCb(puObject* wpl)
804 {
805   ScrolledWaypointList* self = (ScrolledWaypointList*)wpl->getUserData();
806   self->setValue(wpl->getIntegerValue());
807   self->invokeCallback();
808 }
809
810 ScrolledWaypointList::ScrolledWaypointList(int x, int y, int width, int height) :
811   puGroup(x,y),
812   _scrollWidth(16)
813 {
814   // ensure our type is compound, so fgPopup::applySize doesn't descend into
815   // us, and try to cast our children's user-data to GUIInfo*.
816   type |= PUCLASS_LIST;
817   
818   init(width, height);
819 }
820
821 void ScrolledWaypointList::setValue(float v)
822 {
823   puGroup::setValue(v);
824   _list->setValue(v);
825 }
826
827 void ScrolledWaypointList::setValue(int v)
828 {
829   puGroup::setValue(v);
830   _list->setValue(v);
831 }
832
833 void ScrolledWaypointList::init(int w, int h)
834 {
835   _list = new WaypointList(0, 0, w, h);
836   _list->setUpdateCallback(make_callback(this, &ScrolledWaypointList::modelUpdated));
837   _hasVScroll = _list->wantsVScroll();
838   _list->setUserData(this);
839   _list->setCallback(waypointListCb);
840   
841   _list->setScrollCallback(make_callback(this, &ScrolledWaypointList::updateScroll));
842   
843   _scrollbar = new puaScrollBar(w - _scrollWidth, 0, h, 
844     1 /*arrow*/, 1 /* vertical */, _scrollWidth);
845   _scrollbar->setMinValue(0.0);
846   _scrollbar->setMaxValue(1.0);
847   _scrollbar->setUserData(this);
848   _scrollbar->setCallback(handle_scrollbar);
849   close(); // close the group
850   
851   setSize(w, h);
852 }
853
854 void ScrolledWaypointList::modelUpdated()
855 {  
856   int w, h;
857   getSize(&w, &h);
858   updateWantsScroll(w, h);
859 }
860
861 void ScrolledWaypointList::setScrollPercent(float v)
862 {
863   // slider's min is the bottom, so invert the value
864   _list->setVScrollPercent(1.0f - v); 
865 }
866
867 void ScrolledWaypointList::setSize(int w, int h)
868 {
869   updateWantsScroll(w, h);
870   puGroup::setSize(w, h);
871 }
872
873 void ScrolledWaypointList::updateWantsScroll(int w, int h)
874 {
875   _hasVScroll = _list->wantsVScroll();
876   
877   if (_hasVScroll) {
878     _scrollbar->reveal();
879     _scrollbar->setPosition(w - _scrollWidth, 0);
880     _scrollbar->setSize(_scrollWidth, h);
881     _list->setSize(w - _scrollWidth, h);
882     updateScroll();
883   } else {
884     _scrollbar->hide();
885     _list->setSize(w, h);
886   }
887 }
888
889 void ScrolledWaypointList::updateScroll()
890 {
891  // _scrollbar->setMaxValue(_list->numWaypoints());
892   _scrollbar->setValue(1.0f - _list->getVScrollPercent());
893   _scrollbar->setSliderFraction(_list->getVScrollThumbPercent());
894 }
895