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