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