1 // panel_io.cxx - I/O for 2D panel.
3 // Written by David Megginson, started January 2000.
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.
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.
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.
19 // $Id: panel_io.cxx,v 1.26 2006/08/10 11:12:39 mfranz Exp $
29 #include <string.h> // for strcmp()
31 #include <simgear/compiler.h>
32 #include <simgear/math/SGMath.hxx>
33 #include <simgear/structure/exception.hxx>
34 #include <simgear/debug/logstream.hxx>
35 #include <simgear/misc/sg_path.hxx>
36 #include <simgear/props/props.hxx>
37 #include <simgear/props/condition.hxx>
38 #include <simgear/props/props_io.hxx>
41 #include "panel_io.hxx"
42 #include "ApplicationProperties.hxx"
45 ////////////////////////////////////////////////////////////////////////
46 // Read and construct a panel.
48 // The panel is specified as a regular property list, and each of the
49 // instruments is its own, separate property list (and thus, a separate
50 // XML document). The functions in this section read in the files
51 // as property lists, then extract properties to set up the panel
54 // A panel contains zero or more instruments.
56 // An instrument contains one or more layers and zero or more actions.
58 // A layer contains zero or more transformations.
60 // Some special types of layers also contain other objects, such as
61 // chunks of text or other layers.
63 // There are currently four types of layers:
65 // 1. Textured Layer (type="texture"), the default
66 // 2. Text Layer (type="text")
67 // 3. Switch Layer (type="switch")
68 // 4. Built-in Layer (type="built-in", must also specify class)
70 // The only built-in layer so far is the ribbon for the magnetic compass
71 // (class="compass-ribbon").
73 // There are three types of actions:
75 // 1. Adjust (type="adjust"), the default
76 // 2. Swap (type="swap")
77 // 3. Toggle (type="toggle")
79 // There are three types of transformations:
81 // 1. X shift (type="x-shift"), the default
82 // 2. Y shift (type="y-shift")
83 // 3. Rotation (type="rotation")
85 // Each of these may be associated with a property, so that a needle
86 // will rotate with the airspeed, for example, or may have a fixed
87 // floating-point value.
88 ////////////////////////////////////////////////////////////////////////
92 * Read a cropped texture from the instrument's property list.
94 * The x1 and y1 properties give the starting position of the texture
95 * (between 0.0 and 1.0), and the the x2 and y2 properties give the
96 * ending position. For example, to use the bottom-left quarter of a
97 * texture, x1=0.0, y1=0.0, x2=0.5, y2=0.5.
99 static FGCroppedTexture_ptr
100 readTexture (const SGPropertyNode * node)
102 return new FGCroppedTexture(node->getStringValue("path"),
103 node->getFloatValue("x1"),
104 node->getFloatValue("y1"),
105 node->getFloatValue("x2", 1.0),
106 node->getFloatValue("y2", 1.0));
107 SG_LOG(SG_COCKPIT, SG_DEBUG, "Read texture " << node->getName());
111 * Test for a condition in the current node.
114 ////////////////////////////////////////////////////////////////////////
115 // Read a condition and use it if necessary.
116 ////////////////////////////////////////////////////////////////////////
119 readConditions (SGConditional *component, const SGPropertyNode *node)
121 const SGPropertyNode * conditionNode = node->getChild("condition");
122 if (conditionNode != 0)
123 // The top level is implicitly AND
124 component->setCondition(sgReadCondition(ApplicationProperties::Properties,
131 * Read a transformation from the instrument's property list.
133 * The panel module uses the transformations to slide or spin needles,
134 * knobs, and other indicators, and to place layers in the correct
135 * positions. Every layer starts centered exactly on the x,y co-ordinate,
136 * and many layers need to be moved or rotated simply to display the
137 * instrument correctly.
139 * There are three types of transformations:
141 * "x-shift" - move the layer horizontally.
143 * "y-shift" - move the layer vertically.
145 * "rotation" - rotate the layer.
147 * Each transformation may have a fixed offset, and may also have
148 * a floating-point property value to add to the offset. The
149 * floating-point property may be clamped to a minimum and/or
150 * maximum range and scaled (after clamping).
152 * Note that because of the way OpenGL works, transformations will
153 * appear to be applied backwards.
155 static FGPanelTransformation *
156 readTransformation (const SGPropertyNode * node, float w_scale, float h_scale)
158 FGPanelTransformation * t = new FGPanelTransformation;
160 string name = node->getName();
161 string type = node->getStringValue("type");
162 string propName = node->getStringValue("property", "");
163 const SGPropertyNode * target = 0;
166 SG_LOG( SG_COCKPIT, SG_INFO,
167 "No type supplied for transformation " << name
168 << " assuming \"rotation\"" );
172 if (!propName.empty())
173 target = ApplicationProperties::Properties->getNode(propName.c_str(), true);
176 t->min = node->getFloatValue("min", -9999999);
177 t->max = node->getFloatValue("max", 99999999);
178 t->has_mod = node->hasChild("modulator");
180 t->mod = node->getFloatValue("modulator");
181 t->factor = node->getFloatValue("scale", 1.0);
182 t->offset = node->getFloatValue("offset", 0.0);
185 // Check for an interpolation table
186 const SGPropertyNode * trans_table = node->getNode("interpolation");
187 if (trans_table != 0) {
188 SG_LOG( SG_COCKPIT, SG_INFO, "Found interpolation table with "
189 << trans_table->nChildren() << " children" );
190 t->table = new SGInterpTable();
191 for (int i = 0; i < trans_table->nChildren(); i++) {
192 const SGPropertyNode * node = trans_table->getChild(i);
193 if (!strcmp(node->getName(), "entry")) {
194 double ind = node->getDoubleValue("ind", 0.0);
195 double dep = node->getDoubleValue("dep", 0.0);
196 SG_LOG( SG_COCKPIT, SG_INFO, "Adding interpolation entry "
197 << ind << "==>" << dep );
198 t->table->addEntry(ind, dep);
200 SG_LOG( SG_COCKPIT, SG_INFO, "Skipping " << node->getName()
201 << " in interpolation" );
208 // Move the layer horizontally.
209 if (type == "x-shift") {
210 t->type = FGPanelTransformation::XSHIFT;
211 // t->min *= w_scale; //removed by Martin Dressler
212 // t->max *= w_scale; //removed by Martin Dressler
213 t->offset *= w_scale;
214 t->factor *= w_scale; //Added by Martin Dressler
217 // Move the layer vertically.
218 else if (type == "y-shift") {
219 t->type = FGPanelTransformation::YSHIFT;
220 //t->min *= h_scale; //removed
221 //t->max *= h_scale; //removed
222 t->offset *= h_scale;
223 t->factor *= h_scale; //Added
226 // Rotate the layer. The rotation
227 // is in degrees, and does not need
228 // to scale with the instrument size.
229 else if (type == "rotation") {
230 t->type = FGPanelTransformation::ROTATION;
234 SG_LOG( SG_COCKPIT, SG_ALERT, "Unrecognized transformation type " << type );
239 readConditions(t, node);
240 SG_LOG( SG_COCKPIT, SG_DEBUG, "Read transformation " << name );
246 * Read a chunk of text from the instrument's property list.
248 * A text layer consists of one or more chunks of text. All chunks
249 * share the same font size and color (and eventually, font), but
250 * each can come from a different source. There are three types of
253 * "literal" - a literal text string (the default)
255 * "text-value" - the current value of a string property
257 * "number-value" - the current value of a floating-point property.
259 * All three may also include a printf-style format string.
262 readTextChunk (const SGPropertyNode * node)
264 FGTextLayer::Chunk * chunk;
265 string name = node->getStringValue("name");
266 string type = node->getStringValue("type");
267 string format = node->getStringValue("format");
269 // Default to literal text.
271 SG_LOG( SG_COCKPIT, SG_INFO, "No type provided for text chunk " << name
272 << " assuming \"literal\"");
276 // A literal text string.
277 if (type == "literal") {
278 string text = node->getStringValue("text");
279 chunk = new FGTextLayer::Chunk(text, format);
282 // The value of a string property.
283 else if (type == "text-value") {
284 SGPropertyNode * target =
285 ApplicationProperties::Properties->getNode( node->getStringValue("property"), true);
286 chunk = new FGTextLayer::Chunk(FGTextLayer::TEXT_VALUE, target, format);
289 // The value of a float property.
290 else if (type == "number-value") {
291 string propName = node->getStringValue("property");
292 float scale = node->getFloatValue("scale", 1.0);
293 float offset = node->getFloatValue("offset", 0.0);
294 bool truncation = node->getBoolValue("truncate", false);
295 SGPropertyNode * target = ApplicationProperties::Properties->getNode(propName.c_str(), true);
296 chunk = new FGTextLayer::Chunk(FGTextLayer::DOUBLE_VALUE, target,
297 format, scale, offset, truncation);
302 SG_LOG( SG_COCKPIT, SG_ALERT, "Unrecognized type " << type
303 << " for text chunk " << name );
307 readConditions(chunk, node);
313 * Read a single layer from an instrument's property list.
315 * Each instrument consists of one or more layers stacked on top
316 * of each other; the lower layers show through only where the upper
317 * layers contain an alpha component. Each layer can be moved
318 * horizontally and vertically and rotated using transformations.
320 * This module currently recognizes four kinds of layers:
322 * "texture" - a layer containing a texture (the default)
324 * "text" - a layer containing text
326 * "switch" - a layer that switches between two other layers
327 * based on the current value of a boolean property.
329 * "built-in" - a hard-coded layer supported by C++ code in FlightGear.
331 * Currently, the only built-in layer class is "compass-ribbon".
333 static FGInstrumentLayer *
334 readLayer (const SGPropertyNode * node, float w_scale, float h_scale)
336 FGInstrumentLayer * layer = NULL;
337 string name = node->getStringValue("name");
338 string type = node->getStringValue("type");
339 int w = node->getIntValue("w", -1);
340 int h = node->getIntValue("h", -1);
341 bool emissive = node->getBoolValue("emissive", false);
343 w = int(w * w_scale);
345 h = int(h * h_scale);
349 SG_LOG( SG_COCKPIT, SG_INFO,
350 "No type supplied for layer " << name
351 << " assuming \"texture\"" );
356 // A textured instrument layer.
357 if (type == "texture") {
358 FGCroppedTexture_ptr texture = readTexture(node->getNode("texture"));
359 layer = new FGTexturedLayer(texture, w, h);
361 FGTexturedLayer *tl=(FGTexturedLayer*)layer;
362 tl->setEmissive(true);
366 // A group of sublayers.
367 else if (type == "group") {
368 layer = new FGGroupLayer();
369 for (int i = 0; i < node->nChildren(); i++) {
370 const SGPropertyNode * child = node->getChild(i);
371 if (!strcmp(child->getName(), "layer"))
372 ((FGGroupLayer *)layer)->addLayer(readLayer(child, w_scale, h_scale));
377 // A textual instrument layer.
378 else if (type == "text") {
379 FGTextLayer * tlayer = new FGTextLayer(w, h); // FIXME
381 // Set the text color.
382 float red = node->getFloatValue("color/red", 0.0);
383 float green = node->getFloatValue("color/green", 0.0);
384 float blue = node->getFloatValue("color/blue", 0.0);
385 tlayer->setColor(red, green, blue);
387 // Set the point size.
388 float pointSize = node->getFloatValue("point-size", 10.0) * w_scale;
389 tlayer->setPointSize(pointSize);
392 string fontName = node->getStringValue("font", "Helvetica");
393 tlayer->setFontName(fontName);
395 const SGPropertyNode * chunk_group = node->getNode("chunks");
396 if (chunk_group != 0) {
397 int nChunks = chunk_group->nChildren();
398 for (int i = 0; i < nChunks; i++) {
399 const SGPropertyNode * node = chunk_group->getChild(i);
400 if (!strcmp(node->getName(), "chunk")) {
401 FGTextLayer::Chunk * chunk = readTextChunk(node);
403 tlayer->addChunk(chunk);
405 SG_LOG( SG_COCKPIT, SG_INFO, "Skipping " << node->getName()
413 // A switch instrument layer.
414 else if (type == "switch") {
415 layer = new FGSwitchLayer();
416 for (int i = 0; i < node->nChildren(); i++) {
417 const SGPropertyNode * child = node->getChild(i);
418 if (!strcmp(child->getName(), "layer"))
419 ((FGGroupLayer *)layer)->addLayer(readLayer(child, w_scale, h_scale));
425 SG_LOG( SG_COCKPIT, SG_ALERT, "Unrecognized layer type " << type );
431 // Get the transformations for each layer.
433 const SGPropertyNode * trans_group = node->getNode("transformations");
434 if (trans_group != 0) {
435 int nTransformations = trans_group->nChildren();
436 for (int i = 0; i < nTransformations; i++) {
437 const SGPropertyNode * node = trans_group->getChild(i);
438 if (!strcmp(node->getName(), "transformation")) {
439 FGPanelTransformation * t = readTransformation(node, w_scale, h_scale);
441 layer->addTransformation(t);
443 SG_LOG( SG_COCKPIT, SG_INFO, "Skipping " << node->getName()
444 << " in transformations" );
449 readConditions(layer, node);
450 SG_LOG( SG_COCKPIT, SG_DEBUG, "Read layer " << name );
456 * Read an instrument from a property list.
458 * The instrument consists of a preferred width and height
459 * (the panel may override these), together with a list of layers
460 * and a list of actions to be performed when the user clicks
461 * the mouse over the instrument. All co-ordinates are relative
462 * to the instrument's position, so instruments are fully relocatable;
463 * likewise, co-ordinates for actions and transformations will be
464 * scaled automatically if the instrument is not at its preferred size.
466 static FGPanelInstrument *
467 readInstrument (const SGPropertyNode * node)
469 const string name = node->getStringValue("name");
470 int x = node->getIntValue("x", -1);
471 int y = node->getIntValue("y", -1);
472 int real_w = node->getIntValue("w", -1);
473 int real_h = node->getIntValue("h", -1);
474 int w = node->getIntValue("w-base", -1);
475 int h = node->getIntValue("h-base", -1);
477 if (x == -1 || y == -1) {
478 SG_LOG( SG_COCKPIT, SG_ALERT,
479 "x and y positions must be specified and > 0" );
486 w_scale = float(real_w) / float(w);
490 h_scale = float(real_h) / float(h);
494 SG_LOG( SG_COCKPIT, SG_DEBUG, "Reading instrument " << name );
496 FGLayeredInstrument * instrument =
497 new FGLayeredInstrument(x, y, w, h);
500 // Get the layers for the instrument.
502 const SGPropertyNode * layer_group = node->getNode("layers");
503 if (layer_group != 0) {
504 int nLayers = layer_group->nChildren();
505 for (int i = 0; i < nLayers; i++) {
506 const SGPropertyNode * node = layer_group->getChild(i);
507 if (!strcmp(node->getName(), "layer")) {
508 FGInstrumentLayer * layer = readLayer(node, w_scale, h_scale);
510 instrument->addLayer(layer);
512 SG_LOG( SG_COCKPIT, SG_INFO, "Skipping " << node->getName()
518 readConditions(instrument, node);
519 SG_LOG( SG_COCKPIT, SG_DEBUG, "Done reading instrument " << name );
525 * Construct the panel from a property tree.
528 FGReadablePanel::read(SGPropertyNode_ptr root)
530 SG_LOG( SG_COCKPIT, SG_INFO, "Reading properties for panel " <<
531 root->getStringValue("name", "[Unnamed Panel]") );
533 FGPanel * panel = new FGPanel(root);
534 panel->setWidth(root->getIntValue("w", 1024));
535 panel->setHeight(root->getIntValue("h", 443));
537 SG_LOG( SG_COCKPIT, SG_INFO, "Size=" << panel->getWidth() << "x" << panel->getHeight() );
539 // Assign the background texture, if any, or a bogus chequerboard.
541 string bgTexture = root->getStringValue("background");
542 if( !bgTexture.empty() )
543 panel->setBackground( new FGCroppedTexture( bgTexture ) );
544 panel->setBackgroundWidth( root->getDoubleValue( "background-width", 1.0 ) );
545 panel->setBackgroundHeight( root->getDoubleValue( "background-height", 1.0 ) );
546 SG_LOG( SG_COCKPIT, SG_INFO, "Set background texture to " << bgTexture );
549 // Get multibackground if any...
551 for( int i = 0; i < 8; i++ ) {
552 SGPropertyNode * mbgNode = root->getChild( "multibackground", i );
554 if( mbgNode != NULL ) mbgTexture = mbgNode->getStringValue();
555 if( mbgTexture.empty() ) {
556 if( i == 0 ) break; // if first texture is missing, ignore the rest
557 else mbgTexture = "FOO"; // if others are missing - set default texture
559 panel->setMultiBackground( new FGCroppedTexture(mbgTexture), i );
560 SG_LOG( SG_COCKPIT, SG_INFO, "Set multi-background texture" << i << " to " << mbgTexture );
563 // Create each instrument.
565 SG_LOG( SG_COCKPIT, SG_INFO, "Reading panel instruments" );
566 const SGPropertyNode * instrument_group = root->getChild("instruments");
567 if (instrument_group != 0) {
568 int nInstruments = instrument_group->nChildren();
569 for (int i = 0; i < nInstruments; i++) {
570 const SGPropertyNode * node = instrument_group->getChild(i);
571 if (!strcmp(node->getName(), "instrument")) {
572 FGPanelInstrument * instrument = readInstrument(node);
574 panel->addInstrument(instrument);
576 SG_LOG( SG_COCKPIT, SG_INFO, "Skipping " << node->getName()
577 << " in instruments section" );
581 SG_LOG( SG_COCKPIT, SG_INFO, "Done reading panel instruments" );
585 // Return the new panel.
590 // end of panel_io.cxx