FlightGear next
FGPUICompatDialog.cxx
Go to the documentation of this file.
1// SPDX-FileName: FGPUICompatDialog.cxx
2// SPDX-FileComment: XML dialog class without using PUI
3// SPDX-FileCopyrightText: Copyright (C) 2022 James Turner
4// SPDX-License-Identifier: GPL-2.0-or-later
5
6#include "GUI/dialog.hxx"
7#include "config.h"
8
9#include <utility>
10
11#include "FGPUICompatDialog.hxx"
12
13#include <simgear/debug/BufferedLogCallback.hxx>
14#include <simgear/nasal/cppbind/NasalObject.hxx>
15#include <simgear/props/props_io.hxx>
16#include <simgear/scene/tsync/terrasync.hxx>
17#include <simgear/structure/SGBinding.hxx>
18
19#include <Main/fg_os.hxx>
20#include <Main/fg_props.hxx>
21#include <Main/globals.hxx>
24
25#include "FGColor.hxx"
26#include "PUICompatObject.hxx"
27#include "new_gui.hxx"
28
29using namespace std::string_literals;
30
32
33// should really be exposed properly
34extern naRef propNodeGhostCreate(naContext c, SGPropertyNode* n);
35
36class FGPUICompatDialog::DialogPeer : public nasal::Object
37{
38public:
39 DialogPeer(naRef impl) : nasal::Object(impl)
40 {
41 }
42
44 {
45 _dialog = dlg;
46 }
47
48 SGSharedPtr<FGPUICompatDialog> dialog() const
49 {
50 return _dialog.lock();
51 }
52
53private:
54 // the Nasal peer does not hold an owning reference to the
55 // main dialog object (dialogs are owned by the NewGUI subsystem)
56 SGWeakPtr<FGPUICompatDialog> _dialog;
57};
58
59static naRef f_dialogModuleHash(FGPUICompatDialog& dialog, naContext c)
60{
61 auto nas = globals->get_subsystem<FGNasalSys>();
62 if (!nas) {
63 return naNil();
64 }
65
66 return nas->getModule(dialog.nasalModule());
67}
68
69naRef f_dialogRootObject(FGPUICompatDialog& dialog, naContext c)
70{
71 return nasal::to_nasal(c, dialog._root);
72}
73
74//----------------------------------------------------------------------------
75
76naRef f_makeDialogPeer(const nasal::CallContext& ctx)
77{
78 return ctx.to_nasal(SGSharedPtr<FGPUICompatDialog::DialogPeer>(
79 new FGPUICompatDialog::DialogPeer(ctx.requireArg<naRef>(0))));
80}
81
82naRef f_dialogCanResize(FGPUICompatDialog& dialog, naContext c)
83{
84 return nasal::to_nasal(c, dialog.isFlagSet(FGDialog::WindowFlags::Resizable));
85}
86
87void FGPUICompatDialog::setupGhost(nasal::Hash& compatModule)
88{
89 using NasalGUIDialog = nasal::Ghost<SGSharedPtr<FGPUICompatDialog>>;
90 NasalGUIDialog::init("gui.xml.CompatDialog")
91 .member("name", &FGPUICompatDialog::nameString)
93 .member("module", &f_dialogModuleHash)
94 .member("geometry", &FGPUICompatDialog::geometry)
95 .member("x", &FGPUICompatDialog::getX)
96 .member("y", &FGPUICompatDialog::getY)
97 .member("width", &FGPUICompatDialog::width)
98 .member("height", &FGPUICompatDialog::height)
99 .member("windowType", &FGPUICompatDialog::windowType)
100 .member("uiVersion", &FGPUICompatDialog::uiVersion)
101 .member("resizeable", f_dialogCanResize)
102 .member("root", f_dialogRootObject)
103 .method("close", &FGPUICompatDialog::requestClose);
104
105
106 using NasalDialogPeer = nasal::Ghost<SGSharedPtr<DialogPeer>>;
107 NasalDialogPeer::init("CompatDialogPeer")
108 .bases<nasal::ObjectRef>()
109 .method("dialog", &DialogPeer::dialog);
110
111 nasal::Hash dialogHash = compatModule.createHash("Dialog");
112 dialogHash.set("new", &f_makeDialogPeer);
113}
114
116 std::string translationDomain)
117 : FGDialog(props, std::move(translationDomain)),
118 _props(props),
119 _needsRelayout(false)
120{
121 _module = "__dlg:" + props->getStringValue("name", "[unnamed]");
122 _name = props->getStringValue("name", "[unnamed]");
123}
124
126{
127 // nothing to do, all work was done in close()
128}
129
131{
132 if (_peer) {
133 _peer->callMethod<void>("onClose");
134 }
135
136 _props->setIntValue("lastx", getX());
137 _props->setIntValue("lasty", getY());
138 // FIXME: save width/height as well?
139
140 auto nas = globals->get_subsystem<FGNasalSys>();
141 if (nas) {
142 if (_nasal_close) {
143 const auto s = _nasal_close->getStringValue();
144 nas->createModule(_module.c_str(), _module.c_str(), s.c_str(), s.length(), _props);
145 }
146 nas->deleteModule(_module.c_str());
147 }
148
149 _root->recursiveOnDelete();
150
151 _peer.clear();
152}
153
155{
156 _windowType = _props->getStringValue("type", "dialog");
157 _uiVersion = static_cast<uint32_t>(_props->getIntValue("ui-version", 0));
158
159 try {
160 auto nas = globals->get_subsystem<FGNasalSys>();
161
162 nasal::Context ctx;
163 nasal::Hash guiModule{nas->getModule("gui"), ctx};
164 if (guiModule.isNil()) {
165 throw sg_exception("Can't initialize PUICompat Nasal");
166 }
167
168 using SelfRef = SGSharedPtr<FGPUICompatDialog>;
169 using PeerRef = SGSharedPtr<DialogPeer>;
170
171 auto f = guiModule.get<std::function<PeerRef(std::string, SelfRef)>>("_createDialogPeer");
172 if (!f) {
173 SG_LOG(SG_GUI, SG_DEV_ALERT, "PUICompat module loaded incorrectly");
174 return false;
175 }
176
177 _peer = f(_windowType, SelfRef{this});
178 _peer->setDialog(this);
179 _peer->callMethod<void>("init", nas->wrappedPropsNode(_props));
180
181 SGPropertyNode* nasal = _props->getNode("nasal");
182 if (nasal && nas) {
183 _nasal_close = nasal->getNode("close");
184 SGPropertyNode* open = nasal->getNode("open");
185 if (open) {
186 const auto s = open->getStringValue();
187 nas->createModule(_module.c_str(), _module.c_str(), s.c_str(), s.length(), _props);
188 }
189 }
190 display(_props);
191 _peer->callMethod<void>("didBuild");
192 } catch (std::exception& e) {
193 SG_LOG(SG_GUI, SG_ALERT, "Failed to build dialog:" << e.what());
194
195 return false;
196 }
197
198 return true;
199}
200
202{
203 _peer->callMethod<void>("bringToFront");
204}
205
206void FGPUICompatDialog::runCallback(const std::string& name, SGPropertyNode_ptr args)
207{
208 auto nas = globals->get_subsystem<FGNasalSys>();
209 if (!nas)
210 return;
211
212 SGPropertyNode* nasalNode = _props->getNode("nasal");
213 if (!nasalNode)
214 return;
215
216 auto callbackNode = nasalNode->getChild(name);
217 if (!callbackNode) {
218 SG_LOG(SG_GUI, SG_DEV_ALERT, "FGPUICompatDialog::runCallback: no Nasal callback '" << name << "' defined on dialog " << _name);
219 return;
220 }
221
222 auto s = callbackNode->getStringValue();
223 auto fileName = _module.c_str();
224 nas->handleCommand(_module.c_str(), fileName, s.c_str(), args.get());
225}
226
228{
229 return _name.c_str();
230}
231
232void FGPUICompatDialog::updateValues(const std::string& objectName)
233{
234 _root->recursiveUpdateValues(objectName);
235}
236
237void FGPUICompatDialog::applyValues(const std::string& objectName)
238{
239 _root->recursiveApply(objectName);
240}
241
243{
244 _root->recursiveUpdate();
245
246 if (_needsRelayout) {
247 relayout();
248 }
249}
250
251void FGPUICompatDialog::display(SGPropertyNode* props)
252{
253 _root = PUICompatObject::createForType("group", _props);
254 _root->setDialog(this);
255 _root->init();
256
257 relayout();
258}
259
261{
262 _needsRelayout = false;
263
264 // map from physical to logical units for PUI
265 const double ratio = fgGetDouble("/sim/rendering/gui-pixel-ratio", 1.0);
266 const int physicalWidth = fgGetInt("/sim/startup/xsize"),
267 physicalHeight = fgGetInt("/sim/startup/ysize");
268 const int screenw = static_cast<int>(physicalWidth / ratio),
269 screenh = static_cast<int>(physicalHeight / ratio);
270#if 0
271 bool userx = _props->hasValue("x");
272 bool usery = _props->hasValue("y");
273 bool userw = _props->hasValue("width");
274 bool userh = _props->hasValue("height");
275#endif
276 int px, py, savex, savey;
277
278 const int pw = _props->getIntValue("width", -1);
279 const int ph = _props->getIntValue("height", -1);
280 px = savex = _props->getIntValue("x", (screenw - pw) / 2);
281 py = savey = _props->getIntValue("y", (screenh - ph) / 2);
282
283#if 0
284 // Negative x/y coordinates are interpreted as distance from the top/right
285 // corner rather than bottom/left.
286 if (userx && px < 0)
287 px = screenw - pw + px;
288 if (usery && py < 0)
289 py = screenh - ph + py;
290#endif
291
292 _geometry = SGRectd{static_cast<double>(px), static_cast<double>(py),
293 static_cast<double>(pw), static_cast<double>(ph)};
294 _peer->callMethod<void>("geometryChanged");
295}
296
298{
299 return _geometry.x();
300}
301
303{
304 return _geometry.y();
305}
306
308{
309 return _geometry.width();
310}
311
313{
314 return _geometry.height();
315}
316
318{
319 return _geometry;
320}
321
323{
324 return _name;
325}
326
328{
329 return _module;
330}
331
332void FGPUICompatDialog::requestClose()
333{
334 auto gui = globals->get_subsystem<NewGUI>();
335 gui->closeDialog(_name);
336}
337
338std::string FGPUICompatDialog::title() const
339{
340 if (_title.empty())
341 return _name;
342
343 const auto res = "dialog-"s + _name;
344 return FGTranslate(translationDomain()).get(res, _title);
345
346 return _title;
347}
348
349void FGPUICompatDialog::setTitle(const std::string& s)
350{
351 _title = s;
352 _peer->callMethod<void>("titleChanged");
353}
354
356{
357 return _root->widgetByName(name);
358}
naRef f_dialogRootObject(FGPUICompatDialog &dialog, naContext c)
naRef f_makeDialogPeer(const nasal::CallContext &ctx)
naRef f_dialogCanResize(FGPUICompatDialog &dialog, naContext c)
static naRef f_dialogModuleHash(FGPUICompatDialog &dialog, naContext c)
naRef propNodeGhostCreate(naContext c, SGPropertyNode *n)
SGSharedPtr< PUICompatObject > PUICompatObjectRef
Class for retrieving translated strings.
FGDialog(SGPropertyNode *props, std::string translationDomain="core")
Construct a new GUI widget configured by a property tree.
Definition dialog.cxx:45
std::string translationDomain() const noexcept
Return the translation domain of the dialog.
Definition dialog.cxx:61
bool isFlagSet(WindowFlags f) const
Definition dialog.cxx:93
@ Resizable
Definition dialog.hxx:80
naRef callMethod(naRef code, naRef self, int argc, naRef *args, naRef locals)
Definition NasalSys.cxx:331
naRef getModule(const std::string &moduleName) const
void setDialog(FGPUICompatDialog *dlg)
SGSharedPtr< FGPUICompatDialog > dialog() const
An XML-configured dialog box.
std::string nasalModule() const
void runCallback(const std::string &name, SGPropertyNode_ptr args) override
virtual const char * getName()
std::string nameString() const
friend naRef f_dialogRootObject(FGPUICompatDialog &dialog, naContext c)
const std::string & windowType() const
void update() override
Update state.
static void setupGhost(nasal::Hash &compatModule)
SGRectd geometry() const
std::string title() const
void setTitle(const std::string &s)
virtual void bringToFront()
FGPUICompatDialog(SGPropertyNode *props, std::string translationDomain="core")
Construct a new GUI widget configured by a property tree.
void relayout()
Recompute the dialog's layout.
friend naRef f_makeDialogPeer(const nasal::CallContext &ctx)
void close() override
Close the dialog.
uint32_t uiVersion() const
return the UI XML syntax version used by this dialog.
PUICompatObjectRef widgetByName(const std::string &name) const
find the dialog widget with the specified name, or nullptr.
virtual void applyValues(const std::string &objectName="")
Apply the values of all GUI objects with a specific name, or all if an empty name is given (default).
virtual ~FGPUICompatDialog()
Destructor.
virtual void updateValues(const std::string &objectName="")
Update the values of all GUI objects with a specific name, or all if an empty name is given (default)...
Class for retrieving translated strings.
std::string get(const std::string &resource, const std::string &basicId, int index=0) const
Get a single translation.
XML-configured GUI subsystem.
Definition new_gui.hxx:31
virtual bool closeDialog(const std::string &name)
Close a named dialog, if it is open.
Definition new_gui.cxx:369
static PUICompatObjectRef createForType(const std::string &type, SGPropertyNode_ptr config)
const char * name
int fgGetInt(const char *name, int defaultValue)
Get an int value for a property.
Definition fg_props.cxx:532
FGGlobals * globals
Definition globals.cxx:142
double fgGetDouble(const char *name, double defaultValue)
Get a double value for a property.
Definition proptest.cpp:30