void RGBMatrix_Test::initTestCase()
{
    m_doc = new Doc(this);

    QDir fxiDir(INTERNAL_FIXTUREDIR);
    fxiDir.setFilter(QDir::Files);
    fxiDir.setNameFilters(QStringList() << QString("*%1").arg(KExtFixture));
    QVERIFY(m_doc->fixtureDefCache()->loadMap(fxiDir) == true);

    QLCFixtureDef* def = m_doc->fixtureDefCache()->fixtureDef("Stairville", "LED PAR56");
    QVERIFY(def != NULL);
    QLCFixtureMode* mode = def->modes().first();
    QVERIFY(mode != NULL);

    FixtureGroup* grp = new FixtureGroup(m_doc);
    grp->setName("Test Group");
    grp->setSize(QSize(5, 5));
    m_doc->addFixtureGroup(grp);

    for (int i = 0; i < 25; i++)
    {
        Fixture* fxi = new Fixture(m_doc);
        fxi->setFixtureDefinition(def, mode);
        fxi->setAddress(i * fxi->channels());
        m_doc->addFixture(fxi);

        grp->assignFixture(fxi->id());
    }

    QVERIFY(m_doc->rgbScriptsCache()->load(QDir(INTERNAL_SCRIPTDIR)));
    QVERIFY(m_doc->rgbScriptsCache()->names().size() != 0);
}
Exemple #2
0
void RGBMatrix::calculateColorDelta()
{
    m_crDelta = 0;
    m_cgDelta = 0;
    m_cbDelta = 0;
    m_stepCount = 0;

    if (m_endColor.isValid())
    {
        if (doc() == NULL)
            return;

        FixtureGroup* grp = doc()->fixtureGroup(fixtureGroup());
        QMutexLocker algorithmLocker(&m_algorithmMutex);
        if (grp != NULL && m_algorithm != NULL)
        {
            m_stepCount = m_algorithm->rgbMapStepCount(grp->size()) - 1;
            if (m_stepCount > 0)
            {
                m_crDelta = m_endColor.red() - m_startColor.red();
                m_cgDelta = m_endColor.green() - m_startColor.green();
                m_cbDelta = m_endColor.blue() - m_startColor.blue();
            }
        }
    }
}
Exemple #3
0
void RGBMatrix::tap()
{
    if (stopped() == false)
    {
        FixtureGroup* grp = doc()->fixtureGroup(fixtureGroup());
        // Filter out taps that are too close to each other
        if (grp != NULL && uint(m_roundTime->elapsed()) >= (duration() / 4))
            roundCheck(grp->size());
    }
}
Exemple #4
0
RGBMap RGBMatrix::previewMap(int step)
{
    RGBMap map;
    QMutexLocker algorithmLocker(&m_algorithmMutex);
    if (m_algorithm == NULL)
        return map;
    FixtureGroup* grp = doc()->fixtureGroup(fixtureGroup());
    if (grp != NULL)
    {
        map = m_algorithm->rgbMap(grp->size(), m_stepColor.rgb(), step);
    }
    return map;
}
Exemple #5
0
void RGBMatrix::setTotalDuration(quint32 msec)
{
    if (m_fixtureGroupID == FixtureGroup::invalidId() ||
        m_algorithm == NULL)
            return;

    FixtureGroup* grp = doc()->fixtureGroup(fixtureGroup());
    if (grp != NULL)
    {
        int steps = m_algorithm->rgbMapStepCount(grp->size());
        setDuration(msec / steps);
    }
}
Exemple #6
0
int RGBMatrix::stepsCount()
{
    QMutexLocker algorithmLocker(&m_algorithmMutex);

    if (m_algorithm == NULL)
        return 0;

    FixtureGroup* grp = doc()->fixtureGroup(fixtureGroup());
    if (grp != NULL)
        return m_algorithm->rgbMapStepCount(grp->size());

    return 0;
}
Exemple #7
0
void RGBMatrix::preRun(MasterTimer* timer)
{
    Q_UNUSED(timer);

    FixtureGroup* grp = doc()->fixtureGroup(fixtureGroup());
    {
        QMutexLocker algorithmLocker(&m_algorithmMutex);
        if (grp != NULL && m_algorithm != NULL)
        {
            Q_ASSERT(m_fader == NULL);
            m_fader = new GenericFader(doc());
            m_fader->adjustIntensity(getAttributeValue(Intensity));

            // Copy direction from parent class direction
            m_direction = direction();

            if (m_direction == Forward)
            {
                m_step = 0;
                m_stepColor = m_startColor.rgb();
            }
            else
            {
                m_step = m_algorithm->rgbMapStepCount(grp->size()) - 1;
                if (m_endColor.isValid())
                {
                    m_stepColor = m_endColor.rgb();
                }
                else
                {
                    m_stepColor = m_startColor.rgb();
                }
            }
            calculateColorDelta();
            if (m_algorithm->type() == RGBAlgorithm::Script)
            {
                RGBScript *script = static_cast<RGBScript*> (m_algorithm);
                QHashIterator<QString, QString> it(m_properties);
                while(it.hasNext())
                {
                    it.next();
                    script->setProperty(it.key(), it.value());
                }
            }
        }
    }

    m_roundTime->start();

    Function::preRun(timer);
}
Exemple #8
0
void RGBMatrix::setTotalDuration(quint32 msec)
{
    QMutexLocker algorithmLocker(&m_algorithmMutex);

    if (m_algorithm == NULL)
        return;

    FixtureGroup* grp = doc()->fixtureGroup(fixtureGroup());
    if (grp == NULL)
        return;

    int steps = m_algorithm->rgbMapStepCount(grp->size());
    setDuration(msec / steps);
}
Exemple #9
0
quint32 RGBMatrix::totalDuration()
{
    QMutexLocker algorithmLocker(&m_algorithmMutex);

    if (m_algorithm == NULL)
        return 0;

    FixtureGroup* grp = doc()->fixtureGroup(fixtureGroup());
    if (grp == NULL)
        return 0;

    qDebug () << "Algorithm steps:" << m_algorithm->rgbMapStepCount(grp->size());
    return m_algorithm->rgbMapStepCount(grp->size()) * duration();
}
Exemple #10
0
quint32 RGBMatrix::totalDuration()
{
    if (m_fixtureGroupID == FixtureGroup::invalidId() ||
        m_algorithm == NULL)
            return 0;

    FixtureGroup* grp = doc()->fixtureGroup(fixtureGroup());
    if (grp != NULL)
    {
        qDebug () << "Algorithm steps:" << m_algorithm->rgbMapStepCount(grp->size());
        return m_algorithm->rgbMapStepCount(grp->size()) * duration();
    }

    return 0;
}
Exemple #11
0
// ----------------------------------------------------------------------------
//
void ActorSelectField::init( ActorPtrArray& actors, UIDArray& selected_actor_uids )
{
    if ( actors.size() == 0 )
        return;

    CString default_value;
    CString values;

    std::map<UID,CString> actor_to_id;

    for ( SceneActor * actor : actors ) {
        CString label;
        CString id;

        if ( actor->isGroup() ) {
            FixtureGroup* group = m_venue->getFixtureGroup( actor->getActorUID() );
            label.Format( "%s", group->getName() );
            id.Format( "G%lu", group->getNumber() );
        }
        else {
            Fixture* pf = m_venue->getFixture( actor->getActorUID() );
            label.Format( "%s @ %d", pf->getFullName(), pf->getAddress() );
            id.Format( "%lu", pf->getFixtureNumber() );
        }

        m_entries[ id ] = ActorSelectField::entry( id, label, *actor );
        actor_to_id[ actor->getActorUID() ] = id;

        if ( default_value.GetLength() == 0 )
            default_value = id;
    }

    // Preseve selected actor order
    for ( UID selected_uid : selected_actor_uids ) {
        std::map<UID,CString>::iterator it = actor_to_id.find( selected_uid );
        if ( it != actor_to_id.end() ) {
            if ( values.GetLength() != 0 )
                values += ",";
            values.Append( (*it).second );
        }
    }

    if ( values.GetLength() == 0 )
        setValue( default_value );
    else
        setValue( values );
}
Exemple #12
0
QList <RGBMap> RGBMatrix::previewMaps()
{
    QList <RGBMap> steps;

    if (m_algorithm == NULL)
        return steps;

    FixtureGroup* grp = doc()->fixtureGroup(fixtureGroup());
    if (grp != NULL)
    {
        int stepCount = m_algorithm->rgbMapStepCount(grp->size());
        for (int i = 0; i < stepCount; i++)
            steps << m_algorithm->rgbMap(grp->size(), m_stepColor.rgb(), i);
    }

    return steps;
}
Exemple #13
0
// ----------------------------------------------------------------------------
//
CString AbstractAnimation::getSynopsis(void) {
    CString synopsis;

    synopsis.Format( "Fixtures( " );
    for ( UIDArray::iterator it=m_actors.begin(); it != m_actors.end(); ++it ) {
        Fixture *pf = studio.getVenue()->getFixture( (*it) );
        if ( pf != NULL )
            synopsis.AppendFormat( "F%lu ", pf->getNumber() );
        else {
            FixtureGroup* group = studio.getVenue()->getFixtureGroup( (*it) );
            if ( group != NULL )
                synopsis.AppendFormat( "G%lu ", group->getNumber() );
        }
    }
    synopsis += ")";

    return synopsis;
}
Exemple #14
0
void RGBMatrixEditor::slotFixtureGroupChanged(quint32 id)
{
    if (id == m_matrix->fixtureGroup())
    {
        // Update the whole chain -> maybe the fixture layout has changed
        fillFixtureGroupCombo();
        slotFixtureGroupActivated(m_fixtureGroupCombo->currentIndex());
    }
    else
    {
        // Just change the name of the group, nothing else is interesting at this point
        int index = m_fixtureGroupCombo->findData(id);
        if (index != -1)
        {
            FixtureGroup* grp = m_doc->fixtureGroup(id);
            m_fixtureGroupCombo->setItemText(index, grp->name());
        }
    }
}
Exemple #15
0
void RGBMatrix::write(MasterTimer* timer, QList<Universe *> universes)
{
    Q_UNUSED(timer);
    Q_UNUSED(universes);

    FixtureGroup* grp = doc()->fixtureGroup(fixtureGroup());
    if (grp == NULL)
    {
        // No fixture group to control
        stop();
        return;
    }

    // No time to do anything.
    if (duration() == 0)
        return;

    // Invalid/nonexistent script
    {
        QMutexLocker algorithmLocker(&m_algorithmMutex);
        if (m_algorithm == NULL || m_algorithm->apiVersion() == 0)
            return;

        // Get new map every time when elapsed is reset to zero
        if (elapsed() == 0)
        {
            qDebug() << "RGBMatrix stepColor:" << QString::number(m_stepColor.rgb(), 16);
            RGBMap map = m_algorithm->rgbMap(grp->size(), m_stepColor.rgb(), m_step);
            updateMapChannels(map, grp);
        }
    }

    // Run the generic fader that takes care of fading in/out individual channels
    m_fader->write(universes);

    // Increment elapsed time
    incrementElapsed();

    // Check if we need to change direction, stop completely or go to next step
    if (elapsed() >= duration())
        roundCheck(grp->size());
}
Exemple #16
0
void RGBMatrix::preRun(MasterTimer* timer)
{
    Q_UNUSED(timer);

    FixtureGroup* grp = doc()->fixtureGroup(fixtureGroup());
    {
        QMutexLocker algorithmLocker(&m_algorithmMutex);
        if (grp != NULL && m_algorithm != NULL)
        {
            m_direction = direction();

            Q_ASSERT(m_fader == NULL);
            m_fader = new GenericFader(doc());
            m_fader->adjustIntensity(getAttributeValue(Intensity));

            if (m_direction == Forward)
            {
                m_step = 0;
                m_stepColor = m_startColor.rgb();
            }
            else
            {
                m_step = m_algorithm->rgbMapStepCount(grp->size()) - 1;
                if (m_endColor.isValid())
                {
                    m_stepColor = m_endColor.rgb();
                }
                else
                {
                    m_stepColor = m_startColor.rgb();
                }
            }
            calculateColorDelta();
        }
    }

    m_roundTime->start();

    Function::preRun(timer);
}
Exemple #17
0
void RGBMatrixEditor::slotSaveToSequenceClicked()
{
    if (m_matrix == NULL || m_matrix->fixtureGroup() == FixtureGroup::invalidId())
        return;

    FixtureGroup* grp = m_doc->fixtureGroup(m_matrix->fixtureGroup());
    if (grp != NULL && m_matrix->algorithm() != NULL)
    {
        bool testRunning = false;

        if (m_testButton->isChecked() == true)
        {
            m_testButton->click();
            testRunning = true;
        }
        else
            m_previewTimer->stop();

        Scene *grpScene = new Scene(m_doc);
        grpScene->setName(grp->name());
        QList<GroupHead> headList = grp->headList();
        foreach (GroupHead head, headList)
        {
            Fixture *fxi = m_doc->fixture(head.fxi);
            if (fxi == NULL)
                continue;
            QList<quint32> rgbCh = fxi->rgbChannels(head.head);
            if (rgbCh.count() == 3)
            {
                grpScene->setValue(head.fxi, rgbCh.at(0), 0);
                grpScene->setValue(head.fxi, rgbCh.at(1), 0);
                grpScene->setValue(head.fxi, rgbCh.at(2), 0);
            }

            quint32 master = fxi->masterIntensityChannel(head.head);
            if (master != QLCChannel::invalid())
                grpScene->setValue(head.fxi, master, 0);
        }
Exemple #18
0
void Doc::clearContents()
{
    emit clearing();

    // Delete all function instances
    QListIterator <quint32> funcit(m_functions.keys());
    while (funcit.hasNext() == true)
    {
        Function* func = m_functions.take(funcit.next());
        emit functionRemoved(func->id());
        delete func;
    }

    // Delete all fixture instances
    QListIterator <quint32> fxit(m_fixtures.keys());
    while (fxit.hasNext() == true)
    {
        Fixture* fxi = m_fixtures.take(fxit.next());
        emit fixtureRemoved(fxi->id());
        delete fxi;
    }

    // Delete all fixture groups
    QListIterator <quint32> grpit(m_fixtureGroups.keys());
    while (grpit.hasNext() == true)
    {
        FixtureGroup* grp = m_fixtureGroups.take(grpit.next());
        emit fixtureGroupRemoved(grp->id());
        delete grp;
    }

    m_latestFunctionId = 0;
    m_latestFixtureId = 0;
    m_latestFixtureGroupId = 0;
    m_addresses.clear();

    emit cleared();
}
Exemple #19
0
void Doc_Test::addFixtureGroup()
{
    QSignalSpy spy(m_doc, SIGNAL(fixtureGroupAdded(quint32)));

    QCOMPARE(m_doc->fixtureGroups().size(), 0);
    QCOMPARE(m_doc->m_latestFixtureGroupId, quint32(0));

    FixtureGroup* grp = new FixtureGroup(m_doc);
    QCOMPARE(m_doc->addFixtureGroup(grp), true);
    QCOMPARE(grp->id(), quint32(0));
    QCOMPARE(m_doc->m_latestFixtureGroupId, quint32(0));
    QCOMPARE(m_doc->fixtureGroups().size(), 1);
    QCOMPARE(spy.size(), 1);
    QCOMPARE(spy[0].size(), 1);
    QCOMPARE(spy[0][0].toUInt(), quint32(0));

    QCOMPARE(m_doc->addFixtureGroup(grp, 0), false);
    QCOMPARE(m_doc->fixtureGroups().size(), 1);

    grp = new FixtureGroup(m_doc);
    QCOMPARE(m_doc->addFixtureGroup(grp, 0), false);
    QCOMPARE(m_doc->addFixtureGroup(grp, 15), true);
    QCOMPARE(m_doc->m_latestFixtureGroupId, quint32(0));
    QCOMPARE(m_doc->fixtureGroups().size(), 2);
    QCOMPARE(spy.size(), 2);
    QCOMPARE(spy[1].size(), 1);
    QCOMPARE(spy[1][0].toUInt(), quint32(15));

    grp = new FixtureGroup(m_doc);
    QCOMPARE(m_doc->addFixtureGroup(grp), true);
    QCOMPARE(grp->id(), quint32(1));
    QCOMPARE(m_doc->m_latestFixtureGroupId, quint32(1));
    QCOMPARE(m_doc->fixtureGroups().size(), 3);
    QCOMPARE(spy.size(), 3);
    QCOMPARE(spy[2].size(), 1);
    QCOMPARE(spy[2][0].toUInt(), quint32(1));
}
Exemple #20
0
void Doc::clearContents()
{
    emit clearing();

    m_clipboard->resetContents();

    if (m_monitorProps != NULL)
        m_monitorProps->reset();

    destroyAudioCapture();

    // Delete all function instances
    QListIterator <quint32> funcit(m_functions.keys());
    while (funcit.hasNext() == true)
    {
        Function* func = m_functions.take(funcit.next());
        if (func == NULL)
            continue;
        emit functionRemoved(func->id());
        delete func;
    }

    // Delete all fixture groups
    QListIterator <quint32> grpit(m_fixtureGroups.keys());
    while (grpit.hasNext() == true)
    {
        FixtureGroup* grp = m_fixtureGroups.take(grpit.next());
        quint32 grpID = grp->id();
        delete grp;
        emit fixtureGroupRemoved(grpID);
    }

    // Delete all fixture instances
    QListIterator <quint32> fxit(m_fixtures.keys());
    while (fxit.hasNext() == true)
    {
        Fixture* fxi = m_fixtures.take(fxit.next());
        quint32 fxID = fxi->id();
        delete fxi;
        emit fixtureRemoved(fxID);
    }

    // Delete all channels groups
    QListIterator <quint32> grpchans(m_channelsGroups.keys());
    while (grpchans.hasNext() == true)
    {
        ChannelsGroup* grp = m_channelsGroups.take(grpchans.next());
        emit channelsGroupRemoved(grp->id());
        delete grp;
    }

    m_orderedGroups.clear();

    m_latestFunctionId = 0;
    m_latestFixtureId = 0;
    m_latestFixtureGroupId = 0;
    m_latestChannelsGroupId = 0;
    m_addresses.clear();

    emit cleared();
}
Exemple #21
0
bool RGBMatrixEditor::createPreviewItems()
{
    m_previewHash.clear();
    m_scene->clear();

    FixtureGroup* grp = m_doc->fixtureGroup(m_matrix->fixtureGroup());
    if (grp == NULL)
    {
        QGraphicsTextItem* text = new QGraphicsTextItem(tr("No fixture group to control"));
        text->setDefaultTextColor(Qt::white);
        m_scene->addItem(text);
        return false;
    }

    m_previewDirection = m_matrix->direction();

    if (m_previewDirection == Function::Forward)
    {
        m_matrix->setStepColor(m_matrix->startColor());
    }
    else
    {
        if (m_matrix->endColor().isValid())
            m_matrix->setStepColor(m_matrix->endColor());
        else
            m_matrix->setStepColor(m_matrix->startColor());
    }

    m_matrix->calculateColorDelta();
    m_previewMaps = m_matrix->previewMaps();

    if ((m_previewDirection == Function::Forward) || m_previewMaps.isEmpty())
    {
        m_previewStep = 0;
    }
    else
    {
        m_previewStep = m_previewMaps.size() - 1;
    }

    RGBMap map;
    if (m_previewStep < m_previewMaps.size())
        map = m_previewMaps[m_previewStep];

    if (map.isEmpty())
        return false;

    for (int x = 0; x < grp->size().width(); x++)
    {
        for (int y = 0; y < grp->size().height(); y++)
        {
            QLCPoint pt(x, y);

            if (grp->headHash().contains(pt) == true)
            {
                RGBItem* item = new RGBItem;
                item->setRect(x * RECT_SIZE + RECT_PADDING + ITEM_PADDING,
                              y * RECT_SIZE + RECT_PADDING + ITEM_PADDING,
                              ITEM_SIZE - (2 * ITEM_PADDING),
                              ITEM_SIZE - (2 * ITEM_PADDING));
                item->setColor(map[y][x]);
                item->draw(0);
                m_scene->addItem(item);
                m_previewHash[pt] = item;
            }
        }
    }
    return true;
}
Exemple #22
0
void Doc_Test::save()
{
    Scene* s = new Scene(m_doc);
    m_doc->addFunction(s);

    Fixture* f1 = new Fixture(m_doc);
    f1->setName("One");
    f1->setChannels(5);
    f1->setAddress(0);
    f1->setUniverse(0);
    m_doc->addFixture(f1);

    Chaser* c = new Chaser(m_doc);
    m_doc->addFunction(c);

    Fixture* f2 = new Fixture(m_doc);
    f2->setName("Two");
    f2->setChannels(10);
    f2->setAddress(20);
    f2->setUniverse(1);
    m_doc->addFixture(f2);

    Collection* o = new Collection(m_doc);
    m_doc->addFunction(o);

    Fixture* f3 = new Fixture(m_doc);
    f3->setName("Three");
    f3->setChannels(15);
    f3->setAddress(40);
    f3->setUniverse(2);
    m_doc->addFixture(f3);

    EFX* e = new EFX(m_doc);
    m_doc->addFunction(e);

    FixtureGroup* grp = new FixtureGroup(m_doc);
    grp->setName("Group 1");
    m_doc->addFixtureGroup(grp);

    grp = new FixtureGroup(m_doc);
    grp->setName("Group 2");
    m_doc->addFixtureGroup(grp);

    QVERIFY(m_doc->isModified() == true);

    QBuffer buffer;
    buffer.open(QIODevice::WriteOnly | QIODevice::Text);
    QXmlStreamWriter xmlWriter(&buffer);
    xmlWriter.writeStartElement("TestRoot");

    QVERIFY(m_doc->saveXML(&xmlWriter) == true);

    xmlWriter.setDevice(NULL);
    buffer.close();

    buffer.open(QIODevice::ReadOnly | QIODevice::Text);
    QXmlStreamReader xmlReader(&buffer);

    xmlReader.readNextStartElement();
    QVERIFY(xmlReader.name().toString() == "TestRoot");
    xmlReader.readNextStartElement();
    QVERIFY(xmlReader.name().toString() == "Engine");

    uint fixtures = 0, groups = 0, functions = 0, ioMap = 0;

    // Merely tests that the start of each hierarchy is found from the XML document.
    // Their contents are tested individually in their own separate tests.
    while (xmlReader.readNextStartElement())
    {
        if (xmlReader.name() == "Fixture")
            fixtures++;
        else if (xmlReader.name() == "Function")
            functions++;
        else if (xmlReader.name() == "FixtureGroup")
            groups++;
        else if (xmlReader.name() == "InputOutputMap")
            ioMap++;
        else if (xmlReader.name() == "Bus")
            QFAIL("Bus tags should not be saved anymore!");
        else
            QFAIL(QString("Unexpected tag: %1")
                  .arg(xmlReader.name().toString()).toLatin1());

        xmlReader.skipCurrentElement();
    }

    QVERIFY(fixtures == 3);
    QVERIFY(groups == 2);
    QVERIFY(functions == 4);
    QVERIFY(ioMap == 1);

    /* Saving doesn't implicitly reset modified status */
    QVERIFY(m_doc->isModified() == true);
}
Exemple #23
0
void Doc_Test::save()
{
    Scene* s = new Scene(m_doc);
    m_doc->addFunction(s);

    Fixture* f1 = new Fixture(m_doc);
    f1->setName("One");
    f1->setChannels(5);
    f1->setAddress(0);
    f1->setUniverse(0);
    m_doc->addFixture(f1);

    Chaser* c = new Chaser(m_doc);
    m_doc->addFunction(c);

    Fixture* f2 = new Fixture(m_doc);
    f2->setName("Two");
    f2->setChannels(10);
    f2->setAddress(20);
    f2->setUniverse(1);
    m_doc->addFixture(f2);

    Collection* o = new Collection(m_doc);
    m_doc->addFunction(o);

    Fixture* f3 = new Fixture(m_doc);
    f3->setName("Three");
    f3->setChannels(15);
    f3->setAddress(40);
    f3->setUniverse(2);
    m_doc->addFixture(f3);

    EFX* e = new EFX(m_doc);
    m_doc->addFunction(e);

    FixtureGroup* grp = new FixtureGroup(m_doc);
    grp->setName("Group 1");
    m_doc->addFixtureGroup(grp);

    grp = new FixtureGroup(m_doc);
    grp->setName("Group 2");
    m_doc->addFixtureGroup(grp);

    QVERIFY(m_doc->isModified() == true);

    QDomDocument document;
    QDomElement root = document.createElement("TestRoot");

    QVERIFY(m_doc->saveXML(&document, &root) == true);

    uint fixtures = 0, groups = 0, functions = 0;
    QDomNode node = root.firstChild();
    QVERIFY(node.toElement().tagName() == "Engine");

    // Merely tests that the start of each hierarchy is found from the XML document.
    // Their contents are tested individually in their own separate tests.
    node = node.firstChild();
    while (node.isNull() == false)
    {
        QDomElement tag = node.toElement();
        if (tag.tagName() == "Fixture")
            fixtures++;
        else if (tag.tagName() == "Function")
            functions++;
        else if (tag.tagName() == "FixtureGroup")
            groups++;
        else if (tag.tagName() == "Bus")
            QFAIL("Bus tags should not be saved anymore!");
        else
            QFAIL(QString("Unexpected tag: %1")
                  .arg(tag.tagName()).toLatin1());

        node = node.nextSibling();
    }

    QVERIFY(fixtures == 3);
    QVERIFY(groups == 2);
    QVERIFY(functions == 4);

    /* Saving doesn't implicitly reset modified status */
    QVERIFY(m_doc->isModified() == true);
}