/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace PathUtil; namespace AudioControls { namespace WriterStrings { static constexpr const char* LevelsSubFolder = "levels"; static constexpr const char* LibraryExtension = ".xml"; } // namespace WriterStrings //-------------------------------------------------------------------------------------------// AZStd::string_view TypeToTag(EACEControlType type) { switch (type) { case eACET_RTPC: return Audio::ATLXmlTags::ATLRtpcTag; case eACET_TRIGGER: return Audio::ATLXmlTags::ATLTriggerTag; case eACET_SWITCH: return Audio::ATLXmlTags::ATLSwitchTag; case eACET_SWITCH_STATE: return Audio::ATLXmlTags::ATLSwitchStateTag; case eACET_PRELOAD: return Audio::ATLXmlTags::ATLPreloadRequestTag; case eACET_ENVIRONMENT: return Audio::ATLXmlTags::ATLEnvironmentTag; } return ""; } //-------------------------------------------------------------------------------------------// CAudioControlsWriter::CAudioControlsWriter(CATLControlsModel* atlModel, QStandardItemModel* layoutModel, IAudioSystemEditor* audioSystemImpl, FilepathSet& previousLibraryPaths) : m_atlModel(atlModel) , m_layoutModel(layoutModel) , m_audioSystemImpl(audioSystemImpl) { if (m_atlModel && m_layoutModel && m_audioSystemImpl) { m_layoutModel->blockSignals(true); int i = 0; QModelIndex index = m_layoutModel->index(i, 0); while (index.isValid()) { WriteLibrary(index.data(Qt::DisplayRole).toString().toUtf8().data(), index); index = index.sibling(++i, 0); } // Delete libraries that don't exist anymore from disk FilepathSet librariesToDelete; AZStd::set_difference( previousLibraryPaths.begin(), previousLibraryPaths.end(), m_foundLibraryPaths.begin(), m_foundLibraryPaths.end(), AZStd::inserter(librariesToDelete, librariesToDelete.begin()) ); for (auto it = librariesToDelete.begin(); it != librariesToDelete.end(); ++it) { DeleteLibraryFile((*it).c_str()); } previousLibraryPaths = m_foundLibraryPaths; m_layoutModel->blockSignals(false); } } //-------------------------------------------------------------------------------------------// void CAudioControlsWriter::WriteLibrary(const AZStd::string_view libraryName, QModelIndex root) { if (root.isValid()) { TLibraryStorage library; int i = 0; QModelIndex child = root.model()->index(i, 0, root); while (child.isValid()) { WriteItem(child, "", library, root.data(eDR_MODIFIED).toBool()); child = root.model()->index(++i, 0, root); } const char* controlsPath = nullptr; Audio::AudioSystemRequestBus::BroadcastResult(controlsPath, &Audio::AudioSystemRequestBus::Events::GetControlsPath); for (auto& libraryPair : library) { AZStd::string libraryPath; const AZStd::string& scope = libraryPair.first; if (scope.empty()) { // no scope, file at the root level libraryPath.append(controlsPath); AZ::StringFunc::Path::Join(libraryPath.c_str(), libraryName.data(), libraryPath); libraryPath.append(WriterStrings::LibraryExtension); } else { // with scope, inside level folder libraryPath.append(controlsPath); libraryPath.append(WriterStrings::LevelsSubFolder); AZ::StringFunc::Path::Join(libraryPath.c_str(), scope.c_str(), libraryPath); AZ::StringFunc::Path::Join(libraryPath.c_str(), libraryName.data(), libraryPath); libraryPath.append(WriterStrings::LibraryExtension); } // should be able to change this back to GamePathToFullPath once a path normalization bug has been fixed: AZStd::string fullFilePath; AZ::StringFunc::Path::Join(Path::GetEditingGameDataFolder().c_str(), libraryPath.c_str(), fullFilePath); AZStd::to_lower(fullFilePath.begin(), fullFilePath.end()); m_foundLibraryPaths.insert(fullFilePath.c_str()); const SLibraryScope& libScope = libraryPair.second; if (libScope.m_isDirty) { XmlNodeRef fileNode = GetISystem()->CreateXmlNode(Audio::ATLXmlTags::RootNodeTag); fileNode->setAttr(Audio::ATLXmlTags::ATLNameAttribute, libraryName.data()); for (int i = 0; i < eACET_NUM_TYPES; ++i) { if (i != eACET_SWITCH_STATE) // switch_states are written inside the switches { if (libScope.m_nodes[i]->getChildCount() > 0) { fileNode->addChild(libScope.m_nodes[i]); } } } if (QFileInfo::exists(fullFilePath.c_str())) { const DWORD fileAttributes = GetFileAttributes(fullFilePath.c_str()); if (fileAttributes & FILE_ATTRIBUTE_READONLY) { // file is read-only CheckOutFile(fullFilePath); } fileNode->saveToFile(fullFilePath.c_str()); } else { // save the file, CheckOutFile will add it, since it's new fileNode->saveToFile(fullFilePath.c_str()); CheckOutFile(fullFilePath); } } } } } //-------------------------------------------------------------------------------------------// void CAudioControlsWriter::WriteItem(QModelIndex index, const AZStd::string& path, TLibraryStorage& library, bool isParentModified) { if (index.isValid()) { if (index.data(eDR_TYPE) == eIT_FOLDER) { int i = 0; QModelIndex child = index.model()->index(i, 0, index); while (child.isValid()) { AZStd::string newPath = path.empty() ? "" : path + "/"; newPath += index.data(Qt::DisplayRole).toString().toUtf8().data(); WriteItem(child, newPath, library, index.data(eDR_MODIFIED).toBool() || isParentModified); child = index.model()->index(++i, 0, index); } QStandardItem* item = m_layoutModel->itemFromIndex(index); if (item) { item->setData(false, eDR_MODIFIED); } } else { CATLControl* control = m_atlModel->GetControlByID(index.data(eDR_ID).toUInt()); if (control) { SLibraryScope& scope = library[control->GetScope()]; if (IsItemModified(index) || isParentModified) { scope.m_isDirty = true; QStandardItem* item = m_layoutModel->itemFromIndex(index); if (item) { item->setData(false, eDR_MODIFIED); } } WriteControlToXml(scope.m_nodes[control->GetType()], control, path); } } } } //-------------------------------------------------------------------------------------------// bool CAudioControlsWriter::IsItemModified(QModelIndex index) { if (index.data(eDR_MODIFIED).toBool() == true) { return true; } int i = 0; QModelIndex child = index.model()->index(i, 0, index); while (child.isValid()) { if (IsItemModified(child)) { return true; } child = index.model()->index(++i, 0, index); } return false; } //-------------------------------------------------------------------------------------------// void CAudioControlsWriter::WriteControlToXml(XmlNodeRef node, CATLControl* control, const AZStd::string_view path) { const EACEControlType type = control->GetType(); XmlNodeRef childNode = node->createNode(TypeToTag(type).data()); childNode->setAttr(Audio::ATLXmlTags::ATLNameAttribute, control->GetName().c_str()); if (!path.empty()) { childNode->setAttr("path", path.data()); } if (type == eACET_SWITCH) { const size_t size = control->ChildCount(); for (size_t i = 0; i < size; ++i) { WriteControlToXml(childNode, control->GetChild(i), ""); } } else if (type == eACET_PRELOAD) { if (control->IsAutoLoad()) { childNode->setAttr(Audio::ATLXmlTags::ATLTypeAttribute, Audio::ATLXmlTags::ATLDataLoadType); } // New Preloads XML... WriteConnectionsToXml(childNode, control); } else { WriteConnectionsToXml(childNode, control); } node->addChild(childNode); } //-------------------------------------------------------------------------------------------// void CAudioControlsWriter::WriteConnectionsToXml(XmlNodeRef node, CATLControl* control) { if (control && m_audioSystemImpl) { TXmlNodeList otherNodes = control->m_connectionNodes; auto end = AZStd::remove_if(otherNodes.begin(), otherNodes.end(), [](const SRawConnectionData& node) { return node.m_isValid; } ); otherNodes.erase(end, otherNodes.end()); for (auto& connectionNode : otherNodes) { node->addChild(connectionNode.m_xmlNode); } const size_t size = control->ConnectionCount(); for (size_t i = 0; i < size; ++i) { TConnectionPtr connection = control->GetConnectionAt(i); if (connection) { XmlNodeRef childNode = m_audioSystemImpl->CreateXMLNodeFromConnection(connection, control->GetType()); if (childNode) { node->addChild(childNode); control->m_connectionNodes.push_back(SRawConnectionData(childNode, true)); } } } } } //-------------------------------------------------------------------------------------------// void CAudioControlsWriter::CheckOutFile(const AZStd::string& filepath) { IEditor* editor = GetIEditor(); IFileUtil* fileUtil = editor ? editor->GetFileUtil() : nullptr; if (fileUtil) { fileUtil->CheckoutFile(filepath.c_str(), nullptr); } } //-------------------------------------------------------------------------------------------// void CAudioControlsWriter::DeleteLibraryFile(const AZStd::string& filepath) { IEditor* editor = GetIEditor(); IFileUtil* fileUtil = editor ? editor->GetFileUtil() : nullptr; if (fileUtil) { fileUtil->DeleteFromSourceControl(filepath.c_str(), nullptr); } } } // namespace AudioControls