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