]> git.mxchange.org Git - flightgear.git/blob - src/GUI/new_gui.cxx
Merge branch 'next' into comm-subsystem
[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
14 #include <plib/pu.h>
15
16 #include <simgear/compiler.h>
17 #include <simgear/structure/exception.hxx>
18 #include <simgear/props/props_io.hxx>
19 #include <simgear/misc/sg_dir.hxx>
20
21 #include <boost/algorithm/string/case_conv.hpp>
22
23 #include <Main/fg_props.hxx>
24
25 #if defined(SG_UNIX) && !defined(SG_MAC) 
26 #include "GL/glx.h"
27 #endif
28
29 #include "menubar.hxx"
30 #include "dialog.hxx"
31
32 extern puFont FONT_HELVETICA_14;
33 extern puFont FONT_SANS_12B;
34
35
36
37 \f
38 ////////////////////////////////////////////////////////////////////////
39 // Implementation of NewGUI.
40 ////////////////////////////////////////////////////////////////////////
41
42
43
44 NewGUI::NewGUI ()
45     : _menubar(new FGMenuBar),
46       _active_dialog(0)
47 {
48 }
49
50 NewGUI::~NewGUI ()
51 {
52     delete _menubar;
53     _dialog_props.clear();
54     for (_itt_t it = _colors.begin(); it != _colors.end(); ++it)
55         delete it->second;
56 }
57
58 void
59 NewGUI::init ()
60 {
61     setStyle();
62     SGPath p(globals->get_fg_root(), "gui/dialogs");
63     readDir(p);
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 void
237 NewGUI::newDialog (SGPropertyNode* props)
238 {
239     const char* cname = props->getStringValue("name");
240     if(!cname) {
241         SG_LOG(SG_GENERAL, SG_ALERT, "New dialog has no <name> property");
242         return;
243     }
244     string name = cname;
245     if(_active_dialogs.find(name) == _active_dialogs.end())
246         _dialog_props[name] = props;
247 }
248
249 void
250 NewGUI::readDir (const SGPath& path)
251 {
252     simgear::Dir dir(path);
253     simgear::PathList xmls = dir.children(simgear::Dir::TYPE_FILE, ".xml");
254     
255     for (unsigned int i=0; i<xmls.size(); ++i) {
256       SGPropertyNode * props = new SGPropertyNode;
257       try {
258           readProperties(xmls[i].str(), props);
259       } catch (const sg_exception &) {
260           SG_LOG(SG_INPUT, SG_ALERT, "Error parsing dialog "
261                  << xmls[i].str());
262           delete props;
263           continue;
264       }
265       SGPropertyNode *nameprop = props->getNode("name");
266       if (!nameprop) {
267           SG_LOG(SG_INPUT, SG_WARN, "dialog " << xmls[i].str()
268              << " has no name; skipping.");
269           delete props;
270           continue;
271       }
272       string name = nameprop->getStringValue();
273       _dialog_props[name] = props;
274     }
275 }
276
277
278 \f
279 ////////////////////////////////////////////////////////////////////////
280 // Style handling.
281 ////////////////////////////////////////////////////////////////////////
282
283 void
284 NewGUI::setStyle (void)
285 {
286     _itt_t it;
287     for (it = _colors.begin(); it != _colors.end(); ++it)
288       delete it->second;
289     _colors.clear();
290
291     // set up the traditional colors as default
292     _colors["background"] = new FGColor(0.8f, 0.8f, 0.9f, 0.85f);
293     _colors["foreground"] = new FGColor(0.0f, 0.0f, 0.0f, 1.0f);
294     _colors["highlight"]  = new FGColor(0.7f, 0.7f, 0.7f, 1.0f);
295     _colors["label"]      = new FGColor(0.0f, 0.0f, 0.0f, 1.0f);
296     _colors["legend"]     = new FGColor(0.0f, 0.0f, 0.0f, 1.0f);
297     _colors["misc"]       = new FGColor(0.0f, 0.0f, 0.0f, 1.0f);
298     _colors["inputfield"] = new FGColor(0.8f, 0.7f, 0.7f, 1.0f);
299
300     //puSetDefaultStyle();
301
302     int which = fgGetInt("/sim/gui/current-style", 0);
303     SGPropertyNode *sim = globals->get_props()->getNode("sim/gui", true);
304     SGPropertyNode *n = sim->getChild("style", which);
305     if (!n)
306         n = sim->getChild("style", 0, true);
307
308     setupFont(n->getNode("fonts/gui", true));
309     n = n->getNode("colors", true);
310
311     for (int i = 0; i < n->nChildren(); i++) {
312         SGPropertyNode *child = n->getChild(i);
313         _colors[child->getName()] = new FGColor(child);
314     }
315
316     FGColor *c = _colors["background"];
317     puSetDefaultColourScheme(c->red(), c->green(), c->blue(), c->alpha());
318 }
319
320
321 void
322 NewGUI::setupFont (SGPropertyNode *node)
323 {
324     _font = globals->get_fontcache()->get(node);
325     puSetDefaultFonts(*_font, *_font);
326     return;
327 }
328
329
330
331 \f
332 ////////////////////////////////////////////////////////////////////////
333 // FGColor class.
334 ////////////////////////////////////////////////////////////////////////
335
336 void
337 FGColor::print() const {
338     std::cerr << "red=" << _red << ", green=" << _green
339               << ", blue=" << _blue << ", alpha=" << _alpha << std::endl;
340 }
341
342 bool
343 FGColor::merge(const SGPropertyNode *node)
344 {
345     if (!node)
346         return false;
347
348     bool dirty = false;
349     const SGPropertyNode * n;
350     if ((n = node->getNode("red")))
351         _red = n->getFloatValue(), dirty = true;
352     if ((n = node->getNode("green")))
353         _green = n->getFloatValue(), dirty = true;
354     if ((n = node->getNode("blue")))
355         _blue = n->getFloatValue(), dirty = true;
356     if ((n = node->getNode("alpha")))
357         _alpha = n->getFloatValue(), dirty = true;
358     return dirty;
359 }
360
361 bool
362 FGColor::merge(const FGColor *color)
363 {
364     bool dirty = false;
365     if (color && color->_red >= 0.0)
366         _red = color->_red, dirty = true;
367     if (color && color->_green >= 0.0)
368         _green = color->_green, dirty = true;
369     if (color && color->_blue >= 0.0)
370         _blue = color->_blue, dirty = true;
371     if (color && color->_alpha >= 0.0)
372         _alpha = color->_alpha, dirty = true;
373     return dirty;
374 }
375
376
377
378 \f
379 ////////////////////////////////////////////////////////////////////////
380 // FGFontCache class.
381 ////////////////////////////////////////////////////////////////////////
382
383 namespace
384 {
385 struct GuiFont
386 {
387     const char *name;
388     puFont *font;
389     struct Predicate
390         : public std::unary_function<const GuiFont, bool>
391     {
392         Predicate(const char* name_) : name(name_) {}
393         bool operator() (const GuiFont& f1) const
394         {
395             return std::strcmp(f1.name, name) == 0;
396         }
397         const char* name;
398     };
399 };
400
401 const GuiFont guifonts[] = {
402     { "default",      &FONT_HELVETICA_14 },
403     { "FIXED_8x13",   &PUFONT_8_BY_13 },
404     { "FIXED_9x15",   &PUFONT_9_BY_15 },
405     { "TIMES_10",     &PUFONT_TIMES_ROMAN_10 },
406     { "TIMES_24",     &PUFONT_TIMES_ROMAN_24 },
407     { "HELVETICA_10", &PUFONT_HELVETICA_10 },
408     { "HELVETICA_12", &PUFONT_HELVETICA_12 },
409     { "HELVETICA_14", &FONT_HELVETICA_14 },
410     { "HELVETICA_18", &PUFONT_HELVETICA_18 },
411     { "SANS_12B",     &FONT_SANS_12B }
412 };
413
414 const GuiFont* guifontsEnd = &guifonts[sizeof(guifonts)/ sizeof(guifonts[0])];
415 }
416
417 FGFontCache::FGFontCache() :
418     _initialized(false)
419 {
420 }
421
422 FGFontCache::~FGFontCache()
423 {
424 #if defined(SG_UNIX) && !defined(SG_MAC) 
425    // Ugly workaround for a crash on exit with multiple screens configured
426    if (!glXGetCurrentContext())
427       return;
428 #endif
429    PuFontMap::iterator it, end = _puFonts.end();
430    for (it = _puFonts.begin(); it != end; ++it)
431        delete it->second;
432 }
433
434 inline bool FGFontCache::FntParamsLess::operator()(const FntParams& f1,
435                                                    const FntParams& f2) const
436 {
437     int comp = f1.name.compare(f2.name);
438     if (comp < 0)
439         return true;
440     else if (comp > 0)
441         return false;
442     if (f1.size < f2.size)
443         return true;
444     else if (f1.size > f2.size)
445         return false;
446     return f1.slant < f2.slant;
447 }
448
449 struct FGFontCache::fnt *
450 FGFontCache::getfnt(const char *name, float size, float slant)
451 {
452     string fontName = boost::to_lower_copy(string(name));
453     FntParams fntParams(fontName, size, slant);
454     PuFontMap::iterator i = _puFonts.find(fntParams);
455     if (i != _puFonts.end()) {
456         // found in the puFonts map, all done
457         return i->second;
458     }
459     
460     // fntTexFont s are all preloaded into the _texFonts map
461     TexFontMap::iterator texi = _texFonts.find(fontName);
462     fntTexFont* texfont = NULL;
463     puFont* pufont = NULL;
464     if (texi != _texFonts.end()) {
465         texfont = texi->second;
466     } else {
467         // check the built-in PUI fonts (in guifonts array)
468         const GuiFont* guifont = std::find_if(&guifonts[0], guifontsEnd,
469                                               GuiFont::Predicate(name));
470         if (guifont != guifontsEnd) {
471             pufont = guifont->font;
472         }
473     }
474     
475     fnt* f = new fnt;
476     if (pufont) {
477         f->pufont = pufont;
478     } else if (texfont) {
479         f->texfont = texfont;
480         f->pufont = new puFont;
481         f->pufont->initialize(static_cast<fntFont *>(f->texfont), size, slant);
482     } else {
483         f->pufont = guifonts[0].font;
484     }
485     _puFonts[fntParams] = f;
486     return f;
487 }
488
489 puFont *
490 FGFontCache::get(const char *name, float size, float slant)
491 {
492     return getfnt(name, size, slant)->pufont;
493 }
494
495 fntTexFont *
496 FGFontCache::getTexFont(const char *name, float size, float slant)
497 {
498     return getfnt(name, size, slant)->texfont;
499 }
500
501 puFont *
502 FGFontCache::get(SGPropertyNode *node)
503 {
504     if (!node)
505         return get("Helvetica.txf", 15.0, 0.0);
506
507     const char *name = node->getStringValue("name", "Helvetica.txf");
508     float size = node->getFloatValue("size", 15.0);
509     float slant = node->getFloatValue("slant", 0.0);
510
511     return get(name, size, slant);
512 }
513
514 void FGFontCache::init()
515 {
516     if (_initialized) {
517         return;
518     }
519     
520     char *envp = ::getenv("FG_FONTS");
521     if (envp != NULL) {
522         _path.set(envp);
523     } else {
524         _path.set(globals->get_fg_root());
525         _path.append("Fonts");
526     }
527     _initialized = true;
528 }
529
530 SGPath
531 FGFontCache::getfntpath(const char *name)
532 {
533     init();
534     SGPath path(_path);
535     if (name && std::string(name) != "") {
536         path.append(name);
537         if (path.exists())
538             return path;
539     }
540
541     path = SGPath(_path);
542     path.append("Helvetica.txf");
543     SG_LOG(SG_GENERAL, SG_WARN, "Unknown font name '" << name << "', defaulting to Helvetica");
544     return path;
545 }
546
547 bool FGFontCache::initializeFonts()
548 {
549     static string fontext("txf");
550     init();
551     ulDir* fontdir = ulOpenDir(_path.c_str());
552     if (!fontdir)
553         return false;
554     const ulDirEnt *dirEntry;
555     while ((dirEntry = ulReadDir(fontdir)) != 0) {
556         SGPath path(_path);
557         path.append(dirEntry->d_name);
558         if (path.extension() == fontext) {
559             fntTexFont* f = new fntTexFont;
560             if (f->load((char *)path.c_str())) {
561                 // convert font names in the map to lowercase for matching
562                 string fontName = boost::to_lower_copy(string(dirEntry->d_name));
563                 _texFonts[fontName] = f;
564             } else
565                 delete f;
566         }
567     }
568     ulCloseDir(fontdir);
569     return true;
570 }
571
572 // end of new_gui.cxx