]> git.mxchange.org Git - flightgear.git/blob - src/GUI/dialog.cxx
allow to trigger widgets via accelerator key, which is defined via "keynum"
[flightgear.git] / src / GUI / dialog.cxx
1 // dialog.cxx: implementation of an XML-configurable dialog box.
2
3 #include <stdlib.h>             // atof()
4
5 #include <Input/input.hxx>
6
7 #include "dialog.hxx"
8 #include "new_gui.hxx"
9
10 #include "puList.hxx"
11 #include "AirportList.hxx"
12 #include "layout.hxx"
13
14 /**
15  * User data for a GUI object.
16  */
17 struct GUIInfo
18 {
19     GUIInfo (FGDialog * d);
20     virtual ~GUIInfo ();
21
22     FGDialog * dialog;
23     vector <FGBinding *> bindings;
24     int key;
25 };
26
27
28 /**
29  * Key handler.
30  */
31 int fgPopup::checkKey(int key, int updown)
32 {
33     if (updown == PU_UP || !isVisible() || !isActive() || window != puGetWindow())
34         return false;
35
36     puObject *input = getActiveInputField(this);
37     if (input)
38         return input->checkKey(key, updown);
39
40     puObject *object = getKeyObject(this, key);
41     if (!object)
42         return puPopup::checkKey(key, updown);
43
44     object->invokeCallback() ;
45     return true;
46 }
47
48 puObject *fgPopup::getKeyObject(puObject *object, int key)
49 {
50     puObject *ret;
51     if(object->getType() & PUCLASS_GROUP)
52         for (puObject *obj = ((puGroup *)object)->getFirstChild();
53                 obj; obj = obj->getNextObject())
54             if ((ret = getKeyObject(obj, key)))
55                 return ret;
56
57     GUIInfo *info = (GUIInfo *)object->getUserData();
58     if (info && info->key == key)
59        return object;
60
61     return 0;
62 }
63
64 puObject *fgPopup::getActiveInputField(puObject *object)
65 {
66     if(object->getType() & PUCLASS_GROUP)
67         for (puObject *obj = ((puGroup *)object)->getFirstChild();
68                 obj; obj = obj->getNextObject())
69             if (getActiveInputField(obj))
70                 return obj;
71
72     if (object->getType() & PUCLASS_INPUT && ((puInput *)object)->isAcceptingInput())
73         return object;
74
75     return 0;
76 }
77
78 /**
79  * Mouse handler.
80  */
81 int fgPopup::checkHit(int button, int updown, int x, int y)
82 {
83     int result = puPopup::checkHit(button, updown, x, y);
84
85     if ( !_draggable)
86        return result;
87
88     // This is annoying.  We would really want a true result from the
89     // superclass to indicate "handled by child object", but all it
90     // tells us is that the pointer is inside the dialog.  So do the
91     // intersection test (again) to make sure we don't start a drag
92     // when inside controls.
93
94     if(updown == PU_DOWN && !_dragging) {
95         if(!result)
96             return 0;
97
98         int hit = getHitObjects(this, x, y);
99         if(hit & (PUCLASS_BUTTON|PUCLASS_ONESHOT|PUCLASS_INPUT))
100             return result;
101
102         int px, py;
103         getPosition(&px, &py);
104         _dragging = true;
105         _dX = px - x;
106         _dY = py - y;
107     } else if(updown == PU_DRAG && _dragging) {
108         setPosition(x + _dX, y + _dY);
109     } else {
110         _dragging = false;
111     }
112     return result;
113 }
114
115 int fgPopup::getHitObjects(puObject *object, int x, int y)
116 {
117     int type = 0;
118     if(object->getType() & PUCLASS_GROUP)
119         for (puObject *obj = ((puGroup *)object)->getFirstChild();
120                 obj; obj = obj->getNextObject())
121             type |= getHitObjects(obj, x, y);
122
123     int cx, cy, cw, ch;
124     object->getAbsolutePosition(&cx, &cy);
125     object->getSize(&cw, &ch);
126     if(x >= cx && x < cx + cw && y >= cy && y < cy + ch)
127         type |= object->getType();
128     return type;
129 }
130
131
132 \f
133 ////////////////////////////////////////////////////////////////////////
134 // Callbacks.
135 ////////////////////////////////////////////////////////////////////////
136
137 /**
138  * Action callback.
139  */
140 static void
141 action_callback (puObject * object)
142 {
143     GUIInfo * info = (GUIInfo *)object->getUserData();
144     NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
145     gui->setActiveDialog(info->dialog);
146     int nBindings = info->bindings.size();
147     for (int i = 0; i < nBindings; i++) {
148         info->bindings[i]->fire();
149         if (gui->getActiveDialog() == 0)
150             break;
151     }
152     gui->setActiveDialog(0);
153 }
154
155
156 static void
157 format_callback(puObject *obj, int dx, int dy, void *n)
158 {
159     SGPropertyNode *node = (SGPropertyNode *)n;
160     const char *format = node->getStringValue("format"), *f = format;
161     bool number, l = false;
162     // make sure the format matches '[ -+#]?\d*(\.\d*)?l?[fs]'
163     for (; *f; f++) {
164         if (*f == '%') {
165             if (f[1] == '%')
166                 f++;
167             else
168                 break;
169         }
170     }
171     if (*f++ != '%')
172         return;
173     if (*f == ' ' || *f == '+' || *f == '-' || *f == '#')
174         f++;
175     while (*f && isdigit(*f))
176         f++;
177     if (*f == '.') {
178         f++;
179         while (*f && isdigit(*f))
180             f++;
181     }
182     if (*f == 'l')
183         l = true, f++;
184
185     if (*f == 'f')
186         number = true;
187     else if (*f == 's') {
188         if (l)
189             return;
190         number = false;
191     } else
192         return;
193
194     for (++f; *f; f++) {
195         if (*f == '%') {
196             if (f[1] == '%')
197                 f++;
198             else
199                 return;
200         }
201     }
202
203     char buf[256];
204     const char *src = obj->getLabel();
205
206     if (number) {
207         float value = atof(src);
208         snprintf(buf, 256, format, value);
209     } else {
210         snprintf(buf, 256, format, src);
211     }
212
213     buf[255] = '\0';
214
215     SGPropertyNode *result = node->getNode("formatted", true);
216     result->setStringValue(buf);
217     obj->setLabel(result->getStringValue());
218 }
219
220
221 \f
222 ////////////////////////////////////////////////////////////////////////
223 // Static helper functions.
224 ////////////////////////////////////////////////////////////////////////
225
226 /**
227  * Copy a property value to a PUI object.
228  */
229 static void
230 copy_to_pui (SGPropertyNode * node, puObject * object)
231 {
232     // Treat puText objects specially, so their "values" can be set
233     // from properties.
234     if(object->getType() & PUCLASS_TEXT) {
235         object->setLabel(node->getStringValue());
236         return;
237     }
238
239     switch (node->getType()) {
240     case SGPropertyNode::BOOL:
241     case SGPropertyNode::INT:
242     case SGPropertyNode::LONG:
243         object->setValue(node->getIntValue());
244         break;
245     case SGPropertyNode::FLOAT:
246     case SGPropertyNode::DOUBLE:
247         object->setValue(node->getFloatValue());
248         break;
249     default:
250         object->setValue(node->getStringValue());
251         break;
252     }
253 }
254
255
256 static void
257 copy_from_pui (puObject * object, SGPropertyNode * node)
258 {
259     // puText objects are immutable, so should not be copied out
260     if(object->getType() & PUCLASS_TEXT)
261         return;
262
263     switch (node->getType()) {
264     case SGPropertyNode::BOOL:
265     case SGPropertyNode::INT:
266     case SGPropertyNode::LONG:
267         node->setIntValue(object->getIntegerValue());
268         break;
269     case SGPropertyNode::FLOAT:
270     case SGPropertyNode::DOUBLE:
271         node->setFloatValue(object->getFloatValue());
272         break;
273     default:
274         // Special case to handle lists, as getStringValue cannot be overridden
275         if(object->getType() & PUCLASS_LIST)
276         {
277             node->setStringValue(((puList *) object)->getListStringValue());
278         }
279         else
280         {
281             node->setStringValue(object->getStringValue());
282         }
283         break;
284     }
285 }
286
287
288 \f
289 ////////////////////////////////////////////////////////////////////////
290 // Implementation of GUIInfo.
291 ////////////////////////////////////////////////////////////////////////
292
293 GUIInfo::GUIInfo (FGDialog * d)
294     : dialog(d),
295       key(-1)
296 {
297 }
298
299 GUIInfo::~GUIInfo ()
300 {
301     for (unsigned int i = 0; i < bindings.size(); i++) {
302         delete bindings[i];
303         bindings[i] = 0;
304     }
305 }
306
307
308 \f
309 ////////////////////////////////////////////////////////////////////////
310 // Implementation of FGDialog.
311 ////////////////////////////////////////////////////////////////////////
312
313 FGDialog::FGDialog (SGPropertyNode * props)
314     : _object(0),
315       _gui((NewGUI *)globals->get_subsystem("gui"))
316 {
317     char* envp = ::getenv( "FG_FONTS" );
318     if ( envp != NULL ) {
319         _font_path.set( envp );
320     } else {
321         _font_path.set( globals->get_fg_root() );
322         _font_path.append( "Fonts" );
323     }
324
325     display(props);
326 }
327
328 FGDialog::~FGDialog ()
329 {
330     puDeleteObject(_object);
331
332     unsigned int i;
333
334                                 // Delete all the arrays we made
335                                 // and were forced to keep around
336                                 // because PUI won't do its own
337                                 // memory management.
338     for (i = 0; i < _char_arrays.size(); i++) {
339         for (int j = 0; _char_arrays[i][j] != 0; j++)
340             free(_char_arrays[i][j]); // added with strdup
341         delete[] _char_arrays[i];
342     }
343
344                                 // Delete all the info objects we
345                                 // were forced to keep around because
346                                 // PUI cannot delete its own user data.
347     for (i = 0; i < _info.size(); i++) {
348         delete (GUIInfo *)_info[i];
349         _info[i] = 0;
350     }
351
352                                 // Finally, delete the property links.
353     for (i = 0; i < _propertyObjects.size(); i++) {
354         delete _propertyObjects[i];
355         _propertyObjects[i] = 0;
356     }
357 }
358
359 void
360 FGDialog::updateValue (const char * objectName)
361 {
362     for (unsigned int i = 0; i < _propertyObjects.size(); i++) {
363         const string &name = _propertyObjects[i]->name;
364         if (name == objectName)
365             copy_to_pui(_propertyObjects[i]->node,
366                         _propertyObjects[i]->object);
367     }
368 }
369
370 void
371 FGDialog::applyValue (const char * objectName)
372 {
373     for (unsigned int i = 0; i < _propertyObjects.size(); i++) {
374         if (_propertyObjects[i]->name == objectName)
375             copy_from_pui(_propertyObjects[i]->object,
376                           _propertyObjects[i]->node);
377     }
378 }
379
380 void
381 FGDialog::updateValues ()
382 {
383     for (unsigned int i = 0; i < _propertyObjects.size(); i++)
384         copy_to_pui(_propertyObjects[i]->node, _propertyObjects[i]->object);
385 }
386
387 void
388 FGDialog::applyValues ()
389 {
390     for (unsigned int i = 0; i < _propertyObjects.size(); i++)
391         copy_from_pui(_propertyObjects[i]->object,
392                       _propertyObjects[i]->node);
393 }
394
395 void
396 FGDialog::update ()
397 {
398     for (unsigned int i = 0; i < _liveObjects.size(); i++) {
399         puObject *obj = _liveObjects[i]->object;
400         if (obj->getType() & PUCLASS_INPUT && ((puInput *)obj)->isAcceptingInput())
401             continue;
402
403         copy_to_pui(_liveObjects[i]->node, obj);
404     }
405 }
406
407 void
408 FGDialog::display (SGPropertyNode * props)
409 {
410     if (_object != 0) {
411         SG_LOG(SG_GENERAL, SG_ALERT, "This widget is already active");
412         return;
413     }
414
415     int screenw = globals->get_props()->getIntValue("/sim/startup/xsize");
416     int screenh = globals->get_props()->getIntValue("/sim/startup/ysize");
417
418     bool userx = props->hasValue("x");
419     bool usery = props->hasValue("y");
420     bool userw = props->hasValue("width");
421     bool userh = props->hasValue("height");
422
423     // Let the layout widget work in the same property subtree.
424     LayoutWidget wid(props);
425
426     puFont *fnt = _gui->getDefaultFont();
427     wid.setDefaultFont(fnt, int(fnt->getPointSize()));
428
429     int pw=0, ph=0;
430     if(!userw || !userh)
431         wid.calcPrefSize(&pw, &ph);
432     pw = props->getIntValue("width", pw);
433     ph = props->getIntValue("height", ph);
434     int px = props->getIntValue("x", (screenw - pw) / 2);
435     int py = props->getIntValue("y", (screenh - ph) / 2);
436
437     // Define "x", "y", "width" and/or "height" in the property tree if they
438     // are not specified in the configuration file.
439     wid.layout(px, py, pw, ph);
440
441     // Use the dimension and location properties as specified in the
442     // configuration file or from the layout widget.
443     _object = makeObject(props, screenw, screenh);
444
445     // Remove automatically generated properties, so the layout looks
446     // the same next time around.
447     if(!userx) props->removeChild("x");
448     if(!usery) props->removeChild("y");
449     if(!userw) props->removeChild("width");
450     if(!userh) props->removeChild("height");
451
452     if (_object != 0) {
453         _object->reveal();
454     } else {
455         SG_LOG(SG_GENERAL, SG_ALERT, "Widget "
456                << props->getStringValue("name", "[unnamed]")
457                << " does not contain a proper GUI definition");
458     }
459 }
460
461 puObject *
462 FGDialog::makeObject (SGPropertyNode * props, int parentWidth, int parentHeight)
463 {
464     if (props->getBoolValue("hide"))
465         return 0;
466
467     bool presetSize = props->hasValue("width") && props->hasValue("height");
468     int width = props->getIntValue("width", parentWidth);
469     int height = props->getIntValue("height", parentHeight);
470     int x = props->getIntValue("x", (parentWidth - width) / 2);
471     int y = props->getIntValue("y", (parentHeight - height) / 2);
472     string type = props->getName();
473
474     if (type == "")
475         type = "dialog";
476
477     if (type == "dialog") {
478         puPopup * obj;
479         bool draggable = props->getBoolValue("draggable", true);
480         if (props->getBoolValue("modal", false))
481             obj = new puDialogBox(x, y);
482         else
483             obj = new fgPopup(x, y, draggable);
484         setupGroup(obj, props, width, height, true);
485         setColor(obj, props);
486         return obj;
487
488     } else if (type == "group") {
489         puGroup * obj = new puGroup(x, y);
490         setupGroup(obj, props, width, height, false);
491         setColor(obj, props);
492         return obj;
493
494     } else if (type == "frame") {
495         puGroup * obj = new puGroup(x, y);
496         setupGroup(obj, props, width, height, true);
497         setColor(obj, props);
498         return obj;
499
500     } else if (type == "hrule" || type == "vrule") {
501         puFrame * obj = new puFrame(x, y, x + width, y + height);
502         obj->setBorderThickness(0);
503         setColor(obj, props, BACKGROUND|FOREGROUND|HIGHLIGHT);
504         return obj;
505
506     } else if (type == "list") {
507         puList * obj = new puList(x, y, x + width, y + height);
508         setupObject(obj, props);
509         setColor(obj, props);
510         return obj;
511
512     } else if (type == "airport-list") {
513         AirportList * obj = new AirportList(x, y, x + width, y + height);
514         setupObject(obj, props);
515         setColor(obj, props);
516         return obj;
517
518     } else if (type == "input") {
519         puInput * obj = new puInput(x, y, x + width, y + height);
520         setupObject(obj, props);
521         setColor(obj, props, FOREGROUND|LABEL);
522         return obj;
523
524     } else if (type == "text") {
525         puText * obj = new puText(x, y);
526         setupObject(obj, props);
527
528         if (props->getNode("format")) {
529             SGPropertyNode *live = props->getNode("live");
530             if (live && live->getBoolValue())
531                 obj->setRenderCallback(format_callback, props);
532             else
533                 format_callback(obj, x, y, props);
534         }
535         // Layed-out objects need their size set, and non-layout ones
536         // get a different placement.
537         if(presetSize) obj->setSize(width, height);
538         else obj->setLabelPlace(PUPLACE_LABEL_DEFAULT);
539         setColor(obj, props, LABEL);
540         return obj;
541
542     } else if (type == "checkbox") {
543         puButton * obj;
544         obj = new puButton(x, y, x + width, y + height, PUBUTTON_XCHECK);
545         setupObject(obj, props);
546         setColor(obj, props, FOREGROUND|LABEL);
547         return obj;
548
549     } else if (type == "radio") {
550         puButton * obj;
551         obj = new puButton(x, y, x + width, y + height, PUBUTTON_CIRCLE);
552         setupObject(obj, props);
553         setColor(obj, props, FOREGROUND|LABEL);
554         return obj;
555
556     } else if (type == "button") {
557         puButton * obj;
558         const char * legend = props->getStringValue("legend", "[none]");
559         if (props->getBoolValue("one-shot", true))
560             obj = new puOneShot(x, y, legend);
561         else
562             obj = new puButton(x, y, legend);
563         if(presetSize)
564             obj->setSize(width, height);
565         setupObject(obj, props);
566         setColor(obj, props);
567         return obj;
568
569     } else if (type == "combo") {
570         vector<SGPropertyNode_ptr> value_nodes = props->getChildren("value");
571         char ** entries = make_char_array(value_nodes.size());
572         for (unsigned int i = 0, j = value_nodes.size() - 1;
573              i < value_nodes.size();
574              i++, j--)
575             entries[i] = strdup((char *)value_nodes[i]->getStringValue());
576         puComboBox * obj = new puComboBox(x, y, x + width, y + height, entries,
577                            props->getBoolValue("editable", false));
578         setupObject(obj, props);
579         setColor(obj, props, FOREGROUND|LABEL);
580         return obj;
581
582     } else if (type == "slider") {
583         bool vertical = props->getBoolValue("vertical", false);
584         puSlider * obj = new puSlider(x, y, (vertical ? height : width));
585         obj->setMinValue(props->getFloatValue("min", 0.0));
586         obj->setMaxValue(props->getFloatValue("max", 1.0));
587         setupObject(obj, props);
588         if(presetSize)
589             obj->setSize(width, height);
590         setColor(obj, props, FOREGROUND|LABEL);
591         return obj;
592
593     } else if (type == "dial") {
594         puDial * obj = new puDial(x, y, width);
595         obj->setMinValue(props->getFloatValue("min", 0.0));
596         obj->setMaxValue(props->getFloatValue("max", 1.0));
597         obj->setWrap(props->getBoolValue("wrap", true));
598         setupObject(obj, props);
599         setColor(obj, props, FOREGROUND|LABEL);
600         return obj;
601
602     } else if (type == "textbox") {
603         int slider_width = props->getIntValue("slider", parentHeight);
604         int wrap = props->getBoolValue("wrap", true);
605         if (slider_width==0) slider_width=20;
606         puLargeInput * obj = new puLargeInput(x, y,
607                 x+width, x+height, 2, slider_width, wrap);
608
609         if (props->hasValue("editable")) {
610             if (props->getBoolValue("editable")==false)
611                 obj->disableInput();
612             else
613                 obj->enableInput();
614         }
615         setupObject(obj, props);
616         setColor(obj, props, FOREGROUND|LABEL);
617         return obj;
618
619     } else if (type == "select") {
620         vector<SGPropertyNode_ptr> value_nodes;
621         SGPropertyNode * selection_node =
622                 fgGetNode(props->getChild("selection")->getStringValue(), true);
623
624         for (int q = 0; q < selection_node->nChildren(); q++)
625             value_nodes.push_back(selection_node->getChild(q));
626
627         char ** entries = make_char_array(value_nodes.size());
628         for (unsigned int i = 0, j = value_nodes.size() - 1;
629              i < value_nodes.size();
630              i++, j--)
631             entries[i] = strdup((char *)value_nodes[i]->getName());
632         puSelectBox * obj =
633             new puSelectBox(x, y, x + width, y + height, entries);
634         setupObject(obj, props);
635         setColor(obj, props, FOREGROUND|LABEL);
636         return obj;
637     } else {
638         return 0;
639     }
640 }
641
642 void
643 FGDialog::setupObject (puObject * object, SGPropertyNode * props)
644 {
645     object->setLabelPlace(PUPLACE_CENTERED_RIGHT);
646
647     if (props->hasValue("legend"))
648         object->setLegend(props->getStringValue("legend"));
649
650     if (props->hasValue("label"))
651         object->setLabel(props->getStringValue("label"));
652
653     if (props->hasValue("border"))
654         object->setBorderThickness( props->getIntValue("border", 2) );
655
656     if ( SGPropertyNode *nft = props->getNode("font", false) ) {
657        SGPath path( _font_path );
658        const char *name = nft->getStringValue("name", "default");
659        float size = nft->getFloatValue("size", 13.0);
660        float slant = nft->getFloatValue("slant", 0.0);
661        path.append( name );
662        path.concat( ".txf" );
663
664        fntFont *font = new fntTexFont;
665        font->load( (char *)path.c_str() );
666
667        puFont lfnt(font, size, slant);
668        object->setLabelFont( lfnt );
669     }
670
671     if (props->hasValue("property")) {
672         const char * name = props->getStringValue("name");
673         if (name == 0)
674             name = "";
675         const char * propname = props->getStringValue("property");
676         SGPropertyNode_ptr node = fgGetNode(propname, true);
677         copy_to_pui(node, object);
678         PropertyObject* po = new PropertyObject(name, object, node);
679         _propertyObjects.push_back(po);
680         if(props->getBoolValue("live"))
681             _liveObjects.push_back(po);
682     }
683
684     SGPropertyNode * dest = fgGetNode("/sim/bindings/gui", true);
685     vector<SGPropertyNode_ptr> bindings = props->getChildren("binding");
686     if (bindings.size() > 0) {
687         GUIInfo * info = new GUIInfo(this);
688         info->key = props->getIntValue("keynum", -1);
689
690         for (unsigned int i = 0; i < bindings.size(); i++) {
691             unsigned int j = 0;
692             SGPropertyNode *binding;
693             while (dest->getChild("binding", j))
694                 j++;
695
696             binding = dest->getChild("binding", j, true);
697             copyProperties(bindings[i], binding);
698             info->bindings.push_back(new FGBinding(binding));
699         }
700         object->setCallback(action_callback);
701         object->setUserData(info);
702         _info.push_back(info);
703     }
704
705     object->makeReturnDefault(props->getBoolValue("default"));
706 }
707
708 void
709 FGDialog::setupGroup (puGroup * group, SGPropertyNode * props,
710                     int width, int height, bool makeFrame)
711 {
712     setupObject(group, props);
713
714     if (makeFrame) {
715         puFrame* f = new puFrame(0, 0, width, height);
716         setColor(f, props);
717     }
718
719     int nChildren = props->nChildren();
720     for (int i = 0; i < nChildren; i++)
721         makeObject(props->getChild(i), width, height);
722     group->close();
723 }
724
725 void
726 FGDialog::setColor(puObject * object, SGPropertyNode * props, int which)
727 {
728     string type = props->getName();
729     if (type == "")
730         type = "dialog";
731
732     FGColor *c = new FGColor(_gui->getColor("background"));
733     c->merge(_gui->getColor(type));
734     c->merge(props->getNode("color"));
735     if (c->isValid())
736         object->setColourScheme(c->red(), c->green(), c->blue(), c->alpha());
737
738     const struct {
739         int mask;
740         int id;
741         const char *name;
742         const char *cname;
743     } pucol[] = {
744         { BACKGROUND, PUCOL_BACKGROUND, "background", "color-background" },
745         { FOREGROUND, PUCOL_FOREGROUND, "foreground", "color-foreground" },
746         { HIGHLIGHT,  PUCOL_HIGHLIGHT,  "highlight",  "color-highlight" },
747         { LABEL,      PUCOL_LABEL,      "label",      "color-label" },
748         { LEGEND,     PUCOL_LEGEND,     "legend",     "color-legend" },
749         { MISC,       PUCOL_MISC,       "misc",       "color-misc" }
750     };
751
752     const int numcol = sizeof(pucol) / sizeof(pucol[0]);
753
754     for (int i = 0; i < numcol; i++) {
755         bool dirty = false;
756         c->clear();
757         c->setAlpha(1.0);
758
759         dirty |= c->merge(_gui->getColor(type + '-' + pucol[i].name));
760         if (which & pucol[i].mask)
761             dirty |= c->merge(props->getNode("color"));
762
763         if ((pucol[i].mask == LABEL) && !c->isValid())
764             dirty |= c->merge(_gui->getColor("label"));
765
766         if (c->isValid() && dirty)
767             object->setColor(pucol[i].id, c->red(), c->green(), c->blue(), c->alpha());
768     }
769 }
770
771 char **
772 FGDialog::make_char_array (int size)
773 {
774     char ** list = new char*[size+1];
775     for (int i = 0; i <= size; i++)
776         list[i] = 0;
777     _char_arrays.push_back(list);
778     return list;
779 }
780
781
782 \f
783 ////////////////////////////////////////////////////////////////////////
784 // Implementation of FGDialog::PropertyObject.
785 ////////////////////////////////////////////////////////////////////////
786
787 FGDialog::PropertyObject::PropertyObject (const char * n,
788                                            puObject * o,
789                                            SGPropertyNode_ptr p)
790     : name(n),
791       object(o),
792       node(p)
793 {
794 }
795
796
797 // end of dialog.cxx