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