FlightGear next
GraphicsPresets.cxx
Go to the documentation of this file.
1#include "config.h"
2
4
5// std
6#include <unordered_set>
7
8// SG
9#include <simgear/io/iostreams/sgstream.hxx>
10#include <simgear/misc/sg_dir.hxx>
11#include <simgear/props/props_io.hxx>
12#include <simgear/structure/commands.hxx>
13#include <simgear/structure/exception.hxx>
14
15// FG
16#include <Main/fg_props.hxx>
17#include <Main/globals.hxx>
18#include <Main/locale.hxx>
20#include <Scenery/scenery.hxx>
21
22using namespace std;
23
24namespace {
25
26const char* kPresetPropPath = "/sim/rendering/preset";
27const char* kPresetNameProp = "/sim/rendering/preset-name";
28const char* kPresetDescriptionProp = "/sim/rendering/preset-description";
29const char* kPresetActiveProp = "/sim/rendering/preset-active";
30
31
32const char* kRestartRequiredProp = "/sim/rendering/restart-required";
33const char* kSceneryReloadRequiredProp = "/sim/rendering/scenery-reload-required";
34const char* kCompositorReloadRequiredProp = "/sim/rendering/compositor-reload-required";
35
36// define the property prefixes which graphics presets are allowed to
37// modify. Changes to properties outside these prefixes will be
38// blocked
39
40const string_list kWitelistedPrefixes = {
41 "/sim/rendering"};
42
43
44} // namespace
45
46namespace flightgear {
47
48class GraphicsPresets::GraphicsConfigChangeListener : public SGPropertyChangeListener
49{
50 struct WatchedProp {
51 std::string previousValue;
52 SGPropertyNode_ptr node;
53 };
54
55 using PropertyNodeList = std::vector<WatchedProp>;
56 PropertyNodeList _watchedProps;
57
58public:
60 {
61 _presetProp = fgGetNode(kPresetPropPath, true);
62 }
63
64 void registerWithProperty(SGPropertyNode_ptr n)
65 {
66 auto it = findProp(n);
67 if (it != _watchedProps.end()) {
68 // this would happen if a preset somehow set the same property more than once
69 SG_LOG(SG_GUI, SG_ALERT, "GraphicsPresets: Duplicate registration for: " << n->getPath());
70 return;
71 }
72
73 WatchedProp p = {n->getStringValue(), n};
74 _watchedProps.push_back(p);
75 n->addChangeListener(this);
76 }
77
79 {
80 for (const auto& w : _watchedProps) {
81 w.node->removeChangeListener(this);
82 }
83 _watchedProps.clear();
84 }
85
86 void valueChanged(SGPropertyNode* prop) override
87 {
88 if (!_presetProp->hasValue()) {
89 return;
90 }
91
92 auto it = findProp(prop);
93 assert(it != _watchedProps.end());
94 const std::string newValue = prop->getStringValue();
95 if (newValue == it->previousValue) {
96 return;
97 }
98
99 SG_LOG(SG_GUI, SG_INFO, "GraphicsPreset clearing; setting: " << prop->getPath() << " was modified");
100 flightgear::addSentryBreadcrumb("clearing graphics preset, config was customised at: " + prop->getPath(), "info");
101 _presetProp->clearValue();
102
103 auto gp = globals->get_subsystem<GraphicsPresets>();
104 gp->clearPreset();
105 }
106
107private:
108 SGPropertyNode_ptr _presetProp;
109
110 PropertyNodeList::iterator findProp(SGPropertyNode* node)
111 {
112 return std::find_if(_watchedProps.begin(), _watchedProps.end(), [node](const WatchedProp& w) {
113 return w.node == node;
114 });
115 }
116};
117
123class GraphicsPresets::RequiredPropertyListener : public SGPropertyChangeListener
124{
125public:
126 RequiredPropertyListener(const std::string& requiredProp, SGPropertyNode_ptr props)
127 {
128 _requiredProp = fgGetNode(requiredProp, true);
129 _requiredProp->setBoolValue(false);
130
131 // would happen if graphics-properties.xml was malformed
132 if (!props)
133 return;
134
135 for (const auto& c : props->getChildren("property")) {
136 // tolerate exterior whitespace in the XML
137 string path = simgear::strutils::strip(c->getStringValue());
138 if (path.empty())
139 continue;
140
141 SGPropertyNode_ptr n = fgGetNode(path, true);
142 if (n) {
143 n->addChangeListener(this);
144 }
145 } // of properties iteration
146 }
147
149 {
150 if (_requiredProp->getBoolValue()) {
151 _requiredProp->setBoolValue(false);
152 }
153 }
154
155 void valueChanged(SGPropertyNode* prop) override
156 {
157 if (!_requiredProp->getBoolValue()) {
158 SG_LOG(SG_GUI, SG_INFO, "GraphicsPreset: saw modification of: " << prop->getPath() << ", setting:" << _requiredProp->getPath() << " to true");
159 _requiredProp->setBoolValue(true);
160 }
161 }
162
163private:
164 SGPropertyNode_ptr _requiredProp;
165};
166
167static bool do_apply_preset(const SGPropertyNode* arg, SGPropertyNode* root)
168{
169 auto gp = globals->get_subsystem<GraphicsPresets>();
170
171 bool result = false;
172 if (arg->hasChild("path")) {
173 SGPath p = SGPath::fromUtf8(arg->getStringValue("path"));
174 if (!p.exists()) {
175 SG_LOG(SG_IO, SG_ALERT, "apply-graphics-preset: no file at: " << p);
176 return false;
177 }
178
179 result = gp->applyCustomPreset(p);
180 } else if (arg->hasChild("preset-name")) {
181 // helper for PUI UI: PUI ComboBox gives us the name, not the ID.
182 // so allow specify a preset by (localized) name.
183 gp->applyPresetByName(arg->getStringValue("preset-name"));
184 } else if (arg->hasChild("preset")) {
185 fgSetString(kPresetPropPath, arg->getStringValue("preset"));
186 result = gp->applyCurrentPreset();
187 } else {
188 // just apply the current selected one
189 result = gp->applyCurrentPreset();
190 }
191
192 if (arg->getBoolValue("reload-scenery")) {
193 if (fgGetBool(kSceneryReloadRequiredProp)) {
194 SG_LOG(SG_GUI, SG_MANDATORY_INFO, "apply-graphics-preset: triggering scenery reload");
195 globals->get_scenery()->reinit(); // this will set
196 }
197 }
198
199 return result;
200}
201
202static bool do_save_preset(const SGPropertyNode* arg, SGPropertyNode* root)
203{
204 if (!arg->hasChild("path")) {
205 SG_LOG(SG_GUI, SG_ALERT, "do_save_preset: no out path argument provided");
206 return false;
207 }
208
209 const string spath = arg->getStringValue("path");
210 if (spath == "!ask") {
211 }
212
213 const SGPath path = SGPath::fromUtf8(spath);
214 const string name = arg->getStringValue("name");
215 const string description = arg->getStringValue("description");
216 auto gp = globals->get_subsystem<GraphicsPresets>();
217
218 return gp->saveToXML(path, name, description);
219}
220
221static bool do_list_standard_presets(const SGPropertyNode* arg, SGPropertyNode* root)
222{
223 auto gp = globals->get_subsystem<GraphicsPresets>();
224 if (!arg->hasValue("destination-path")) {
225 SG_LOG(SG_GUI, SG_ALERT, "list-graphics-preset: no destination path supplied");
226 return false;
227 }
228
229 SGPropertyNode_ptr destRoot = fgGetNode(arg->getStringValue("destination-path"), true /* create */);
230
231 if (arg->hasValue("clear-destination")) {
232 destRoot->removeAllChildren();
233 }
234
235 // format the way PUI combo-box (actualy, fgValueList) like it
236 if (arg->getBoolValue("as-combobox-values")) {
237 for (const auto& preset : gp->listPresets()) {
238 SGPropertyNode_ptr v = destRoot->addChild("value");
239 v->setStringValue(preset.name);
240 }
241 } else {
242 for (const auto& preset : gp->listPresets()) {
243 SGPropertyNode_ptr pn = destRoot->addChild("preset");
244 pn->setStringValue("name", preset.name);
245 pn->setStringValue("id", preset.id);
246 pn->setStringValue("description", preset.description);
247 }
248 }
249
250 return true;
251}
252
253
255{
256 // needs to be done early so that applyInitialPreset
257 // can setup the registration
258 _listener.reset(new GraphicsConfigChangeListener);
259}
260
264
266{
267 const string currentPreset = fgGetString(kPresetPropPath);
268 fgSetBool(kPresetActiveProp, false);
269 if (!currentPreset.empty()) {
270 SG_LOG(SG_GUI, SG_INFO, "Applying graphics preset: " << currentPreset);
271 addSentryBreadcrumb("Startup selection of preset: " + currentPreset, "info");
273 }
274}
275
277{
278 // create the change listeners. Because we do the initial application before this, we won't
279 // see any changes caused by any initial preset load (or autosave.xml load), only
280 // future changes made by the user via settings UI.
281 SGPropertyNode_ptr graphicsPropsXML(new SGPropertyNode);
282 try {
283 readProperties(globals->findDataPath("Video/graphics-properties.xml"), graphicsPropsXML.get());
284
285 _restartListener.reset(new RequiredPropertyListener{kRestartRequiredProp, graphicsPropsXML->getChild("restart-required")});
286 _sceneryReloadListener.reset(new RequiredPropertyListener{kSceneryReloadRequiredProp, graphicsPropsXML->getChild("scenery-reload-required")});
287 _compositorReloadListener.reset(new RequiredPropertyListener{kCompositorReloadRequiredProp, graphicsPropsXML->getChild("compositor-reload-required")});
288
289 SGPropertyNode_ptr toSave = graphicsPropsXML->getChild("save-to-file");
290 if (toSave) {
291 for (const auto& p : toSave->getChildren("property")) {
292 string t = simgear::strutils::strip(p->getStringValue());
293 if (t.at(0) == '/') {
294 t = t.substr(1); // remove leading '/'
295 }
296 _propertiesToSave.push_back(t);
297 }
298 }
299 } catch (sg_exception& e) {
300 SG_LOG(SG_GUI, SG_ALERT, "Failed to read graphics-properties.xml");
301 }
302
303 globals->get_commands()->addCommand("apply-graphics-preset", do_apply_preset);
304 globals->get_commands()->addCommand("save-graphics-preset", do_save_preset);
305 globals->get_commands()->addCommand("list-graphics-presets", do_list_standard_presets);
306}
307
308void GraphicsPresets::update(double delta_time_sec)
309{
310 SG_UNUSED(delta_time_sec);
311}
312
314{
315 globals->get_commands()->removeCommand("apply-graphics-preset");
316 globals->get_commands()->removeCommand("save-graphics-preset");
317 globals->get_commands()->removeCommand("list-graphics-presets");
318
319 _listener->unregisterFromProperties();
320 _listener.reset();
321}
322
324{
325 _listener->unregisterFromProperties();
326
327 const string presetId = fgGetString(kPresetPropPath);
329 const auto ok = loadStandardPreset(presetId, info);
330 if (!ok) {
331 return false;
332 }
333
334 addSentryBreadcrumb("loading graphics preset:" + presetId, "info");
335
336 return innerApplyPreset(info, true);
337}
338
339bool GraphicsPresets::loadStandardPreset(const std::string& id, GraphicsPresetInfo& info)
340{
341 const auto path = globals->findDataPath("Video/" + id + "-preset.xml");
342 if (!path.exists()) {
343 SG_LOG(SG_GUI, SG_ALERT, "No such graphics preset '" << id << "' found");
344 return false;
345 }
346
347 return loadPresetXML(path, info);
348}
349
350
352{
353 GraphicsPresetVec result;
354
355 auto videoPaths = globals->get_data_paths("Video");
356 for (const auto& vp : videoPaths) {
357 simgear::Dir videoDir(vp);
358 for (const auto& presetFile : videoDir.children(simgear::Dir::TYPE_FILE, "-preset.xml")) {
360 loadPresetXML(presetFile, info);
361 result.push_back(info);
362 }
363 } // of Video/ data dirs iteration
364
365 // Sort the resulting list by the order number or alphabetically if some
366 // presets have the same order number
367 sort(result.begin(), result.end(), [](auto &a, auto &b) {
368 if (a.orderNum != b.orderNum)
369 return a.orderNum < b.orderNum;
370 return a.name < b.name;
371 });
372
373 return result;
374}
375
377{
379 const auto ok = loadPresetXML(path, info);
380 if (!ok) {
381 return false;
382 }
383
384 addSentryBreadcrumb("loading graphics preset from: " + path.utf8Str(), "info");
385 return innerApplyPreset(info, true);
386}
387
389{
390 const auto presets = listPresets();
391 auto it = std::find_if(presets.begin(), presets.end(), [name](const GraphicsPresetInfo& pi) { return simgear::strutils::iequals(name, pi.name); });
392 if (it == presets.end()) {
393 SG_LOG(SG_GUI, SG_ALERT, "Couldn't find graphics preset with name: " << name);
394 return false;
395 }
396
397 fgSetString(kPresetPropPath, it->id);
398 return applyCurrentPreset();
399}
400
401bool GraphicsPresets::innerApplyPreset(const GraphicsPresetInfo& info, bool overwriteAutosaved)
402{
403 fgSetString(kPresetNameProp, info.name);
404 fgSetString(kPresetDescriptionProp, info.description);
405
406 if (info.id == "custom") {
407 fgSetBool(kPresetActiveProp, false);
408 }
409
410 std::unordered_set<string> leafProps;
411
412 copyPropertiesIf(info.properties, globals->get_props(), [overwriteAutosaved, &leafProps](const SGPropertyNode* src) {
413 if (src->getParent() == nullptr)
414 return true; // root node passes
415
416 // due to the slightly odd way SGPropertyNode::getPath works, we
417 // don't need to omit settings here; it will be dropped
418 // automatically.
419 const auto path = src->getPath(true);
420
421 auto it = std::find_if(kWitelistedPrefixes.begin(), kWitelistedPrefixes.end(), [&path](const string& p) {
422 // if we're high up in the tree, eg looking at /sim, then we
423 // want to check if at least one prefix includes that path
424 if (path.length() < p.length()) {
425 return simgear::strutils::starts_with(p, path);
426 }
427
428 // if the prefix is longer (more specific) than our path, we
429 // want to consider the full prefix
430 return simgear::strutils::starts_with(path, p);
431 });
432
433 if (it == kWitelistedPrefixes.end()) {
434 return false; // skip entirely
435 }
436
437 // find the corresponding destination node
438 auto dstNode = globals->get_props()->getNode(path);
439
440 // if destination exists, and we're not over-writing, check its
441 // ARCHIVE flag.
442 if (!overwriteAutosaved && dstNode && (dstNode->getAttribute(SGPropertyNode::ARCHIVE) == false)) {
443 return false;
444 }
445
446 // only watch the leaf properties
447 const bool isLeaf = src->nChildren() == 0;
448 if (isLeaf) {
449 leafProps.insert(path);
450 }
451 return true; // easy, just copy it
452 });
453
454 _listener->unregisterFromProperties();
455 for (const auto& p : leafProps) {
456 _listener->registerWithProperty(fgGetNode(p));
457 }
458
459 fgSetBool(kPresetActiveProp, true);
460 return true;
461}
462
463void GraphicsPresets::clearPreset()
464{
465 fgSetString(kPresetNameProp, "");
466 fgSetString(kPresetDescriptionProp, "");
467 fgSetBool(kPresetActiveProp, false);
468 _listener->unregisterFromProperties();
469}
470
471bool GraphicsPresets::loadPresetXML(const SGPath& p, GraphicsPresetInfo& info)
472{
473 SGPropertyNode_ptr props(new SGPropertyNode);
474
475 try {
476 readProperties(p, props.get());
477 } catch (sg_exception& e) {
478 SG_LOG(SG_IO, SG_ALERT, "XML errors loading " << p.str() << "\n\t" << e.getFormattedMessage());
479 return false;
480 }
481
482 const string id = props->getStringValue("id");
483 const string rawName = props->getStringValue("name");
484 const string rawDesc = props->getStringValue("description");
485 int orderNum = props->getIntValue("order-num", 99);
486
487 if (id.empty() || rawName.empty() || rawDesc.empty()) {
488 SG_LOG(SG_IO, SG_ALERT, "Missing preset info loading: " << p.str());
489 return false;
490 }
491
492 info.id = id;
493 info.name = globals->get_locale()->getLocalizedString(rawName, "graphics-presets");
494 info.description = globals->get_locale()->getLocalizedString(rawDesc, "graphics-presets");
495 info.orderNum = orderNum;
496
497 if (info.name.empty())
498 info.name = rawName; // no translation defined
499 if (info.description.empty())
500 info.description = rawDesc;
501
502 info.properties = props->getChild("settings");
503 if (!info.properties) {
504 SG_LOG(SG_IO, SG_ALERT, "Missing settings loading: " << p.str());
505 return false;
506 }
507
508 // read devices list
509 info.devices.clear();
510 SGPropertyNode_ptr devices = props->getChild("devices");
511 if (devices) {
512 for (auto d : devices->getChildren("device")) {
513 const auto t = simgear::strutils::strip(d->getStringValue());
514 info.devices.push_back(t);
515 }
516 }
517
518 return true;
519}
520
521bool GraphicsPresets::saveToXML(const SGPath& path, const std::string& name, const std::string& desc)
522{
523 SGPropertyNode_ptr presetXML(new SGPropertyNode);
524 presetXML->setStringValue("id", path.file_base()); // without .xml
525 presetXML->setStringValue("name", name);
526 presetXML->setStringValue("description", desc);
527
528 auto settingsNode = presetXML->getChild("settings", 0, true);
529
530 for (const auto& path : _propertiesToSave) {
531 auto srcNode = fgGetNode(path);
532 if (!srcNode || !srcNode->hasValue())
533 continue;
534
535 auto dstNode = settingsNode->getNode(path, true);
536 copyProperties(srcNode, dstNode);
537 }
538
539 try {
540 sg_ofstream os(path, std::ios::out | std::ios::trunc);
541 writeProperties(os, presetXML, true /*write all*/);
542 } catch (sg_exception& e) {
543 SG_LOG(SG_GENERAL, SG_ALERT, "Failed to save presets file to: " << path << "\nt\tFailed: " << e.getFormattedMessage());
544 return false;
545 }
546
547 return true;
548}
549
550// Register the subsystem.
551SGSubsystemMgr::Registrant<GraphicsPresets> registrantGraphicsPresets(
552 SGSubsystemMgr::DISPLAY);
553
554
555} // namespace flightgear
#define p(x)
SGPropertyNode * get_props()
Definition globals.hxx:320
monitor a collection of properties, and set a flag property to true when any of them are modified.
RequiredPropertyListener(const std::string &requiredProp, SGPropertyNode_ptr props)
bool applyCustomPreset(const SGPath &path)
void applyInitialPreset()
init() is called too late (after fgOSInit), so we call this method early, to load the initial preset ...
bool applyPresetByName(const std::string &name)
apply a preset identified by its (localized) name.
bool saveToXML(const SGPath &path, const std::string &name, const std::string &desc)
GraphicsPresetVec listPresets()
retrieve all standard presets which are defined
bool applyCurrentPreset()
Apply the settings defined in the current graphics preset, to the property tree.
void update(double delta_time_sec) override
std::vector< GraphicsPresetInfo > GraphicsPresetVec
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
Definition fg_props.cxx:556
FGGlobals * globals
Definition globals.cxx:142
std::vector< std::string > string_list
Definition globals.hxx:36
FlightGear Localization Support.
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
Definition Addon.cxx:53
SGSubsystemMgr::Registrant< GraphicsPresets > registrantGraphicsPresets(SGSubsystemMgr::DISPLAY)
static bool do_list_standard_presets(const SGPropertyNode *arg, SGPropertyNode *root)
static bool do_save_preset(const SGPropertyNode *arg, SGPropertyNode *root)
const char * name
void addSentryBreadcrumb(const std::string &, const std::string &)
static bool do_apply_preset(const SGPropertyNode *arg, SGPropertyNode *root)
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
Definition proptest.cpp:25
bool fgSetBool(char const *name, bool val)
Set a bool value for a property.
Definition proptest.cpp:24
bool fgSetString(char const *name, char const *str)
Set a string value for a property.
Definition proptest.cpp:26
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27
static const double pi
Definition sview.cxx:59