1 /*
2 * Stellarium
3 * Copyright (C) 2006 Fabien Chereau
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18 */
19
20 #include <QDebug>
21 #include <QPluginLoader>
22 #include <QSettings>
23 #include <QDir>
24 #include <QRegularExpression>
25
26 #include "StelModuleMgr.hpp"
27 #include "StelApp.hpp"
28 #include "StelModule.hpp"
29 #include "StelFileMgr.hpp"
30 #include "StelPluginInterface.hpp"
31 #include "StelPropertyMgr.hpp"
32 #include "StelIniParser.hpp"
33 #include "StelLocaleMgr.hpp"
34 #include "StelUtils.hpp"
35
StelModuleMgr()36 StelModuleMgr::StelModuleMgr() : callingListsToRegenerate(true), pluginDescriptorListLoaded(false)
37 {
38 qRegisterMetaType<StelModule::StelModuleSelectAction>("StelModule::StelModuleSelectAction");
39 // Initialize empty call lists for each possible actions
40 callOrders[StelModule::ActionDraw]=QList<StelModule*>();
41 callOrders[StelModule::ActionUpdate]=QList<StelModule*>();
42 callOrders[StelModule::ActionHandleMouseClicks]=QList<StelModule*>();
43 callOrders[StelModule::ActionHandleMouseMoves]=QList<StelModule*>();
44 callOrders[StelModule::ActionHandleKeys]=QList<StelModule*>();
45 }
46
~StelModuleMgr()47 StelModuleMgr::~StelModuleMgr()
48 {
49 }
50
51 // Regenerate calling lists if necessary
update()52 void StelModuleMgr::update()
53 {
54 if (callingListsToRegenerate)
55 generateCallingLists();
56 callingListsToRegenerate = false;
57 }
58
59 /*************************************************************************
60 Register a new StelModule to the list
61 *************************************************************************/
registerModule(StelModule * m,bool fgenerateCallingLists)62 void StelModuleMgr::registerModule(StelModule* m, bool fgenerateCallingLists)
63 {
64 QString name = m->objectName();
65 if (modules.contains(name))
66 {
67 qWarning() << "Module" << name << "is already loaded.";
68 return;
69 }
70 modules.insert(name, m);
71 m->setParent(this);
72
73 //register with StelPropertyMgr
74 StelApp::getInstance().getStelPropertyManager()->registerObject(m);
75
76 if (fgenerateCallingLists)
77 generateCallingLists();
78 }
79
80 /*************************************************************************
81 Unregister and delete a StelModule.
82 *************************************************************************/
unloadModule(const QString & moduleID,bool alsoDelete)83 void StelModuleMgr::unloadModule(const QString& moduleID, bool alsoDelete)
84 {
85 StelModule* m = getModule(moduleID);
86 if (!m)
87 {
88 qWarning() << "Module" << moduleID << "is not loaded.";
89 return;
90 }
91 modules.remove(moduleID);
92 m->setParent(Q_NULLPTR);
93 callingListsToRegenerate = true;
94 if (alsoDelete)
95 {
96 m->deinit();
97 delete m;
98 }
99 }
100
101 /*************************************************************************
102 Get the corresponding module or Q_NULLPTR if can't find it.
103 *************************************************************************/
getModule(const QString & moduleID,bool noWarning) const104 StelModule* StelModuleMgr::getModule(const QString& moduleID, bool noWarning) const
105 {
106 StelModule* module = modules.value(moduleID, Q_NULLPTR);
107 if (module == Q_NULLPTR)
108 {
109 if (noWarning==false)
110 qWarning() << "Unable to find module called" << moduleID;
111 }
112 return module;
113 }
114
115
116 /*************************************************************************
117 Load an external plugin
118 *************************************************************************/
loadPlugin(const QString & moduleID)119 StelModule* StelModuleMgr::loadPlugin(const QString& moduleID)
120 {
121 for (const auto& desc : getPluginsList())
122 {
123 if (desc.info.id==moduleID)
124 {
125 Q_ASSERT(desc.pluginInterface);
126 StelModule* sMod = desc.pluginInterface->getStelModule();
127 qDebug() << "Loaded plugin" << moduleID;
128 pluginDescriptorList[moduleID].loaded=true;
129 return sMod;
130 }
131 }
132 qWarning() << "Unable to find plugin called" << moduleID;
133 return Q_NULLPTR;
134 }
135
loadExtensions(const QString & moduleID)136 QObjectList StelModuleMgr::loadExtensions(const QString &moduleID)
137 {
138 for (const auto& desc : getPluginsList())
139 {
140 if (desc.info.id==moduleID)
141 {
142 Q_ASSERT(desc.pluginInterface);
143 QObjectList exts = desc.pluginInterface->getExtensionList();
144 if(!exts.isEmpty())
145 {
146 extensions.append(exts);
147 emit extensionsAdded(exts);
148 qDebug() << "Loaded"<<exts.size()<<"extensions for"<<moduleID;
149 }
150
151 return exts;
152 }
153 }
154 qWarning() << "Unable to find plugin called" << moduleID;
155 return QObjectList();
156 }
157
158 struct StelModuleOrderComparator
159 {
StelModuleOrderComparatorStelModuleOrderComparator160 StelModuleOrderComparator(StelModule::StelModuleActionName aaction) : action(aaction) {;}
operator ()StelModuleOrderComparator161 bool operator()(StelModule* x, StelModule* y) {return x->getCallOrder(action)<y->getCallOrder(action);}
162 private:
163 StelModule::StelModuleActionName action;
164 };
165
166
167 // Unload all plugins
unloadAllPlugins()168 void StelModuleMgr::unloadAllPlugins()
169 {
170 QListIterator<PluginDescriptor> i(getPluginsList());
171 i.toBack();
172 while (i.hasPrevious())
173 {
174 const PluginDescriptor& d = i.previous();
175 if (d.loaded==false)
176 continue;
177 unloadModule(d.info.id, true);
178 qDebug() << "Unloaded plugin" << d.info.id;
179 }
180 // Call update now to make sure that all references to the now deleted plugins modules
181 // are removed (fix crashes at application shutdown).
182 update();
183 }
184
setPluginLoadAtStartup(const QString & key,bool b)185 void StelModuleMgr::setPluginLoadAtStartup(const QString& key, bool b)
186 {
187 QSettings* conf = StelApp::getInstance().getSettings();
188 conf->setValue("plugins_load_at_startup/"+key, b);
189 if (pluginDescriptorList.contains(key))
190 {
191 pluginDescriptorList[key].loadAtStartup=b;
192 }
193 }
194
isPluginLoaded(const QString & moduleID)195 bool StelModuleMgr::isPluginLoaded(const QString &moduleID)
196 {
197 if (pluginDescriptorList.contains(moduleID))
198 return pluginDescriptorList[moduleID].loaded;
199 else
200 return false;
201 }
202
203 /*************************************************************************
204 Generate properly sorted calling lists for each action (e,g, draw, update)
205 according to modules orders dependencies
206 *************************************************************************/
generateCallingLists()207 void StelModuleMgr::generateCallingLists()
208 {
209 // For each actions (e.g. "draw", "update", etc..)
210 for (auto mc = callOrders.begin(); mc != callOrders.end(); ++mc)
211 {
212 // Flush previous call orders
213 mc.value().clear();
214 // and init them with modules in creation order
215 for (auto* m : getAllModules())
216 {
217 mc.value().push_back(m);
218 }
219 std::sort(mc.value().begin(), mc.value().end(), StelModuleOrderComparator(mc.key()));
220 }
221 }
222
223 /*************************************************************************
224 Return the list of all the external module found in the modules/ directories
225 *************************************************************************/
getPluginsList()226 QList<StelModuleMgr::PluginDescriptor> StelModuleMgr::getPluginsList()
227 {
228 if (pluginDescriptorListLoaded)
229 {
230 return pluginDescriptorList.values();
231 }
232
233 // First list all static plugins.
234 // If a dynamic plugin with the same ID exists, it will take precedence on the static one.
235 for (auto* plugin : QPluginLoader::staticInstances())
236 {
237 StelPluginInterface* pluginInterface = qobject_cast<StelPluginInterface*>(plugin);
238 if (pluginInterface)
239 {
240 StelModuleMgr::PluginDescriptor mDesc;
241 mDesc.info = pluginInterface->getPluginInfo();
242 mDesc.pluginInterface = pluginInterface;
243 pluginDescriptorList.insert(mDesc.info.id, mDesc);
244 }
245 }
246
247 // Then list dynamic libraries from the modules/ directory
248 QSet<QString> moduleDirs;
249 moduleDirs = StelFileMgr::listContents("modules",StelFileMgr::Directory);
250
251 for (auto dir : moduleDirs)
252 {
253 QString moduleFullPath = QString("modules/") + dir + "/lib" + dir;
254 #ifdef Q_OS_WIN
255 moduleFullPath += ".dll";
256 #else
257 #ifdef Q_OS_MAC
258 moduleFullPath += ".dylib";
259 #else
260 moduleFullPath += ".so";
261 #endif
262 #endif
263 moduleFullPath = StelFileMgr::findFile(moduleFullPath, StelFileMgr::File);
264 if (moduleFullPath.isEmpty())
265 continue;
266
267 QPluginLoader loader(moduleFullPath);
268 if (!loader.load())
269 {
270 qWarning() << "Couldn't load the dynamic library:" << QDir::toNativeSeparators(moduleFullPath) << ": " << loader.errorString();
271 qWarning() << "Plugin" << dir << "will not be loaded.";
272 continue;
273 }
274
275 QObject* obj = loader.instance();
276 if (!obj)
277 {
278 qWarning() << "Couldn't open the dynamic library:" << QDir::toNativeSeparators(moduleFullPath) << ": " << loader.errorString();
279 qWarning() << "Plugin" << dir << "will not be open.";
280 continue;
281 }
282
283 StelPluginInterface* pluginInterface = qobject_cast<StelPluginInterface *>(obj);
284 if (pluginInterface)
285 {
286 StelModuleMgr::PluginDescriptor mDesc;
287 mDesc.info = pluginInterface->getPluginInfo();
288 mDesc.pluginInterface = pluginInterface;
289 pluginDescriptorList.insert(mDesc.info.id, mDesc);
290 }
291 }
292
293 // Load for each plugin if it should be loaded at startup
294 QSettings* conf = StelApp::getInstance().getSettings();
295 Q_ASSERT(conf);
296 conf->beginGroup("plugins_load_at_startup");
297 for (auto iter = pluginDescriptorList.begin(); iter != pluginDescriptorList.end(); ++iter)
298 {
299 bool startByDefault = iter.value().info.startByDefault;
300 iter->loadAtStartup = conf->value(iter.key(), startByDefault).toBool();
301 // Save the value in case no such key exists
302 conf->setValue(iter.key(), iter->loadAtStartup);
303 }
304 conf->endGroup();
305
306 pluginDescriptorListLoaded = true;
307 return pluginDescriptorList.values();
308 }
309
getStandardSupportLinksInfo(QString moduleName,bool furtherInfo)310 QString StelModuleMgr::getStandardSupportLinksInfo(QString moduleName, bool furtherInfo)
311 {
312 // Regexp to replace {text} with an HTML link.
313 const QRegularExpression a_rx("[{]([^{]*)[}]");
314 QString html;
315 html += "<h3>" + q_("Links") + "</h3>";
316 html += "<p>" + QString(q_("Support is provided via the Github website. Be sure to put \"%1\" in the subject when posting.")).arg(moduleName) + "</p>";
317 html += "<p><ul>";
318 // TRANSLATORS: The text between braces is the text of an HTML link.
319 html += "<li>" + q_("If you have a question, you can {get an answer here}.").toHtmlEscaped().replace(a_rx, "<a href=\"https://groups.google.com/forum/#!forum/stellarium\">\\1</a>") + "</li>";
320 // TRANSLATORS: The text between braces is the text of an HTML link.
321 html += "<li>" + q_("Bug reports and feature requests can be made {here}.").toHtmlEscaped().replace(a_rx, "<a href=\"https://github.com/Stellarium/stellarium/issues\">\\1</a>") + "</li>";
322 html += "</ul></p>";
323 if (furtherInfo)
324 {
325 QStringList ver = StelUtils::getApplicationVersion().split(".");
326 QString URL = QString("<a href=\"http://stellarium.org/doc/%1.%2/\">\\1</a>").arg(ver[0], ver[1]);
327 // TRANSLATORS: The text between braces is the text of an HTML link.
328 html += "<p>" + q_("Further information can be found in the {developer documentation}.").toHtmlEscaped().replace(a_rx, URL) + "</p>";
329 }
330
331 return html;
332 }
333