]> git.mxchange.org Git - flightgear.git/blob - src/Cockpit/panel.cxx
Use new SGBucket API in tile-manager
[flightgear.git] / src / Cockpit / panel.cxx
1 //  panel.cxx - default, 2D single-engine prop instrument panel
2 //
3 //  Written by David Megginson, started January 2000.
4 //
5 //  This program is free software; you can redistribute it and/or
6 //  modify it under the terms of the GNU General Public License as
7 //  published by the Free Software Foundation; either version 2 of the
8 //  License, or (at your option) any later version.
9 // 
10 //  This program is distributed in the hope that it will be useful, but
11 //  WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 //  General Public License for more details.
14 //
15 //  You should have received a copy of the GNU General Public License
16 //  along with this program; if not, write to the Free Software
17 //  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 //
19 //  $Id$
20
21 //JVK
22 // On 2D panels all instruments include light sources were in night displayed
23 // with a red mask (instrument light). It is not correct for light sources
24 // (bulbs). There is added new layer property "emissive" (boolean) (only for
25 // textured layers).
26 // If a layer has to shine set it in the "instrument_def_file.xml" inside the
27 // <layer> tag by adding <emissive>true</emissive> tag. When omitted the default
28 // value is for backward compatibility set to false.
29
30 #ifdef HAVE_CONFIG_H
31 #  include <config.h>
32 #endif
33
34 #include "panel.hxx"
35
36 #include <stdio.h>      // sprintf
37 #include <string.h>
38 #include <iostream>
39 #include <cassert>
40
41 #include <osg/CullFace>
42 #include <osg/Depth>
43 #include <osg/Material>
44 #include <osg/Matrixf>
45 #include <osg/TexEnv>
46 #include <osg/PolygonOffset>
47
48 #include <simgear/compiler.h>
49
50 #include <plib/fnt.h>
51
52 #include <boost/foreach.hpp>
53 #include <simgear/debug/logstream.hxx>
54 #include <simgear/misc/sg_path.hxx>
55 #include <simgear/misc/strutils.hxx>
56 #include <simgear/scene/model/model.hxx>
57 #include <osg/GLU>
58
59 #include <Main/globals.hxx>
60 #include <Main/fg_props.hxx>
61 #include <Viewer/viewmgr.hxx>
62 #include <Viewer/viewer.hxx>
63 #include <Time/light.hxx>
64 #include <GUI/FGFontCache.hxx>  
65 #include <Instrumentation/dclgps.hxx>
66
67 #define WIN_X 0
68 #define WIN_Y 0
69 #define WIN_W 1024
70 #define WIN_H 768
71
72 // The number of polygon-offset "units" to place between layers.  In
73 // principle, one is supposed to be enough.  In practice, I find that
74 // my hardware/driver requires many more.
75 #define POFF_UNITS 8
76
77 const double MOUSE_ACTION_REPEAT_DELAY = 0.5; // 500msec initial delay
78 const double MOUSE_ACTION_REPEAT_INTERVAL = 0.1; // 10Hz repeat rate
79
80 using std::map;
81
82 ////////////////////////////////////////////////////////////////////////
83 // Local functions.
84 ////////////////////////////////////////////////////////////////////////
85
86
87 /**
88  * Calculate the aspect adjustment for the panel.
89  */
90 static float
91 get_aspect_adjust (int xsize, int ysize)
92 {
93   float ideal_aspect = float(WIN_W) / float(WIN_H);
94   float real_aspect = float(xsize) / float(ysize);
95   return (real_aspect / ideal_aspect);
96 }
97 \f
98 ////////////////////////////////////////////////////////////////////////
99 // Implementation of FGTextureManager.
100 ////////////////////////////////////////////////////////////////////////
101
102 map<std::string,osg::ref_ptr<osg::Texture2D> > FGTextureManager::_textureMap;
103
104 osg::Texture2D*
105 FGTextureManager::createTexture (const std::string &relativePath, bool staticTexture)
106 {
107   osg::Texture2D* texture = _textureMap[relativePath].get();
108   if (texture == 0) {
109     SGPath tpath = globals->resolve_aircraft_path(relativePath);
110     texture = SGLoadTexture2D(staticTexture, tpath);
111
112     _textureMap[relativePath] = texture;
113     if (!_textureMap[relativePath].valid()) 
114       SG_LOG( SG_COCKPIT, SG_ALERT, "Texture *still* doesn't exist" );
115     SG_LOG( SG_COCKPIT, SG_DEBUG, "Created texture " << relativePath );
116   }
117
118   return texture;
119 }
120
121
122 void FGTextureManager::addTexture(const std::string &relativePath,
123                                   osg::Texture2D* texture)
124 {
125     _textureMap[relativePath] = texture;
126 }
127 \f
128 ////////////////////////////////////////////////////////////////////////
129 // Implementation of FGCropped Texture.
130 ////////////////////////////////////////////////////////////////////////
131
132
133 FGCroppedTexture::FGCroppedTexture ()
134   : _path(""), _texture(0),
135     _minX(0.0), _minY(0.0), _maxX(1.0), _maxY(1.0)
136 {
137 }
138
139
140 FGCroppedTexture::FGCroppedTexture (const std::string &path,
141                                     float minX, float minY,
142                                     float maxX, float maxY)
143   : _path(path), _texture(0),
144     _minX(minX), _minY(minY), _maxX(maxX), _maxY(maxY)
145 {
146 }
147
148
149 FGCroppedTexture::~FGCroppedTexture ()
150 {
151 }
152
153
154 osg::StateSet*
155 FGCroppedTexture::getTexture ()
156 {
157   if (_texture == 0) {
158     _texture = new osg::StateSet;
159     _texture->setTextureAttribute(0, FGTextureManager::createTexture(_path));
160     _texture->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON);
161     _texture->setTextureAttribute(0, new osg::TexEnv(osg::TexEnv::MODULATE));
162   }
163   return _texture.get();
164 }
165
166
167 \f
168 ////////////////////////////////////////////////////////////////////////
169 // Implementation of FGPanel.
170 ////////////////////////////////////////////////////////////////////////
171
172 static fntRenderer text_renderer;
173 static sgVec4 panel_color;
174 static sgVec4 emissive_panel_color = {1,1,1,1};
175
176 /**
177  * Constructor.
178  */
179 FGPanel::FGPanel ()
180   : _mouseDown(false),
181     _mouseInstrument(0),
182     _width(WIN_W), _height(int(WIN_H * 0.5768 + 1)),
183     _x_offset(fgGetNode("/sim/panel/x-offset", true)),
184     _y_offset(fgGetNode("/sim/panel/y-offset", true)),
185     _jitter(fgGetNode("/sim/panel/jitter", true)),
186     _flipx(fgGetNode("/sim/panel/flip-x", true)),
187     _xsize_node(fgGetNode("/sim/startup/xsize", true)),
188     _ysize_node(fgGetNode("/sim/startup/ysize", true)),
189     _enable_depth_test(false),
190     _autohide(true),
191     _drawPanelHotspots("/sim/panel-hotspots")
192 {
193 }
194
195
196 /**
197  * Destructor.
198  */
199 FGPanel::~FGPanel ()
200 {
201   for (instrument_list_type::iterator it = _instruments.begin();
202        it != _instruments.end();
203        it++) {
204     delete *it;
205     *it = 0;
206   }
207 }
208
209
210 /**
211  * Add an instrument to the panel.
212  */
213 void
214 FGPanel::addInstrument (FGPanelInstrument * instrument)
215 {
216   _instruments.push_back(instrument);
217 }
218
219 double
220 FGPanel::getAspectScale() const
221 {
222   // set corner-coordinates correctly
223   
224   int xsize = _xsize_node->getIntValue();
225   int ysize = _ysize_node->getIntValue();
226   float aspect_adjust = get_aspect_adjust(xsize, ysize);
227   
228   if (aspect_adjust < 1.0)
229     return ysize / (double) WIN_H;
230   else
231     return xsize /(double) WIN_W;  
232 }
233
234 /**
235  * Handle repeatable mouse events.  Called from update() and from
236  * fgUpdate3DPanels().  This functionality needs to move into the
237  * input subsystem.  Counting a tick every two frames is clumsy...
238  */
239 void FGPanel::updateMouseDelay(double dt)
240 {
241   if (!_mouseDown) {
242     return;
243   }
244
245   _mouseActionRepeat -= dt;
246   while (_mouseActionRepeat < 0.0) {
247     _mouseActionRepeat += MOUSE_ACTION_REPEAT_INTERVAL;
248     _mouseInstrument->doMouseAction(_mouseButton, 0, _mouseX, _mouseY);
249     
250   }
251 }
252
253 void
254 FGPanel::draw(osg::State& state)
255 {
256     
257   // In 3D mode, it's possible that we are being drawn exactly on top
258   // of an existing polygon.  Use an offset to prevent z-fighting.  In
259   // 2D mode, this is a no-op.
260   static osg::ref_ptr<osg::StateSet> panelStateSet;
261   if (!panelStateSet.valid()) {
262     panelStateSet = new osg::StateSet;
263     panelStateSet->setAttributeAndModes(new osg::PolygonOffset(-1, -POFF_UNITS));
264     panelStateSet->setTextureAttribute(0, new osg::TexEnv);
265
266     // Draw the background
267     panelStateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON);
268     panelStateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
269     panelStateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
270     panelStateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
271   
272     osg::Material* material = new osg::Material;
273     material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
274     material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4(1, 1, 1, 1));
275     material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4(1, 1, 1, 1));
276     material->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4(0, 0, 0, 1));
277     material->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4(0, 0, 0, 1));
278     panelStateSet->setAttribute(material);
279     
280     panelStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::ON);
281     panelStateSet->setAttributeAndModes(new osg::CullFace(osg::CullFace::BACK));
282     panelStateSet->setAttributeAndModes(new osg::Depth(osg::Depth::LEQUAL));
283   }
284   if ( _enable_depth_test )
285     panelStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::ON);
286   else
287     panelStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
288   state.pushStateSet(panelStateSet.get());
289   state.apply();
290   state.setActiveTextureUnit(0);
291   state.setClientActiveTextureUnit(0);
292
293   FGLight *l = (FGLight *)(globals->get_subsystem("lighting"));
294   sgCopyVec4( panel_color, l->scene_diffuse().data());
295   if ( fgGetDouble("/systems/electrical/outputs/instrument-lights") > 1.0 ) {
296       if ( panel_color[0] < 0.7 ) panel_color[0] = 0.7;
297       if ( panel_color[1] < 0.2 ) panel_color[1] = 0.2;
298       if ( panel_color[2] < 0.2 ) panel_color[2] = 0.2;
299   }
300   glColor4fv( panel_color );
301   if (_bg != 0) {
302     state.pushStateSet(_bg.get());
303     state.apply();
304     state.setActiveTextureUnit(0);
305     state.setClientActiveTextureUnit(0);
306
307     glBegin(GL_POLYGON);
308     glTexCoord2f(0.0, 0.0); glVertex2f(WIN_X, WIN_Y);
309     glTexCoord2f(1.0, 0.0); glVertex2f(WIN_X + _width, WIN_Y);
310     glTexCoord2f(1.0, 1.0); glVertex2f(WIN_X + _width, WIN_Y + _height);
311     glTexCoord2f(0.0, 1.0); glVertex2f(WIN_X, WIN_Y + _height);
312     glEnd();
313     state.popStateSet();
314     state.apply();
315     state.setActiveTextureUnit(0);
316     state.setClientActiveTextureUnit(0);
317
318   } else {
319     for (int i = 0; i < 4; i ++) {
320       // top row of textures...(1,3,5,7)
321       state.pushStateSet(_mbg[i*2].get());
322       state.apply();
323       state.setActiveTextureUnit(0);
324       state.setClientActiveTextureUnit(0);
325
326       glBegin(GL_POLYGON);
327       glTexCoord2f(0.0, 0.0); glVertex2f(WIN_X + (_width/4) * i, WIN_Y + (_height/2));
328       glTexCoord2f(1.0, 0.0); glVertex2f(WIN_X + (_width/4) * (i+1), WIN_Y + (_height/2));
329       glTexCoord2f(1.0, 1.0); glVertex2f(WIN_X + (_width/4) * (i+1), WIN_Y + _height);
330       glTexCoord2f(0.0, 1.0); glVertex2f(WIN_X + (_width/4) * i, WIN_Y + _height);
331       glEnd();
332       state.popStateSet();
333       state.apply();
334       state.setActiveTextureUnit(0);
335       state.setClientActiveTextureUnit(0);
336
337       // bottom row of textures...(2,4,6,8)
338       state.pushStateSet(_mbg[i*2+1].get());
339       state.apply();
340       state.setActiveTextureUnit(0);
341       state.setClientActiveTextureUnit(0);
342
343       glBegin(GL_POLYGON);
344       glTexCoord2f(0.0, 0.0); glVertex2f(WIN_X + (_width/4) * i, WIN_Y);
345       glTexCoord2f(1.0, 0.0); glVertex2f(WIN_X + (_width/4) * (i+1), WIN_Y);
346       glTexCoord2f(1.0, 1.0); glVertex2f(WIN_X + (_width/4) * (i+1), WIN_Y + (_height/2));
347       glTexCoord2f(0.0, 1.0); glVertex2f(WIN_X + (_width/4) * i, WIN_Y + (_height/2));
348       glEnd();
349       state.popStateSet();
350       state.apply();
351       state.setActiveTextureUnit(0);
352       state.setClientActiveTextureUnit(0);
353
354     }
355   }
356
357   // Draw the instruments.
358   // Syd Adams: added instrument clipping
359   instrument_list_type::const_iterator current = _instruments.begin();
360   instrument_list_type::const_iterator end = _instruments.end();
361
362   GLdouble blx[4]={1.0,0.0,0.0,0.0};
363   GLdouble bly[4]={0.0,1.0,0.0,0.0};
364   GLdouble urx[4]={-1.0,0.0,0.0,0.0};
365   GLdouble ury[4]={0.0,-1.0,0.0,0.0};
366
367   for ( ; current != end; current++) {
368     FGPanelInstrument * instr = *current;
369     glPushMatrix();
370     glTranslated(instr->getXPos(), instr->getYPos(), 0);
371
372     int ix= instr->getWidth();
373     int iy= instr->getHeight();
374     glPushMatrix();
375     glTranslated(-ix/2,-iy/2,0);
376     glClipPlane(GL_CLIP_PLANE0,blx);
377     glClipPlane(GL_CLIP_PLANE1,bly);
378     glEnable(GL_CLIP_PLANE0);
379     glEnable(GL_CLIP_PLANE1);
380
381     glTranslated(ix,iy,0);
382     glClipPlane(GL_CLIP_PLANE2,urx);
383     glClipPlane(GL_CLIP_PLANE3,ury);
384     glEnable(GL_CLIP_PLANE2);
385     glEnable(GL_CLIP_PLANE3);
386     glPopMatrix();
387     instr->draw(state);
388
389     glPopMatrix();
390   }
391
392   glDisable(GL_CLIP_PLANE0);
393   glDisable(GL_CLIP_PLANE1);
394   glDisable(GL_CLIP_PLANE2);
395   glDisable(GL_CLIP_PLANE3);
396
397   state.popStateSet();
398   state.apply();
399   state.setActiveTextureUnit(0);
400   state.setClientActiveTextureUnit(0);
401
402
403   // Draw yellow "hotspots" if directed to.  This is a panel authoring
404   // feature; not intended to be high performance or to look good.
405   if ( _drawPanelHotspots ) {
406     static osg::ref_ptr<osg::StateSet> hotspotStateSet;
407     if (!hotspotStateSet.valid()) {
408       hotspotStateSet = new osg::StateSet;
409       hotspotStateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::OFF);
410       hotspotStateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
411     }
412
413     state.pushStateSet(hotspotStateSet.get());
414     state.apply();
415     state.setActiveTextureUnit(0);
416     state.setClientActiveTextureUnit(0);
417
418
419     glPushAttrib(GL_ENABLE_BIT);
420     glDisable(GL_COLOR_MATERIAL);
421     glColor3f(1, 1, 0);
422     
423     for ( unsigned int i = 0; i < _instruments.size(); i++ )
424       _instruments[i]->drawHotspots(state);
425
426   // disable drawing of panel extents for the 2.8 release, since it
427   // confused use of hot-spot drawing in tutorials.
428 #ifdef DRAW_PANEL_EXTENTS
429     glColor3f(0, 1, 1);
430
431     int x0, y0, x1, y1;
432     getLogicalExtent(x0, y0, x1, y1);
433     
434     glBegin(GL_LINE_LOOP);
435     glVertex2f(x0, y0);
436     glVertex2f(x1, y0);
437     glVertex2f(x1, y1);
438     glVertex2f(x0, y1);
439     glEnd();
440 #endif
441     
442     glPopAttrib();
443
444     state.popStateSet();
445     state.apply();
446     state.setActiveTextureUnit(0);
447     state.setClientActiveTextureUnit(0);
448
449   }
450 }
451
452 /**
453  * Set the panel's background texture.
454  */
455 void
456 FGPanel::setBackground (osg::Texture2D* texture)
457 {
458   osg::StateSet* stateSet = new osg::StateSet;
459   stateSet->setTextureAttribute(0, texture);
460   stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON);
461   stateSet->setTextureAttribute(0, new osg::TexEnv(osg::TexEnv::MODULATE));
462   _bg = stateSet;
463 }
464
465 /**
466  * Set the panel's multiple background textures.
467  */
468 void
469 FGPanel::setMultiBackground (osg::Texture2D* texture, int idx)
470 {
471   _bg = 0;
472
473   osg::StateSet* stateSet = new osg::StateSet;
474   stateSet->setTextureAttribute(0, texture);
475   stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON);
476   stateSet->setTextureAttribute(0, new osg::TexEnv(osg::TexEnv::MODULATE));
477   _mbg[idx] = stateSet;
478 }
479
480 /**
481  * Set the panel's x-offset.
482  */
483 void
484 FGPanel::setXOffset (int offset)
485 {
486   if (offset <= 0 && offset >= -_width + WIN_W)
487     _x_offset->setIntValue( offset );
488 }
489
490
491 /**
492  * Set the panel's y-offset.
493  */
494 void
495 FGPanel::setYOffset (int offset)
496 {
497   if (offset <= 0 && offset >= -_height)
498     _y_offset->setIntValue( offset );
499 }
500
501 /**
502  * Handle a mouse action in panel-local (not screen) coordinates.
503  * Used by the 3D panel code in Model/panelnode.cxx, in situations
504  * where the panel doesn't control its own screen location.
505  */
506 bool
507 FGPanel::doLocalMouseAction(int button, int updown, int x, int y)
508 {
509   // Note a released button and return
510   if (updown == 1) {
511     if (_mouseInstrument != 0)
512         _mouseInstrument->doMouseAction(_mouseButton, 1, _mouseX, _mouseY);
513     _mouseDown = false;
514     _mouseInstrument = 0;
515     return false;
516   }
517
518   // Search for a matching instrument.
519   for (int i = 0; i < (int)_instruments.size(); i++) {
520     FGPanelInstrument *inst = _instruments[i];
521     int ix = inst->getXPos();
522     int iy = inst->getYPos();
523     int iw = inst->getWidth() / 2;
524     int ih = inst->getHeight() / 2;
525     if (x >= ix - iw && x < ix + iw && y >= iy - ih && y < iy + ih) {
526       _mouseDown = true;
527       _mouseActionRepeat = MOUSE_ACTION_REPEAT_DELAY;
528       _mouseInstrument = inst;
529       _mouseButton = button;
530       _mouseX = x - ix;
531       _mouseY = y - iy;
532       // Always do the action once.
533       return _mouseInstrument->doMouseAction(_mouseButton, 0,
534                                              _mouseX, _mouseY);
535     }
536   }
537   return false;
538 }
539
540 /**
541  * Perform a mouse action.
542  */
543 bool
544 FGPanel::doMouseAction (int button, int updown, int x, int y)
545 {
546                                 // FIXME: this same code appears in update()
547   int xsize = _xsize_node->getIntValue();
548   int ysize = _ysize_node->getIntValue();
549   float aspect_adjust = get_aspect_adjust(xsize, ysize);
550
551                                 // Scale for the real window size.
552   if (aspect_adjust < 1.0) {
553     x = int(((float)x / xsize) * WIN_W * aspect_adjust);
554     y = int(WIN_H - ((float(y) / ysize) * WIN_H));
555   } else {
556     x = int(((float)x / xsize) * WIN_W);
557     y = int((WIN_H - ((float(y) / ysize) * WIN_H)) / aspect_adjust);
558   }
559
560                                 // Adjust for offsets.
561   x -= _x_offset->getIntValue();
562   y -= _y_offset->getIntValue();
563
564   // Having fixed up the coordinates, fall through to the local
565   // coordinate handler.
566   return doLocalMouseAction(button, updown, x, y);
567
568
569 void FGPanel::setDepthTest (bool enable) {
570     _enable_depth_test = enable;
571 }
572
573 class IntRect
574 {
575   
576 public:
577   IntRect() : 
578     x0(std::numeric_limits<int>::max()), 
579     y0(std::numeric_limits<int>::max()), 
580     x1(std::numeric_limits<int>::min()), 
581     y1(std::numeric_limits<int>::min()) 
582   { }
583   
584   IntRect(int x, int y, int w, int h) :
585     x0(x), y0(y), x1(x + w), y1( y + h)
586   { 
587     if (x1 < x0) {
588       std::swap(x0, x1);
589     }
590     
591     if (y1 < y0) {
592       std::swap(y0, y1);
593     }
594     
595     assert(x0 <= x1);
596     assert(y0 <= y1);
597   }
598   
599   void extend(const IntRect& r)
600   {
601     x0 = std::min(x0, r.x0);
602     y0 = std::min(y0, r.y0);
603     x1 = std::max(x1, r.x1);
604     y1 = std::max(y1, r.y1);
605   }
606   
607   int x0, y0, x1, y1;
608 };
609
610 void FGPanel::getLogicalExtent(int &x0, int& y0, int& x1, int &y1)
611 {  
612   IntRect result;
613   BOOST_FOREACH(FGPanelInstrument *inst, _instruments) {
614     inst->extendRect(result);
615   }
616   
617   x0 = result.x0;
618   y0 = result.y0;
619   x1 = result.x1;
620   y1 = result.y1;
621 }
622 \f
623 ////////////////////////////////////////////////////////////////////////.
624 // Implementation of FGPanelAction.
625 ////////////////////////////////////////////////////////////////////////
626
627 FGPanelAction::FGPanelAction ()
628 {
629 }
630
631 FGPanelAction::FGPanelAction (int button, int x, int y, int w, int h,
632                               bool repeatable)
633     : _button(button), _x(x), _y(y), _w(w), _h(h), _repeatable(repeatable)
634 {
635 }
636
637 FGPanelAction::~FGPanelAction ()
638 {
639   for (unsigned int i = 0; i < 2; i++) {
640       for (unsigned int j = 0; j < _bindings[i].size(); j++)
641           delete _bindings[i][j];
642   }
643 }
644
645 void
646 FGPanelAction::addBinding (SGBinding * binding, int updown)
647 {
648   _bindings[updown].push_back(binding);
649 }
650
651 bool
652 FGPanelAction::doAction (int updown)
653 {
654   if (test()) {
655     if ((updown != _last_state) || (updown == 0 && _repeatable)) {
656         int nBindings = _bindings[updown].size();
657         for (int i = 0; i < nBindings; i++)
658             _bindings[updown][i]->fire();
659     }
660     _last_state = updown;
661     return true;
662   } else {
663     return false;
664   }
665 }
666
667
668 \f
669 ////////////////////////////////////////////////////////////////////////
670 // Implementation of FGPanelTransformation.
671 ////////////////////////////////////////////////////////////////////////
672
673 FGPanelTransformation::FGPanelTransformation ()
674   : table(0)
675 {
676 }
677
678 FGPanelTransformation::~FGPanelTransformation ()
679 {
680   delete table;
681 }
682
683
684 \f
685 ////////////////////////////////////////////////////////////////////////
686 // Implementation of FGPanelInstrument.
687 ////////////////////////////////////////////////////////////////////////
688
689
690 FGPanelInstrument::FGPanelInstrument ()
691 {
692   setPosition(0, 0);
693   setSize(0, 0);
694 }
695
696 FGPanelInstrument::FGPanelInstrument (int x, int y, int w, int h)
697 {
698   setPosition(x, y);
699   setSize(w, h);
700 }
701
702 FGPanelInstrument::~FGPanelInstrument ()
703 {
704   for (action_list_type::iterator it = _actions.begin();
705        it != _actions.end();
706        it++) {
707     delete *it;
708     *it = 0;
709   }
710 }
711
712 void
713 FGPanelInstrument::drawHotspots(osg::State& state)
714 {
715   for ( unsigned int i = 0; i < _actions.size(); i++ ) {
716     FGPanelAction* a = _actions[i];
717     float x1 = getXPos() + a->getX();
718     float x2 = x1 + a->getWidth();
719     float y1 = getYPos() + a->getY();
720     float y2 = y1 + a->getHeight();
721
722     glBegin(GL_LINE_LOOP);
723     glVertex2f(x1, y1);
724     glVertex2f(x1, y2);
725     glVertex2f(x2, y2);
726     glVertex2f(x2, y1);
727     glEnd();
728   }
729 }
730
731 void
732 FGPanelInstrument::setPosition (int x, int y)
733 {
734   _x = x;
735   _y = y;
736 }
737
738 void
739 FGPanelInstrument::setSize (int w, int h)
740 {
741   _w = w;
742   _h = h;
743 }
744
745 int
746 FGPanelInstrument::getXPos () const
747 {
748   return _x;
749 }
750
751 int
752 FGPanelInstrument::getYPos () const
753 {
754   return _y;
755 }
756
757 int
758 FGPanelInstrument::getWidth () const
759 {
760   return _w;
761 }
762
763 int
764 FGPanelInstrument::getHeight () const
765 {
766   return _h;
767 }
768
769 void
770 FGPanelInstrument::extendRect(IntRect& r) const
771 {
772   IntRect instRect(_x, _y, _w, _h);
773   r.extend(instRect);
774   
775   BOOST_FOREACH(FGPanelAction* act, _actions) {
776     r.extend(IntRect(getXPos() + act->getX(), 
777                      getYPos() + act->getY(),
778                      act->getWidth(),
779                      act->getHeight()
780                      ));
781   }
782 }
783
784 void
785 FGPanelInstrument::addAction (FGPanelAction * action)
786 {
787   _actions.push_back(action);
788 }
789
790                                 // Coordinates relative to centre.
791 bool
792 FGPanelInstrument::doMouseAction (int button, int updown, int x, int y)
793 {
794   if (test()) {
795     action_list_type::iterator it = _actions.begin();
796     action_list_type::iterator last = _actions.end();
797     for ( ; it != last; it++) {
798       if ((*it)->inArea(button, x, y) &&
799           (*it)->doAction(updown))
800         return true;
801     }
802   }
803   return false;
804 }
805
806
807 \f
808 ////////////////////////////////////////////////////////////////////////
809 // Implementation of FGLayeredInstrument.
810 ////////////////////////////////////////////////////////////////////////
811
812 FGLayeredInstrument::FGLayeredInstrument (int x, int y, int w, int h)
813   : FGPanelInstrument(x, y, w, h)
814 {
815 }
816
817 FGLayeredInstrument::~FGLayeredInstrument ()
818 {
819   for (layer_list::iterator it = _layers.begin(); it != _layers.end(); it++) {
820     delete *it;
821     *it = 0;
822   }
823 }
824
825 void
826 FGLayeredInstrument::draw (osg::State& state)
827 {
828   if (!test())
829     return;
830   
831   for (int i = 0; i < (int)_layers.size(); i++) {
832     glPushMatrix();
833     _layers[i]->draw(state);
834     glPopMatrix();
835   }
836 }
837
838 int
839 FGLayeredInstrument::addLayer (FGInstrumentLayer *layer)
840 {
841   int n = _layers.size();
842   if (layer->getWidth() == -1) {
843     layer->setWidth(getWidth());
844   }
845   if (layer->getHeight() == -1) {
846     layer->setHeight(getHeight());
847   }
848   _layers.push_back(layer);
849   return n;
850 }
851
852 int
853 FGLayeredInstrument::addLayer (const FGCroppedTexture &texture,
854                                int w, int h)
855 {
856   return addLayer(new FGTexturedLayer(texture, w, h));
857 }
858
859 void
860 FGLayeredInstrument::addTransformation (FGPanelTransformation * transformation)
861 {
862   int layer = _layers.size() - 1;
863   _layers[layer]->addTransformation(transformation);
864 }
865
866
867 \f
868 ////////////////////////////////////////////////////////////////////////
869 // Implementation of FGSpecialInstrument.
870 ////////////////////////////////////////////////////////////////////////
871
872 FGSpecialInstrument::FGSpecialInstrument (DCLGPS* sb)
873   : FGPanelInstrument()
874 {
875   complex = sb;
876 }
877
878 FGSpecialInstrument::~FGSpecialInstrument ()
879 {
880 }
881
882 void
883 FGSpecialInstrument::draw (osg::State& state)
884 {
885   complex->draw(state);
886 }
887
888
889 \f
890 ////////////////////////////////////////////////////////////////////////
891 // Implementation of FGInstrumentLayer.
892 ////////////////////////////////////////////////////////////////////////
893
894 FGInstrumentLayer::FGInstrumentLayer (int w, int h)
895   : _w(w),
896     _h(h)
897 {
898 }
899
900 FGInstrumentLayer::~FGInstrumentLayer ()
901 {
902   for (transformation_list::iterator it = _transformations.begin();
903        it != _transformations.end();
904        it++) {
905     delete *it;
906     *it = 0;
907   }
908 }
909
910 void
911 FGInstrumentLayer::transform () const
912 {
913   transformation_list::const_iterator it = _transformations.begin();
914   transformation_list::const_iterator last = _transformations.end();
915   while (it != last) {
916     FGPanelTransformation *t = *it;
917     if (t->test()) {
918       float val = (t->node == 0 ? 0.0 : t->node->getFloatValue());
919
920       if (t->has_mod)
921           val = fmod(val, t->mod);
922       if (val < t->min) {
923         val = t->min;
924       } else if (val > t->max) {
925         val = t->max;
926       }
927
928       if (t->table==0) {
929         val = val * t->factor + t->offset;
930       } else {
931         val = t->table->interpolate(val) * t->factor + t->offset;
932       }
933       
934       switch (t->type) {
935       case FGPanelTransformation::XSHIFT:
936         glTranslatef(val, 0.0, 0.0);
937         break;
938       case FGPanelTransformation::YSHIFT:
939         glTranslatef(0.0, val, 0.0);
940         break;
941       case FGPanelTransformation::ROTATION:
942         glRotatef(-val, 0.0, 0.0, 1.0);
943         break;
944       }
945     }
946     it++;
947   }
948 }
949
950 void
951 FGInstrumentLayer::addTransformation (FGPanelTransformation * transformation)
952 {
953   _transformations.push_back(transformation);
954 }
955
956
957 \f
958 ////////////////////////////////////////////////////////////////////////
959 // Implementation of FGGroupLayer.
960 ////////////////////////////////////////////////////////////////////////
961
962 FGGroupLayer::FGGroupLayer ()
963 {
964 }
965
966 FGGroupLayer::~FGGroupLayer ()
967 {
968   for (unsigned int i = 0; i < _layers.size(); i++)
969     delete _layers[i];
970 }
971
972 void
973 FGGroupLayer::draw (osg::State& state)
974 {
975   if (test()) {
976     transform();
977     int nLayers = _layers.size();
978     for (int i = 0; i < nLayers; i++)
979       _layers[i]->draw(state);
980   }
981 }
982
983 void
984 FGGroupLayer::addLayer (FGInstrumentLayer * layer)
985 {
986   _layers.push_back(layer);
987 }
988
989
990 \f
991 ////////////////////////////////////////////////////////////////////////
992 // Implementation of FGTexturedLayer.
993 ////////////////////////////////////////////////////////////////////////
994
995
996 FGTexturedLayer::FGTexturedLayer (const FGCroppedTexture &texture, int w, int h)
997   : FGInstrumentLayer(w, h),
998     _emissive(false)
999 {
1000   setTexture(texture);
1001 }
1002
1003
1004 FGTexturedLayer::~FGTexturedLayer ()
1005 {
1006 }
1007
1008
1009 void
1010 FGTexturedLayer::draw (osg::State& state)
1011 {
1012   if (test()) {
1013     int w2 = _w / 2;
1014     int h2 = _h / 2;
1015     
1016     transform();
1017     state.pushStateSet(_texture.getTexture());
1018     state.apply();
1019     state.setActiveTextureUnit(0);
1020     state.setClientActiveTextureUnit(0);
1021
1022     glBegin(GL_POLYGON);
1023
1024     if (_emissive) {
1025       glColor4fv( emissive_panel_color );
1026     } else {
1027                                 // From Curt: turn on the panel
1028                                 // lights after sundown.
1029       glColor4fv( panel_color );
1030     }
1031
1032     glTexCoord2f(_texture.getMinX(), _texture.getMinY()); glVertex2f(-w2, -h2);
1033     glTexCoord2f(_texture.getMaxX(), _texture.getMinY()); glVertex2f(w2, -h2);
1034     glTexCoord2f(_texture.getMaxX(), _texture.getMaxY()); glVertex2f(w2, h2);
1035     glTexCoord2f(_texture.getMinX(), _texture.getMaxY()); glVertex2f(-w2, h2);
1036     glEnd();
1037     state.popStateSet();
1038     state.apply();
1039     state.setActiveTextureUnit(0);
1040     state.setClientActiveTextureUnit(0);
1041
1042   }
1043 }
1044
1045
1046 \f
1047 ////////////////////////////////////////////////////////////////////////
1048 // Implementation of FGTextLayer.
1049 ////////////////////////////////////////////////////////////////////////
1050
1051 FGTextLayer::FGTextLayer (int w, int h)
1052   : FGInstrumentLayer(w, h), _pointSize(14.0), _font_name("Helvetica.txf")
1053 {
1054   _then.stamp();
1055   _color[0] = _color[1] = _color[2] = 0.0;
1056   _color[3] = 1.0;
1057 }
1058
1059 FGTextLayer::~FGTextLayer ()
1060 {
1061   chunk_list::iterator it = _chunks.begin();
1062   chunk_list::iterator last = _chunks.end();
1063   for ( ; it != last; it++) {
1064     delete *it;
1065   }
1066 }
1067
1068 void
1069 FGTextLayer::draw (osg::State& state)
1070 {
1071   if (test()) {
1072     glColor4fv(_color);
1073     transform();
1074
1075     FGFontCache *fc = globals->get_fontcache();
1076     fntFont* font = fc->getTexFont(_font_name.c_str());
1077     if (!font) {
1078         return; // don't crash on missing fonts
1079     }
1080     
1081     text_renderer.setFont(font);
1082
1083     text_renderer.setPointSize(_pointSize);
1084     text_renderer.begin();
1085     text_renderer.start3f(0, 0, 0);
1086
1087     _now.stamp();
1088     double diff = (_now - _then).toUSecs();
1089
1090     if (diff > 100000 || diff < 0 ) {
1091       // ( diff < 0 ) is a sanity check and indicates our time stamp
1092       // difference math probably overflowed.  We can handle a max
1093       // difference of 35.8 minutes since the returned value is in
1094       // usec.  So if the panel is left off longer than that we can
1095       // over flow the math with it is turned back on.  This (diff <
1096       // 0) catches that situation, get's us out of trouble, and
1097       // back on track.
1098       recalc_value();
1099       _then = _now;
1100     }
1101
1102     // Something is goofy.  The code in this file renders only CCW
1103     // polygons, and I have verified that the font code in plib
1104     // renders only CCW trianbles.  Yet they come out backwards.
1105     // Something around here or in plib is either changing the winding
1106     // order or (more likely) pushing a left-handed matrix onto the
1107     // stack.  But I can't find it; get out the chainsaw...
1108     glFrontFace(GL_CW);
1109     text_renderer.puts((char *)(_value.c_str()));
1110     glFrontFace(GL_CCW);
1111
1112     text_renderer.end();
1113     glColor4f(1.0, 1.0, 1.0, 1.0);      // FIXME
1114   }
1115 }
1116
1117 void
1118 FGTextLayer::addChunk (FGTextLayer::Chunk * chunk)
1119 {
1120   _chunks.push_back(chunk);
1121 }
1122
1123 void
1124 FGTextLayer::setColor (float r, float g, float b)
1125 {
1126   _color[0] = r;
1127   _color[1] = g;
1128   _color[2] = b;
1129   _color[3] = 1.0;
1130 }
1131
1132 void
1133 FGTextLayer::setPointSize (float size)
1134 {
1135   _pointSize = size;
1136 }
1137
1138 void
1139 FGTextLayer::setFontName(const std::string &name)
1140 {
1141   _font_name = name + ".txf";
1142   FGFontCache *fc = globals->get_fontcache();
1143   fntFont* font = fc->getTexFont(_font_name.c_str());
1144   if (!font) {
1145       SG_LOG(SG_COCKPIT, SG_WARN, "unable to find font:" << name);
1146   }
1147 }
1148
1149
1150 void
1151 FGTextLayer::setFont(fntFont * font)
1152 {
1153   text_renderer.setFont(font);
1154 }
1155
1156
1157 void
1158 FGTextLayer::recalc_value () const
1159 {
1160   _value = "";
1161   chunk_list::const_iterator it = _chunks.begin();
1162   chunk_list::const_iterator last = _chunks.end();
1163   for ( ; it != last; it++) {
1164     _value += (*it)->getValue();
1165   }
1166 }
1167
1168
1169 \f
1170 ////////////////////////////////////////////////////////////////////////
1171 // Implementation of FGTextLayer::Chunk.
1172 ////////////////////////////////////////////////////////////////////////
1173
1174 FGTextLayer::Chunk::Chunk (const std::string &text, const std::string &fmt)
1175     : _type(FGTextLayer::TEXT),
1176     _fmt(simgear::strutils::sanitizePrintfFormat(fmt))
1177 {
1178   _text = text;
1179   if (_fmt.empty()) 
1180     _fmt = "%s";
1181 }
1182
1183 FGTextLayer::Chunk::Chunk (ChunkType type, const SGPropertyNode * node,
1184                            const std::string &fmt, float mult, float offs,
1185                            bool truncation)
1186 : _type(type),
1187     _fmt(simgear::strutils::sanitizePrintfFormat(fmt)),
1188     _mult(mult),
1189     _offs(offs),
1190     _trunc(truncation)
1191 {
1192   if (_fmt.empty()) {
1193     if (type == TEXT_VALUE)
1194       _fmt = "%s";
1195     else
1196       _fmt = "%.2f";
1197   }
1198   _node = node;
1199 }
1200
1201 const char *
1202 FGTextLayer::Chunk::getValue () const
1203 {
1204   if (test()) {
1205     _buf[0] = '\0';
1206     switch (_type) {
1207     case TEXT:
1208       sprintf(_buf, _fmt.c_str(), _text.c_str());
1209       return _buf;
1210     case TEXT_VALUE:
1211       sprintf(_buf, _fmt.c_str(), _node->getStringValue());
1212       break;
1213     case DOUBLE_VALUE:
1214       double d = _offs + _node->getFloatValue() * _mult;
1215       if (_trunc)  d = (d < 0) ? -floor(-d) : floor(d);
1216       sprintf(_buf, _fmt.c_str(), d);
1217       break;
1218     }
1219     return _buf;
1220   } else {
1221     return "";
1222   }
1223 }
1224
1225
1226 \f
1227 ////////////////////////////////////////////////////////////////////////
1228 // Implementation of FGSwitchLayer.
1229 ////////////////////////////////////////////////////////////////////////
1230
1231 FGSwitchLayer::FGSwitchLayer ()
1232   : FGGroupLayer()
1233 {
1234 }
1235
1236 void
1237 FGSwitchLayer::draw (osg::State& state)
1238 {
1239   if (test()) {
1240     transform();
1241     int nLayers = _layers.size();
1242     for (int i = 0; i < nLayers; i++) {
1243       if (_layers[i]->test()) {
1244           _layers[i]->draw(state);
1245           return;
1246       }
1247     }
1248   }
1249 }
1250
1251 \f
1252 // end of panel.cxx