From 5884818826458539e9e959947051bd845d6acce4 Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Mon, 11 Feb 2019 10:06:28 +0200 Subject: Implement variants tags inspector work Implemented features: - UI for the variants tags in the inspector. - adding, renaming, and deleting tags. - adding, renaming, and deleting tag groups. - adding, renaming tags/groups is checked for name uniqueness across project. - deleting tags/groups checked for in-use across the project. - toggling tags state. - tags are synced with the UIA/UIP. - changing group color. - the variants property in saved in the part of the UIP. Remaining tasks: - implement import and export. - issue: renaming a selected tag, toggle it unselected till next project load. - issue: update the property after deleting a group that is in-use in the property in the currently open doc but not saved. - thorough test and tweaking. Task-no: QT3DS-2983 Change-Id: Iecbb6c854a00ec75864de5dd0f31f6ca3721c8ec Reviewed-by: Miikka Heikkinen --- src/Authoring/Client/Code/Core/Doc/Doc.cpp | 9 +- .../Client/Code/Core/Doc/IComposerSerializer.cpp | 24 +- .../Client/Code/Core/Doc/IComposerSerializer.h | 4 +- src/Authoring/Studio/Application/ProjectFile.cpp | 608 +++++++++++++++++++++ src/Authoring/Studio/Application/ProjectFile.h | 27 + src/Authoring/Studio/Application/StudioApp.cpp | 1 + .../Palettes/Inspector/InspectorControlModel.cpp | 11 +- .../Palettes/Inspector/InspectorControlModel.h | 5 +- .../Palettes/Inspector/InspectorControlView.cpp | 64 ++- .../Palettes/Inspector/InspectorControlView.h | 4 + .../Palettes/Inspector/InspectorControlView.qml | 158 +++++- .../Studio/Palettes/Inspector/VariantTagDialog.cpp | 106 ++++ .../Studio/Palettes/Inspector/VariantTagDialog.h | 73 +++ .../Studio/Palettes/Inspector/VariantTagDialog.ui | 113 ++++ .../Palettes/Inspector/VariantsGroupModel.cpp | 181 ++++++ .../Studio/Palettes/Inspector/VariantsGroupModel.h | 77 +++ .../Studio/Palettes/Inspector/VariantsTagModel.cpp | 108 ++++ .../Studio/Palettes/Inspector/VariantsTagModel.h | 62 +++ src/Authoring/Studio/Qt3DStudio.pro | 13 +- .../res/DataModelMetadata/en-us/MetaData.xml | 3 + 20 files changed, 1633 insertions(+), 18 deletions(-) create mode 100644 src/Authoring/Studio/Palettes/Inspector/VariantTagDialog.cpp create mode 100644 src/Authoring/Studio/Palettes/Inspector/VariantTagDialog.h create mode 100644 src/Authoring/Studio/Palettes/Inspector/VariantTagDialog.ui create mode 100644 src/Authoring/Studio/Palettes/Inspector/VariantsGroupModel.cpp create mode 100644 src/Authoring/Studio/Palettes/Inspector/VariantsGroupModel.h create mode 100644 src/Authoring/Studio/Palettes/Inspector/VariantsTagModel.cpp create mode 100644 src/Authoring/Studio/Palettes/Inspector/VariantsTagModel.h diff --git a/src/Authoring/Client/Code/Core/Doc/Doc.cpp b/src/Authoring/Client/Code/Core/Doc/Doc.cpp index ecb16fb8..a7730603 100644 --- a/src/Authoring/Client/Code/Core/Doc/Doc.cpp +++ b/src/Authoring/Client/Code/Core/Doc/Doc.cpp @@ -2391,7 +2391,8 @@ std::shared_ptr CDoc::CreateSerializer() *theCoreSystem.GetActionCore(), *m_AssetGraph, *theFullSystem.GetSlideSystem(), *theFullSystem.GetActionSystem(), *theCoreSystem.GetSlideGraphCore(), theClientBridge.GetObjectDefinitions(), m_ImportFailedHandler, - *theCoreSystem.GetGuideSystem(), *GetSceneGraph()->GetPathManager()); + *theCoreSystem.GetGuideSystem(), *GetSceneGraph()->GetPathManager(), + *theFullSystem.GetPropertySystem()); } std::shared_ptr CDoc::CreateTransactionlessSerializer() @@ -2407,8 +2408,10 @@ std::shared_ptr CDoc::CreateTransactionlessSeria *theCoreSystem.GetTransactionlessAnimationCore(), *theCoreSystem.GetTransactionlessActionCore(), *m_AssetGraph, *theFullSystem.GetSlideSystem(), *theFullSystem.GetActionSystem(), - *theCoreSystem.GetTransactionlessSlideGraphCore(), theClientBridge.GetObjectDefinitions(), - m_ImportFailedHandler, *theCoreSystem.GetGuideSystem(), *GetSceneGraph()->GetPathManager()); + *theCoreSystem.GetTransactionlessSlideGraphCore(), + theClientBridge.GetObjectDefinitions(), m_ImportFailedHandler, + *theCoreSystem.GetGuideSystem(), *GetSceneGraph()->GetPathManager(), + *theFullSystem.GetPropertySystem()); } std::shared_ptr CDoc::CreateDOMWriter() diff --git a/src/Authoring/Client/Code/Core/Doc/IComposerSerializer.cpp b/src/Authoring/Client/Code/Core/Doc/IComposerSerializer.cpp index 09707e46..462549b5 100644 --- a/src/Authoring/Client/Code/Core/Doc/IComposerSerializer.cpp +++ b/src/Authoring/Client/Code/Core/Doc/IComposerSerializer.cpp @@ -321,6 +321,7 @@ struct SComposerSerializerImpl : public IComposerSerializer qt3dsdm::IStringTable &m_StringTable; std::shared_ptr m_ImportFailedHandler; qt3ds::render::IPathManager &m_PathManager; + IPropertySystem &m_propertySystem; // The instances we have discovered when we are writing THandleToIdMap m_HandleToIdMap; @@ -370,7 +371,8 @@ struct SComposerSerializerImpl : public IComposerSerializer IActionSystem &inActionSystem, ISlideGraphCore &inSlideGraphCore, SComposerObjectDefinitions &inObjectDefinitions, std::shared_ptr inFailedHandler, - IGuideSystem &inGuideSystem, qt3ds::render::IPathManager &inPathManager) + IGuideSystem &inGuideSystem, qt3ds::render::IPathManager &inPathManager, + IPropertySystem &inPropSystem) : m_DataCore(inDataCore) , m_MetaData(inMetaData) , m_SlideCore(inSlideCore) @@ -385,6 +387,7 @@ struct SComposerSerializerImpl : public IComposerSerializer , m_StringTable(inDataCore.GetStringTable()) , m_ImportFailedHandler(inFailedHandler) , m_PathManager(inPathManager) + , m_propertySystem(inPropSystem) , m_Foundation(Q3DStudio::Foundation::SStudioFoundation::Create()) , m_InputStreamFactory(qt3ds::render::IInputStreamFactory::Create(*m_Foundation.m_Foundation)) , m_PreserveFileIds(true) @@ -923,8 +926,10 @@ struct SComposerSerializerImpl : public IComposerSerializer const pair &theValue(inList[idx]); TCharStr theName(m_DataCore.GetProperty(theValue.first).m_Name); WriteDataModelValue(theValue.second, theValueStr); - if (GetValueType(theValue.second) == DataModelDataType::String || theValueStr.size()) - inWriter.Att(theName.wide_str(), theValueStr.wide_str()); + if (GetValueType(theValue.second) == DataModelDataType::String || theValueStr.size()) { + if (theName != L"variants") // this property is saved under the node + inWriter.Att(theName.wide_str(), theValueStr.wide_str()); + } } } @@ -1607,6 +1612,15 @@ struct SComposerSerializerImpl : public IComposerSerializer IDOMWriter::Scope __instanceScope(inWriter, theType->wide_str()); inWriter.Att(L"id", GetInstanceId(inInstance)); + // for layers, save the variants property under the + if (theType.getValue() == L"Layer") { + auto prop = m_propertySystem.GetAggregateInstancePropertyByName(inInstance, + L"variants"); + SValue sVal; + if (m_propertySystem.GetInstancePropertyValue(inInstance, prop, sVal)) + inWriter.Att(L"variants", get(sVal)->GetData()); + } + m_InstanceSet.insert(inInstance); Qt3DSDMSlideHandle theAssociatedSlide = GetAssociatedSlide(inInstance); @@ -2854,10 +2868,10 @@ std::shared_ptr IComposerSerializer::CreateGraphSlideSerial ISlideSystem &inSlideSystem, IActionSystem &inActionSystem, ISlideGraphCore &inSlideGraphCore, SComposerObjectDefinitions &inObjectDefinitions, std::shared_ptr inFailedHandler, IGuideSystem &inGuideSystem, - qt3ds::render::IPathManager &inPathManager) + qt3ds::render::IPathManager &inPathManager, IPropertySystem &inPropSystem) { return std::shared_ptr(new SComposerSerializerImpl( inDataCore, inMetaData, inSlideCore, inAnimationCore, inActionCore, inAssetGraph, inSlideSystem, inActionSystem, inSlideGraphCore, inObjectDefinitions, inFailedHandler, - inGuideSystem, inPathManager)); + inGuideSystem, inPathManager, inPropSystem)); } diff --git a/src/Authoring/Client/Code/Core/Doc/IComposerSerializer.h b/src/Authoring/Client/Code/Core/Doc/IComposerSerializer.h index f02677f1..98fba780 100644 --- a/src/Authoring/Client/Code/Core/Doc/IComposerSerializer.h +++ b/src/Authoring/Client/Code/Core/Doc/IComposerSerializer.h @@ -49,6 +49,7 @@ class IActionSystem; class ISlideGraphCore; class IGuideSystem; class SComposerObjectDefinitions; +class IPropertySystem; }; namespace Q3DStudio { @@ -108,7 +109,8 @@ public: qt3dsdm::IActionSystem &inActionSystem, qt3dsdm::ISlideGraphCore &inSlideGraphCore, qt3dsdm::SComposerObjectDefinitions &inObjectDefinitions, std::shared_ptr inFailedHandler, - qt3dsdm::IGuideSystem &inGuideSystem, qt3ds::render::IPathManager &inPathManager); + qt3dsdm::IGuideSystem &inGuideSystem, qt3ds::render::IPathManager &inPathManager, + qt3dsdm::IPropertySystem &inPropSystem); }; } diff --git a/src/Authoring/Studio/Application/ProjectFile.cpp b/src/Authoring/Studio/Application/ProjectFile.cpp index ecf5a065..af80556d 100644 --- a/src/Authoring/Studio/Application/ProjectFile.cpp +++ b/src/Authoring/Studio/Application/ProjectFile.cpp @@ -43,6 +43,8 @@ #include #include #include +#include +#include ProjectFile::ProjectFile() { @@ -928,3 +930,609 @@ ProjectFile::getDiBindingtypesFromSubpresentations() const return map; } + +// load variant data from uia to m_variantsDef +void ProjectFile::loadVariants() +{ + if (!m_fileInfo.exists()) + return; + + QFile file(getProjectFilePath()); + if (!file.open(QFile::Text | QFile::ReadOnly)) { + qWarning() << file.errorString(); + return; + } + + m_variantsDef.clear(); + + QXmlStreamReader reader(&file); + reader.setNamespaceProcessing(false); + + while (!reader.atEnd()) { + if (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("variantgroup")) { + VariantGroup g; + g.m_title = reader.attributes().value(QLatin1String("id")).toString(); + g.m_color = reader.attributes().value(QLatin1String("color")).toString(); + m_variantsDef.append(g); + } else if (reader.name() == QLatin1String("variant")) { + m_variantsDef.last().m_tags.append(reader.attributes().value( + QLatin1String("id")).toString()); + } else if (m_variantsDef.length() > 0) { + break; + } + } + } +} + +// Add a new tag to a variants group +void ProjectFile::addVariantTag(const QString &group, const QString &newTag) +{ + QDomDocument domDoc; + QSaveFile file(getProjectFilePath()); + if (!StudioUtils::openDomDocumentSave(file, domDoc)) + return; + + QDomElement newTagElem = domDoc.createElement(QStringLiteral("variant")); + newTagElem.setAttribute(QStringLiteral("id"), newTag); + + QDomNodeList groupsElems = domDoc.documentElement() + .firstChildElement(QStringLiteral("variants")) + .elementsByTagName(QStringLiteral("variantgroup")); + + // update and save the uia + for (int i = 0; i < groupsElems.count(); ++i) { + QDomElement gElem = groupsElems.at(i).toElement(); + if (gElem.attribute(QStringLiteral("id")) == group) { + gElem.appendChild(newTagElem); + StudioUtils::commitDomDocumentSave(file, domDoc); + break; + } + } + + // update m_variantsDef + for (auto &v : m_variantsDef) { + if (v.m_title == group) { + v.m_tags.append(newTag); + break; + } + } +} + +// Add a new group, it is assumes that the new group name is unique +void ProjectFile::addVariantGroup(const QString &newGroup) +{ + QDomDocument domDoc; + QSaveFile file(getProjectFilePath()); + if (!StudioUtils::openDomDocumentSave(file, domDoc)) + return; + + QDomElement variantsElem = domDoc.documentElement() + .firstChildElement(QStringLiteral("variants")); + + if (variantsElem.isNull()) { + QDomElement newVariantsElem = domDoc.createElement(QStringLiteral("variants")); + domDoc.documentElement().appendChild(newVariantsElem); + variantsElem = newVariantsElem; + } + + // generate random semi-bright color + int r = 0x555555 + QRandomGenerator::global()->generate() % 0x555555; // 0x555555 = 0xffffff / 3 + QString newColor = QLatin1Char('#') + QString::number(r, 16); + + QDomElement newGroupElem = domDoc.createElement(QStringLiteral("variantgroup")); + newGroupElem.setAttribute(QStringLiteral("id"), newGroup); + newGroupElem.setAttribute(QStringLiteral("color"), newColor); + variantsElem.appendChild(newGroupElem); + StudioUtils::commitDomDocumentSave(file, domDoc); + + // update m_variantsDef + VariantGroup g; + g.m_title = newGroup; + g.m_color = newColor; + m_variantsDef.append(g); +} + +void ProjectFile::renameVariantTag(const QString &group, const QString &oldTag, + const QString &newTag) +{ + QDomDocument domDoc; + QSaveFile file(getProjectFilePath()); + if (!StudioUtils::openDomDocumentSave(file, domDoc)) + return; + + // rename the tag in all uip files + QDomNodeList presElems = domDoc.documentElement() + .firstChildElement(QStringLiteral("assets")) + .elementsByTagName(QStringLiteral("presentation")); + for (int i = 0; i < presElems.count(); ++i) { + QString pPath = m_fileInfo.path() + QLatin1Char('/') + + presElems.at(i).toElement().attribute(QStringLiteral("src")); + renameTagInUip(pPath, group, oldTag, newTag); + } + + // update and save the uia + QDomNodeList groupsElems = domDoc.documentElement() + .firstChildElement(QStringLiteral("variants")) + .elementsByTagName(QStringLiteral("variantgroup")); + + bool renamed = false; + for (int i = 0; i < groupsElems.count(); ++i) { + QDomElement gElem = groupsElems.at(i).toElement(); + if (gElem.attribute(QStringLiteral("id")) == group) { + QDomNodeList tagsElems = gElem.childNodes(); + for (int j = 0; j < tagsElems.count(); ++j) { + QDomElement tElem = tagsElems.at(j).toElement(); + if (tElem.attribute(QStringLiteral("id")) == oldTag) { + tElem.setAttribute(QStringLiteral("id"), newTag); + StudioUtils::commitDomDocumentSave(file, domDoc); + renamed = true; + break; + } + } + if (renamed) + break; + } + } + + // update m_variantsDef + renamed = false; + for (auto &g : m_variantsDef) { + if (g.m_title == group) { + for (auto &t : g.m_tags) { + if (t == oldTag) { + t = newTag; + renamed = true; + break; + } + } + if (renamed) + break; + } + } +} + +// rename a variant group, newGroup is assumed to be unique +void ProjectFile::renameVariantGroup(const QString &oldGroup, const QString &newGroup) +{ + QDomDocument domDoc; + QSaveFile file(getProjectFilePath()); + if (!StudioUtils::openDomDocumentSave(file, domDoc)) + return; + + // rename the group in all uip files + QDomNodeList presElems = domDoc.documentElement() + .firstChildElement(QStringLiteral("assets")) + .elementsByTagName(QStringLiteral("presentation")); + for (int i = 0; i < presElems.count(); ++i) { + QString pPath = m_fileInfo.path() + QLatin1Char('/') + + presElems.at(i).toElement().attribute(QStringLiteral("src")); + renameGroupInUip(pPath, oldGroup, newGroup); + } + + // update and save the uia + QDomNodeList groupsElems = domDoc.documentElement() + .firstChildElement(QStringLiteral("variants")) + .elementsByTagName(QStringLiteral("variantgroup")); + + for (int i = 0; i < groupsElems.count(); ++i) { + QDomElement gElem = groupsElems.at(i).toElement(); + if (gElem.attribute(QStringLiteral("id")) == oldGroup) { + gElem.setAttribute(QStringLiteral("id"), newGroup); + StudioUtils::commitDomDocumentSave(file, domDoc); + break; + } + } + + // update m_variantsDef + for (auto &g : m_variantsDef) { + if (g.m_title == oldGroup) { + g.m_title = newGroup; + break; + } + } +} + +void ProjectFile::deleteVariantGroup(const QString &group) +{ + CDoc *doc = g_StudioApp.GetCore()->GetDoc(); + + QDomDocument domDoc; + QSaveFile file(getProjectFilePath()); + if (!StudioUtils::openDomDocumentSave(file, domDoc)) + return; + + // check if group is in use in other presentations in the porject + int inUseIdx = -1; // index of first presentation that has the group in-use + QDomNodeList presElems = domDoc.documentElement() + .firstChildElement(QStringLiteral("assets")) + .elementsByTagName(QStringLiteral("presentation")); + for (int i = 0; i < presElems.count(); ++i) { + QString pPath = m_fileInfo.path() + QLatin1Char('/') + + presElems.at(i).toElement().attribute(QStringLiteral("src")); + if (groupExistsInUip(pPath, group)) { + inUseIdx = i; + break; + } + } + + // check that the group is in use in the current doc (could be set in the property but not saved + if (inUseIdx == -1) { + auto propertySystem = doc->GetStudioSystem()->GetPropertySystem(); + int instance = doc->GetSelectedInstance(); + auto bridge = doc->GetStudioSystem()->GetClientDataModelBridge(); + + if (instance != 0 && bridge->IsLayerInstance(instance)) { + int property = propertySystem->GetAggregateInstancePropertyByName(instance, + L"variants"); + qt3dsdm::SValue sValue; + if (propertySystem->GetInstancePropertyValue(instance, property, sValue)) { + QString val = QString::fromWCharArray( + qt3dsdm::get(sValue)->GetData()); + if (val.contains(group + QLatin1Char(':'))) { + // this big value will trigger the in-use warning, but will skip updaing the + // uips which is not needed. + inUseIdx = 9999; + } + } + } + } + + if (inUseIdx != -1) { + QMessageBox box; + box.setWindowTitle(tr("Group tags in use")); + box.setText(tr("Some tags in the Group '%1' are in use in the project, are you sure you" + " want to delete the group?").arg(group)); + box.setIcon(QMessageBox::Warning); + box.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel); + box.setButtonText(QMessageBox::Yes, QStringLiteral("Delete")); + switch (box.exec()) { + case QMessageBox::Yes: + // delete the group from all uips that use it + for (int i = inUseIdx; i < presElems.count(); ++i) { + QString pPath = m_fileInfo.path() + QLatin1Char('/') + + presElems.at(i).toElement().attribute(QStringLiteral("src")); + if (pPath != doc->GetDocumentPath()) + deleteGroupFromUip(pPath, group); + } + if (inUseIdx == 9999) { + // property has the deleted group, need to update it, else the deleted group + // will be saved the uip if the user saves the presentation. + + // TODO: implement + } + break; + + default: + // abort deletion + return; + } + } + + // delete the group from current doc, if exists + deleteGroupFromUip(doc->GetDocumentPath(), group); + + QDomElement variantsElem = domDoc.documentElement() + .firstChildElement(QStringLiteral("variants")); + QDomNodeList groupsElems = variantsElem.elementsByTagName(QStringLiteral("variantgroup")); + // update and save the uia + bool deleted = false; + for (int i = 0; i < groupsElems.count(); ++i) { + QDomElement gElem = groupsElems.at(i).toElement(); + if (gElem.attribute(QStringLiteral("id")) == group) { + variantsElem.removeChild(gElem); + StudioUtils::commitDomDocumentSave(file, domDoc); + deleted = true; + break; + } + } + + // update m_variantsDef + for (auto &g : m_variantsDef) { + if (g.m_title == group) { + m_variantsDef.erase(&g); + break; + } + } +} + +void ProjectFile::changeVariantGroupColor(const QString &group, const QString &newColor) +{ + QDomDocument domDoc; + QSaveFile file(getProjectFilePath()); + if (!StudioUtils::openDomDocumentSave(file, domDoc)) + return; + + // update and save the uia + QDomNodeList groupsElems = domDoc.documentElement() + .firstChildElement(QStringLiteral("variants")) + .elementsByTagName(QStringLiteral("variantgroup")); + + for (int i = 0; i < groupsElems.count(); ++i) { + QDomElement gElem = groupsElems.at(i).toElement(); + if (gElem.attribute(QStringLiteral("id")) == group) { + gElem.setAttribute(QStringLiteral("color"), newColor); + StudioUtils::commitDomDocumentSave(file, domDoc); + break; + } + } + + // update m_variantsDef + for (auto &g : m_variantsDef) { + if (g.m_title == group) { + g.m_color = newColor; + break; + } + } +} + +bool ProjectFile::tagExistsInUip(const QString &src, const QString &group, const QString &tag) const +{ + QFile file(src); + if (!file.open(QFile::Text | QFile::ReadOnly)) { + qWarning() << file.errorString(); + return false; + } + + QXmlStreamReader reader(&file); + reader.setNamespaceProcessing(false); + + while (!reader.atEnd()) { + if (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("Layer") + && reader.attributes().hasAttribute(QLatin1String("variants"))) { + QStringRef v = reader.attributes().value(QLatin1String("variants")); + if (v.contains(group + QLatin1Char(':') + tag)) + return true; + } else if (reader.name() == QLatin1String("Logic")) { + break; + } + } + } + + return false; +} + +bool ProjectFile::groupExistsInUip(const QString &src, const QString &group) const +{ + QFile file(src); + if (!file.open(QFile::Text | QFile::ReadOnly)) { + qWarning() << file.errorString(); + return false; + } + + QXmlStreamReader reader(&file); + reader.setNamespaceProcessing(false); + + while (!reader.atEnd()) { + if (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("Layer") + && reader.attributes().hasAttribute(QLatin1String("variants"))) { + QStringRef v = reader.attributes().value(QLatin1String("variants")); + if (v.contains(group + QLatin1Char(':'))) + return true; + } else if (reader.name() == QLatin1String("Logic")) { + break; + } + } + } + + return false; +} + +// renames a tag (if exists) in all layers in a uip file +void ProjectFile::renameTagInUip(const QString &src, const QString &group, const QString &tag, + const QString &newName) +{ + QDomDocument domDoc; + QSaveFile file(src); + if (!StudioUtils::openDomDocumentSave(file, domDoc)) + return; + + QDomNodeList layerElems = domDoc.documentElement() + .elementsByTagName(QStringLiteral("Layer")); + bool needSave = false; + for (int i = 0; i < layerElems.count(); ++i) { + QDomElement lElem = layerElems.at(i).toElement(); + if (lElem.hasAttribute(QStringLiteral("variants"))) { + QStringList tagPairs = lElem.attribute(QStringLiteral("variants")) + .split(QLatin1Char(',')); + QString tagFrom = group + QLatin1Char(':') + tag; + QString tagTo = group + QLatin1Char(':') + newName; + + if (tagPairs.contains(tagFrom)) { + tagPairs.replaceInStrings(tagFrom, tagTo); + lElem.setAttribute(QStringLiteral("variants"), tagPairs.join(QLatin1Char(','))); + needSave = true; + } + } + } + + if (needSave) + StudioUtils::commitDomDocumentSave(file, domDoc); +} + +void ProjectFile::renameGroupInUip(const QString &src, const QString &group, const QString &newName) +{ + QDomDocument domDoc; + QSaveFile file(src); + if (!StudioUtils::openDomDocumentSave(file, domDoc)) + return; + + QDomNodeList layerElems = domDoc.documentElement() + .elementsByTagName(QStringLiteral("Layer")); + bool needSave = false; + for (int i = 0; i < layerElems.count(); ++i) { + QDomElement lElem = layerElems.at(i).toElement(); + if (lElem.hasAttribute(QStringLiteral("variants"))) { + QString variants = lElem.attribute(QStringLiteral("variants")); + if (variants.contains(group + QLatin1Char(':'))) { + variants.replace(group + QLatin1Char(':'), newName + QLatin1Char(':')); + lElem.setAttribute(QStringLiteral("variants"), variants); + needSave = true; + } + } + } + + if (needSave) + StudioUtils::commitDomDocumentSave(file, domDoc); +} + +// deletes a tag (if exists) from all layers in a uip file +void ProjectFile::deleteTagFromUip(const QString &src, const QString &group, const QString &tag) +{ + QDomDocument domDoc; + QSaveFile file(src); + if (!StudioUtils::openDomDocumentSave(file, domDoc)) + return; + + QDomNodeList layerElems = domDoc.documentElement() + .elementsByTagName(QStringLiteral("Layer")); + bool needSave = false; + for (int i = 0; i < layerElems.count(); ++i) { + QDomElement lElem = layerElems.at(i).toElement(); + if (lElem.hasAttribute(QStringLiteral("variants"))) { + QStringList tagPairs = lElem.attribute(QStringLiteral("variants")) + .split(QLatin1Char(',')); + QString tagPair = group + QLatin1Char(':') + tag; + if (tagPairs.contains(tagPair)) { + tagPairs.removeOne(tagPair); + lElem.setAttribute(QStringLiteral("variants"), tagPairs.join(QLatin1Char(','))); + needSave = true; + } + } + } + + if (needSave) + StudioUtils::commitDomDocumentSave(file, domDoc); +} + +// deletes a group (if exists) from all layers in a uip file +void ProjectFile::deleteGroupFromUip(const QString &src, const QString &group) +{ + QDomDocument domDoc; + QSaveFile file(src); + if (!StudioUtils::openDomDocumentSave(file, domDoc)) + return; + + QDomNodeList layerElems = domDoc.documentElement() + .elementsByTagName(QStringLiteral("Layer")); + bool needSave = false; + QRegExp rgx(group + ":\\w*,|," + group + ":\\w*"); + for (int i = 0; i < layerElems.count(); ++i) { + QDomElement lElem = layerElems.at(i).toElement(); + if (lElem.hasAttribute(QStringLiteral("variants"))) { + QString val = lElem.attribute(QStringLiteral("variants")); + if (rgx.indexIn(val) != -1) { + val.replace(rgx, ""); + lElem.setAttribute(QStringLiteral("variants"), val); + needSave = true; + } + } + } + + if (needSave) + StudioUtils::commitDomDocumentSave(file, domDoc); +} + +bool ProjectFile::isVariantGroupUnique(const QString &group) const +{ + for (auto &g : qAsConst(m_variantsDef)) { + if (g.m_title == group) + return false; + } + + return true; +} + +bool ProjectFile::isVariantTagUnique(const QString &group, const QString &tag) const +{ + for (auto &g : qAsConst(m_variantsDef)) { + if (g.m_title == group) + return !g.m_tags.contains(tag); + } + + return true; +} + +void ProjectFile::deleteVariantTag(const QString &group, const QString &tag) +{ + QDomDocument domDoc; + QSaveFile file(getProjectFilePath()); + if (!StudioUtils::openDomDocumentSave(file, domDoc)) + return; + + // check if tag is in use in other presentations in the porject + int inUseIdx = -1; // list of presentations that has the tag in use + QDomNodeList presElems = domDoc.documentElement() + .firstChildElement(QStringLiteral("assets")) + .elementsByTagName(QStringLiteral("presentation")); + for (int i = 0; i < presElems.count(); ++i) { + QString pPath = m_fileInfo.path() + QLatin1Char('/') + + presElems.at(i).toElement().attribute(QStringLiteral("src")); + if (pPath != g_StudioApp.GetCore()->GetDoc()->GetDocumentPath() + && tagExistsInUip(pPath, group, tag)) { + inUseIdx = i; + break; + } + } + + if (inUseIdx != -1) { + QMessageBox box; + box.setWindowTitle(tr("Tag in use")); + box.setText(tr("The tag '%1' is in use in another presentation, are you sure you want to" + " delete it?").arg(tag)); + box.setIcon(QMessageBox::Warning); + box.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel); + box.setButtonText(QMessageBox::Yes, QStringLiteral("Delete")); + switch (box.exec()) { + case QMessageBox::Yes: + // delete the tag from all uips that use it + for (int i = inUseIdx; i < presElems.count(); ++i) { + QString pPath = m_fileInfo.path() + QLatin1Char('/') + + presElems.at(i).toElement().attribute(QStringLiteral("src")); + if (pPath != g_StudioApp.GetCore()->GetDoc()->GetDocumentPath()) + deleteTagFromUip(pPath, group, tag); + } + break; + + default: + // abort deletion + return; + } + } + + // delete the tag from current doc, if exists + deleteTagFromUip(g_StudioApp.GetCore()->GetDoc()->GetDocumentPath(), group, tag); + + QDomNodeList groupsElems = domDoc.documentElement() + .firstChildElement(QStringLiteral("variants")) + .elementsByTagName(QStringLiteral("variantgroup")); + // update and save the uia + bool deleted = false; + for (int i = 0; i < groupsElems.count(); ++i) { + QDomElement gElem = groupsElems.at(i).toElement(); + if (gElem.attribute(QStringLiteral("id")) == group) { + QDomNodeList tagsElems = gElem.childNodes(); + for (int j = 0; j < tagsElems.count(); ++j) { + QDomElement tElem = tagsElems.at(j).toElement(); + if (tElem.attribute(QStringLiteral("id")) == tag) { + gElem.removeChild(tElem); + StudioUtils::commitDomDocumentSave(file, domDoc); + deleted = true; + break; + } + } + if (deleted) + break; + } + } + + // update m_variantsDef + for (auto &g : m_variantsDef) { + if (g.m_title == group) { + g.m_tags.removeOne(tag); + break; + } + } +} diff --git a/src/Authoring/Studio/Application/ProjectFile.h b/src/Authoring/Studio/Application/ProjectFile.h index 3e47da28..390ccb4c 100644 --- a/src/Authoring/Studio/Application/ProjectFile.h +++ b/src/Authoring/Studio/Application/ProjectFile.h @@ -46,6 +46,12 @@ class ProjectFile : public QObject public: ProjectFile(); + struct VariantGroup { + QString m_title; + QString m_color; + QStringList m_tags; + }; + void create(const QString &uiaPath); void ensureProjectFile(); void initProjectFile(const QString &presPath); @@ -81,6 +87,19 @@ public: void deletePresentationFile(const QString &filePath); void renameMaterial(const QString &oldName, const QString &newName); bool duplicatePresentation(const QString &oldPres, const QString &newPres); + void loadVariants(); + void addVariantTag(const QString &group, const QString &newTag); + void renameVariantTag(const QString &group, const QString &oldTag, const QString &newTag); + void deleteVariantTag(const QString &group, const QString &tag); + void addVariantGroup(const QString &newGroup); + void renameVariantGroup(const QString &oldGroup, const QString &newGroup); + void deleteVariantGroup(const QString &group); + void changeVariantGroupColor(const QString &group, const QString &newColor); + bool isVariantGroupUnique(const QString &group) const; + bool isVariantTagUnique(const QString &group, const QString &tag) const; + + QVector variantsDef() const { return m_variantsDef; } + Q_SIGNALS: void presentationIdChanged(const QString &path, const QString &id); @@ -88,9 +107,17 @@ Q_SIGNALS: private: QString ensureUniquePresentationId(const QString &id) const; + bool tagExistsInUip(const QString &src, const QString &group, const QString &tag) const; + bool groupExistsInUip(const QString &src, const QString &group) const; + void deleteTagFromUip(const QString &src, const QString &group, const QString &tag); + void deleteGroupFromUip(const QString &src, const QString &group); + void renameTagInUip(const QString &src, const QString &group, const QString &tag, + const QString &newName); + void renameGroupInUip(const QString &src, const QString &group, const QString &newName); QFileInfo m_fileInfo; // uia file info QString m_initialPresentation; + QVector m_variantsDef; // definition of variants }; #endif // PROJECTFILE_H diff --git a/src/Authoring/Studio/Application/StudioApp.cpp b/src/Authoring/Studio/Application/StudioApp.cpp index 8b58e4f5..c86a5dba 100644 --- a/src/Authoring/Studio/Application/StudioApp.cpp +++ b/src/Authoring/Studio/Application/StudioApp.cpp @@ -1729,6 +1729,7 @@ bool CStudioApp::OnLoadDocument(const QString &inDocument, bool inShowStartupDia m_core->getProjectFile().updateDocPresentationId(); m_core->getProjectFile().loadSubpresentationsAndDatainputs(m_subpresentations, m_dataInputDialogItems); + m_core->getProjectFile().loadVariants(); getRenderer().RegisterSubpresentations(m_subpresentations); m_authorZoom = false; diff --git a/src/Authoring/Studio/Palettes/Inspector/InspectorControlModel.cpp b/src/Authoring/Studio/Palettes/Inspector/InspectorControlModel.cpp index d4fdc306..1232939c 100644 --- a/src/Authoring/Studio/Palettes/Inspector/InspectorControlModel.cpp +++ b/src/Authoring/Studio/Palettes/Inspector/InspectorControlModel.cpp @@ -62,6 +62,7 @@ #include "foundation/Qt3DSLogging.h" #include "Dialogs.h" #include "Dispatch.h" +#include "VariantsGroupModel.h" static QStringList renderableItems() { @@ -107,8 +108,9 @@ static std::pair getSlideCharacteristics(qt3dsdm::Qt3DSDMInstanceHan return std::make_pair(hasNextSlide, hasPreviousSlide); } -InspectorControlModel::InspectorControlModel(QObject *parent) - : QAbstractListModel(parent) +InspectorControlModel::InspectorControlModel(VariantsGroupModel *variantsModel, QObject *parent) + : m_variantsModel(variantsModel) + , QAbstractListModel(parent) , m_UpdatableEditor(*g_StudioApp.GetCore()->GetDoc()) { m_modifiedProperty.first = 0; @@ -1229,7 +1231,6 @@ void InspectorControlModel::updatePropertyValue(InspectorControlBase *element) c metaDataProvider->GetMetaDataProperty(instance, element->m_property)); } - bool skipEmits = false; switch (element->m_dataType) { case qt3dsdm::DataModelDataType::String: { @@ -1239,6 +1240,10 @@ void InspectorControlModel::updatePropertyValue(InspectorControlBase *element) c if (index != -1) stringValue = stringValue.mid(index + 1); } + + if (bridge->IsLayerInstance(instance)) + m_variantsModel->refresh(); + element->m_value = stringValue; } // intentional fall-through for other String-derived datatypes case qt3dsdm::DataModelDataType::StringOrInt: diff --git a/src/Authoring/Studio/Palettes/Inspector/InspectorControlModel.h b/src/Authoring/Studio/Palettes/Inspector/InspectorControlModel.h index e329dba1..21e6b3a7 100644 --- a/src/Authoring/Studio/Palettes/Inspector/InspectorControlModel.h +++ b/src/Authoring/Studio/Palettes/Inspector/InspectorControlModel.h @@ -42,6 +42,7 @@ class CInspectableBase; class Qt3DSDMInspectable; class SGuideInspectableImpl; +class VariantsGroupModel; namespace qt3dsdm { class ISignalConnection; @@ -107,7 +108,7 @@ class InspectorControlModel : public QAbstractListModel { Q_OBJECT public: - explicit InspectorControlModel(QObject *parent); + explicit InspectorControlModel(VariantsGroupModel *variantsModel, QObject *parent); ~InspectorControlModel() = default; enum Roles { @@ -247,6 +248,8 @@ private: bool isGroupRebuildRequired(CInspectableBase *inspectable, int theIndex) const; static int handleToGuidePropIndex(int handle) { return handle - 1; } + + VariantsGroupModel *m_variantsModel = nullptr; }; #endif // INSPECTORCONTROLMODEL_H diff --git a/src/Authoring/Studio/Palettes/Inspector/InspectorControlView.cpp b/src/Authoring/Studio/Palettes/Inspector/InspectorControlView.cpp index 69543a12..6252c0ea 100644 --- a/src/Authoring/Studio/Palettes/Inspector/InspectorControlView.cpp +++ b/src/Authoring/Studio/Palettes/Inspector/InspectorControlView.cpp @@ -58,6 +58,8 @@ #include "MaterialRefView.h" #include "BasicObjectsModel.h" #include "Qt3DSDMSlides.h" +#include "VariantsGroupModel.h" +#include "VariantTagDialog.h" #include #include @@ -69,7 +71,8 @@ InspectorControlView::InspectorControlView(const QSize &preferredSize, QWidget *parent) : QQuickWidget(parent), TabNavigable(), - m_inspectorControlModel(new InspectorControlModel(this)), + m_variantsGroupModel(new VariantsGroupModel(this)), + m_inspectorControlModel(new InspectorControlModel(m_variantsGroupModel, this)), m_meshChooserView(new MeshChooserView(this)), m_instance(0), m_handle(0), @@ -241,6 +244,7 @@ void InspectorControlView::initialize() CStudioPreferences::setQmlContextProperties(rootContext()); rootContext()->setContextProperty(QStringLiteral("_parentView"), this); rootContext()->setContextProperty(QStringLiteral("_inspectorModel"), m_inspectorControlModel); + rootContext()->setContextProperty(QStringLiteral("_variantsGroupModel"), m_variantsGroupModel); rootContext()->setContextProperty(QStringLiteral("_resDir"), StudioUtils::resourceImageUrl()); rootContext()->setContextProperty(QStringLiteral("_tabOrderHandler"), tabOrderHandler()); rootContext()->setContextProperty(QStringLiteral("_mouseHelper"), &m_mouseHelper); @@ -412,6 +416,8 @@ void InspectorControlView::setInspectable(CInspectableBase *inInspectable) m_inspectorControlModel->setInspectable(inInspectable); Q_EMIT titleChanged(); + + m_variantsGroupModel->refresh(); } } @@ -452,6 +458,62 @@ void InspectorControlView::showContextMenu(int x, int y, int handle, int instanc m_handle = 0; } +void InspectorControlView::showTagContextMenu(int x, int y, const QString &group, + const QString &tag) +{ + QMenu theContextMenu; + + auto actionRename = theContextMenu.addAction(QObject::tr("Rename Tag")); + connect(actionRename, &QAction::triggered, this, [&]() { + VariantTagDialog dlg(VariantTagDialog::RenameTag, group, tag); + if (dlg.exec() == QDialog::Accepted) { + g_StudioApp.GetCore()->getProjectFile().renameVariantTag(group, dlg.getNames().first, + dlg.getNames().second); + } + }); + + auto actionDelete = theContextMenu.addAction(QObject::tr("Delete Tag")); + connect(actionDelete, &QAction::triggered, this, [&]() { + g_StudioApp.GetCore()->getProjectFile().deleteVariantTag(group, tag); + }); + + theContextMenu.exec(mapToGlobal({x, y})); +} + +void InspectorControlView::showGroupContextMenu(int x, int y, const QString &group) +{ + QMenu theContextMenu; + + ProjectFile &projectFile = g_StudioApp.GetCore()->getProjectFile(); + + auto actionRename = theContextMenu.addAction(QObject::tr("Rename Group")); + connect(actionRename, &QAction::triggered, this, [&]() { + VariantTagDialog dlg(VariantTagDialog::RenameGroup, {}, group); + if (dlg.exec() == QDialog::Accepted) { + projectFile.renameVariantGroup(dlg.getNames().first, dlg.getNames().second); + } + }); + + auto actionColor = theContextMenu.addAction(QObject::tr("Change Group Color")); + connect(actionColor, &QAction::triggered, this, [&]() { + const auto variantsDef = g_StudioApp.GetCore()->getProjectFile().variantsDef(); + for (auto &g : variantsDef) { + if (g.m_title == group) { + QColor newColor = this->showColorDialog(g.m_color); + projectFile.changeVariantGroupColor(group, newColor.name()); + break; + } + } + }); + + auto actionDelete = theContextMenu.addAction(QObject::tr("Delete Group")); + connect(actionDelete, &QAction::triggered, this, [&]() { + projectFile.deleteVariantGroup(group); + }); + + theContextMenu.exec(mapToGlobal({x, y})); +} + void InspectorControlView::toggleMasterLink() { Q3DStudio::ScopedDocumentEditor editor(*g_StudioApp.GetCore()->GetDoc(), diff --git a/src/Authoring/Studio/Palettes/Inspector/InspectorControlView.h b/src/Authoring/Studio/Palettes/Inspector/InspectorControlView.h index 98d67f41..14936b57 100644 --- a/src/Authoring/Studio/Palettes/Inspector/InspectorControlView.h +++ b/src/Authoring/Studio/Palettes/Inspector/InspectorControlView.h @@ -39,6 +39,7 @@ #include "DataInputSelectView.h" class InspectorControlModel; +class VariantsGroupModel; class CInspectableBase; class ImageChooserView; class DataInputSelectView; @@ -72,6 +73,8 @@ public: QString titleIcon() const; Q_INVOKABLE void showContextMenu(int x, int y, int handle, int instance); + Q_INVOKABLE void showTagContextMenu(int x, int y, const QString &group, const QString &tag); + Q_INVOKABLE void showGroupContextMenu(int x, int y, const QString &group); Q_INVOKABLE QObject *showImageChooser(int handle, int instance, const QPoint &point); Q_INVOKABLE QObject *showFilesChooser(int handle, int instance, const QPoint &point); Q_INVOKABLE QObject *showMeshChooser(int handle, int instance, const QPoint &point); @@ -126,6 +129,7 @@ private: std::vector> m_connections; QColor m_backgroundColor; + VariantsGroupModel *m_variantsGroupModel = nullptr; InspectorControlModel *m_inspectorControlModel = nullptr; CInspectableBase *m_inspectableBase = nullptr; QPointer m_imageChooserView; diff --git a/src/Authoring/Studio/Palettes/Inspector/InspectorControlView.qml b/src/Authoring/Studio/Palettes/Inspector/InspectorControlView.qml index b2e8b286..833bbd65 100644 --- a/src/Authoring/Studio/Palettes/Inspector/InspectorControlView.qml +++ b/src/Authoring/Studio/Palettes/Inspector/InspectorControlView.qml @@ -28,6 +28,8 @@ import QtQuick 2.8 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.2 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Extras 1.4 import Qt3DStudio 1.0 import "../controls" @@ -286,6 +288,7 @@ Rectangle { ColumnLayout { // Property row and datainput control Layout.alignment: Qt.AlignTop + visible: modelData.title !== "variants" spacing: 0 RowLayout { // Property row Layout.alignment: Qt.AlignLeft @@ -315,7 +318,7 @@ Rectangle { anchors.fill: parent acceptedButtons: Qt.RightButton | Qt.LeftButton hoverEnabled: true - onClicked: { + onClicked: { if (mouse.button === Qt.LeftButton) { _inspectorModel.setPropertyAnimated( model.modelData.instance, @@ -423,6 +426,9 @@ Rectangle { opacity: enabled ? 1 : .5 Layout.alignment: Qt.AlignTop sourceComponent: { + if (modelData.title === "variants") + return variantTagsComponent; + const dataType = modelData.dataType; switch (dataType) { case DataModelDataType.Long: @@ -1098,4 +1104,154 @@ Rectangle { } } } + + Component { + id: variantTagsComponent + + Column { + width: root.width - 10 + spacing: 10 + + Row { + anchors.right: parent.right + anchors.rightMargin: 5 + spacing: 5 + + ToolButton { + id: importButton + text: qsTr("Import...") + width: 70 + height: 20 + + onClicked: { + // TODO: implement + } + } + + ToolButton { + id: exportButton + text: qsTr("Export...") + width: 70 + height: 20 + + onClicked: { + // TODO: implement + } + } + } + + Text { + text: qsTr("There are no variant tags yet. Click [+ Group] to add a new tag group and start adding tags.") + color: "#ffffff" + visible: _variantsGroupModel.rowCount() === 0 + } + + Repeater { + id: tagsReeater + model: _variantsGroupModel + + Row { + id: variantTagsRow + + readonly property var tagsModel: model.tags + readonly property var groupModel: model + + Text { + text: model.group + color: model.color + width: 50 + anchors.top: parent.top + anchors.topMargin: 5 + + MouseArea { + anchors.fill: parent; + acceptedButtons: Qt.RightButton + onClicked: { + if (mouse.button === Qt.RightButton) { + const coords = mapToItem(root, mouse.x, mouse.y); + _parentView.showGroupContextMenu(coords.x, coords.y, model.group); + } + } + } + } + + Flow { + width: root.width - 110 + spacing: 5 + + Repeater { + model: tagsModel + + Loader { + readonly property var tagsModel: model + readonly property var grpModel: groupModel + sourceComponent: tagComponent + } + } + + ToolButton { + id: addTagButton + text: qsTr("+ Tag") + height: 25 + + onClicked: { + _variantsGroupModel.addNewTag(groupModel.group) + } + + } + } + } + } + + Item { width: 1; height: 5 } // vertical spacer + + ToolButton { + id: addGroupButton + text: qsTr("+ Group") + width: 60 + height: 25 + onClicked: { + _variantsGroupModel.addNewGroup() + } + } + + Item { width: 1; height: 5 } // vertical spacer + } + } + + Component { + id: tagComponent + + Rectangle { + property bool toggled: tagsModel.selected + property string grpColor: grpModel ? grpModel.color : "" + + width: Math.max(tLabel.width + 10, 60) + height: 25 + color: toggled ? grpColor : "#2e2f30" + border.color: "#959596" + + Text { + id: tLabel + anchors.centerIn: parent + text: tagsModel.tag + color: toggled ? "#ffffff" : "#959596" + } + + MouseArea { + anchors.fill: parent; + acceptedButtons: Qt.RightButton | Qt.LeftButton + onClicked: { + if (mouse.button === Qt.LeftButton) { + toggled = !toggled; + _variantsGroupModel.setTagState(grpModel.group, tagsModel.tag, toggled); + } else if (mouse.button === Qt.RightButton) { + const coords = mapToItem(root, mouse.x, mouse.y); + _parentView.showTagContextMenu(coords.x, coords.y, grpModel.group, + tagsModel.tag); + } + } + } + } + } } diff --git a/src/Authoring/Studio/Palettes/Inspector/VariantTagDialog.cpp b/src/Authoring/Studio/Palettes/Inspector/VariantTagDialog.cpp new file mode 100644 index 00000000..83e72e7b --- /dev/null +++ b/src/Authoring/Studio/Palettes/Inspector/VariantTagDialog.cpp @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "VariantTagDialog.h" +#include "ui_VariantTagDialog.h" +#include "Dialogs.h" +#include "StudioApp.h" +#include "Core.h" +#include "ProjectFile.h" + +VariantTagDialog::VariantTagDialog(DialogType type, const QString &group, const QString &name, + QWidget *parent) + : QDialog(parent) + , m_type(type) + , m_group(group) + , m_ui(new Ui::VariantTagDialog) +{ + m_ui->setupUi(this); + + m_names.first = name; + + if (type == AddGroup) { + setWindowTitle(tr("Add new Group")); + m_ui->label->setText(tr("Group name")); + } else if (type == RenameGroup) { + setWindowTitle(tr("Rename Group")); + m_ui->label->setText(tr("Group name")); + m_ui->lineEditTagName->setText(name); + m_ui->lineEditTagName->selectAll(); + } else if (type == RenameTag) { + m_ui->lineEditTagName->setText(name); + m_ui->lineEditTagName->selectAll(); + } +} + +void VariantTagDialog::accept() +{ + QString name = m_ui->lineEditTagName->text(); + + if (name.isEmpty()) { + displayWarning(EmptyWarning); + } else if (name == m_names.first) { // no change + QDialog::reject(); + } else if (((m_type == AddGroup || m_type == RenameGroup) + && !g_StudioApp.GetCore()->getProjectFile().isVariantGroupUnique(name)) + || (!g_StudioApp.GetCore()->getProjectFile().isVariantTagUnique(m_group, name))) { + displayWarning(UniqueWarning); + } else { + m_names.second = name; + QDialog::accept(); + } +} + +std::pair VariantTagDialog::getNames() const +{ + return m_names; +} + +void VariantTagDialog::displayWarning(WarningType warningType) +{ + QString warning; + if (warningType == EmptyWarning) { + if (m_type == AddGroup || m_type == RenameGroup) + warning = tr("The group name must not be empty."); + else + warning = tr("The tag name must not be empty."); + } else if (warningType == UniqueWarning) { + if (m_type == AddGroup || m_type == RenameGroup) + warning = tr("The group name must be unique."); + else + warning = tr("The tag name must be unique within the tag group."); + } + + g_StudioApp.GetDialogs()->DisplayMessageBox(tr("Warning"), warning, + Qt3DSMessageBox::ICON_WARNING, false); +} + +VariantTagDialog::~VariantTagDialog() +{ + delete m_ui; +} diff --git a/src/Authoring/Studio/Palettes/Inspector/VariantTagDialog.h b/src/Authoring/Studio/Palettes/Inspector/VariantTagDialog.h new file mode 100644 index 00000000..b5e3989f --- /dev/null +++ b/src/Authoring/Studio/Palettes/Inspector/VariantTagDialog.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef VARIANTTAGDIALOG_H +#define VARIANTTAGDIALOG_H + +#include + +#ifdef QT_NAMESPACE +using namespace QT_NAMESPACE; +#endif + +QT_BEGIN_NAMESPACE +namespace Ui { +class VariantTagDialog; +} +QT_END_NAMESPACE + +class VariantTagDialog : public QDialog +{ + Q_OBJECT + +public: + enum DialogType { AddTag, RenameTag, AddGroup, RenameGroup }; + + explicit VariantTagDialog(DialogType type, const QString &group = {}, const QString &name = {}, + QWidget *parent = nullptr); + ~VariantTagDialog() override; + +public Q_SLOTS: + void accept() override; + std::pair getNames() const; + +private: + enum WarningType { + EmptyWarning, + UniqueWarning + }; + + void displayWarning(WarningType warningType); + + DialogType m_type; + QString m_group; + Ui::VariantTagDialog *m_ui; + std::pair m_names; // holds the tags values before and after rename +}; + +#endif // VARIANTTAGDIALOG_H diff --git a/src/Authoring/Studio/Palettes/Inspector/VariantTagDialog.ui b/src/Authoring/Studio/Palettes/Inspector/VariantTagDialog.ui new file mode 100644 index 00000000..aa5d24ef --- /dev/null +++ b/src/Authoring/Studio/Palettes/Inspector/VariantTagDialog.ui @@ -0,0 +1,113 @@ + + + VariantTagDialog + + + + 0 + 0 + 241 + 89 + + + + Add new Tag + + + false + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Tag name + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + buttonBox + accepted() + VariantTagDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + VariantTagDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/Authoring/Studio/Palettes/Inspector/VariantsGroupModel.cpp b/src/Authoring/Studio/Palettes/Inspector/VariantsGroupModel.cpp new file mode 100644 index 00000000..3a2a2a45 --- /dev/null +++ b/src/Authoring/Studio/Palettes/Inspector/VariantsGroupModel.cpp @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "VariantsGroupModel.h" +#include "VariantsTagModel.h" +#include "StudioApp.h" +#include "Core.h" +#include "QDebug" // TODO: remove +#include "Qt3DSDMStudioSystem.h" +#include "ClientDataModelBridge.h" +#include "IDocumentEditor.h" +#include "VariantTagDialog.h" + +VariantsGroupModel::VariantsGroupModel(QObject *parent) + : QAbstractListModel(parent) +{ + +} + +void VariantsGroupModel::refresh() +{ + int instance = g_StudioApp.GetCore()->GetDoc()->GetSelectedInstance(); + auto bridge = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge(); + + if (instance == 0 || !bridge->IsLayerInstance(instance)) { + m_instance = 0; + m_property = 0; + return; + } + + auto propertySystem = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetPropertySystem(); + m_instance = instance; + m_property = propertySystem->GetAggregateInstancePropertyByName(instance, L"variants"); + + qt3dsdm::SValue sValue; + if (propertySystem->GetInstancePropertyValue(m_instance, m_property, sValue)) { + beginResetModel(); + m_data.clear(); + + QString val = QString::fromWCharArray( + qt3dsdm::get(sValue)->GetData()); + // TODO: remove qDebug when the variants work is fully done. + qDebug() << "\x1b[42m \x1b[1m" << __FUNCTION__ + << ", val=" << val + << "\x1b[m"; + QHash propTags; + if (!val.isEmpty()) { + const QStringList propTagsList = val.split(QChar(',')); + for (auto &propTag : propTagsList) { + const QStringList propTagPair = propTag.split(QChar(':')); + propTags[propTagPair[0]].append(propTagPair[1]); + } + } + + // build the variants data model + const auto variantsDef = g_StudioApp.GetCore()->getProjectFile().variantsDef(); + for (auto &group : variantsDef) { + TagGroupData g; + g.m_title = group.m_title; + g.m_color = group.m_color; + + VariantsTagModel *m = new VariantsTagModel(this); + QVector > tags; + for (int i = 0; i < group.m_tags.length(); ++i) + tags.append({group.m_tags[i], propTags[group.m_title].contains(group.m_tags[i])}); + + m->init(tags); + g.m_tagsModel = m; + + m_data.push_back(g); + } + + endResetModel(); + } +} + +int VariantsGroupModel::rowCount(const QModelIndex &parent) const +{ + // For list models only the root node (an invalid parent) should return the list's size. For all + // other (valid) parents, rowCount() should return 0 so that it does not become a tree model. + if (parent.isValid()) + return 0; + + return m_data.size(); +} + +QVariant VariantsGroupModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (role == GroupTitleRole) + return m_data.at(index.row()).m_title; + else if (role == GroupColorRole) + return m_data.at(index.row()).m_color; + else if (role == TagRole) + return QVariant::fromValue(m_data.at(index.row()).m_tagsModel); + + return QVariant(); +} + +void VariantsGroupModel::setTagState(const QString &group, const QString &tag, bool selected) +{ + QString val; + QString tagsStr; + bool skipFirst = false; + for (auto &g : qAsConst(m_data)) { + if (g.m_title == group) + g.m_tagsModel->updateTagState(tag, selected); + + tagsStr = g.m_tagsModel->serialize(g.m_title); + if (!tagsStr.isEmpty()) { + if (skipFirst) + val.append(QChar(',')); + val.append(tagsStr); + skipFirst = true; + } + } + + auto sVal = std::make_shared(Q3DStudio::CString::fromQString(val)); + Q3DStudio::SCOPED_DOCUMENT_EDITOR(*g_StudioApp.GetCore()->GetDoc(), QObject::tr("Set Property")) + ->SetInstancePropertyValue(m_instance, m_property, sVal); +} + +void VariantsGroupModel::addNewTag(const QString &group) +{ + VariantTagDialog dlg(VariantTagDialog::AddTag, group); + + if (dlg.exec() == QDialog::Accepted) + g_StudioApp.GetCore()->getProjectFile().addVariantTag(group, dlg.getNames().second); +} + +void VariantsGroupModel::addNewGroup() +{ + VariantTagDialog dlg(VariantTagDialog::AddGroup); + + if (dlg.exec() == QDialog::Accepted) + g_StudioApp.GetCore()->getProjectFile().addVariantGroup(dlg.getNames().second); +} + +QHash VariantsGroupModel::roleNames() const +{ + auto names = QAbstractListModel::roleNames(); + names.insert(GroupTitleRole, "group"); + names.insert(GroupColorRole, "color"); + names.insert(TagRole, "tags"); + return names; +} + +Qt::ItemFlags VariantsGroupModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + + return Qt::ItemIsEditable; +} diff --git a/src/Authoring/Studio/Palettes/Inspector/VariantsGroupModel.h b/src/Authoring/Studio/Palettes/Inspector/VariantsGroupModel.h new file mode 100644 index 00000000..695e3fb0 --- /dev/null +++ b/src/Authoring/Studio/Palettes/Inspector/VariantsGroupModel.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef VARIANTSGROUPMODEL_H +#define VARIANTSGROUPMODEL_H + +#include + +class VariantsTagModel; + +class VariantsGroupModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit VariantsGroupModel(QObject *parent = nullptr); + + enum Roles { + GroupTitleRole = Qt::UserRole + 1, + GroupColorRole, + TagRole + }; + + struct TagGroupData + { + QString m_title; + QString m_color; + VariantsTagModel *m_tagsModel = nullptr; + }; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = GroupTitleRole) const override; + + Qt::ItemFlags flags(const QModelIndex& index) const override; + + void refresh(); + + Q_INVOKABLE void setTagState(const QString &group, const QString &tag, bool selected); + Q_INVOKABLE void addNewTag(const QString &group); + Q_INVOKABLE void addNewGroup(); + + +protected: + QHash roleNames() const override; + +private: + QVector m_data; + int m_instance = 0; // selected layer instance + int m_property = 0; // variant tags property handler +}; + +#endif // VARIANTSGROUPMODEL_H diff --git a/src/Authoring/Studio/Palettes/Inspector/VariantsTagModel.cpp b/src/Authoring/Studio/Palettes/Inspector/VariantsTagModel.cpp new file mode 100644 index 00000000..910b4f09 --- /dev/null +++ b/src/Authoring/Studio/Palettes/Inspector/VariantsTagModel.cpp @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "VariantsTagModel.h" + +VariantsTagModel::VariantsTagModel(QObject *parent) + : QAbstractListModel(parent) +{ + +} + +void VariantsTagModel::init(const QVector > &data) +{ + m_data = data; +} + +void VariantsTagModel::updateTagState(const QString &tag, bool selected) +{ + for (auto &t : m_data) { + if (t.first == tag) { + t.second = selected; + break; + } + } +} + +// return the tags in a formatted string to be saved to the property +QString VariantsTagModel::serialize(const QString &groupName) const +{ + QString ret; + bool skipFirst = false; + for (auto &t : qAsConst(m_data)) { + if (t.second) { + if (skipFirst) + ret.append(QLatin1Char(',')); + + ret.append(groupName + QLatin1Char(':') + t.first); + + skipFirst = true; + } + } + + return ret; +} + +int VariantsTagModel::rowCount(const QModelIndex &parent) const +{ + // For list models only the root node (an invalid parent) should return the list's size. For all + // other (valid) parents, rowCount() should return 0 so that it does not become a tree model. + if (parent.isValid()) + return 0; + + return m_data.size(); +} + +QVariant VariantsTagModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (role == TagRole) + return m_data.at(index.row()).first; + else if (role == SelectedRole) + return m_data.at(index.row()).second; + + return QVariant(); +} + +QHash VariantsTagModel::roleNames() const +{ + auto names = QAbstractListModel::roleNames(); + names.insert(TagRole, "tag"); + names.insert(SelectedRole, "selected"); + return names; +} + +Qt::ItemFlags VariantsTagModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + + return Qt::ItemIsEditable; // FIXME: Implement me! +} diff --git a/src/Authoring/Studio/Palettes/Inspector/VariantsTagModel.h b/src/Authoring/Studio/Palettes/Inspector/VariantsTagModel.h new file mode 100644 index 00000000..01bb4e73 --- /dev/null +++ b/src/Authoring/Studio/Palettes/Inspector/VariantsTagModel.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef VARIANTSTAGMODEL_H +#define VARIANTSTAGMODEL_H + +#include + +class VariantsTagModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit VariantsTagModel(QObject *parent = nullptr); + + enum Roles { + TagRole = Qt::UserRole + 1, + SelectedRole, + }; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = TagRole) const override; + + Qt::ItemFlags flags(const QModelIndex& index) const override; + + void init(const QVector > &data); + void updateTagState(const QString &tag, bool selected); + QString serialize(const QString &groupName) const; + +protected: + QHash roleNames() const override; + +private: + QVector > m_data; // [{tagName, selectedState}, ...] +}; + +#endif // VARIANTSTAGMODEL_H diff --git a/src/Authoring/Studio/Qt3DStudio.pro b/src/Authoring/Studio/Qt3DStudio.pro index bd251740..86103a2c 100644 --- a/src/Authoring/Studio/Qt3DStudio.pro +++ b/src/Authoring/Studio/Qt3DStudio.pro @@ -226,7 +226,10 @@ HEADERS += \ Palettes/scenecamera/scenecameraview.h \ Palettes/scenecamera/scenecamerascrollarea.h \ Palettes/scenecamera/scenecameraglwidget.h \ - Palettes/TimelineGraphicsView/ui/RowTimelineCommentItem.h + Palettes/TimelineGraphicsView/ui/RowTimelineCommentItem.h \ + Palettes/Inspector/VariantsGroupModel.h \ + Palettes/Inspector/VariantsTagModel.h \ + Palettes/Inspector/VariantTagDialog.h FORMS += \ MainFrm.ui \ @@ -246,7 +249,8 @@ FORMS += \ UI/StartupDlg.ui \ Palettes/Project/EditPresentationIdDlg.ui \ Palettes/Project/ChooseImagePropertyDlg.ui \ - Palettes/scenecamera/scenecameraview.ui + Palettes/scenecamera/scenecameraview.ui \ + Palettes/Inspector/VariantTagDialog.ui SOURCES += \ Application/AboutDlg.cpp \ @@ -402,7 +406,10 @@ SOURCES += \ Palettes/scenecamera/scenecameraview.cpp \ Palettes/scenecamera/scenecamerascrollarea.cpp \ Palettes/scenecamera/scenecameraglwidget.cpp \ - Palettes/TimelineGraphicsView/ui/RowTimelineCommentItem.cpp + Palettes/TimelineGraphicsView/ui/RowTimelineCommentItem.cpp \ + Palettes/Inspector/VariantsGroupModel.cpp \ + Palettes/Inspector/VariantsTagModel.cpp \ + Palettes/Inspector/VariantTagDialog.cpp RESOURCES += \ MainFrm.qrc \ diff --git a/src/Runtime/res/DataModelMetadata/en-us/MetaData.xml b/src/Runtime/res/DataModelMetadata/en-us/MetaData.xml index b376d272..534b4c17 100644 --- a/src/Runtime/res/DataModelMetadata/en-us/MetaData.xml +++ b/src/Runtime/res/DataModelMetadata/en-us/MetaData.xml @@ -94,6 +94,9 @@ + + + -- cgit v1.2.3