Exposing enumerations to QML

• Feb 25, 2016 - 00:32

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
    Currently Ms::NoteHead::Group as well as Ms::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 in NoteHeadGroup and NoteHeadType 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 of name.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 type Ms::MSQE_name::E. Only useful if the next point is also done
  • In mscore.cpp - void MScore::init()
    qRegisterMetaTypename::E>("name");
    Allows using Ms::MSQE_name::E as return type for the READ function and parameter type for the WRITE function on a Q_PROPERTY of that type on the condition that you also declare Q_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 by werner

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!

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)

  • the enum is accessible from plugins
  • We don't use cast, or anything that the compiler can't catch
  • QVariant<->Enum conversion without registerConverter
  • converting to/from text in write() and read properties,
  • uniform handling in inspector and style

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