]> git.mxchange.org Git - flightgear.git/blob - src/Cockpit/panel_io.cxx
0b190175084637b3a3e0c5808cc00a8e0ced4607
[flightgear.git] / src / Cockpit / panel_io.cxx
1 //  panel_io.cxx - I/O for 2D 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., 675 Mass Ave, Cambridge, MA 02139, USA.
18 //
19 //  $Id$
20
21 #ifdef HAVE_CONFIG_H
22 #  include <config.h>
23 #endif
24
25 #ifdef HAVE_WINDOWS_H          
26 #  include <windows.h>
27 #endif
28
29 #include <simgear/compiler.h>
30
31 #include <simgear/misc/sg_path.hxx>
32 #include <simgear/debug/logstream.hxx>
33 #include <simgear/misc/props.hxx>
34
35 #include STL_IOSTREAM
36 #include STL_FSTREAM
37 #include STL_STRING
38
39 #include <Main/globals.hxx>
40 #include <Main/fg_props.hxx>
41
42 #include "panel.hxx"
43 #include "steam.hxx"
44 #include "panel_io.hxx"
45
46 #if !defined (SG_HAVE_NATIVE_SGI_COMPILERS)
47 SG_USING_STD(istream);
48 SG_USING_STD(ifstream);
49 #endif
50 SG_USING_STD(string);
51
52
53 \f
54 ////////////////////////////////////////////////////////////////////////
55 // Default panel, instrument, and layer for when things go wrong...
56 ////////////////////////////////////////////////////////////////////////
57
58 static FGCroppedTexture defaultTexture("Textures/default.rgb");
59
60
61 /**
62  * Default layer: the default texture.
63  */
64 class DefaultLayer : public FGTexturedLayer
65 {
66 public:
67   DefaultLayer () : FGTexturedLayer(defaultTexture)
68   {
69   }
70   
71 };
72
73 /**
74  * Default instrument: a single default layer.
75  */
76 class DefaultInstrument : public FGLayeredInstrument
77 {
78 public:
79   DefaultInstrument (int x, int y, int w, int h)
80     : FGLayeredInstrument(x, y, w, h)
81   {
82     addLayer(new DefaultLayer());
83   }
84 };
85
86
87 /**
88  * Default panel: the default texture.
89  */
90 class DefaultPanel : public FGPanel
91 {
92 public:
93   DefaultPanel (int x, int y, int w, int h) : FGPanel(x, y, w, h)
94   {
95     setBackground(defaultTexture.getTexture());
96   }
97 };
98
99
100 \f
101 ////////////////////////////////////////////////////////////////////////
102 // Built-in layer for the magnetic compass ribbon layer.
103 //
104 // TODO: move this out into a special directory for built-in
105 // layers of various sorts.
106 ////////////////////////////////////////////////////////////////////////
107
108 class FGMagRibbon : public FGTexturedLayer
109 {
110 public:
111   FGMagRibbon (int w, int h);
112   virtual ~FGMagRibbon () {}
113
114   virtual void draw ();
115 };
116
117 FGMagRibbon::FGMagRibbon (int w, int h)
118   : FGTexturedLayer(w, h)
119 {
120   FGCroppedTexture texture("Aircraft/c172/Instruments/Textures/compass-ribbon.rgb");
121   setTexture(texture);
122 }
123
124 void
125 FGMagRibbon::draw ()
126 {
127   double heading = FGSteam::get_MH_deg();
128   double xoffset, yoffset;
129
130   while (heading >= 360.0) {
131     heading -= 360.0;
132   }
133   while (heading < 0.0) {
134     heading += 360.0;
135   }
136
137   if (heading >= 60.0 && heading <= 180.0) {
138     xoffset = heading / 240.0;
139     yoffset = 0.75;
140   } else if (heading >= 150.0 && heading <= 270.0) {
141     xoffset = (heading - 90.0) / 240.0;
142     yoffset = 0.50;
143   } else if (heading >= 240.0 && heading <= 360.0) {
144     xoffset = (heading - 180.0) / 240.0;
145     yoffset = 0.25;
146   } else {
147     if (heading < 270.0)
148       heading += 360.0;
149     xoffset = (heading - 270.0) / 240.0;
150     yoffset = 0.0;
151   }
152
153   xoffset = 1.0 - xoffset;
154                                 // Adjust to put the number in the centre
155   xoffset -= 0.25;
156
157   FGCroppedTexture &t = getTexture();
158   t.setCrop(xoffset, yoffset, xoffset + 0.5, yoffset + 0.25);
159   FGTexturedLayer::draw();
160 }
161
162
163 \f
164 ////////////////////////////////////////////////////////////////////////
165 // Read and construct a panel.
166 //
167 // The panel is specified as a regular property list, and each of the
168 // instruments is its own, separate property list (and thus, a separate
169 // XML document).  The functions in this section read in the files
170 // as property lists, then extract properties to set up the panel
171 // itself.
172 //
173 // A panel contains zero or more instruments.
174 //
175 // An instrument contains one or more layers and zero or more actions.
176 //
177 // A layer contains zero or more transformations.
178 //
179 // Some special types of layers also contain other objects, such as 
180 // chunks of text or other layers.
181 //
182 // There are currently four types of layers:
183 //
184 // 1. Textured Layer (type="texture"), the default
185 // 2. Text Layer (type="text")
186 // 3. Switch Layer (type="switch")
187 // 4. Built-in Layer (type="built-in", must also specify class)
188 //
189 // The only built-in layer so far is the ribbon for the magnetic compass
190 // (class="compass-ribbon").
191 //
192 // There are three types of actions:
193 //
194 // 1. Adjust (type="adjust"), the default
195 // 2. Swap (type="swap")
196 // 3. Toggle (type="toggle")
197 //
198 // There are three types of transformations:
199 //
200 // 1. X shift (type="x-shift"), the default
201 // 2. Y shift (type="y-shift")
202 // 3. Rotation (type="rotation")
203 //
204 // Each of these may be associated with a property, so that a needle
205 // will rotate with the airspeed, for example, or may have a fixed
206 // floating-point value.
207 ////////////////////////////////////////////////////////////////////////
208
209
210 /**
211  * Read a cropped texture from the instrument's property list.
212  *
213  * The x1 and y1 properties give the starting position of the texture
214  * (between 0.0 and 1.0), and the the x2 and y2 properties give the
215  * ending position.  For example, to use the bottom-left quarter of a
216  * texture, x1=0.0, y1=0.0, x2=0.5, y2=0.5.
217  */
218 static FGCroppedTexture
219 readTexture (const SGPropertyNode * node)
220 {
221   FGCroppedTexture texture(node->getStringValue("path"),
222                            node->getFloatValue("x1"),
223                            node->getFloatValue("y1"),
224                            node->getFloatValue("x2", 1.0),
225                            node->getFloatValue("y2", 1.0));
226   SG_LOG(SG_COCKPIT, SG_DEBUG, "Read texture " << node->getName());
227   return texture;
228 }
229
230
231 /**
232  * Read an action from the instrument's property list.
233  *
234  * The action will be performed when the user clicks a mouse button
235  * within the specified region of the instrument.  Actions always work
236  * by modifying the value of a property (see the SGPropertyNode
237  * class).
238  *
239  * The following action types are defined:
240  *
241  * "adjust" - modify the value of a floating-point property by
242  *    the increment specified.  This is the default.
243  *
244  * "swap" - swap the values of two-floating-point properties.
245  *
246  * "toggle" - toggle the value of a boolean property between true and
247  *    false.
248  *
249  * For the adjust action, it is possible to specify an increment
250  * (use a negative number for a decrement), a minimum allowed value,
251  * a maximum allowed value, and a flag to indicate whether the value
252  * should freeze or wrap-around when it reachs the minimum or maximum.
253  *
254  * The action will be scaled automatically if the instrument is not
255  * being drawn at its regular size.
256  */
257 static FGPanelAction *
258 readAction (const SGPropertyNode * node, float w_scale, float h_scale)
259 {
260   FGPanelAction * action = 0;
261
262   string name = node->getStringValue("name");
263   string type = node->getStringValue("type");
264
265   int button = node->getIntValue("button");
266   int x = int(node->getIntValue("x") * w_scale);
267   int y = int(node->getIntValue("y") * h_scale);
268   int w = int(node->getIntValue("w") * w_scale);
269   int h = int(node->getIntValue("h") * h_scale);
270
271   if (type == "") {
272     SG_LOG(SG_COCKPIT, SG_ALERT,
273            "No type supplied for action " << name << " assuming \"adjust\"");
274     type = "adjust";
275   }
276
277                                 // Adjust a property value
278   if (type == "adjust") {
279     string propName = node->getStringValue("property");
280     SGPropertyNode * target = fgGetNode(propName, true);
281     float increment = node->getFloatValue("increment", 1.0);
282     float min = node->getFloatValue("min", 0.0);
283     float max = node->getFloatValue("max", 0.0);
284     bool wrap = node->getBoolValue("wrap", false);
285     if (min == max)
286       SG_LOG(SG_COCKPIT, SG_ALERT, "Action " << node->getName()
287              << " has same min and max value");
288     action = new FGAdjustAction(button, x, y, w, h, target,
289                                 increment, min, max, wrap);
290   } 
291
292                                 // Swap two property values
293   else if (type == "swap") {
294     string propName1 = node->getStringValue("property1");
295     string propName2 = node->getStringValue("property2");
296     SGPropertyNode * target1 = fgGetNode(propName1, true);
297     SGPropertyNode * target2 = fgGetNode(propName2, true);
298     action = new FGSwapAction(button, x, y, w, h, target1, target2);
299   } 
300
301                                 // Toggle a boolean value
302   else if (type == "toggle") {
303     string propName = node->getStringValue("property");
304     SGPropertyNode * target = fgGetNode(propName, true);
305     action = new FGToggleAction(button, x, y, w, h, target);
306   } 
307
308                                 // Unrecognized type
309   else {
310     SG_LOG( SG_COCKPIT, SG_ALERT, "Unrecognized action type " << type );
311     return 0;
312   }
313
314   return action;
315 }
316
317
318 /**
319  * Read a transformation from the instrument's property list.
320  *
321  * The panel module uses the transformations to slide or spin needles,
322  * knobs, and other indicators, and to place layers in the correct
323  * positions.  Every layer starts centered exactly on the x,y co-ordinate,
324  * and many layers need to be moved or rotated simply to display the
325  * instrument correctly.
326  *
327  * There are three types of transformations:
328  *
329  * "x-shift" - move the layer horizontally.
330  *
331  * "y-shift" - move the layer vertically.
332  *
333  * "rotation" - rotate the layer.
334  *
335  * Each transformation may have a fixed offset, and may also have
336  * a floating-point property value to add to the offset.  The
337  * floating-point property may be clamped to a minimum and/or
338  * maximum range and scaled (after clamping).
339  *
340  * Note that because of the way OpenGL works, transformations will
341  * appear to be applied backwards.
342  */
343 static FGPanelTransformation *
344 readTransformation (const SGPropertyNode * node, float w_scale, float h_scale)
345 {
346   FGPanelTransformation * t = new FGPanelTransformation;
347
348   string name = node->getName();
349   string type = node->getStringValue("type");
350   string propName = node->getStringValue("property", "");
351   SGPropertyNode * target = 0;
352
353   if (type == "") {
354     SG_LOG( SG_COCKPIT, SG_ALERT,
355             "No type supplied for transformation " << name
356             << " assuming \"rotation\"" );
357     type = "rotation";
358   }
359
360   if (propName != (string)"") {
361     target = fgGetNode(propName, true);
362   }
363
364   t->node = target;
365   t->min = node->getFloatValue("min", -9999999);
366   t->max = node->getFloatValue("max", 99999999);
367   t->factor = node->getFloatValue("scale", 1.0);
368   t->offset = node->getFloatValue("offset", 0.0);
369
370                                 // Check for an interpolation table
371   const SGPropertyNode * trans_table = node->getNode("interpolation");
372   if (trans_table != 0) {
373     SG_LOG( SG_COCKPIT, SG_INFO, "Found interpolation table with "
374             << trans_table->nChildren() << "children" );
375     t->table = new SGInterpTable();
376     for(int i = 0; i < trans_table->nChildren(); i++) {
377       const SGPropertyNode * node = trans_table->getChild(i);
378       if (node->getName() == "entry") {
379         double ind = node->getDoubleValue("ind", 0.0);
380         double dep = node->getDoubleValue("dep", 0.0);
381         SG_LOG( SG_COCKPIT, SG_INFO, "Adding interpolation entry "
382                 << ind << "==>" << dep );
383         t->table->addEntry(ind, dep);
384       } else {
385         SG_LOG( SG_COCKPIT, SG_INFO, "Skipping " << node->getName()
386                 << " in interpolation" );
387       }
388     }
389   } else {
390     t->table = 0;
391   }
392   
393                                 // Move the layer horizontally.
394   if (type == "x-shift") {
395     t->type = FGPanelTransformation::XSHIFT;
396 //     t->min *= w_scale; //removed by Martin Dressler
397 //     t->max *= w_scale; //removed by Martin Dressler
398     t->offset *= w_scale;
399     t->factor *= w_scale; //Added by Martin Dressler
400   } 
401
402                                 // Move the layer vertically.
403   else if (type == "y-shift") {
404     t->type = FGPanelTransformation::YSHIFT;
405     //t->min *= h_scale; //removed
406     //t->max *= h_scale; //removed
407     t->offset *= h_scale;
408     t->factor *= h_scale; //Added
409   } 
410
411                                 // Rotate the layer.  The rotation
412                                 // is in degrees, and does not need
413                                 // to scale with the instrument size.
414   else if (type == "rotation") {
415     t->type = FGPanelTransformation::ROTATION;
416   } 
417
418   else {
419     SG_LOG( SG_COCKPIT, SG_ALERT, "Unrecognized transformation type " << type );
420     delete t;
421     return 0;
422   }
423
424   SG_LOG( SG_COCKPIT, SG_DEBUG, "Read transformation " << name );
425   return t;
426 }
427
428
429 /**
430  * Read a chunk of text from the instrument's property list.
431  *
432  * A text layer consists of one or more chunks of text.  All chunks
433  * share the same font size and color (and eventually, font), but
434  * each can come from a different source.  There are three types of
435  * text chunks:
436  *
437  * "literal" - a literal text string (the default)
438  *
439  * "text-value" - the current value of a string property
440  *
441  * "number-value" - the current value of a floating-point property.
442  *
443  * All three may also include a printf-style format string.
444  */
445 FGTextLayer::Chunk *
446 readTextChunk (const SGPropertyNode * node)
447 {
448   FGTextLayer::Chunk * chunk;
449   string name = node->getStringValue("name");
450   string type = node->getStringValue("type");
451   string format = node->getStringValue("format");
452
453                                 // Default to literal text.
454   if (type == "") {
455     SG_LOG( SG_COCKPIT, SG_INFO, "No type provided for text chunk " << name
456             << " assuming \"literal\"");
457     type = "literal";
458   }
459
460                                 // A literal text string.
461   if (type == "literal") {
462     string text = node->getStringValue("text");
463     chunk = new FGTextLayer::Chunk(text, format);
464   }
465
466                                 // The value of a string property.
467   else if (type == "text-value") {
468     SGPropertyNode * target =
469       fgGetNode(node->getStringValue("property"), true);
470     chunk = new FGTextLayer::Chunk(FGTextLayer::TEXT_VALUE, target, format);
471   }
472
473                                 // The value of a float property.
474   else if (type == "number-value") {
475     string propName = node->getStringValue("property");
476     float scale = node->getFloatValue("scale", 1.0);
477     SGPropertyNode * target = fgGetNode(propName, true);
478     chunk = new FGTextLayer::Chunk(FGTextLayer::DOUBLE_VALUE, target,
479                                    format, scale);
480   }
481
482                                 // Unknown type.
483   else {
484     SG_LOG( SG_COCKPIT, SG_ALERT, "Unrecognized type " << type
485             << " for text chunk " << name );
486     return 0;
487   }
488
489   return chunk;
490 }
491
492
493 /**
494  * Read a single layer from an instrument's property list.
495  *
496  * Each instrument consists of one or more layers stacked on top
497  * of each other; the lower layers show through only where the upper
498  * layers contain an alpha component.  Each layer can be moved
499  * horizontally and vertically and rotated using transformations.
500  *
501  * This module currently recognizes four kinds of layers:
502  *
503  * "texture" - a layer containing a texture (the default)
504  *
505  * "text" - a layer containing text
506  *
507  * "switch" - a layer that switches between two other layers
508  *   based on the current value of a boolean property.
509  *
510  * "built-in" - a hard-coded layer supported by C++ code in FlightGear.
511  *
512  * Currently, the only built-in layer class is "compass-ribbon".
513  */
514 static FGInstrumentLayer *
515 readLayer (const SGPropertyNode * node, float w_scale, float h_scale)
516 {
517   FGInstrumentLayer * layer = NULL;
518   string name = node->getStringValue("name");
519   string type = node->getStringValue("type");
520   int w = node->getIntValue("w", -1);
521   int h = node->getIntValue("h", -1);
522   if (w != -1)
523     w = int(w * w_scale);
524   if (h != -1)
525     h = int(h * h_scale);
526
527
528   if (type == "") {
529     SG_LOG( SG_COCKPIT, SG_ALERT,
530             "No type supplied for layer " << name
531             << " assuming \"texture\"" );
532     type = "texture";
533   }
534
535
536                                 // A textured instrument layer.
537   if (type == "texture") {
538     FGCroppedTexture texture = readTexture(node->getNode("texture"));
539     layer = new FGTexturedLayer(texture, w, h);
540   }
541
542
543                                 // A textual instrument layer.
544   else if (type == "text") {
545     FGTextLayer * tlayer = new FGTextLayer(w, h); // FIXME
546
547                                 // Set the text color.
548     float red = node->getFloatValue("color/red", 0.0);
549     float green = node->getFloatValue("color/green", 0.0);
550     float blue = node->getFloatValue("color/blue", 0.0);
551     tlayer->setColor(red, green, blue);
552
553                                 // Set the point size.
554     float pointSize = node->getFloatValue("point-size", 10.0) * w_scale;
555     tlayer->setPointSize(pointSize);
556
557                                 // Set the font.
558     // TODO
559
560     const SGPropertyNode * chunk_group = node->getNode("chunks");
561     if (chunk_group != 0) {
562       int nChunks = chunk_group->nChildren();
563       for (int i = 0; i < nChunks; i++) {
564         const SGPropertyNode * node = chunk_group->getChild(i);
565         if (node->getName() == "chunk") {
566           FGTextLayer::Chunk * chunk = readTextChunk(node);
567           if (chunk != 0)
568             tlayer->addChunk(chunk);
569         } else {
570           SG_LOG( SG_COCKPIT, SG_INFO, "Skipping " << node->getName()
571                   << " in chunks" );
572         }
573       }
574       layer = tlayer;
575     }
576   }
577
578                                 // A switch instrument layer.
579   else if (type == "switch") {
580     SGPropertyNode * target =
581       fgGetNode(node->getStringValue("property"), true);
582     FGInstrumentLayer * layer1 =
583       readLayer(node->getNode("layer1"), w_scale, h_scale);
584     FGInstrumentLayer * layer2 =
585       readLayer(node->getNode("layer2"), w_scale, h_scale);
586     layer = new FGSwitchLayer(w, h, target, layer1, layer2);
587   }
588
589                                 // A built-in instrument layer.
590   else if (type == "built-in") {
591     string layerclass = node->getStringValue("class");
592
593     if (layerclass == "mag-ribbon") {
594       layer = new FGMagRibbon(w, h);
595     }
596
597     else if (layerclass == "") {
598       SG_LOG( SG_COCKPIT, SG_ALERT, "No class provided for built-in layer "
599               << name );
600       return 0;
601     }
602
603     else {
604       SG_LOG( SG_COCKPIT, SG_ALERT, "Unknown built-in layer class "
605               << layerclass);
606       return 0;
607     }
608   }
609
610                                 // An unknown type.
611   else {
612     SG_LOG( SG_COCKPIT, SG_ALERT, "Unrecognized layer type " << type );
613     delete layer;
614     return 0;
615   }
616   
617   //
618   // Get the transformations for each layer.
619   //
620   const SGPropertyNode * trans_group = node->getNode("transformations");
621   if (trans_group != 0) {
622     int nTransformations = trans_group->nChildren();
623     for (int i = 0; i < nTransformations; i++) {
624       const SGPropertyNode * node = trans_group->getChild(i);
625       if (node->getName() == "transformation") {
626         FGPanelTransformation * t = readTransformation(node, w_scale, h_scale);
627         if (t != 0)
628           layer->addTransformation(t);
629       } else {
630         SG_LOG( SG_COCKPIT, SG_INFO, "Skipping " << node->getName()
631                 << " in transformations" );
632       }
633     }
634   }
635   
636   SG_LOG( SG_COCKPIT, SG_DEBUG, "Read layer " << name );
637   return layer;
638 }
639
640
641 /**
642  * Read an instrument from a property list.
643  *
644  * The instrument consists of a preferred width and height
645  * (the panel may override these), together with a list of layers
646  * and a list of actions to be performed when the user clicks 
647  * the mouse over the instrument.  All co-ordinates are relative
648  * to the instrument's position, so instruments are fully relocatable;
649  * likewise, co-ordinates for actions and transformations will be
650  * scaled automatically if the instrument is not at its preferred size.
651  */
652 static FGPanelInstrument *
653 readInstrument (const SGPropertyNode * node)
654 {
655   const string &name = node->getStringValue("name");
656   int x = node->getIntValue("x", -1);
657   int y = node->getIntValue("y", -1);
658   int real_w = node->getIntValue("w", -1);
659   int real_h = node->getIntValue("h", -1);
660   int w = node->getIntValue("w-base", -1);
661   int h = node->getIntValue("h-base", -1);
662
663   if (x == -1 || y == -1) {
664     SG_LOG( SG_COCKPIT, SG_ALERT,
665             "x and y positions must be specified and > 0" );
666     return 0;
667   }
668
669   float w_scale = 1.0;
670   float h_scale = 1.0;
671   if (real_w != -1) {
672     w_scale = float(real_w) / float(w);
673     w = real_w;
674   }
675   if (real_h != -1) {
676     h_scale = float(real_h) / float(h);
677     h = real_h;
678   }
679
680   SG_LOG( SG_COCKPIT, SG_DEBUG, "Reading instrument " << name );
681
682   FGLayeredInstrument * instrument =
683     new FGLayeredInstrument(x, y, w, h);
684
685   //
686   // Get the actions for the instrument.
687   //
688   const SGPropertyNode * action_group = node->getNode("actions");
689   if (action_group != 0) {
690     int nActions = action_group->nChildren();
691     for (int i = 0; i < nActions; i++) {
692       const SGPropertyNode * node = action_group->getChild(i);
693       if (node->getName() == "action") {
694         FGPanelAction * action = readAction(node, w_scale, h_scale);
695         if (action != 0)
696           instrument->addAction(action);
697       } else {
698         SG_LOG( SG_COCKPIT, SG_INFO, "Skipping " << node->getName()
699                 << " in actions" );
700       }
701     }
702   }
703
704   //
705   // Get the layers for the instrument.
706   //
707   const SGPropertyNode * layer_group = node->getNode("layers");
708   if (layer_group != 0) {
709     int nLayers = layer_group->nChildren();
710     for (int i = 0; i < nLayers; i++) {
711       const SGPropertyNode * node = layer_group->getChild(i);
712       if (node->getName() == "layer") {
713         FGInstrumentLayer * layer = readLayer(node, w_scale, h_scale);
714         if (layer != 0)
715           instrument->addLayer(layer);
716       } else {
717         SG_LOG( SG_COCKPIT, SG_INFO, "Skipping " << node->getName()
718                 << " in layers" );
719       }
720     }
721   }
722     
723   SG_LOG( SG_COCKPIT, SG_DEBUG, "Done reading instrument " << name );
724   return instrument;
725 }
726
727
728 /**
729  * Construct the panel from a property tree.
730  */
731 FGPanel *
732 readPanel (const SGPropertyNode * root)
733 {
734   SG_LOG( SG_COCKPIT, SG_INFO, "Reading properties for panel " <<
735           root->getStringValue("name", "[Unnamed Panel]") );
736
737   FGPanel * panel = new FGPanel(0, 0, 1024, 768);
738   panel->setWidth(root->getIntValue("w", 1024));
739   panel->setHeight(root->getIntValue("h", 443));
740
741   //
742   // Grab the visible external viewing area, default to 
743   //
744   panel->setViewHeight(root->getIntValue("view-height",
745                                          768 - panel->getHeight() + 2));
746
747   //
748   // Grab the panel's initial offsets, default to 0, 0.
749   //
750   if (!fgHasNode("/sim/panel/x-offset"))
751     fgSetInt("/sim/panel/x-offset", root->getIntValue("x-offset", 0));
752
753   if (!fgHasNode("/sim/panel/y-offset"))
754     fgSetInt("/sim/panel/y-offset", root->getIntValue("y-offset", 0));
755
756   //
757   // Assign the background texture, if any, or a bogus chequerboard.
758   //
759   string bgTexture = root->getStringValue("background");
760   if (bgTexture == "")
761     bgTexture = "FOO";
762   panel->setBackground(FGTextureManager::createTexture(bgTexture.c_str()));
763   SG_LOG( SG_COCKPIT, SG_INFO, "Set background texture to " << bgTexture );
764
765
766   //
767   // Create each instrument.
768   //
769   SG_LOG( SG_COCKPIT, SG_INFO, "Reading panel instruments" );
770   const SGPropertyNode * instrument_group = root->getChild("instruments");
771   if (instrument_group != 0) {
772     int nInstruments = instrument_group->nChildren();
773     for (int i = 0; i < nInstruments; i++) {
774       const SGPropertyNode * node = instrument_group->getChild(i);
775       if (node->getName() == "instrument") {
776         FGPanelInstrument * instrument = readInstrument(node);
777         if (instrument != 0)
778           panel->addInstrument(instrument);
779       } else {
780         SG_LOG( SG_COCKPIT, SG_INFO, "Skipping " << node->getName()
781                 << " in instruments section" );
782       }
783     }
784   }
785   SG_LOG( SG_COCKPIT, SG_INFO, "Done reading panel instruments" );
786
787
788   //
789   // Return the new panel.
790   //
791   return panel;
792 }
793
794
795 /**
796  * Read a panel from a property list.
797  *
798  * Each panel instrument will appear in its own, separate
799  * property list.  The top level simply names the panel and
800  * places the instruments in their appropriate locations (and
801  * optionally resizes them if necessary).
802  *
803  * Returns 0 if the read fails for any reason.
804  */
805 FGPanel *
806 fgReadPanel (istream &input)
807 {
808   SGPropertyNode root;
809
810   if (!readProperties(input, &root)) {
811     SG_LOG( SG_COCKPIT, SG_ALERT, "Malformed property list for panel." );
812     return 0;
813   }
814   return readPanel(&root);
815 }
816
817
818 /**
819  * Read a panel from a property list.
820  *
821  * This function opens a stream to a file, then invokes the
822  * main fgReadPanel() function.
823  */
824 FGPanel *
825 fgReadPanel (const string &relative_path)
826 {
827   SGPath path(globals->get_fg_root());
828   path.append(relative_path);
829   SGPropertyNode root;
830
831   if (!readProperties(path.str(), &root)) {
832     SG_LOG( SG_COCKPIT, SG_ALERT, "Malformed property list for panel." );
833     return 0;
834   }
835   return readPanel(&root);
836 }
837
838
839
840 // end of panel_io.cxx