2

I'm passing a shared_ptr<Track> to a QtQuick2ApplicationViewer via setContextProperty. The passed value (inputStream) is then assigned to a property of PianoRoll, a custom QQuickItem which has a property stream of type shared_ptr<Track>.

I get the following error message:

PianoRollDemo.qml:10: unable to assign shared_ptr<Track> to [unknown property type]

main.cpp

Q_DECLARE_METATYPE(shared_ptr<Track>)
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    // qmlRegisterType<Track>("Track", 1, 0, "Track");
    qmlRegisterType<PianoRoll>("PianoRoll", 1, 0, "PianoRoll");

    QtQuick2ApplicationViewer viewer;
    viewer.setMainQmlFile(QStringLiteral("qml/Diplomarbeit/PianoRollDemo.qml"));
    viewer.showExpanded();

    if (argc >= 2)
    {
        if (strcmp(argv[1],"-readFile") == 0)
        {
            string fileName = argv[2];
            cout << "Reading from file " << fileName << endl;
            GP5Reader reader;
            MainGame mainGame;
            reader.read(mainGame.score, fileName);

            mainGame.playerInputs.push_back(shared_ptr<Track>(new Track(0)));
            mainGame.onPlayerInput(0, shared_ptr<Note>(new Note(295309, 100, 69, 92)));
            mainGame.onPlayerInput(0, shared_ptr<Note>(new Note(295306, 100, 64, 92)));
            mainGame.onPlayerInput(0, shared_ptr<Note>(new Note(295300, 100, 57, 92)));
            mainGame.onPlayerInput(0, shared_ptr<Note>(new Note(295315, 100, 45, 92)));
        }

        else if(strcmp(argv[1],"-listenToMidi") == 0)
        {
            int port = atoi(argv[2]);
            cout << "Listening to port " << port << endl;
            MidiInput* midiIn = new MidiInput();
            midiIn->listen(port);
            viewer.rootContext()->setContextProperty("inputStream", QVariant::fromValue(midiIn->track));
            /*
            QDeclarativeView view;
            QUrl url = QUrl::fromLocalFile("qml/Diplomarbeit/PianoRollDemo.qml");
            bool valid = url.isValid();
            view.setSource(url);
            view.show();
            */
        }
    }
    else
    {
        cout << "No arguments received." << endl;
    }

    #ifdef Q_OS_ANDROID
        GP5Reader reader;
        Score score;
        reader.read(score, "/storage/emulated/0/test.gp5");
    #endif

    return app.exec();
}

pianoroll.h

#ifndef PIANOROLL_H
#define PIANOROLL_H

#include <QQuickItem>
#include <QSGGeometry>
#include <QSGFlatColorMaterial>

#include <track.h>


class PianoRoll : public QQuickItem
{
    Q_OBJECT
    Q_PROPERTY(shared_ptr<Track> stream READ stream WRITE setStream NOTIFY streamChanged)

public:
    PianoRoll(QQuickItem* parent = 0);

    // Get Methods
    shared_ptr<Track> stream() const;

    // Set Methods
    void setStream(shared_ptr<Track> stream);

protected:
    QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *data);

private:
    shared_ptr<Track> m_stream;
    QSGGeometry m_geometry;
    QSGFlatColorMaterial m_material;

signals:
    void streamChanged();
};

QML_DECLARE_TYPE(PianoRoll)

#endif // PIANOROLL_H

pianoroll.cpp

#include <QQuickItem>
#include <QSGGeometry>
#include <QSGFlatColorMaterial>
#include <QSGGeometryNode>
#include <QSGSimpleRectNode>

#include <iostream>
#include <memory>

#include "pianoroll.h"
#include "note.h"

using namespace std;


PianoRoll::PianoRoll(QQuickItem *parent) :
    QQuickItem(parent),
    m_geometry(QSGGeometry::defaultAttributes_Point2D(), 4),
    m_stream(new Track)
{
    // Important, otherwise the paint method is never called
    setFlag(ItemHasContents);
    m_material.setColor(Qt::red);

    // TODO: remove TestData
    shared_ptr<Note> noteOn1(new Note(0, 500, 70, 100));
    shared_ptr<Note> noteOn2(new Note(500, 500, 40, 100));
    shared_ptr<Note> noteOn3(new Note(1000, 200, 30, 100));
    m_stream->addNote(noteOn1);
    m_stream->addNote(noteOn2);
    m_stream->addNote(noteOn3);
    //m_stream.addNoteEvent(new NoteEvent(700, 700, 70, 100));
}


shared_ptr<Track> PianoRoll::stream() const
{
    return m_stream;
}


void PianoRoll::setStream(shared_ptr<Track> stream)
{
    if (m_stream == stream) return;
    m_stream = stream;
    emit streamChanged();
    update();
}

QSGNode *PianoRoll::updatePaintNode(QSGNode *n, QQuickItem::UpdatePaintNodeData *data)
{
    QSGGeometryNode *node = static_cast<QSGGeometryNode *>(n);
    if (!node)
    {
        node = new QSGSimpleRectNode(boundingRect(), Qt::white);
    }

    node->removeAllChildNodes();

    qreal msPerScreen = 10000;
    qreal pitchesPerScreen = 128;
    qreal x_factor = (qreal) boundingRect().width() / msPerScreen;
    qreal y_factor = (qreal) boundingRect().height() / pitchesPerScreen;

    for (unsigned int i = 0; i < m_stream->notes.size(); i++)
    {
        shared_ptr<Note> note = m_stream->notes.at(i);
        qreal left = boundingRect().left() + note->getTime() * x_factor;
        qreal top = boundingRect().top() + note->getPitch() * y_factor;
        qreal width = note->getDuration() * x_factor;
        qreal height = y_factor;

        QRectF noteRectangle = QRectF(left, top, width, height);
        node->appendChildNode(new QSGSimpleRectNode(noteRectangle, Qt::black));
    }
    return node;

}

track.h

#ifndef NOTESTREAM_H
#define NOTESTREAM_H


#include <vector>
#include <memory>

#include <QVariantList>
#include <QQuickView>

#include "note.h"

using namespace std;


class Track : public QObject
{
public:
    void print();

    //TODO: private:
    QList<shared_ptr<Note>> notes;
    int gMInstrument;
    int trackIndex;
    int stringCount;
    vector<int> tuning;

    bool operator==(Track& other) const;

    Track(int trackIndex = -1);
    ~Track();

public slots:
    void addNote(shared_ptr<Note> note);

signals:


};

#endif // NOTESTREAM_H

track.cpp

#include <vector>
#include <memory>
#include <iostream>

#include <QMetaType>
/*
#include <QtDeclarative/QDeclarativeView>
#include <QtDeclarative/QDeclarativeContext>
*/
#include <QQuickView>
#include <QQmlContext>
#include <qqml.h>

#include "track.h"
#include "note.h"


using namespace std;


void Track::print()
{
    for (unsigned int i = 0; i < notes.size(); i++)
    {
        shared_ptr<Note> event = notes.at(i);
        cout << event->getTime() << "  Pitch: " << event->getPitch() << endl;
    }
    cout << notes.size() << " notes" << endl;
}

bool Track::operator==(Track &other) const
{
    return other.notes == this->notes;
}

Track::Track(int trackIndex)
{
    this->trackIndex = trackIndex;
}

Track::~Track()
{
}

void Track::addNote(shared_ptr<Note> note)
{
    //print();
    this->notes.append(note);
    /*
    listElems.append(QVariant::fromValue(new NoteEvent(1, 1, 1, 1)));
    view->rootContext()->setContextProperty("dataModel",QVariant::fromValue(listElems));
    */
}

PianoRollDemo.qml

import QtQuick 2.0
import PianoRoll 1.0

Rectangle {
    width: 500
    height: 200


    PianoRoll {
        stream: inputStream
        anchors.fill: parent
    }

}
DJMcMayhem
  • 7,285
  • 4
  • 41
  • 61
user2052244
  • 308
  • 3
  • 14
  • Qt doesn't know anything about the shared_ptr type. You will at the very least need to register it with Q_DECLARE_METATYPE/qRegisterMetaType, but I doubt that will work with QML. You'll probably need to use a raw Track*. – Dan Milburn Oct 16 '13 at 16:34

1 Answers1

4

There is little point in passing a shared_ptr (or QSharedPointer) to QML, thus the QML engine doesn't support it. QML supports a very limited list of data types. If you're passing a pointer, it must be a naked pointer to QObject.

You can manage the lifetime of the object either yourself, or let QML do it. You declare your intent via void QQmlEngine::setObjectOwnership(QObject*, ObjectOwnership).

If you want to keep the object around for yourself, you set CppOwnership. If you intend to move the ownership to the QML engine, then set JavaScriptOwnership. That's all there's to it. No need for passing smart pointers anywhere.

The following error:

PianoRollDemo.qml:10: unable to assign Track* to [unknown property type]

happens when you assign Track* to the stream property of PianoRoll. That property is of type shared_ptr<Track>. Again, it can't be. It must be of type Track*. The type you put into Q_PROPERTY is the type visible to external users, and for QObjects it must be simply Track*. Internally you can store it in whichever smart pointer type you wish.

There is also absolutely no point to using Q_DECLARE_METATYPE on types derived from QObject*. You must, of course, call qmlRegisterType on all QObject-derived types exposed to QML.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Changing the type of the property stream to Track* and calling `viewer.rootContext()->setContextProperty("inputStream", track)` where track is a Track* changes the error message to `PianoRollDemo.qml:10: unable to assign Track* to [unknown property type]`. I do call `Q_DECLARE_METATYPE(Track*)` right before the declaration of int main(). – user2052244 Oct 17 '13 at 07:02
  • Unless I'm missing something, "That's all there's to it." is a bit of a cop-out. Given JS is garbage-collected, how can I possibly use `CppOwnership`? At whatever point in time C++ deletes the object, JS could always still have a pointer to it, right? – Ben Nov 26 '19 at 15:13