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