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