]> git.mxchange.org Git - flightgear.git/blob - src/GUI/new_gui.cxx
Merge branch 'next' of git://gitorious.org/fg/flightgear into next
[flightgear.git] / src / GUI / new_gui.cxx
1 // new_gui.cxx: implementation of XML-configurable GUI support.
2
3 #ifdef HAVE_CONFIG_H
4 #  include <config.h>
5 #endif
6
7 #include "new_gui.hxx"
8
9 #include <algorithm>
10 #include <iostream>
11 #include <cstring>
12 #include <sys/types.h>
13 #include <plib/ul.h>
14
15 #include <plib/pu.h>
16 #include <plib/ul.h>
17
18 #include <simgear/compiler.h>
19 #include <simgear/structure/exception.hxx>
20 #include <simgear/props/props_io.hxx>
21
22 #include <boost/algorithm/string/case_conv.hpp>
23
24 #include <Main/fg_props.hxx>
25
26 #include "menubar.hxx"
27 #include "dialog.hxx"
28
29 extern puFont FONT_HELVETICA_14;
30 extern puFont FONT_SANS_12B;
31
32
33
34 \f
35 ////////////////////////////////////////////////////////////////////////
36 // Implementation of NewGUI.
37 ////////////////////////////////////////////////////////////////////////
38
39
40
41 NewGUI::NewGUI ()
42     : _menubar(new FGMenuBar),
43       _active_dialog(0)
44 {
45 }
46
47 NewGUI::~NewGUI ()
48 {
49     delete _menubar;
50     _dialog_props.clear();
51     for (_itt_t it = _colors.begin(); it != _colors.end(); ++it)
52         delete it->second;
53 }
54
55 void
56 NewGUI::init ()
57 {
58     setStyle();
59     char path1[1024];
60     char path2[1024];
61     ulMakePath(path1, globals->get_fg_root().c_str(), "gui");
62     ulMakePath(path2, path1, "dialogs");
63     readDir(path2);
64     _menubar->init();
65 }
66
67 void
68 NewGUI::reinit ()
69 {
70     reset(true);
71     fgSetBool("/sim/signals/reinit-gui", true);
72 }
73
74 void
75 NewGUI::redraw ()
76 {
77     reset(false);
78 }
79
80 void
81 NewGUI::reset (bool reload)
82 {
83     map<string,FGDialog *>::iterator iter;
84     vector<string> dlg;
85     // close all open dialogs and remember them ...
86     for (iter = _active_dialogs.begin(); iter != _active_dialogs.end(); ++iter)
87         dlg.push_back(iter->first);
88
89     unsigned int i;
90     for (i = 0; i < dlg.size(); i++)
91         closeDialog(dlg[i]);
92
93     setStyle();
94
95     unbind();
96     delete _menubar;
97     _menubar = new FGMenuBar;
98
99     if (reload) {
100         _dialog_props.clear();
101         init();
102     } else {
103         _menubar->init();
104     }
105
106     bind();
107
108     // open dialogs again
109     for (i = 0; i < dlg.size(); i++)
110         showDialog(dlg[i]);
111 }
112
113 void
114 NewGUI::bind ()
115 {
116     fgTie("/sim/menubar/visibility", this,
117           &NewGUI::getMenuBarVisible, &NewGUI::setMenuBarVisible);
118 }
119
120 void
121 NewGUI::unbind ()
122 {
123     fgUntie("/sim/menubar/visibility");
124 }
125
126 void
127 NewGUI::update (double delta_time_sec)
128 {
129     map<string,FGDialog *>::iterator iter = _active_dialogs.begin();
130     for(/**/; iter != _active_dialogs.end(); iter++)
131         iter->second->update();
132 }
133
134 bool
135 NewGUI::showDialog (const string &name)
136 {
137     if (_dialog_props.find(name) == _dialog_props.end()) {
138         SG_LOG(SG_GENERAL, SG_ALERT, "Dialog " << name << " not defined");
139         return false;
140     } else {
141         if(!_active_dialogs[name])
142             _active_dialogs[name] = new FGDialog(_dialog_props[name]);
143         return true;
144     }
145 }
146
147 bool
148 NewGUI::closeActiveDialog ()
149 {
150     if (_active_dialog == 0)
151         return false;
152
153     // Kill any entries in _active_dialogs...  Is there an STL
154     // algorithm to do (delete map entries by value, not key)?  I hate
155     // the STL :) -Andy
156     map<string,FGDialog *>::iterator iter = _active_dialogs.begin();
157     for(/**/; iter != _active_dialogs.end(); iter++) {
158         if(iter->second == _active_dialog) {
159             _active_dialogs.erase(iter);
160             // iter is no longer valid
161             break;
162         }
163     }
164
165     delete _active_dialog;
166     _active_dialog = 0;
167     return true;
168 }
169
170 bool
171 NewGUI::closeDialog (const string& name)
172 {
173     if(_active_dialogs.find(name) != _active_dialogs.end()) {
174         if(_active_dialog == _active_dialogs[name])
175             _active_dialog = 0;
176         delete _active_dialogs[name];
177         _active_dialogs.erase(name);
178         return true;
179     }
180     return false; // dialog wasn't open...
181 }
182
183 SGPropertyNode_ptr
184 NewGUI::getDialogProperties (const string &name)
185 {
186     if(_dialog_props.find(name) != _dialog_props.end())
187         return _dialog_props[name];
188
189     SG_LOG(SG_GENERAL, SG_DEBUG, "dialog '" << name << "' missing");
190     return 0;
191 }
192
193 FGDialog *
194 NewGUI::getDialog (const string &name)
195 {
196     if(_active_dialogs.find(name) != _active_dialogs.end())
197         return _active_dialogs[name];
198
199     SG_LOG(SG_GENERAL, SG_DEBUG, "dialog '" << name << "' missing");
200     return 0;
201 }
202
203 void
204 NewGUI::setActiveDialog (FGDialog * dialog)
205 {
206     _active_dialog = dialog;
207 }
208
209 FGDialog *
210 NewGUI::getActiveDialog ()
211 {
212     return _active_dialog;
213 }
214
215 FGMenuBar *
216 NewGUI::getMenuBar ()
217 {
218     return _menubar;
219 }
220
221 bool
222 NewGUI::getMenuBarVisible () const
223 {
224     return _menubar->isVisible();
225 }
226
227 void
228 NewGUI::setMenuBarVisible (bool visible)
229 {
230     if (visible)
231         _menubar->show();
232     else
233         _menubar->hide();
234 }
235
236 static bool
237 test_extension (const char * path, const char * ext)
238 {
239     int pathlen = strlen(path);
240     int extlen = strlen(ext);
241
242     for (int i = 1; i <= pathlen && i <= extlen; i++) {
243         if (path[pathlen-i] != ext[extlen-i])
244             return false;
245     }
246     return true;
247 }
248
249 void
250 NewGUI::newDialog (SGPropertyNode* props)
251 {
252     const char* cname = props->getStringValue("name");
253     if(!cname) {
254         SG_LOG(SG_GENERAL, SG_ALERT, "New dialog has no <name> property");
255         return;
256     }
257     string name = cname;
258     if(_active_dialogs.find(name) == _active_dialogs.end())
259         _dialog_props[name] = props;
260 }
261
262 void
263 NewGUI::readDir (const char * path)
264 {
265     ulDir * dir = ulOpenDir(path);
266
267     if (dir == 0) {
268         SG_LOG(SG_GENERAL, SG_ALERT, "Failed to read GUI files from "
269                << path);
270         return;
271     }
272
273     for (ulDirEnt * dirEnt = ulReadDir(dir);
274          dirEnt != 0;
275          dirEnt = ulReadDir(dir)) {
276
277         char subpath[1024];
278
279         ulMakePath(subpath, path, dirEnt->d_name);
280
281         if (!dirEnt->d_isdir && test_extension(subpath, ".xml")) {
282             SGPropertyNode * props = new SGPropertyNode;
283             try {
284                 readProperties(subpath, props);
285             } catch (const sg_exception &) {
286                 SG_LOG(SG_INPUT, SG_ALERT, "Error parsing dialog "
287                        << subpath);
288                 delete props;
289                 continue;
290             }
291             SGPropertyNode *nameprop = props->getNode("name");
292             if (!nameprop) {
293                 SG_LOG(SG_INPUT, SG_WARN, "dialog " << subpath
294                    << " has no name; skipping.");
295                 delete props;
296                 continue;
297             }
298             string name = nameprop->getStringValue();
299             if (_dialog_props[name])
300                 delete (SGPropertyNode *)_dialog_props[name];
301
302             _dialog_props[name] = props;
303         }
304     }
305     ulCloseDir(dir);
306 }
307
308
309 \f
310 ////////////////////////////////////////////////////////////////////////
311 // Style handling.
312 ////////////////////////////////////////////////////////////////////////
313
314 void
315 NewGUI::setStyle (void)
316 {
317     _itt_t it;
318     for (it = _colors.begin(); it != _colors.end(); ++it)
319       delete it->second;
320     _colors.clear();
321
322     // set up the traditional colors as default
323     _colors["background"] = new FGColor(0.8f, 0.8f, 0.9f, 0.85f);
324     _colors["foreground"] = new FGColor(0.0f, 0.0f, 0.0f, 1.0f);
325     _colors["highlight"]  = new FGColor(0.7f, 0.7f, 0.7f, 1.0f);
326     _colors["label"]      = new FGColor(0.0f, 0.0f, 0.0f, 1.0f);
327     _colors["legend"]     = new FGColor(0.0f, 0.0f, 0.0f, 1.0f);
328     _colors["misc"]       = new FGColor(0.0f, 0.0f, 0.0f, 1.0f);
329     _colors["inputfield"] = new FGColor(0.8f, 0.7f, 0.7f, 1.0f);
330
331     //puSetDefaultStyle();
332
333     int which = fgGetInt("/sim/gui/current-style", 0);
334     SGPropertyNode *sim = globals->get_props()->getNode("sim/gui", true);
335     SGPropertyNode *n = sim->getChild("style", which);
336     if (!n)
337         n = sim->getChild("style", 0, true);
338
339     setupFont(n->getNode("fonts/gui", true));
340     n = n->getNode("colors", true);
341
342     for (int i = 0; i < n->nChildren(); i++) {
343         SGPropertyNode *child = n->getChild(i);
344         _colors[child->getName()] = new FGColor(child);
345     }
346
347     FGColor *c = _colors["background"];
348     puSetDefaultColourScheme(c->red(), c->green(), c->blue(), c->alpha());
349 }
350
351
352 void
353 NewGUI::setupFont (SGPropertyNode *node)
354 {
355     _font = globals->get_fontcache()->get(node);
356     puSetDefaultFonts(*_font, *_font);
357     return;
358 }
359
360
361
362 \f
363 ////////////////////////////////////////////////////////////////////////
364 // FGColor class.
365 ////////////////////////////////////////////////////////////////////////
366
367 void
368 FGColor::print() const {
369     std::cerr << "red=" << _red << ", green=" << _green
370               << ", blue=" << _blue << ", alpha=" << _alpha << std::endl;
371 }
372
373 bool
374 FGColor::merge(const SGPropertyNode *node)
375 {
376     if (!node)
377         return false;
378
379     bool dirty = false;
380     const SGPropertyNode * n;
381     if ((n = node->getNode("red")))
382         _red = n->getFloatValue(), dirty = true;
383     if ((n = node->getNode("green")))
384         _green = n->getFloatValue(), dirty = true;
385     if ((n = node->getNode("blue")))
386         _blue = n->getFloatValue(), dirty = true;
387     if ((n = node->getNode("alpha")))
388         _alpha = n->getFloatValue(), dirty = true;
389     return dirty;
390 }
391
392 bool
393 FGColor::merge(const FGColor *color)
394 {
395     bool dirty = false;
396     if (color && color->_red >= 0.0)
397         _red = color->_red, dirty = true;
398     if (color && color->_green >= 0.0)
399         _green = color->_green, dirty = true;
400     if (color && color->_blue >= 0.0)
401         _blue = color->_blue, dirty = true;
402     if (color && color->_alpha >= 0.0)
403         _alpha = color->_alpha, dirty = true;
404     return dirty;
405 }
406
407
408
409 \f
410 ////////////////////////////////////////////////////////////////////////
411 // FGFontCache class.
412 ////////////////////////////////////////////////////////////////////////
413
414 namespace
415 {
416 struct GuiFont
417 {
418     const char *name;
419     puFont *font;
420     struct Predicate
421         : public std::unary_function<const GuiFont, bool>
422     {
423         Predicate(const char* name_) : name(name_) {}
424         bool operator() (const GuiFont& f1) const
425         {
426             return std::strcmp(f1.name, name) == 0;
427         }
428         const char* name;
429     };
430 };
431
432 const GuiFont guifonts[] = {
433     { "default",      &FONT_HELVETICA_14 },
434     { "FIXED_8x13",   &PUFONT_8_BY_13 },
435     { "FIXED_9x15",   &PUFONT_9_BY_15 },
436     { "TIMES_10",     &PUFONT_TIMES_ROMAN_10 },
437     { "TIMES_24",     &PUFONT_TIMES_ROMAN_24 },
438     { "HELVETICA_10", &PUFONT_HELVETICA_10 },
439     { "HELVETICA_12", &PUFONT_HELVETICA_12 },
440     { "HELVETICA_14", &FONT_HELVETICA_14 },
441     { "HELVETICA_18", &PUFONT_HELVETICA_18 },
442     { "SANS_12B",     &FONT_SANS_12B }
443 };
444
445 const GuiFont* guifontsEnd = &guifonts[sizeof(guifonts)/ sizeof(guifonts[0])];
446 }
447
448 FGFontCache::FGFontCache() :
449     _initialized(false)
450 {
451 }
452
453 FGFontCache::~FGFontCache()
454 {
455    PuFontMap::iterator it, end = _puFonts.end();
456    for (it = _puFonts.begin(); it != end; ++it)
457        delete it->second;
458 }
459
460 inline bool FGFontCache::FntParamsLess::operator()(const FntParams& f1,
461                                                    const FntParams& f2) const
462 {
463     int comp = f1.name.compare(f2.name);
464     if (comp < 0)
465         return true;
466     else if (comp > 0)
467         return false;
468     if (f1.size < f2.size)
469         return true;
470     else if (f1.size > f2.size)
471         return false;
472     return f1.slant < f2.slant;
473 }
474
475 struct FGFontCache::fnt *
476 FGFontCache::getfnt(const char *name, float size, float slant)
477 {
478     string fontName = boost::to_lower_copy(string(name));
479     FntParams fntParams(fontName, size, slant);
480     PuFontMap::iterator i = _puFonts.find(fntParams);
481     if (i != _puFonts.end()) {
482         // found in the puFonts map, all done
483         return i->second;
484     }
485     
486     // fntTexFont s are all preloaded into the _texFonts map
487     TexFontMap::iterator texi = _texFonts.find(fontName);
488     fntTexFont* texfont = NULL;
489     puFont* pufont = NULL;
490     if (texi != _texFonts.end()) {
491         texfont = texi->second;
492     } else {
493         // check the built-in PUI fonts (in guifonts array)
494         const GuiFont* guifont = std::find_if(&guifonts[0], guifontsEnd,
495                                               GuiFont::Predicate(name));
496         if (guifont != guifontsEnd) {
497             pufont = guifont->font;
498         }
499     }
500     
501     fnt* f = new fnt;
502     if (pufont) {
503         f->pufont = pufont;
504     } else if (texfont) {
505         f->texfont = texfont;
506         f->pufont = new puFont;
507         f->pufont->initialize(static_cast<fntFont *>(f->texfont), size, slant);
508     } else {
509         f->pufont = guifonts[0].font;
510     }
511     _puFonts[fntParams] = f;
512     return f;
513 }
514
515 puFont *
516 FGFontCache::get(const char *name, float size, float slant)
517 {
518     return getfnt(name, size, slant)->pufont;
519 }
520
521 fntTexFont *
522 FGFontCache::getTexFont(const char *name, float size, float slant)
523 {
524     return getfnt(name, size, slant)->texfont;
525 }
526
527 puFont *
528 FGFontCache::get(SGPropertyNode *node)
529 {
530     if (!node)
531         return get("Helvetica.txf", 15.0, 0.0);
532
533     const char *name = node->getStringValue("name", "Helvetica.txf");
534     float size = node->getFloatValue("size", 15.0);
535     float slant = node->getFloatValue("slant", 0.0);
536
537     return get(name, size, slant);
538 }
539
540 void FGFontCache::init()
541 {
542     if (_initialized) {
543         return;
544     }
545     
546     char *envp = ::getenv("FG_FONTS");
547     if (envp != NULL) {
548         _path.set(envp);
549     } else {
550         _path.set(globals->get_fg_root());
551         _path.append("Fonts");
552     }
553     _initialized = true;
554 }
555
556 SGPath
557 FGFontCache::getfntpath(const char *name)
558 {
559     init();
560     SGPath path(_path);
561     if (name && std::string(name) != "") {
562         path.append(name);
563         if (path.exists())
564             return path;
565     }
566
567     path = SGPath(_path);
568     path.append("Helvetica.txf");
569     SG_LOG(SG_GENERAL, SG_WARN, "Unknown font name '" << name << "', defaulting to Helvetica");
570     return path;
571 }
572
573 bool FGFontCache::initializeFonts()
574 {
575     static string fontext("txf");
576     init();
577     ulDir* fontdir = ulOpenDir(_path.c_str());
578     if (!fontdir)
579         return false;
580     const ulDirEnt *dirEntry;
581     while ((dirEntry = ulReadDir(fontdir)) != 0) {
582         SGPath path(_path);
583         path.append(dirEntry->d_name);
584         if (path.extension() == fontext) {
585             fntTexFont* f = new fntTexFont;
586             if (f->load((char *)path.c_str())) {
587                 // convert font names in the map to lowercase for matching
588                 string fontName = boost::to_lower_copy(string(dirEntry->d_name));
589                 _texFonts[fontName] = f;
590             } else
591                 delete f;
592         }
593     }
594     ulCloseDir(fontdir);
595     return true;
596 }
597
598 // end of new_gui.cxx