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