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