summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMahmoud Badri <mahmoud.badri@qt.io>2019-02-11 10:06:28 +0200
committerMahmoud Badri <mahmoud.badri@qt.io>2019-02-11 19:40:36 +0000
commit5884818826458539e9e959947051bd845d6acce4 (patch)
treee41e15fd8ad9ce59bc7ed5dd10c870f130fd7157
parent89390421091d40b4dd8f34c5419257b3418ab57f (diff)
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 <Graph> 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 <miikka.heikkinen@qt.io>
-rw-r--r--src/Authoring/Client/Code/Core/Doc/Doc.cpp9
-rw-r--r--src/Authoring/Client/Code/Core/Doc/IComposerSerializer.cpp24
-rw-r--r--src/Authoring/Client/Code/Core/Doc/IComposerSerializer.h4
-rw-r--r--src/Authoring/Studio/Application/ProjectFile.cpp608
-rw-r--r--src/Authoring/Studio/Application/ProjectFile.h27
-rw-r--r--src/Authoring/Studio/Application/StudioApp.cpp1
-rw-r--r--src/Authoring/Studio/Palettes/Inspector/InspectorControlModel.cpp11
-rw-r--r--src/Authoring/Studio/Palettes/Inspector/InspectorControlModel.h5
-rw-r--r--src/Authoring/Studio/Palettes/Inspector/InspectorControlView.cpp64
-rw-r--r--src/Authoring/Studio/Palettes/Inspector/InspectorControlView.h4
-rw-r--r--src/Authoring/Studio/Palettes/Inspector/InspectorControlView.qml158
-rw-r--r--src/Authoring/Studio/Palettes/Inspector/VariantTagDialog.cpp106
-rw-r--r--src/Authoring/Studio/Palettes/Inspector/VariantTagDialog.h73
-rw-r--r--src/Authoring/Studio/Palettes/Inspector/VariantTagDialog.ui113
-rw-r--r--src/Authoring/Studio/Palettes/Inspector/VariantsGroupModel.cpp181
-rw-r--r--src/Authoring/Studio/Palettes/Inspector/VariantsGroupModel.h77
-rw-r--r--src/Authoring/Studio/Palettes/Inspector/VariantsTagModel.cpp108
-rw-r--r--src/Authoring/Studio/Palettes/Inspector/VariantsTagModel.h62
-rw-r--r--src/Authoring/Studio/Qt3DStudio.pro13
-rw-r--r--src/Runtime/res/DataModelMetadata/en-us/MetaData.xml3
20 files changed, 1633 insertions, 18 deletions
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<Q3DStudio::IComposerSerializer> 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<Q3DStudio::IComposerSerializer> CDoc::CreateTransactionlessSerializer()
@@ -2407,8 +2408,10 @@ std::shared_ptr<Q3DStudio::IComposerSerializer> 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<qt3dsdm::IDOMWriter> 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<Q3DStudio::IImportFailedHandler> 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<Q3DStudio::IImportFailedHandler> 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<Qt3DSDMPropertyHandle, SValue> &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 <Graph> 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 <Graph>
+ 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<TDataStrPtr>(sVal)->GetData());
+ }
+
m_InstanceSet.insert(inInstance);
Qt3DSDMSlideHandle theAssociatedSlide = GetAssociatedSlide(inInstance);
@@ -2854,10 +2868,10 @@ std::shared_ptr<IComposerSerializer> IComposerSerializer::CreateGraphSlideSerial
ISlideSystem &inSlideSystem, IActionSystem &inActionSystem, ISlideGraphCore &inSlideGraphCore,
SComposerObjectDefinitions &inObjectDefinitions,
std::shared_ptr<Q3DStudio::IImportFailedHandler> inFailedHandler, IGuideSystem &inGuideSystem,
- qt3ds::render::IPathManager &inPathManager)
+ qt3ds::render::IPathManager &inPathManager, IPropertySystem &inPropSystem)
{
return std::shared_ptr<SComposerSerializerImpl>(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<Q3DStudio::IImportFailedHandler> 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 <QtCore/qdiriterator.h>
#include <QtCore/qsavefile.h>
#include <QtCore/qtimer.h>
+#include <QtCore/qrandom.h>
+#include <QtWidgets/qmessagebox.h>
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<qt3dsdm::TDataStrPtr>(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<VariantGroup> 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<VariantGroup> 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<bool, bool> 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 <QtCore/qtimer.h>
#include <QtQml/qqmlcontext.h>
@@ -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<std::shared_ptr<qt3dsdm::ISignalConnection>> m_connections;
QColor m_backgroundColor;
+ VariantsGroupModel *m_variantsGroupModel = nullptr;
InspectorControlModel *m_inspectorControlModel = nullptr;
CInspectableBase *m_inspectableBase = nullptr;
QPointer<ImageChooserView> 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<QString, QString> 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 <QtWidgets/qdialog.h>
+
+#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<QString, QString> getNames() const;
+
+private:
+ enum WarningType {
+ EmptyWarning,
+ UniqueWarning
+ };
+
+ void displayWarning(WarningType warningType);
+
+ DialogType m_type;
+ QString m_group;
+ Ui::VariantTagDialog *m_ui;
+ std::pair<QString, QString> 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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>VariantTagDialog</class>
+ <widget class="QDialog" name="VariantTagDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>241</width>
+ <height>89</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Add new Tag</string>
+ </property>
+ <property name="sizeGripEnabled">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="widget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QWidget" name="labelEditLayout" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout" stretch="0">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Tag name</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="lineEditTagName"/>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>VariantTagDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>VariantTagDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
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<qt3dsdm::TDataStrPtr>(sValue)->GetData());
+ // TODO: remove qDebug when the variants work is fully done.
+ qDebug() << "\x1b[42m \x1b[1m" << __FUNCTION__
+ << ", val=" << val
+ << "\x1b[m";
+ QHash<QString, QStringList> 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<std::pair<QString, bool> > 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<qt3dsdm::CDataStr>(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<int, QByteArray> 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 <QtCore/qabstractitemmodel.h>
+
+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<int, QByteArray> roleNames() const override;
+
+private:
+ QVector<TagGroupData> 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<std::pair<QString, bool> > &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<int, QByteArray> 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 <QtCore/qabstractitemmodel.h>
+
+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<std::pair<QString, bool> > &data);
+ void updateTagState(const QString &tag, bool selected);
+ QString serialize(const QString &groupName) const;
+
+protected:
+ QHash<int, QByteArray> roleNames() const override;
+
+private:
+ QVector<std::pair<QString, bool> > 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 @@
<Property name="blendtype" formalName="Blend Mode" list="Normal:Screen:Multiply:Add:Subtract:*Overlay:*ColorBurn:*ColorDodge" default="Normal" description="Mode of blending between this layer\nand layers below. Modes marked with\n* are available only with HW supporting\nadvanced blending modes." category="Basic Properties" />
<Property name="sourcepath" formalName="Sub-Presentation" description="Presentation or QML stream to\nrender for this layer" type="Renderable" controllable="True" category="Basic Properties" />
+ <!-- Variant Tags -->
+ <Property name="variants" type="String" category="Variant Tags" />
+
<!-- Antialiasing -->
<Property name="progressiveaa" formalName="Progressive AA" description="Improves the visual quality when no\nitems are moving" list="None:2x:4x:8x" default="None" category="Antialiasing" />
<Property name="multisampleaa" formalName="Multisample AA" description="Improves geometry quality, e.g.\nsilhouettes." list="None:2x:4x:SSAA" default="None" category="Antialiasing" />