1 #include "FGQQWindowManager.hxx"
2
3 #include <algorithm>
4
5 #include <QAbstractListModel>
6 #include <QQmlComponent>
7 #include <QRectF>
8 #include <QSettings>
9
10 #include <simgear/props/props_io.hxx>
11
12 #include "QtHelpers.hxx"
13 #include <Main/globals.hxx>
14
15 ////////////////////////////////////////////////////////////////////////////
16
17 /**
18 * @brief information on all the dialogs / windows we have registered
19 */
20 struct DialogInfo {
DialogInfoDialogInfo21 DialogInfo(SGPropertyNode* node)
22 {
23 id = QString::fromStdString(node->getStringValue("id"));
24 if (id.isEmpty()) {
25 throw std::runtime_error("missing ID");
26 }
27
28 title = QString::fromStdString(node->getStringValue("title"));
29
30 SGPath p = globals->get_fg_root() / "gui" / "quick" / node->getStringValue("file");
31 if (!p.exists()) {
32 qWarning() << "missing dialog QML file: " << p;
33 }
34 resource = QUrl::fromLocalFile(QString::fromStdString(p.utf8Str()));
35 }
36
37 QString id;
38 QString title;
39 QUrl resource;
40 // flags?
41 };
42
43 ////////////////////////////////////////////////////////////////////////////
44
45 const int WindowContentUrl = Qt::UserRole + 1;
46 const int WindowSize = Qt::UserRole + 2;
47 const int WindowPosition = Qt::UserRole + 3;
48 const int WindowId = Qt::UserRole + 4;
49 const int WindowTitle = Qt::UserRole + 5;
50
51 /**
52 * @brief data about an active window
53 */
54 struct WindowData {
55 QString windowId;
56 QUrl source;
57 QRectF geometry;
58 QString title;
59
60 // frame flags (close, popout, etc)
61 };
62
63 class WindowsModel : public QAbstractListModel
64 {
65 public:
WindowsModel(FGQQWindowManager * wm)66 WindowsModel(FGQQWindowManager* wm) : QAbstractListModel(wm)
67 {
68 }
69
rowCount(const QModelIndex &) const70 int rowCount(const QModelIndex&) const override
71 {
72 return static_cast<int>(_windowData.size());
73 }
74
roleNames() const75 QHash<int, QByteArray> roleNames() const override
76 {
77 QHash<int, QByteArray> r;
78 r[WindowId] = "windowId";
79 r[WindowContentUrl] = "windowContentSource";
80 r[WindowSize] = "windowSize";
81 r[WindowPosition] = "windowPosition";
82 r[WindowTitle] = "windowTitle";
83
84 return r;
85 }
86
87 QVariant data(const QModelIndex& index, int role) const override;
88 bool setData(const QModelIndex& index, const QVariant& value, int role) override;
89
show(const DialogInfo & dinfo)90 void show(const DialogInfo& dinfo)
91 {
92 QSettings settings;
93 QRectF geom = settings.value(dinfo.id + "-geometry").toRectF();
94 if (!geom.isValid()) {
95 // fixme: define default window geometry
96 geom = QRectF{100, 100, 400, 400};
97 }
98
99 WindowData winfo = {dinfo.id, dinfo.resource, geom};
100
101 beginInsertRows({}, _windowData.size(), _windowData.size());
102 _windowData.push_back(winfo);
103 endInsertRows();
104 }
105
close(QString windowId)106 void close(QString windowId)
107 {
108 auto it = findWindow(windowId);
109 if (it == _windowData.end()) {
110 SG_LOG(SG_GUI, SG_DEV_WARN, "trying to close non-open window:" << windowId.toStdString());
111 return;
112 }
113
114 const int index = std::distance(_windowData.begin(), it);
115 beginRemoveRows({}, index, index);
116 _windowData.erase(it);
117 endRemoveRows();
118 }
119
findWindow(QString windowId) const120 std::vector<WindowData>::const_iterator findWindow(QString windowId) const
121 {
122 return std::find_if(_windowData.begin(), _windowData.end(),
123 [windowId](const WindowData& d) { return d.windowId == windowId; });
124 }
125
findWindow(QString windowId)126 std::vector<WindowData>::iterator findWindow(QString windowId)
127 {
128 return std::find_if(_windowData.begin(), _windowData.end(),
129 [windowId](const WindowData& d) { return d.windowId == windowId; });
130 }
131
132 std::vector<WindowData> _windowData;
133 };
134
data(const QModelIndex & index,int role) const135 QVariant WindowsModel::data(const QModelIndex& index, int role) const
136 {
137 if ((index.row() < 0) || (index.row() >= _windowData.size()))
138 return {};
139
140 const auto& d = _windowData.at(static_cast<size_t>(index.row()));
141 switch (role) {
142 case WindowId:
143 return d.windowId;
144 case WindowContentUrl:
145 return d.source;
146 case WindowSize:
147 return d.geometry.size();
148 case WindowPosition:
149 return d.geometry.topLeft();
150 case WindowTitle:
151 return d.title;
152
153 default: break;
154 }
155 return {};
156 }
157
setData(const QModelIndex & index,const QVariant & value,int role)158 bool WindowsModel::setData(const QModelIndex& index, const QVariant& value, int role)
159 {
160 if ((index.row() < 0) || (index.row() >= _windowData.size()))
161 return false;
162
163 auto& d = _windowData[static_cast<size_t>(index.row())];
164 QSettings settings;
165 if (role == WindowSize) {
166 d.geometry.setSize(value.toSizeF());
167 settings.setValue(d.windowId + "-geometry", d.geometry);
168 emit dataChanged(index, index, {role});
169 return true;
170 } else if (role == WindowPosition) {
171 d.geometry.setTopLeft(value.toPointF());
172 settings.setValue(d.windowId + "-geometry", d.geometry);
173 emit dataChanged(index, index, {role});
174 return true;
175 }
176
177 return false;
178 }
179
180 ////////////////////////////////////////////////////////////////////////////
181
182 class FGQQWindowManager::WindowManagerPrivate
183 {
184 public:
WindowManagerPrivate(FGQQWindowManager * outer)185 WindowManagerPrivate(FGQQWindowManager* outer) : _p(outer),
186 _windows(new WindowsModel{outer})
187 {
188 // scan dir to find built-in dialogs for now
189 SGPath p = globals->get_fg_root() / "gui" / "quick" / "dialogs.xml";
190 if (p.exists()) {
191 SGPropertyNode_ptr dialogProps(new SGPropertyNode);
192 readProperties(p, dialogProps);
193 for (auto nd : dialogProps->getChildren("dialog")) {
194 try {
195 DialogInfo dinfo(nd);
196 _dialogDefinitions.push_back(dinfo);
197 } catch (std::exception&) {
198 // skip this dialog
199 SG_LOG(SG_GUI, SG_DEV_WARN, "Skipped bad QML dialog definition");
200 }
201 }
202 } else {
203 SG_LOG(SG_GUI, SG_DEV_WARN, "No QML dialog definitions found");
204 }
205 }
206
207 FGQQWindowManager* _p;
208 WindowsModel* _windows;
209 std::vector<DialogInfo> _dialogDefinitions;
210 };
211
212 ////////////////////////////////////////////////////////////////////////////
213
FGQQWindowManager(QQmlEngine * engine,QObject * parent)214 FGQQWindowManager::FGQQWindowManager(QQmlEngine* engine, QObject* parent) : QObject(parent),
215 _d(new WindowManagerPrivate(this)),
216 _engine(engine)
217 {
218 // register commands to open / close / etc
219 }
220
~FGQQWindowManager()221 FGQQWindowManager::~FGQQWindowManager()
222 {
223 }
224
windows() const225 QAbstractItemModel* FGQQWindowManager::windows() const
226 {
227 return _d->_windows;
228 }
229
show(QString windowId)230 bool FGQQWindowManager::show(QString windowId)
231 {
232 // find QML file corresponding to windowId
233 auto it = std::find_if(_d->_dialogDefinitions.begin(),
234 _d->_dialogDefinitions.end(),
235 [windowId](const DialogInfo& dd) { return dd.id == windowId; });
236 if (it == _d->_dialogDefinitions.end()) {
237 SG_LOG(SG_GUI, SG_DEV_WARN, "Unknown dialog ID:" << windowId.toStdString());
238 return false;
239 }
240
241 _d->_windows->show(*it);
242 return true;
243 }
244
requestPopout(QString windowId)245 bool FGQQWindowManager::requestPopout(QString windowId)
246 {
247 return false;
248 }
249
requestClose(QString windowId)250 bool FGQQWindowManager::requestClose(QString windowId)
251 {
252 _d->_windows->close(windowId);
253 return true;
254 }
255
requestPopin(QString windowId)256 bool FGQQWindowManager::requestPopin(QString windowId)
257 {
258 return false;
259 }
260