QML coding: can't initialize dialog of dialog-type plugin

• Jul 27, 2019 - 01:26

I have written a dialog-type plugin which works perfectly in the plugin creator, but not when run from the plugin menu. What seems to be happening in the latter case is that it ignores the data that my onRun() has placed in the mainLayout dialog, and displays it with default values. I can prove that the onRun is running, but there seems to be no way it can convey data to the dialog or the button handlers, although it does so perfectly when running in the Plugin creator. I have written a plugin which putatively looks at stuff and offers to you to change it, but seemingly can't. What am I missing?

The enclosed minimal test case shows "RIGHT" when run under the Creator, and "WRONG" when run from the menu. (In Qt 5.9.4).

Attachment Size
testdlg.qml 916 bytes


Confirmed in MuseScore 3.2.3 on Windows, Qt 5.12.2

Other dialog type plugins don't try to do that, like TempoChanges. Others that use dialogs are not being declared as being dialog type, like batch_convert and are using that mechanism to set up things in onRun().

BTW, that

         text: qsTranslate("PrefsDialogBase", "Cancel")

won't work, there is no such string in that context. Try e.g.

         text: qsTranslate("InsertMeasuresDialogBase", "Cancel")

instead. And

         text: qsTranslate("Ms::MuseScore", "Right or wrong") + ":"

won't work at all, as there is no such string at all anywhere in MuseScore

In reply to by Jojo-Schmitz

Thank you for looking into this for me.

Is there rhyme or reason or, heaven forbid, documentation, on how to do this (qsTr) properly? What if InsertMeasuresDialogBase goes away or changes name some day, as the string I was using already did? I just copied it from some other plugin (which must thus be broken, too).

Reading what you said, is it thus that there is just no way that a plugin can set stuff up from the score and then ask for input? This is a "fairly severe limitation" if so. I just want to say "the current value is 200; what would you like it to be?" Not crazy, no? I know that Tempo Changes dialog puts up values (e.g., last linear/curved setting) from "Settings" presumably stored in some persistent place outside the plugin. Is there just no way, or do I not know enough? Should there be a way? Ought something be re-engineered or added in the MS plugin framework?

MS QML plugin writing needs a book.

In reply to by [DELETED] 1831606

Could it fairly be described as a bug that the dialog-initializations of onRun are ignored? It was also discovered fortuitously that "property" plugin-wide variables set by onRun are also overwritten. onRun for a dialog plugin apparently can do nothing except check stuff and complain if not OK (and this mechanism is buggy too, subject of forthcoming report).

In reply to by [DELETED] 1831606

If you're referencing other translations, you're alway at risk to point to nowhere eventually. Same as with symbolicg links in Linux/UNIX/maxOS, they can point to something non-existing.
For "Cancel", "OK" and the likes pointing to the corresponding Qt resource might be the saver bet and lay the translation into the hands and responsibility of the Qt developers.
I believe "QPlatformTheme" might be the context to use and indepentant of MuseScore's translation.
For that other string you need to translate it for that plugin, Check how other plugins are doing this.

In reply to by Jojo-Schmitz

What is the purpose of the qsTr(anslate), anyway? What does that operand mean, and how should it be chosen? Anyway, that is a very small problem because the fixed strings appear correctly in spite of it. That "I want to write a plugin that shows something and offers to change it" seems beyond the state of the art is a much bigger problem. My current work-around is "cute", you have to press a button, "show current value", but inane; no device should have a "please press me" button.

In reply to by [DELETED] 1831606

qsTr("string") is for a string to be translated allong with this plugin, extract with lupdate (into translation/locale_*.ts files), translate (manually editing those ts files or using Qt Linguist), release using lrelease (or Qt Linguist) (generates translations/locale_*.qm files)
`qsTranslate("context", "string") merely refers to a string from another context and pulls its translation (if available) from there

In reply to by Jojo-Schmitz

I see, language translation. So I have to find a string somewhere which is close enough to what I want and name the place where it's used. What happens if I have to use a new string? I guess I have to learn how to say it in Thai, Polish, and Malayalam. I could probably use a per-mille symbol instead of a string for my input-field label...but it is indeed a high bar for adding controls or diagnostic messages.

In reply to by [DELETED] 1831606

"close enough" is not close enough, needs to be 100% identical.
And you don't need to do translations yourself, but just ask for help on those and accept PRs to your plugin.

Like I do only the German translation to my plugins, jeetee does the Dutch ones for his, and we help one another, so I do the German one for his and he the Dutch ones for mine.

It gets easier if you can refer to existing strings, in which case no translation is needed, as you just take the work done by others. I'm using that exessively in the notenames plugin for example.

Using qsTr("String") just serves as the infrastructure for translations, the i18n part, the translation then is the l11n part.

In reply to by Jojo-Schmitz

What I meant by "close enough" is "close enough in meaning", e.g., "see" vs "view". Obviously lexical "closeness" is silly! However, "cow" in context 1 might not be the same as "cow" in context 2 (nod to S.I. Hayakawa). Even lexical identity might not be enough! I've seen localization files in plugin zipfiles -- how does that work?

In reply to by [DELETED] 1831606

If yoiu want your plugin being available in different laguages and cannot source the strungs to translated from Qt or MuseScore, you got to have these 'private' translation files. The notenames plugin doesn't need them (except of maybe 1 or 2 strings), the batch_export plugin does need them for all strings (well, the latest version only for most of the strings), the TempoChanges needs it for some strings but not all. Therse plugin also come with batch files to generated these files. For Mac those would need to get turned into shell scripts, but that isn't rocket science, each of the 2 batch files is basically just one comment plus its arguments.

It turns out that this is a bug in MuseScore back from 2013 which causes run() signal being emitted in a wrong instance of a plugin so some UI gets updated but not the one that is visible after launching a plugin from a menu. This issue has also been reported in the tracker, see:
#72416: Plug-in code working differently when called directly and when run under Plugin Creator
#97316: Investigate differences between plugins ran from the Creator and the Menu

This is easy to correct so I'll push a fix for it a bit later. As an immediate workaround, you may consider putting your initialization code into Component.onCompleted instead of onRun as it was done, for instance, here. Component.onCompleted is executed in every plugin instance so the correct instance will get its UI updated by that code as well.

In reply to by [DELETED] 1831606

I was out of electrical civilization over the weekend, hence the late reply/acknowledgement.

As you by now know, I've experienced similar issues back in 2016 (wow, time does fly, even when having less fun as one would like..). My issue was noticed especially with dynamically trying to load combobox/dropdown contents for a plugin of the type dialog during work on the UltraStar Exporter plugin.
The solution there was to not only move most dialog initialization into onCompleted instead of onRun but I even had to resort to creating a QML dialog object myself rather than use the MuseScore dialog type one.

Now that it's on Dmitri's list we're bound to slowly, but steadily, gain an increasingly stable and useful framework in place.

In reply to by jeetee

The OnRun() event is a MuseScore plugin creation that is fired by the C++ code after providing the QML code to a Component. Other than that it was no relationship with things going on within the QML model.

So doing everything from OnRun() or nothing in OnRun() (using Component.onCompleted) seems safest to ensure you're working in the same code and time line.

At least that's what I see in the C++ code. If QML engine processing does things asynchronously you aren't guaranteed OnRun occurs before QML loading or object instantiation, or... whatever else. OnRun needs some kind of interlock with the QML Component stuff.


In reply to by DLLarson

That's bizarre. I wonder, then, what is the right time and place to make a check that prevents the dialog from coming up at all (right now, in error cases, the dialog comes up, but is immediately shadowed by an error box, which, when dismissed, closes all cleanly nevertheless).

In reply to by [DELETED] 1831606

Event driven programming can be challenging. It's a matter of understating the event model of the framework. That's why things like Component.onCompleted exist. When it's called you know it has completed its setup in this case.

Perhaps there's a Component.onCompleted at the MuseScore object level?

Pretty much all GUI's are event driven so it would be bizarre to not do it that way. The issue here is specifically OnRun.


In reply to by DLLarson

Just some stuff to chew on...

I've attached a new version of BSG's testdlg.qml for testing.

Here's the order of event calls when running the attached testdlg-v2.qml from the menu...

qml: MuseScore-Component.onCompleted called.
qml: GridLayout-Component.onCompleted called.
qml: MuseScore-Component.onCompleted called.
qml: GridLayout-Component.onCompleted called.
qml: MuseScore.onRun Called.
qml: hello test dlg

I don't know why the Components are called twice before MuseScore.onRun is called.

Here's the order of event calls when running testdlg-v2.qml from Plugin Creator...

In the Creator console I see these:

Plugin Details:
  Menu Path: Plugins.TestDlg
  Version: 3.0
  Description: This plugin tests dialog initializaiton
  Requires Score
Debug: MuseScore.onRun Called.
Debug: hello test dlg

But the Component.onCompleted console.log calls show up on the debugger console (when I debug the release application.)...

qml: MuseScore-Component.onCompleted called.
qml: GridLayout-Component.onCompleted called.

I don't know why that is.

Weird results regardless the approach.


Attachment Size
testdlg-v2.qml 1.15 KB

In reply to by DLLarson

From the QML docs...


Emitted after the object has been instantiated. This can be used to execute script code at startup, once the full QML environment has been established.

The corresponding handler is onCompleted. It can be declared on any object. The order of running the onCompleted handlers is undefined.

It's notable that the "The order of running the onCompleted handlers is undefined."

In reply to by [DELETED] 1831606

The actual problem is that you can't look at this as Javascript programming per se. It's QML: Quick Markup Language or Qt Modeling Language depending where you look programming.

Although it uses Javascript to perform complicated actions and computations, it's still mostly QML.

We have a miss-match of skills.

Given that the UI's for MuseScore plugin's are pretty simple, it seems boilerplate code is needed for the three different models: Dock, Dialog, and no-UI. Then a non-QML but sorta Javascript programmer can not feel lost when a UI is involved.

But still, to truly make it sing, a person would need to understand QML in more detail.


In reply to by [DELETED] 1831606

There's also a third level, of course, which is neither Javascript nor QML, but the specifics that MuseScore expects of plugins, the APIs and objects it exposes, and the way common tasks are expected to be expressed. It is not at all simple; I suppose only skilled professional programmers like ourselves can deal with such high piles of stuff capably.

Do you still have an unanswered question? Please log in first to post your question.