Exposing enumerations to QML
As a consequence of werners comment in #96631: Expose text styles to plugins I dived a bit into the possibilities to expose enumerations to the plugin framework.
Point to be addressed with this technique:
- keep enum class definitions
- No changes to the current codebase required.
- typesafety and forward declaration available
- No manual code duplication
QML-exposed enum and internal enum class always in synch - separate name for each enum, both internal as exposed
CurrentlyMs::NoteHead::Group
as well asMs::NoteHead::Type
are exposed as NoteHead.VALUE. This means that while on the C++ side both Group and Type could have value NONE, which currently leads to a collision in the QML-exposed version. 'Fixing' this would mean pulling those enums out of the NoteHead class and give them a new name, which would most likely result inNoteHeadGroup
andNoteHeadType
as enum classes.
The 'magic' is this rather basic code-expansion Macro:
#define MS_QML_ENUM(name, storageType, ...)\ enum class name : storageType {\ __VA_ARGS__\ };\ class MSQE_##name {\ Q_GADGET\ Q_ENUMS(E)\ public:\ enum class E : storageType {\ __VA_ARGS__\ };\ };
Example of exposing the now existing PlaceText
enumeration:
MS_QML_ENUM(PlaceText, char,\ AUTO, \ ABOVE, \ BELOW, \ LEFT\ )
Obvious downside is having to put a backslash at the end of every valueline to keep the Macro going.
In addition to using the Macro (which only generates the enum class
and the QML class wrapper), you still have to do the exposing itself manually:
- In mscore.cpp - QQmlEngine* MScore::qml()
qmlRegisterUncreatableTypename>("MuseScore", 1, 0, "name", tr("You can't create an enumeration"))
Allows usage ofname.VALUE
from within a plugin as value - At the bottom of the file where you use this Macro
Q_DECLARE_METATYPE(Ms::MSQE_name::E);
Allows declaring Q_PROPERTY of the typeMs::MSQE_name::E
. Only useful if the next point is also done - In mscore.cpp - void MScore::init()
qRegisterMetaTypename::E>("name");
Allows usingMs::MSQE_name::E
as return type for theREAD
function and parameter type for theWRITE
function on a Q_PROPERTY of that type on the condition that you also declareQ_ENUMS(Ms::MSQE_name::E)
for that same Q_OBJECT
Q_INVOKABLES can't use the QML-wrapped enum type, nor the enum class type as parameters. Those should be from the correct storageType and static_casts should be applied when necessary. See also #93976: QML calls to functions with Enum parameters ignore the provided parameter
If there are no objections with this approach I'd like to use it to implement #96631: Expose text styles to plugins. I'd put the Macro in mscore.h, just above where the first enum class is defined currently.
Another upside is that having the Macro could make it rather easy to create #26796: automated documentation of Q_ENUMS for plugins needed
Comments
Your proposal looks good and seems to work well. I am just experimenting with some code to replace enums with a normal class. For example:
class Direction {
Q_GADGET
Q_ENUS(E)
int val;
public:
...
const char* toString() const;
static void fillComboBox(QComboBox*);
...
enum E { ATUO, UP, DOWN }
}
The use is similar to an scoped enum, you can write Direction a = Direction::UP; etc. The class can be expanded with functions for type conversion to char* and QVariant for use in xml read/write serialization and other nice stuff as iterating over the enum values, visualization in the gui etc.
In reply to Your proposal looks good and by [DELETED] 3
Wouldn't you still have to write
Direction a = Direction::E::UP
in C++ ? And you'd lose the compile-time reduction enabled by forward declaration.I do like the idea of including utility-methods in the wrapping class and I'm interested to see where your experimenting takes you.
As for my 'immediate' need of QML-exposure I'll go ahead and use the Macro approach for now. Thanks for your insight!
In reply to Wouldn't you still have to by jeetee
The enum E is a "normal" c++ enum, not a scoped enum so its really Direction a = Direction::Up. You are right with the impossible forward declaration :-(
I haven't had a closer look yet, but now that the codebase moved up to Qt 5.6 it could be useful to look into the new Q_ENUM macro (https://woboq.com/blog/q_enum.html).
Also, for reference, repeating the list of things we want to handle with a real solution: (from https://github.com/musescore/MuseScore/pull/2743#issuecomment-232343517)
I don't know if this post is outdated but I found this https://www.kdab.com/new-qt-5-8-meta-object-support-namespaces/ which seems to have a solution to expose enums to QML.
In reply to I don't know if this post is… by Soerboe
werner picked up on that for master; see also the MuseScore3 guidelines at https://github.com/musescore/MuseScore/blob/master/mscore3.txt#L149
Doesn't help for 2.x unfortunately
In reply to werner picked up on that for… by jeetee
Which is using Qt 5.6 and as such can't use this