You can influence the contents of the clipboard by using some of those cmds indeed, but we don't provide direct access to the clipboard contents, nor a way to adjust it directly with raw access.
I'm trying to write a plugin for working with isorhythms. What I want to do is copy a bar or two, and then "paste" either the rhythm or the pitch values. So I figured the easiest way was accessing the clipboard contents, and then using that as a template for the other bars.
The other way that I would do it is insert the rhythm or melody preceding the bars, and then apply it. That's what I did with my vowel-to-pitch plugin, but it becomes a bit tedious for isorhythm. I suppose that another way would be to use text marks in the score, and then scan the score and apply melody and rhythm based on text marks.
My approach with the current system would then be to make your plugin have 3 buttons (or shortcuts), once where you read in a selection (to substitute for the "copy" action) then have subsequent paste commands using the previously read in data.
Let me try to make my question a little more precise: How does the plugin get its shortcut information? Your solution seems sound, but I need to know how to ascertain what key invoked the script.
Yes, I do understand that the plugin needs to do the "copy" and "paste" itself. (Please give me some credit, I've been writing code for 40 years, and I've debugged memory overwrites in a multithreaded network processes.) And yes, I've been reading the code for the various plugins and the plugin documentation.
The MuseScore plugin interface starts with onRun: { }, and then the documentation goes into the score elements, etc. There doesn't appear to be an obvious way to get information about how the script was invoked.
So if a shortcut is adamant for this, I'd split the plugin into three separate plugins where the copy plugin would write its information into some kind of temporary "clipboard" file which is then read back in by the paste plugins.
You can then use the Plugin Manager to assign a specific shortcut command to each of the plugins.
Another approach would be to make a dockable plugin with three buttons, avoiding the need for a temporary file. But you'd then loose the possibility of assigning a shortcut to each function.
that is because plugins should not call Qt.quit(), but simply return. Docked plugins still disappear when they are closed. If people have been using Qt.quit() all along, it's a bad thing and it should be stated in the docs to avoid it, but there's nothing that could be done except rewriting a good part of the plugin engine.
How about you let things in memory, and simply handle how docks show up again when onRun is emitted? That way you never have to use Qt.quit(). You simply need to write a little function to hide your stuff and one to show it again when needed.
That's great for a plugin with no UI, but any dialog-type plugin will not close its dialog unless Qt.Quit() is called. I tried many devious techniques to try to make a dialog-type plugin hide its face from me, but succeeded not. When you've solved that, call the New York Times. Or at least post here.
There needs to be a QML API that means "shut down this plugin, esp its UI, not the QML world."
1) make a first dock-type plugin: here's the code. It never calls Qt.Quit()
run it, see that it works.
2) save it as test.qml under the plugins directory
3) plugin manager, activate your plugin
4) plugin creator, new plugin, exactly the same, but a green rectangle this time.
5) launch both plugins at the same time, they should never conflict one another.
Of course in this simple case, you can delete all the call to the visible property of the rectangle without changing the behaviour, but I put it there to show that more complex situations can be handled with the visible property.
Of course again, it is more tedious than using Qt.quit(), and there might be some situations where it is not possible (if you find one, call the New York Times. Or at least post here.)
Now, you say the problem is with dialog-type plugins, how are they different from dock-type plugins?
Here's the challenge. Make a dialog-type, not dock-type, plugin with a button "quit" that causes it to dismiss without calling Qt.quit(). I have been in the past unable to coerce dialog-type plugins to disappear by manipulating the "visible" property. The problem is that dialog plugins kill dock plugins.
That is because dialog-type plugins are virtually useless. Just use the QtQuick.dialogs thing instead.
import QtQuick 2.0
import QtQuick.Dialogs 1.2
import MuseScore 3.0
MuseScore {
menuPath: "Plugins.CallIt"
description: "Call the New York Times. Or at least post here"
version: "1.0"
requiresScore: false
onRun: dial.visible = true
Dialog {
id: dial
visible: false
title: "Blue sky dialog"
contentItem: Rectangle {
color: "skyblue"
implicitWidth: 100
implicitHeight: 100
Text {
anchors.centerIn: parent
text: "click to close"
}
MouseArea {
anchors.fill: parent
onClicked: {
dial.visible = false
}
}
}
}
}
Anyway, I get what you mean, you cannot programmatically close the internal Docks or Dialog. The solution for dialog type plugins is use QtQuick.dialogs, but there is no workaround (at least none I am aware of) for dock plugins. However, I can suggest to keep in mind that docks should not have to be closed programmatically, they are docked to be non annoying to the user.
Wait, what do you mean out of the window? I first read it like a metaphor (saying that dialog-type plugins were useless) and then read it litterally (the dialog shows up outside of the window, thus useless).
Oh, I'm sorry. "Window" is a term of art here, my fault! :) What I meant was, "Can any or all of the dialog layout tools, such as GridLayout, and named buttons with dynamic text, be used in these QuickDialogs, and if not, how do I layout a nontrivial and dynamic little UI such as the triller plugin's dialog?"
!!! Excuse me, I have to call the NYT and Washington Post. Haven't tried or diff'ed it yet, but I assume you have; if this is so, all the dialog-type plugins we have should be changed at once, no?
Yes, I didn't work on that. I thought the issue was about the layout.
I believe it shouldn't be more complicated than replacingQt.quit() with dialog.visible = false
I'll have a look though
That's what I did. I gave the Dialog an id "dialogo" and put "dialogo.visible=false" everywhere I had Qt.quit(). It does see to work, but you should try it, too.
This may be a complete success. The Plugin Creator doesn't "quit" the plugin, but I suppose that's correct. It does start up properly again when asked. You should check that, too.
Ok, New PR, with the necessary fixes. I am glad it helped, it really was a quick fix after all.
Remains a question for dmitrio: "Why did this dialog plugin type exist?"
Actually, it doesn't work at all. It fails to create a trill. I suppose we have not accounted for the Undo Stack mechanism. I suppose that the system undoes everything it does because it's not a dialog-type plugin. I have to call the New York Times now.
Yes I can, but it looks like it is the same in the old version (I posted a screencast on github) I have no idea if I'm using this plugin the right way.
The only problem is that the "OK" button doesn't belong there. "Apply" is what you're supposed to press to make it act and quit. And it does! Get rid of the OK button and we're done.
To test it, just enter a quarter note, click on it, invoke the plugin and say 8 or 10 for Schläge, hit apply, play it, and repeat and see if it deduced the number properly...and change it and repeat.
The whole interaction with the Undo system is changed; maybe it needs one less or one more StartCmd() or whatever it is, but it thinks it has the Undo Stack under its own control, and now obviously it does not. The RedNote plugin knows how to make a change that persists, though, without being a dialog plugin. So some serious redesign is needed here. See if you can reproduce my failure.
(This is the answer to the question in this comment)
Actually the answer is, it existed there historically at least in MuseScore 2.X times, therefore it was added to version 3 API as well. The practical difference with a self-crafted dialog is, with "dialog" type plugins the framework calls startCmd()/endCmd() for you while with your own dialog implementation you have to take care of this yourself (much like "dock" type plugins).
As for the Qt.quit() problem, I see at least three ways to resolve it:
1) Disable Qt.quit() at all, add a new API method which plugins would be required to call to terminate. This wouldn't work well with existing plugins though.
2) Disable Qt.quit(), add a new API method and add some heuristic to clean up plugins which probably have terminated their execution: for example, delete plugin objects after onRun() call for non-"dock" type plugins and require plugins to notify the framework if they require somewhat different lifecycle (like in case of plugins with their custom dialogs, like what is discussed here). This sounds like one of the best options as it would correctly dispose of unused plugins objects even if plugin author didn't specially handle that, but, again, this would perform not so well with existing plugins.
3) Use a separate QML engine instance for each plugin instance. This would naturally resolve the issue with overlapping Qt.quit() calls but will still make some plugin termination heuristics desirable: now plugin engines number will grow in time instead of just plugin instances. The advatage of this method is that we would probably need to do something like this anyway if we ever decide to implement something like permissions model for plugins.
Can we get jeetee and dimitrio95 involved in this? I think in the short term, upgrading the most popular (read "TempoChange") to use the new "Quick dialog" model is a fair stopgap measure. Without it, dock plugins aren't so great....
I've been looking into this today; prepare for a slightly lengthy reply :)
Why the dialog plugin type exists
Short answer
Historic raisins
Long answer
Dialog plugins were the original (and back then only) plugins. All actions performed by such a plugin were automatically wrapped into a single undo-stack option. A bit later the dockable type was added, and had to be specified; the default was still the dialog type plugin.
Still a bit later, the option was added to run plugins without a GUI. That was the time at which the dialog type became something you had to specify.
The main thing it still provides is the undo-stack wrapping (for all non-dockable plugins) and the inheritance of the application window color
What's the Qt.quit() issue?
Qt.quit()forces the underlying QmlEngine to emit its quit signal. In itself this isn't really an issue as it doesn't terminate the engine itself as far as I can tell.
But the intended use of that signal is to inform anyone that wants/needs to know about it that the engine is very likely being shut down.
To that end, it is imho correct that MuseScore connects that single quit signal from the engine to closing the dock and dialog plugins. They would otherwise become invalid and unhinged things in case the engine actually was quitting (such as at program shutdown).
On an additional note: I believe we should also bind the exit signal from the engine to closing all plugins as well.
side note
This means that if you create a dockable plugin that calls Qt.quit() (or likely even a non-gui one) all plugins will be closed as well. I've indeed succeeded in creating a dockable plugin with a button that kills off any other opened plugin as well.
The reason dockable plugins aren't affecting dialog plugins so visibly is two-fold:
(1) The chance that a dockable plugin calls quit while a dialog plugin is open is quite slim
(2) The chance that a dockable plugin calls quit at all is quite slim, as it is intended to stay open
Then what does happen when closing a dockable plugin?
The close button acts on the wrapping dockwidget object, not on the plugin itself; therefor Qt.quit isn't called.
Can we catch/disable Qt.quit from MuseScore?
Not really.
As the method isn't part of the QMLEngine itself (we already have our own subclass of that, so that could've been doable), catching it becomes quite hard. It lives in the core Qt QML Type itself. We could roll our own version of that, but it would require extra maintenance work. And even if we can subclass/create a wrapper class; it would very likely lead to a QML namespace conflict.
This would likely lead to plugins having to call a different function; and if that is the end result, then we could provide a different function with a lot less work on the MuseScore end.
What about rolling your own Dialog?
It's a decent workaround, but has some drawbacks:
(1) Additional wrapping code
(2) You must handle the undo-stack yourself as your now a non-GUI plugin
(3) When the engine quits, your plugin isn't closed as it should be. I'm unsure about the implications of this.
What's your proposal then, mr. smartypants?
Currently when MuseScore receives the quit signal, it calls close on the plugin (or its dock). So perhaps that is what our dialog plugin should do as well?
Dialog plugins
Give the plugin itself an id
Add import QtQuick.Window 2.2
When the plugin needs to close, use pluginId.parent.Window.window.close();
The clue for this approach was discovered by reading this stackoverflow answer
Theoretically, you should be able to call the Window.window attached property on any item within the window scene. By calling it on the pluginId.parent which is a QQuickRootItem (the contentItem of the QQuickView that is the plugin) the traversed path for the attached property reach the correct object should be minimized.
Dockable plugins
In my opinion, these simply shouldn't attempt to close themselves at all.
Moreover, the used workaround from above doesn't work for these. You can reach the QQuickView from the plugin, but calling close on it has no effect. Instead it should be called on the wrapping QDockWidget which is unreachable from within the QML itself.
So what now?
I propose we create an issue to update the default plugin template in MuseScore as well as any default plugins it delivers. It can't hurt to mention this in the documentation somewhere as well.
TempoChanges for v3 has been updated (reference commit), my other plugins naturally will follow in the coming days/weeks
There is still the case of plugins of the "default" type: right now the only way for them to be terminated is calling Qt.quit(). A natural way to resolve this issue seems to be destroying the plugin object after the onRun() code ends its execution but it will break existing plugins which use their own implementation of dialog interface.
Indeed, those should technically be killed of if their onRun returns.
The main question then would be why those need to resort to a plugin rolled by itself.
Counter question though: they do call Qt.quit, but as the engine isn't really quit at that point, are they terminated currently?
Well, after checking, they don't seem to be terminated. For some reason I thought they are. Then it seems Qt.quit() doesn't really do anything useful for plugins right now indeed. Some Plugin Creator logic depends on Qt.quit() but it can be adjusted without any harm to compatibility with old plugins.
May it make sense to have an API method which would allow to actually terminate a plugin then (as seems to be also proposed in the Telegram chat)? It would suite all plugin types (being not very useful for "dock" plugins though) and would be easier to use than the parent.Window.window.close() approach.
> May it make sense to have an API method which would allow to actually terminate a plugin then…? It would suite all plugin types (being not very useful for "dock" plugins though) and would be easier to use than the parent.Window.window.close() approach.
That's a great idea. It will keep plugins cleaner-looking and also more future-proof in case the preferred method of terminating needs to change in the future.
Hmm, the "Batch Convert" plugin is using Qt.Quit() only in one case, a case that shouldn't happen anyway (trying to run it on MuseScore 2), else it is just 'falling off the edge' of onRun.
The "ColorVoices" and "Notenames" plugins are not a "dialog" type. so not affected by this problem, or are they?
MuseScore's "ABC import", "Scorelist", "Scoreview" and HelloQML plugins would be affected though, being "dialog" type plugins.
Also the "Panel" and "Random2" plugin, being "dock" type plugins.
Is the upshot of this that "one-shot, no UI" plugins like RedNote (blushing child of ColorNotes), now modified not to call Qt.quit(), accumulate non-garbage-collectable detritus every time they are called? What actually happens when they are invoked a second time (today, other than visibly functioning correctly)?
My dialog plugins currently used Marr11317's "Dialog wrapper/visible=false" method. I gather this leaves garbage, while dialogID.Window.window.close() does not? It'd be best if I can continue operability prior to better API's..
Just confirmed this on 2.x by editing a plugin to create an array of a million strings in its onRun, and, yes, it leaks; massively if it’s a property var, minimally if it’s an onRun-local var.
Comments
It is not. We don't provide a clipboard wrapper class.
What is the goal you're trying to achieve? Perhaps there is another way.
In reply to It is not. We don't provide… by jeetee
not even via
cmd("copy")
etc.?In reply to not even via cmd("copy") etc… by Jojo-Schmitz
You can influence the contents of the clipboard by using some of those cmds indeed, but we don't provide direct access to the clipboard contents, nor a way to adjust it directly with raw access.
In reply to It is not. We don't provide… by jeetee
I'm trying to write a plugin for working with isorhythms. What I want to do is copy a bar or two, and then "paste" either the rhythm or the pitch values. So I figured the easiest way was accessing the clipboard contents, and then using that as a template for the other bars.
The other way that I would do it is insert the rhythm or melody preceding the bars, and then apply it. That's what I did with my vowel-to-pitch plugin, but it becomes a bit tedious for isorhythm. I suppose that another way would be to use text marks in the score, and then scan the score and apply melody and rhythm based on text marks.
In reply to I'm trying to write a plugin… by bmiller793
My approach with the current system would then be to make your plugin have 3 buttons (or shortcuts), once where you read in a selection (to substitute for the "copy" action) then have subsequent paste commands using the previously read in data.
In reply to My approach with the current… by jeetee
Thanks! Is there a docs page about that? Is this a "signals and slots" thing?
In reply to Thanks! Is there a docs… by bmiller793
No and no
In reply to Thanks! Is there a docs… by bmiller793
This is all the documentation there is about the plugin API along with these plugins to inspect to figure stuff out from others.
Note that when I said "paste" command, it means you having to write your own code to re-insert the information you've read in previously, Note by Note
In reply to This is all the… by jeetee
Let me try to make my question a little more precise: How does the plugin get its shortcut information? Your solution seems sound, but I need to know how to ascertain what key invoked the script.
Yes, I do understand that the plugin needs to do the "copy" and "paste" itself. (Please give me some credit, I've been writing code for 40 years, and I've debugged memory overwrites in a multithreaded network processes.) And yes, I've been reading the code for the various plugins and the plugin documentation.
The MuseScore plugin interface starts with onRun: { }, and then the documentation goes into the score elements, etc. There doesn't appear to be an obvious way to get information about how the script was invoked.
In reply to Let me try to make my… by bmiller793
It doesn't get shortcut/caller information.
So if a shortcut is adamant for this, I'd split the plugin into three separate plugins where the copy plugin would write its information into some kind of temporary "clipboard" file which is then read back in by the paste plugins.
You can then use the Plugin Manager to assign a specific shortcut command to each of the plugins.
Another approach would be to make a dockable plugin with three buttons, avoiding the need for a temporary file. But you'd then loose the possibility of assigning a shortcut to each function.
In reply to It doesn't get shortcut… by jeetee
Dockable plugins are completely broken. Any other plugin that calls Qt.quit() cancels them all. The paradigm is in need of deep thought.
In reply to Dockable plugins are… by [DELETED] 1831606
that is because plugins should not call
Qt.quit()
, but simplyreturn
. Docked plugins still disappear when they are closed. If people have been using Qt.quit() all along, it's a bad thing and it should be stated in the docs to avoid it, but there's nothing that could be done except rewriting a good part of the plugin engine.How about you let things in memory, and simply handle how docks show up again when
onRun
is emitted? That way you never have to useQt.quit()
. You simply need to write a little function to hide your stuff and one to show it again when needed.In reply to that is because plugins… by ecstrema
That's great for a plugin with no UI, but any dialog-type plugin will not close its dialog unless Qt.Quit() is called. I tried many devious techniques to try to make a dialog-type plugin hide its face from me, but succeeded not. When you've solved that, call the New York Times. Or at least post here.
There needs to be a QML API that means "shut down this plugin, esp its UI, not the QML world."
In reply to That's great for a plugin… by [DELETED] 1831606
Challenge accepted. Here it is:
1) make a first dock-type plugin: here's the code. It never calls Qt.Quit()
run it, see that it works.
2) save it as test.qml under the plugins directory
3) plugin manager, activate your plugin
4) plugin creator, new plugin, exactly the same, but a green rectangle this time.
5) launch both plugins at the same time, they should never conflict one another.
Of course in this simple case, you can delete all the call to the
visible
property of the rectangle without changing the behaviour, but I put it there to show that more complex situations can be handled with the visible property.Of course again, it is more tedious than using
Qt.quit()
, and there might be some situations where it is not possible (if you find one, call the New York Times. Or at least post here.)Now, you say the problem is with dialog-type plugins, how are they different from dock-type plugins?
In reply to Challenge accepted. Here it… by ecstrema
Oh, and if you absolutely want to destroy your UI, why not use
destroy
? https://doc.qt.io/qt-5/qtqml-javascript-dynamicobjectcreation.html#dele…In reply to Oh, and if you absolutely… by ecstrema
Here's the challenge. Make a dialog-type, not dock-type, plugin with a button "quit" that causes it to dismiss without calling Qt.quit(). I have been in the past unable to coerce dialog-type plugins to disappear by manipulating the "visible" property. The problem is that dialog plugins kill dock plugins.
In reply to Thanks - let me play with… by [DELETED] 1831606
That is because dialog-type plugins are virtually useless. Just use the QtQuick.dialogs thing instead.
Anyway, I get what you mean, you cannot programmatically close the internal Docks or Dialog. The solution for dialog type plugins is use QtQuick.dialogs, but there is no workaround (at least none I am aware of) for dock plugins. However, I can suggest to keep in mind that docks should not have to be closed programmatically, they are docked to be non annoying to the user.
In reply to That is because dialog-type… by ecstrema
!!! Where is this documented? I gather the whole button architecture goes out the window?
In reply to !!! Where is this… by [DELETED] 1831606
https://doc.qt.io/qt-5/qml-qtquick-dialogs-dialog.html
Indeed, I always found it sort of useless.
In reply to https://doc.qt.io/qt-5/qml… by ecstrema
Is GridLayout and all that just gone?
In reply to Is GridLayout and all that… by [DELETED] 1831606
It shouldn't
You should import QtQuick.Layouts $someversion$
Edit:
import QtQuick.Layouts 1.3
seems to workIn reply to !!! Where is this… by [DELETED] 1831606
Wait, what do you mean out of the window? I first read it like a metaphor (saying that dialog-type plugins were useless) and then read it litterally (the dialog shows up outside of the window, thus useless).
In reply to Wait, what do you mean out… by ecstrema
Oh, I'm sorry. "Window" is a term of art here, my fault! :) What I meant was, "Can any or all of the dialog layout tools, such as GridLayout, and named buttons with dynamic text, be used in these QuickDialogs, and if not, how do I layout a nontrivial and dynamic little UI such as the triller plugin's dialog?"
In reply to Oh, I'm sorry. "Window" is a… by [DELETED] 1831606
Here's your modified triller :)
You can diff it, you'll see only 4 lines changed.
In reply to Here's your modified triller… by ecstrema
!!! Excuse me, I have to call the NYT and Washington Post. Haven't tried or diff'ed it yet, but I assume you have; if this is so, all the dialog-type plugins we have should be changed at once, no?
In reply to Oh, I'm sorry. "Window" is a… by [DELETED] 1831606
PR created ;)
In reply to PR created ;) by ecstrema
Seems like it still calls Qt.quit(), no?
In reply to Seems like it still calls Qt… by [DELETED] 1831606
Yes, I didn't work on that. I thought the issue was about the layout.
I believe it shouldn't be more complicated than replacing
Qt.quit()
withdialog.visible = false
I'll have a look though
In reply to Yes, I didn't work on that… by ecstrema
That's what I did. I gave the Dialog an id "dialogo" and put "dialogo.visible=false" everywhere I had Qt.quit(). It does see to work, but you should try it, too.
In reply to That's what I did. I gave… by [DELETED] 1831606
This may be a complete success. The Plugin Creator doesn't "quit" the plugin, but I suppose that's correct. It does start up properly again when asked. You should check that, too.
In reply to This may be a complete… by [DELETED] 1831606
Ok, New PR, with the necessary fixes. I am glad it helped, it really was a quick fix after all.
Remains a question for dmitrio: "Why did this dialog plugin type exist?"
In reply to Ok, New PR, with the… by ecstrema
Also, Documentation about not using Qt.quit() should be added to the handbook. (And filtered?)
In reply to Also, Documentation about… by ecstrema
Did I review the right one? Does it work in repeated invocations from the menu (not the Creator) when installed?
@Jeetee should upgrade the popular TempoChanges plugin.
In reply to Did I review the right one? … by [DELETED] 1831606
yep, you reviewed the right one. It does work from repeated menu invocations.
In reply to yep, you reviewed the right… by ecstrema
Actually, it doesn't work at all. It fails to create a trill. I suppose we have not accounted for the Undo Stack mechanism. I suppose that the system undoes everything it does because it's not a dialog-type plugin. I have to call the New York Times now.
In reply to Actually, it doesn't work at… by [DELETED] 1831606
That's bad news, I doubt it's because of the undo stack. It's more probably because of a call to
parent
which is not the same anymore (hopes)In reply to That's bad news, I doubt it… by ecstrema
There's no call to parent that can cause that it seems... :(
In reply to There's no call to parent… by ecstrema
You can reproduce the failure, right?
In reply to You can reproduce the… by [DELETED] 1831606
Yes I can, but it looks like it is the same in the old version (I posted a screencast on github) I have no idea if I'm using this plugin the right way.
In reply to Yes I can, but it looks like… by ecstrema
The only problem is that the "OK" button doesn't belong there. "Apply" is what you're supposed to press to make it act and quit. And it does! Get rid of the OK button and we're done.
In reply to The only problem is that the… by [DELETED] 1831606
To test it, just enter a quarter note, click on it, invoke the plugin and say 8 or 10 for Schläge, hit apply, play it, and repeat and see if it deduced the number properly...and change it and repeat.
In reply to That's bad news, I doubt it… by ecstrema
Maybe it just needs parent.parent or something? Wanna look into it (since this new technology is your specialty)?
In reply to Maybe it just needs parent… by [DELETED] 1831606
I am looking into (and moving further discussion to the github PR)
In reply to Did I review the right one? … by [DELETED] 1831606
@jeetee is probably not the only one ;)
In reply to @jeetee is probably not the… by ecstrema
The whole interaction with the Undo system is changed; maybe it needs one less or one more StartCmd() or whatever it is, but it thinks it has the Undo Stack under its own control, and now obviously it does not. The RedNote plugin knows how to make a change that persists, though, without being a dialog plugin. So some serious redesign is needed here. See if you can reproduce my failure.
In reply to The whole interaction with… by [DELETED] 1831606
BSG look at that last comment's number (click on the time, and check the adress bar)
In reply to BSG look at that last… by ecstrema
WHOOOOOOOOA!
In reply to Ok, New PR, with the… by ecstrema
(This is the answer to the question in this comment)
Actually the answer is, it existed there historically at least in MuseScore 2.X times, therefore it was added to version 3 API as well. The practical difference with a self-crafted dialog is, with "dialog" type plugins the framework calls
startCmd()
/endCmd()
for you while with your own dialog implementation you have to take care of this yourself (much like "dock" type plugins).As for the
Qt.quit()
problem, I see at least three ways to resolve it:1) Disable
Qt.quit()
at all, add a new API method which plugins would be required to call to terminate. This wouldn't work well with existing plugins though.2) Disable
Qt.quit()
, add a new API method and add some heuristic to clean up plugins which probably have terminated their execution: for example, delete plugin objects afteronRun()
call for non-"dock" type plugins and require plugins to notify the framework if they require somewhat different lifecycle (like in case of plugins with their custom dialogs, like what is discussed here). This sounds like one of the best options as it would correctly dispose of unused plugins objects even if plugin author didn't specially handle that, but, again, this would perform not so well with existing plugins.3) Use a separate QML engine instance for each plugin instance. This would naturally resolve the issue with overlapping
Qt.quit()
calls but will still make some plugin termination heuristics desirable: now plugin engines number will grow in time instead of just plugin instances. The advatage of this method is that we would probably need to do something like this anyway if we ever decide to implement something like permissions model for plugins.In reply to Actually the answer is, it… by dmitrio95
Can we get jeetee and dimitrio95 involved in this? I think in the short term, upgrading the most popular (read "TempoChange") to use the new "Quick dialog" model is a fair stopgap measure. Without it, dock plugins aren't so great....
In reply to Can we get jeetee and… by [DELETED] 1831606
I've been looking into this today; prepare for a slightly lengthy reply :)
Why the dialog plugin type exists
Short answer
Historic raisins
Long answer
Dialog plugins were the original (and back then only) plugins. All actions performed by such a plugin were automatically wrapped into a single undo-stack option. A bit later the dockable type was added, and had to be specified; the default was still the dialog type plugin.
Still a bit later, the option was added to run plugins without a GUI. That was the time at which the dialog type became something you had to specify.
The main thing it still provides is the undo-stack wrapping (for all non-dockable plugins) and the inheritance of the application window color
What's the Qt.quit() issue?
Qt.quit() forces the underlying QmlEngine to emit its quit signal. In itself this isn't really an issue as it doesn't terminate the engine itself as far as I can tell.
But the intended use of that signal is to inform anyone that wants/needs to know about it that the engine is very likely being shut down.
To that end, it is imho correct that MuseScore connects that single quit signal from the engine to closing the dock and dialog plugins. They would otherwise become invalid and unhinged things in case the engine actually was quitting (such as at program shutdown).
On an additional note: I believe we should also bind the exit signal from the engine to closing all plugins as well.
side note
This means that if you create a dockable plugin that calls
Qt.quit()
(or likely even a non-gui one) all plugins will be closed as well. I've indeed succeeded in creating a dockable plugin with a button that kills off any other opened plugin as well.The reason dockable plugins aren't affecting dialog plugins so visibly is two-fold:
(1) The chance that a dockable plugin calls quit while a dialog plugin is open is quite slim
(2) The chance that a dockable plugin calls quit at all is quite slim, as it is intended to stay open
Then what does happen when closing a dockable plugin?
The close button acts on the wrapping dockwidget object, not on the plugin itself; therefor Qt.quit isn't called.
Can we catch/disable Qt.quit from MuseScore?
Not really.
As the method isn't part of the QMLEngine itself (we already have our own subclass of that, so that could've been doable), catching it becomes quite hard. It lives in the core Qt QML Type itself. We could roll our own version of that, but it would require extra maintenance work. And even if we can subclass/create a wrapper class; it would very likely lead to a QML namespace conflict.
This would likely lead to plugins having to call a different function; and if that is the end result, then we could provide a different function with a lot less work on the MuseScore end.
What about rolling your own Dialog?
It's a decent workaround, but has some drawbacks:
(1) Additional wrapping code
(2) You must handle the undo-stack yourself as your now a non-GUI plugin
(3) When the engine quits, your plugin isn't closed as it should be. I'm unsure about the implications of this.
What's your proposal then, mr. smartypants?
Currently when MuseScore receives the quit signal, it calls close on the plugin (or its dock). So perhaps that is what our dialog plugin should do as well?
Dialog plugins
import QtQuick.Window 2.2
pluginId.parent.Window.window.close();
The clue for this approach was discovered by reading this stackoverflow answer
Theoretically, you should be able to call the
Window.window
attached property on any item within the window scene. By calling it on thepluginId.parent
which is aQQuickRootItem
(the contentItem of the QQuickView that is the plugin) the traversed path for the attached property reach the correct object should be minimized.Dockable plugins
In my opinion, these simply shouldn't attempt to close themselves at all.
Moreover, the used workaround from above doesn't work for these. You can reach the QQuickView from the plugin, but calling close on it has no effect. Instead it should be called on the wrapping QDockWidget which is unreachable from within the QML itself.
So what now?
I propose we create an issue to update the default plugin template in MuseScore as well as any default plugins it delivers. It can't hurt to mention this in the documentation somewhere as well.
TempoChanges for v3 has been updated (reference commit), my other plugins naturally will follow in the coming days/weeks
In reply to I've been looking into this… by jeetee
There is still the case of plugins of the "default" type: right now the only way for them to be terminated is calling
Qt.quit()
. A natural way to resolve this issue seems to be destroying the plugin object after theonRun()
code ends its execution but it will break existing plugins which use their own implementation of dialog interface.In reply to There is still the case of… by dmitrio95
Indeed, those should technically be killed of if their onRun returns.
The main question then would be why those need to resort to a plugin rolled by itself.
Counter question though: they do call Qt.quit, but as the engine isn't really quit at that point, are they terminated currently?
In reply to Indeed, those should… by jeetee
Well, after checking, they don't seem to be terminated. For some reason I thought they are. Then it seems
Qt.quit()
doesn't really do anything useful for plugins right now indeed. Some Plugin Creator logic depends onQt.quit()
but it can be adjusted without any harm to compatibility with old plugins.May it make sense to have an API method which would allow to actually terminate a plugin then (as seems to be also proposed in the Telegram chat)? It would suite all plugin types (being not very useful for "dock" plugins though) and would be easier to use than the
parent.Window.window.close()
approach.In reply to Well, after checking, they… by dmitrio95
> May it make sense to have an API method which would allow to actually terminate a plugin then…? It would suite all plugin types (being not very useful for "dock" plugins though) and would be easier to use than the
parent.Window.window.close()
approach.That's a great idea. It will keep plugins cleaner-looking and also more future-proof in case the preferred method of terminating needs to change in the future.
In reply to There is still the case of… by dmitrio95
TIL that
Qt.quit();
even in default plugins has no effect (other than logging a warning about the signal), in 2.x and 3.x…… and all I wanted was a way to exit the
onRun
method early in case of errors.In reply to I've been looking into this… by jeetee
Hmm, the "Batch Convert" plugin is using
Qt.Quit()
only in one case, a case that shouldn't happen anyway (trying to run it on MuseScore 2), else it is just 'falling off the edge' ofonRun
.The "ColorVoices" and "Notenames" plugins are not a "dialog" type. so not affected by this problem, or are they?
MuseScore's "ABC import", "Scorelist", "Scoreview" and HelloQML plugins would be affected though, being "dialog" type plugins.
Also the "Panel" and "Random2" plugin, being "dock" type plugins.
In reply to Hmm, the "Batch Convert"… by Jojo-Schmitz
Is the upshot of this that "one-shot, no UI" plugins like RedNote (blushing child of ColorNotes), now modified not to call Qt.quit(), accumulate non-garbage-collectable detritus every time they are called? What actually happens when they are invoked a second time (today, other than visibly functioning correctly)?
My dialog plugins currently used Marr11317's "Dialog wrapper/visible=false" method. I gather this leaves garbage, while dialogID.Window.window.close() does not? It'd be best if I can continue operability prior to better API's..
In reply to Is the upshot of this that … by [DELETED] 1831606
Just confirmed this on 2.x by editing a plugin to create an array of a million strings in its
onRun
, and, yes, it leaks; massively if it’s aproperty var
, minimally if it’s anonRun
-local var.Answering to the initial thread, it should not be much to implement: https://stackoverflow.com/questions/40092352/passing-qclipboard-to-qml