]> git.mxchange.org Git - flightgear.git/blob - src/GUI/dialog.cxx
Andrew Midson:
[flightgear.git] / src / GUI / dialog.cxx
1 // dialog.cxx: implementation of an XML-configurable dialog box.
2
3 #include <Input/input.hxx>
4
5 #include "dialog.hxx"
6 #include "new_gui.hxx"
7
8 #include "puList.hxx"
9 #include "AirportList.hxx"
10 #include "layout.hxx"
11
12 int fgPopup::checkHit(int button, int updown, int x, int y)
13 {
14     int result = puPopup::checkHit(button, updown, x, y);
15
16     // This is annoying.  We would really want a true result from the
17     // superclass to indicate "handled by child object", but all it
18     // tells us is that the pointer is inside the dialog.  So do the
19     // intersection test (again) to make sure we don't start a drag
20     // when inside controls.
21     if(!result) return result;
22     puObject* child = getFirstChild();
23     if(child) child = child->getNextObject(); // Skip the puFrame
24     while(child) {
25         int cx, cy, cw, ch;
26         child->getAbsolutePosition(&cx, &cy);
27         child->getSize(&cw, &ch);
28         if(x >= cx && x < cx + cw && y >= cy && y < cy + ch)
29             return result;
30         child = child->getNextObject();
31     }
32
33     // Finally, handle the mouse event
34     if(updown == PU_DOWN) {
35         int px, py;
36         getPosition(&px, &py);
37         _dragging = true;
38         _dX = px - x;
39         _dY = py - y;
40     } else if(updown == PU_DRAG && _dragging) {
41         setPosition(x + _dX, y + _dY);
42     } else {
43         _dragging = false;
44     }
45     return 1;
46 }
47
48 \f
49 ////////////////////////////////////////////////////////////////////////
50 // Callbacks.
51 ////////////////////////////////////////////////////////////////////////
52
53 /**
54  * User data for a GUI object.
55  */
56 struct GUIInfo
57 {
58     GUIInfo (FGDialog * d);
59     virtual ~GUIInfo ();
60
61     FGDialog * dialog;
62     vector <FGBinding *> bindings;
63 };
64
65
66 /**
67  * Action callback.
68  */
69 static void
70 action_callback (puObject * object)
71 {
72     GUIInfo * info = (GUIInfo *)object->getUserData();
73     NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
74     gui->setActiveDialog(info->dialog);
75     int nBindings = info->bindings.size();
76     for (int i = 0; i < nBindings; i++) {
77         info->bindings[i]->fire();
78         if (gui->getActiveDialog() == 0)
79             break;
80     }
81     gui->setActiveDialog(0);
82 }
83
84
85 \f
86 ////////////////////////////////////////////////////////////////////////
87 // Static helper functions.
88 ////////////////////////////////////////////////////////////////////////
89
90 /**
91  * Copy a property value to a PUI object.
92  */
93 static void
94 copy_to_pui (SGPropertyNode * node, puObject * object)
95 {
96     // Treat puText objects specially, so their "values" can be set
97     // from properties.
98     if(object->getType() & PUCLASS_TEXT) {
99         object->setLabel(node->getStringValue());
100         return;
101     }
102
103     switch (node->getType()) {
104     case SGPropertyNode::BOOL:
105     case SGPropertyNode::INT:
106     case SGPropertyNode::LONG:
107         object->setValue(node->getIntValue());
108         break;
109     case SGPropertyNode::FLOAT:
110     case SGPropertyNode::DOUBLE:
111         object->setValue(node->getFloatValue());
112         break;
113     default:
114         object->setValue(node->getStringValue());
115         break;
116     }
117 }
118
119
120 static void
121 copy_from_pui (puObject * object, SGPropertyNode * node)
122 {
123     // puText objects are immutable, so should not be copied out
124     if(object->getType() & PUCLASS_TEXT)
125         return;
126
127     switch (node->getType()) {
128     case SGPropertyNode::BOOL:
129     case SGPropertyNode::INT:
130     case SGPropertyNode::LONG:
131         node->setIntValue(object->getIntegerValue());
132         break;
133     case SGPropertyNode::FLOAT:
134     case SGPropertyNode::DOUBLE:
135         node->setFloatValue(object->getFloatValue());
136         break;
137     default:
138         // Special case to handle lists, as getStringValue cannot be overridden
139         if(object->getType() & PUCLASS_LIST)
140         {
141             node->setStringValue(((puList *) object)->getListStringValue());
142         }
143         else
144         {
145             node->setStringValue(object->getStringValue());
146         }
147         break;
148     }
149 }
150
151
152 \f
153 ////////////////////////////////////////////////////////////////////////
154 // Implementation of GUIInfo.
155 ////////////////////////////////////////////////////////////////////////
156
157 GUIInfo::GUIInfo (FGDialog * d)
158     : dialog(d)
159 {
160 }
161
162 GUIInfo::~GUIInfo ()
163 {
164     for (unsigned int i = 0; i < bindings.size(); i++) {
165         delete bindings[i];
166         bindings[i] = 0;
167     }
168 }
169
170
171 \f
172 ////////////////////////////////////////////////////////////////////////
173 // Implementation of FGDialog.
174 ////////////////////////////////////////////////////////////////////////
175
176 FGDialog::FGDialog (SGPropertyNode * props)
177     : _object(0)
178 {
179     display(props);
180 }
181
182 FGDialog::~FGDialog ()
183 {
184     puDeleteObject(_object);
185
186     unsigned int i;
187
188                                 // Delete all the arrays we made
189                                 // and were forced to keep around
190                                 // because PUI won't do its own
191                                 // memory management.
192     for (i = 0; i < _char_arrays.size(); i++) {
193         for (int j = 0; _char_arrays[i][j] != 0; j++)
194             free(_char_arrays[i][j]); // added with strdup
195         delete[] _char_arrays[i];
196     }
197
198                                 // Delete all the info objects we
199                                 // were forced to keep around because
200                                 // PUI cannot delete its own user data.
201     for (i = 0; i < _info.size(); i++) {
202         delete (GUIInfo *)_info[i];
203         _info[i] = 0;
204     }
205
206                                 // Finally, delete the property links.
207     for (i = 0; i < _propertyObjects.size(); i++) {
208         delete _propertyObjects[i];
209         _propertyObjects[i] = 0;
210     }
211 }
212
213 void
214 FGDialog::updateValue (const char * objectName)
215 {
216     for (unsigned int i = 0; i < _propertyObjects.size(); i++) {
217         const string &name = _propertyObjects[i]->name;
218         if (name == objectName)
219             copy_to_pui(_propertyObjects[i]->node,
220                         _propertyObjects[i]->object);
221     }
222 }
223
224 void
225 FGDialog::applyValue (const char * objectName)
226 {
227     for (unsigned int i = 0; i < _propertyObjects.size(); i++) {
228         if (_propertyObjects[i]->name == objectName)
229             copy_from_pui(_propertyObjects[i]->object,
230                           _propertyObjects[i]->node);
231     }
232 }
233
234 void
235 FGDialog::updateValues ()
236 {
237     for (unsigned int i = 0; i < _propertyObjects.size(); i++)
238         copy_to_pui(_propertyObjects[i]->node, _propertyObjects[i]->object);
239 }
240
241 void
242 FGDialog::applyValues ()
243 {
244     for (unsigned int i = 0; i < _propertyObjects.size(); i++)
245         copy_from_pui(_propertyObjects[i]->object,
246                       _propertyObjects[i]->node);
247 }
248
249 void
250 FGDialog::update ()
251 {
252     for (unsigned int i = 0; i < _liveObjects.size(); i++)
253         copy_to_pui(_liveObjects[i]->node, _liveObjects[i]->object);
254 }
255
256 void
257 FGDialog::display (SGPropertyNode * props)
258 {
259     if (_object != 0) {
260         SG_LOG(SG_GENERAL, SG_ALERT, "This widget is already active");
261         return;
262     }
263
264     int screenw = globals->get_props()->getIntValue("/sim/startup/xsize");
265     int screenh = globals->get_props()->getIntValue("/sim/startup/ysize");
266
267     bool userx = props->hasValue("x");
268     bool usery = props->hasValue("y");
269     bool userw = props->hasValue("width");
270     bool userh = props->hasValue("height");
271
272     LayoutWidget wid(props);
273     int pw=0, ph=0;
274     if(!userw || !userh)
275         wid.calcPrefSize(&pw, &ph);
276     pw = props->getIntValue("width", pw);
277     ph = props->getIntValue("height", ph);
278     int px = props->getIntValue("x", (screenw - pw) / 2);
279     int py = props->getIntValue("y", (screenh - ph) / 2);
280     wid.layout(px, py, pw, ph);
281
282     _object = makeObject(props, screenw, screenh);
283
284     // Remove automatically generated properties, so the layout looks
285     // the same next time around.
286     if(!userx) props->removeChild("x");
287     if(!usery) props->removeChild("y");
288     if(!userw) props->removeChild("width");
289     if(!userh) props->removeChild("height");
290
291     if (_object != 0) {
292         _object->reveal();
293     } else {
294         SG_LOG(SG_GENERAL, SG_ALERT, "Widget "
295                << props->getStringValue("name", "[unnamed]")
296                << " does not contain a proper GUI definition");
297     }
298 }
299
300 puObject *
301 FGDialog::makeObject (SGPropertyNode * props, int parentWidth, int parentHeight)
302 {
303     bool presetSize = props->hasValue("width") && props->hasValue("height");
304     int width = props->getIntValue("width", parentWidth);
305     int height = props->getIntValue("height", parentHeight);
306     int x = props->getIntValue("x", (parentWidth - width) / 2);
307     int y = props->getIntValue("y", (parentHeight - height) / 2);
308
309     string type = props->getName();
310     if (type == "")
311         type = "dialog";
312
313     if (type == "dialog") {
314         puPopup * dialog;
315         if (props->getBoolValue("modal", false))
316             dialog = new puDialogBox(x, y);
317         else
318             dialog = new fgPopup(x, y);
319         setupGroup(dialog, props, width, height, true);
320         return dialog;
321     } else if (type == "group") {
322         puGroup * group = new puGroup(x, y);
323         setupGroup(group, props, width, height, false);
324         return group;
325     } else if (type == "list") {
326         puList * list = new puList(x, y, x + width, y + height);
327         setupObject(list, props);
328         return list;
329     } else if (type == "airport-list") {
330         AirportList * list = new AirportList(x, y, x + width, y + height);
331         setupObject(list, props);
332         return list;
333     } else if (type == "input") {
334         puInput * input = new puInput(x, y, x + width, y + height);
335         setupObject(input, props);
336         return input;
337     } else if (type == "text") {
338         puText * text = new puText(x, y);
339         setupObject(text, props);
340         // Layed-out objects need their size set, and non-layout ones
341         // get a different placement.
342         if(presetSize) text->setSize(width, height);
343         else text->setLabelPlace(PUPLACE_LABEL_DEFAULT);
344         return text;
345     } else if (type == "checkbox") {
346         puButton * b;
347         b = new puButton(x, y, x + width, y + height, PUBUTTON_XCHECK);
348         b->setColourScheme(.8, .7, .7); // matches "PUI input pink"
349         setupObject(b, props);
350         return b;
351     } else if (type == "radio") {
352         puButton * b;
353         b = new puButton(x, y, x + width, y + height, PUBUTTON_CIRCLE);
354         b->setColourScheme(.8, .7, .7); // matches "PUI input pink"
355         setupObject(b, props);
356         return b;
357     } else if (type == "button") {
358         puButton * b;
359         const char * legend = props->getStringValue("legend", "[none]");
360         if (props->getBoolValue("one-shot", true))
361             b = new puOneShot(x, y, legend);
362         else
363             b = new puButton(x, y, legend);
364         if(presetSize)
365             b->setSize(width, height);
366         setupObject(b, props);
367         return b;
368     } else if (type == "combo") {
369         vector<SGPropertyNode_ptr> value_nodes = props->getChildren("value");
370         char ** entries = make_char_array(value_nodes.size());
371         for (unsigned int i = 0, j = value_nodes.size() - 1;
372              i < value_nodes.size();
373              i++, j--)
374             entries[i] = strdup((char *)value_nodes[i]->getStringValue());
375         puComboBox * combo =
376             new puComboBox(x, y, x + width, y + height, entries,
377                            props->getBoolValue("editable", false));
378         setupObject(combo, props);
379         return combo;
380     } else if (type == "slider") {
381         bool vertical = props->getBoolValue("vertical", false);
382         puSlider * slider = new puSlider(x, y, (vertical ? height : width));
383         slider->setMinValue(props->getFloatValue("min", 0.0));
384         slider->setMaxValue(props->getFloatValue("max", 1.0));
385         setupObject(slider, props);
386         if(presetSize)
387             slider->setSize(width, height);
388         return slider;
389     } else if (type == "dial") {
390         puDial * dial = new puDial(x, y, width);
391         dial->setMinValue(props->getFloatValue("min", 0.0));
392         dial->setMaxValue(props->getFloatValue("max", 1.0));
393         dial->setWrap(props->getBoolValue("wrap", true));
394         setupObject(dial, props);
395         return dial;
396     } else if (type == "textbox") {
397        int slider_width = props->getIntValue("slider", parentHeight);
398        if (slider_width==0) slider_width=20;
399        puLargeInput * puTextBox =
400                      new puLargeInput(x, y, x+width, x+height, 2, slider_width);
401        if  (props->hasValue("editable"))
402        {
403           if (props->getBoolValue("editable")==false)
404              puTextBox->disableInput();
405           else
406              puTextBox->enableInput();
407        }
408        setupObject(puTextBox,props);
409        return puTextBox;
410     } else if (type == "select") {
411         vector<SGPropertyNode_ptr> value_nodes;
412         SGPropertyNode * selection_node =
413                 fgGetNode(props->getChild("selection")->getStringValue(), true);
414
415         for (int q = 0; q < selection_node->nChildren(); q++)
416             value_nodes.push_back(selection_node->getChild(q));
417
418         char ** entries = make_char_array(value_nodes.size());
419         for (unsigned int i = 0, j = value_nodes.size() - 1;
420              i < value_nodes.size();
421              i++, j--)
422             entries[i] = strdup((char *)value_nodes[i]->getName());
423         puSelectBox * select =
424             new puSelectBox(x, y, x + width, y + height, entries);
425         setupObject(select, props);
426         return select;
427     } else {
428         return 0;
429     }
430 }
431
432 void
433 FGDialog::setupObject (puObject * object, SGPropertyNode * props)
434 {
435     object->setLabelPlace(PUPLACE_CENTERED_RIGHT);
436
437     if (props->hasValue("legend"))
438         object->setLegend(props->getStringValue("legend"));
439
440     if (props->hasValue("label"))
441         object->setLabel(props->getStringValue("label"));
442
443     if (props->hasValue("property")) {
444         const char * name = props->getStringValue("name");
445         if (name == 0)
446             name = "";
447         const char * propname = props->getStringValue("property");
448         SGPropertyNode_ptr node = fgGetNode(propname, true);
449         copy_to_pui(node, object);
450         PropertyObject* po = new PropertyObject(name, object, node);
451         _propertyObjects.push_back(po);
452         if(props->getBoolValue("live"))
453             _liveObjects.push_back(po);
454     }
455
456     vector<SGPropertyNode_ptr> nodes = props->getChildren("binding");
457     if (nodes.size() > 0) {
458         GUIInfo * info = new GUIInfo(this);
459
460         for (unsigned int i = 0; i < nodes.size(); i++)
461             info->bindings.push_back(new FGBinding(nodes[i]));
462         object->setCallback(action_callback);
463         object->setUserData(info);
464         _info.push_back(info);
465     }
466
467     object->makeReturnDefault(props->getBoolValue("default"));
468 }
469
470 void
471 FGDialog::setupGroup (puGroup * group, SGPropertyNode * props,
472                     int width, int height, bool makeFrame)
473 {
474     setupObject(group, props);
475
476     if (makeFrame) {
477         puFrame* f = new puFrame(0, 0, width, height);
478         f->setColorScheme(0.8, 0.8, 0.9, 0.85);
479     }
480
481     int nChildren = props->nChildren();
482     for (int i = 0; i < nChildren; i++)
483         makeObject(props->getChild(i), width, height);
484     group->close();
485 }
486
487 char **
488 FGDialog::make_char_array (int size)
489 {
490     char ** list = new char*[size+1];
491     for (int i = 0; i <= size; i++)
492         list[i] = 0;
493     _char_arrays.push_back(list);
494     return list;
495 }
496
497
498 \f
499 ////////////////////////////////////////////////////////////////////////
500 // Implementation of FGDialog::PropertyObject.
501 ////////////////////////////////////////////////////////////////////////
502
503 FGDialog::PropertyObject::PropertyObject (const char * n,
504                                            puObject * o,
505                                            SGPropertyNode_ptr p)
506     : name(n),
507       object(o),
508       node(p)
509 {
510 }
511
512
513 // end of dialog.cxx