1 //=============================================================================
2 // MuseScore
3 // Linux Music Score Editor
4 //
5 // Copyright (C) 2009-2012 Werner Schweer and others
6 //
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License version 2.
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., 675 Mass Ave, Cambridge, MA 02139, USA.
18 //=============================================================================
19
20 #include "musescore.h"
21 #include "config.h"
22 #include "preferences.h"
23 #include "qmlplugin.h"
24 #include "qmlpluginengine.h"
25 #include "pluginManager.h"
26
27 namespace Ms {
28
29 //---------------------------------------------------------
30 // registerPlugin
31 //---------------------------------------------------------
32
registerPlugin(PluginDescription * plugin)33 void MuseScore::registerPlugin(PluginDescription* plugin)
34 {
35 QString _pluginPath = plugin->path;
36 QFileInfo np(_pluginPath);
37 if (np.suffix() != "qml")
38 return;
39 QString baseName = np.completeBaseName();
40
41 foreach(QString s, plugins) {
42 QFileInfo fi(s);
43 if (fi.completeBaseName() == baseName) {
44 if (MScore::debugMode)
45 qDebug(" Plugin <%s> already registered", qPrintable(_pluginPath));
46 return;
47 }
48 }
49
50 QFile f(_pluginPath);
51 if (!f.open(QIODevice::ReadOnly)) {
52 if (MScore::debugMode)
53 qDebug("Loading Plugin <%s> failed", qPrintable(_pluginPath));
54 return;
55 }
56 if (MScore::debugMode)
57 qDebug("Register Plugin <%s>", qPrintable(_pluginPath));
58 f.close();
59 QObject* obj = 0;
60 QQmlComponent component(getPluginEngine(), QUrl::fromLocalFile(_pluginPath));
61 obj = component.create();
62 if (obj == 0) {
63 qDebug("creating component <%s> failed", qPrintable(_pluginPath));
64 foreach(QQmlError e, component.errors()) {
65 qDebug(" line %d: %s", e.line(), qPrintable(e.description()));
66 }
67 return;
68 }
69 //
70 // load translation
71 //
72 QLocale locale;
73 QString t = np.absolutePath() + "/translations/locale_" + MuseScore::getLocaleISOCode().left(2) + ".qm";
74 QTranslator* translator = new QTranslator;
75 if (!translator->load(t)) {
76 // qDebug("cannot load qml translations <%s>", qPrintable(t));
77 delete translator;
78 }
79 else {
80 // qDebug("load qml translations <%s>", qPrintable(t));
81 qApp->installTranslator(translator);
82 }
83
84 QmlPlugin* item = qobject_cast<QmlPlugin*>(obj);
85 QString menuPath = item->menuPath();
86 plugin->menuPath = menuPath;
87 plugins.append(_pluginPath);
88 createMenuEntry(plugin);
89
90 QAction* a = plugin->shortcut.action();
91 pluginActions.append(a);
92 connect(a, &QAction::triggered, mscore, [_pluginPath]() { mscore->pluginTriggered(_pluginPath); });
93
94 delete obj;
95 }
96
unregisterPlugin(PluginDescription * plugin)97 void MuseScore::unregisterPlugin(PluginDescription* plugin)
98 {
99 QString _pluginPath = plugin->path;
100 QFileInfo np(_pluginPath);
101 if (np.suffix() != "qml")
102 return;
103 QString baseName = np.completeBaseName();
104
105 bool found = false;
106 foreach(QString s, plugins) {
107 QFileInfo fi(s);
108 if (fi.completeBaseName() == baseName) {
109 found = true;
110 break;
111 }
112 }
113 if (!found) {
114 if (MScore::debugMode)
115 qDebug(" Plugin <%s> not registered", qPrintable(_pluginPath));
116 return;
117 }
118 plugins.removeAll(_pluginPath);
119
120
121 removeMenuEntry(plugin);
122 QAction* a = plugin->shortcut.action();
123 pluginActions.removeAll(a);
124
125 disconnect(a, 0, mscore, 0);
126 }
127
128 //---------------------------------------------------------
129 // createMenuEntry
130 // syntax: "entry.entry.entry"
131 //---------------------------------------------------------
132
createMenuEntry(PluginDescription * plugin)133 void MuseScore::createMenuEntry(PluginDescription* plugin)
134 {
135 QString menu = plugin->menuPath;
136 QStringList ml;
137 QString s;
138 bool escape = false;
139 foreach (QChar c, menu) {
140 if (escape) {
141 escape = false;
142 s += c;
143 }
144 else {
145 if (c == '\\')
146 escape = true;
147 else {
148 if (c == '.') {
149 ml += s;
150 s = "";
151 }
152 else {
153 s += c;
154 }
155 }
156 }
157 }
158 if (!s.isEmpty())
159 ml += s;
160
161 int n = ml.size();
162 QWidget* curMenu = menuBar();
163
164 for(int i = 0; i < n; ++i) {
165 QString m = ml[i];
166 bool found = false;
167 QList<QObject*> ol = curMenu->children();
168 foreach(QObject* o, ol) {
169 QMenu* cmenu = qobject_cast<QMenu*>(o);
170 if (!cmenu)
171 continue;
172 if (cmenu->objectName() == m || cmenu->title() == m) {
173 curMenu = cmenu;
174 found = true;
175 break;
176 }
177 }
178 if (!found) {
179 if (i == 0) {
180 curMenu = new QMenu(m, menuBar());
181 curMenu->setObjectName(m);
182 menuBar()->insertMenu(menuBar()->actions().back(), (QMenu*)curMenu);
183 if (MScore::debugMode)
184 qDebug("add Menu <%s>", qPrintable(m));
185 }
186 else if (i + 1 == n) {
187 QStringList sl = m.split(":");
188 QAction* a = plugin->shortcut.action();
189 QMenu* cm = static_cast<QMenu*>(curMenu);
190 if (sl.size() == 2) {
191 QList<QAction*> al = cm->actions();
192 QAction* ba = 0;
193 foreach(QAction* ia, al) {
194 if (ia->text() == sl[0]) {
195 ba = ia;
196 break;
197 }
198 }
199 a->setText(sl[1]);
200 cm->insertAction(ba, a);
201 }
202 else {
203 a->setText(m);
204 cm->addAction(a);
205 }
206
207 if (MScore::debugMode)
208 qDebug("plugins: add action <%s>", qPrintable(m));
209 }
210 else {
211 curMenu = ((QMenu*)curMenu)->addMenu(m);
212 if (MScore::debugMode)
213 qDebug("add menu <%s>", qPrintable(m));
214 }
215 }
216 }
217 }
218
219 //---------------------------------------------------------
220 // addPluginMenuEntries
221 //---------------------------------------------------------
222
addPluginMenuEntries()223 void MuseScore::addPluginMenuEntries()
224 {
225 for (int i = 0; i < pluginManager->pluginCount(); ++i) {
226 PluginDescription* d = pluginManager->getPluginDescription(i);
227 if (d->load)
228 createMenuEntry(d);
229 }
230 }
231
removeMenuEntry(PluginDescription * plugin)232 void MuseScore::removeMenuEntry(PluginDescription* plugin)
233 {
234 QString menu = plugin->menuPath;
235 QStringList ml;
236 QString s;
237 bool escape = false;
238 foreach (QChar c, menu) {
239 if (escape) {
240 escape = false;
241 s += c;
242 }
243 else {
244 if (c == '\\')
245 escape = true;
246 else {
247 if (c == '.') {
248 ml += s;
249 s = "";
250 }
251 else {
252 s += c;
253 }
254 }
255 }
256 }
257 if (!s.isEmpty())
258 ml += s;
259
260 if(ml.isEmpty())
261 return;
262
263 int n = ml.size();
264 QWidget* curMenu = menuBar();
265
266 for(int i = 0; i < n-1; ++i) {
267 QString m = ml[i];
268 QList<QObject*> ol = curMenu->children();
269 foreach(QObject* o, ol) {
270 QMenu* cmenu = qobject_cast<QMenu*>(o);
271 if (!cmenu)
272 continue;
273 if (cmenu->objectName() == m || cmenu->title() == m) {
274 curMenu = cmenu;
275 break;
276 }
277 }
278 }
279 QString m = ml[n-1];
280 QStringList sl = m.split(":");
281 QAction* a = plugin->shortcut.action();
282 QMenu* cm = static_cast<QMenu*>(curMenu);
283 cm->removeAction(a);
284 for(int i = n-2; i >= 0; --i) {
285
286 QMenu* cmenu = qobject_cast<QMenu*>(cm->parent());
287 if (cm->isEmpty())
288 delete cm;
289
290 cm = cmenu;
291 }
292 }
293
294 //---------------------------------------------------------
295 // pluginIdxFromPath
296 //---------------------------------------------------------
297
pluginIdxFromPath(QString _pluginPath)298 int MuseScore::pluginIdxFromPath(QString _pluginPath) {
299 QFileInfo np(_pluginPath);
300 QString baseName = np.completeBaseName();
301 int idx = 0;
302 foreach(QString s, plugins) {
303 QFileInfo fi(s);
304 if (fi.completeBaseName() == baseName)
305 return idx;
306 idx++;
307 }
308 return -1;
309 }
310
311 //---------------------------------------------------------
312 // loadPlugins
313 //---------------------------------------------------------
314
loadPlugins()315 void MuseScore::loadPlugins()
316 {
317 for (int i = 0; i < pluginManager->pluginCount(); ++i) {
318 PluginDescription* d = pluginManager->getPluginDescription(i);
319 if (d->load)
320 registerPlugin(d);
321 }
322 }
323
324 //---------------------------------------------------------
325 // unloadPlugins
326 //---------------------------------------------------------
327
unloadPlugins()328 void MuseScore::unloadPlugins()
329 {
330 for (int idx = 0; idx < plugins.size() ; idx++) {
331 ; // TODO
332 }
333 }
334
335 //---------------------------------------------------------
336 // loadPlugin
337 //---------------------------------------------------------
338
loadPlugin(const QString & filename)339 bool MuseScore::loadPlugin(const QString& filename)
340 {
341 bool result = false;
342
343 QDir pluginDir(mscoreGlobalShare + "plugins");
344 if (MScore::debugMode)
345 qDebug("Plugin Path <%s>", qPrintable(mscoreGlobalShare + "plugins"));
346
347 if (filename.endsWith(".qml")){
348 QFileInfo fi(pluginDir, filename);
349 if (!fi.exists())
350 fi = QFileInfo(preferences.getString(PREF_APP_PATHS_MYPLUGINS), filename);
351 if (fi.exists()) {
352 QString path(fi.filePath());
353 PluginDescription* p = new PluginDescription;
354 p->path = path;
355 p->load = false;
356 if (collectPluginMetaInformation(p))
357 registerPlugin(p);
358 result = true;
359 }
360 }
361 return result;
362 }
363
364 //---------------------------------------------------------
365 // pluginTriggered
366 //---------------------------------------------------------
367
pluginTriggered(int idx)368 void MuseScore::pluginTriggered(int idx)
369 {
370 if (plugins.size() > idx)
371 pluginTriggered(plugins[idx]);
372 }
373
pluginTriggered(QString pp)374 void MuseScore::pluginTriggered(QString pp)
375 {
376 QmlPluginEngine* engine = getPluginEngine();
377
378 QQmlComponent component(engine);
379 component.loadUrl(QUrl::fromLocalFile(pp));
380 QObject* obj = component.create();
381 if (obj == 0) {
382 foreach(QQmlError e, component.errors())
383 qDebug(" line %d: %s", e.line(), qPrintable(e.description()));
384 return;
385 }
386
387 QmlPlugin* p = qobject_cast<QmlPlugin*>(obj);
388 if(MuseScoreCore::mscoreCore->currentScore() == nullptr && p->requiresScore() == true) {
389 QMessageBox::information(0,
390 QMessageBox::tr("MuseScore"),
391 QMessageBox::tr("No score open.\n"
392 "This plugin requires an open score to run.\n"),
393 QMessageBox::Ok, QMessageBox::NoButton);
394 delete obj;
395 return;
396 }
397
398 if (p->pluginType() == "dock" || p->pluginType() == "dialog") {
399 QQuickView* view = new QQuickView(engine, 0);
400 view->setSource(QUrl::fromLocalFile(pp));
401 if (QmlPlugin* viewPluginInstance = qobject_cast<QmlPlugin*>(view->rootObject())) {
402 // a new plugin instance was created by view, use it instead.
403 delete p;
404 p = viewPluginInstance;
405 }
406 view->setTitle(p->menuPath().mid(p->menuPath().lastIndexOf(".") + 1));
407 view->setColor(QApplication::palette().color(QPalette::Window));
408 //p->setParentItem(view->contentItem());
409 //view->setWidth(p->width());
410 //view->setHeight(p->height());
411 view->setResizeMode(QQuickView::SizeRootObjectToView);
412 if (p->pluginType() == "dock") {
413 QDockWidget* dock = new QDockWidget(view->title(), 0);
414 dock->setAttribute(Qt::WA_DeleteOnClose);
415 Qt::DockWidgetArea area = Qt::RightDockWidgetArea;
416 if (p->dockArea() == "left")
417 area = Qt::LeftDockWidgetArea;
418 else if (p->dockArea() == "top")
419 area = Qt::TopDockWidgetArea;
420 else if (p->dockArea() == "bottom")
421 area = Qt::BottomDockWidgetArea;
422 QWidget* w = QWidget::createWindowContainer(view);
423 dock->setWidget(w);
424 addDockWidget(area, dock);
425 const Qt::Orientation orientation =
426 (area == Qt::RightDockWidgetArea || area == Qt::LeftDockWidgetArea)
427 ? Qt::Vertical
428 : Qt::Horizontal;
429 const int size = (orientation == Qt::Vertical) ? view->initialSize().height() : view->initialSize().width();
430 resizeDocks({ dock }, { size }, orientation);
431 connect(engine, SIGNAL(quit()), dock, SLOT(close()));
432 dock->show();
433 }
434 else {
435 connect(engine, SIGNAL(quit()), view, SLOT(close()));
436 view->show();
437 }
438 }
439
440 connect(engine, &QmlPluginEngine::endCmd, p, &QmlPlugin::endCmd);
441
442 p->setFilePath(pp.section('/', 0, -2));
443
444 // don’t call startCmd for non modal dialog
445 if (cs && p->pluginType() != "dock")
446 cs->startCmd();
447 p->runPlugin();
448 if (cs && p->pluginType() != "dock")
449 cs->endCmd();
450 // endCmd();
451 }
452
453 //---------------------------------------------------------
454 // collectPluginMetaInformation
455 /// returns false if loading a plugin for the given
456 /// description has failed
457 //---------------------------------------------------------
458
collectPluginMetaInformation(PluginDescription * d)459 bool collectPluginMetaInformation(PluginDescription* d)
460 {
461 qDebug("Collect meta for <%s>", qPrintable(d->path));
462
463 QQmlComponent component(mscore->getPluginEngine(), QUrl::fromLocalFile(d->path));
464 QObject* obj = component.create();
465 if (obj == 0) {
466 qDebug("creating component <%s> failed", qPrintable(d->path));
467 foreach(QQmlError e, component.errors()) {
468 qDebug(" line %d: %s", e.line(), qPrintable(e.description()));
469 }
470 return false;
471 }
472 QmlPlugin* item = qobject_cast<QmlPlugin*>(obj);
473 const bool isQmlPlugin = bool(item);
474 if (item) {
475 d->version = item->version();
476 d->description = item->description();
477 }
478 delete obj;
479 return isQmlPlugin;
480 }
481 }
482
483